All this would be easier to understand if the terminology was precise. Perhaps the terminology is precise, but I never found, read and internalized the clear definitions.
The terminology is precise. People on this forum are not.
Binding only ever happens between an object and the OpenGL context. The term for associating one object with with another object is “attachment”, not binding. Shader objects are attached to program objects. Texture objects are attached to program objects. Buffer Objects are attached to VAOs.
What might be the best way to clear this up for me is to state in pseudocode or word-description of exactly what the glDrawElements() function refers-to and does when it is called.
Very well.
OpenGL is a state machine. Most OpenGL function calls simply set various pieces of that state.
Objects in OpenGL are defined (in most cases) as a specific subset of the full OpenGL state. For example, here is the OpenGL context state for a texture object:
struct TextureObjectState
{
void *pTextureData;
InternalFormat eFormat;
TextureType eType; //1D, 3D, Cube, etc.
int iWidth;
int iHeight;
int iDepth;
TextureFilter eFilter;
int iMaxAnisotropy;
};
static TextureObjectState *pContextTextureState = new TextureObjectState();
Conceptually, the implementation of glTexture1D would look like this:
void glTextureImage1D()
{
DeleteTextureData(pContextTextureState->pTextureData);
pContextTextureState->pTextureData = AllocateTextureData(iWidth, eFormat);
pContextTextureState->iWidth = iWidth;
pContextTextureState->eFormat = eFormat;
pContextTextureState->eType = GL_TEXTURE_1D;
if(pData)
{
CopyTextureData(pContextTextureState->pTextureData, pData);
}
}
So, if you call “glBindTexture” with a texture object, what you do is this:
void glBindTexture(TextureObjectState *pNewObj)
{
pContextTextureState = pNewObj;
}
This leaves out the fact that you the user don’t directly have TextureObjectState objects. You have texture names that map to TextureObjectState objects. If you pass texture name 0, that automatically maps to the default TextureObjectState that was originally stored in pContextTextureState.
It should be obvious that calling any function that modifies texture object state after binding will cause these functions to modify the texture object you provided.
You asked about vertex state. Here’s what that looks like:
struct VertexAttributeState
{
bool bIsEnabled = false;
int iSize = 4; //This is the number of elements in each attrib, 1-4.
unsigned int iStride = 0;
VertexAttribType eType = GL_FLOAT;
bool bIsNormalized = false;
bool bIsIntegral = false;
void * pPtrOrBufferObjectOffset = 0;
BufferObject * pBufferObj = 0;
};
struct VertexArrayObjectState
{
BufferObject *pElementArrayBufferObject = NULL;
VertexAttributeState attributes[MAX_VERTEX_ATTRIB];
}
static VertexArrayObjectState *pContextVAOState = new VertexArrayObjectState();
static BufferObject *pCurrentArrayBuffer = NULL;
Coincidentally, VertexArrayObjectState is the sum total of state that gets stored in a Vertex Array Object. You can find the definition of all of these in the state tables section (6.2) of the OpenGL specification versions 3.1. Older versions have more state in them, but still retain this basic structure.
The remaining question is this: how do you set this state?
void glBindBuffer(enum target, uint buffer)
{
BufferObject *pBuffer = ConvNameToBufferObj(buffer);
switch(target)
{
case GL_ARRAY_BUFFER:
pCurrentArrayBuffer = pBuffer;
break;
case GL_ELEMENT_ARRAY_BUFFER:
pContextVAOState->pElementArrayBufferObject = pBuffer;
break;
...
}
}
void glEnableVertexAttribArray(uint index)
{
pContextVAOState->attributes[index].bIsEnabled = true;
}
void glDisableVertexAttribArray(uint index)
{
pContextVAOState->attributes[index].bIsEnabled = false;
}
void glVertexAttribPointer(uint index, int size, enum type, boolean normalized, sizei stride, const void *pointer)
{
VertexAttributeState &currAttrib = pContextVAOState->attributes[index];
bool bIsEnabled = false;
int iSize = 4; //This is the number of elements in each attrib, 1-4.
unsigned int iStride = 0;
VertexAttribType eType = GL_FLOAT;
bool bIsNormalized = false;
bool bIsIntegral = false;
void * pPtrOrBufferObjectOffset = 0;
BufferObject * pBufferObj = 0;
currAttrib.iSize = size;
currAttrib.eType = type;
currAttrib.iStride = stride;
currAttrib.bIsNormalized = normalized;
currAttrib.bIsIntegral = true;
currAttrib.pPtrOrBufferObjectOffset = pointer;
currAttrib.pBufferObj = pCurrentArrayBuffer;
}
This works just like any other state object. Allocated Vertex Array Objects will have their state set by these functions.
The last line of glVertexAttribPointer is the critical association between the Buffer Object and the Vertex Array Object. It uses whatever object is in pCurrentArrayBuffer (part of the OpenGL context). We saw above that this is set by glBindBuffer(GL_ARRAY_BUFFER).
Aside: you can see how stressed and strained the OpenGL object model is when you look at things like FBO and shader/program objects. When you bind an FBO to the context, it actually changes the state vector for the framebuffer. Shader and program objects don’t even use the context, except to be rendered. You set other objects directly into them.
glDrawElements is one of those functions that does not change OpenGL state. It is a rendering command. How it renders is dependent on the current state of OpenGL. All you need to do is set up the current OpenGL state to be what you need it to be and call glDrawElements.