SFML community forums

Help => System => Topic started by: NPS on January 20, 2013, 03:03:26 am

Title: [2.0] Using a thread to load game resources
Post by: NPS on January 20, 2013, 03:03:26 am
I'm making a loading screend and I thought I needed a second thread to load the game resources in. In the meantime the main thread would keep running main game loop, handling events, showing "loading" image and keeping window responsive. So the second thread only loads the resources and then ends.

For simplicity's sake (if I simplify my problem too much and you won't be able to help me then I'll go into more detail and paste some code but I'd like to avoid that) let's say a have this big Game class which has all resources as fields and and auxiliary private Load() method (which only loads these resources, nothing else). My game is very small so one big class is enough, actually. Anyway, in the main thread I call a Game's method (let's call it PublicLoad()) which starts a new thread giving it its Load() method and "this" pointer, then I keep running the main loop until the data are loaded (I have a boolean variable "loaded" which is set to "true" by the 2nd thread when the loading is done and the main thread keeps checking this variable).

After all that, the Game class should start "playing" the game using the loaded resources. But this doesn't work as inteded, as I can see some of the resources loaded and used, and others not (I can hear the music and sounds, I can see some images but not others). Although, after checking the resources with debugger everything seems loaded and fine). Just nothing shows up on the screen.

Also actually the only things that I can't see on my screen are classes using sf::RenderTexture, I don't know if it's related to using threads in anyway.

Oh yeah, and the same Game class works fine when I'm loading using only the main thread.

Edit: Ok, it's definitely something with sf::RenderTexture, because if I draw directly into sf::RenderWindow instead of sf::RenderTarget (and then from it to the window) everything works fine.

Any idea? Should I post some code (what code would be helpful here)?
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on January 21, 2013, 10:06:04 am
What's your graphics card? You should try the latest version of your graphics driver, and the latest sources of SFML.
Title: Re: [2.0] Using a thread to load game resources
Post by: NPS on January 21, 2013, 11:16:22 am
I have GTX 460. Is "SFML 2.0 snapshot" the latest version?
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on January 21, 2013, 11:56:02 am
Quote
Is "SFML 2.0 snapshot" the latest version?
Yes.
Title: Re: [2.0] Using a thread to load game resources
Post by: NPS on January 21, 2013, 12:15:43 pm
So I am using latest graphics driver and sources version.
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on January 21, 2013, 12:21:23 pm
So you should post a complete and minimal code that reproduces your problem.
Title: Re: [2.0] Using a thread to load game resources
Post by: NPS on January 21, 2013, 01:03:08 pm
It would be something like this. The number on the left (1337) displays fine, the number on the right (7331, loaded in a separate thread) doesn't show up at all.

main.cpp:
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include "DigitDisplay.hpp"

typedef sf::Vector2<float> float2;

sf::RenderWindow window;
DigitDisplay numberNormal;
DigitDisplay numberThread;

void LoadResources(void)
{
    numberThread.Load("data/digits.png", "7331");
}

int main(void)
{
    numberNormal.Load("data/digits.png", "1337");
    numberNormal.SetPosition(float2(300.0f, 300.0f));
    sf::Thread thread(LoadResources);
    thread.launch();
    thread.wait();
    numberThread.SetPosition(float2(600.0f, 300.0f));

    window.create(sf::VideoMode(800, 600, 32), "SFML Framework",
        sf::Style::Titlebar | sf::Style::Close);
    window.setVerticalSyncEnabled(true);
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                window.close();
            }
        }
        window.clear(sf::Color::Black);
        numberNormal.Render();
        numberThread.Render();
        window.display();
    }
    return EXIT_SUCCESS;
}
 
DigitDisplay.hpp:
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>

typedef unsigned int uint;
typedef sf::Vector2<int> int2;
typedef sf::Vector2<float> float2;

extern sf::RenderWindow window;

class DigitDisplay
{
    private:
    sf::Texture digitImage;
    std::vector<sf::Sprite> digits;
    sf::RenderTexture renderTexture;
    sf::Sprite textureSprite;
    std::string text;
    float2 position;

    void CreateRenderTexture(void);
    inline int GetDigit(char c);

    public:
    void Load(std::string filename, std::string _text = "");
    void Render(void);
    void SetPosition(float2 _position);
};
 
DigitDisplay.cpp:
#include "DigitDisplay.hpp"

