OpenGL中绑定点的目的?

Purpose of binding points in OpenGL?

我不明白OpenGL中绑定点(例如GL_ARRAY_BUFFER)的用途是什么。据我了解,glGenBuffers() 创建了一种指向位于 GPU 内存中某处的顶点缓冲区对象的指针。

所以:

glGenBuffers(1, &bufferID)

意味着我现在有一个句柄,bufferID,到显卡上的 1 个顶点对象。现在我知道下一步是将 bufferID 绑定到绑定点

glBindBuffer(GL_ARRAY_BUFFER, bufferID)

这样我就可以使用该绑定点使用 glBufferData() 函数向下发送数据,如下所示:

glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW)

但为什么我不能只使用 bufferID 来指定我想将数据发送到哪里呢?类似于:

glBufferData(bufferID, sizeof(data), data, GL_STATIC_DRAW)

然后,在调用绘制函数时,我也只需将哪个 ID 放入我希望绘制函数绘制的任何 VBO。类似于:

glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)

为什么我们需要 glBindBuffers 的额外间接步骤?

OpenGL 将对象绑定点用于两件事:指定要用作渲染过程的一部分的对象,以及能够修改对象。

前者使用它们的原因很简单:OpenGL 需要大量对象才能渲染。

考虑一下您过于简单的示例:

glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)

API 不允许我有来自单独缓冲区的单独顶点属性。当然,您可能会提议 glDrawArrays(GLint count, GLuint *object_array, ...)。但是如何将特定的缓冲区对象连接到特定的顶点属性呢?或者你如何让 2 个属性来自缓冲区 0 而第三个属性来自缓冲区 1?这些是我现在可以用当前 API 做的事情。但是你提议的无法处理。

即便如此,您还需要渲染 许多 其他对象:program/pipeline 对象、纹理对象、UBO、SSBO、变换反馈对象、查询对象等。在单个命令中指定所有需要的对象从根本上说是行不通的(并且撇开性能成本不谈)。

并且每次 API 需要添加一种新的对象时,您都必须添加 glDraw* 函数的新变体。而现在,有 over a dozen such functions。你的方式会给我们带来 数百.

因此,OpenGL 定义了让您说 "the next time I render, use this object in this way for that process." 这就是绑定对象以供使用的方式。


But why couldn't I just use the bufferID to specifiy where I want to send the data instead?

这是为了修改对象而绑定一个对象,并不是说会用到。那是……另当别论。

显而易见的答案是,"You can't do it because the OpenGL API (until 4.5) doesn't have a function to let you do it." 但我更怀疑这个问题真的是为什么 OpenGL 没有这样的 APIs(直到 4.5,其中存在 glNamedBufferStorage 等) .

确实,4.5 确实具有此类功能的事实证明,4.5 之前的 OpenGL 的绑定对象修改 API 没有 技术 原因。这确实是一个 "decision" 由 OpenGL API 从 1.0 演变而来的,这要归功于遵循阻力最小的路径。反复.

的确,几乎 OpenGL 做出的每一个错误决定都可以追溯到 API 中选择阻力最小的路径。但是我跑题了。

在OpenGL 1.0中,只有一种对象:显示列表对象。这意味着 甚至纹理 都没有存储在对象中。所以每次切换纹理时,都必须用 glTexImage*D 重新指定整个纹理。这意味着重新上传它。现在,您可以(并且人们确实)将每个纹理的创建包装在一个显示列表中,这允许您通过执行该显示列表来切换纹理。希望驱动程序会意识到您正在这样做,而是适当地分配视频内存等。

所以当 1.1 出现时,OpenGL ARB 意识到这是多么愚蠢。因此他们创建了纹理对象,它封装了纹理的内存存储和其中的各种状态。当你想使用纹理时,你就绑定它。但是有一个障碍。即,如何改变它。

看,1.0 有一堆已经存在的功能,如 glTexImage*DglTexParamter 等。这些修改纹理的状态。现在,ARB 可以添加新函数来做同样的事情,但将纹理对象作为参数。

但这意味着将所有 OpenGL 用户分成两个阵营:使用纹理对象的和不使用纹理对象的。这意味着,如果您想使用纹理对象,则必须重写 所有 修改纹理的现有代码。如果您有一些函数对当前纹理进行了一系列 glTexParameter 调用,则必须更改该函数以调用新的纹理对象函数。但是您 必须更改调用它的函数,以便它可以将它所操作的纹理对象作为参数。

如果那个函数不属于你(因为它是你正在使用的库的一部分),那么你甚至不能做 that.

因此 ARB 决定保留那些旧函数,并根据纹理是否绑定到上下文简单地让它们表现不同。如果一个被绑定,那么 glTexParameter/etc 将修改绑定的纹理,而不是上下文的正常纹理。

这一决定成立the general paradigm shared by almost all OpenGL objects.

ARB_vertex_buffer_object 出于同样的原因使用此范例。请注意各种 gl*Pointer 函数(glVertexAttribPointer 等)如何与缓冲区相关联。您必须将缓冲区绑定到 GL_ARRAY_BUFFER,然后调用其中一个函数来设置属性数组。当缓冲区绑定到该槽时,该函数将拾取它并将指针视为在调用 *Pointer 函数时绑定的缓冲区中的偏移量。

为什么?出于同样的原因:易于兼容(或促进懒惰,取决于您希望如何看待它)。 ATI_vertex_array_object 必须为 gl*Pointer 函数创建新的类似物。而 ARB_vertex_buffer_object 只是搭载了现有的入口点。

用户不必从使用 glVertexPointer 更改为 glVertexBufferOffset 或其他一些功能。他们所要做的就是在调用设置顶点信息的函数之前绑定一个缓冲区(当然还要将指针更改为字节偏移量)。

这也意味着他们不必添加一堆 glDrawElementsWithBuffer 类型的函数来使用来自缓冲区对象的索引进行渲染。

所以这在短期内不是一个坏主意。但与大多数短期决策一样,随着时间的推移,它开始变得不那么合理了。

当然,如果您可以访问 GL 4.5/ARB_direct_state_access,您可以按照原来的方式做事。