Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: [Solved] Creating sf::RenderTexture in a separate thread  (Read 6172 times)

0 Members and 1 Guest are viewing this topic.

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
[Solved] Creating sf::RenderTexture in a separate thread
« on: July 28, 2015, 10:51:13 am »
When my game loads its resources, it also creates some sf::RenderTexture's. While this happens, I draw loading animation on the screen. Strangely enough, sf::RenderTexture's are not created and don't display anything drawn on them. Here's a minimal example:

#include <SFML/Graphics.hpp>
#include <thread>

bool created;

void createTexture(sf::RenderTexture& rt, sf::Sprite& sprite) {
    rt.create(512, 480);
    sprite.setTexture(rt.getTexture(), true);
    created = true;
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(512, 480), "Test");
    window.setFramerateLimit(60);

    sf::RenderTexture rt;
    sf::Sprite sprite;

    created = false;

    // just a triangle to draw for tests
    sf::VertexArray triangle(sf::Triangles, 3);
    triangle[0].position = sf::Vector2f(10, 10);
    triangle[1].position = sf::Vector2f(100, 10);
    triangle[2].position = sf::Vector2f(100, 100);
    triangle[0].color = sf::Color::Red;
    triangle[1].color = sf::Color::Blue;
    triangle[2].color = sf::Color::Green;

    std::thread loadingThread(&createTexture, std::ref(rt), std::ref(sprite));
    loadingThread.detach();

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();

        if (created) {
            // the createTexture function is finished by this point
            rt.clear();      
            rt.draw(triangle);
            rt.display();

            window.draw(sprite);
        }
       
        window.display();
    }
}

Note that a global "created" bool variable is used. This makes sure that sf::RenderTexture is not used when it's created.

If I don't use a thread there and just call createTexture directly, then the code works as expected: the triangle is displayed. But if the thread is used, the triangle is not displayed.

Maybe I'm doing something wrong and I need to suspend the main thread when I create a sf::RenderTexture? Or is this a bug in SFML?
« Last Edit: July 29, 2015, 10:07:34 am by Elias Daler »
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

AlexAUT

  • Sr. Member
  • ****
  • Posts: 396
    • View Profile
Re: Creating sf::RenderTexture in a separate thread
« Reply #1 on: July 28, 2015, 11:07:16 am »
iirc there has to be an active context when you are creating a rendetexture, but the context of the Window is active in the main-thread and cannot be active at the "loading"-thread at the same time. Source: http://www.sfml-dev.org/tutorials/2.0/graphics-draw.php#drawing-from-threads


AlexAUT

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re: Creating sf::RenderTexture in a separate thread
« Reply #2 on: July 28, 2015, 11:21:37 am »
iirc there has to be an active context when you are creating a rendetexture, but the context of the Window is active in the main-thread and cannot be active at the "loading"-thread at the same time. Source: http://www.sfml-dev.org/tutorials/2.0/graphics-draw.php#drawing-from-threads


AlexAUT

So, do I need to temporarly deactivate it to create a RenderTexture? I tried to do it in createTexture function but it didn't help.
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

AlexAUT

  • Sr. Member
  • ****
  • Posts: 396
    • View Profile
Re: Creating sf::RenderTexture in a separate thread
« Reply #3 on: July 28, 2015, 11:36:03 am »
Yeah i just tried it too with no success, so we have to summon binary  :D


AlexAUT

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Creating sf::RenderTexture in a separate thread
« Reply #4 on: July 28, 2015, 02:40:44 pm »
According to that link that Alex posted, the window (context) was deactivated in the main thread before allowing the thread to use it. Did you try deactivating the render texture before calling the thread?
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

dabbertorres

  • Hero Member
  • *****
  • Posts: 505
    • View Profile
    • website/blog
Re: Creating sf::RenderTexture in a separate thread
« Reply #5 on: July 28, 2015, 07:00:31 pm »
I figured out what is going on. Because the RenderTexture's context is created on a different thread, the context stays there. When that thread dies, its context dies with it.

So, one easy way to fix it would be to keep the thread alive for as long as you use the RenderTexture.

I found some interesting behavior though. I'm having the createTexture thread wait once it's finished, calling rt.setActive(true) on the main thread, then letting the createTexture thread continue and exit.
When doing this, I can clear from the RenderTexture, but I can't draw to it:

(click to show/hide)

I'm clearing sf::Color::White in the sf::RenderTexture, and clearing sf::Color::Black on the window. I'm also drawing triangles to the sf::RenderTexture. All that shows is white.
(See attached photo if needed.)