void DigitDisplay::CreateRenderTexture(void)
{
    int2 size(digitImage.getSize().x * text.size() / 10, digitImage.getSize().y);
    renderTexture.create(size.x, size.y);
    textureSprite.setTexture(renderTexture.getTexture());
    textureSprite.setTextureRect(sf::IntRect(0, 0, size.x, size.y));
}

int DigitDisplay::GetDigit(char c)
{
    switch (c)
    {
        case '0':
            return 0;
        case '1':
            return 1;
        case '2':
            return 2;
        case '3':
            return 3;
        case '4':
            return 4;
        case '5':
            return 5;
        case '6':
            return 6;
        case '7':
            return 7;
        case '8':
            return 8;
        case '9':
            return 9;
        default:
            return -1;
    }
}

void DigitDisplay::Load(std::string filename, std::string _text)
{
    digitImage.loadFromFile(filename);
    uint digitWidth = digitImage.getSize().x / 10;
    uint frameHeight = digitImage.getSize().y;
    digits.resize(10);
    for (int i = 0 ; i < 10 ; ++i)
    {
        digits[i].setTexture(digitImage);
        digits[i].setTextureRect(sf::IntRect(i * digitWidth, 0, digitWidth, frameHeight));
    }
    text = _text;
    if (text.size())
    {
        CreateRenderTexture();
    }
}

void DigitDisplay::Render(void)
{
    renderTexture.clear(sf::Color::Transparent);
    float digitWidth = digitImage.getSize().x * 0.1f;
    for (uint i = 0 ; i < text.size() ; ++i)
    {
        int digit = GetDigit(text[i]);
        digits[digit].setPosition(i * digitWidth, 0.0f);
        renderTexture.draw(digits[digit]);
    }
    renderTexture.display();
    textureSprite.setPosition(position);
    window.draw(textureSprite);
}

void DigitDisplay::SetPosition(float2 _position)
{
    position = _position;
}
 

======================================================================
Edit: I've made some progress. I found out that when I call DigitDisplay::CreateRenderTexture() from the 2nd thread nothing shows up on the screen. However, when I call it from the main thread (after loading the image and setting up sf::Sprite in DigitDisplay in 2nd thread) the image shows up on the screen and everything seems to be working fine.

Can't I use sf::RenderTexture::create() in another thread? If I can, how to do it correctly?
Title: Re: [2.0] Using a thread to load game resources
Post by: cire on January 21, 2013, 06:35:05 pm
You don't exactly get the concept of minimal code, do you?

The following exhibits similar behavior for me.

#include <SFML/Graphics.hpp>

struct LoadTexture
{
    sf::Texture& texture ;
    bool & success ;
    std::string filename ;

    LoadTexture(sf::Texture& t, bool& s, const std::string& fname)
        : texture(t), success(s), filename(fname) {}

    void operator()()
        { success = texture.loadFromFile(filename) ; }
};

int main()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "sf::Thread test");

    bool success = false ;
    sf::Texture green ;

    {
        sf::Thread thread(LoadTexture(green, success, "GreenTile.png")) ;
        thread.launch() ;
    }
//    success = green.loadFromFile("GreenTile.png") ;

    if ( !success )
        return 0 ;

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

        window.clear() ;
        window.draw(sf::Sprite(green)) ;
        window.display() ;
    }
}

Been awhile since I've updated to the latest SFML sources.  I'll do that later today.
Title: Re: [2.0] Using a thread to load game resources
Post by: NPS on January 21, 2013, 06:59:42 pm
You don't exactly get the concept of minimal code, do you?
I didn't want to cut out anything relevant (and trust me - I've cut out a lot already). I my case the problem is with sf::RenderTexture (vide my last post's edit note). I don't see any sf::RenderTexture in your code. :P
Title: Re: [2.0] Using a thread to load game resources
Post by: cire on January 21, 2013, 08:34:14 pm
I didn't want to cut out anything relevant (and trust me - I've cut out a lot already).

Instead of worrying about cutting out anything "relevant" you cut it down to minimal.  If the problem isn't still exhibited, you know it was something that you left out.  Add stuff back in until you find what triggers the issue.  Now you have minimal code that still reproduces the error.


