最近我在写OpenGL 3.3的代码,使用了顶点数组对象(VAO),后来在英特尔显卡上测试时发现,令我失望的是,元素数组缓冲区绑定显然不是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);
渲染出几何形状。我认为这只是Intel实现的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 quad\n", (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_BLACK_QUAD_ELEMENT_ARRAY_BUFFER宏可以使四边形变为蓝色,因为最后绑定了蓝色索引缓冲区。但是无论如何都不会呈现红色四边形。
所以我认为,要么是我对VAO工作原理的理解存在严重误解,要么是我的代码有bug,要么是一个驱动程序的bug。