1
$\begingroup$

TLDR: I can use the provided sample code to draw a texture using the GPU module, when I use exactly the code from the sample.

But, if I load the image when my modal operator is invoked, and then draw the image in the bpy.types.SpaceView3D.draw_handler that runs when the operator is running, then the image is all black. Finally, if I also include the sample code, then the image is properly drawn from both the origin sample and in the modal operator draw function.


So, here is the sample from the Blender documentation, slightly modified to import the icon I want:

from gpu_extras.batch import batch_for_shader
icon_path = Path(os.path.realpath(__file__)).parent / "icons" / ""
icon_name = "MYICON.png"

bpy.data.images.load(f"{icon_path}/{icon_name}", check_existing=True)
image = bpy.data.images[icon_name]
texture = gpu.texture.from_image(image)

def draw():
    bgl.glEnable(bgl.GL_BLEND)
    shader = gpu.shader.from_builtin('2D_IMAGE')
    batch = batch_for_shader(
        shader, 'TRI_FAN',
        {
            "pos": ((100, 100), (200, 100), (200, 200), (100, 200)),
            "texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)),
        },
    )
    shader.bind()
    shader.uniform_sampler("image", texture)
    batch.draw(shader)
    bgl.glDisable(bgl.GL_BLEND)

bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_PIXEL')

This sample works...

But I have to split the code a bit, I want to draw the image only when my modal operator is running. So, this function is ran when the operator is invoked and load the images:

def load_icons():
    global icons
    icon_path = Path(os.path.realpath(__file__)).parent / "icons" / ""
    icon_name = "MYICON.png"
    icons[icon_name] = gpu.texture.from_image(bpy.data.images.load(f"{icon_path}/{icon_name}", check_existing=True))

And I put this in the draw function added to the bpy.types.SpaceView3D.draw_handler_add that runs from the moment the operator is invoked.

global icons
shader_2d_image = gpu.shader.from_builtin('2D_IMAGE')
batch = batch_for_shader(
    shader_2d_image, 'TRI_FAN',
    {
        "pos": ((200, 200), (300, 200), (300, 300), (200, 300)),
        "texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)),
    },
)
shader_2d_image.bind()
shader_2d_image.uniform_sampler("image",icons["MYICON.png"])
batch.draw(shader_2d_image)

When I do that, only a black square is displayed. But, if I print the content of icons["MYICON.png"].read() in the draw function, it does show the right rgba values. So the GPU texture has the correct data.

And, if I include the sample mentionned above, then the image is properly rendered twice, once for the sample, and once for the draw function just above.

It looks like it fails to draw the icons if it is not loaded and rendered from the moment the module is loaded... I am a bit lost to be honest.

$\endgroup$

2 Answers 2

1
$\begingroup$
import bpy
import gpu
from gpu_extras.batch import batch_for_shader


def draw_image(image='MYICON'):
    ''' Draw an image.

    image (str, optional) - Image to draw.
    '''
    img = bpy.data.images.load(icons_dir() + image + '.png', check_existing=True)
    texture = gpu.texture.from_image(img)

    shader = gpu.shader.from_builtin('2D_IMAGE')
    batch = batch_for_shader(
        shader, 'TRI_FAN',
        {
            "pos": ((100, 100), (200, 100), (200, 200), (100, 200)),
            "texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)),
        },
    )
    shader.bind()
    shader.uniform_sampler("image", texture)
    batch.draw(shader)

    if image and img.gl_load():
        raise Exception()

    bpy.data.images.remove(img)
$\endgroup$
2
  • $\begingroup$ Thank you for this solution. It does work, but the first part of the code (Load icons from icons directory) is actually useless, the rest of the code works without it, and is simply not using the preview icons. It is also loading the images at every draw() call, which is really not optimal. There must be a way to cache the images and draw them later on. $\endgroup$
    – JulienH
    Commented Oct 28, 2022 at 13:10
  • $\begingroup$ After looking into it, the key here that make your code works and not mine is that you remove the image from bpy.data.images. If this line is removed, the code renders a black picture. I also tried replacing ` bpy.data.images.remove(img)` by img.reload() and it works too, but I would really like to avoid reloading the picture every frame. $\endgroup$
    – JulienH
    Commented Oct 28, 2022 at 13:42
0
$\begingroup$

I got the answer to my problem by iceythe on Blender Artist.

My problem actually came from part of the code that I did not even mention, stupidly thinking that simplifying the problem would be better.

My code was using bgl.glEnable(bgl.GL_BLEND) to use alphatest on the picture, I thought this would not impact the rest of the situation but I was wrong.

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.

Here a similar code to the original one above, that works as expected:

icons = {}
shader_2d_image = gpu.shader.from_builtin('2D_IMAGE')
def draw_image(image):
    gpu.state.blend_set("ALPHA")
    if image not in icons :
        texture_path = Path(os.path.dirname(__file__)) / "icons" / f'{image}.tif'
        img = bpy.data.images.load(str(texture_path), check_existing=True)
    icons[image] = 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", icons[image])
    batch.draw(shader_2d_image)

    gpu.state.blend_set("NONE")
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .