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

Author Topic: Threads To Take Screenshots Seamlessly?  (Read 6876 times)

0 Members and 1 Guest are viewing this topic.

Spirrwell

  • Newbie
  • *
  • Posts: 11
    • View Profile
Threads To Take Screenshots Seamlessly?
« on: October 30, 2015, 11:00:06 am »
Hi there! I'd been looking at a tutorial to make a simple screenshot function, and it had suggested to use threads so game performance wasn't impacted. Currently I'm using the C++11 threads. I was having a terrible time trying to get this to work, but found that you have to disable the OpenGL context when dealing with a window in another thread with the RenderWindow's setActive().

The problem with this, at least as I understand it, I have to wait for the function\thread to be done so I can set the window active again and therefore I suffer from the 1-2 second pause of taking a screenshot that I was trying to avoid because the capture() is slow (according to the SFML wiki)

Is there any way around this?

To give you an idea of what I have:

void AMEngine::ScreenCapture()
{
        sf::Image screenshot = MainWindow->capture();

        time_t epoch_time;
        struct tm tm_p;
        errno_t err;
        epoch_time = time(NULL);
        err = localtime_s(&tm_p, &epoch_time);

        //ToDo: Improve screenshot name formatting
        std::string screenshotName = std::to_string(
                tm_p.tm_mday) +
                "-" +
                std::to_string(tm_p.tm_mon) +
                "-" +
                std::to_string(tm_p.tm_year) +
                " " +
                std::to_string(tm_p.tm_hour) +
                std::to_string(tm_p.tm_min) +
                std::to_string(tm_p.tm_sec);

        screenshot.saveToFile("screenshots/" + screenshotName + ".png");

        MainWindow->display();
}

void AMEngine::Update()
{
        sf::Event event;
        while (MainWindow->pollEvent(event))
        {
                if (event.type == sf::Event::Closed)
                        m_bIsRunning = false;
                if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                        m_bIsRunning = false;

                if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::F5)
                {
                        MainWindow->setActive(false);
                        std::thread tSC(&AMEngine::ScreenCapture, this);
                        tSC.join();
                        MainWindow->setActive(true);
                }

        }
}

Or is this a pipe dream?

I tried searching about screenshots with threads, but ironically, there's a thread on this forum called the Screenshot Thread. XD

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10895
    • View Profile
    • development blog
    • Email
Re: Threads To Take Screenshots Seamlessly?
« Reply #1 on: October 30, 2015, 11:09:21 am »
Transferring data from VRAM to RAM shouldn't take very long and it's suggested to not do that in a separate thread since OpenGL itself isn't multi-threaded. Instead once you get your sf::Image, you could spin up a thread to save it to disk, which is usually the slow processes. But using a thread is only useful if you actually don't call join() in the main thread, otherwise your main thread will just wait and you don't get anything, but in fact lose time, since thread creation and spin-up has its overhead as well.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Spirrwell

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Threads To Take Screenshots Seamlessly?
« Reply #2 on: October 30, 2015, 06:19:07 pm »
Transferring data from VRAM to RAM shouldn't take very long and it's suggested to not do that in a separate thread since OpenGL itself isn't multi-threaded. Instead once you get your sf::Image, you could spin up a thread to save it to disk, which is usually the slow processes. But using a thread is only useful if you actually don't call join() in the main thread, otherwise your main thread will just wait and you don't get anything, but in fact lose time, since thread creation and spin-up has its overhead as well.

Ah thanks for that, I've never really used threads, but now I know what .detach() is for. But there is no doubt that the RenderWindow's capture() slows it down. Saving the file might, but if I just don't save it, there's still a pause or a point where the screen kind of locks about a second regardless of if I use a thread or not.

And I'm not running on ancient hardware either. Actually most of my hardware is completely brand new with Intel's new Skylake CPU, with an SSD and the whole works. The only "old" thing I have is a 660 Ti for a graphics card.

Anyway I sidetracked there, basically RenderWindow's capture is just plain slow. Is there any other way to do it? What about rendering using sf::Texture and copying it to an image or does that not work right with OpenGL or would it be just as slow?
« Last Edit: October 30, 2015, 06:21:02 pm by Spirrwell »

mkalex777

  • Full Member
  • ***
  • Posts: 206
    • View Profile
