SFML community forums

Help => System => Topic started by: Brodal on August 30, 2012, 04:02:16 pm

Title: Questions and problems with sf::thread
Post by: Brodal on August 30, 2012, 04:02:16 pm
First off I just want to say what I'm making to give you a little better understanding of what it is that I am trying to achieve. I'm trying to make an optimized way of drawing huge amounts of tiled textures onto to screen, or rather an optimized way of storing them and choosing which of them to be drawn onto the screen. So far I've managed to have 3 million 16x16 tiles spread all over my map with an fps of about 1300 - 1400, although it chews up alot of my memory at the moment. To maintain a high fps i render the tiles offscreen to several rendertextures that have the size 1920 x 1088  and draw these onto the screen. My big is that preparing these rendertextures take quite some time, and I preparing them when the texture gets close to the camera. The loading of the texture takes so long that it freezes the screen for the whole duration that it loads. So what i tried is using an sf::thread that is in charge of preparing these textures to be drawn so that the whole program does not freeze, so far I haven't succeeded very well and I have no idea if I'm doing it wrong or if this is the way threads work. Basically the way I'm doing it is this way:


int main ()
{
     sf::RenderWindow app(sf::VideoMode(1920, 1080), "test");

    MyClass myClass;
    sf::thread loadThread(&MyClass::loadTextures, &myClass);
    loadthread.launch();
   
    while ( app.isOpen() )
    {
         // Program here
         // Check for textures that are intersecting the view and have been prepared by the thread
         // Then draw them onto the screen
     }

    return 0;
}

//MyClass.h
class MyClass
{
   public:
   MyClass();
   void loadTextures();

   private:

   bool doLoad;
};

//MyClass.cpp

MyClass::MyClass():
      doLoad(true)
{

}

void MyClass::loadTextures()
{
    while ( doLoad )
    {
          //Check for textures intersecting the view and update these here
     }
}

 

What happens is that the main loop " while ( app.isOpen() ) " freezes when the thread finds a texture to update and starts updating it, the main loop then continues when the updating of the texture has been completed. What I'm trying to do is to make them run in parallel.

I thank you for taking the time to read this! :)

The code above is NOT actual code from my program, this is the basic logic of the draw and texture update parts of my program stripped down to its absolute basics.
Title: Re: Questions and problems with sf::thread
Post by: Laurent on August 30, 2012, 05:29:50 pm
This looks ok, so I have the feeling that this piece of code doesn't show the most important things ;)
Title: Re: Questions and problems with sf::thread
Post by: Brodal on August 30, 2012, 06:02:04 pm
I dont exactly know how i would reduce this to a minimal example the way it is done now so I'm hoping this will do.

void OptiNode::DrawToTexture()
{

        //Draw all the sprites that this node contains to an offscreen texture,
        //then ready the texture to be drawn by sfml
        for ( int i = 0; i < mNumberOfLayers; i++ )
        {
                //Create a new rendertexture that we can draw to
                sf::RenderTexture *rendTex = new sf::RenderTexture();

                //Copy the rendertexture into the vector holding the rendertextures
                mOptiTextures.push_back(rendTex);

                //Call the create command for it be valid for rendering
                //This is vector containing sf::RenderTexture*
                mOptiTextures[i]->create(mSize.x, mSize.y);
                //Set a view for the rendertexture
                //The view specifies in which area of the screen that it should " look " at
                mOptiTextures[i]->setView(mNodeView);

                //Clear the textures with the color magenta
                //The reason for this is cause it's a weird color that will probably never be used otherwise,
                //which makes it ideal for masking out all the transparent areas of the texture
                mOptiTextures[i]->clear(sf::Color::Magenta);


                for ( std::vector<OptiSprite*>::size_type j = mCurrentSprite; j < mStaticSprites.size(); j++ )
                {
                        if ( mStaticSprites[j]->GetLayer() == i )
                        {
                                mOptiTextures[i]->draw(mStaticSprites[j]->GetSprite());
                        }
                               
                }

                //When all the tiles have been added to the texture, display it
                mOptiTextures[i]->display();

                //Mask away the magenta color to make the texture transparent in the right places
                mMaskImage = mOptiTextures[i]->getTexture().copyToImage();
                mMaskImage.createMaskFromColor(sf::Color::Magenta);

                sf::Texture maskedTexture;
                mMaskedTextures.push_back(maskedTexture);

                mMaskedTextures[i].loadFromImage(mMaskImage);

                sf::Sprite optiSprite(mMaskedTextures[i]);
                optiSprite.setPosition(mPosition);
               
                //Push back the ready sprite contianing the 1920 x 1088 texture into the mOptiSprites vector
                mOptiSprites.push_back(optiSprite);

                //Signal that we are done
                mStatus = READY;

                mIsUnloaded = false;
        }
}
 

