Skip to main content
GameDev.net gamedev.net
🔒 Locked

One big dynamic VBO, or multiple small static ones?

Started by Zomgbie Jan 4, 2011 at 1:05 AM 14 replies 12.7k views
Original Post
Zomgbie
Zomgbie
Hello you!

As i'm slowly moving closer to a good understanding of Vertex Buffer Objects, i stumbled upon a question.

Consider this; You have many objects (minimum 10, and maximum 10000) in a 2D space. The objects are all just textured cubes, but with various sizes. They all need to have their position updated every frame. What would be the best way to draw these objects using VBOs?

Currently i have one tiny littly VBO at a default size. I then draw this VBO once per object, but translating, scaleing and changing texture as we go. This is of course a huge performance boost compared to simple immediate mode, but i'm still sending more calls to the GPU then i perhaps need too. What if i would setup one big dynamic VBO, that contains all my objects and updates every frame? I could store the textures in a texture atlas (That's what they are called, i belive) and i've all of the sudden reduced the number of calls i make to... well alot less.

So my question to you is; Is the performance hit from using a dynamic VBO more worth it then doing what i'm currently doing?

Thanks for your time! :)
Omg, zombie! Zomgbie.
Rusenec
Rusenec
The common rule and also a setup recommended by both MS and nVidia is to avoid using a huge amount of draw calls and to try to batch your data.
The recommended batch was from around 500 to 1500-2000 vertices. You have to balance between DIP calls and batch size.
In case when you need to draw a lot of the same objects you should also look into instancing as the best way to solve situations where you draw one and the same vb many times with only difference in some parameters like transformations for example.
Rusenec
samoth
samoth
In OpenGL, as opposed to DirectX, 10.000 draw calls are not an issue as long as there are no state changes in between that will make the pipeline stall. That is the reason why OpenGL did not have instancing for years, since it was not necessary. Drawing 10k cubes will probably even be possible with acceptable performance in immediate mode.

If you target OpenGL 3.0 and later, you can use glDrawElementsInstanced, which may be somewhat faster than calling glDrawElements 10k times, but expect no miracles.

Not changing state that causes stalls (read as: use a texture atlas) yields much greater performance advantages.
Danny02
Danny02
So because you were talking about 2D, if u really meant cubes or quads?
If so you could use point spirits(u just have to provide 1 Vertex for each quad).


Also be sure if u really move every object per frame or just the camera.
In this case u could split up your modelview matrix in the model and view part, and only the view had to be updated every frame.

If some cubes are more static then others(changing only every 2 sec or so).
I would recommend that u create different buckets(VBOs) and re upload data as needed.

another thing to optimize would be to use a texture atlas.


ps:are you trying to create a 2D side scrolling tile renderer?


Zomgbie
Zomgbie
Thanks for all your answers!

Quote:
Not changing state that causes stalls (read as: use a texture atlas) yields much greater performance advantages.

I had no idea there was such a performance gain from using a texture atlas. I'll keep this in mind, thank you!

Quote:
If so you could use point spirits(u just have to provide 1 Vertex for each quad).

This is interesting. Would i draw them using immediate mode? And how exactly does this work with a texture atlas, if i don't provide any texture coords?

Quote:
I would recommend that u create different buckets(VBOs) and re upload data as needed.

I've thought about this aswell. I could perhaps use one VBO per list of objects. This would result in about 1500-2000 vertices per VBO, as suggested by Rusenec.

Quote:
ps:are you trying to create a 2D side scrolling tile renderer?

Not right now ;> But the code i'm writing now is probally going to be reused later on for such a project!

Once again, thank you all for the great answers :)
Omg, zombie! Zomgbie.
Erik Rufelt
Erik Rufelt
Quote:
Original post by Zomgbie
This is interesting. Would i draw them using immediate mode? And how exactly does this work with a texture atlas, if i don't provide any texture coords?


It doesn't work with a texture atlas unless you use shaders.
Zomgbie
Zomgbie
Quote:
Original post by Erik Rufelt
It doesn't work with a texture atlas unless you use shaders.


Okey. Hmm, then i guess the question is; Which is best?

Omg, zombie! Zomgbie.
Danny02
Danny02
so when u don't use shaders u can't use a texture atlas(TA) with point spirits(PS), only one. In this case i would recommend to use a TA and create one VBO for each Texture so u will only have to change each Texture once.

