6
votes

I'm programming in C# using the .NET Framework 4, and aiming to make a tile-based game with XNA. I have one large texture (256 pixels by 4096 pixels). Remember this is a tile-based game, so this texture is so massive only because it contains many tiles, which are each 32 pixels by 32 pixels. I think the experts will definitely know what a tile-based game is like. The orientation is orthogonal (like a chess board), not isometric.

In the Game.Draw() method, I have two choices, one of which will be incredibly more efficient than the other.

Choice/Method #1:

Semi-Pseudocode:

    public void Draw()
    {
        // map tiles are drawn left-to-right, top-to-bottom
        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                SpriteBatch.Draw(
                    MyLargeTexture, // One large 256 x 4096 texture
                    new Rectangle(x, y, 32, 32), // Destination rectangle - ignore this, its ok
                    new Rectangle(x, y, 32, 32), // Notice the source rectangle 'cuts out' 32 by 32 squares from the texture corresponding to the loop
                    Color.White); // No tint - ignore this, its ok
            }
        }
    }

Caption: So, effectively, the first method is referencing one large texture many many times, each time using a small rectangle of this large texture to draw the appropriate tile image.

Choice/Method #2:

Semi-Pseudocode:

    public void Draw()
    {
        // map tiles are drawn left-to-right, top-to-bottom
        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                Texture2D tileTexture = map.GetTileTexture(x, y); // Getting a small 32 by 32 texture (different each iteration of the loop)

                SpriteBatch.Draw(
                    tileTexture,
                    new Rectangle(x, y, 32, 32), // Destination rectangle - ignore this, its ok
                    new Rectangle(0, 0, tileTexture.Width, tileTexture.Height), // Notice the source rectangle uses the entire texture, because the entire texture IS 32 by 32
                    Color.White); // No tint - ignore this, its ok
            }
        }
    }

Caption: So, effectively, the second method is drawing many small textures many times.

The Question: Which method and why? Personally, I would think it would be incredibly more efficient to use the first method. If you think about what that means for the tile array in a map (think of a large map with 2000 by 2000 tiles, let's say), each Tile object would only have to contain 2 integers, for the X and Y positions of the source rectangle in the one large texture - 8 bytes. If you use method #2, however, each Tile object in the tile array of the map would have to store a 32by32 Texture - an image - which has to allocate memory for the R G B A pixels 32 by 32 times - is that 4096 bytes per tile then? So, which method and why? First priority is speed, then memory-load, then efficiency or whatever you experts believe.

2

2 Answers

9
votes

The first method is going to be much faster! It doesn't really have anything to do with your CPU-side data structures, either. You could probably get down to a couple of bytes in either case - so it wouldn't matter.

The reason that the first method is going to be faster is because of the way the GPU works. To put it simply you send the GPU draw commands with lists of vertices/triangles. These vertices have various data (position, colour, texture coordinates, etc). Crucially they cannot specify a texture - the texture to draw the triangles with must be specified on the graphics device before you draw, for that entire list of triangles.

What SpriteBatch does is automatically batch together your sprites, where it can, so that it minimises texture swaps and therefore draw commands sent. This is why it has an option for doing sort-by-texture. It is also why the source-rectangle parameter exists - to allow the use of sprite sheets - so more sprites can be drawn without having to change textures.

(In fact, the draw commands you send to the GPU - even if you are not using SpritBatch - are known as "batches". When you send too many you become "batch limited". It is surprisingly easy to become batch limited - a few hundred batches is a very rough estimate of the limit.)

2
votes

Agree. The less textures you pull from the faster this will be because it minimizes the state changes on the GPU.

To make it even faster note that most of your tiles do not change between frames. So draw them all to a single screen sized render target once and then on subsequent frames just redraw that single texture with one draw call. Add the tiles that change over the top.