This is the code that is being looped in the load texture thread. It is only being done on the nodes that intersect the camera view. From what i have seen it seems that the RenderTexture.Create() function is one of the things making it freeze. But should the whole program freeze while it loads when I'm doing it in a separate thread than the main loop? If not, what could I be doing wrong?
Title: Re: Questions and problems with sf::thread
Post by: Laurent on August 30, 2012, 06:48:07 pm
I have no idea, this code shouldn't block the main thread.
Title: Re: Questions and problems with sf::thread
Post by: Brodal on August 30, 2012, 07:03:46 pm
Can it be because the draw function in the main thread displays the textures that the side thread creates and updates? The main thread does not do any writing or anything it just fetches references to the textures and displays them on the screen. I don't use any mutexes or locks. I'm using a ready flag ( boolean value ) that tells the main thread if the textures are ready to be drawn onto the screen or not so it doesn't try to fetch them while they're being made. I also tried not having a loop in the load thread and instead called the thread.launch() during every frame in the main loop, that produced the same result as having it in a separate thread. I get why the side thread would freeze, as the RenderTexture.create() takes about 0.3 - 0.5 seconds to complete, and all in all 1 loop in the side thread should take about 1.5 seconds. But I dont see why this is affecting my main loop.
Title: Re: Questions and problems with sf::thread
Post by: Laurent on August 30, 2012, 07:27:28 pm
You should try a similar but minimal code, i.e. a main thread with a standard main loop which draws a certain number of sprites, and a parallel thread that create an equal number of textures.
Title: Re: Questions and problems with sf::thread
Post by: Brodal on August 30, 2012, 10:27:09 pm
I tried recreating the problem but with minimal code. The result was the same, while the texture is being prepared the main the whole program goes horribly slow. I can't figure out what is causing this.


#include <SFML/Graphics.hpp>
 
bool doLoad = true;
bool status = false;

std::vector<sf::Sprite> SpriteVector;
sf::RenderTexture *OptiTexture;
sf::Texture MaskedTexture;
sf::Sprite OptiSprite;
sf::Image mMaskImage;

sf::View mNodeView;

