I’ve been working on a 3D simulation/game engine for a while, and while it works, I still don’t fully understand all the relationships between:
[FONT=courier new]#1: VAO == vertex array object
#2: IBO == GL_ELEMENT_ARRAY_BUFFER object
#3: VBO == GL_ARRAY_BUFFER object
#4: vertex-attributes
#5: uniform variables
#6: shader objects
#7: program objects
And especially, when various actions need to be taken (when certain OpenGL functions need to be called).
I suppose I better briefly describe the nature of the engine to provide context.
The engine contains “batches”, each of which contains a VAO, IBO, VBO. Each batch contains objects that are “compatible” in the sense they are composed of the same primitives (points, lines or triangles/surfaces), their vertices are arrays of the same vertex structure (and thus all have identical attributes at identical byte offsets), all access the same images (texturemaps, surfacemans, conemaps, etc) from the same array textures and texture-units, all can be rendered with the same program object (and thus set of shaders).
Therefore, to draw everything for a frame just requires looping through all the batches and calling glDrawElements() once, passing in the number of indices in the IBO and a starting offset of zero.
Whenever a batch becomes full, or when an object is added that is composed of a different type of primitive, or has a different vertex layout, or is to be rendered by a different program object, or requires any image (texturemap, surfacemap, conemap, etc) that is not currently available in the currently active array textures… a new batch is created for that object and any future object that is compatible.
So far, all objects have the same vertex structure and other requirements stated in the previous paragraph… except for objects that expect to be rendered as points or lines, which naturally cause new batches to be created for them.
When a new batch is created, a new VAO, IBO, VBO is created and their OpenGL identifiers are saved in the batch structure for future reference. Also, the following code is executed to specify the byte-offsets of each vertex-attribute in every vertex in the VBO, and since the VAO, IBO, VBO are first made active by calling glBindVertexArray() and glBindBuffer()[FONT=palatino linotype][FONT=courier new] [/FONT][/FONT][FONT=palatino linotype]and[/FONT][FONT=palatino linotype][FONT=courier new] glBindBuffer() [/FONT] [/FONT](in that order), I assume the IBO and byte-offsets of all vertex-attributes are recorded properly in the VAO.
As an aside, later I plan to have fewer but huge (maximum size) VBO objects that contain vertices for many batches and thus many IBO. For some reason I didn’t originally realize the one-for-one mapping of IBO and VBO was unnecessary and also unwise. But for now, there is one VAO, IBO, VBO for each batch.
//
// get offset to each element in ig_vertex32 structure (vertex structure we put into VBO)
//
u08* base = (u08*)&vertex;
u08* offset0 = (u08*)((u08*)&vertex.position - (u08*)base); // 0x0000 to 0x000F ::: 4 * f32 == 3 * f32 position.xyz + 1 * f32 east.x
u08* offset1 = (u08*)((u08*)&vertex.zenith - (u08*)base); // 0x0010 to 0x001F ::: 4 * f32 == 3 * f32 zenith.xyz + 1 * f32 east.y
u08* offset2 = (u08*)((u08*)&vertex.north - (u08*)base); // 0x0020 to 0x002F ::: 4 * f32 == 3 * f32 north.xyz + 1 * f32 east.z
u08* offset3 = (u08*)((u08*)&vertex.color - (u08*)base); // 0x0030 to 0x0033 ::: 4 * u08 == 4 * u08 color.rgba
u08* offset4 = (u08*)((u08*)&vertex.tcoord - (u08*)base); // 0x0034 to 0x0037 ::: 2 * u16 == 2 * u16 tcoord.xy
u08* offset5 = (u08*)((u08*)&vertex.mixmatsay - (u08*)base); // 0x0038 to 0x003B ::: 1 * u32 == 2 * u32 mixmatsay.xy
//
// define vertex attributes --- each corresponds to one component of the vertex structure we put into the VBO
//
glVertexAttribPointer (0, 4, GL_FLOAT, 0, vbytes, offset0); // 4 * f32 == position.xyz + east.x
glVertexAttribPointer (1, 4, GL_FLOAT, 0, vbytes, offset1); // 4 * f32 == zenith.xyz + east.y
glVertexAttribPointer (2, 4, GL_FLOAT, 0, vbytes, offset2); // 4 * f32 == north.xyz + east.z
glVertexAttribPointer (3, 4, GL_UNSIGNED_BYTE, 1, vbytes, offset3); // 4 * u08 == color.rgba
glVertexAttribPointer (4, 2, GL_UNSIGNED_SHORT, 1, vbytes, offset4); // 2 * u16 == tcoord.xy
glVertexAttribIPointer (5, 2, GL_UNSIGNED_INT, vbytes, offset5); // 2 * u32 == mixmatsay.xy
//
// enable every vertex attribute ::: causes OpenGL to automatically transfer these vertex attributes from VBO to each vertex shader before vertex shader execution starts
//
glEnableVertexAttribArray (0); // enable vertex position.xyz : east.x
glEnableVertexAttribArray (1); // enable vertex zenith.xyz : east.y
glEnableVertexAttribArray (2); // enable vertex north.xyz : east.z
glEnableVertexAttribArray (3); // enable vertex color.rgba
glEnableVertexAttribArray (4); // enable vertex tcoord.xy
glEnableVertexAttribArray (5); // enable vertex mixmatsay.xy
batch->index_position = glGetAttribLocation (glprogram, "ig_position"); // position.xyz + east.x ::: in vertex shader == layout (location = 0) in vec4 ig_position; // vertex position.xyz ::: position.w contains east.x
batch->index_zenith = glGetAttribLocation (glprogram, "ig_zenith"); // zenith.xyz + east.y ::: in vertex shader == layout (location = 1) in vec4 ig_zenith; // vertex zenith.xyz vector AKA normal vector ::: zenith.w contains east.y
batch->index_north = glGetAttribLocation (glprogram, "ig_north"); // north.xyz + east.z ::: in vertex shader == layout (location = 2) in vec4 ig_north; // vertex north.xyz vector AKA bitangent vector ::: north.w contains east.z
batch->index_color = glGetAttribLocation (glprogram, "ig_color"); // color.rgba ::: in vertex shader == layout (location = 3) in vec4 ig_color; // vertex color.rgba
batch->index_tcoord = glGetAttribLocation (glprogram, "ig_tcoord"); // tcoord.xy ::: in vertex shader == layout (location = 4) in vec4 ig_tcoord; // vertex tcoord.xy
batch->index_mixmatsay = glGetAttribLocation (glprogram, "ig_mixmatsay"); // mixmatsay == mixid, tmatid, saybit ::: in vertex shader == layout (location = 5) in ivec2 ig_mixmatsay; // vertex mixmatsay.xy ::: mixmatsay.x == tmapid, smapid, cmapid, xmapid : mixmatsay.y = tmapid, saybit
//
// should the slots of uniform variables be specified here... or somewhere else?
//
batch->index_transform = glGetUniformLocation (glprogram, "ig_transform"); // uniform variable == transform matrix (modelviewprojection) - xxxxx
batch->index_clight0 = glGetUniformLocation (glprogram, "ig_clight0"); // uniform variable == light #0 color
batch->index_clight1 = glGetUniformLocation (glprogram, "ig_clight1"); // uniform variable == light #1 color
batch->index_clight2 = glGetUniformLocation (glprogram, "ig_clight2"); // uniform variable == light #2 color
batch->index_clight3 = glGetUniformLocation (glprogram, "ig_clight3"); // uniform variable == light #3 color
batch->index_plight0 = glGetUniformLocation (glprogram, "ig_plight0"); // uniform variable == light #0 position
batch->index_plight1 = glGetUniformLocation (glprogram, "ig_plight1"); // uniform variable == light #1 position
batch->index_plight2 = glGetUniformLocation (glprogram, "ig_plight2"); // uniform variable == light #2 position
batch->index_plight3 = glGetUniformLocation (glprogram, "ig_plight3"); // uniform variable == light #3 position
batch->index_pcamera = glGetUniformLocation (glprogram, "ig_pcamera"); // uniform variable == camera position == active camera
batch->index_tmap = glGetUniformLocation (glprogram, "ig_tmap"); // texture-map #0 == texture-unit #0 : texture maps
batch->index_smap = glGetUniformLocation (glprogram, "ig_smap"); // texture-map #1 == texture-unit #1 : surface maps
batch->index_cmap = glGetUniformLocation (glprogram, "ig_cmap"); // texture-map #2 == texture-unit #2 : cone maps
batch->index_xmap = glGetUniformLocation (glprogram, "ig_xmap"); // texture-map #3 == texture-unit #3 : x maps (unknown maps)
More and more of the IBO and VBO are filled with glBufferSubData() as new shape objects are created and their indices and vertices are appended to the IBO and VBO assigned to that batch.
QUESTION: I assume that [maybe] I need to record the vertex-attribute “layout locations” in the batch structure as done in the code snippet above. But is that necessary? Will making the VAO of a given batch active by calling glBindVertexArray() do everything necessary inform OpenGL of the vertex-attribute byte offsets and vertex-attribute “layout locations” in the future… when batches are drawn?
NOTE: Standard practice for my shaders is to fully specify layout (location == index) for every vertex-attribute and every uniform variable/vector/matrix. Therefore, whenever any program object is made active by calling glUseProgram(), OpenGL has to know the locations of every vertex-attribute and every uniform variable/vector/matrix without my code specifying any locations. In fact, my engine never specifies any locations, it only queries and saves the locations of vertex-attrivutes and uniform variables/vectors/matrices from OpenGL to save in its various structures (batch structures and program structures).
When the batches are drawn, code like the following is executed:
u32 vaoid = batch->vaoid;
u32 iboid = batch->iboid;
u32 vboid = batch->vboid;
u32 ptype = batch->primitive;
u32 icount = batch->ielementn;
//
// bind VAO
//
if (vaoid != glstate.active_vao) {
glBindVertexArray (vaoid);
glstate.active_vao = vaoid;
}
//
// bind IBO
//
if (iboid != glstate.active_ibo) {
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, iboid);
glstate.active_ibo = iboid;
}
//
// bind VBO
//
if (vboid != glstate.active_vbo) {
glBindBuffer (GL_ARRAY_BUFFER, vboid);
glstate.active_vbo = vboid;
}
switch (ptype) {
case IG_PRIMITIVE_TRIANGLE: gmode = GL_TRIANGLES; break;
case IG_PRIMITIVE_LINE: gmode = GL_LINES; break;
case IG_PRIMITIVE_POINT: gmode = GL_POINTS; break;
default: gmode = GL_TRIANGLES; break;
}
if (icount) {
glDrawElements (gmode, icount, GL_UNSIGNED_INT, 0); // 32-bit indices only
}
NOTE: As a matter of general policy, in every case before the engine changes any OpenGL state, it checks to see whether the same state is already set, and if so, skips calling the OpenGL function to set that state. Hence that strange code you see above where any OpenGL state is set.
Elsewhere, when a program object is created, the following code is executed in ig_program_create() where program objects are created:
//
// set OpenGL "program object" in IG "program object" structure
//
program->objid_vshader = objid_vshader; // objid of vertex shader
program->objid_tcshader = objid_tcshader; // objid of tesselate_control shader
program->objid_teshader = objid_teshader; // objid of tesselate_evaluate shader
program->objid_gshader = objid_gshader; // objid of geometry shader
program->objid_fshader = objid_fshader; // objid of fragment shader
program->objid_yshader = objid_yshader; // objid of y shader
program->objid_zshader = objid_zshader; // objid of z shader
program->objid_cshader = objid_cshader; // objid of compute shader
glerror = glGetError();
//
// all the following slots for vertex-attributes and uniform variables are known for the program object because this information is explicitly specified in the shaders with "layout" and "location" syntax
// - note that program objects do not care about byte offsets of the various vertex-attributes within the vertex structure because each attribute is delivered to the shaders in a specific 16-byte "location"
// - therefore, for example, even though color.rgba only consumes 32-bits in the vertex (4 * u08 RGBA elements), color is converted by OpenGL to (4 * f32 RGBA elements) and consumes all of "location 3" AKA "slot 3"
// - therefore, for example, even though tcoord.xy only consumes 32-bits in the vertex (2 * u16 x,y elements), tcoord.xy is converted by OpenGL to (4 * f32 xy00 elements) and consumes all of "location 4" AKA "slot 4"
//
program->index_position = glGetAttribLocation (glprogram, "ig_position"); // position.xyz + east.x ::: in vertex shader == layout (location = 0) in vec4 ig_position; // vertex position.xyz ::: position.w contains east.x
program->index_zenith = glGetAttribLocation (glprogram, "ig_zenith"); // zenith.xyz + east.y ::: in vertex shader == layout (location = 1) in vec4 ig_zenith; // vertex zenith.xyz vector AKA normal vector ::: zenith.w contains east.y
program->index_north = glGetAttribLocation (glprogram, "ig_north"); // north.xyz + east.z ::: in vertex shader == layout (location = 2) in vec4 ig_north; // vertex north.xyz vector AKA bitangent vector ::: north.w contains east.z
program->index_color = glGetAttribLocation (glprogram, "ig_color"); // color.rgba ::: in vertex shader == layout (location = 3) in vec4 ig_color; // vertex color.rgba
program->index_tcoord = glGetAttribLocation (glprogram, "ig_tcoord"); // tcoord.xy ::: in vertex shader == layout (location = 4) in vec4 ig_tcoord; // vertex tcoord.xy
program->index_mixmatsay = glGetAttribLocation (glprogram, "ig_mixmatsay"); // mixmatsay == mixid, tmatid, saybit ::: in vertex shader == layout (location = 5) in ivec2 ig_mixmatsay; // vertex mixmatsay.xy ::: mixmatsay.x == tmapid, smapid, cmapid, xmapid : mixmatsay.y = tmatid, saybit
glerror = glGetError();
//
// all the following slots for uniform variables are known for the program object because this information is explicitly specified in the shaders with "layout" and "location" syntax
// - every uniform variable or vector consumes exactly one 16-byte "location" AKA "slot"
// - every uniform matrix consumes exactly four 16-byte "locations" or "slots"
//
program->index_transform = glGetUniformLocation (glprogram, "ig_transform"); // uniform variable == transform matrix (modelviewprojection) == layout (location = 0) uniform mat4 ig_traansform
program->index_clight0 = glGetUniformLocation (glprogram, "ig_clight0"); // uniform variable == light #0 color == layout (location = 4) uniform vec4 ig_clight0
program->index_clight1 = glGetUniformLocation (glprogram, "ig_clight1"); // uniform variable == light #1 color == layout (location = 4) uniform vec4 ig_clight1
program->index_clight2 = glGetUniformLocation (glprogram, "ig_clight2"); // uniform variable == light #2 color == layout (location = 4) uniform vec4 ig_clight2
program->index_clight3 = glGetUniformLocation (glprogram, "ig_clight3"); // uniform variable == light #3 color == layout (location = 4) uniform vec4 ig_clight3
program->index_plight0 = glGetUniformLocation (glprogram, "ig_plight0"); // uniform variable == light #0 position == layout (location = 4) uniform vec4 ig_plight0
program->index_plight1 = glGetUniformLocation (glprogram, "ig_plight1"); // uniform variable == light #1 position == layout (location = 4) uniform vec4 ig_plight1
program->index_plight2 = glGetUniformLocation (glprogram, "ig_plight2"); // uniform variable == light #2 position == layout (location = 4) uniform vec4 ig_plight2
program->index_plight3 = glGetUniformLocation (glprogram, "ig_plight3"); // uniform variable == light #3 position == layout (location = 4) uniform vec4 ig_plight3
program->index_pcamera = glGetUniformLocation (glprogram, "ig_pcamera"); // uniform variable == camera position == active camera == layout (location = 4) uniform vec4 ig_pcamera
program->index_tmap = glGetUniformLocation (glprogram, "ig_tmap"); // texture-map #0 == texture-unit #0 : texture maps == layout (location = 4) uniform vec4 ig_tmap
program->index_smap = glGetUniformLocation (glprogram, "ig_smap"); // texture-map #1 == texture-unit #1 : surface maps == layout (location = 4) uniform vec4 ig_smap
program->index_cmap = glGetUniformLocation (glprogram, "ig_cmap"); // texture-map #2 == texture-unit #2 : cone maps == layout (location = 4) uniform vec4 ig_cmap
program->index_xmap = glGetUniformLocation (glprogram, "ig_xmap"); // texture-map #3 == texture-unit #3 : x maps ? == layout (location = 4) uniform vec4 ig_xmap
glerror = glGetError();
//
// put OpenGL program object identifier
//
program->glprogram = glprogram; // OpenGL "program object" identifier
QUESTION: This code captures the objid of the shaders in this program, the layout (location = n) of the vertex-attributes, and the layout (location = n) of the uniform variables/vectors/matrices in the program object structure so data can be written to the appropriate [16-byte] “location” in the default uniform block (and later into a UBO). But as with much of this topic, I’m not sure where this information should be captured, how much needs to be saved, and when these saved values might be needed for some reason. My only though at the moment is the following. When the engine needs to write variables, vectors or matrices into a UBO, presumably the engine should grab the value from one of these program->index_xxxxxx variables in the program object structure, write data to that location with one of the glUniform*() functions… or multiply the location by 16-bytes to know where to write into a CPU memory buffer that will later be written to a UBO in the GPU. But… I have a difficult time keeping this straight.
Perhaps the real question should be… when is any of this information needed? What needs to be set up before any OpenGL function is called?
Let me just babble and bit, make some guesses, and correct me when I’m wrong. Or just provide a nice clean statement of what needs to be done when.
***** create batch == create VAO, IBO, VBO *****
- create and bind VAO, IBO, VBO
- specify vertex-attribute byte-offsets by calling glVertexAttribPointer() or glVertexAttribIPointer()
- enable vertex-attributes by calling glEnableVertexAttribArray() for each vertex-attribute == location
- capture and save in batch object structure the layout location of each vertex-attribute by calling glGetAttribLocation()
- capture and save in batch object structure the layout location of each uniform variable/vector/matrix by calling glGetUniformLocation()
That’s’ what the code does, but thinking about this makes the last item look stupid because the uniform variables don’t really have much of anything directly to do with the batch. And besides, presumably the only way glGetUniformLocation() can return values at all is because some OpenGL program object was previously made active by calling glUseProgram(). But does the program object necessarily have anything to do with any given batch? Doesn’t seem so… off hand at least. So maybe storing the layout location of the uniform variables during batch create is stupid.
***** write indices/elements into IBO *****
- bind the IBO if not already active
- write new or updated indices/elements into IBO by calling glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, … )
- no need to bind associated VAO ???
- no need to bind associated VBO ???
***** write vertices into[FONT=courier new] VBO *****
- bind the VBO if not already active
- write new or updated vertices into VBO by calling glBufferSubData(GL_VERTEX_ARRAY_BUFFER, … )
- no need to bind associated VAO ???
- no need to bind associated IBO ???
***** draw batch of shape objects *****
- bind the VAO created to serve this batch
- bind the IBO created to serve this batch — is this necessary ???
- bind the VBO created to serve this batch — is this necessary ???
- update conents of array-textures on texture-units 0, 1, 2, 3… 29, 30, 31 as needed
[FONT=palatino linotype] - make active the OpenGL program object appropriate to draw this batch of objects - set any uniform values this program accesses that may have changed since the last draw
- call[FONT=courier new] glDrawElements() to draw this batch of shape objects
[/FONT][/FONT]
What else?
What else happens that has anything to do with vertex-attributes?
What else happens that has anything to do with any uniform value or a UBO?
Frankly, I’m not even sure the above is the correct way to ask these questions. Hopefully the above at least says enough to spur someone to reply with a simple and concise statement of what needs to be done and when (before something else is done). It really can’t be as complicated as it seems.
QUESTION: Maybe VAO and program objects operate utterly and totally independently. Is that correct? Does anything in OpenGL keep VAOs, vertices and vertex-attributes consistent with the active program? Can a VAO have a totally different number (and types, and significance) of vertex-attributes than the active program expects?
Anyway, if anyone really understands all these [somewhat] related topics and how they fit together… please try to explain so a dummy like me can comprehend.
[/FONT][/FONT]