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

Author Topic: Understanding why setVerticalSync causes mutex to lock but setFrameRate doesn't.  (Read 3753 times)

0 Members and 2 Guests are viewing this topic.

Zamadatix

  • Newbie
  • *
  • Posts: 27
    • View Profile
I was working on some code where the source buffer for updating a texture was shared from another thread and of a different frame rate than the GUI thread. It looks something like:

Code: [Select]
// GUI Thread (handles menus, events, and display)
while(window.isOpen())
{
// Rest of GUI and event logic

if(atomicOutputPixelsUpdated)
{
sharedData.outputPixelsMutex.lock();
atomicOutputPixelsUpdated = false;
// Copy pixel data into outputTexture
sharedData.outputPixelsMutex.unlock();
}

outputTexture.update(&texturePixels[0]);

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

Mutexes, atomics, and threads are the builtins provided by the std lib (<mutex>, <atomic>, and <thread>) rather than SFML. In a separate thread there is code along the lines of:

Code: [Select]
// Do a bunch of logic
sharedData.outputPixelsMutex.lock();
// Copy the results into the shared buffer
atomicOutputPixelsUpdated = true;
sharedData.outputPixelsMutex.unlock();

// Naively rate limit the task so we don't eat up the core
std::this_thread::sleep_for(std::chrono::milliseconds(1));

Note that in each case the mutex only locks at the read/write pixels portion of the logic, not the entire function.

For some reason if I run this with setVerticalSync enabled the logic thread will only update 60 times per second. If I use this with setFrameRate(60) then the logic updates ~1000 times per second. Why is this? I would figure since the mutex is unlocked before draw() is called it shouldn't be blocked by the vsync wait? Is there any way I can have asynchronus data fed into my draw thread without causing the other thread to run in lockstep?
« Last Edit: July 15, 2018, 10:12:32 pm by Zamadatix »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
Splitting logic and drawing into separate threads is not recommended, as properly handling multi-threading is really hard, the overhead caused by synchronization needs to be paid off with even more performance gain and most oft the time, you don't even gain anything from trying to do so.

Your synchronization code for example is already flawed and introduces a race condition where the if statement can validate to true and just after the mutex is locked regardless.
I highly recommend to not try and handcraft some odd synchronization code, but to consult some good resource and implement it after well proven concepts.

Why it acts like you experienced, I can't really tell. Both ways will block the main thread in the display() function. setFramerateLimit uses sf::Clock and sf::sleep while vsync is done by the graphics driver and waits for the next screen refresh.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Zamadatix

  • Newbie
  • *
  • Posts: 27
    • View Profile
Splitting logic and drawing into separate threads is not recommended, as properly handling multi-threading is really hard, the overhead caused by synchronization needs to be paid off with even more performance gain and most oft the time, you don't even gain anything from trying to do so.

Is there an easier way to handle asynchronous source content than separating the threads? My issue in this case is it may take up to dozens of seconds for this logic to finish generating a new output and I want to avoid locking the UI/Window from updating during that period.

Your synchronization code for example is already flawed and introduces a race condition where the if statement can validate to true and just after the mutex is locked regardless.

The atomic flag was added for cheap copy efficiency (prevent the texture from updating every frame if the source buffer is unchanged, which is often with this source) not as a way to prevent the mutexes from locking at the same time. The lock race itself is intended as it saves a few MB of RAM over a double buffer and the display thread is nowhere near <1ms causing a missed frame yet.

Why it acts like you experienced, I can't really tell.

Darn, I'll see if I can replicate this in a test project on different build environments and find the root cause. I appreciate you taking the moment to look at this for me - I've sent a project donation on behalf of your time.
« Last Edit: July 16, 2018, 10:38:40 pm by Zamadatix »