From d3d7d4c89c5547c09ff28d7ab13d9f3bccc8595e Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 12 Dec 2024 15:29:07 +0000 Subject: [PATCH] VBlank: If previous frame is still in progress, wait until next frame to trigger a new vblank Previously, the vblank's thread loop would block on each iteration until the current async callback had finished, at which point a new async callback would be immediately triggered. The new implementation only waits on the vblank event. If a vblank callback is still in progress the next time the vblank event is signalled, we assume the last frame is overrunning and avoid sending a new async update. The intention of this change is to avoid saturating the message thread with expensive vblank callbacks. It's also nice to remove a lock, although that's just an incidental change. --- .../native/juce_VBlank_windows.cpp | 53 +++++++------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_VBlank_windows.cpp b/modules/juce_gui_basics/native/juce_VBlank_windows.cpp index 4c941bb823..1c2ddcb62f 100644 --- a/modules/juce_gui_basics/native/juce_VBlank_windows.cpp +++ b/modules/juce_gui_basics/native/juce_VBlank_windows.cpp @@ -56,12 +56,7 @@ public: { cancelPendingUpdate(); - { - const std::scoped_lock lock { mutex }; - threadState = ThreadState::exit; - } - - condvar.notify_one(); + state |= flagExit; stopThread (-1); } @@ -121,24 +116,24 @@ private: { if (output->WaitForVBlank() == S_OK) { - if (const auto now = Time::getMillisecondCounterHiRes(); - now - lastVBlankEvent.exchange (now) < 1.0) - { - Thread::sleep (1); - } + const auto now = Time::getMillisecondCounterHiRes(); - std::unique_lock lock { mutex }; - condvar.wait (lock, [this] { return threadState != ThreadState::sleep; }); + if (now - lastVBlankEvent.exchange (now) < 1.0) + sleep (1); - if (threadState == ThreadState::exit) + const auto stateToRead = state.fetch_or (flagPaintPending); + + if ((stateToRead & flagExit) != 0) return; - threadState = ThreadState::sleep; + if ((stateToRead & flagPaintPending) != 0) + continue; + triggerAsyncUpdate(); } else { - Thread::sleep (1); + sleep (1); } } } @@ -150,32 +145,22 @@ private: for (auto& listener : listeners) listener.get().onVBlank (timestampSec); - { - const std::scoped_lock lock { mutex }; - - if (threadState == ThreadState::sleep) - threadState = ThreadState::paint; - } - - condvar.notify_one(); + state &= ~flagPaintPending; } + enum Flags + { + flagExit = 1 << 0, + flagPaintPending = 1 << 1, + }; + //============================================================================== ComSmartPtr output; HMONITOR monitor = nullptr; std::vector> listeners; - enum class ThreadState - { - sleep, - paint, - exit, - }; - std::atomic lastVBlankEvent{}; - ThreadState threadState = ThreadState::paint; - std::condition_variable condvar; - std::mutex mutex; + std::atomic state{}; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VBlankThread) JUCE_DECLARE_NON_MOVEABLE (VBlankThread)