Game Development Stack Exchange is a question and answer site for professional and independent game developers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm looking to improve the performance of my Draw() method for my tilemap. I've poked around for a few hours and done things like:

  • Removed any new calls from the for loops where I'm drawing the tiles.
  • Made a texture atlas/spritesheet; all my tiles for the map are on the same file to avoid texture swapping.
  • Culling - I only show the tiles that are on the screen (plus an extra two to avoid tiles popping in at the bounds of the camera).

The code is as follows:

        public void Draw(SpriteBatch spriteBatch)
    {
        int firstTileX = (int)Camera2D.Position.X / Tile.WIDTH;
        int firstTileY = (int)Camera2D.Position.Y / Tile.HEIGHT;

        // For smooth camera movement on the tilemap.
        int offsetX = (int)Camera2D.Position.X % Tile.WIDTH;
        int offsetY = (int)Camera2D.Position.Y % Tile.HEIGHT;

        // ScreenScale factor included for culling.
        int tileCountX = (int)(Camera2D.Width / (GraphicsConfig.ScreenScaleX * Tile.WIDTH)) + 2;
        int tileCountY = (int)(Camera2D.Height / (GraphicsConfig.ScreenScaleY * Tile.HEIGHT)) + 2;

        // No index out of range exceptions allowed!
        if (tileCountX + firstTileX >= _width)
            tileCountX = (_width - 1) - firstTileX;

        if (tileCountY + firstTileY >= _height)
            tileCountY = (_width - 1) - firstTileY;

        Vector2 tmpPositionHolder = new Vector2();

        for (int i = 0; i < tileCountY; ++i)
        {
            for (int j = 0; j < tileCountX; ++j)
            {
                int row = i + firstTileY;
                int col = j + firstTileX;

                // If the camera's position is off the map (e.g. firstTileX < 0 and/or firstTileY < 0).
                if (row < 0 || col < 0)
                    continue;

                tmpPositionHolder.X = ((j * Tile.WIDTH) - offsetX);
                tmpPositionHolder.Y = ((i * Tile.HEIGHT) - offsetY);
                spriteBatch.Draw(AssetManager.Textures["Textures/tile_spritesheet_v2"],
                                 tmpPositionHolder,
                                 _data[row, col].Source,
                                 Color.White);
            }
        }
    }

I will note that I do use a Dictionary to store my textures in a static class; I've been pondering changing this but I've had a really difficult time finding a current argument for storing the textures per class or in a asset manager (and loading/unloading as appropriate).

I've looked through the source for MonoGame and noted that, to avoid redundant method calls, I could try filling out all of the parameters for spriteBatch.Draw() (origin, scale, etc) and/or using a Rectangle from the get-go (again, after looking through MonoGame's source) but the performance it would appear to save seems trivial.

The virtual resolution is set to 400x300 and the current window is set to 800x600 (scale factor of 2 in both X and Y) and the tiles are 16x16. I get a noticeable improvement in performance if I shrink tileCountX or tileCountY by 1 (~15FPS from 150 to 165), which makes me feel like my problem is indeed related to how I'm rendering my tiles.

Help is appreciated, thank you!

Edit: Updated code -

        public void Draw(SpriteBatch spriteBatch)
    {
        int firstTileX = (int)Camera2D.Position.X / Tile.WIDTH;
        int firstTileY = (int)Camera2D.Position.Y / Tile.HEIGHT;

        // For smooth camera movement on the tilemap.
        int offsetX = (int)Camera2D.Position.X % Tile.WIDTH;
        int offsetY = (int)Camera2D.Position.Y % Tile.HEIGHT;

        // ScreenScale factor included to improve HSR.
        int tileCountX = (int)(Camera2D.Width / (GraphicsConfig.ScreenScaleX * Tile.WIDTH)) + 2;
        int tileCountY = (int)(Camera2D.Height / (GraphicsConfig.ScreenScaleY * Tile.HEIGHT)) + 2;

        // No index out of range exceptions allowed!
        if (tileCountX + firstTileX >= _width)
            tileCountX = (_width - 1) - firstTileX;

        if (tileCountY + firstTileY >= _height)
            tileCountY = (_width - 1) - firstTileY;

        Vector2 tmpPositionHolder = new Vector2();

        for (int i = 0; i < tileCountY; ++i)
        {
            for (int j = 0; j < tileCountX; ++j)
            {
                int row = i + firstTileY;
                int col = j + firstTileX;

                // If the camera's position is off the map (e.g. firstTileX < 0 and/or firstTileY < 0).
                if (row < 0 || col < 0)
                    continue;

                tmpPositionHolder.X = ((j * Tile.WIDTH) - offsetX);
                tmpPositionHolder.Y = ((i * Tile.HEIGHT) - offsetY);

                spriteBatch.Draw(_texture, tmpPositionHolder, _data[row, col].Source, Color.White);
            }
        }
    }
share|improve this question
    
What if you move the AssetManager.Textures["Textures/tile_spritesheet_v2"] line to above the for loops? – craftworkgames Nov 7 at 5:09
    
Hmm, that's actually a good point I missed - I was about to go to bed but let me boot up my PC and give that a check, will have results in a few minutes! – Sheer Nov 7 at 5:24
    
Improvement! It brought it from ~160FPS to ~200. Although I will say I did not play around with that enough to see if it maintains a higher framerate with different resolutions (e.g. if it's more resistant to frame drops with more tiles visible). I simply added a private field for the Texture and assigned it in the LoadContent() method for my class. – Sheer Nov 7 at 5:33
    
Just curious, what fps are you aiming for? I see you can reach 200fps already. Isn't this going into the realm of micro-optisation where additional effort is hardly worth the improvement anymore? – Felsir Nov 7 at 6:41
    
I'm aiming for a high framerate even if there's a lot of tiles on the screen due to resolution changes (e.g. if there's a few thousand tiles on screen). I'll be able to toy around more with the results of the potential solution @craftworkgames mentioned later, but for now I'm still looking to render a large sum of tiles. – Sheer Nov 7 at 14:55

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.