6

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.).

TravisG
  • 4,402
  • 4
  • 36
  • 59
  • 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? – BlueRaja - Danny Pflughoeft Jun 18 '11 at 19:41
  • 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. – TravisG Jun 18 '11 at 19:51
  • Also, when doing performance tests, make sure Game.IsFixedTimeStep and graphics.SynchronizeWithVerticalRetrace are set to false. – BlueRaja - Danny Pflughoeft Jun 18 '11 at 19:57
  • 1
    @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. – AttackingHobo Jun 18 '11 at 20:09
  • I just quickly tested my engine, I drop to about 30FPS with 50000 calls using the same instance to a 64x64 texture, with quite a few instructions to calculate position, etc. I would check the size and as @AttackingHobo says, try to make sure you reference the same Textures. It seems strange though, because the ContentManager automatically returns a texture reference if it is already loaded... – Jonathan Connell Jun 18 '11 at 20:17
  • Alright, manually saving the texture to a tmp texture before rendering and then using it for all spriteBatch.draw() calls significantly increases the performance, but it again starts dropping to 15 fps at around 7500 sprites. This still doesn't make a lot of sense to me though, as spr.Texture should return a reference to the used texture (although the reference itself is copied by value of course). I will no try to change the other stuff to see if I can improve performance further somehow. – TravisG Jun 18 '11 at 20:30
  • Hello, another update, and finally (I guess) a solution. I think the size of the texture was the problem. I reduced the size of the texture to 64x64 (from 200x200 previously) and it now runs at about 60 FPS when drawing 50000 sprites. Thanks to all of you. I wasn't aware that bigger textures actually made that much of a difference (I knew that using, lets say, 1028x1028 textures costs a lot of performance, but I didn't think that a small difference of 120x120 texels makes that much of a difference). – TravisG Jun 18 '11 at 20:47
  • 1
    @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. – AttackingHobo Jun 18 '11 at 21:25
  • @heishe glad you found the problem – Jonathan Connell Jun 18 '11 at 21:33

3 Answers3

9

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.)

Andrew Russell
  • 21,268
  • 7
  • 56
  • 103
6

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.

AttackingHobo
  • 7,091
  • 4
  • 36
  • 48
  • 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. – AttackingHobo Jun 18 '11 at 23:15
  • I didn't downvote you, but I do find the idea of the maths operations there dominating CPU usage very hard to swallow. – Andrew Russell Jun 19 '11 at 01:40
  • 3
    Huh, I never said anything about it dominating CPU usage, just that doing extra math and calling extra function could slow it down. – AttackingHobo Jun 19 '11 at 02:11
  • 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. – Andrew Russell Jun 19 '11 at 02:24
  • 1
    Putting operators and function calls inside a function call can make it harder to read, and debug, not easier. – AttackingHobo Jun 19 '11 at 06:05
  • Moving them from the parameter list into temporary locals immediately preceding it can sometimes improve readability and has no impact on performance (a matter of taste). Pre-calculating things is a good performance improvement strategy - but it will necessarily spread related code into different locations, which is generally bad for readability and maintenance. – Andrew Russell Jun 19 '11 at 06:30
  • I'm no god, and I sure have no authority, but I just don't understand the need for some folks to troll other people who give some good advice... Now, @Hobo: Earlier I was working on an HUD menu system and I was about to add a SpriteBatch to the class and I told myself "Do I really want each and every component to have his own sprite batch with .start and .end.. not really!" :-D – Tipx Jul 10 '11 at 00:26
  • @Tipx exactly, you could store the entire hud on one sprite-sheet, so you only have to do one spritebatch. – AttackingHobo Jul 10 '11 at 03:53
1

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

This also may be usefull to you.