Image draw with GPU module renders black unless image is constantly reloaded

Hi here!

I am working on a free blender addon (QuickSnap), and I would like to draw some image in the viewport using the gpu module to give additional information to the user about the current state of the tool.

My plan was to load the image when the modal operator is activated, then draw it using bpy.types.SpaceView3D.draw_handler_add until the operator is terminated.
And I cannot manage to do it like that. If I do that, the image renders black. I ended up with the help of someone to get that code, that works:

icons = {}
shader_2d_image = gpu.shader.from_builtin('2D_IMAGE')
def draw_image(image):
    bgl.glEnable(bgl.GL_BLEND)
    if image not in bpy.data.images:
        texture_path = Path(os.path.dirname(__file__)) / "icons" / f'{image}.tif'
        img = bpy.data.images.load(str(texture_path), check_existing=True)
    texture = gpu.texture.from_image(img)
    batch = batch_for_shader(
        shader_2d_image, 'TRI_FAN',
        {
            "pos": ((100, 100), (200, 100), (200, 200), (100, 200)),
            "texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)),
        },
    )
    shader_2d_image.bind()
    shader_2d_image.uniform_sampler("image", texture)
    batch.draw(shader_2d_image)

    img.reload() #If this line is removed, the texture renders black.
    bgl.glDisable(bgl.GL_BLEND)

It is working, BUT as you can read, I need to reload the image at every gpu draw() call.
I can also remove the image from bpy.data.images and load it again… But I would like to avoid loading the image constantly, it sounds like such a waste.

I have also tried loading the image, then storing the result of gpu.texture.from_image(img) in a variable, and using that, but same result: the image is rendered all black.

The strange things is that if I print the read() of that gpu texture, it looks like it contains the right information, but it is not used in the batch.draw() call.

Don’t use bgl and replace your calls using

bgl.glEnable(bgl.GL_BLEND)
bgl.glDisable(bgl.GL_BLEND)

with

gpu.state.blend_set("ALPHA")
gpu.state.blend_set("NONE")

To elaborate a bit, Blender had a major rewrite to the opengl backend which meant that state changing api calls (like glEnable/glDisable) effectively caused Blender to no longer being able to do its own state bookkeeping and the opengl context would be invalidated.

The gpu.state.blend_set function is a drop-in replacement for bgl.glEnable(bgl.GL_BLEND), with some added functionality like specifying premultiplied alpha and common blend operations like invert, multiply, subtract and additive.

3 Likes

Oooh, great, thank you for the solution and the explanation! :smile:
I have been looking for a solution to that problem for quite some time.

Out of curiosity, I am replacing the other bgl calls I am using, I am guessing:
bgl.glEnable(bgl.GL_DEPTH_TEST) is replaced by gpu.state.depth_test_set('LESS')
bgl.glLineWidth(line_width) is replaced by gpu.state.line_width_set(line_width)
bgl.glPointSize(point_width) is replaced by gpu.state.point_size_set(point_width)

But I cannot find a replacement for bgl.glEnable(bgl.GL_LINE_SMOOTH), do you happen to know how to achieve that without using bgl?

1 Like

No, there’s no substitute for that or any other primitive smoothing with a simple call.

Modern opengl uses multi/supersampling framebuffers for full scene antialiasing, or custom glsl shader programs for selective antialiasing. Eevee/workbench uses the latter extensively.

The gpu.types.GPUShader class lets you build your own shader using two glsl source code as strings, the vertex and fragment shader programs.

1 Like