Sign in to follow this  
Dark_Oppressor

OpenGL VBOs vs. immediate mode for 2D sprites

Recommended Posts

I am working on a project using C++ and SDL+OpenGL. I was rendering 2D sprites to the screen using immediate mode. However, hoping to improve rendering performance, I spent the last few days figuring out how to use VBOs. Unfortunately, now that I've got them working, I have found that they have almost no effect on performance whatsoever. In fact, the game runs slightly slower (a .02 ms/frame difference on average).

To clarify, I'm storing an array of vertices and indices in a VBO for each spritesheet. So, for example, the player will have one VBO for all of his animations.

Does this sound like I've done something wrong in implementing VBOs, or could it just be that VBOs aren't very helpful with what I'm doing?

Share this post


Link to post
Share on other sites
I'm going to guess that it's not helping you that much, depending on how you're batching your calls.

The big draw of VBO is that it allows you to minimize data transfer and the number of API calls. When you have big meshes moving from immediate to VBO saves you from having to transmit the entire mesh each frame. Though with small quads the saving is pretty minimal.

The one thing you could do is to try to minimize your number of draw calls, this is one way you could get extra performance.

Calling draw once for each tile is pretty bad, try to put all of your stationary objects into a single buffer (or as many of them as you can that share a single spritesheet).

Share this post


Link to post
Share on other sites
Do you mean limiting my use of glDrawElements() ?

That does get called once per tile. However, I don't see how I could call it any less, as I change things using glTranslate, etc. in between draw calls.

Is there something I am missing?

Share this post


Link to post
Share on other sites
Yes, you want to reduce glDrawElements as much as possible. If every single thing on your screen is moving independently of each other than you probably can't do any better.

Is that really your case though? If there's anything in your scene that is stationary you may be able to combine them together.

For example instead of drawing a tile at (0,0), translating to the right 1, and then drawing another tile at (0,0), you can make a VBO that draws two tiles at (0,0) and (1,0). See if you can batch anything together into a single call (multiple grass tiles, trees, walls, etc). I don't have any idea what your game is like though so it might not be applicable.

Share this post


Link to post
Share on other sites
Well, rats, then it probably won't help me much. I am working on the rendering code for a platformer project I have, but I am much more interested in using it for a strategy game (which will pretty much be only moving objects, heh).

So, on a slightly related topic, is this pretty much as good as it gets for a 2D game? I'd really love to be able to render tens of thousands (a couple tens of thousands, anyway) of sprites onscreen and get some kind of decent performance. Is this completely impractical? I wouldn't mind moving on to a 3D engine like Ogre or something if I could create a 2D (gameplay-wise) game with tons of units flying about.

Share this post


Link to post
Share on other sites
Even if you can't reduce the number of draw calls, there's still other performance tricks you could use.