I my case the problem is with sf::RenderTexture (vide my last post's edit note). I don't see any sf::RenderTexture in your code. :P

Would you imagine that sf::RenderTexture uses sf::Texture internally?
Title: Re: [2.0] Using a thread to load game resources
Post by: cire on January 22, 2013, 06:10:31 am
Updating to the latest source had no effect.

I did manage to get it  working using the following (Windows specific) code, though.

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

struct LoadTexture
{
    sf::Texture& texture ;
    bool & success ;
    std::string filename ;

    LoadTexture(sf::Texture& t, bool& s, const std::string& fname )
        : texture(t), success(s), filename(fname) {}

    void operator()()
    {  
        wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext()) ;
        success = texture.loadFromFile(filename) ;
        wglMakeCurrent(NULL, NULL) ;
    }
};

int main()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "sf::Thread test");

    bool success = false ;
    sf::Texture green ;

    {
        sf::Thread thread(LoadTexture(green, success, "GreenTile.png")) ;
        thread.launch() ;
    }


//    success = green.loadFromFile("GreenTile.png") ;

    if ( !success )
        return 0 ;

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

        window.clear() ;
        window.draw(sf::Sprite(green)) ;
        window.display() ;
    }
}

[edit:
And now I see sf::Context (http://www.sfml-dev.org/documentation/2.0/classsf_1_1Context.php) which somehow escaped me when I looked earlier.  That would provide a much better solution.]
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on January 22, 2013, 08:48:31 am
SFML is supposed to activate a context whenever it needs one, you should'nt bave to worry about that. I'm currently on vacation so I won't be able to help more, sorry.
Title: Re: [2.0] Using a thread to load game resources
Post by: NPS on January 22, 2013, 06:27:10 pm
So as I've said, now I'm calling sf::RenderTexture::create() method from the main thread and that is working fine on my PC (nvidia card). But later I tried running the same code on my laptop (ati card) and 2 things happen: I don't see images using sf::RenderTexture (as was the case initially on my PC) and also the application crashes after closing the window saying something about uncaught exception and "access violation reading location...". :P
Title: Re: [2.0] Using a thread to load game resources
Post by: NPS on January 24, 2013, 08:45:46 pm
It gets worse - on my laptop I always get said error when loading using another thread - not only with sf::RenderTarget. :P
Title: Re: [2.0] Using a thread to load game resources
Post by: BlueCobold on May 10, 2013, 08:54:47 pm
Is there any news on that? I have a similar issue on 2 of 3 PCs with that. Loading all works fine, but when closing the Window and internally wglDeleteContext gets called, an access violation is thrown. When loading stuff without using a thread, everything is fine. Loading from within an sf::Thread causes trouble.
(ATI card)
I created other games with GL before and also loaded my stuff in separate threads and never had that issue there.
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on May 11, 2013, 09:31:53 am
No.

Can you show a complete and minimal code that reproduces your problem?
Title: Re: [2.0] Using a thread to load game resources
Post by: BlueCobold on May 11, 2013, 11:00:03 am
Here you go.
Access violation in ~RenderWindow -> ~Window ->... ~WglContext -> wglDeleteContext()
I assume the destruction of the texture causes the problem, because it is deleted in another thread than in which it had been created in. But I have no such issues in my own engine and I have also no idea how to bypass this in SFML.

#include <SFML/Window/Event.hpp>
#include <SFML/System/Thread.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Texture.hpp>

int main(int argc, char* argv[])
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "access violation in destructor");

    bool loaded = false;

    sf::Texture* tex;
    sf::Thread thread([&](){tex = new sf::Texture(); loaded = tex->loadFromFile("res/img/gui/Window.png");});
// same in this version:
//    sf::Texture* tex = new sf::Texture();
//    sf::Thread thread([&](){loaded = tex->loadFromFile("res/img/gui/Window.png");});

    thread.launch() ;

    while ( window.isOpen() )
    {
        sf::Event event ;
        while ( window.pollEvent(event) )
        {
            if ( event.type == sf::Event::Closed )
                window.close() ;
        }
        if(loaded)
            window.setTitle("Thread done, close now");
    }
    delete tex;

    return 0; // Access violation here - in ~RenderWindow -> ~Window ->... ~WglContext -> wglDeleteContext()
}
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on May 11, 2013, 11:52:56 am
Have you tried a glFlush at the end of the theaded function? If you draw the texture (with a sprite), does it show correctly?
Title: Re: [2.0] Using a thread to load game resources
Post by: BlueCobold on May 11, 2013, 12:12:59 pm
glFlush() solves the issue indeed. My bad, thanks a lot!  :D