On the other hand, shader are really simple to use and to learn from my experience. Especially when u use them for so simple tasks. With shaders you can do what ever you want, use TA and PS at the same time.
Zomgbie
Zomgbie
Maybe it's time that i learn shaders then. Sadly, i have no idea what they or nor how to use them. But a few well spent hours on google should take care of that :)
Omg, zombie! Zomgbie.
Juanxo
Juanxo
here you have some links link1 link2 where you can learn to use OpenGL shaders
samoth
samoth
Quote:
Original post by Zomgbie
I had no idea there was such a performance gain...
It breaks pipelining, which is evil. I'm going to tell you wrong, because in reality, it's a million times more complicated. But in general the idea is like this:

Several calls to glDraw[Element|Array|Whatever] will just push commands on the task queue. This is fast and efficient. Changing a texture will, in the best case, also just place a command on the queue, which still is fast and efficient on your end (assuming that the texture is already uploaded and all).

The driver's worker thread will pull items off that queue and execute them. It will try to overlap operations as much as possible, to take advantage of parallelism and to hide transfer latencies and stalls due to memory access, etc.
This makes sure that the available computing power is used as close to 100% as possible. So for example, it might transform the vertices of the second draw call while still processing fragments of the first. Or it might transfer vertex data from your VBO to GPU memory that correspond to your 10th draw call while the first one is transforming. Or, anything it deems the best thing to do, whatever that is.

