4
\$\begingroup\$

I realize this is a variation on what has got to be a common question, but after reading several (good answers) I'm no closer to a solution here. So here's my situation:

I'm making a 2D game which has (among some other things) a tiled world, and so, drawing this world implies drawing a jillion tiles each frame (depending on resolution: it's roughly a 64x32 tile with some transparency).

Now I want the user to be able to maximize the game (or fullscreen mode, actually, as its a bit more efficient) and instead of scaling textures (bleagh) this will just allow lots and lots of tiles to be shown at once. Which is great!

But it turns out this makes upward of 2000 tiles on the screen each time, and this is framerate-limiting (I've commented out enough other parts of the game to make sure this is the bottleneck). It gets worse if I use multiple source rectangles on the same texture (I use a tilesheet; I believe changing textures entirely makes things worse), or if you tint the tiles, or whatever.

So, the general question is this:

What are some general methods for improving the drawing of thousands of repetitive sprites?

Answers pertaining to XNA's SpriteBatch would be helpful but I'm equally happy with general theory. Also, any tricks pertaining to this situation in particular (drawing a tiled world efficiently) are also welcome.

I really do want to draw all of them, though, and I need the SpriteMode.BackToFront to be active, because I use the layering quite essentially.

I don't have any easily digested sample code since the method has become somewhat complicated, but no extra tiles are being "drawn," and all the drawing is inside one SpriteBatch.Begin / SpriteBatch.End call.

\$\endgroup\$
5
  • \$\begingroup\$ Could you provide a bit of sample code? It would make it easier to see what you are doing right now. \$\endgroup\$
    – Moss
    Commented Dec 12, 2012 at 14:04
  • 1
    \$\begingroup\$ Without a sample code, we can't know what is going wrong. Are you calling each spriteBatch.Begin and spriteBatch.End only once? Are you avoiding to draw the tiles that do not appear because they're out of the screen? \$\endgroup\$ Commented Dec 12, 2012 at 16:40
  • \$\begingroup\$ I'm trying to think of a sample code which would be helpful. I think it would have more ellipses than actual code, ultimately. But yes, I'm calling spriteBatch.Begin and spriteBack.End only once, and I'm only drawing the tiles that are actually being drawn. \$\endgroup\$ Commented Dec 12, 2012 at 18:21
  • \$\begingroup\$ Are you drawing other parts of the world which go "on top" of the tiles (e.g. characters) in the same spritebatch begin/end block? \$\endgroup\$
    – Martin
    Commented Dec 12, 2012 at 18:56
  • \$\begingroup\$ Yes, but some of the tiles will go on top of some of the characters, to some degree. For example, walls come up a bit. \$\endgroup\$ Commented Dec 13, 2012 at 14:23

1 Answer 1

2
\$\begingroup\$

I'm just going over the things I found important in my game. Some of these you already addressed but I'm listing all of them for completeness.

For you I recommend changed from SpriteMode.BackToFront to SpriteMode.Deffered and having the logic of your code structured that id draws in the correct order. BackToFront/FrontToBack both scale the worst of all the sprite modes because the more items the longer the sorting takes.

The other thing I recommend is making sure that the "new" keyword is not used at all in the drawing loop for your tiles. This advice is pretty generic but without any code (seeing the loop for the tiles would be very helpful) its all I can offer.

Overall I'm more suspicious that the efficiency of how you iterate though and store your data that describes how the tile set is used is the culprit and not the actually render time.

1) XNA doesn't do culling for you so make sure you are culling off tiles that doesn't need to render.

2 ) Avoid putting things on the stack or using the new keyword at all in the loops (see how I create 1 rectangle and only shift the x,y value). Also iterating though what ever data structure holds your data in such a way that its acceded sequentially will help out the cashing and speed things up.

        //Init the holder
        _holder = new Rectangle(0, 0, TileWidth, TileHeight);

        //Figure out the min and max tile indices to draw
        var minX = Math.Max((int)Math.Floor((float)worldArea.Left / TileWidth), 0);
        var maxX = Math.Min((int)Math.Ceiling((float)worldArea.Right / TileWidth), Width);

        var minY = Math.Max((int)Math.Floor((float)worldArea.Top / TileHeight), 0);
        var maxY = Math.Min((int)Math.Ceiling((float)worldArea.Bottom / TileHeight), Height);

        foreach (var tileLayer in TileLayers) {
            for (var y = minY; y < maxY; y++) {
                for (var x = minX; x < maxX; x++) {

                    _holder.X = x * TileWidth;
                    _holder.Y = y * TileHeight;

                    var t = tileLayer[y * Width + x];
                    spriteBatch.Draw(
                        t.Texture,
                        _holder, 
                        t.SourceRectangle,
                        Color.White, 
                        0,
                        Vector2.Zero,
                        t.SpriteEffects, 
                        0);
                }
            }
        }

3) Also make sure you are using only 1 sprite sheet for all of the tiles.

4) SpriteMode.Deferred is the fastest SpriteMode by far. Its enough of an improvident that often having multiple Begin/End pairs using SpriteMode.Deferred for layers is often faster than using sorting. I would go as far to say and to never use BackToFront or FrontToBack, needing to do so is often a result of not properly abstracting out your rendering code.

\$\endgroup\$
5
  • \$\begingroup\$ I thought SpriteMode.Immediate is slightly faster than Deferred in the case where you only use one SpriteBatch begin/end call. \$\endgroup\$
    – ashes999
    Commented Dec 13, 2012 at 0:27
  • \$\begingroup\$ Nope. Used to be but not anymore, forget which version this changed. \$\endgroup\$ Commented Dec 13, 2012 at 1:39
  • \$\begingroup\$ So are they comparable, or is Deferred actually faster? \$\endgroup\$
    – ashes999
    Commented Dec 13, 2012 at 3:22
  • 2
    \$\begingroup\$ For the case of only 1 graphics state being needed Deferred is significantly faster. Immediate allows you to change state within a Begin/End pair. Only in the case is saves you multiple Begin/End pairs can immediate be faster. \$\endgroup\$ Commented Dec 13, 2012 at 5:26
  • \$\begingroup\$ 1) and 3) really are being done, I promise. I'll look through for 2) as well, though I think it's pretty efficient. As for 4), I'm quite surprised, since sorting a few thousand objects with easy compareTo methods (here, just the floating point depth value) should be so trivial as to be negligible on a modern machine. But I'll check it out. \$\endgroup\$ Commented Dec 13, 2012 at 14:19

You must log in to answer this question.

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