Re: Threads To Take Screenshots Seamlessly?
« Reply #3 on: October 30, 2015, 06:34:22 pm »
try something like that:
var screenshot=_window.Capture();
ThreadPool.QueueUserWorkItem(arg => screenshot.SaveToFile("aaa.png"));
 

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
Re: Threads To Take Screenshots Seamlessly?
« Reply #4 on: October 30, 2015, 06:42:25 pm »
try something like that:
var screenshot=_window.Capture();
ThreadPool.QueueUserWorkItem(arg => screenshot.SaveToFile("aaa.png"));
 
We are doing C++ here, so this is pretty useless... and he's trying to speed up the capture itself, not saving it to a file.
Laurent Gomila - SFML developer

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Threads To Take Screenshots Seamlessly?
« Reply #5 on: October 30, 2015, 06:48:43 pm »
The reason why capturing the screen is painfully slow is because SFML uses a very very inefficient method of doing it. You can have a look at it here.

The main problem is that glReadPixels is a synchronous call, unlike almost all other OpenGL. The driver/GPU has to lock the framebuffer from modification while the data is being copied out to some temporary buffer which prevents any drawing obviously. That by itself would not be that bad. The really nasty bit is that the OpenGL specification promises that you will have the data you asked for as soon as the call returns (basically an implicit glFinish()). This means that you will have to wait in that call for a full CPU-GPU-CPU roundtrip, which can and does take a while. In contrast, other OpenGL operations don't make such promises and are completely asynchronous. Often you just tell OpenGL to "get stuff done some time in the future".

You might think that having to call glReadPixels a single time is already unacceptable... well you see that for loop? SFML calls it as many times as there are rows in the framebuffer, meaning that with a 1080p screen, you will end up with 1080 calls to glReadPixels, each of them having to make the CPU-GPU-CPU roundtrip and stalling the CPU.

There are so many better ways of taking screenshots, especially given the newer hardware that supports many newer extensions. The simplest way I can currently think of would be to perform a GPU copy of the framebuffer into an sf::Texture and use copyToImage() to get the data into system RAM. Yeah... it is still reading back from GPU to CPU and it could be done asynchronously etc. etc. but using this method, taking screenshots should be order of magnitudes faster than glReadPixels and barely noticeable unless you are watching an FPS graph.

Who knows... I might even implement this some day. ;D
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

mkalex777

  • Full Member
  • ***
  • Posts: 206
    • View Profile
Re: Threads To Take Screenshots Seamlessly?
« Reply #6 on: October 30, 2015, 06:50:20 pm »
We are doing C++ here, so this is pretty useless... and he's trying to speed up the capture itself, not saving it to a file.

I think that there is no significant difference what kind of language is used to explain an algorithm :)

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Threads To Take Screenshots Seamlessly?
« Reply #7 on: October 30, 2015, 08:27:11 pm »
I think that there is no significant difference what kind of language is used to explain an algorithm :)
It does, if language-specific functionality is used, such as in your example.

There is no equivalent of ThreadPool.QueueUserWorkItem() in standard C++, and for somebody who doesn't know C#, it's unclear that the => syntax can be transferred to a lambda expression (which is also an advanced and only recently introduced language construct, by the way) ;)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
Re: Threads To Take Screenshots Seamlessly?
« Reply #8 on: October 30, 2015, 08:53:40 pm »
Quote
Who knows... I might even implement this some day.
That would be really cool :P
But let's not over-engineer a task that's not meant to be performance critical. Remember what "taking a screenshot" is. And if you want to do that at a high rate efficiently (ie. capture a video), then use FRAPS or whatever tool that is designed and optimized for that task.

Quote
The simplest way I can currently think of would be to perform a GPU copy of the framebuffer into an sf::Texture and use copyToImage() to get the data into system RAM. Yeah... it is still reading back from GPU to CPU and it could be done asynchronously etc. etc. but using this method, taking screenshots should be order of magnitudes faster than glReadPixels and barely noticeable unless you are watching an FPS graph.
Since this is already implemented (see sf::Texture::update(sf::Window&)), let's try it.

Quote
I think that there is no significant difference what kind of language is used to explain an algorithm
It's not an algorithm, it's a single call to a language core library using a language-specific syntax... and none of this stuff is relevant because we are talking of a different language.
If all you wanted to describe is the algorithm, then you could just have said "save the image in a thread"... ;)
Laurent Gomila - SFML developer

Spirrwell

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Threads To Take Screenshots Seamlessly?
« Reply #9 on: October 31, 2015, 01:24:06 am »
Oh I love the enthusiasm. A native SFML feature would be great since it is a function so many people look for.

