2
\$\begingroup\$

I'm having an issue with my tilemap renderer, with small maps (like 40*40 tiles) it works fine but with maps that have 400*400 tiles the framerate becomes really slow when I try to move the camera.

Here are my rendering methods

/// Renders the map
void Map::Map::render(sf::RenderWindow& window)
{
    for (auto c : m_tiles)
    {
        for (Tile& tile : c)
        {
            m_renderer.render(tile);
        }
    }
}

And for the TileRenderer

void Renderer::TileRenderer::render(const Map::Tile& tile) const
{
    try
    {
        if (m_previousType != tile.getType())
            m_sprite.setTextureRect(getTypeTextureRect(tile.getType()));

        m_sprite.setPosition(tile.getAbsolutePosition());

        m_renderWindow.draw(m_sprite);
        m_previousType = tile.getType();
    }
    catch (ETileTypeUndefined e)
    {
        Logger::getInstance().log(e.what());
    }
}

Any idea to solve that poor framerate ? It doesn't seems to be a memory issue. Visual Studio debugger tells me that external code spend a lot of time in the renderer's render function (calls to sf::RenderWindow::draw(sf::Drawable) I guess).

\$\endgroup\$
  • \$\begingroup\$ How large are your tiles? And are all 400x400 on screen at the same time? \$\endgroup\$ – William Mariager May 16 '15 at 11:14
  • \$\begingroup\$ My tiles are 32*32px and no they aren't but they could since I'm making a map editor which allows to unzoom the map. \$\endgroup\$ – Claymeuns May 16 '15 at 11:17
  • \$\begingroup\$ Then you have to get creative. Even only 400x400 that's 160000 draw calls per frame. Do you have access to fragment-/pixel-shaders? \$\endgroup\$ – William Mariager May 16 '15 at 11:29
  • \$\begingroup\$ Yes, I have access to them. \$\endgroup\$ – Claymeuns May 16 '15 at 11:32
  • 1
    \$\begingroup\$ Then you might be interested in this: blog.tojicode.com/2012/07/sprite-tile-maps-on-gpu.html \$\endgroup\$ – William Mariager May 16 '15 at 11:39
2
\$\begingroup\$

I think what you're looking for is to draw all of your tiles each frame to a sf::RenderTexture and then render that single object to the screen.

The render texture is stored in an offscreen texture which is great for precomputing a complex static texture (like a level's background from multiple tiles) etc.

Example:

// Create a new render-window
sf::RenderWindow window(sf::VideoMode(800, 600), "SFML window");
// Create a new render-texture
sf::RenderTexture texture;
if (!texture.create(500, 500))
    return -1;
// The main loop
while (window.isOpen())
{
   // Event processing
   // ...
   // Clear the whole texture with red color
   texture.clear(sf::Color::Red);
   // Draw stuff to the texture
   texture.draw(sprite);  // sprite is a sf::Sprite
   texture.draw(shape);   // shape is a sf::Shape
   texture.draw(text);    // text is a sf::Text
   // We're done drawing to the texture
   texture.display();
   // Now we start rendering to the window, clear it first
   window.clear();
   // Draw the texture
   sf::Sprite sprite(texture.getTexture());
   window.draw(sprite);
   // End the current frame and display its contents on screen
   window.display();
}
\$\endgroup\$
  • \$\begingroup\$ Seems interesting too, I'll try that too when I'll have time. Thank you \$\endgroup\$ – Claymeuns May 18 '15 at 20:25
1
\$\begingroup\$

You are going to have to embrace level of details. The rendering itself may already be using mipmaps, so the reduction filter will not consume lots of bandwidth to draw sprites that end up being 2x2 on the screen even if its texture is originally 200x200. Your problem comes from too many draw calls !
so you are going to have to make a decision about what could be completely cut out of the render after a certain level of zoom out.
Maybe you don't need terrain details like stones or bushes who knows.
You could also identify all the static parts of your world, and fuse this all together by using DanoThorn's technique of baking them into an impostor. Most complex worlds require layers, so you can bake in layers as necessary. You will need a cache manager to keep baked impostors with a garbage collection technique based on frequency of use. When the camera spans impostors will move out of screen. You can attach a "counter of not-rendered frames" and the highest get de-allocated first when you decide it's time to free memory.
This is all very costly, so you have to decide. Do you need to zoom out so much that you will pay to implement all of this ? or will you cut the feature altogether and be content by limiting the zoom out factor so that your on-screen number of tiles never exceed 5k/10k top ?
Your game, your choice.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.