The texture actually doesn't show in the sample above. In my real code it does.
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on May 11, 2013, 01:02:15 pm
Ok. This is still a bug that needs to be fixed, but at least now I know exactly what it is.
Title: Re: [2.0] Using a thread to load game resources
Post by: fallahn on June 18, 2013, 12:49:29 pm
I've been having a similar problem using sf::RenderTexture. I'm using a thread to load a map file (which works fine) then create a set of entities via an entity manager based on the loaded map data (eg spawn points etc). The entity manager loads the corresponding textures and passes references to them to the entities it creates, which actually draw fine, but entities which use a render texture internally to create their sprite texture do not draw. As far as I can see the actual render texture is not clear/draw/displaying during the entity's draw() call. For example if I add a clear() call immediately after create() with sf::color::green the entity will draw from the main thread as a green sprite... but the entity's draw function should be clearing it yellow.


#include <SFML/system.hpp>
#include <SFML/graphics.hpp>
#include <SFML/window.hpp>

#include <memory>
#include <vector>
#include <atomic>
#include <iostream>

class MyEntity
{
public:
    MyEntity()
    {
        m_renderTexture.create(40u, 40u);
        m_renderTexture.setActive(false); //deactivate because created in another thread
    }
    void Draw(sf::RenderTarget& rt)
    {
        m_renderTexture.clear(sf::Color::Yellow);
        m_renderTexture.display();
        rt.draw(sf::Sprite(m_renderTexture.getTexture()));
    }
private:
    sf::RenderTexture m_renderTexture;
};

class MyEntityManager
{
public:
    MyEntityManager(){};
    void CreateEntities()
    {
        //load resources needed for entity here
        m_entities.push_back(std::unique_ptr<MyEntity>(new MyEntity()));
    }
    void Draw(sf::RenderTarget& rt)
    {
        for(auto ent = m_entities.begin(); ent != m_entities.end(); ++ent)
        {
            MyEntity& myEnt = **ent;
            myEnt.Draw(rt);
        }
    };
private:
    std::vector< std::unique_ptr<MyEntity> > m_entities;
};

//---------------------------------------------------------//

MyEntityManager entManager;
std::atomic<bool> loaded(false);
void ThreadFunc()
{
    //load map data here first

    //create entities based on loaded map data
    entManager.CreateEntities();
    loaded = true;
    std::cout << "Thread Quitting" << std::endl;
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(800u, 600u), "rt test");
    //MyEntity ent;

    sf::Thread thread(&ThreadFunc);
    thread.launch();

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

        //draw test item - this works
        /*window.clear();
        ent.Draw(window);
        window.display();*/


        if(loaded) //drawing this doesn't
        {
          window.clear();
          entManager.Draw(window);
          window.display();
        }
    }
    thread.wait();
    return 0;
}

 

This is using the latest SFML compiled from source, and happens using gcc/mingw and VS11 compilers. I've also tested it on WinXP / nVidia GTS450, Vista64 / Radeon 7970 and Win7-64 / Radeon 7750 with latest drivers and they all exhibit the problem.

I've tried putting break points on the render texture's clear/draw call but in debug mode I get 'expression can not be evaluated' and in release mode VS tells me it can't insert a break point there as the compiler has optimised it out...
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on June 18, 2013, 01:09:17 pm
And haven't you tried the suggested fix (glFlush)?
Title: Re: [2.0] Using a thread to load game resources
Post by: fallahn on June 18, 2013, 01:15:57 pm
sorry, I forgot to mention that I did, and it made no difference.
Title: Re: [2.0] Using a thread to load game resources
Post by: fallahn on July 02, 2013, 11:25:29 am
sorry to bump the thread but I just saw that the glFlush() fix has been included in an update on github. I've recompiled SFML with latest version for both VS11 and MinGW, but it seems it still doesn't fix the render texture problem I posted above.
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on July 02, 2013, 11:32:08 am
It might be a RenderTexture bug rather than a threading issue.

Could you try to simplify your code (remove classes, just put one render-texture in main()) and get rid of C++11 features (std::atomic)?
Title: Re: [2.0] Using a thread to load game resources
Post by: fallahn on July 02, 2013, 12:36:17 pm
Ok, I've narrowed it down a bit. Having a RenderTexture as a member of an object created in a separate thread still has the same problem - the sprite draws green instead of yellow (it works when creating the object in the same thread):

class MyEntity
{
public:
    MyEntity()
    {
        m_renderTexture.create(40u, 40u);
        m_renderTexture.clear(sf::Color::Green);
        m_renderTexture.display();
        m_renderTexture.setActive(false); //deactivate because created in another thread
    }
    void Draw(sf::RenderTarget& rt)
    {
        m_renderTexture.clear(sf::Color::Yellow);
        m_renderTexture.display();
        rt.draw(sf::Sprite(m_renderTexture.getTexture()));
    }
private:
    sf::RenderTexture m_renderTexture;
};

