OpenGL Drawing Multiple VBOs in a VAO

This topic is 1264 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

I'm trying to finish my prototype sprite packer, and I'm close. Everything packs well, and all of the textures load. I'm going to stay away from using texture arrays for now, and just draw every sprite with its own draw call since each sprite has its own unique texture. All my sprites use the same vertex struct so they can all share the same VAO, so that's somewhat efficient.

I'm able to draw each quad, and my system uses each quad's corresponding texture correctly. The problem is, all the vertices for all VBOs seem to use the vertices calculated for the last SpriteBinRect's VBO I buffered data into. So, instead of having all of my sprites draw onscreen in their correct positions and sizes, they all draw in the same spot and the same size as the last sprite's vertices to be generated.

I start off by creating my VAO in my SpritePacker class' constructor:

SpritePacker::SpritePacker() : BinPacker()
{
// generate VAO
glGenVertexArrays(1, &vao); // get an unused name
glBindVertexArray(vao); // allocate the name
}


Next, I load each image that'll create a corresponding SpriteBinRect. I'm loading around 70 images in a loop right after calling the SpritePacker constructor above, and it looks like this:

SpriteBinRect::SpriteBinRect(GLint width, GLint height, GLenum imageFormat, GLenum type, void *data) : BinRect((float)width, (float)height)
{
// allocate and setup the texture
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &handle);
glBindTexture(GL_TEXTURE_2D, handle);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, imageFormat, type, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// generate the VBO
glGenBuffers(1, &vbo); // get an unused name
glBindBuffer(GL_ARRAY_BUFFER, vbo); // allocate it
}


Then, I add each rect into the SpritePacker instance, and run the packing algorithm. The algorithm positions my rects in the smallest packed space, and rotates the sprites' rects by swapping the rect's dimensions, and flagging it as rotated if they fit better as rotated. Once that's complete, I setup the vertices client-side, and upload them to the SpriteBinRect's VBO:

void SpriteBinRect::GenerateVertices()
{
// make sure there's a VBO setup
if (!vbo)
{
std::cout << "ERROR: no VBO generated" << std::endl;
return;
}

// generate vertices based on rotation
SpriteBinVertex vertices[4];
if (IsRotated())
{
vertices[0].Set(size.x + position.x,	position.y,				1.0f, 1.0f);
vertices[1].Set(position.x,				position.y,				1.0f, 0.0f);
vertices[2].Set(position.x,				size.y + position.y,	0.0f, 0.0f);
vertices[3].Set(size.x + position.x,	size.y + position.y,	0.0f, 1.0f);
} else {
vertices[0].Set(size.x + position.x,	position.y,				1.0f, 0.0f);
vertices[1].Set(position.x,				position.y,				0.0f, 0.0f);
vertices[2].Set(position.x,				size.y + position.y,	0.0f, 1.0f);
vertices[3].Set(size.x + position.x,	size.y + position.y,	1.0f, 1.0f);
}

// bind, and generate VBO data
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(SpriteBinVertex) * 4, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteBinVertex), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteBinVertex), (void*)sizeof(Vector2));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
}


I have verified that the calculated vertex positions and texture coordinates are correct. The VBOs are also correct. Finally, I draw it each frame:

void SpritePackerState::Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (spritePacker != nullptr && shader != nullptr)
{
glBindVertexArray(spritePacker->GetVAO());
for (int i = 0; i < (int)spritePacker->GetNumRects(); ++i)
{
glBindTexture(GL_TEXTURE_2D, spritePacker->GetRect(i)->GetHandle());
glBindBuffer(GL_ARRAY_BUFFER, spritePacker->GetRect(i)->GetVBO());
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
}
}


Again, not the most efficient, but it can work. In my application's SpritePackerState (it's a use-case testing state) Render member, I clear the framebuffer, and bind the VAO. Then, for each SpriteBinRect, I bind its texture and VBO before I finally draw it.

It's as if all of my previous VBOs got overwritten each time I buffered data into a VBO in SpriteBinRect::GenerateVertices(). I made sure that the correct VBO was bound before calling glBufferData().

Ugh... so after some more work, I think I found a solution. I understand  that VAOs remember the vertex states, which is supposedly going to help efficiency. See, the problem is, I want to draw multiple objects of the same vertex configuration, but with different sets of vertices. I have a VBO for each set of vertices I want to draw, and the VAO remembers whatever the I set glVertexAttribPointer() to last. The problem is, I need glVertexAttribPointer to point to my current VBO's data whenever I bind to it. So, in my drawing loop, I re-added glVertexAttribPointer() to reset the my VAO's pointer to my currently-bound VBO's relative data position like so:

