OpenGL: Is VAO creation expensive?

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?


Replying to mmozeiko (#29701)

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).

How would code look like in that example?


Replying to mmozeiko (#29707)

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 :)


Edited by da447m on Reason: small msitake in sizeof
Replying to mmozeiko (#29707)

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?


Replying to mmozeiko (#29714)

Call to glVertexAttribPointer with index 2 is useless.

BTW this is totally wrong, I tested by commenting that part and program crashes.


Replying to da447m (#29715)

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.


Edited by Mārtiņš Možeiko on
Replying to da447m (#29715)

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.


Edited by Mārtiņš Možeiko on
Replying to da447m (#29716)

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.


Edited by da447m on
Replying to mmozeiko (#29718)

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.


Replying to da447m (#29721)

My entire code is there, there is literally nothing else regarding VAO and VBO. It only works if I [un]bind VAO to zero.


Replying to mmozeiko (#29728)