【问题标题】:VAO and element array buffer stateVAO 和元素数组缓冲区状态
【发布时间】:2012-02-16 22:10:27
【问题描述】:

我最近使用顶点数组对象 (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 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 应该如何工作的理解上的致命误解,要么是我的代码中的错误,要么是驱动程序错误。

Full source
Binaries (windows, 32 bit)

【问题讨论】:

  • 很有可能您的代码最初并未将元素缓冲区放入 VAO。你为什么不向我们展示你的 VAO 初始化代码。
  • 哦,拜托,没那么傻。另外,我说它适用于 GeForce 260 / 480。在编写 cmets 之前阅读帖子。我完全有能力调试我的 OpenGL 代码。这个问题是关于 OpenGL 实现和兼容性之间的差异。
  • 代码工作并不意味着它是正确的。无论是偶然情况还是其他情况,代码都可以正常工作。它在 NVIDIA 驱动程序上既失败又成功的事实表明用户错误。如果它在 NVIDIA 上运行而在 ATI 上失败,或者反之亦然,则更有可能是驱动程序错误。但 NVIDIA 尤其是自相似。因此,如果它有时可以在某些 NVIDIA 硬件上工作,有时不能,这听起来像是用户错误。

标签: opengl intel nvidia opengl-3 vertex-array


【解决方案1】:

一段时间后,我发现这实际上是我的坏事。配备移动 NVIDIA Quadro 4200 显卡的笔记本电脑已设置为默认情况下所有应用程序都将在 Intel 显卡上运行,即使笔记本电脑处于性能模式也是如此。我不明白为什么有人要这样做,因为那时任何应用程序都无法使用来自 OpenGL 的更强大的 GPU(仍然可以将它用于 OpenCL,因为有明确的设备选择,也可能用于DirectX - 这可以解释为什么有些游戏运行流畅)。

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

我很抱歉提出这个问题,因为不知道以上内容就无法给出好的答案。

【讨论】:

  • 您是否向英特尔提交了错误?这个故事有后续吗?作为一个新手图形程序员,知道世界上存在如此微妙的问题给我带来了深深的困扰。
  • @Philip 我没有考虑过,我几乎不认为英特尔是可用的 GPU,所以对我来说很容易说我不会支持英特尔 GPU。但也许这是个好主意。如果您这样做,请在评论中发布指向错误跟踪器的链接。谢谢。
  • 即使对您没有帮助,在 SO 上记录驱动程序错误对我也有帮助。
【解决方案2】:

我实际上认为 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 包含的整个状态是这三个表的内容,除了指出的例外情况。

扩展名是针对The OpenGL Graphics Specification version 2.1 (PDF) 编写的。因此,任何页码、章节标签或表格编号都是相对于该规范引用的。

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

  • ELEMENT_ARRAY_BUFFER_BINDING

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

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

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

  2. 用户错误。我知道您声称您的代码有效,因此很好。 每个人 都对某些代码提出了这样的要求。有很多次我会上下发誓,一些代码工作得很好。然而它被打破了;它只是碰巧过去了。它发生了。如果您发布您的代码,那么至少我们可以打折这种可能性。否则,我们只有你的话。考虑到人类在这方面犯错的频率,这并不值得。

【讨论】:

  • 很公平。我无法发布完整的代码,但我会尝试提供足以重现错误的最少代码。 ARB 的东西也很稳固,状态似乎确实存在。
  • 这里是源代码(在原始问题中编辑)。那么这是驱动程序错误还是我的错?
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 这实际上是不可能的,因为我将上下文创建为向前兼容,所以它不能默认为较低的。或者如果确实如此,那绝对不应该。
    • 我认为您可以运行 glGet 调用来查询上下文版本。找出答案。
    • @StevenLu 是的,有glGetString(GL_VERSION) 但这仅意味着一个错误(未缓存元素数组)只是由更糟糕的错误(创建旧的 2.1 上下文而不是转发)引起的- 兼容的)。
    【解决方案5】:

    我可以想象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。

    【讨论】:

    • 嗯,你描述的行为(元素数组未缓存)实际上是违反规范的,这样做没有什么意义。当您想绘制一些几何图形时,只有极少数情况下顶点和索引是可分离的。大多数情况下,模型是从艺术家创建的文件中加载的,一个模型的索引不适合任何其他模型的顶点。所以实际上同时绑定索引缓冲区和顶点属性指针是非常有意义的,至少在“图形应用程序,显示 3D 网格”场景中。
    猜你喜欢
    • 2012-12-24
    • 2020-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-16
    • 1970-01-01
    相关资源
    最近更新 更多