OpenGL中绑定点的目的?
我不明白 OpenGL 中绑定点(例如 GL_ARRAY_BUFFER
)的目的是什么.据我了解, glGenBuffers()
创建了一种指向位于 GPU 内存中某处的顶点缓冲区对象的指针.
I don't understand what the purpose is of binding points (such as GL_ARRAY_BUFFER
) in OpenGL. To my understanding glGenBuffers()
creates a sort of pointer to a vertex buffer object located somewhere within GPU memory.
所以:
glGenBuffers(1, &bufferID)
意味着我现在有一个句柄,bufferID,指向显卡上的 1 个顶点对象.现在我知道下一步是将 bufferID 绑定到绑定点
means I now have a handle, bufferID, to 1 vertex object on the graphics card. Now I know the next step would be to bind bufferID to a binding point
glBindBuffer(GL_ARRAY_BUFFER, bufferID)
这样我就可以使用该绑定点使用 glBufferData()
函数向下发送数据,如下所示:
so that I can use that binding point to send data down using the glBufferData()
function like so:
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW)
但是为什么我不能只使用 bufferID 来指定我想将数据发送到哪里呢?比如:
But why couldn't I just use the bufferID to specifiy where I want to send the data instead? Something like:
glBufferData(bufferID, sizeof(data), data, GL_STATIC_DRAW)
然后在调用绘图函数时,我也只需将哪个 ID 放入我希望绘图函数绘制的任何 VBO.比如:
Then when calling a draw function I would also just put in which ever ID to whichever VBO I want the draw function to draw. Something like:
glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)
为什么我们需要额外的 glBindBuffers
间接步骤?
Why do we need the extra step of indirection with glBindBuffers
?
推荐答案
OpenGL 使用对象绑定点做两件事:指定要用作渲染过程的一部分的对象,以及能够修改对象.
OpenGL uses object binding points for two things: to designate an object to be used as part of a rendering process, and to be able to modify the object.
为什么将它们用于前者很简单:OpenGL 需要大量对象才能进行渲染.
Why it uses them for the former is simple: OpenGL requires a lot of objects to be able to render.
考虑您过于简单的示例:
Consider your overly simplistic example:
glDrawArrays(bufferID, GL_TRIANGLES, 0, 3)
该 API 不允许我将单独的顶点属性来自单独的缓冲区.当然,你可能会提出 glDrawArrays(GLint count, GLuint *object_array, ...)
.但是如何将特定的缓冲区对象连接到特定的顶点属性呢?或者你怎么有来自缓冲区 0 的 2 个属性和来自缓冲区 1 的第三个属性?这些是我现在可以使用当前 API 做的事情.但是你提议的那个处理不了.
That API doesn't let me have separate vertex attributes come from separate buffers. Sure, you might then propose glDrawArrays(GLint count, GLuint *object_array, ...)
. But how do you connect a particular buffer object to a particular vertex attribute? Or how do you have 2 attributes come from buffer 0 and a third attribute from buffer 1? Those are things I can do right now with the current API. But your proposed one can't handle it.
即便如此,您也需要将 许多 其他需要渲染的对象放在一边:程序/管道对象、纹理对象、UBO、SSBO、变换反馈对象、查询对象等.在单个命令中指定所需的对象从根本上是不可行的(并且撇开性能成本不谈).
And even that is putting aside the many other objects you need to render: program/pipeline objects, texture objects, UBOs, SSBOs, transform feedback objects, query objects, etc. Having all of the needed objects specified in a single command would be fundamentally unworkable (and that leaves aside the performance costs).
每次 API 需要添加一种新类型的对象时,您都必须添加 glDraw*
函数的新变体.现在,有超过十几个这样的功能.你的方式会给我们数百.
And every time the API would need to add a new kind of object, you would have to add new variations of the glDraw*
functions. And right now, there are over a dozen such functions. Your way would have given us hundreds.
因此,OpenGL 为您定义了下次渲染时,以这种方式在该过程中使用此对象"的方式.这就是绑定对象以供使用的意义.
So instead, OpenGL defines ways for you to say "the next time I render, use this object in this way for that process." That's what binding an object for use means.
但是为什么我不能只使用 bufferID 来指定我想将数据发送到哪里呢?
But why couldn't I just use the bufferID to specifiy where I want to send the data instead?
这是为了修改对象而绑定一个对象,而不是说它会被使用.那是……另一回事.
This is about binding an object for the purpose of modifying the object, not saying that it will be used. That is... a different matter.
显而易见的答案是,你不能这样做,因为 OpenGL API(直到 4.5)没有让你这样做的功能."但我宁愿怀疑这个问题真的是为什么 OpenGL 没有这样的 API(直到 4.5,其中存在 glNamedBufferStorage
等).
The obvious answer is, "You can't do it because the OpenGL API (until 4.5) doesn't have a function to let you do it." But I rather suspect the question is really why OpenGL doesn't have such APIs (until 4.5, where glNamedBufferStorage
and such exist).
确实,4.5 确实具有这样的功能这一事实证明,4.5 之前的 OpenGL 的 bind-object-to-modify API 没有技术原因.这确实是 OpenGL API 从 1.0 演变而来的决定",这要归功于遵循阻力最小的路径.反复.
Indeed, the fact that 4.5 does have such functions proves that there is no technical reason for pre-4.5 OpenGL's bind-object-to-modify API. It really was a "decision" that came about by the evolution of the OpenGL API from 1.0, thanks to following the path of least resistance. Repeatedly.
确实,OpenGL 所做的几乎每一个错误决定都可以追溯到采用 API 中阻力最小的路径.但我离题了.
Indeed, just about every bad decision that OpenGL has made can be traced back to taking the path of least resistance in the API. But I digress.
在 OpenGL 1.0 中,只有一种对象:显示列表对象.这意味着 甚至纹理 都没有存储在对象中.因此,每次切换纹理时,都必须使用 glTexImage*D
重新指定整个纹理.这意味着重新上传它.现在,您可以(并且人们确实)将每个纹理的创建包装在一个显示列表中,这允许您通过执行该显示列表来切换纹理.并且希望驱动程序会意识到您正在这样做,而是适当地分配视频内存等等.
In OpenGL 1.0, there was only one kind of object: display list objects. That means that even textures were not stored in objects. So every time you switched textures, you had to re-specify the entire texture with glTexImage*D
. That means re-uploading it. Now, you could (and people did) wrap each texture's creation in a display list, which allowed you to switch textures by executing that display list. And hopefully the driver would realize you were doing that and instead allocate video memory and so forth appropriately.
所以当 1.1 出现时,OpenGL ARB 意识到这是多么愚蠢.所以他们创建了纹理对象,它封装了纹理的内存存储和其中的各种状态.当你想使用纹理时,你绑定它.但是有一个障碍.即,如何改变它.
So when 1.1 came around, the OpenGL ARB realized how mind-bendingly silly that was. So they created texture objects, which encapsulate both the memory storage of a texture and the various state within. When you wanted to use the texture, you bound it. But there was a snag. Namely, how to change it.
看,1.0 有一堆已经存在的函数,如 glTexImage*D
、glTexParamter
等.这些修改纹理的状态.现在,ARB 可以添加新的函数来做同样的事情,但将纹理对象作为参数.
See, 1.0 had a bunch of already existing functions like glTexImage*D
, glTexParamter
and the like. These modify the state of the texture. Now, the ARB could have added new functions that do the same thing but take texture objects as parameters.
但这意味着将所有 OpenGL 用户分为两个阵营:使用纹理对象的阵营和不使用纹理对象的阵营.这意味着,如果您想使用纹理对象,则必须重写 所有 修改纹理的现有代码.如果您有一些函数对当前纹理进行了一堆 glTexParameter
调用,则必须更改该函数以调用新的纹理对象函数.但是您也必须更改调用它的函数,以便它将操作的纹理对象作为参数.
But that would mean dividing all OpenGL users into 2 camps: those who used texture objects and those who did not. It meant that, if you wanted to use texture objects, you had to rewrite all of your existing code that modified textures. If you had some function that made a bunch of glTexParameter
calls on the current texture, you would have to change that function to call the new texture object function. But you would also have to change the function of yours that calls it so that it would take, as a parameter, the texture object that it operates on.
如果那个函数不属于你(因为它是你正在使用的库的一部分),那么你甚至不能那样做.
And if that function didn't belong to you (because it was part of a library you were using), then you couldn't even do that.
因此,ARB 决定保留这些旧函数,只是让它们根据纹理是否绑定到上下文而表现不同.如果绑定了一个,那么 glTexParameter
/etc 将修改绑定的纹理,而不是上下文的正常纹理.
So the ARB decided to keep those old functions around and simply have them behave differently based on whether a texture was bound to the context or not. If one was bound, then glTexParameter
/etc would modify the bound texture, rather than the context's normal texture.
这一决定建立了通用范式共享几乎所有 OpenGL 对象.
ARB_vertex_buffer_object 出于同样的原因使用此范例.请注意各种 gl*Pointer
函数(glVertexAttribPointer
等)如何与缓冲区相关联.您必须将缓冲区绑定到 GL_ARRAY_BUFFER
,然后调用其中一个函数来设置属性数组.当缓冲区绑定到该插槽时,该函数将拾取它并将指针视为在调用 *Pointer
函数时绑定的缓冲区的偏移量.
ARB_vertex_buffer_object used this paradigm for the same reason. Notice how the various gl*Pointer
functions (glVertexAttribPointer
and the like) work in relation to buffers. You have to bind a buffer to GL_ARRAY_BUFFER
, then call one of those functions to set up an attribute array. When a buffer is bound to that slot, the function will pick that up and treat the pointer as an offset into the buffer that was bound at the time the *Pointer
function was called.
为什么?出于同样的原因:易于兼容(或促进懒惰,取决于您希望如何看待它).ATI_vertex_array_object 必须为 gl*Pointer
函数创建新的类似物.而 ARB_vertex_buffer_object 只是捎带了现有的入口点.
Why? For the same reason: ease of compatibility (or to promote laziness, depending on how you want to see it). ATI_vertex_array_object had to create new analogs to the gl*Pointer
functions. Whereas ARB_vertex_buffer_object just piggybacked off of the existing entrypoints.
用户不必从使用 glVertexPointer
更改为 glVertexBufferOffset
或其他一些函数.他们所要做的就是在调用设置顶点信息的函数之前绑定一个缓冲区(当然还要更改指向字节偏移的指针).
Users didn't have to change from using glVertexPointer
to glVertexBufferOffset
or some other function. All they had to do was bind a buffer before calling a function that set up vertex information (and of course change the pointers to byte offsets).
这也意味着他们不必添加一堆 glDrawElementsWithBuffer
类型的函数来使用来自缓冲区对象的索引进行渲染.
It also mean that they didn't have to add a bunch of glDrawElementsWithBuffer
-type functions for rendering with indices that come from buffer objects.
所以在短期内这不是一个坏主意.但与大多数短期决策一样,随着时间的推移,它开始变得不那么合理.
So this wasn't a bad idea in the short term. But as with most short-term decision making, it starts being less reasonable with time.
当然,如果您有权访问 GL 4.5/ARB_direct_state_access,您就可以按照他们原本应该做的方式做事.
Of course, if you have access to GL 4.5/ARB_direct_state_access, you can do things the way they ought to have been done originally.
相关文章