No problem!
Let me see if I have this, then
…2. Then, binding a VAO is merely a shorthand way of binding all the specified VBOs at once rather than looping through a list and doing it manually.
3. Lastly, a shader program is another OpernGL “slot” that should be bound (along with the VBOs) before you call the draw function
Exactly. Though a VAO captures more than just the array and element buffer bindings. It also captures the info you’ve provided on how to parse through these buffer objects (via glVertexAttribPointer and gl{Enable,Disable}VertexAttribArray).
- You allocate a VBO and give it a purpose, and bind it so it’s “current”.
Yes. You yourself give it a purpose, however as far as OpenGL calls go, you just give it “contents”. There is no special “purpose” for the VBO that you provide to OpenGL. (Yes, there is a usage hint in glBufferData(), but drivers generally ignore that because so many developers set it wrong.)
Think of a malloced() block of CPU memory: it has a size (in bytes) and a handle (a pointer) you can get to it with. GL buffer objects are the same, except that the “handle” is just an integer key used by the driver to lookup the buffer object.
Each such area is associated with a OpenGL purpose-slot-ish-thing (vertices, index offsets, textures1-4, normal vectors, whatever),
Better worded: Each such area is associated with a contiguous block of bytes. There’s no purpose or slot metadata inherently associated with a buffer object. It’s just a dumb block of bytes!
In fact, it’s most common for best performance to have a single VBO contain “all” of your vertex attributes needed in a draw call, interleaved with each other. For example, in one VBO:
- Vertex 0’s position, normal, texcoord.
- Vertex 1’s position, normal, texcoord.
- Vertex 2’s position, normal, texcoord.
…
And as mhagain said, you can store data for multiple draw calls in one VBO. You can even stream vertex and index data into the same VBO if it’s useful to you.
…and OpenGL would now know what to do with that data if you called a Draw call, since it “sees” a (bound) list of vertices and a (bound) list of index values, it “Just Knows” what to do with each one.
No. Really seriously, buffer objects are just dumb blocks of memory! You fill them with contents, and you and only you know the format of what you’ve put in them. You then tell OpenGL how to interpret them (i.e. where to fetch data from them and how to interpret that data).
The parameters you provide to glVertexAttribPointer, gl{Enable,Disable}VertexAttribArray, glDrawElements, and glDrawArrays tell OpenGL how to interpret the buffer object’s you’ve bound to GL_ARRAY_BUFFER and GL_ELEMENT_ARRAY_BUFFER.
For instance, in glDrawElements, the type param says what type of indices to expect, the “count” param says how many indices to expect, and the “offset” param says at what offset in the bound GL_ELEMENT_ARRAY_BUFFER OpenGL should look to find that block of “count” indices of type “type”.
No VAO needed!
In the compatibility profile, no, you don’t have to have a bound VAO.
In the core profile, you have to have a bound VAO. That’s why I said if you just want to ignore VAOs for now, bind one on startup and then forget about them. This approach works regardless which OpenGL profile you’re developing for.
Theoretically if you called glDrawElements without first binding a vbo to GL_ELEMENT_ARRAY_BUFFER, it would barf, because OpenGL would “just know” that it didn’t have all the bits it needs.
In the core profile, yes.
In the compatibility profile, no.
(More detail if you care: The thing is, OpenGL didn’t always have VBO support. Before that, you provided glVertexAttribPointer (and friends) and glDrawElements pointers to CPU memory buffers in your app which contained the vertex attribute and index list data, respectively, rather than binding buffer objects and providing GL offsets into those GL buffer objects. You can still do that with the compatibility profile.)
This also explains why the glDrawXXX calls don’t have any vao/vbo/program/texture arguments - they’re all implicit from what’s currently “bound”.
Right.
So after all that, why ARE there different draw calls?
Two reasons: Different use cases, and API evolution.
OpenGL has evolved over time as GPUs have gotten more and more complex and capable. Originally, draw calls were very simple. But now there’s a bunch of different “options” you can specify when it’s useful to you.
True, we could have a “one draw call to rule them all” with a boatload of options you have to specify. But this would make it harder for new folks to understand how the heck to use it and why it’s so complex, and for devs in general to get the usage right consistently.
For now I’d just stick with glDrawArrays() and glDrawElements() until you get more familiar. Then when you start thinking: gee, I wish I could do “this” with draw calls, surf the draw calls the latest OpenGL offers and chances are you’ll find one that does what you want. (For instance, search for “glDraw” here, ignoring glDrawBuffer and glDrawBuffers as those two aren’t draw calls).