Having a VBO/VAO for each object.

Started by
8 comments, last by Chris_F 10 years, 1 month ago

http://tomdalling.com/blog/category/modern-opengl/

I asked this earlier, but I'm gonna do it again with a bit more detail.

I'm working on a 2D Game Framework for a college assignment, I need to handle Basic sprite creation and animation, along with text display. I've read through probably about 30 different tutorials now, and this one above seems promising.

However I've been told that having a VAO/VBO for each object I'm rendering (each sprite in this case) is not good as that leads to more OpenGL binding and draw calls. In that tutorial (and almost every other tutorial I've seen) objects each have their own VBO/VAO.

How would I go about getting multiple textured quads on screen using only one VBO?

Advertisement

One VBO per object is one valid way of doing it, if you don't have too many different object types, if you can sort by object type before drawing, and if you implement some state-change filtering on glBindBuffer. A couple of 10s of buffer object changes per-frame isn't going to significantly show up on any performance graph (you'll be more likely to bottleneck on transforms and fillrate), a couple of 100s is more likely to be problematical.

For sprites however, and as you've deduced, it's not a good idea. 4 vertices per buffer isn't a great ratio, and if you're animating as well, you may be even needing to fill all of these buffers dynamically each frame too. That's not the kind of use case that buffer objects were designed for.

That's not to say that you shouldn't use buffer objects at all for drawing sprites, just that drawing them is a little bit more complex than "just use a VBO". You need to implement some buffer object streaming in order to do this and make it efficient. The link I've provided gives a good overview of the concept, several implementation possibilities, and useful links for further reading.

If this sounds like it's going to be overkill for what you want to do, then another alternative is to just not use VBOs at all. Client-side vertex arrays are one option, but another is just using glBegin/glEnd. If you don't absolutely have to use VBOs then the latter can be viable, and you should definitely benchmark it and consider it as a possibility. You may read horrible things about it elsewhere, but remember - Quake used it in 1996, it didn't suffer from horrible performance problems on account of using it, and it can easily hit almost 1000fps on modern hardware. So - if vertex counts are low enough to begin with, if fillrate is going to be a bigger bottleneck anyway (as will be the case with sprites), and if you can already hit your performance target without needing to optimize further, glBegin/glEnd can be perfectly OK to use.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

What version and profile are you using? In core you require a valid VAO to be bound, but you don't need a VBO bound to render. For instance:

glsl:


const vec2 position[4] = vec2[]
(
    vec2(-1.0,  1.0),
    vec2(-1.0, -1.0),
    vec2( 1.0,  1.0),
    vec2( 1.0, -1.0)
);

void main()
{
    gl_Position = vec4(position[gl_VertexID], 0.0, 1.0);
}

gl:


glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

This draws a fullscreen quad without any need for vertex buffers, vertex attributes, etc. Maybe you could use glDrawArraysInstanced to render multiple quads with one call and use a UBO/SSBO to store an array of sprite data which contains your position, orientation, etc for each sprite. You can index this array with gl_InstanceID. You can also store an index into a texture array that contains all of your sprite textures. This would keep the number of GL calls to a minimum.

I'm working on a 2D Game Framework for a college assignment, I need to handle Basic sprite creation and animation, along with text display.

You don't need a vbo then. About 7 years ago when I started openGL I used to draw all my models with glBegin()/glEnd() drawing 100,000 some vertices at 60 fps or more I dont know. So for a basic 2D game say mario, then vbo's arent even needed. So whatever you decide to do I wouldn't worry too much about architecture in managing sprites. Managing sprites/particles in a next-gen 3d renderer is when you need to get down to managing sprite vbos.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

I did plan on implementing a Particle System next for collision response on my projectiles.

[double post, ignore]

I'm working on a 2D Game Framework for a college assignment, I need to handle Basic sprite creation and animation, along with text display.

You don't need a vbo then. About 7 years ago when I started openGL I used to draw all my models with glBegin()/glEnd() drawing 100,000 some vertices at 60 fps or more I dont know. So for a basic 2D game say mario, then vbo's arent even needed. So whatever you decide to do I wouldn't worry too much about architecture in managing sprites. Managing sprites/particles in a next-gen 3d renderer is when you need to get down to managing sprite vbos.

The link in the OP's post mentions "modern OpenGL", and I would recommend that he pursue something with OpenGL 3.x/4.x core profile instead of going after compatibility mode, or fixed function OpenGL, which has been deprecated for the better part of a decade.

I played around with my idea (this is my first time working with this kind of thing.) I create a SSBO (UBO would work too) that contains the position, scale, rotation, and texture index for each of my sprites. I upload my sprites into a 2D texture array. Then I call glDrawArraysInstanced. In my vertex shader I get the vertex position using gl_VertexID and I look up my sprite attributes in the SSBO using gl_InstanceID.

2048 sprites rendered to screen with 1 OpenGL call and no attribute/element buffers needed. If anyone knows of a better way to handle this, I'd like to hear about it. smile.png

@Chris_F: this sounds really nice!

I would love to see the sourcecode for this if you want to share it (I am new to this too)

There is a nice example on instanced particles on opengl-tutorial.org which I think could be used for sprites too:

http://www.opengl-tutorial.org/intermediate-tutorials/billboards-particles/particles-instancing/

chaos, panic and disorder - my work here is finished

You will definitively want to put more than one object into one VBO. Not necessarily all (depends on whether some data is static and other isn't, and on the total amount of data), but you sure want something better than one buffer per object. This is even more true if "object" means something like "two triangles" or "one point", as is the case with sprites.

Draw calls are one thing, but binds are the more important reason (draw calls are actually still pretty cheap). Whenever you redefine or bind a buffer, OpenGL must run some more or less elaborate consistency checks. At the very least, it must check if the object name is valid and whether the object type is "buffer", and if the buffer is backed by a memory block. It must look up (in some kind of map structure) and access whatever internal structure it has for the object, which most likely means at least 2 cache misses.

That's a lot of useless work if you do it a thousand times per frame.

If you do not absolutely need the totally highest possible speed (which is likely), you can draw your sprites with the relatively easy technique of orphaning buffers. This is decribed as the first way of "Server-side multi buffering" in the article linked to by mhagain above. It is very, very slightly slower than the more advanced techniques, but by all means sufficient, and very easy.

You end up with one buffer that is in use and one that you can map and write your vertex coordinates (or whatever data you want) to, just like you would write to an array.

Depending on circumstances, you may also consider one or several of the following (note that some options are mutually exclusive):

  • Coordinate compression by e.g. transmitting a center point and a radius (for square sprites) or no radius at all (for sprites that are all the same size), using the vertex and geometry shader to figure out the final sprite
  • Write coordinates directly in NDC (that is, ranging from -1 to +1 rather than in screen pixel coordinates) if the graphics card is very poor and you want to save vertex shader work. This obviously adds CPU work, but for static or mostly static things that change only maybe once or twice per second, it may actually be a win.
  • Use array textures for sprites
  • Use a texture atlas (if no array textures available). Remember having at least a 1-px gap between tiles even without mipmaps to avoid surprises, and remember generating mipmaps correctly.
  • Use distance fields for fonts
  • Maybe even use distance fields for sprites if you want higher quality when scaling (possible if they are single-color, or don't have more than at most 4 distinct colors per sprite)

SSBO (UBO would work too)
Isn't a shader storage buffer object considerably slower than an uniform buffer object?

Though of course the SSBO allows you to run sprite animations right on the GPU and write the resulting positions into the SSBO without round-trip. Given an array texture, the u/v texture coordinates are redundant, too, so you probably only need to care about which layer a sprite uses as actual data... really a good idea actually.

Now how do you simulate a SSBO when you need to support OpenGL 3.3 as well... glCopyBufferSubData from something else (transform feedback, texture) into an UBO?

This topic is closed to new replies.

Advertisement