Now, if you change state such as a texture, future fragments drawn will (obviously) draw with a different texture. Which means that the driver must change the texture unit (hardware) to refer to some other data. It cannot do this now, however, because that would affect the currently rendering fragments[1]. Thus, it has to block until all the previous processing has finished. This means that there is no more parallelism, so the GPU becomes underutilized.
In addition, the texture data might not reside in GPU memory at the present time. Since GPU memory is limited, the driver sometimes has to make a selection on what to keep in there and what not to keep (very similar to paging in every operating system). Which means it might have to do a DMA transfer to get the texture to the card first (you don't know about that, it will just happen). Ideally it will pipeline that, so the transfer is "free", but it might not always be possible.

Pipelining is the main reason why we use buffer objects, too. In principle one could think it makes no difference whether one uses a buffer object or a simple vertex array. After all, you only define some vertex coordinates one way or the other, and the GL has to draw them somehow, so what's the matter.
The difference is that if you map/unmap a buffer, the driver will upload the data to the card whenever the PCIe bus isn't used and whenever it has nothing better to do. When you tell it to render something, the data is (hopefully) already there. In any case, the queue is full with commands.
On the other hand, if you draw from a normal vertex array, you tell the driver at one specific time to render a certain dataset. The driver cannot possibly know if this data was valid before and it cannot know if it will be valid afterwards (or how long). So, it has only one choice, it must block your thread (this guarantees that won't modify the data, but it also means you are unable to queue commands), send your data over the bus and wait for this transfer to complete. Only then, the call can return, and the driver can pull the next command from the queue, after you have submitted one. So, paralellism is going down close to zero.


[1]This is one of the things that I'm not telling you the full truth about. In reality, there are several sets of several texture units (and other GPU resources) which the driver can switch between, so sometimes you will not stall when only switching between a few states. But in general, you don't know and you have no guarantee, so for a "big state change" (texture, shader, blending) you must always assume a stall.
Erik Rufelt
Erik Rufelt
About the shaders, if you just want to draw lots of textured quads the gain from using point sprites with a texture atlas is not that great. I wouldn't bother, but concentrate on drawing quads efficiently, as all those parts of the process is needed anyway.
If, after you are done with that, you want to squeeze some extra performance out, you could cut the actual bandwidth down by using the geometry shader to construct the quads.
Danny02
Danny02

I can't really agree with Erik Rufelt here.

1. When I profiled my app once I found out that binding Textures was the function which took the longest time to process then any other function. I had a little game which had also some game logic but over 50% of the time my prog was just switching Textures.

So I think that your just wrong when saying the Texture Atlases don't help very much, especially when your Texture swaps aren't ordered.

2. what do you mean with
Quote:
but concentrate on drawing quads efficiently


there isn't any more effective way to draw quads(with no rotation) then point spirits. First because you don't have to care to get camera aligned quads, second you just have to calculate one position instead of 4. When you want to rotate the quads you might need a geometry shader but this is in the most cases not necessary.
Erik Rufelt
Erik Rufelt
I'm afraid I explained myself badly, apologies for that. I most definitely agree that a texture atlas is one of the most important things, and that's part of what I mean with drawing quads efficiently.

What I'm referring to is only point sprites (1 vertex per quad) vs indexed quads (4 vertices per quad).
Pretty much every other optimization works the same for both methods, so the choice between those two can be done at a later time. If Zomgbie wants to draw a few thousand sprites and never used shaders before, then that step in itself can complicate things quite a lot, hindering his progress. Once the other optimizations are complete, if it's still too slow, then converting it to point sprites is easier when everything else is done.

Since he seems to be talking about 2D, rotations are not an issue(do you want to rotate your sprites?), however, I'm still a bit confused with what you mean with a texture-atlas and point sprites without the geometry shader. I haven't worked too much with this in OpenGL except for the very basics, but as far as I know a point sprite always get texture coordinates from 0 -> 1. Please correct me if I'm wrong here, and there's a method to map a point sprite to a texture atlas without the geometry shader. As long as only a single vertex passes the vertex shader, I don't see this happening, which would mean a texture atlas is incompatible with point sprites without the geometry shader.

So what it comes down to is two things. The first is cutting bandwidth from RAM -> GPU memory, which can be either to about 1/2 or in the area of 1/4 or possibly greater savings depending on the data and method used. I doubt this bandwidth will be a bottle-neck at all. For 10k sprites we're talking maybe 1MB per frame for textured quads, which is not much at all, that you might be able to cut to 200Kb.

The second gain is from constructing the quads on the GPU instead of the CPU. This will also not lead to very significant savings at all for simple 2D quads, which are quite cheap to set up.

I have actually optimized this myself quite a bit (though with D3D11), and currently when drawing about 10k sprites arranged in a few texture atlases I cut the rendering time down perhaps 30% with the geometry shader and point sprites. This is using a buffer on the GPU for the texture coords and only sending 2 floats + an index per sprite from the CPU, instead of 4 floats * 4 vertices per quad. Also, even with quads on my setup (GeForce 8800GT, quad-core CPU), 10k sprites are drawn in less than 1 ms, so I doubt this part of a game will ever be the bottle-neck, if designed properly, geometry-shader or not.

Giving 10k entities proper handling in a game however, that might be costly.

My point is primarily, if you want to make a sprite-game, don't overdo it with the tech, it's probably not needed. You definitely should use a texture atlas if there are more than tens of textures, and you definitely should use a VBO and update as many sprites as possible in one transfer from CPU to GPU, but except for those things it's not that important to find the absolute most efficient way.
Zomgbie
Zomgbie
I'm surprised about the answers i've gotten here. They are extremly informative and helpfull!

After reading through all of them, i think i've gotten a better understanding then i had before. Since i'm planning on rotating my sprites, and currently don't feel like taking the step towards shaders(Or geometry shader, if i'm correct), i've decided to implement a texture atlas. This will, if i understood samoth's beautifull post, eliminate the pipeline stalling. Though i still don't like drawing in immidieate mode, even if it would be sufficient, so i'm going to have one VBO per texture.

Once again, thank you for the completly awesome answers! :)
Omg, zombie! Zomgbie.
Danny02
Danny02
sure, if u want to rotate your spirits, then just calculating the 4 vertices on the CPU will be easier for you.(One could distinguish between rotating and not rotating ones ;-) )

@Erik Rufelt
I'm agreeing with you, using point spirits won't save that much performance, but I see it also from a coding point of view. You won't need to write as much code with point spirits and it's easier to implement especially when you are in 3D i think.

About the PS implementation without geometry shader. Just pass the texture atlas offset and scale threw the vertex- to the fragment shader.

Erik Rufelt
Erik Rufelt
Quote:
Original post by Danny02
@Erik Rufelt
About the PS implementation without geometry shader. Just pass the texture atlas offset and scale threw the vertex- to the fragment shader.


Ah, didn't think of that. :)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.