void loadTextures()
{
        while ( doLoad )
        {
                //Create a new rendertexture that we can draw to
                if ( !OptiTexture )
                {
                        OptiTexture = new sf::RenderTexture();
                }
                //Call the create command for it be valid for rendering
                //This is vector containing sf::RenderTexture*
                OptiTexture->create(1920, 1080);
                //Set a view for the rendertexture
                //The view specifies in which area of the screen that it should " look " at
                OptiTexture->setView(mNodeView);

                //Clear the textures with the color magenta
                //The reason for this is cause it's a weird color that will probably never be used otherwise,
                //which makes it ideal for masking out all the transparent areas of the texture
                OptiTexture->clear(sf::Color::Magenta);

                for ( std::vector<sf::Sprite>::size_type j = 0; j < SpriteVector.size(); j++ )
                {
                        OptiTexture->draw(SpriteVector[j]);
                }

                //When all the tiles have been added to the texture, display it
                OptiTexture->display();

                //Mask away the magenta color to make the texture transparent in the right places
                mMaskImage = OptiTexture->getTexture().copyToImage();
                mMaskImage.createMaskFromColor(sf::Color::Magenta);

                       
                MaskedTexture.loadFromImage(mMaskImage);

                OptiSprite.setTexture(MaskedTexture);
                OptiSprite.setPosition(0,0);
               
                //Signal that we are done
                status = true;
        }
}

 int main()
 {
     // Create the main window
     sf::RenderWindow window(sf::VideoMode(1920, 1080), "SFML window");
         window.setFramerateLimit(60);
         window.setVerticalSyncEnabled(true);
         sf::View view;
         view.setSize(1920,1080);
     // Load a sprite to display
     sf::Texture texture;
     if (!texture.loadFromFile("dirt.png"))
         return EXIT_FAILURE;
     sf::Sprite sprite(texture);

        mNodeView.setSize(1920,1088);
        mNodeView.setCenter((1920/2),(1088/2));

         for ( int i = 0; i < 10; i++ )
         {
                 for ( int j = 0; j < 10; j++ )
                 {
                         sprite.setPosition(i*16,j*16);
                         SpriteVector.push_back(sprite);
                 }
         }
         status = false;
         
         sf::Thread loadThread(&loadTextures);
         loadThread.launch();
 
     // Start the game loop
     while (window.isOpen())
     {
         // Process events
         sf::Event event;
         while (window.pollEvent(event))
         {
             // Close window : exit
             if (event.type == sf::Event::Closed)
                 window.close();
         }

                  if ( sf::Keyboard::isKeyPressed(sf::Keyboard::W) )
                 {
                         view.move(sf::Vector2f(0,-10));
                 }
                 if ( sf::Keyboard::isKeyPressed(sf::Keyboard::S) )
                 {
                         view.move(sf::Vector2f(0,10));
                 }
                 if ( sf::Keyboard::isKeyPressed(sf::Keyboard::A) )
                 {
                         view.move(sf::Vector2f(-10,0));
                 }
                 if ( sf::Keyboard::isKeyPressed(sf::Keyboard::D) )
                 {
                         view.move(sf::Vector2f(10,0));
                 }
                 
         // Clear screen
         window.clear();
                 window.setView(view);
                 sf::IntRect cameraBounds;
                 cameraBounds.left = view.getCenter().x - (view.getSize().x/2);
                 cameraBounds.top = view.getCenter().y - (view.getSize().y/2);
                 cameraBounds.width = view.getSize().x;
                 cameraBounds.height = view.getSize().y;
                         if ( cameraBounds.intersects(
                                 sf::IntRect(OptiSprite.getPosition().x,
                                 OptiSprite.getPosition().y,
                                 1920,
                                 1088)) )
                         {
                                 if ( status == true )
                                 {
                                        window.draw(OptiSprite);
                                        window.draw(sprite);
                                 }
                         }
         // Update the window
         window.display();
     }
 
     return EXIT_SUCCESS;
 }

 

I hope that you will be able to help me in some way, I've been trying to do this for a couple of days now.
Thank you!
Title: Re: Questions and problems with sf::thread
Post by: Mario on August 30, 2012, 10:39:00 pm
Maybe I'm missing something in your example (late already), but there seems to be no real use for your while loop within the thread of your minimal example. You just loop over and over without ever setting doLoad to false?

Also you're lacking some point where a context switch could occur, so I'd add some sf::Sleep(0) in your drawing loop (not necessarily triggering every iteration).
Title: Re: Questions and problems with sf::thread
Post by: Brodal on August 30, 2012, 11:25:16 pm
the reason for it to be just looping over and over again is so that i can see what happens when i move the camera at the same time that it is creating the textures, which is that it is lagging horribly in intervals. Which is the behaviour that I'm trying to get rid of. In my larger application this while loop just redraws when a change has been made to that texture, or if texture no longer exists. What does a context switch do?
Title: Re: Questions and problems with sf::thread
Post by: Laurent on August 30, 2012, 11:36:53 pm
No problem for me, your code runs smoothly even in debug mode. Are your graphics drivers up-to-date?

