为什么纹理索引 31 在 moderngl 中被覆盖?

Why does texture index 31 is overridden in moderngl?

我正在编写一个简单的应用程序,它加载图像并将其显示在屏幕上,以便分别呈现左半部分和右半部分。

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'))

运行 此程序将使用您的图像 test.jpg 打开一个 window。

但是 纹理索引 31 处发生了一些奇怪的事情:

如果您更改索引,使 first 加载的纹理(在我们的例子中是左侧纹理,如 render 方法中所述)具有索引 31,它将被 other 纹理覆盖,您会看到右半部分重复两次。

我应该指出,如果我只有一个纹理,而不是两个,并且该纹理的索引为 31,就不会有问题。仅当存在索引为 31 的纹理和在 31 纹理 之后 加载另一纹理时才会出现问题。

[编辑:我还应该指出,加载 超过 32 个纹理没有问题。如果我要将我的图像分成 32 个或更多的图块(而不是上面示例中的 2 个图块),甚至 64 个图块或更多,唯一的问题是纹理索引 31 将被覆盖上次 纹理加载。]

我有一个模糊的猜测,这与数字 31 作为 int 进行操作的方式有关? (喜欢 here

所以,最后,我的问题是 - 这里发生了什么??我是否错过了正在发生的更大的事情,或者这只是生活中的一个事实,我应该避免纹理索引 31 而忘记它?

这个问题似乎与 Context.texture 的内部实现有关。

当纹理加载到 GPU 时,会生成纹理对象并将其绑定到目标和纹理单元(DSA 除外)。 ModernGL 似乎在内部使用纹理单元 31 来完成此任务。

我对此非常确定,因为当我在 self.ctx.texture(left.size, 3, left.tobytes())

之后打印活动纹理单元时
from OpenGL.GL import *
print(glGetInteger(GL_ACTIVE_TEXTURE) - GL_TEXTURE0)

输出为 31,与 LEFT_TEXTURE_IDXRIGHT_TEXTURE_IDX 的值无关。


解释:

当您使用纹理单元 31 和 32 时:

LEFT_TEXTURE_IDX = 0 
RIGHT_TEXTURE_IDX = 1 

textur_left 使用纹理单元 31:

在内部加载
self.texture_left = self.ctx.texture(left.size, 3, left.tobytes())

textue_left 显式绑定到纹理单元 31:

self.texture_left.use(self.LEFT_TEXTURE_IDX)

texture_right 使用纹理单元 31 在内部加载。这打破了 textue_left 到纹理单元 31 的绑定:

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

texture_right 显式绑定到纹理单元 32:

self.texture_right.use(self.RIGHT_TEXTURE_IDX)

最终texture_right绑定到纹理单元31和32。

更改说明的顺序以解决问题。首先加载两个纹理,然后将它们分配给纹理单元:

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)