Why does texture index 31 is overridden in moderngl?

242 Views Asked by At

I am writing a simple app that loads an image and displays it on the screen such that the left and right half are rendered separately.

import glm
import moderngl_window
import numpy as np
from PIL import Image


class BugExample(moderngl_window.WindowConfig):
    LEFT_TEXTURE_IDX = 0 
    RIGHT_TEXTURE_IDX = 1 

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        image = Image.open("test.jpg").transpose(Image.FLIP_TOP_BOTTOM)
        w, h = image.size
        w_even = 2 * (w // 2)
        left = image.crop((0, 0, w_even // 2, h))
        right = image.crop((w_even // 2, 0, w_even, h))
        self.texture_left = self.ctx.texture(left.size, 3, left.tobytes())
        self.texture_left.use(self.LEFT_TEXTURE_IDX)
        self.texture_right = self.ctx.texture(right.size, 3, right.tobytes())
        self.texture_right.use(self.RIGHT_TEXTURE_IDX)

        self.program = self.ctx.program(
            vertex_shader="""
                    #version 330
                    in vec2 in_position;
                    uniform mat4 model;
                    out vec2 uv0;
                    void main() {
                        gl_Position = model * vec4(in_position, 0.0, 1.0);
                        uv0 = (0.5 * in_position) + vec2(0.5);
                    }
                    """,
            fragment_shader="""
                    #version 330
                    out vec4 fragColor;
                    uniform sampler2D texture_idx;
                    in vec2 uv0;
                    void main() {
                        fragColor = texture(texture_idx, uv0);
                    }
                    """)
        self.left_scale_mat = glm.scale(glm.mat4(), glm.vec3(0.5, 1.0, 1.0))
        self.left_translate_mat = glm.translate(glm.mat4(), glm.vec3(-0.5, 0.0, 0.0))
        self.left_model_mat = self.left_translate_mat * self.left_scale_mat

        self.right_scale_mat = glm.scale(glm.mat4(), glm.vec3(0.5, 1.0, 1.0))
        self.right_translate_mat = glm.translate(glm.mat4(), glm.vec3(0.5, 0.0, 0.0))
        self.right_model_mat = self.right_translate_mat * self.right_scale_mat

        vertices = np.array([-1.0, 1.0, -1.0, -1.0, 1.0, -1.0,
                             -1.0, 1.0, 1.0, -1.0, 1.0, 1.0], dtype='f4')
        self.vbo = self.ctx.buffer(vertices)
        self.vao = self.ctx.simple_vertex_array(self.program, self.vbo, 'in_position')

    def render(self, time, frame_time):
        self.ctx.clear(1.0, 1.0, 1.0)

        self.program["model"].write(bytes(self.left_model_mat))
        self.program["texture_idx"].value = self.LEFT_TEXTURE_IDX
        self.vao.render()

        self.program["model"].write(bytes(self.right_model_mat))
        self.program["texture_idx"].value = self.RIGHT_TEXTURE_IDX
        self.vao.render()


if __name__ == '__main__':
    moderngl_window.run_window_config(BugExample, args=('--window', 'glfw'))

Running this program will open a window with your image test.jpg.

BUT something strange is happening at texture index 31:

If you change the indices such that the texture loaded first (the left texture in our case, as is described in the render method) has index 31, it will be overridden by the other texture, and you will see the right half repeated twice.

example bug

I should point out that if I had only one texture, and not two, and that texture had an index of 31, there would have been no problem. The problem only arises when there is one texture with index 31 and another texture that is loaded after the 31 texture.

[EDIT: I should also point out that I have no problem loading more than 32 textures. If I was to separate my image into 32 tiles or more (instead of the 2 tiles in the example above), and even 64 tiles or more, the only problem will be with texture index 31 that will be overridden by the last texture loaded.]

I have a vague guess this has something to do with the way the number 31 is manipulated as an int? (like here)

So, finally, my question is - what is happening here?? Am I missing something larger that is happening, or is it just a fact of life and I should avoid texture index 31 and forget about it?

1

There are 1 best solutions below

1
On BEST ANSWER

The issue seems to be related to the internal implementation of Context.texture.

When a texture is loaded to the GPU, a texture object is generated and bound to a target and texture unit (except DSA). ModernGL seems to use texture unit 31 internally for this task.

I'm very sure about that, because when I print the active texture unit after self.ctx.texture(left.size, 3, left.tobytes()),

from OpenGL.GL import *
print(glGetInteger(GL_ACTIVE_TEXTURE) - GL_TEXTURE0)

the output is 31, independent on the values of LEFT_TEXTURE_IDX and RIGHT_TEXTURE_IDX.


Explanation:

When you use the texture units 31 and 32:

LEFT_TEXTURE_IDX = 0 
RIGHT_TEXTURE_IDX = 1 

textur_left is loaded internally using texture unit 31:

self.texture_left = self.ctx.texture(left.size, 3, left.tobytes())

textue_left is explicitly bound to texture unit 31:

self.texture_left.use(self.LEFT_TEXTURE_IDX)

texture_right is loaded internally using texture unit 31. This breaks the binding of the textue_left to the texture unit 31:

self.texture_right = self.ctx.texture(right.size, 3, right.tobytes())

texture_right is explicitly bound to texture unit 32:

self.texture_right.use(self.RIGHT_TEXTURE_IDX)

Finally texture_right is bound to the texture units 31 and 32.

Change the order of the instructions to solve the issue. First load both textures and then assign them to texture units:

self.texture_left = self.ctx.texture(left.size, 3, left.tobytes())
self.texture_right = self.ctx.texture(right.size, 3, right.tobytes())
self.texture_left.use(self.LEFT_TEXTURE_IDX)
self.texture_right.use(self.RIGHT_TEXTURE_IDX)