I am trying to understand the theory behind OpenGL and I'm studying VBOs at the moment.
This is what I understand so far: when we declare a series of vertices, let's say 3 vertices that form a triangle primitive, we basically store those nowhere, they're simply declared in code.
But, if we want to store them somewhere we can use a VBO that stores the definition of those vertices. And, through the same VBO we send all that vertex info to the Vertex Shader (which is a bunch of code). Now, the VBO is located in the GPU, so we are basically storing all that info on the GPU's memory when we call the VBO. Then the Vertex Shader, which is part of the Pipeline Rendering process, "comes" to the GPU's memory, "looks" into the VBO and retrieves all that info. In other words, the VBO stores the vertex data (triangle vertices) and sends it to the Vertex Shader.
So, VBO -> send info to -> Vertex Shader.
Is this correct? I'm asking to make sure if this is the correct interpretation, as I find myself drawing triangles on screen and sometimes letters made up of many triangles with a bunch of code and functions that I basically learned by memory but don't really understand what they do.
To break it down:
// here I declare the VBO
unsigned int VBO;
// we have 1 VBO, so we generate an ID for it, and that ID is: GL_ARRAY_BUFFER
glGenBuffers(1, &VBO)
// GL_ARRAY_BUFFER is the VBO's ID, which we are going to bind to the VBO itself
glBindBuffer(GL_ARRAY_BUFFER, VBO)
// bunch of info in here that basically says: I want to take the vertex data (the
// triangle that I declared as a float) and send it to the VBO's ID, which is
// GL_ARRAY_BUFFER, then I want to specify the size of the vertex
// data, the vertex data itself and the 'static draw' thingy
glBufferData(...).
After doing all that, the VBO now contains all the vertex data within. So we tell the VBO, ok now send it to the Vertex Shader.
And that's the start of the Pipeline, jsut the beginning.
Is this correct? (I haven't read what VAOs do yet, before I get to that I'd like to know if the way I deconstruct VBOs in my mind is the right way, or else I'm confused)
I think you are mixing up lots of different things and have several confusions, so I'm try to work through most of them in the order you brought them up:
when we declare a series of vertices, let's say 3 vertices that form a triangle primitive, we basically store those nowhere, they're simply declared in code.
No. If you store data "nowhere", then you don't have it. Also you are mixing up declaration, definiton and initialization of variables here. For vertex data (like all other forms of data), there are two basic strategies:
You store the data somewhere, typically in a file. Specifying it directly in source code just means that it is stored in some binary file, potentially the executable itself (or some shared library used by it)
You procedurally generate the data through some mathematical formula or more general by some algortihm
Methods 1. and 2 can of course be mixed, and usually, method 2 will need some parameters (which itself need to be stored somewhere, so the parameters are just case 1 again).
And, through the same VBO we send all that vertex info to the Vertex Shader (which is a bunch of code). Now, the VBO is located in the GPU, so we are basically storing all that info on the GPU's memory when we call the VBO.
OpenGL is actually just a specification which is completely agnostic about the existence of a GPU and the existence of VRAM. And as such, OpenGL uses the concept of buffer objects (BOs) as some continuous block of memory of a certain size which is completely managed by the GL implementation. You as the user can ask the GL to create or destroy such BOs, specify their size, and have complete control of the contents - you can put an MP3 file into a BO if you like (not that there would be a good use case for this).
The GL implementation on the other hand controls where this memory is actually allocated, and GL implementations for GPUs
which actually have dedicated video memory have the option to store a BO directly in VRAM. The hints like GL_STATIC_DRAW are there to help the GL implementation decide where to best put such a buffer (but that hint system is somewhat flawed, and better alternatives exist in modern GL, but I'm not going into that here). GL_STATIC_DRAW means you intent to specify the contents once and use the may times as the source of a drawing option - so the data won't change often (and certainly not on a per-frame basis or even more often), and it might be a very good idea to store it in VRAM if such a thing exists.
Then the Vertex Shader, which is part of the Pipeline Rendering process, "comes" to the GPU's memory, "looks" into the VBO and retrieves all that info.
I think one could put it that way, although some GPUs have a dedicated "vertex fetch" hardware stage which actually reads the vertex data which is then fed to the vertex shaders. But that's not a really important point - the vertex shader needs to access each vertex' data, and that means the GPU will read that memory (VRAM or system memory or whatever) at some point before or during the execution of a vertex shader.
In other words, the VBO stores the vertex data (triangle vertices)
Yes. A buffer object which is used as source for the vertex shader's per-vertex inputs ("vertex attributes") is called a vertex buffer object ("VBO"), so that just follows directly from the definition of the term.
and sends it to the Vertex Shader.
I wouldn't put it that way. A BO is just a block of memory, it doesn't actively do anything. It is just a passive element: it is being written to or being read from. That's all.
// here I declare the VBO
unsigned int VBO;
No, you are declaring (and defining) a variable in the context of your programming language, and this variable is later used to hold the name of a buffer object. And in the GL, object names are just positive integers (so 0 is reserved for the GL as "no such object" or "default object", depending on the object type).
// we have 1 VBO, so we generate an ID for it, and that ID is: GL_ARRAY_BUFFER
glGenBuffers(1, &VBO)
No. glGenBuffers(n,ptr) just generates names for n new buffer objects, so it will generate n previously unused buffer names (and mark them as used) and returns them by writing them to the array pointed to byptr. So in this case, it just creates one new buffer object name and stores it in your VBO variable.
GL_ARRAY_BUFFER has nothing to do with this.
// GL_ARRAY_BUFFER is the VBO's ID, which we are going to bind to the VBO itself
glBindBuffer(GL_ARRAY_BUFFER, VBO)
No, GL_ARRAY_BUFFER is not the VBO's ID, the value of yourVBO variable is the VBO's ID (name!).
GL_ARRAY_BUFFER is the binding target. OpenGL buffer objects can be used for different purposes, and using them as the source for vertex data is just one of them, and GL_ARRAY_BUFFER refers to that use case.
Note that classic OpenGL uses the concept of binding for two purposes:
bind-to-use: Whenever you issue a GL call which depends on some GL objects, the objects you want to work with have to be currently bound to some (specific, depending on the use case) binding target (not only buffer objects, but also textures and others).
bind-to_modify: Whenever you as the user want to modify the state of some object, you have to bind it first to some binding target, and all the object state modify functions don't directly take the name of the GL object to work on as parameter, but the binding target, and will affect the object which is currently bound at that target. (Modern GL also has direct state access which allows you to modify objects without having to bind them first, but I'm also not going into details about that here).
Binding a buffer object to some of the buffer object binding targets means that you can use that object for the purpose defined by the target. But note that a buffer object doesn't change because it is bound to a target. You can bind a buffer object to different targets even at the same time. A GL buffer object doesn't have a type. Calling a buffer a "VBO" usually just means that you intent to use it as GL_ARRAY_BUFFER, but the GL doesn't care. It does care about what is buffer is bound as GL_ARRAY_BUFFER at the time of the glVertexAttribPointer() call.
// bunch of info in here that basically says: I want to take the vertex data (the
// triangle that I declared as a float) and send it to the VBO's ID, which is
// GL_ARRAY_BUFFER, then I want to specify the size of the vertex
// data, the vertex data itself and the 'static draw' thingy
glBufferData(...).
Well, glBufferData just creates the actual data storage for a GL buffer object (that is, the real memory), meaning you specify the size of the buffer (and the usage hint I mentioned earlier where you tell the GL how you intend to use the memory), and it optionally allows you to initialize the buffer by copying data from your application's memory into the buffer object. It doesn't care about the actual data, and the types you use).
Since you use GL_ARRAY_BUFFER here as the target parameter, this operation will affect the BO which is currently bound as GL_ARRAY_BUFFER.
After doing all that, the VBO now contains all the vertex data within.
Basically, yes.
So we tell the VBO, ok now send it to the Vertex Shader.
No. The GL uses Vertex Array Objects (VAOs) which store for each vertex shader input attribute where to find the data (in which buffer object, at which offset inside the buffer object) and how to interpret this data (by specifying the data types).
Later during the the draw call, the GL will fetch the data from the relevant locations within the buffer objects, as you specified it in the VAO. If this memory access is triggered by the vertex shader itself, or if there is a dedicated vertex fetch stage which reads the data before and forwards it to the vertex shader - or if there is a GPU at all - is totally implementation-specific, and none of your concern.
And that's the start of the Pipeline, just the beginning.
Well, depends on how you look at things. In a traditional rasterizer-based rendering pipline, the "vertex fetch" is more or less the first stage, and vertex buffer objects will just hold the memory where to fetch the vertex data from (and VAOs telling it which buffer objects to use, and which actual locations, and how to interpret them).
It all boils down to this: when you work in "normal" programs, all what you have is the CPU, caches, registers, main memory, etc.
However, when you work with computer graphics (and other fields), you want to use a GPU because it is faster for that particular task. The GPU is an independent computer on its own, with its own processor, pipeline and main even memory.
This means your program needs to somehow transfer all the data to the other computer and tell the other computer what to do with it. This is no easy task, so OpenGL simplifies things for you. Thus they give you an abstraction (VBO) that represents a buffer of vertices in the GPU, among many other abstractions for other tasks. Then they give you functions to create that resource (glGenBuffers), fill it with data (glBufferData), "bind it" to work with it (glBindBuffer), etc.
Remember, it is all a simplification for your benefit. In truth, the details of how everything is performed underneath is way more complex. Having abstractions like VBOs for vertices or IBOs for indexes makes it easier to work with them.
I've been delving into OpenGL a while, and now, working on a project, I found that, when I'm creating an index buffer, if I bind it to a GL_ARRAY_BUFFER instead to a GL_ELEMENT_ARRAY_BUFFER has (apparently) the same result.
I mean, the vertex buffers are always bound to GL_ARRAY_BUFFER, but if I create an index buffers like this:
glCreateBuffers(1, &m_BufferID);
glBindBuffer(GL_ARRAY_BUFFER, m_BufferID);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(uint), vertices, GL_STATIC_DRAW);
And then, when drawing geometry for instance, I bind it to a GL_ELEMENT_ARRAY_BUFFER, works just fine, and I don't know why I thought that index buffers had to be created with GL_ELEMENT_ARRAY_BUFFER too, but... Is there any "inner" difference actually?
In terms of the nature of the buffer object itself? No. All buffer objects are the same and can be used for any task appropriate for a buffer object. A buffer object does not take on special properties by which binding it was initially used with.
However, the GL_ELEMENT_ARRAY_BUFFER binding point is itself a bit unusual. It's not part of global context state; it's part of VAO state. So if you have no VAO bound (under a core profile context), then you can't bind anything to that binding point. And when you bind to that binding point, you are affecting the state of the currently bound VAO. And if you change the currently bound VAO, you will be changing which buffer is bound to the element array binding point.
So generally, you should only bind to that point if what you intend to do is attach the buffer to the currently bound VAO.
Yes it ist. While the ARRAY_BUFFER binding is a global state, the ELEMENT_ARRAY_BUFFER binding is stated in the Vertex Array Object. See Index buffers.
Hence, glBindBuffer(GL_ELEMENT_ARRAY_BUFFER m_BufferID) changes a state in the currently bound Vertex Array Object.
Note that compared to the index buffer (ELEMENT_ARRAY_BUFFER), the vertex buffer binding (ARRAY_BUFFER) is a global state.
Each attribute which is stated in the VAOs state vector may refer to a different ARRAY_BUFFER. This reference is stored when glVertexAttribPointer is called. Then the buffer which is currently bound to the target ARRAY_BUFFER, is associated to the specified attribute index and the name (value) of the object is stored in the state vector of the currently bound VAO.
However the index buffer is a state of the VAO. If a buffer is bound to the target ELEMENT_ARRAY_BUFFER, this buffer is assigned to the currently bound Vertex Array Object.
I was reading the documentation on glBindBuffer when I saw this:
Likewise, the GL_UNIFORM_BUFFER, GL_ATOMIC_COUNTER_BUFFER and GL_SHADER_STORAGE_BUFFER buffer binding points may be used, but do not directly affect uniform buffer, atomic counter buffer or shader storage buffer state, respectively.
What does "do not directly affect uniform buffer, atomic counter buffer or shader storage buffer state" mean?
I bound a buffer to the generic GL_SHADER_STORAGE_BUFFER binding point, and when querying for the state of SHADER_STORAGE_BUFFER_BINDING, the previously bound buffer name is returned.
On this site describing the shader_storage_buffer_object extension, it states that SHADER_STORAGE_BUFFER_BINDING is part of the shader storage buffer state.
Therefore I believe that using glBindBuffer on a generic binding point (such as GL_SHADER_STORAGE_BUFFER) does indeed affect the shader storage buffer state.
Yes obviously, if you bind the buffers to the context, you can query those bind points. But the point the docs is trying to get across is that those binding points don't mean anything.
Consider GL_COPY_READ_BUFFER. Binding to this target means something. If you call glCopyBufferSubData, it will use the buffer bound to GL_COPY_READ_BUFFER as the source buffer for that copy operation.
By contrast, there is no OpenGL operation that will use the buffer bound to GL_SHADER_STORAGE_BUFFER for any actual OpenGL operations. Sure, you can query it, but that's all you can do with it. If you want to use a buffer for actual storage buffer operations, you must use glBindBufferRange/Base or equivalent functions.
I have a problem understanding association between VAO and buffer bound to GL_ELEMENT_ARRAY_BUFFER (let's call it EBO). I got to know that it is part of VAO state, but differ from buffers used along with glVertexAttribPointer calls (those buffers are simply remembered by VAO as attributes storage omitting need to bind them - if I understood correctly).
In this discussion it is claimed:
Unlike GL_ARRAY_BUFFER, a VAO store the current binding for GL_ELEMENT_ARRAY_BUFFER. Calling glBindBuffer(GL_ELEMENT_ARRAY_BUFFER) stores a reference to the specified buffer in the currently-bound VAO. glDrawElements() etc take the vertex indices from the buffer stored in the currently-bound VAO. So switching between VAOs switches between element arrays.
But do I need to call glBindBuffer(GL_ELEMENT_ARRAY_BUFFER) between bind and unbind of VAO to make sure it will 'save' it in its state or VAO will just take currently bound EBO when VAO gets created?
Also the answer quoted above isn't clear to me whether VAO just 'knows' (as attribute storage buffers) which buffer indices (glDrawElements) have to be taken from or does VAO binds proper EBO when VAO gets bound?
edit: First of my questions has been answered here, however I believe the second one was answered by #Rabbid76.
Do I need to call glBindBuffer(GL_ELEMENT_ARRAY_BUFFER) between bind and unbind of VAO?
The OpenGL specification (chapter 10.3. VERTEX ARRAYS) clearly says:
A vertex array object is created by binding a name returned by GenVertexArrays
with the commandvoid BindVertexArray( uint array ); array is the vertex array object name. The resulting vertex array object is a new
state vector, comprising all the state and with the same initial values listed in tables 23.3 and 23.4.
BindVertexArray may also be used to bind an existing vertex array object.
If the bind is successful no change is made to the state of the bound vertex array
object, and any previous binding is broken.
The Tables 23.3 and 23.4 contain ELEMENT_ARRAY_BUFFER_BINDING.
This means, that the GL_ELEMENT_ARRAY_BUFFER has to be bound after the vertex array object has been bound (glBindVertexArray).
The "name" of the GL_ELEMENT_ARRAY_BUFFER object is stored in the vertex array objects state vector. If the vertex array object has been unbound and is bound again, then the GL_ELEMENT_ARRAY_BUFFER is known and bound again, too.
Note, glBufferData creates a new data store for a buffer object. glBufferData delete any existing data store, and set the values of the buffer object’s state variables. This means, that the data are associated to the buffer object until you associate new data. - see Khronos reference page glBufferData and OpenGL 4.6 API Compatibility Profile Specification; 6.1 Creating and Binding Buffer Objects
If you set things up correctly, at draw time you should only be binding VAOs. That is sort of the whole point, avoid the CPU overhead of calling into the GL driver repeatedly for each piece of geometry.
During your initial setup you should create and bind a VAO, then bind all attribute buffers and describe them with glVertexAttribPointer, enable them with glEnableVertexAttribArray and lastly bind the GL_ELEMENT_ARRAY_BUFFER. After that, I would bind the VAO to null so you don't accidentally bind something else in the following lines.
(Note that the glBindBuffer of the attribute buffer itself is not directly recorded into the VAO, but rather inferred at the call site of glVertexAttribPointer. That is, whatever is currently bound to GL_ARRAY_BUFFER when you call glVertexAttribPointer will be what the VAO sources for vertex pulling.)
During your render loop you should bind the different VAOs you created earlier.
It's a little bit confusing because in practice sometimes binding a VAO means "I want to start recording a little macro" (at initial setup time) and sometimes binding a VAO means "I want you to play back all the bindings in that macro I recorded earlier" (at render time). It is described in the original RFC as:
The currently bound vertex array object is used for all commands
which modify vertex array state, such as VertexAttribPointer and
EnableVertexAttribArray; all commands which draw from vertex arrays,
such as DrawArrays and DrawElements; and all queries of vertex
array state.
https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_vertex_array_object.txt
See also tables 6.4 and 6.5 in the GL standard to see exactly what fields are stored in the VAO struct:
https://www.khronos.org/registry/OpenGL/specs/gl/glspec32.core.pdf
I am struggling to understand how exactly VAO is handling buffer mapping.
What I'm doing could be described in this pseudocode:
SetUp:
BindVAO
BindArrayBuffer
glBufferData(GL_ARRAY_BUFFER, ExpectedMaxCount, NULL, GL_DYNAMIC_DRAW);//Allocate storage
glEnableVertexAttribArray
glVertexAttribPointer
BindElementBuffer
Allocate storage (no data yet)
UnbindVAO
UnbindArrayBuffer
UnbindElementBuffer
Draw:
SubArrayAndElementDataIfNeeded
BindVAO
DrawElements
Is this correct that when DrawElements is called OpenGL uses bound VAO to resolve array and element buffer bindings? After a Draw call the bound array buffer is 0, but element buffer is still the one that was used to Draw.
Is it mandatory to allocate buffer memory during VAO setup? Would VAO be invalidated if BufferData was called after setup?
I am struggling to understand how exactly VAO is handling buffer mapping.
Be very careful when using the word "mapping" around "buffers"; that has a specific meaning when dealing with buffer objects, one that you probably don't intend.
Is this correct that when DrawElements is called OpenGL uses bound VAO to resolve array and element buffer bindings? After a Draw call the bound array buffer is 0, but element buffer is still the one that was used to Draw.
One has nothing to do with the other. A Vertex Array Object, as the name implies, contains all of the state that is necessary to pull vertex data from arrays. When you bind one, all of that state comes back into the context.
The reason the "bound array buffer" is 0 after the call is because it was 0 before the call. Draw calls do not change OpenGL state.
Furthermore, you seem to have fallen for the GL_ARRAY_BUFFER trap. The GL_ARRAY_BUFFER binding only matters to three functions: glVertexAttribPointer, glVertexAttribIPointer, and glVertexAttribLPointer (see a pattern?). These are the only functions that look at that binding. What they do is take the buffer that is bound at the time these functions are called and associates that buffer with the current VAO. GL_ARRAY_BUFFER is not part of the VAO's state. A buffer object becomes associated with a VAO only when you call one of those three functions. Once you make that call, you can bind whatever you want to GL_ARRAY_BUFFER.
GL_ELEMENT_ARRAY_BUFFER is part of the VAO's state.
Is it mandatory to allocate buffer memory during VAO setup? Would VAO be invalidated if BufferData was called after setup?
Technically no, but it's good form to not use a buffer until it has storage. Especially if they're static buffers.