//---------------------------------------------------------//

std::unique_ptr<MyEntity> ent;

bool loaded(false);
void ThreadFunc()
{
    ent = std::unique_ptr<MyEntity>(new MyEntity);
    loaded = true;
    std::cout << "Thread Quitting" << std::endl;
    glFlush();
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(800u, 600u), "rt test");

    sf::Thread thread(&ThreadFunc);
    thread.launch();

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

        if(loaded) //drawing this doesn't
        {
            window.clear();
            ent->Draw(window);
            window.display();
        }
    }
    thread.wait();
    return 0;
}
 

However, declaring a RenderTexture in the main thread and calling create() on it in a separate thread seems to work:

sf::RenderTexture rt;
bool loaded(false);
void ThreadFunc()
{
    rt.create(40u, 40u);
    rt.clear(sf::Color::Green);
    rt.display();
    rt.setActive(false);
    loaded = true;
    std::cout << "Thread Quitting" << std::endl;
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(800u, 600u), "rt test");

    sf::Thread thread(&ThreadFunc);
    thread.launch();

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

        if(loaded)
        {
            rt.clear(sf::Color::Yellow);
            rt.display();
            window.clear();
            window.draw(sf::Sprite(rt.getTexture()));
            window.display();
        }
    }
    thread.wait();
    return 0;
}
 
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on July 02, 2013, 12:44:54 pm
So, this code doesn't work:

sf::RenderTexture* rt;
bool loaded(false);
void ThreadFunc()
{
    rt = new sf::RenderTexture;
    rt->create(40u, 40u);
    rt->clear(sf::Color::Green);
    rt->display();
    rt->setActive(false);
    loaded = true;
    std::cout << "Thread Quitting" << std::endl;
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(800u, 600u), "rt test");

    sf::Thread thread(&ThreadFunc);
    thread.launch();

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

        if(loaded)
        {
            rt->clear(sf::Color::Yellow);
            rt->display();
            window.clear();
            window.draw(sf::Sprite(rt->getTexture()));
            window.display();
        }
    }
    delete rt;
    return 0;
}

But this code does:

sf::RenderTexture* rt;
bool loaded(false);
void ThreadFunc()
{
    rt->create(40u, 40u);
    rt->clear(sf::Color::Green);
    rt->display();
    rt->setActive(false);
    loaded = true;
    std::cout << "Thread Quitting" << std::endl;
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(800u, 600u), "rt test");

    rt = new sf::RenderTexture;
    sf::Thread thread(&ThreadFunc);
    thread.launch();

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

        if(loaded)
        {
            rt->clear(sf::Color::Yellow);
            rt->display();
            window.clear();
            window.draw(sf::Sprite(rt->getTexture()));
            window.display();
        }
    }
    delete rt;
    return 0;
}

Right?
Title: Re: [2.0] Using a thread to load game resources
Post by: fallahn on July 02, 2013, 12:54:23 pm
Hmm. Actually apart from changing it to

rt->getTexture()
 

(just a typo, I know) they both work for me.
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on July 02, 2013, 01:20:22 pm
Hmm... I can't find any other major difference between my first code and yours, the code is the same I just removed the Entity class and copied the contents of its functions wherever they were called :-\
Title: Re: [2.0] Using a thread to load game resources
Post by: fallahn on July 02, 2013, 02:10:20 pm
AHA. It seems that the problem occurs if I call rt->clear() *after* window.clear(). If I clear the RenderTexture first it works ok. This is only the case when rt is created in a separate thread. Create it in the main thread and it works fine whether it's cleared before or after window.

So your example:
            rt->clear(sf::Color::Yellow);
            rt->display();
            window.clear();
            window.draw(sf::Sprite(rt->getTexture()));
            window.display();
 

works fine (both creating in a separate and the main thread). On the other hand:

            window.clear();
            rt->clear(sf::Color::Yellow);
            rt->display();        
            window.draw(sf::Sprite(rt->getTexture()));
            window.display();
 

or

            window.clear();
            entity.Draw(window);
            window.display();
 

only works when the RenderTexture / entity object are created in the main thread.
Title: Re: [2.0] Using a thread to load game resources
Post by: Laurent on July 02, 2013, 10:40:36 pm
Hum... :-\