And yes, since your example has an infinite loop in the loading thread it should let the CPU breathe with a sf::sleep(/* small value */). A "context" switch is a thread switch. sf::sleep triggers such a switch, when the loading thread goes to sleep the other one can be executed.
Title: Re: Questions and problems with sf::thread
Post by: Brodal on August 31, 2012, 01:10:28 am
Did you run my code exactly as is, or did you make any changes to it? because I can't find a way to make it run smooth, the camera moves very choppy when the second thread is making the textures. I've tried reinstalling my graphics drivers but that did no difference at all. Sorry for the quantities or replies that I'm posting but I've been stuck on this for several days and I can't figure out why it works smooth for you but not for me. And thank you very much for the quick replies that I've been getting!

Do i maybe have to make two threads instead of one? one running the main loop and the other loading the textures. Or is it sufficient to just make one sf::thread that loads the textures? Creating 2 sf::threads instead of one poses a new set of problems, where one of them being that the window context is all botched.
Title: Re: Questions and problems with sf::thread
Post by: Laurent on August 31, 2012, 10:31:29 am
No, your code is ok.

The problem might come from your graphics card; what is it? Are your drivers up to date?
Title: Re: Questions and problems with sf::thread
Post by: Brodal on August 31, 2012, 10:39:08 am
my drivers are up to date, downloaded the latest catalyst control center package with all the things in it yesterday. I have an ati radeon hd 5850.

I've investigated what happens a bit more and it seems that the main thread is waiting for the secondary thread when the secondary thread is loading the textures( or at least the main thread stops doing anything while the textures are being loaded ). Don't know why though.

Also, could it matter in which fashion i link sfml? I'm using dynamic linking I think as I have to include all the .dll's in the project folder, does this matter or not?

Also the lag seems to be drastically reduced when i build my project and run it through its executable ( it still drops to about 16 FPS for a very short amount of time though ). Can this problem be related to my IDE? I'm using visual studio 2010 premium.

Not to ask too many questions at the same time but, is there by any chance any way to template the rendertexture, because calling renderTexture.create() everytime a texture needs to be draw seems to be a little heavy, so I'm wondering if there is some way to maybe template it and just assign somethink like a copy ( which I know doesn't work as RenderTexture is a noncopyable ) but something in that direction that can lower the amount of time needed to ready a texture. The reason for throwing away the texture and not reusing it is because it will chew up too much of the memory if I reuse a large amount of textures as I want this to support huge maps.

http://www.youtube.com/watch?v=zTN5Lykt_9I

I made a video of the problem I'm having, it's a little hard to see but if you watch it in fullscreen with 480p activated you can see the stuttering that occurs sometimes. This stuttering is the problem I'm having and it happens when new textures are being created and prepared.
Title: Re: Questions and problems with sf::thread
Post by: Mario on September 01, 2012, 01:55:28 pm
You don't have to recreate the texture over and over again. Just create it right after you've created its object and you're fine (same for the view).

I'd add a short "sf::Sleep()" to your texture loop, because it might be due to bad luck/scheduling that your render loop simply doesn't have enough time.

Also, what's your CPU? Is it a dual core only?
Title: Re: Questions and problems with sf::thread
Post by: Brodal on September 01, 2012, 10:27:27 pm
The thing is that I do delete it mario, because my rendering consist of many many nodes that each have their own textures, so if I don't delete the ones that I do not draw anymore I will run out of ram memory, I'm already up at about 1 gb of ram with 3 million tiles, and that is when I delete the textures I don't use anymore. That is why I have to create new ones, but I'm wondering if there is a more efficient way to do it than that, and not waste ram? And the view is not created more than once, it is just applied to the RenderTexture upon recreating the RenderTexture.

I have added severals sleeps to my texture loop, but that still doesn't solve the problem.
I have an Intel I-7 processor, Quad core. so the CPU shouldnt be the problem.

