VAO和元素数组缓冲状态



我最近用顶点数组对象(VAO)编写了一些OpenGL 3.3代码,后来在英特尔图形适配器上进行了测试,令我失望的是,我发现元素数组缓冲区绑定显然不是VAO状态的一部分,因为调用:

glBindVertexArray(my_vao);
glDrawElements(GL_TRIANGLE_STRIP, count, GL_UNSIGNED_INTEGER, 0);

没有效果,而:

glBindVertexArray(my_vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, my_index_buffer); // ?
glDrawElements(GL_TRIANGLE_STRIP, count, GL_UNSIGNED_INTEGER, 0);

渲染几何体。我认为这只是OpenGL的英特尔实现中的一个错误(因为它在GL_ARB_vertex_array_object(甚至在GL_OES_vertex_array_object)中明确表示元素数组是保存状态的部分),但后来它发生在移动NVIDIA Quadro 4200上。这不好玩。

是驱动程序错误、规范错误还是我代码中的某个错误?该代码在GeForce 260和480上完美运行。

有人有类似的经历吗?

同样奇怪的是,GL_EXT_direct_state_access没有将元素数组缓冲区绑定到VAO的函数(但它有指定顶点属性数组的函数,因此也有指定数组缓冲区的函数)。GPU制造商是不是搞砸了规格,欺骗了我们?

编辑

我最初不打算显示任何源代码,因为我认为这里没有必要。但根据要求,这里是再现问题的最小测试用例:

static GLuint n_vertex_buffer_object, p_index_buffer_object_list[3];
static GLuint p_vao[2];
bool InitGLObjects()
{
    const float p_quad_verts_colors[] = {
        1, 0, 0, -1, 1, 0,
        1, 0, 0, 1, 1, 0,
        1, 0, 0, 1, -1, 0,
        1, 0, 0, -1, -1, 0, // red quad
        0, 0, 1, -1, 1, 0,
        0, 0, 1, 1, 1, 0,
        0, 0, 1, 1, -1, 0,
        0, 0, 1, -1, -1, 0, // blue quad
        0, 0, 0, -1, 1, 0,
        0, 0, 0, 1, 1, 0,
        0, 0, 0, 1, -1, 0,
        0, 0, 0, -1, -1, 0 // black quad
    };
    const unsigned int p_quad_indices[][6] = {
        {0, 1, 2, 0, 2, 3},
        {4, 5, 6, 4, 6, 7},
        {8, 9, 10, 8, 10, 11}
    };
    glGenBuffers(1, &n_vertex_buffer_object);
    glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
    glBufferData(GL_ARRAY_BUFFER, sizeof(p_quad_verts_colors), p_quad_verts_colors, GL_STATIC_DRAW);
    glGenBuffers(3, p_index_buffer_object_list);
    for(int n = 0; n < 3; ++ n) {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[n]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(p_quad_indices[n]), p_quad_indices[n], GL_STATIC_DRAW);
    }
    glGenVertexArrays(2, p_vao);
    glBindVertexArray(p_vao[0]);
    {
        glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(0));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(3 * sizeof(float)));
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[0]); // red
    }
    glBindVertexArray(0);
    glBindVertexArray(p_vao[1]);
    {
        glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(0));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(3 * sizeof(float)));
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[1]); // blue
    }
    glBindVertexArray(0);
#ifdef BIND_BLACK_QUAD_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[2]);
    // bind the buffer with the black quad (not inside VAO, should NOT be seen)
#endif // BIND_BLACK_QUAD_ELEMENT_ARRAY_BUFFER
    // [compile shaders here]
    return true; // success
}

上面的代码创建了一个顶点缓冲区,包含三个四边形,红色一个,蓝色一个和黑色一个。然后创建三个索引缓冲区,这些缓冲区指向各个四边形。然后创建并设置两个VAO,一个应包含红色四元索引,另一个应包括蓝色四元索引。根本不应渲染黑色四边形(假设定义了BIND_black_quad_ELEMENT_ARRAY_BUFFER)。

void onDraw()
{
    glClearColor(.5f, .5f, .5f, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);
    glUseProgram(n_program_object);
    static int n_last_color = -1;
    int n_color = (clock() / 2000) % 2;
    if(n_last_color != n_color) {
        printf("now drawing %s quadn", (n_color)? "blue" : "red");
        n_last_color = n_color;
    }
    glBindVertexArray(p_vao[n_color]);
#ifdef VAO_DOESNT_STORE_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[n_color]); // fixes the problem
#endif // VAO_DOESNT_STORE_ELEMENT_ARRAY_BUFFER
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}

