XNA Spritebatch? Watch Your Scaling!


Here’s an interesting tidbit I ran across in an App Hub thread about xna spritebatch performance: scaling down sprites can cost performance. A lot of it. While drawing a large texture scaled down will probably be faster than drawing that large texture at its full size, the difference between drawing a large texture scaled down versus drawing a smaller texture at its native scale can be surprisingly large. One of the examples in the thread showed that the framerate tripled when switching from drawing 256×256 textures at 0.125 scale versus drawing 32×32 textures at 1.0 scale.

With a performance difference that big I just had to see it for myself, so I decided to whip up a little experiment to try it out.

First of all, I start with the performance timing application I put together in an earlier post. Then, I setup a large number of sprites (1024 to be exact because, well you know, programmers like power-of-2 numbers) to be drawn at various random positions. I also make half of the sprites translucent while the other half have full alpha. Finally, I scale all the sprites by 0.5.

public struct TileSprite
{
	public Texture2D Texture;
	public Rectangle? SourceRect;
	public Vector2 Position;
	public float Scale;
	public Color Color;
	public float Depth;

	public TileSprite(Texture2D _texture, Rectangle? _sourceRect, Vector2 _position, float _scale, Color _color, float _depth)
	{
		Texture = _texture;
		SourceRect = _sourceRect;
		Position = _position;
		Scale = _scale;
		Color = _color;
		Depth = _depth;
	}

	public void Draw(SpriteBatch spriteBatch)
	{	
		spriteBatch.Draw(Texture, Position, SourceRect, Color, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0.0f);
	}
}


protected override void LoadContent()
{
	// Create a new SpriteBatch, which can be used to draw textures.
	spriteBatch = new SpriteBatch(GraphicsDevice);

	// TODO: use this.Content to load your game content here
	stopWatchFont = Content.Load<SpriteFont>("DefaultFont");

	List<Texture2D> textures = new List<Texture2D>();
	float spriteScale = 0.5f;
	
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Boy"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Cat Girl"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Horn Girl"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Pink Girl"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Princess Girl"));
	List<Rectangle> spriteRects = new List<Rectangle>();
	Texture2D packedTexture = TexturePacker.PackSprites(GraphicsDevice, textures, spriteRects);

	Random rng = new Random(1024);
	sprites = new List<TileSprite>(SPRITE_COUNT);
	for (int i = 0; i < SPRITE_COUNT; ++i)
	{
		Vector2 spritePosition = new Vector2((float)rng.NextDouble() * GraphicsDevice.PresentationParameters.BackBufferWidth - 50, (float)rng.NextDouble() * GraphicsDevice.PresentationParameters.BackBufferHeight + 20);
		float alphaChance = (float)rng.NextDouble();
		Color spriteColor = new Color(1.0f, 1.0f, 1.0f, alphaChance <= 0.5f ? 0.5f + alphaChance : 1.0f);

		TileSprite spriteData = new TileSprite(packedTexture, spriteRects[rng.Next(spriteRects.Count)], spritePosition, spriteScale, spriteColor, (float)i / SPRITE_COUNT);
		//TileSprite spriteData = new TileSprite(textures[rng.Next(textures.Count)], null, spritePosition, spriteColor);
                
		sprites.Add(spriteData);
	}
}

It’s a pretty rough approximation of what may get rendered in a real game, but it’ll do for illustrating the change in performance due to sprite scaling.

Captured off my Xbox 360, it takes roughly 4.4 ms to draw these 1024 sprites. The sprite textures are 101×171 scaled by 0.5 during rendering.

Okay, so I have a performance baseline for the downscaled texture case. The next step is to modify my code to draw smaller textures at full scale. Popping open Gimp, I create half-size versions of the PlanetCute character textures then modify my sprite setup code:

	List<Texture2D> textures = new List<Texture2D>();
	float spriteScale = 1.0f;
	
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Boy Small"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Cat Girl Small"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Horn Girl Small"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Pink Girl Small"));
	textures.Add(Content.Load<Texture2D>("PlanetCute PNG/Character Princess Girl Small"));
	List<Rectangle> spriteRects = new List<Rectangle>();
	Texture2D packedTexture = TexturePacker.PackSprites(GraphicsDevice, textures, spriteRects);

Running this version of the code gets me the following:

Looking at the drawtime per frame, I can see that the performance is vastly improved. There are some scaling artifacts but those are from inconsistencies between Gimp’s scaling and DirectX’s.

From the App Hub thread, according to Shawn Hargreaves this change in performance is due to the Xbox 360 not being optimized for scaled textures. Large textures being scaled down results in lots of texels being sampled for each on-screen pixel and the texture cache getting cleared unnecessarily. He goes on to say that mipmaps will help this sort of situation since a smaller mipmap texture will be cached better for a small sprite.

So, there you go. Scaling down hurts. Sure, large textures scaled down will generally still cost less draw time than large textures drawn at full size, but you end up leaving a lot of potential performance on the table when your scaled-down sprites don’t have appropriately scaled-down textures to support them.

Share this Article:
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • Print

One thought on “XNA Spritebatch? Watch Your Scaling!

  1. Pingback: Spatial Indexing for Fun and Performance | Game Dev Without a Cause

Comments are closed.