Thanks for the help. Part of the reason for my frustration is… the engine just stopped working (only drew one point in the center of the window). Though my changes were extensive, no code related to VAO or drawing was changed. The only change was a slight difference in the order functions were executed (because I also made program objects full objects, which previously I put off until “someday” and finally implemented). Note: I don’t mean C++ objects, I mean “objects” in the sense my C program functions in terms of objects of various kinds… much like OpenGL does, albeit somewhat different.
Anyway, the fact that a slight difference in execution order made drawing stop working was a surprise to me, and demonstrated that I didn’t fully understand what needs to happen before what else for everything to function [properly].
Thanks to you and Alfonse (primarily), I understand better now. Maybe not 100%, but better. Of course, now that better understand the whole VAO situation (in the old/normal approach), Alfonse points out the “new” approach with glVertexArrayVertexAttrib?Format() and glVertexArrayVertexBuffer() and glVertexArrayVertexAttribBinding() is better given my goals. That threw me back into confusion again, but after struggling to understand for hours in my dreams last night, I think I sorta “get it” now. We’ll see when I modify my code and click “run”.
Honestly, there’s something seriously wrong with the way pretty much everyone describes OpenGL. Even the sources I like the best are confusing to me. Probably this is true because pretty much everyone picks up the approach from the specification documents… though nobody explains as terribly as the specification documents!
What would I do?
Well, I think you hit upon the correct approach in your message when you said, “it’s all about the draw calls” and “think in terms of draw calls”. For the VAO and related topics, that’s the correct approach. What I would probably do (not having thought this through carefully), is to say something like:
When an application wants to draw, the software driver and/or GPU hardware need to know the following to perform the draw operation:
- what primitives to draw ::: points, lines or triangles (surfaces)
[SIZE=3] - which IBO holds the indices/elements
- datatype of each index/element (u08, u16, u32)
- which index/element is the first to process (byte offset to first)
- how many indices/elements to process (and thereby how many points, lines, triangles to draw)
- within each vertex…
— where is each attribute in the vertex (byte-offset)
— what datatype is each attribute (s08, u08, s16, u16, s32, u32, s64, u64, f16, f32, f64)
— how many variables of specified datatype in each attribute (1,2,3,4)
— what datatype to deliver the attribute to the GPU (convert to f16, f32, f64 or not)
— which 16-byte location (or locations for matrices) to deliver each attribute
— which VBO to grab the vertex attributes from (or which VBO if you’re a SoA fan)
I probably forgot something, but you get the point. Truth is, the list above should probably also contain items like “program object to perform the draw” and more (probably everything that impacts the draw should be mentioned, even if only to say “bind the one you want [say where to bind it to] to make it active/accessed/operational during the draw”.
Then explain how to specify all these items so the draw works. For example:
#1: The type of primitive to be drawn (points, lines, triangles) is specified by the “gmode” argument in the glDrawElements() function, not by OpenGL state. Then refer to the section of the document that explains the different ways lines and triangles can be constructed.
#2: The number of indices processed (and thus the number of points, lines, triangles to be rendered) is specified by the “count” argument in the glDrawElements() function. A count of 6 will draw 6 points, 3 to 5 lines (depending on the specific line drawing mode specified in “gmode”), or 2 to 4 triangles (depending on the specific triangle drawing mode specified in “gmode”). See section yada-yada-yada for details.
#3. Most of the information required to draw is specified by the contents of the VAO that is currently made active by calling function glBindVertexArray(vao). And this is where to go into great detail to explain what information needs to be inserted into the VAO, why draw functions need the information, and and how each impacts the draw operation. In other words, a page or several of text about VAO state follows.
Something that still isn’t clear to me… and maybe doesn’t even have a singular answer for every way to work with OpenGL… is whether telling the draw functions how to send vertex-attributes (and also the contents of uniform blocks and shader storage blocks) is fully independent of the program object or not (meaning, independent of what the program object expects to get based upon the contents of layout location and binding syntax in the shaders).
I may be in the minority (not sure), but I’d prefer the whole VAO and draw function part be 100% independent of the program object (and what it expects). I mean, it is fine, great, wonderful and totally desirable to have (and call when desired) functions like glGetVertexAttribLocation() and similar for UBO and SSBO elements for debugging and for programmers who always like to make sure “nothing screwy happened”. But as far as I’m concerned, if the draw function does what is specified in the VAO and arguments, then it has done its job properly. Likewise, if the shaders grab attributes where specified by layout location and binding syntax, the shaders have done their jobs properly too. If some moron writes OpenGL that puts the position in 16-byte location #0 and the normal in 16-byte location #1, but the shader decides to call location #0 “normal” and location #1 “position”… and proceed to write sensible code based upon those names, well, that is not my problem, that is not the problem of the VAO, that is not the problem of the OpenGL programmer, that is not the problem of the draw function, that is not the problem of GLSL, that is not the problem of the GPU… that is abject carelessness on the part of the shader writer. Of course it may be the OpenGL programmer made this mistake, not the shader programmer, in which case the careless moron is someone else.
It is great to have those query functions available for debugging… to help the programmers figure out who was the careless moron and fix the problem quickly. But there is no reason to couple the VAO and draw functions with the shaders and program objects. At least in my opinion.
Just to add a bit of color to my opinion, note that my vertices contain “zenith”, “north” and “east” vectors… not “normal”, “tangent” and “bi-tangent” vectors — even though they more-or-less mean the same thing. You might ask why. Does bootstrap just like to be a jerk? Just like to be different? No, not really. In fact, I hate the proliferation of vague and imprecise language! Which, in fact, is why I chose “zenith”, “north”, “east” for my vertex vector names. Fact is, I can easily, naturally, intuitively imagine looking down on any vertex on any surface and seeing the vector pointing towards the “zenith” from that vertex, another vector pointing “north” along the surface, and another vector pointing “east” along the surface. And you know what? I immediately know I’m working with a right-handed coordinate system, because otherwise one of those vectors would be pointing the opposite direction. Try to imagine such a clear and precise visualization based upon “normal”, “tangent”, “bitangent”. Good luck with that. Plus, you cannot know whether you are working with a right-handed or left-handed coordinate system.
And so, if someone wants to write a shader that contains the terms “normal”, “tangent”, “bitangent” for the vectors… he or she will have no problem understanding exactly what the “zenith”, “north”, “east” vectors in the engine mean… and how to map his names to the engine names. Likewise, if I wanted to write a shader to work with an engine that had “normal”, “tangent”, “bitangent” vectors, I could figure that out. Not quite as easily, of course, due to the question of handedness, and also due to the fact that nothing absolutely forces the “tangent” vector to point “north” (could be north, south, east or west) and nothing absolutely forces the “bitangent” vector to point 90-degrees clockwise or counterclockwise from the “normal” vector.
Anyway, too much babble I suppose. I appreciate that you started your explanation from a wise, sensible, practical point of view. But honestly, that is quite rare for OpenGL documentation and also for OpenGL conversations (probably because people tend to talk in ways they learned from OpenGL documentation or books).
PS: I know very well that writing well is extremely difficult. That’s one reason why I don’t write — not counting software applications… or screenplays. But I truly believe that OpenGL has a much worse reputation than it should have due to the ineffective way OpenGL is described in writing. Maybe (not sure) the specifications need to be written the way it is. But nothing else about OpenGL need be written so non-intuitively.
Thanks for the help.
Oh, one last comment. If someone ever does write a new OpenGL book (and boy should they ever!!!), they could make OpenGL vastly, vastly, vastly easier to understand if every non-trivial discussion included one to a few drawings to present the elements and their connections and encapsulations in a fully visual way. The human brain is extremely good at comprehending visual configurations and representations… vastly better than abstract lingo, none of which presents the entire structure or configuration in a single glance. If someone was to do write such a book, and present topics in the way you approached this one (from an operational or functional perspective), OpenGL would be vastly easier to consume and comprehend. Oh, and leave out OpenGl history, or put in an appendix somewhere (better yet, in some other book). Just present the latest and greatest and most powerful and lowest driver-overhead approaches… and leave out everything else. Let them read other books to accomplish things “the bad old lame ways”.[/SIZE]