Basically I'm writing a little program making the platonic solids.
I can reuse the shader for any like-shape, like cubes that have different sprite clips, but I cannot use a tetrahedron model with that same shader unless I re-create the VAO. This is because the indexes of glVertexAttribPointer
functions that will bind to the locations 0, 1, that are in the shader programme hard coded.
The thing is I didn't succeed in making the glDisableVertexAttribArray
functions work, to switch on and off the different models, so I need to literally recreate the VAO for each model.
Any work around this? Or is this not really an expensive thing to do? (I'm not re doing the VBOs, only the VAO)
It does not really matter much. Single VAO should work fine if you use it correctly.
But what you should be using instead is separate vertex attribute format. It allows to separate buffer binding from vertex format specification, so you can use same format (that shader expects) with different buffers/offsets in case you have different meshes to draw. It is way better way to switch buffers for draw call.
Hi Martins, thank you!
I assume that something like
void Model::generate_VAO() { glGenVertexArrays( 1, &m_VAO ); glBindVertexArray( m_VAO ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.points ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.texcoords ); glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_VBO.indexes ); glVertexAttribPointer( 2, 1, GL_BYTE, GL_FALSE, 0, NULL ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glEnableVertexAttribArray( 2 ); } would become what exactly? In the example in the link, we have ```cpp glBindVertexBuffer(0, buff, baseOffset, sizeof(Vertex)); glEnableVertexAttribArray(0); glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex, position)); glVertexAttribBinding(0, 0); glEnableVertexAttribArray(1); glVertexAttribFormat(1, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex, normal)); glVertexAttribBinding(1, 0); glEnableVertexAttribArray(2); glVertexAttribFormat(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, offsetof(Vertex, color)); glVertexAttribBinding(2, 0);
using a stride of zero and pointer to nothing since I'm breaking apart the arrays, would this sorta become
glBindVertexBuffer(0, points_VBO, 0, sizeof(points_vector)); glEnableVertexAttribArray(0); glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(0, 0); glBindVertexBuffer(1, texcoords_VBO, 0, sizeof(texcoords_vector)); glEnableVertexAttribArray(1); glVertexAttribFormat(1, 2, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(1, 0); glBindVertexBuffer(2, indexes_VBO, 0, sizeof(indexes_vector)); glEnableVertexAttribArray(2); glVertexAttribFormat(2, 1, GL_UNSIGNED_BYTE, GL_TRUE,0); glVertexAttribBinding(2, 0);
?
If yes, would this allow me to switch on/off the VertexAttribArray of each model so I can build the whole thing, VBO to VAO, only once and then just call the model for drawing?
You would bind buffers just before draw call. They would use format described by vertex attribs that can be shared between multiple buffers. This layout would be only shader specific (because vertex shader expects specific inputs), but not buffer specific. That's the advantage.
Old style glVertexAttribPointer combines format and VBO buffer binding. So you cannot change buffer without respecifying all the format.
Of course, if you're drawing one or two meshes, then all of this does not matter. Any way will work fine. Also if you're sharing VBO memory across meshes, then just using one VBO and specifying offsets in draw call will work better (with glDraw*BaseVertex calls).
Henlo,
I honestly still have no clue how to make it work, there is nothing too intuitive about it and I've tried a ton of combinations.
For instance, instead of doing this (generating VBO once and storing)
glGenBuffers( 1, &m_VBO.points ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.points ); glBufferData( GL_ARRAY_BUFFER, points.size()*sizeof(points[0]), points.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.texcoords ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.texcoords ); glBufferData( GL_ARRAY_BUFFER, texcoords.size()*sizeof(texcoords[0]), texcoords.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.indexes ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_VBO.indexes ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexes.size()*sizeof(indexes[0]), indexes.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.normals ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.normals ); glBufferData( GL_ARRAY_BUFFER, normals.size()*sizeof(normals[0]), normals.data(), GL_STATIC_DRAW );
... and then calling on EACH DRAW this:
void Model::generate_VAO() { #if 1 glGenVertexArrays( 1, &m_VAO ); glBindVertexArray( m_VAO ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.points ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.texcoords ); glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_VBO.indexes ); glVertexAttribPointer( 2, 1, GL_BYTE, GL_FALSE, 0, NULL ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.normals ); glVertexAttribPointer( 3, 3, GL_FLOAT, GL_FALSE, 0, NULL ); /// points and texture coords have to match the layout (location = x) in the shader /// programms, i.e. if the index for points is 0, it will be passed to the shader for /// the location = 0 enable_ArrayAttrib(); #endif }
... and right after drawing deleting the VAO or it piles up in the mem:
void Model::delete_VAO() { glDeleteVertexArrays(1, &m_VAO); }
... all of which works fine but surely has some performance penalty, I'm trying to use the new API and doing one or another variation of the code below, which is creating all at once, only once (which is the intended result), and then trying to just bind VAO before drawing.
(I'm assuming bindingindex
-- where I'm passing model_count -- would do the trick of separating the different model's VAOs, but does it? Is it supposed to do this? What is wrong there? I get errors)
void foo() { static int model_count; glGenBuffers( 1, &m_VBO.points ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.points ); glBufferData( GL_ARRAY_BUFFER, points.size()*sizeof(points[0]), points.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.texcoords ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.texcoords ); glBufferData( GL_ARRAY_BUFFER, texcoords.size()*sizeof(texcoords[0]), texcoords.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.indexes ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_VBO.indexes ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexes.size()*sizeof(indexes[0]), indexes.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.normals ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.normals ); glBufferData( GL_ARRAY_BUFFER, normals.size()*sizeof(normals[0]), normals.data(), GL_STATIC_DRAW ); #if 1 glGenVertexArrays( 1, &m_VAO ); glBindVertexArray( m_VAO ); glVertexArrayVertexBuffer(m_VAO, model_count, m_VBO.points, 0, points.size()*sizeof(points[0])); glEnableVertexAttribArray(0); glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(0, model_count); glVertexArrayVertexBuffer(m_VAO, model_count, m_VBO.texcoords, 0, texcoords.size()*sizeof(texcoords[0])); glEnableVertexAttribArray(1); glVertexAttribFormat(1, 2, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(1, model_count); glVertexArrayVertexBuffer(m_VAO, model_count, m_VBO.indexes, 0, indexes.size()*sizeof(indexes[0])); glEnableVertexAttribArray(2); glVertexAttribFormat(2, 1, GL_UNSIGNED_BYTE, GL_FALSE, 0); glVertexAttribBinding(2, model_count); glVertexArrayVertexBuffer(m_VAO, model_count, m_VBO.normals, 0, normals.size()*sizeof(normals[0])); glEnableVertexAttribArray(3); glVertexAttribFormat(3, 3, GL_FLOAT, GL_FALSE, 0); glVertexAttribBinding(3, model_count); ++model_count; #endif }
Would you be so kind as to provide some code example?
Help :)
There are multiple problems with your code.
First - you are mixing 4.5 style (ARB_direct_state_access extension) with older calls together. You should not do that - it makes everything look strange and hard to think about.
Either use all DSA style call (glVertexArrayVertexBuffer, glVertexArrayAttribFormat) or none of them (glBindVertexBuffer, glVertexAttribFormat).
Second - index buffer is not an attribute. It's a separate thing. Not part of vertex attribute setup. This is a problem in your generate_VAO function too. Call to glVertexAttribPointer
with index 2 is useless.
Third - if you're storing each attribute in separate VBO (why? don't do this), then your model_count
value for each glVertexAttribBinding
call should be different. It is index of VBO binding.
Before each draw call bind all three of your buffers to these bindings - glVertexArrayVertexBuffer
call with second argument set to index.
Basically in setup step you create VAO and set Format/Binding/Enable things - specify different index for each attribute. Like in your case 0=poosition, 1=texcoord, 3=normals. For each index you specify which buffer you'll use with Binding function. If everything is in same VBO, then just use 0, otherwise specify whatever numbers.
Then at draw step you bind VAO, and bind all the buffers to whichever binding index you need. Then do draw call.
I have different data that I prefer to keep separate for now (points, texcoords, indexes, normals), thus I use for each one of those a separate VBO. I tested and a single VBO for all won't work.
It would be helpful if there is some code example, how would you rewrite the code above in new API such that, according to you, it will be possible to not having to re-create the VAO of each model every time I call draw, but instead just call bind_VAO for each model and just draw?
Constants:
// attribute index in GLSL shader const GLint a_pos = 0; const GLint a_texcoords = 1; const GLint a_normals = 2; // vertex buffer index used in setup & draw calls const GLuint vbuf_index_pos = 0; const GLuint vbuf_index_texcoords = 1; const GLuint vbuf_index_normals = 2;
Format setup:
GLuint vao; glCreateVertexArrays(1, &vao); glVertexArrayAttribFormat(vao, a_pos, 3, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(vao, a_pos, vbuf_index_pos); glEnableVertexArrayAttrib(vao, a_pos); glVertexArrayAttribFormat(vao, a_texcoords, 2, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(vao, a_texcoords, vbuf_index_texcoords); glEnableVertexArrayAttrib(vao, a_texcoords); glVertexArrayAttribFormat(vao, a_normals, 3, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(vao, a_normals, vbuf_index_normals); glEnableVertexArrayAttrib(vao, a_normals);
mesh setup:
GLuint vbuf_pos; GLuint vbuf_texcoord; GLuint vbuf_normals; GLuint ibuf; // can create all of them with one call, just pass array of 4 glCreateBuffers(1, &vbuf_pos); glCreateBuffers(1, &vbuf_texcoord); glCreateBuffers(1, &vbuf_normals); glCreateBuffers(1, &ibuf); glNamedBufferStorage(vbuf_pos, points.size()*sizeof(points[0]), points.data()); glNamedBufferStorage(vbuf_texcoord, texcoords.size()*sizeof(texcoords[0]), texcoords.data()); glNamedBufferStorage(vbuf_normals, normals.size()*sizeof(normals[0]), normals.data()); glNamedBufferStorage(ibuf, indexes.size()*sizeof(indexes[0]), indexes.data());
draw call:
// do it once glBindVertexArray(format.vao); // for each mesh do: glVertexArrayVertexBuffer(format.vao, vbuf_index_pos, mesh.vbuf_pos, 0, sizeof(PointStruct)); glVertexArrayVertexBuffer(format.vao, vbuf_index_texcoords, mesh.vbuf_texcoord, 0, sizeof(TexcoordStruct)); glVertexArrayVertexBuffer(format.vao, vbuf_index_normals, mesh.vbuf_normals, 0, sizeof(NormalStruct)); glVertexArrayElementBuffer(format.vao, mesh.ibuf); glDrawElements(GL_TRIANGLES, mesh.vertex_count, GL_UNSIGNED_BYTE, NULL);
This code will require at least GL 4.5 version. Or ARB_direct_state_access extension presence).
Be aware that interleaved attributes in one buffer will be better for performance.
BTW this is totally wrong, I tested by commenting that part and program crashes.
No, it is not wrong. Index buffer is not part of vertex attributes. It is completely different thing. What exactly for would you use attribute with index 2 in your GLSL shader?
If something crashes then some other thing in your code is incorrect - if you specify vertex attributes in wrong way then it is very likely it will crash somewhere in GL driver, as it will try reading from bad pointers.
Try enabling debug callback, it usually will tell about incorrect bindings / vertex pointer values. See https://gist.github.com/mmozeiko/ed2ad27f75edf9c26053ce332a1f6647#file-win32_opengl-c-L325-L327 + line 305.
If something crashes then some other thing in your code is incorrect
Feel free to point it out, important parts of my code are above but I'll post it moire complete below (not using the pointer to indexes now).
Model::Model(const V_Float& points, const V_Float& texcoords, const V_Byte& indexes, const V_Float& normals) { generate_VBOs(points, texcoords, indexes, normals); generate_VAO(); /// THIS IS EXTREMELY NECESSARY, for each VAO binding, first we need to unbind /// it to zero or there will be conflicts/bugs glBindVertexArray(0); m_indexes_count = indexes.size(); } Model::~Model() { glDeleteVertexArrays(1, &m_VAO); glDeleteBuffers(1, &m_VBO.indexes); glDeleteBuffers(1, &m_VBO.texcoords); glDeleteBuffers(1, &m_VBO.points); glDeleteBuffers(1, &m_VBO.normals); } void Model::generate_VBOs(const V_Float& points, const V_Float& texcoords, const V_Byte& indexes, const V_Float& normals) { glGenBuffers( 1, &m_VBO.points ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.points ); glBufferData( GL_ARRAY_BUFFER, points.size()*sizeof(points[0]), points.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.texcoords ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.texcoords ); glBufferData( GL_ARRAY_BUFFER, texcoords.size()*sizeof(texcoords[0]), texcoords.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.indexes ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_VBO.indexes ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexes.size()*sizeof(indexes[0]), indexes.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_VBO.normals ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.normals ); glBufferData( GL_ARRAY_BUFFER, normals.size()*sizeof(normals[0]), normals.data(), GL_STATIC_DRAW ); } void Model::generate_VAO() { glGenVertexArrays( 1, &m_VAO ); glBindVertexArray( m_VAO ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.points ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.texcoords ); glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO.normals ); glVertexAttribPointer( 2, 3, GL_FLOAT, GL_FALSE, 0, NULL ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_VBO.indexes ); /// points and texture coords have to match the layout (location = x) in the shader /// programms, i.e. if the index for points is 0, it will be passed to the shader for /// the location = 0 glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glEnableVertexAttribArray( 2 ); } void Model::bind_VAO() { glBindVertexArray(m_VAO); } void Model::unbind_VAO() { glBindVertexArray(0); }
Drawing call is then
void draw(Shader* shader, Entity* entity, Model* model, GLuint texture) { shader->load_model_matrix( transform_model(entity).m ); model->bind_VAO(); glBindTexture(GL_TEXTURE_2D, texture); glDrawElements(GL_TRIANGLES, model->indexes_count(), GL_UNSIGNED_BYTE, (GLvoid*)0); model->unbind_VAO(); }; ///////////////////////////// shader.use_programme(); shader.load_projection_matrix(proj_mat.m); /// camera shader.load_view_matrix( transform_view(&cam).m ); /// Tetrahedron draw(&shader, &tetra, &tetra_model, tex_tetra); /// Cube draw(&shader, &dice, &dice_model, tex_dice); /// Octahedron draw(&shader, &octa, &octa_model, tex_octa); /// Dodecahedron draw(&shader, &dodecad, &dodecad_model, tex_dodecad); // Icosahedron draw(&shader, &icosa, &icosa_model, tex_icosa);
Hence the need to switch VAO properly.
After trial and error I finally discovered what was wrong, after each draw I need to "unbind" current VAO by calling glBindVertexArray(0)
before binding the next VAO. This is what model->unbind_VAO();
above does.
Calling that function solely with a new VAO does not work as expected, i.e., won't just make current VAO "current" within openGL context or whatever, which is very counter intuitive unless there is a good reason to have more than one VAO bound at the same time.
Ok so this is the solution to using a single VBO but still keeping different vectors for those vertices, now this is becoming extremely clear how it works.
void Model::generate(const V_Float& points, const V_Float& texcoords, const V_Byte& indexes, const V_Float& normals) { /// OBS: pay attention to order, we make an huge single vector so we just /// commit is all to a single VBO, then pass the proper pointer offsets to /// glVertexAttribPointer, and make sure layouts match in teh shader code /// V_Float vdata; for (auto var : points) { vdata.push_back(var); } for (auto var : normals) { vdata.push_back(var); } for (auto var : texcoords) { vdata.push_back(var); } glGenVertexArrays( 1, &m_VAO ); glBindVertexArray( m_VAO ); glGenBuffers( 1, &m_VBO ); glBindBuffer( GL_ARRAY_BUFFER, m_VBO ); glBufferData( GL_ARRAY_BUFFER, vdata.size()*sizeof(vdata[0]), vdata.data(), GL_STATIC_DRAW ); glGenBuffers( 1, &m_EBO ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_EBO ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexes.size()*sizeof(indexes[0]), indexes.data(), GL_STATIC_DRAW ); size_t points_offset = sizeof(points[0])*points.size(); size_t normals_offset = sizeof(normals[0])*normals.size(); /// points glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); /// normals glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 0, (void*)(points_offset) ); /// texture coordinates glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, 0, (void*)(points_offset+normals_offset) ); /// points and texture coords have to match the layout (location = x) in the shader /// programms, i.e. if the index for points is 0, it will be passed to the shader for /// the location = 0 glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glEnableVertexAttribArray( 2 ); }
of course you would like to just commit all of this to some struct or vector to begin with and not copy all these data back and forth
There is no need to unbind VAO for this to work. If there is any problem, then it is in other place of your code. For example, if some other place is doing vertex pointer operations without their own VAO bound, then it will overwrite vertex pointers bindings in whatever was previously bound.