这会将视口清除为灰色,并以重复的方式渲染蓝色或红色四边形(还会打印哪一个)。虽然这在桌面GPU上有效,但在笔记本GPU上不起作用(除非定义了VAO_DOESNT_STORE_ELEMENT_ARRAY_BUFFER宏,否则会渲染黑色四边形。取消定义BIND_LACK_quad_ELEMENT_ARRAY_BUFFER宏会使四边形变为蓝色,因为蓝色索引缓冲区最后绑定。但无论如何,它都不会渲染红色四边形。

因此,在我看来,这要么是我对VAO应该如何工作的理解中的致命误解,要么是我代码中的错误,要么是驱动程序错误。

完整来源
二进制文件(windows,32位)

过了一段时间,我发现这实际上是我的问题。配备移动NVIDIA Quadro 4200显卡的笔记本电脑经过设置,即使笔记本电脑处于性能模式,默认情况下所有应用程序都会在英特尔显卡上运行。我不明白为什么会有人想这么做,因为当时任何应用程序都无法使用OpenGL中更强大的GPU(仍然可以将其用于OpenCL,因为有明确的设备选择,也可能用于DirectX——这可以解释为什么一些游戏运行平稳)。

尽管如此,所描述的错误行为只是英特尔驱动程序中的一个错误,仅此而已。英特尔驱动程序不会保存ELEMENT_ARRAY_BUFFER_BINDING。那里

我真的很抱歉问这个问题,因为在不了解以上情况的情况下,没有办法给出一个好的答案。

实际上,我认为ARB-VAO缺少元素数组缓冲区绑定(或任何其他缓冲区绑定)状态。

不需要信念;规范告诉了事实。

来自ARB_vertex_array_object规范:

命令

void GenVertexArrays(sizei n, uint *arrays);

返回中以前未使用的顶点数组对象名称。这些名称标记为已使用,仅用于GenVertexArrays,并使用表6.6(CLIENT_ACTIVE_TEXTURE选择器状态除外)、6.7和6.8(ARRAY_BUFFER_BINDING状态除外)中列出的状态进行初始化。

因此,我们有了它:VAO包含的整个状态是这三个表的内容,除了注意到的例外。

该扩展是根据OpenGL图形规范2.1版(PDF)编写的。因此,任何页码、章节标签或表格编号都是相对于该规范参考的

我不打算抄这三张表。但是,如果您查看第273页(按规范的页数)/第287页(按物理页数),您会发现表6.8。那张桌子上有以下内容:

  • ELEMENT_ARRAY_BUFFER_BINDING

这里没有歧义。信息可能没有明确说明。但毫无疑问,它就在那里。ELEMENT_ARRAY_BUFFER_BINDING是VAO状态的一部分。

因此,您的问题可能来自两个来源之一:

  1. 驱动程序错误。正如我在评论中所说,驱动程序错误似乎不太可能出现。并非不可能,只是不太可能。NVIDIA的驱动程序在不同的硬件上非常相似,VAO在硬件中几乎没有镜像。除非您使用不同版本的驱动程序,否则几乎没有理由认为错误是由驱动程序错误引起的。

  2. 用户错误。我知道你声称你的代码是有效的,因此它很好Everyone都对某些代码做出了这样的声明。有很多次,我会上下发誓,有些代码运行得很好。然而它被打破了;它只是碰巧通过了。它发生了。如果你发布你的代码,那么至少我们可以降低这种可能性。否则,我们只有你的保证。考虑到人类在这方面经常犯错,这并没有什么价值。

同样奇怪的是,GL_EXT_direct_state_access没有将元素数组缓冲区绑定到VAO 的函数

对于目前偶然发现这一点并至少以OpenGL 4.5为目标的人来说,现在有一个函数可以直接将元素数组缓冲区绑定到VAO,而无需绑定VAO:glVertexArrayElementBuffer,使用它可能会解决任何让你挖掘这个问题的问题。

可能的原因是您的Intel适配器无法提供OpenGL 3.3上下文,而是默认为2.1或类似版本。正如其他人所指出的,在OpenGL的早期版本中,元素数组缓冲区状态不是VAO的一部分。

我可以想象ELEMENT缓冲区没有被缓存;如果你这样做:

glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(0));

这就像在说:

gBoundBuffer_GL_ARRAY_BUFFER=n_vertex_buffer_object;
currentVAO->enable|=(1<<0);
currentVAO->vertexBuffer=IndexToPointer(gBoundBuffer_GL_ARRAY_BUFFER);

换句话说,glBindBuffer()除了设置GL_ARRAY_BUFFER的值之外什么都不做。在glVertexAttribPointer()中修改VAO。

所以当你这样做的时候:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[0]);
glBindVertexArray(0);

你真的做到了:

gBoundBuffer_GL_ELEMENT_ARRAY_BUFFER=p_index_buffer_object_list[0];
currentVAO=0;

其中GL_ELEMENT_ARRAY_BUFFER绑定没有做任何事情是有意义的。我不确定元素是否有类似glVertexAttribPointer()的变体(比如glElementPointer()),它实际上会作用于VAO。

相关内容

  • 没有找到相关文章

最新更新