Anyway, I appreciate the help! If only I knew how to perform a GPU copy of the framebuffer. I searched for it, a lot. Got lot's of lovely information about VBOs using GLEW for extensions and so on. Oh well, not my specialty. I love how I can work with an engine and not even have the slightest clue of what I'm actually doing and make something work. Heh.

It's funny, I'd been using Quaternions and whatnot and didn't know how they worked at all until like, yesterday. Can't wait to keep messing with this stuff and figure out how things like Matrices work how they interact with other things. Really wish they taught stuff like that in my high school.

Ah... Well sorry for the off-topic tangent, this has just been a cool learning experience.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10895
    • View Profile
    • development blog
    • Email
Re: Threads To Take Screenshots Seamlessly?
« Reply #10 on: October 31, 2015, 02:22:07 am »
This seems to run seamlessly for me. Hope it's not just because of my fast PC. ;)
The image is now passed by value to the lambda, if its captured by reference, you'll be dealing with shared memory and would have to make sure that it stays alive as long as the thread is using it, plus you can't modify it as long as the thread uses it.
Also you need to make sure that the texture size is at least as big as the window size, otherwise you'll get a crash.

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

int main()
{
    sf::RenderWindow window({1920, 1080}, "Screenshot!");
    sf::Texture tex;
    tex.create(1920, 1080);
    sf::Clock cl;

    while(window.isOpen())
    {
        sf::Time dt = cl.restart();
        window.setTitle(std::to_string(1.f/dt.asSeconds()));

        sf::Event event;
        while(window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
            else if(event.type == sf::Event::KeyPressed)
            {
                tex.update(window);
                sf::Image img = tex.copyToImage();
                std::thread t([img]() {
                                 img.saveToFile("test.png");
                             });
                t.detach();
            }
        }

        window.clear(sf::Color(0xFF0000FF));
        window.display();
    }
}
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Spirrwell

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Threads To Take Screenshots Seamlessly?
« Reply #11 on: October 31, 2015, 07:17:56 am »
Also you need to make sure that the texture size is at least as big as the window size, otherwise you'll get a crash.

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

int main()
{
    sf::RenderWindow window({1920, 1080}, "Screenshot!");
    sf::Texture tex;
    tex.create(1920, 1080);
    sf::Clock cl;

    while(window.isOpen())
    {
        sf::Time dt = cl.restart();
        window.setTitle(std::to_string(1.f/dt.asSeconds()));

        sf::Event event;
        while(window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
            else if(event.type == sf::Event::KeyPressed)
            {
                tex.update(window);
                sf::Image img = tex.copyToImage();
                std::thread t([img]() {
                                 img.saveToFile("test.png");
                             });
                t.detach();
            }
        }

        window.clear(sf::Color(0xFF0000FF));
        window.display();
    }
}

Yeah, heh, I noticed that when I tried this after it was mentioned, but it's still not seamless. It's better, but not seamless. There is a very noticeable half a second pause when it's done. What the crap do you have, a dual Xeon with a GTX Titan, and an enterprise grade SSD? O.o

Resethel

  • Newbie
  • *
  • Posts: 22
    • View Profile
    • Email
Re: Threads To Take Screenshots Seamlessly?
« Reply #12 on: October 31, 2015, 08:49:35 am »

Yeah, heh, I noticed that when I tried this after it was mentioned, but it's still not seamless. It's better, but not seamless. There is a very noticeable half a second pause when it's done. What the crap do you have, a dual Xeon with a GTX Titan, and an enterprise grade SSD? O.o


Even with a rather small ( yet still decent ), it's seamless!

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10895
    • View Profile
    • development blog
    • Email
Threads To Take Screenshots Seamlessly?
« Reply #13 on: October 31, 2015, 11:55:25 am »
It worked fine for me in debug but maybe your compiler adds a lot of debug stuff to threads so make sure to test it in release mode.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Spirrwell

  • Newbie
  • *
  • Posts: 11
    • View Profile
Re: Threads To Take Screenshots Seamlessly?
« Reply #14 on: October 31, 2015, 05:23:44 pm »
It worked fine for me in debug but maybe your compiler adds a lot of debug stuff to threads so make sure to test it in release mode.

Ah, that's a good idea. Got to setup my release libraries and I'll try that. I also wonder if it has any impact that I'm using the 64 bit version at the moment.

Edit: Tried release, didn't seem to do much. I'm using VS2013. Dunno :/
« Last Edit: October 31, 2015, 05:29:44 pm by Spirrwell »