Whereas before I made this change, clearing sf::Color::White on the sf::RenderTexture would still show all black.


binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Creating sf::RenderTexture in a separate thread
« Reply #6 on: July 29, 2015, 09:22:24 am »
Here is the "fixed" code with a workaround (only really necessary in this example):
#include <SFML/Graphics.hpp>
#include <thread>

bool created;

void createTexture(sf::RenderTexture& rt, sf::Sprite& sprite) {
        rt.create(512, 480);
        sprite.setTexture(rt.getTexture(), true);
        created = true;

        sf::Context context;
}

int main()
{
        sf::RenderWindow window(sf::VideoMode(512, 480), "Test");
        window.setFramerateLimit(60);

        sf::Texture::getMaximumSize();
        sf::Shader::isAvailable();

        sf::RenderTexture rt;
        sf::Sprite sprite;

        created = false;

        // just a triangle to draw for tests
        sf::VertexArray triangle(sf::Triangles, 3);
        triangle[0].position = sf::Vector2f(10, 10);
        triangle[1].position = sf::Vector2f(100, 10);
        triangle[2].position = sf::Vector2f(100, 100);
        triangle[0].color = sf::Color::Red;
        triangle[1].color = sf::Color::Blue;
        triangle[2].color = sf::Color::Green;

        std::thread loadingThread(&createTexture, std::ref(rt), std::ref(sprite));
        loadingThread.detach();

        while (window.isOpen())
        {
                sf::Event event;
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window.close();
                }

                window.clear();

                if (created) {
                        // the createTexture function is finished by this point
                        rt.clear();
                        rt.draw(triangle);
                        rt.display();

                        window.draw(sprite);
                }

                window.display();
        }
}

Like dabbertorres said, when you .create() the RenderTexture in a separate thread, it will create its own context there as well. However, unlike what he said, the context doesn't get destroyed in the thread, it gets stored in the RenderTexture object to be activated wherever it is used in the future. By creating the RenderTexture in the secondary thread, you are activating it there, but it will never be deactivated. Deactivation has to be explicit (which isn't what SFML currently does) and does not automatically follow from the termination of a thread. Trying to activate the context for drawing to the RenderTexture in the primary thread will always "fail" (silently) if it is still currently active in another thread, even if that thread is no longer running. The easiest way to deactivate a context in SFML currently is simply to create another temporary one. This will override the active context within a thread.

The sf::Texture::getMaximumSize(); and sf::Shader::isAvailable(); workarounds are really only necessary in this example since you don't make any use of the RenderWindow for drawing before actually creating the RenderTexture. In any bigger application, they would typically be indirectly called from any .draw(), texture or shader call.

OK... so now that that mysterious issue is solved, time for the other thing...

You shouldn't be doing this in the first place. :P

Maybe this has not happened to you (yet), but continuously creating your RenderTextures in a separate thread will, as described above, also continuously create more and more new contexts which, in the current SFML implementation, unfortunately will not be destroyed until the application exits. You can try doing what you do in this code a few thousand times, it will eventually crash if you are lucky.

Also, I hope you are aware that moving this code into a thread makes little to no sense. If you time the calls that are placed in the thread, you will notice that they take almost no time (ignoring the overhead of constant context creation which is a problem on its own anyway). For our intents and purposes, all OpenGL calls are asynchronous.
  • Creating OpenGL resources from a thread does not make sense.
  • Drawing from a thread does not make sense.
  • Drawing simultaneously from multiple threads definitely does not make sense.
I've probably already said too many times: you only have a single logical GPU. OpenGL only supports the concept of a single command stream. Trying to multitask only wastes your time unless you are really really into advanced OpenGL and understand everything that goes on on the hardware and in the driver. This does not apply to 99% of SFML users, which is the reason I give this general advice.

If you are trying to asynchronously load resources in a secondary thread, then do that, but no more. I often see people get lazy and just throw everything into a thread (even without proper synchronization), which ends up causing even more headaches. The bottleneck is the disk read and loading process, so that is the only thing that has to go into the thread. Everything else can stay in the main thread. If you are loading textures, load the sf::Image in the thread and use the loaded image data in your primary thread once it is ready. Clean multi-threaded resource loading can be done easily in SFML because it is separated out cleanly in the API, people only have to make use of it as well.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re: Creating sf::RenderTexture in a separate thread
« Reply #7 on: July 29, 2015, 10:07:08 am »
@binary1248 thanks for you in-depth answer! I guess the problem is solved. I will not be lazy from now on ;D
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler