glVertexAttribPointer are two drawbacks to glVertexAttribPointer , one of which is glVertexAttribPointer , and the other is objective.
The first drawback is its dependence on GL_ARRAY_BUFFER . This means that the behavior of glVertexAttribPointer depends on what was associated with GL_ARRAY_BUFFER during its GL_ARRAY_BUFFER . But as soon as it is called, what is connected with GL_ARRAY_BUFFER no longer matters; the reference to the buffer object is copied to the VAO. All this is not very intuitive and confusing even some experienced users.
It also requires that you specify the offset in the buffer object as a "pointer" rather than an integer byte offset. This means that you are performing an awkward cast from an integer to a pointer (which must match the same awkward cast in the driver).
The second drawback is that it combines two operations, which, logically, are completely different. To define an array of vertices that OpenGL can read, you must provide two things:
- How to get data from memory.
- What does this data look like.
glVertexAttribPointer provides both of them at the same time. GL_ARRAY_BUFFER buffer, as well as the offset “pointer” and step, determine where the data is stored and how to get it. Other parameters describe how one unit of data looks. Let's call it the vertex array format .
From a practical point of view, users are much more likely to change the source of vertex data than vertex formats. After all, many objects in the scene store their vertices the same way. Whatever the method, 3 floating-point numbers for a position, 4 unsigned bytes for colors, 2 short unsigned characters for text coordinates, etc. In general, you have only a few vertex formats.
While you have a lot more places from where you retrieve data. Even if all objects come from the same buffer, you probably want to update the offset in that buffer to switch from object to object.
With glVertexAttribPointer you cannot update only the offset. You must specify all format + buffer information at the same time. Everytime.
VAOs reduce the need to make all of these calls for each object, but it turns out that they do not really solve the problem. Yes, of course, you do not need to call glVertexAttribPointer . But this does not change the fact that changing vertex formats is expensive.
As already mentioned , changing vertex formats is quite expensive. When you link a new VAO (more precisely, when you render after linking a new VAO), the implementation either changes the vertex format independently, or must compare the two VAOs to see if the vertex formats they define are different. In any case, it does work that does not need to be done.
glVertexAttribFormat and glBindVertexBuffer both problems. glBindVertexBuffer directly points to a buffer object and takes the byte offset as an actual (64-bit) integer. So there is no inconvenient use of the GL_ARRAY_BUFFER binding; this binding is used solely to manipulate the buffer object.
And since these two separate concepts are now separate functions, you can have a VAO that stores the format, binds it, and then binds vertex buffers for each object or group of objects that you visualize. Changing the vertex buffer binding state is cheaper than the vertex format state.
Note that this separation is formalized in the GL 4.5 Direct Access API . That is, the DSA version glVertexAttribPointer ; You should use glVertexArrayAttribFormat and other separate format APIs.
Separate attribute binding functions work as follows. glVertexAttrib*Format The glVertexAttrib*Format functions provide all vertex formatting options for an attribute. Each of its parameters has the same meaning as the parameters from the equivalent call to glVertexAttrib*Pointer .
Everything is a bit confusing with glBindVertexBuffer .
Its first parameter is the index. But this is not a location attribute; it's just a buffer anchor point. This is a separate array from attribute locations with its own maximum limit. Thus, the fact that you are tying the buffer to index 0 means nothing about where attribute 0 gets its data from.
The relationship between buffer bindings and attribute locations is determined by glVertexAttribBinding . The first parameter is the location of the attribute, and the second is the buffer binding index, with which you can select the location of this attribute. Since the function name starts with "VertexAttrib", you should consider this as part of the state of the vertex format and therefore change it expensive.
The nature of the biases can be a little confusing at first. glVertexAttribFormat has an offset parameter. But glBindVertexBuffer does the glBindVertexBuffer . But these biases mean different things. The easiest way to understand the difference is to use an example of an interleaved data structure:
struct Vertex { GLfloat pos[3]; GLubyte color[4]; GLushort texCoord[2]; };
The vertex buffer binding offset determines the byte offset from the beginning of the buffer object to the first vertex index. That is, when you display index 0, the GPU will extract memory from the address of the buffer object + binding offset.
The vertex format offset determines the offset from the beginning of each vertex to the data of a particular attribute. If the data in the buffer is determined by Vertex , then the offset for each attribute will be:
glVertexAttribFormat(0, ..., offsetof(Vertex, pos)); //AKA: 0 glVertexAttribFormat(1, ..., offsetof(Vertex, color)); //Probably 12 glVertexAttribFormat(2, ..., offsetof(Vertex, texCoord)); //Probably 16
Thus, the anchor offset determines where vertex 0 is in memory, and format offsets determine where each attribute data is located inside the vertex.
The last thing to understand is that buffer binding is where the step is determined. This may seem strange, but think about it in terms of hardware.
The buffer binding should contain all the information necessary for the equipment to convert the vertex index or instance index into the memory area. Once this is done, the vertex format explains how to interpret the bytes in this memory location.
For the same reason, the instance divider is part of the buffer binding state through glVertexBindingDivisor . The divider must know the hardware in order to translate the instance index into a memory address.
Of course, this also means that you can no longer rely on OpenGL to calculate the step for you. In the above cast, you just use sizeof(Vertex) .
Separate attribute formats completely cover the old glVertexAttribPointer model so well that the old function is now fully defined in terms of the new:
void glVertexAttrib*Pointer(GLuint index, GLint size, GLenum type, {GLboolean normalized,} GLsizei stride, const GLvoid * pointer) { glVertexAttrib*Format(index, size, type, {normalized,} 0); glVertexAttribBinding(index, index); GLuint buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, buffer); if(buffer == 0) glErrorOut(GL_INVALID_OPERATION);
Note that this equivalent function uses the same index value for the attribute location and buffer binding index. If you use alternating attributes, you should avoid this where possible; instead, use the same buffer binding for all attributes that alternate from the same buffer.