GLTexImage2D doesn't seem to update

I’m adding viewport rendering to my custom renderer add-on, and I’m running into a problem where the color rendered in my viewport isn’t changing when my pixel buffer is updated. Here’s what I know so far:

  • I’m basing my implementation on Blender’s example CustomDrawData class (https://docs.blender.org/api/current/bpy.types.RenderEngine.html)
  • I’m initiating the class with a self.buffer variable that holds a list of pixel color values, just like the example (although the example calls it self.pixels). I start with a solid red color ( [1.0, 0.0, 0.0, 1.0] ), and it displays in the viewport perfectly fine.
  • After the red box appears I flood the buffer with 1’s to turn it white ( [1.0, 1.0, 1.0, 1.0] ). I can see that my self.buffer attribute is updated correctly, but the function I call to update the glTexImage2d object doesn’t seem to do anything, because the color stays red and never turns white.
  • I am calling FrameBuffer.build_gl_texture() (see below) and engine.tag_redraw() after updating my buffer values to refresh the texture in OpenGL.

Here is the code to my custom class so far. I’m not super familiar with OpenGL in general - is there something obvious that I’m missing that I need to do to get the glTexImage2d object to properly register the new pixel values? I’m not getting any errors in the console, so I have very little info to go on outside of the docs and the code I have.

class FrameBuffer:
    def __init__(self):
        # This is hard-coded for now, will change later
        self.width, self.height = 512, 512

        # For debugging purposes, I'm starting with a solid red color
        # so I KNOW when the color changes
        pixels = [1.0, 0.0, 0.0, 1.0] * self.width * self.height
        self.buffer = bgl.Buffer(bgl.GL_FLOAT, self.width * self.height * 4, pixels)

        self.init_opengl()

    def init_opengl(self):
        self.texture = bgl.Buffer(bgl.GL_INT, 1)
        bgl.glGenTextures(1, self.texture)

        self.build_gl_texture()

        shader_program = bgl.Buffer(bgl.GL_INT, 1)
        bgl.glGetIntegerv(bgl.GL_CURRENT_PROGRAM, shader_program)

        self.vertex_array = bgl.Buffer(bgl.GL_INT, 1)
        bgl.glGenVertexArrays(1, self.vertex_array)
        bgl.glBindVertexArray(self.vertex_array[0])

        texturecoord_location = bgl.glGetAttribLocation(shader_program[0], "texCoord")
        position_location = bgl.glGetAttribLocation(shader_program[0], "pos")

        bgl.glEnableVertexAttribArray(texturecoord_location)
        bgl.glEnableVertexAttribArray(position_location)

        position = [0.0, 0.0, self.width, 0.0, self.width, self.height, 0.0, self.height]
        position = bgl.Buffer(bgl.GL_FLOAT, len(position), position)
        texcoord = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]
        texcoord = bgl.Buffer(bgl.GL_FLOAT, len(texcoord), texcoord)

        self.vertex_buffer = bgl.Buffer(bgl.GL_INT, 2)

        bgl.glGenBuffers(2, self.vertex_buffer)
        bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[0])
        bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, position, bgl.GL_STATIC_DRAW)
        bgl.glVertexAttribPointer(position_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None)

        bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[1])
        bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, texcoord, bgl.GL_STATIC_DRAW)
        bgl.glVertexAttribPointer(texturecoord_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None)

        bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, 0)
        bgl.glBindVertexArray(0)

    # This gets called every time I update self.buffer to refresh the OpenGL texture with the new values
    def build_gl_texture(self):
        bgl.glActiveTexture(bgl.GL_TEXTURE0)
        bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0])
        bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_RGBA16F, self.width, self.height, 0, bgl.GL_RGBA, bgl.GL_FLOAT, self.buffer)
        bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
        bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
        bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)

    def __del__(self):
        bgl.glDeleteBuffers(2, self.vertex_buffer)
        bgl.glDeleteVertexArrays(1, self.vertex_array)
        bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
        bgl.glDeleteTextures(1, self.texture)

    def draw(self):
        bgl.glActiveTexture(bgl.GL_TEXTURE0)
        bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0])
        bgl.glBindVertexArray(self.vertex_array[0])
        bgl.glDrawArrays(bgl.GL_TRIANGLE_FAN, 0, 4)
        bgl.glBindVertexArray(0)
        bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)

Just seeing a similarly appropriate thread about the BGL module here - should I be using the GPU module for this instead?