I'm a bit rusty on the new vertex attrib binding extension (which these new vertex array DSA functions are an alternative for), but I'll give it a go.
In the beginning, there was nothing glVertexArribPointer would let you describe the format (type, size, etc) of a vertex attribute as well as where to source that data from, albeit this source was implicit from the currently bound vertex buffer object. Let's take your code as an example... so you have two vertex attributes:
Vertex attribute 0:
- Buffer: buffers[0]
- Index: 0
- Num components: 3
- Type: Float
- Stride to next element: 12 bytes (implicit from number of components and data type)
- Buffer offset of first element: 0 bytes
Vertex attribute 1:
- Buffer: buffers[1]
- Index: 1
- Num components: 2
- Type: Float
- Stride to next element: 8 bytes (implicit from number of components and data type)
- Buffer offset of first element: 0 bytes
This might make it clearer that there are two things being described for each attribute. The "layout" of the attribute, and the buffer from which to read the data.
The vertex attrib binding extension was Khronos' solution for separating these two descriptors. Instead of using glVertexAttribPointer, you use glVertexAttribFormat to describe the attribute format, glBindVertexBuffer to describe a buffer which can be read from and how to read from it, and glVertexAttribBinding to associate link formats with buffer sources (or their DSA counterparts).
Taking your code again, it would look something like this (non-DSA, because it's easier for me to write):
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
With the vertex attrib binding function, we get something like this (now in DSA):
glEnableVertexAttribArray(vao, 0);
glEnableVertexAttribArray(vao, 1);
// Setup the formats
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vao, 1, 2, GL_FLOAT, GL_FALSE, 0);
// Setup the buffer sources
glVertexArrayVertexBuffer(vao, 0, buffers[0], 0, 0); // Note the 2nd argument here is a 'binding index', not the attribute index
glVertexArrayVertexBuffer(vao, 1, buffers[1], 0, 0);
// Link them up
glVertexArrayAttribBinding(vao, 0, 0); // Associate attrib 0 (first 0) with binding 0 (second 0).
glVertexArrayAttribBinding(vao, 1, 1);
I've not tested this, but I'm pretty sure it works :)
The remaining question you probably have is why do both the vertex buffer and the vertex attribute have an offset argument? Let's imagine that you store multiple models in a single vertex buffer, and not all of them have the same format. You can set up the formats ahead of time, for example:
- Attrib 0: vec3, offset 0
- Attrib 1: vec2, offset 16
Your buffer sources will also have its own offset, which is the base from where to start reading. So you might have a model's vertex data stored in a vertex buffer, but it's at offset 320 bytes from the start. You can set this offset on the vertex buffer and the format doesn't have to take this into account as it and the buffer source are separated! This also lets you swap buffer sources and formats a whole lot more easily, instead of having to rebind a load of buffers and respecifying the attribute types every time, as you would need to with the original glVertexAttribPointer