OpenGL中绑定点的目的?
Posted
技术标签:
【中文标题】OpenGL中绑定点的目的?【英文标题】:Purpose of binding points in OpenGL? 【发布时间】:2016-10-07 16:23:35 【问题描述】:我不明白 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
进行额外的间接步骤?
【问题讨论】:
"意味着我现在有一个句柄,bufferID,到 1 个顶点对象"到一个 缓冲区对象。没有“顶点对象”这样的东西。 "为什么我不能只使用 bufferID 来指定我想将数据发送到哪里?"...ARB_direct_state_access
?
【参考方案1】:
OpenGL 将对象绑定点用于两件事:指定要用作渲染过程的一部分的对象,以及能够修改该对象。
为什么将它们用于前者很简单:OpenGL 需要大量对象才能进行渲染。
考虑您过于简单的示例:
glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)
该 API 不允许我将单独的顶点属性来自单独的缓冲区。当然,你可能会建议glDrawArrays(GLint count, GLuint *object_array, ...)
。但是如何将特定的缓冲区对象连接到特定的顶点属性呢?或者你怎么有来自缓冲区 0 的 2 个属性和来自缓冲区 1 的第三个属性?这些是我现在可以使用当前 API 做的事情。但是你提议的那个处理不了。
即便如此,您也需要将 许多 其他需要渲染的对象放在一边:程序/管道对象、纹理对象、UBO、SSBO、变换反馈对象、查询对象等。在单个命令中指定所需的对象从根本上是不可行的(并且撇开性能成本不谈)。
每次 API 需要添加一种新类型的对象时,您都必须添加 glDraw*
函数的新变体。现在,有over a dozen such functions。你的方式会给我们数百。
因此,OpenGL 为您定义了“下次渲染时,以这种方式在该过程中使用此对象”的方式。这就是绑定对象以供使用的意义。
但是为什么我不能只使用 bufferID 来指定我想将数据发送到哪里呢?
这是为了修改对象而绑定一个对象,而不是说它会被使用。那是……另一回事。
显而易见的答案是,“你不能这样做,因为 OpenGL API(直到 4.5)没有让你这样做的功能。”但我宁愿怀疑问题是为什么 OpenGL 没有这样的 API(直到 4.5,glNamedBufferStorage
等存在)。
确实,4.5 确实具有这样的功能这一事实证明,4.5 之前的 OpenGL 的 bind-object-to-modify API 没有技术原因。这确实是 OpenGL API 从 1.0 演变而来的“决定”,这要归功于遵循阻力最小的路径。反复。
确实,OpenGL 所做的几乎每一个错误决定都可以追溯到采用 API 中阻力最小的路径。但我离题了。
在 OpenGL 1.0 中,只有一种对象:显示列表对象。这意味着 甚至纹理 都没有存储在对象中。因此,每次切换纹理时,都必须使用glTexImage*D
重新指定整个纹理。这意味着重新上传它。现在,您可以(并且人们确实)将每个纹理的创建包装在一个显示列表中,这允许您通过执行该显示列表来切换纹理。并且希望驱动程序会意识到您正在这样做,而是适当地分配视频内存等等。
所以当 1.1 出现时,OpenGL ARB 意识到这是多么愚蠢。所以他们创建了纹理对象,它封装了纹理的内存存储和其中的各种状态。当你想使用纹理时,你绑定它。但是有一个障碍。即,如何改变它。
你看,1.0 有一堆已经存在的函数,比如glTexImage*D
、glTexParamter
等等。这些修改纹理的状态。现在,ARB 可以添加新的函数来做同样的事情,但将纹理对象作为参数。
但这意味着将所有 OpenGL 用户分为两个阵营:使用纹理对象的阵营和不使用纹理对象的阵营。这意味着,如果您想使用纹理对象,则必须重写 所有 修改纹理的现有代码。如果您有某个函数对当前纹理进行了一堆glTexParameter
调用,则必须更改该函数以调用新的纹理对象函数。但是您也必须更改调用它的函数,以便它将操作的纹理对象作为参数。
如果那个函数不属于你(因为它是你正在使用的库的一部分),那么你甚至不能那样做。
因此,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,您就可以按照他们原本应该做的方式做事。
【讨论】:
感谢您对这个基于历史过程的解释。当你开始使用 OpenGL 时,你必须消化的所有过于神秘、冗长、复杂的废话最终在解释了一些设计合理性时才开始变得有意义。它并没有使代码看起来更漂亮,但它确实有助于我理解如何导航 API。以上是关于OpenGL中绑定点的目的?的主要内容,如果未能解决你的问题,请参考以下文章