6
\$\begingroup\$

I've just now tested rendering a lot of sprites using XNA's SpriteBatch class. What I basically do is this:

Somewhere in the code, I have a list which contains all sprites that have to be rendered. I prefilled this list with 2500 Sprites, all of which have the same rotational angle, texture, position etc.

I then draw them like this:

    spriteBatch.Begin();
    foreach (Sprite spr in sprites)
        spriteBatch.Draw(spr.Texture, spr.Position+spr.Center, spr.SourceRect, spr.Color,MathHelper.ToRadians( spr.Rotation ), spr.Center, spr.Scale, SpriteEffects.None, spr.Depth);

    spriteBatch.End();

and I'm getting really poor performance. I only average about 15FPS with my Radeon HD 6850 graphics card and i5 2500k CPU.

This is very surprising to me, since I average about 70 FPS doing exactly the same thing with my own custom OpenGL engine, which uses old OpenGL rendering (meaning one draw call for every single sprite). I actually expected XNAs rendering to be a ton faster, since it draws everything in one draw call.

Am I rendering this the wrong way? Playing around with SpriteSortMode settings has made almost no difference, some of them just make it a tiny bit slower (depth sorting etc.).

\$\endgroup\$
9
  • \$\begingroup\$ If you remove the code shown (and only the code shown), does the game go fast again? Are you adding the sprites to sprites every frame, or only once? Have you tried removing the +spr.Center and Math.ToRadians to see if the math calls are slowing it down? \$\endgroup\$ Commented Jun 18, 2011 at 19:41
  • \$\begingroup\$ Yes, it does run fast again. Around 6500-7000 FPS without the code. I have not tried the other things yet, but I will try out the suggested changes (also the ones AttackingHobo suggested) now to see if it changes anything, however I'm dubious if it will, after all drawing is mostly a graphics card thing (the cpu<-> gpu pipeline limitations shouldn't be a problem since spriteBatch is supposed to transfer all data in just a few calls) and those other things are CPU calculations. \$\endgroup\$
    – TravisG
    Commented Jun 18, 2011 at 19:51
  • \$\begingroup\$ Also, when doing performance tests, make sure Game.IsFixedTimeStep and graphics.SynchronizeWithVerticalRetrace are set to false. \$\endgroup\$ Commented Jun 18, 2011 at 19:57
  • 1
    \$\begingroup\$ @heishe, It will only do that if you have everything in one texture. If you are sending 2500 unique textures to the GPU it will do a spritebatch for each one. \$\endgroup\$ Commented Jun 18, 2011 at 20:09
  • 1
    \$\begingroup\$ @heishe 64x64 = 4,096. 200x200 = 40,000. Of course 64x64 will run faster. At 200x200 it will have to transfer and draw almost 10 times as much data. \$\endgroup\$ Commented Jun 18, 2011 at 21:25

3 Answers 3

6
\$\begingroup\$

I am not sure, but I think you have each sprite with an instance of its own texture. Meaning instead of doing 1 sprite batch, you are doing 2500 sprite batches. Try replacing spr.Texture with a single local Texture instance.

Also you do arithmetic and call functions from inside the spritebatch function call, which could slow it down more. Try to have all your calculations done somewhere else, and try to store the rotations in the correct format instead of what you are doing.

\$\endgroup\$
8
  • \$\begingroup\$ I don't mind downvotes, but if you leave one, would mind leaving a comment as well so I may know what mistake I have made. \$\endgroup\$ Commented Jun 18, 2011 at 23:15
  • \$\begingroup\$ I didn't downvote you, but I do find the idea of the maths operations there dominating CPU usage very hard to swallow. \$\endgroup\$ Commented Jun 19, 2011 at 1:40
  • 3
    \$\begingroup\$ Huh, I never said anything about it dominating CPU usage, just that doing extra math and calling extra function could slow it down. \$\endgroup\$ Commented Jun 19, 2011 at 2:11
  • \$\begingroup\$ It's all about limits: If you're GPU limited, then this change achieves nothing. If you're CPU limited - then this tiny optimisation is unlikely to get you below the limit - you're much better off doing the one or two optimisations that will. Optimising takes effort and makes code harder to read, so it is not a good idea to simply do it willy-nilly. \$\endgroup\$ Commented Jun 19, 2011 at 2:24
  • 1
    \$\begingroup\$ Putting operators and function calls inside a function call can make it harder to read, and debug, not easier. \$\endgroup\$ Commented Jun 19, 2011 at 6:05
9
\$\begingroup\$

Take a look at this question and answer: Optimizing an XNA 2D game.

I would guess you are either hitting a fill/texture-fetch limit (a GPU limit) or a batch limit (a CPU limit, as described in that linked answer).

Try setting the position of all of your sprites off-screen, and seeing if it makes it faster. If it does, you're hitting a fill limit. You need to reduce the amount of over-draw or simply reduce the amount of stuff you're drawing.

If you're hitting the batch limit, then SpriteSortMode.Texture is likely to give you an enormous speed-up. If you cannot use this as a real-world solution, then the correct solution is to use sprite-sheets to reduce texture swaps. (Of course, if you're only using one texture already, or your textures are pre-sorted, then this won't help.)

I seriously doubt the maths operations are dominating your CPU usage. There are faster alternatives to the + operator and ToRadians, but it's not like these are slow! (If you're accessing each sprite's information through properties - each property access has the same overhead - you'd be better off switching to fields rather inlining those functions.)

One thing that might be eating your performance is cache coherence. If each Sprite is a reference type (ie: class), then you're likely suffering a huge number of cache misses as you loop through them. Try making sprites an array of value types (ie: struct). (see this answer)

A possible worst-case is if they are already value types and you're accessing them through an interface - in that case you're doing an epic amount of boxing (slow and allocates memory).

(Of course, there could be other things wrong, but that's all I can think of at the moment based on what you've posted.)

\$\endgroup\$
2
  • \$\begingroup\$ I doubt cache coherency plays a large role for such a small loop. However, +1 for sprite-sheets. \$\endgroup\$ Commented Jun 19, 2011 at 5:34
  • \$\begingroup\$ Agreed. I'd certainly check all the other things first - but it's worth being aware of (maybe he's got a very heavy Sprite class). \$\endgroup\$ Commented Jun 19, 2011 at 6:25
1
\$\begingroup\$

About XNA sprite sheet example: https://stackoverflow.com/questions/726921/recommend-sprite-size-for-games-xna

This also may be usefull to you.

\$\endgroup\$
1
  • \$\begingroup\$ Although it's not really relevant to my problem, it's an excellent sample! Thank you. \$\endgroup\$
    – TravisG
    Commented Jun 18, 2011 at 22:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .