Long story short, I’m planning to make a custom UV viewer/editor with limited functionality (no mesh manipulation etc. just colors) for a larger addon in the works. This portion of the addon should only receives UV data from an array of objects and display it in dearpygui window.
The only thing I’m wondering, as the title suggests, if it’s possible to have both the gpu module and dearpygui portion of the code running on their own shared thread separate from the main-thread that Blender uses? Because AI’s telling me that the GPU or GL context can only exist on the main thread (where Blender runs) but when I read the documentation I’m getting the understanding that that’s not true at all, but I could be wrong.
For now I’m using gpu.types.GPUOffScreen for creating buffers necessary for rendering and then writing the rendered image into a buffer object applicable to be sent to dearypygui but I believe that this part is done on the CPU side, which is CPU intensive? Is there a way to skip this step and have a direct communication, or in another words, transfer of image by the FBO to dearpygui?
Or better yet, has anyone done anything similar? Can you give me any pointers how this is all properly done?
Here’s the code that runs on the main thread, am wondering if it’s possible to make it work on a separate thread. I realize that my writing might not be most coherent and sensical and that’s probably due to my lack of knowledge on the rendering subject as well as mutl-threading.
import gpu
import numpy as np
from mathutils import Matrix
from gpu_extras.presets import draw_circle_2d
import dearpygui.dearpygui as dpg
from gpu.types import Buffer
def render_offscreen_to_numpy(w=512, h=512):
offscreen = gpu.types.GPUOffScreen(w, h)
with offscreen.bind():
# Draw into the full off‑screen buffer
gpu.state.viewport_set(0, 0, w, h)
fb = gpu.state.active_framebuffer_get()
fb.clear(color=(0.0, 0.0, 0.0, 0.0))
with gpu.matrix.push_pop():
gpu.matrix.load_matrix(Matrix.Identity(4))
gpu.matrix.load_projection_matrix(Matrix.Identity(4))
amount = 10
for i in range(-amount, amount + 1):
x = i / amount
draw_circle_2d((x, 0.0), (1, 1, 1, 1), 0.5, segments=128)
# Read back as 8‑bit unsigned bytes into a GPUBuffer
buf = Buffer('UBYTE', w * h * 4)
fb.read_color(0, 0, w, h, 4, 0, 'UBYTE', data=buf)
offscreen.free()
# Convert buffer → NumPy array
arr = np.fromiter(buf, count=w * h * 4, dtype=np.uint8)
arr = arr.reshape((h, w, 4))
# Flip vertically for Dear PyGui (top‑left origin)
return np.flip(arr, axis=0)
def show_in_dearpygui(img: np.ndarray):
h, w, _ = img.shape
dpg.create_context()
with dpg.texture_registry(show=False):
tex = dpg.add_static_texture(w, h, img.flatten() / 255.0)
with dpg.window(label="Off‑Screen → DPG", width=w + 20, height=h + 40):
dpg.add_image(tex)
dpg.create_viewport(title="Off‑Screen Render Preview", width=w + 40, height=h + 80)
dpg.setup_dearpygui()
dpg.show_viewport()
while dpg.is_dearpygui_running():
dpg.render_dearpygui_frame()
dpg.destroy_context()
def main():
img = render_offscreen_to_numpy(512, 512)
show_in_dearpygui(img)
if __name__ == "__main__":
main()