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

Author Topic: How to render to RenderTexture with std::async safely?  (Read 363 times)

0 Members and 1 Guest are viewing this topic.

GetterSetter

  • Newbie
  • *
  • Posts: 18
    • View Profile
How to render to RenderTexture with std::async safely?
« on: April 09, 2024, 03:56:54 pm »
Hello, what is the safe way to use RenderTexture in asynchronous code? I need to render some objects to the texture in the background without suspending the main loop. Now I have the following code (simplified).

async_func(...)
{
        std::vector<sf::Texture> textures;
        RenderTexture t;
        for(...)
       {
          //...
          t.clear();
          t.draw();
          t.display();
          textures.emplace_back(t.getTexture());
       }
        return textures;
}

main()
{
        std::future<...>async_render;

       //inside the loop
       if(event) async_render=std::async(async_func, ...);
       if (async_render.wait_for(0s) == std::future_status::ready)
            {
                auto c = async_render.get();
                //....
            }

        //window.clear/draw/display();
}

And it works with varrying success because sometimes my app terminates abnormally with different errors:
X Error of failed request:  GLXBadPbuffer
  Major opcode of failed request:  152 (GLX)
  Minor opcode of failed request:  28 (X_GLXDestroyPbuffer)
  Serial number of failed request:  7663
  Current serial number in output stream:  7666

or

(according to the callstack it raises from window.pollEvent(event))
GUI: ../../src/xcb_conn.c:215: write_vec: Assertion `!c->out.queue_len&#39; failed.
 

I've also noticed that the first error occurs when textures.size()>1.

The callstack for the second error:
libc.so.6!__pthread_kill_implementation(int no_tid, int signo, pthread_t threadid) (pthread_kill.c:44)
libc.so.6!__pthread_kill_internal(int signo, pthread_t threadid) (pthread_kill.c:78)
libc.so.6!__GI___pthread_kill(pthread_t threadid, int signo) (pthread_kill.c:89)
libc.so.6!__GI_raise(int sig) (raise.c:26)
libc.so.6!__GI_abort() (abort.c:79)
libc.so.6!__assert_fail_base(const char * fmt, const char * assertion, const char * file, unsigned int line, const char * function) (assert.c:92)
libc.so.6!__GI___assert_fail(const char * assertion, const char * file, unsigned int line, const char * function) (assert.c:101)
libxcb.so.1![Unknown/Just-In-Time compiled code] (Unknown:0)
libxcb.so.1!xcb_writev (Unknown:0)
libX11.so.6!_XSend (Unknown:0)
libX11.so.6!_XFlush (Unknown:0)
libX11.so.6!XCheckIfEvent (Unknown:0)
libsfml-window-d.so.2.6!sf::priv::WindowImplX11::processEvents(sf::priv::WindowImplX11 * const this) (/LIBS/SFML-2.6.1/src/SFML/Window/Unix/WindowImplX11.cpp:723)
libsfml-window-d.so.2.6!sf::priv::WindowImpl::popEvent(sf::priv::WindowImpl * const this, sf::Event & event, bool block) (/LIBS/SFML-2.6.1/src/SFML/Window/WindowImpl.cpp:147)
libsfml-window-d.so.2.6!sf::WindowBase::pollEvent(sf::WindowBase * const this, sf::Event & event) (/LIBS/SFML-2.6.1/src/SFML/Window/WindowBase.cpp:165)
main() (GUI.cpp:123)
« Last Edit: April 09, 2024, 09:31:15 pm by GetterSetter »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10835
    • View Profile
    • development blog
    • Email
Re: How to render to RenderTexture with std::async safely?
« Reply #1 on: April 09, 2024, 05:41:17 pm »
The short answer: Don't ;D

The explanation: OpenGL isn't actually multi-thread capable, so all the commands you issue, essentially end up in the same command queue and thus there's generally little to gain from doing things in a separate thread.
Additionally, you need to have an active context per thread, which means you'd have to create a new context, which by itself can be rather expensive.

For SFML 2 your code is additionally problematic, as you use a non-stable container (i.e. vector) to store textures, so when you add a new texture, what can happen is that all the textures need to be moved somewhere else, which is done through a copy (as SFML 2 has no move semantics), which means the texture data is downloade from VRAM to RAM and then uploaded again to VRAM, thus very expensive.

It's also recommended to use as few textures as possible to prevent texture swapping and when possible to just use different sections of the same texture instead. As such it's questionable to split the different renderings into multiple texture instances, which btw. is again a texture copy (VRAM -> RAM -> VRAM).

My suggestion would be to render everything to one large RenderTexture and then store a list of texture rects that tell you which sections of the large texture you should use and doing everything in the main loop.
I don't know the rest of your program, but this should be fast enough.

The error message itself sounds rather odd. Maybe you're not properly protecting shared resources, thus running into some undefined behavior?
« Last Edit: April 09, 2024, 07:59:12 pm by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

GetterSetter

  • Newbie
  • *
  • Posts: 18
    • View Profile
Re: How to render to RenderTexture with std::async safely?
« Reply #2 on: April 09, 2024, 06:59:37 pm »
Your explanation was quite comprehensive. Thank you for that.
Some thoughts on using std::vector: I was considering returning it as a std::shared_ptr<std::vector> and now I am sure that I should do so because I see that copying is expensive.
The problem with using only one RenderTexture is that the resolution of each individual texture is 2480 x 3507. Therefore, creating a large texture may not be technically possible due to limitations of VRAM (?).
The reason for wanting to separate rendering is that when I start the rendering function, my window freezes and it appears that my program has stopped working, but in fact it hasn't. Additionally, some operating systems may suggest the user to close the inactive window, which is unfortunate.
Of course, I can render the textures in the following way: one per loop tick.
Do you have any suggestions on how to prevent the window from freezing?
« Last Edit: April 09, 2024, 09:07:31 pm by GetterSetter »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10835
    • View Profile
    • development blog
    • Email
Re: How to render to RenderTexture with std::async safely?
« Reply #3 on: April 10, 2024, 08:26:43 am »
I believe the primary slow down of things are currently the multiple copies of textures you're doing (explicitly and implicitly). If you reduce that, it should probably be possible to do it in the main loop with minimal lag.

Do you want to reuse the textures or just save it to disk?

If you just want to render some images to disk, I suggest to not first render all the textures and then save it, but to get the RenderTexture's texture, save that, then render the next one. This will remove the copying of the RenderTexture's texture into another texture instance and also remove the need for a storage container.
What you then could consider, is moving the texture data into memory first, then use std::async to write the data from RAM to disk async, to not block on slow IO operations.

If you do want to store the texture somehow for reuse, then I'd suggest std::vector<std::unique_ptr<sf::Texture>> as a container. If you use C++17 and above, you should benefit from guaranteed copy elision, as such, you should be able to return the full vector.
I'd suggest to try and spread the rendering across multiple frames, if that's at all possible, so you leave enough frametime for the reset of the loop.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

GetterSetter

  • Newbie
  • *
  • Posts: 18
    • View Profile
Re: How to render to RenderTexture with std::async safely?
« Reply #4 on: April 10, 2024, 01:39:00 pm »
Quote
Do you want to reuse the textures or just save it to disk?

I want to reuse them: show the user the render result and, if it is satisfactory, the user can then save it. Thank you for your advice about asynchronous saving. I have taken note of it.
Thank you very much for your help  ;) !