I am developing a sprite based 2D game for mobile platform(s) and I'm using OpenGL (well, actually Irrlicht) to render graphics. First I implemented sprite rendering in a simple way: every game object is rendered as a quad with its own GPU draw call, meaning that if I had 200 game objects, I made 200 draw calls per frame. Of course this was a bad choice and my game was completely CPU bound because there is a little CPU overhead assosiacted in every GPU draw call. GPU stayed idle most of the time.

Now, I thought I could improve performance by collecting objects into large batches and rendering these batches with only a few draw calls. I implemented batching (so that every game object sharing the same texture is rendered in same batch) and thought that my problems are gone... only to find out that my frame rate was even lower than before.

Why? Well, I have 200 (or more) game objects, and they are updated 60 times per second. Every frame I have to recalculate new position (translation and rotation) for vertices in CPU (GPU on mobile platforms does not support instancing so I can't do it there), and doing this calculation 48000 per second (200*60*4 since every sprite has 4 vertices) simply seems to be too slow.

What I could do to improve performance? All game objects are moving/rotating (almost) every frame so I really have to recalculate vertex positions. Only optimization that I could think of is a look-up table for rotations so that I wouldn't have to calculate them. Would point sprites help? Any nasty hacks? Anything else?



Did you use my port of irrlicht for android ? For 2d sprites on Android and iphone, i use the same tricks as you: batching. I try many solutions in OpenGL ES 1.x and 2.x:

  • sort by z (parallax) and by texture, do the transformations on the CPU and call glDrawArrays or glDrawElements (fastest way). Use one big texture if you can.
  • same trick with VBO, not faster because for each frame you refresh all informations. It can be useful for statics sprites.
  • use OpenGL ES 2.x and use Vertex shader to compute positions (slower)
  • use PointSprites (no solution if it is not a square and too many transparent pixels kill fillrate)
  • use gldrawtexoes extension...
  • use a drawcall for each sprite (slowest method)

So as you, all transformations are done by the CPU for OGLES 1.x or OGLES 2.x. If you have neon instructions, you can use them to speed your computations.

Ps: on iphone or android devices, i'm not CPU limited but fill rate limited. So it is very important to limit overdraw.

  • \$\begingroup\$ Excellent, this is something I was looking for. I was not aware of your Irrlicht port but I have my version on of Irrlicht running on iOS already. You say you're not CPU limited - how many sprites are you drawing? And what are your framerates, say, for 100 sprites on iPhone? If I have 200 objects I end up doing 48000 calculations per second. Your point about fillrate is good. \$\endgroup\$ – user4241 Mar 1 '11 at 7:03
  • \$\begingroup\$ Static sprites (background) are in VBO. I use one VBO per parallax. Otherwise, i have 100 to 200 sprites on Moblox. On all iphones including the 3G, i have more than 30fps (as i remember). But big sprites are very costly (fillrate problem).... \$\endgroup\$ – Ellis Mar 1 '11 at 10:34
  • \$\begingroup\$ I'm working on a particle engine, which i can use up to 20 000 particules with all positions computing done on CPU and i have 10fps with extreme settings (on 3GS and iPhone4). So 1000 sprites must be possible on 3GS or iPhone4 with good framerate. \$\endgroup\$ – Ellis Mar 1 '11 at 10:39
  • \$\begingroup\$ Thank you, very helpful! How are you implementing your particle engine? I suppose you're playing around with shaders? \$\endgroup\$ – user4241 Mar 1 '11 at 10:42
  • \$\begingroup\$ I use shaders because i need gl_PointSize to setup each particle size. I don't work anymore with OGLES 1.x because old phones aren't my target. First, all my code was OGLES 1.x, then OGLES 1.x and OGLES 2.x (no performance improvement) and now OGLES 2.x (rendering improvement). \$\endgroup\$ – Ellis Mar 1 '11 at 10:57

I would recommend having a VBO, with each vertex containing the position/rotation of each rendered object and batching based on texture like you're doing. I'm not very familiar with ogl ES, so i'm not sure which version of glsl it supports, but you might even be able to batch based on a set of textures, and store which of the 4 or so textures you are passing in you'd be using inside of the vertex. Point sprites would definitely improve you're performance because it would cut down the amount of data you are sending over drastically, and batching should never decrease performance if you are doing it correctly. Also, you could improve performance a bit by computing rotation on the shader and only passing in an int/float value into the params or inside the vertex itself. (params would be faster, just create an array for each batch and have an index into that array stored inside the vertex itself)

  • \$\begingroup\$ Thank you for your answer. Your suggestion about doing rotation calculation in shader is excellet but unfortunately I'm using OpenGL ES 1 which does not support shaders so I am stuck with fixed pipeline. I'll try point sprites but I can't use them in all cases because there is upper limit for their size. I'm still little bit pessimistic about VBO, if I'm recalculating position of each vertex every frame, how does VBO help? \$\endgroup\$ – user4241 Feb 28 '11 at 17:03
  • \$\begingroup\$ it allows your vertex data to stay on the gpu, which decreases the amount of data you have to send over to the gpu each frame. you don't need shaders to take advantage of this tho, you shouldn't need to change the vertex data at all, if you have a base position (such as the origin) for each sprite, you can just alter the world matrix by it's transform before calling draw. however, this might be tough when batching. using fixed function, it'd probably be more beneficial to just switch to VBOs and drop the batching for now at least, that will definitely give you a boost. \$\endgroup\$ – sringer Feb 28 '11 at 17:41
  • \$\begingroup\$ I see your point. So after all, you're not talking about batching but simply using one draw call to draw one game object. I'll definitely test how VBO without batching affects FPS in my game but still 200 draw calls per frame sounds too big... but I guess I have to live with it then. I will accept your answer if no other answers will show up. \$\endgroup\$ – user4241 Feb 28 '11 at 18:08

You mention mobile platforms which do not have instancing. But, you still do have vertex shaders, don't you?

In that case, you can still do pseudo instancing, which is very fast too. Make a VBO (GL_STATIC_DRAW) with the corner points (relative to the center point of the sprite, e.g. -1/-1, 1/-1, 1/1, -1/1) and any texture coordinates you need, in it.
Then set one of the generic vertex attributes for every draw call to the centerpoint of the sprite, and draw the two triangles with the buffer bound. Inside the vertex shader, read the generic vertex attribute and add the vertex's coordinates.

That will save you blocking on a data transfer for every sprite and should be much faster. The actual number of draw calls is not so terribly important, the blocking/stalling in between is.

  • \$\begingroup\$ This sounds good solution for OpenGL ES 2.0. Unfortunately I'm using ES 1 which does not have shaders at all. \$\endgroup\$ – user4241 Mar 1 '11 at 6:58

The problem resides in the amount of data you are sending to the GPU each frame. Just create a VBO for each batch and populate it once, then apply the corresponding transformation matrices (via glMultMatrix, or a shader if you are using ES 2.0) when drawing the batches.

  • \$\begingroup\$ I don't understand how does this help when I have 200 separate game objects with unique transforms? Using glMultMatrix would apply same transformation to all objects which is not what I want. Also, sending data to GPU is not bottleneck; if I remove CPU-side transformations perfomance is very good. \$\endgroup\$ – user4241 Feb 28 '11 at 13:47
  • \$\begingroup\$ Yes, but a VBO could still improve performance if applied correctly. How are you currently rendering your 200 objects? Are you using glBegin/glEnd? \$\endgroup\$ – TheBuzzSaw Feb 28 '11 at 15:02
  • 1
    \$\begingroup\$ I'm using Irrlicht 3D engine with custom scene node so I'm not using OpenGL directly (but I suppose it's using simple glBegin/glEnd in this case). Would VBO really help since I would have to modify the whole buffer every frame? Also, this does not solve the fundamental problem about being CPU-bound because of vertex transform calculations. But thank you for your answers anyway! \$\endgroup\$ – user4241 Feb 28 '11 at 15:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.