Thank you for answering, I appreciate the help! :)
Title: Re: Questions and problems with sf::thread
Post by: FRex on September 01, 2012, 10:38:33 pm
Do you have only one huge texture for tileset? Maybe try making a class that takes window reference and then draws chunks of map(each chunk being class object containing small vertex array) that fall in view, it's substantially easier when there is only one texture.
Title: Re: Questions and problems with sf::thread
Post by: Brodal on September 01, 2012, 10:53:52 pm
Nay, I draw  bunch of tiles to several larger textures, 1 texture is x = (1920/2), y = (1088/2) in size. So instead of calling draw for a couple of thousand tiles, I call draw 4 times. One for each of the larger textures containing tiles drawn onto them.
Title: Re: Questions and problems with sf::thread
Post by: FRex on September 01, 2012, 10:57:48 pm
I mean if the things you draw are all from one texture/no texture? And how many tiles do you draw per call?
Title: Re: Questions and problems with sf::thread
Post by: Brodal on September 01, 2012, 11:03:53 pm
I don't fully understand what you mean so I will clarify how I do things. All tiles are their own objects, they do however use references to textures as not to waste memory. These tiles are then drawn onto larger textures. These larger textures are then drawn onto the screen. So the wast amount of small tiles are not iterated often and they do not take long time to draw onto the textures themselves, It's the creation of the textures that is taking to long. The reason that drawing onto the textures doesn't take long is because the tiles are grouped into different std::vectors depending on where on the screen they are, each large texture has a vector associated to it. The reason for this is so I dont waste alot of cpu time iterating through every tile on the screen, I just iterate through the relevent ones. So to the problem is creation of the textures, not the drawing onto them. I have checked this several times with timers and know for a fact that the RenderTexture.create() function is one of the things that is causing the slowdown.
Title: Re: Questions and problems with sf::thread
Post by: FRex on September 01, 2012, 11:07:24 pm
Would it be feasible to merge your tiles into vertex arrays of quads?
(ie : you don't ever move, rotate, scale them, they use one or very few sf::textures for their graphics)
Title: Re: Questions and problems with sf::thread
Post by: Brodal on September 01, 2012, 11:57:06 pm
I have never before used vertex arrays, so it might be possible.  Do you know any site that explains how to use them?. I dont move rotate or scale these, they are static. I do however need to be able to remove and add new ones during runtime. But I'm guessing that wouldnt be a problem when using vertex arrays.

You gave me a very good idea with the usage of very few sf::textures. I can just hold a few textures and change which vector of tiles is associated to which of the textures depending on where the camera is, or something like that. Should take care of all the lag. Thanks alot for the idea! :)
Title: Re: Questions and problems with sf::thread
Post by: FRex on September 02, 2012, 12:10:33 am
I mean your tiles, the ones from which the graphics come, not rendertextures, textures, if there is one it's perfect to use vertex array there.
Vertex arrays hold vertices(a 'better point' that is point on texture and point in sfml space, optionally colored) and the form they are supposed to take(the interesting one for tiles is quads, it means that each 4(0-3,4-7,8-11,ect..) vertices will form a 4 sided shape). You have to pass texture pointer as state in the draw call if you want it textured.
The removal and adding(but mostly removal) with arrays might be problematic but class that'd take care of that could probably be written with a bit of trouble.
Title: Re: Questions and problems with sf::thread
Post by: Brodal on September 02, 2012, 10:04:19 am
I will definitely look into  vertex arrays, as of now I don't really need them i think as I'm running 3 million tiles at 2000 fps. But I will try to implement them anyway, just to learn ;D.

Lag issue fixed, using 1 rendertexture that I pass a pointer to when rendering my tiles. Works like a charm!
Title: Re: Questions and problems with sf::thread
Post by: FRex on September 02, 2012, 12:32:51 pm
That is quite a lot of tiles, arrays won't do probably.
Title: Re: Questions and problems with sf::thread
Post by: Brodal on September 02, 2012, 12:39:55 pm
Vectors work just fine at the moment, as I'm splitting them into several vectors. Don't know how those vertex arrays work though.
Title: Re: Questions and problems with sf::thread
Post by: FRex on September 02, 2012, 12:48:34 pm
Almost everything is in documentation, what isn't is primitives explanation(but that can be googles/guessed) and how to texture them(by passing &texture as second parameter to draw).