Are you minimizing your state/texture changes? (Don't set the texture for every tile, group all tiles that use the same texture and render them together).

Hows your fill rate? Could you get any benefit from sorting your objects front to back to minimize overdraw?

I'm having a hard time imagining any game that needs tens of thousands of independently moving entities on screen at the same time, could you describe more what kind of game you're envisioning? That seems pretty excessive.

Share this post


Link to post
Share on other sites
I already do limit texture changes to an extent (that is something I will improve upon in the new project).

I don't believe there is anything else I could do to limit overdraw, as the tiles prevent the player (or other stuff) from going on top/behind things.

I'm picturing a really large-scale space strategy game, with tons of ships (just a few different kinds). So, very little to render aside from some backgrounds, the ships themselves, and planets or whatever. The only thing that would be large in number would be the ships, but I wanted to play around with a very large number just to see if it is fun. I can still just do that and see what happens, I just wanted to work on the rendering stuff now, since I'm working on it for something else.

Perhaps tens of thousands of ships onscreen is an overestimate. Basically, I am thinking they would be large enough to only show a reasonable number onscreen if zoomed all the way in, but I want to add the ability to zoom the camera out. Perhaps beyond a certain point, only large ships (if any) would show up, thus preventing tens of thousands of tiny ships being shown.

Share this post


Link to post
Share on other sites
Another thing you may want to look into is geometry instancing. It's a relatively new extension so I don't know too much about it, but its essentially a way to stamp down hundreds of copies of a mesh in different places from one draw call. Might help you out in your spaceships example.

Share this post


Link to post
Share on other sites
glDrawElements in itself isn't necessarily a problem. When I tested NVidias stateless drawing extension a while ago, I wrote a test that draws 12,500 objects with a separate glDrawElements for each object, and that runs at 60 FPS. Changing matrices with glTranslatef for 10k objects however can be significantly more expensive. (Though you definitely want to avoid calling it 10k times a frame, but 1k should be OK most of the time).

Just to reiterate what karwosts have said, if you have a tile-map, you can easily draw 10,000 tiles with a single glDrawElements, since these tiles don't move independently of each other. If you have 10,000 ships that move independently, then you might need more calls. Instancing can be good if each ship uses the same model and texture, but has a different translation.

There are also other ways of drawing the ships in a single draw call. Do you plan to keep your game in 2D so that each ship is a single quad?
Do you rotate the quads to rotate the ships, or is everything handled by changing the sprite texture, so that the quad can always remain a rectangle?
Also, what OpenGL version and hardware are you targeting?
And lastly, are you using shaders, or are you otherwise willing to use them?

Share this post


Link to post
Share on other sites
I'm definitely not an expert, but you could try transforming all of your objects on the CPU rather than using glTranslate.

For the type of objects you're using (2D quads) the math is very light as you only really have to transform one point per object; this way you can build a single large buffer to draw all at once, as everything will be in worldspace.

Share this post


Link to post
Share on other sites
Quote:
Original post by raigan
I'm definitely not an expert, but you could try transforming all of your objects on the CPU rather than using glTranslate.

For the type of objects you're using (2D quads) the math is very light as you only really have to transform one point per object; this way you can build a single large buffer to draw all at once, as everything will be in worldspace.
You can also do this on the GPU - for potentially much greater speed gains.

The trick is that you don't need a full 4x4 matrix transformation just to store a 2-dimensional position and rotation. Rather than building 4x4 rotation matrices CPU-side and then passing them to the GPU (i.e. what glTranslate/glRotate does), just pass a buffer of 2d vectors and rotation angles down to a custom vertex shader, and perform the transformation manually in the shader.

You will have cut out the cost of building all the matrices on the CPU, cut down the bandwidth required to send those matrices to the GPU, *and* made it possible to draw everything which shares the same texture with a single draw call - effectively, it is a specialised form of old-fashioned OpenGL instancing.

Share this post


Link to post
Share on other sites
So what I'm currently doing is having an array of "sprite data".

Each instance has X,Y,W,H,angle,framenumber.

The shader turns those into corners. In order to do this, I allocate 4x as many slots as sprites. Each slot also has a "corner" number.

To draw a frame, clone slots 0..(N-1) into N..2N-1, clone them again from 0..2N-1 -> 2N..4N-1

I then write into the corner number elements, 0..N-1 := 0, N..2N-1 := 1 etc.

I've pre-setup an index array which has 0,N,2N,3N, 1, N+1, 2N+1, 3N+1 etc and so rendering the quads is just drawing that array.


The shader then picks up the corner number and the other data and produces corner coords. It also produces a "sheet number" which is passed to the pixel shader (to choose a texture unit to use) and texture coords from the frame number and some uniforms describing the texture data.

I haven't worked out a way to do this without needing to clone the data yet. If anyone knows of a way, I'd be interested it hear it.

Share this post


Link to post
Share on other sites
Quote:
Original post by Katie
I haven't worked out a way to do this without needing to clone the data yet. If anyone knows of a way, I'd be interested it hear it.
Place the data into a texture (instead of a VBO), and sample the texture in the vertex shader to retrieve it. You pass an index along with each vertex, and then the texture coord to sample from is mod(index, 4).

If you are targeting only modern OpenGL you can use a texture-buffer-object for this, but a plain old texture works fine too.

NVidia has a sample along these lines, demonstrating how textures let you do fancy indexing into vertex data arrays.

Share this post


Link to post
Share on other sites
Quote:
Original post by Erik Rufelt
There are also other ways of drawing the ships in a single draw call. Do you plan to keep your game in 2D so that each ship is a single quad?
Do you rotate the quads to rotate the ships, or is everything handled by changing the sprite texture, so that the quad can always remain a rectangle?
Also, what OpenGL version and hardware are you targeting?
And lastly, are you using shaders, or are you otherwise willing to use them?


I do plan on keeping the game 2D, using a quad for each ship. As of now, I am planning on rotating the quads themselves.

As for OpenGL version/hardware, I just want anyone with a somewhat recent-ish card to be able to play. I was requiring either 1.5 or the ARB_vertex_buffer_object extension when experimenting with VBOs, but at the moment there aren't any requirements. I would certainly be open to upping the requirement some, however, if there was some extension that would really help.

I am not using shaders, although I recall reading somewhere that they could actually be used to render things other than shadows (which is all I thought they were for). I could probably learn how to use them if I needed to.

Share this post


Link to post
Share on other sites
Quote:
Original post by Dark_Oppressor
I do plan on keeping the game 2D, using a quad for each ship. As of now, I am planning on rotating the quads themselves.

As for OpenGL version/hardware, I just want anyone with a somewhat recent-ish card to be able to play. I was requiring either 1.5 or the ARB_vertex_buffer_object extension when experimenting with VBOs, but at the moment there aren't any requirements. I would certainly be open to upping the requirement some, however, if there was some extension that would really help.

I am not using shaders, although I recall reading somewhere that they could actually be used to render things other than shadows (which is all I thought they were for). I could probably learn how to use them if I needed to.


Shaders are used for everything. If you write glTranslatef(...); glVertex3f(...); then when the vertex is actually drawn, a vertex shader will be run that multiplies your vertex with a matrix that includes your translation. If you write these shaders manually you can exploit how your particular game works in order to optimize things.
Whenever a pixel is drawn on the screen, a pixel shader is run that fetches the texture color and multiplies it by the vertex-color.
Shaders are run billions of times per second on modern hardware.

If you learn to write and use shaders, a whole new world opens up, so I would definitely recommend it if you want to implement something more advanced. Learning the basics of how shaders work is not too hard, so you probably won't need to dedicate too much time to it before being able to continue with your game.
Optimizing everything to perfection can be a bit harder, but shouldn't be too difficult once you learn how to pass arrays and data to the vertex-shader. I think a vertex-shader is the single thing that could benefit you the most here. If you target DX10+ class hardware, a geometry shader can make it even easier and possibly more efficient too.

If you want to try without shaders first, create a really large array of 1000*4 vertices or so, and manually put 1000 quads into it. Then upload the data to your VBO and draw the 1000 quads with glDrawElements. Then instead of using glTranslate and glRotate you manually update the array with translated coordinates and call glBufferData each frame before drawing. This could be efficient enough. If you use different textures you might need to use a few glDrawElements, but only as many as you have different sprite sheets, if you sort your objects by texture.
With this you would also need to manually put the texture-coords into the array depending on which animation-frame is to be used.

Instancing can be an easy way to reduce calls, but it also requires shaders.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Forum Statistics

    • Total Topics
      627740
    • Total Posts
      2978884
  • Similar Content

    • By DelicateTreeFrog
      Hello! As an exercise for delving into modern OpenGL, I'm creating a simple .obj renderer. I want to support things like varying degrees of specularity, geometry opacity, things like that, on a per-material basis. Different materials can also have different textures. Basic .obj necessities. I've done this in old school OpenGL, but modern OpenGL has its own thing going on, and I'd like to conform as closely to the standards as possible so as to keep the program running correctly, and I'm hoping to avoid picking up bad habits this early on.
      Reading around on the OpenGL Wiki, one tip in particular really stands out to me on this page:
      For something like a renderer for .obj files, this sort of thing seems almost ideal, but according to the wiki, it's a bad idea. Interesting to note!
      So, here's what the plan is so far as far as loading goes:
      Set up a type for materials so that materials can be created and destroyed. They will contain things like diffuse color, diffuse texture, geometry opacity, and so on, for each material in the .mtl file. Since .obj files are conveniently split up by material, I can load different groups of vertices/normals/UVs and triangles into different blocks of data for different models. When it comes to the rendering, I get a bit lost. I can either:
      Between drawing triangle groups, call glUseProgram to use a different shader for that particular geometry (so a unique shader just for the material that is shared by this triangle group). or
      Between drawing triangle groups, call glUniform a few times to adjust different parameters within the "master shader", such as specularity, diffuse color, and geometry opacity. In both cases, I still have to call glBindTexture between drawing triangle groups in order to bind the diffuse texture used by the material, so there doesn't seem to be a way around having the CPU do *something* during the rendering process instead of letting the GPU do everything all at once.
      The second option here seems less cluttered, however. There are less shaders to keep up with while one "master shader" handles it all. I don't have to duplicate any code or compile multiple shaders. Arguably, I could always have the shader program for each material be embedded in the material itself, and be auto-generated upon loading the material from the .mtl file. But this still leads to constantly calling glUseProgram, much more than is probably necessary in order to properly render the .obj. There seem to be a number of differing opinions on if it's okay to use hundreds of shaders or if it's best to just use tens of shaders.
      So, ultimately, what is the "right" way to do this? Does using a "master shader" (or a few variants of one) bog down the system compared to using hundreds of shader programs each dedicated to their own corresponding materials? Keeping in mind that the "master shaders" would have to track these additional uniforms and potentially have numerous branches of ifs, it may be possible that the ifs will lead to additional and unnecessary processing. But would that more expensive than constantly calling glUseProgram to switch shaders, or storing the shaders to begin with?
      With all these angles to consider, it's difficult to come to a conclusion. Both possible methods work, and both seem rather convenient for their own reasons, but which is the most performant? Please help this beginner/dummy understand. Thank you!
    • By JJCDeveloper
      I want to make professional java 3d game with server program and database,packet handling for multiplayer and client-server communicating,maps rendering,models,and stuffs Which aspect of java can I learn and where can I learn java Lwjgl OpenGL rendering Like minecraft and world of tanks
    • By AyeRonTarpas
      A friend of mine and I are making a 2D game engine as a learning experience and to hopefully build upon the experience in the long run.

      -What I'm using:
          C++;. Since im learning this language while in college and its one of the popular language to make games with why not.     Visual Studios; Im using a windows so yea.     SDL or GLFW; was thinking about SDL since i do some research on it where it is catching my interest but i hear SDL is a huge package compared to GLFW, so i may do GLFW to start with as learning since i may get overwhelmed with SDL.  
      -Questions
      Knowing what we want in the engine what should our main focus be in terms of learning. File managements, with headers, functions ect. How can i properly manage files with out confusing myself and my friend when sharing code. Alternative to Visual studios: My friend has a mac and cant properly use Vis studios, is there another alternative to it?  
    • By ferreiradaselva
      Both functions are available since 3.0, and I'm currently using `glMapBuffer()`, which works fine.
      But, I was wondering if anyone has experienced advantage in using `glMapBufferRange()`, which allows to specify the range of the mapped buffer. Could this be only a safety measure or does it improve performance?
      Note: I'm not asking about glBufferSubData()/glBufferData. Those two are irrelevant in this case.
    • By xhcao
      Before using void glBindImageTexture(    GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format), does need to make sure that texture is completeness. 
  • Popular Now