Search code examples
c++sdlgame-enginesdl-2game-development

SDL tilemap rendering quite slow


Im using SDL to write a simulation that displays quite a big tilemap(around 240*240 tiles). Since im quite new to the SDL library I cant really tell if the pretty slow performance while rendering more than 50,000 tiles is actually normal. Every tile is visible at all times, being around 4*4px big. Currently its iterating every frame through a 2d array and rendering every single tile, which gives me about 40fps, too slow to actually put any game logic behind the system.

I tried to find some alternative systems, like only updating updated tiles but people always commented on how this is a bad practice and that the renderer is supposed to be cleaned every frame and so on.

Here a picture of the map

So I basically wanted to ask if there is any more performant system than rendering every single tile every frame.

Edit: So heres the simple rendering method im using

void World::DirtyBiomeDraw(Graphics *graphics) {


    if(_biomeTexture == NULL) {
        _biomeTexture = graphics->loadImage("assets/biome_sprites.png");
        printf("Biome texture loaded.\n");
    }

    for(int i = 0; i < globals::WORLD_WIDTH; i++) {
        for(int l = 0; l < globals::WORLD_HEIGHT; l++) {
            SDL_Rect srect;

            srect.h = globals::SPRITE_SIZE;
            srect.w = globals::SPRITE_SIZE;

            if(sites[l][i].biome > 0) {
                srect.y = 0;
                srect.x = (globals::SPRITE_SIZE * sites[l][i].biome) - globals::SPRITE_SIZE;
            }
            else {
                srect.y = globals::SPRITE_SIZE;
                srect.x = globals::SPRITE_SIZE * fabs(sites[l][i].biome);
            }
            SDL_Rect drect = {i * globals::SPRITE_SIZE * globals::SPRITE_SCALE, l * globals::SPRITE_SIZE * globals::SPRITE_SCALE, 
                globals::SPRITE_SIZE * globals::SPRITE_SCALE, globals::SPRITE_SIZE * globals::SPRITE_SCALE};



            graphics->blitOnRenderer(_biomeTexture, &srect, &drect);

        }
    }
}

So in this context every tile is called "site", this is because they're also storing information like moisture, temperature and so on.

Every site got a biome assigned during the generation process, every biome is basically an ID, every land biome has an ID higher than 0 and every water id is 0 or lower.

This allows me to put every biome sprite ordered by ID into the "biome_sprites.png" image. All the land sprites are basically in the first row, while all the water tiles are in the second row. This way I dont have to manually assign a sprite to a biome and the method can do it itself by multiplying the tile size(basically the width) with the biome.

Heres the biome ID table from my SDD/GDD and the actual spritesheet.

The blitOnRenderer method from the graphics class basically just runs a SDL_RenderCopy blitting the texture onto the renderer.

void Graphics::blitOnRenderer(SDL_Texture *texture, SDL_Rect 
*sourceRectangle, SDL_Rect *destinationRectangle) {
    SDL_RenderCopy(this->_renderer, texture, sourceRectangle, destinationRectangle);
}

In the game loop every frame a RenderClear and RenderPresent gets called.

I really hope I explained it understandably, ask anything you want, im the one asking you guys for help so the least I can do is be cooperative :D


Solution

  • Poke the SDL2 devs for a multi-item version of SDL_RenderCopy() (similar to the existing SDL_RenderDrawLines()/SDL_RenderDrawPoints()/SDL_RenderDrawRects() functions) and/or batched SDL_Renderer backends.

    Right now you're trying slam at least 240*240 = 57000 draw-calls down the GPU's throat; you can usually only count on 1000-4000 draw-calls in any given 16 milliseconds.

    Alternatively switch to OpenGL & do the batching yourself.