void SpritePackerState::Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (spritePacker != nullptr && shader != nullptr)
{
glBindVertexArray(spritePacker->GetVAO());
for (int i = 0; i < (int)spritePacker->GetNumRects(); ++i)
{
glBindTexture(GL_TEXTURE_2D, spritePacker->GetRect(i)->GetHandle());
glBindBuffer(GL_ARRAY_BUFFER, spritePacker->GetRect(i)->GetVBO());

// SpriteBinVertex isn't defined here, so I improvised the size here
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2) * 2, (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2) * 2, (void*)sizeof(Vector2));
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
}
}


With that, everything draws. Even though this is for offline preparation purposes, it's still nice to be efficient. I only need to render this sprite atlas to a texture, so I'll only render once whenever I change something. Next, I'll consolidate everything into a single VBO, and store all of my loaded images into texture arrays. This reduces draw calls down to 1, and eliminates a lot of gl* calls in generate in the render frame.

This seems disturbing though, because what if I wanted to draw a bunch of different models that used the same vertex attributes and even vertex pointer 2, types, etc? I'd still have to reset those pointers just because of the data offset. I guess I could load all of my models into a giant VBO, and do something like: glDrawElementsInstanceBaseVertex(), or something like that.

Again, I'm totally new to modern OpenGL, and I haven't used the majority of the new glDraw* functions. I still need to learn texture arrays, but I'm sure I'll need to store the texture index (normalized float) as an attribute since I'm batching vertices.

Edited by Vincent_M

Share on other sites

After just close to 2 months an answer occurs (the 60 days are not gone yet ;) )

This seems disturbing though, because what if I wanted to draw a bunch of different models that used the same vertex attributes and even vertex pointer 2, types, etc? I'd still have to reset those pointers just because of the data offset. I guess I could load all of my models into a giant VBO, and do something like: glDrawElementsInstanceBaseVertex(), or something like that.

You can write all vertices to the same VBO, and use the parameter named first of glDrawArrays to specify where to start. No need for giving each mesh another offset with glVertexAttribPointer. Of course, you need to ensure that each vertex can be reached by a multiple of the stride beginning at offset, regardless of which model it originates from.

Edited by haegarr

Share on other sites

You can write all vertices to the same VBO, and use the parameter named first of glDrawArrays to specify where to start.

Bingo, this is the way to do it.  What I do is create a VAO and 1 VBO with quads for sprites.  As I add sprites, I add data to the VBO and use the first parameter.  I also try to draw sprites that share the same atlas at the same time using instancing.  But yeah, the first parameter along with the count is how I do it.

Share on other sites

Hey guys! I've learned quite a bit since that first post. When I first got into OpenGL buffers, I knew early-on that a buffer was just data --that's it. What matters is in which context(s) is/are that buffer bound to. For example, I could very well have just stored all of my vertices in a single VBO. In fact, I could have easily stored all of my vertices AND elements into the same VBO, and just call glBindBuffer() twice for both GL_ARRAY_BUFFER and GL_ELEMENT_ARRAY_BUFFER on the same VBO handle. Then, offset glDrawElements' offset by whatever the size of all the vertices residing in the buffer is. This would work pretty well since I won't be needing to modify the size of my vertices from frame-to-frame. Still though, performance doesn't really matter because I just generate the texture once, and just modify it whenever needed.

One thing I learned early-on, but forgot about was that I can draw objects using multiple VBOs. So, for drawing instanced models, I can store my common mesh data into a static VBO, and all of my instanced data into another VBO that may change on a frame-by-frame basis (due to drawing). So, my Renderer class (could be a regular Camera, OculusCamera or LightProbe, etc) would use its frustum(s) to gather lists of visible, drawable entities in space (sprites, billboards, particle systems, terrain, models, etc) into queues (opaque, transparent, etc), and depending on the kind of instance vertex data that subclass of Drawer needs, the subclass would provide it to the Renderer's list of VBOs that are also per-queue (billboards provide position, color and sprite ID, sprites provide similar data, everything else provide their Transform's final model matrix, etc).

I bind to the model's VBO to set up all of the vertex attribute pointers associated with the Drawable, and then bind to the Drawable's corresponding VBO stored in the Renderer that holds all of the instanced data. Another nice thing I could do is buffer prediction where I'd pre-allocate each Renderer's VBOs by some size, and then double that size every time I run out of space due to too many objects.

1. 1
Rutin
21
2. 2
3. 3
JoeJ
18
4. 4
5. 5

• 13
• 38
• 23
• 13
• 13
• Forum Statistics

• Total Topics
631714
• Total Posts
3001866
×