Sfml: Much lower performance when sf::Window is not created

Created on 5 Jun 2020  路  11Comments  路  Source: SFML/SFML

Subject of the issue

I have written an app recreating images by drawing multiple circles (using genetic algorithm) and I wanted to make GUI optional (I wanted to draw without showing any window), but I have experienced a massive performance drop when I was removing sf::Window.

Your environment

  • Ubuntu 20.04
  • 2.5.1+dfsg-1build1
  • GNU 9.3.0

Steps to reproduce

#include <SFML/Graphics.hpp>

int main()
{
    //sf::Window window(sf::VideoMode(200, 200), "SFML"); //Uncomment to see difference

    std::vector<sf::CircleShape> circles_;
    for (int i = 0; i < 200; i++)
    {
        sf::CircleShape circle(rand() % 50 + 10, 20);
        circle.setFillColor(sf::Color(rand() % 256, rand() % 256, rand() % 256, 128));
        circle.setPosition(rand() % (100), rand() % (100));
        circles_.push_back(circle);
    }

    sf::Clock clock;
    uint i = 0;
    while (i < 10)
    {

        sf::RenderTexture generated_texture;
        generated_texture.create(100, 100);
        for (auto &shape : circles_)
        {
            generated_texture.draw(shape);
        }
        i++;
    }
    double result = double(i) / clock.getElapsedTime().asSeconds();
    cout << "Result: " << result << "loops/sec ";
    return 0;
}

Expected behavior

When window is created:
Result: 1611.86loops/sec

Actual behavior

When sf::Window is not used:
Result: 7.43716loops/sec

not an issue

Most helpful comment

When sf::RenderTexture is done drawing it tries to restore the state to how it was before the draw call. Since this was the "no context" state it will repeatedly activate and deactivate the context every iteration. This is standard behaviour for any OpenGL resource that can live on its own without necessarily having a window.

If you want to do anything rendering related it is recommended to always have some kind of context-owning-thing lying around. If you don't want a full sf::Window then an sf::Context will have to do.

Due to the nature of OpenGL, you will never get around the limitation of having some kind of window (whether it's visible or not) that itself owns a context. That's just the way the API designers designed it 25 years ago.

All 11 comments

This is most likely because the sf::RenderTexture is able to reuse the OpenGL context created by the window.

But this is just a side effect, your real problem is that you're creating a new sf::RenderTexture at every iteration. Create a single render-texture, big enough to hold all your drawings, and simply clear it at every iteration.

This is just simplified example of this what happens inside of my app, but yes, moving sf::RenderTexture generated_texture; before loop gives performance boost, but we're still not quite there.
Performance after modification:
When window is created:
Result: 2453.39loops/sec
When sf::Window is not used:
Result: 16.7695loops/sec

It's more RenderTexture::create than the constructor, that impacts performances.

Yes, it impacts preformance, but it isn't source of this problem.

When I move things you mentioned from main loop, problem still exists.

Main loop now:

    while (i < 10)
    {

        for (auto &shape : circles_)
        {
            generated_texture.draw(shape);
        }
        i++;
    }

I think this should be marked as a bug, or strange property of this library which should be mentioned in documentation of SFML.

Can you post a modified version of your minimal example that takes in account these differences, with the corresponding benchmark results?

int main()
{
    // sf::Window window(sf::VideoMode(200, 200), "SFML");

    std::vector<sf::CircleShape> circles_;
    for (int i = 0; i < 200; i++)
    {
        sf::CircleShape circle(rand() % 50 + 10, 20);
        circle.setFillColor(sf::Color(rand() % 256, rand() % 256, rand() % 256, 128));
        circle.setPosition(rand() % (100), rand() % (100));
        circles_.push_back(circle);
    }
    sf::RenderTexture generated_texture;
    generated_texture.create(100, 100);
    sf::Clock clock;
    uint i = 0;
    while (i < 10)
    {

        for (auto &shape : circles_)
        {
            generated_texture.draw(shape);
        }
        i++;
    }
    double result = double(i) / clock.getElapsedTime().asSeconds();
    cout << "Result: " << result << " loops/sec";
    return 0;
}

Performance after modification:
When window is created:
Result: 2141.79 loops/sec
When sf::Window is not used:
Result: 147.822 loops/sec

Ok, now that's strange 馃槃

@binary1248 any idea?

When sf::RenderTexture is done drawing it tries to restore the state to how it was before the draw call. Since this was the "no context" state it will repeatedly activate and deactivate the context every iteration. This is standard behaviour for any OpenGL resource that can live on its own without necessarily having a window.

If you want to do anything rendering related it is recommended to always have some kind of context-owning-thing lying around. If you don't want a full sf::Window then an sf::Context will have to do.

Due to the nature of OpenGL, you will never get around the limitation of having some kind of window (whether it's visible or not) that itself owns a context. That's just the way the API designers designed it 25 years ago.

@binary1248
Thanks, your solution seems to work. :smile:

It wasn't easy to find someone who knows what could be wrong here. (I was asking about this on SFML Discord and I have also asked a question on stackoverflow).

I think it would be good to mention it somewhere in tutorials or documentation.

When sf::RenderTexture is done drawing it tries to restore the state to how it was before the draw call. Since this was the "no context" state it will repeatedly activate and deactivate the context every iteration. This is standard behaviour for any OpenGL resource that can live on its own without necessarily having a window.
If you want to do anything rendering related it is recommended to always have some kind of context-owning-thing lying around. If you don't want a full sf::Window then an sf::Context will have to do.
Due to the nature of OpenGL, you will never get around the limitation of having some kind of window (whether it's visible or not) that itself owns a context. That's just the way the API designers designed it 25 years ago.

@binary1248 Could I quote your words in my answer on stackoverflow?

This is the internet. Do whatever you want with the information. 馃槃

Was this page helpful?
0 / 5 - 0 ratings