From 96890dee37ebaed8cab7adb27c9a968c46d654f5 Mon Sep 17 00:00:00 2001 From: Mohd Faour Date: Thu, 8 Oct 2026 17:46:08 +0200 Subject: [PATCH 1/2] fix(videoout): wait for flips with the high-resolution condition on Windows The flip queue's condition variable went through winpthreads, whose timed waits end on GetTickCount64 steps: the present thread's 10 ms poll for SDL events and the pad, taken while no flip is queued, ended 11 to 24 ms later on Windows, and WaitForFlipRoom's 60 s bound had the same granularity. Use TimedWait::Condition, which waits on an event and a high-resolution timer, so the poll ends at 10 ms and a submitted flip still wakes the present thread at once. Co-Authored-By: Claude Fable 5.1 --- .../libSceVideoOut/include/VideoOutDriver.hpp | 3 ++- .../prx/libSceVideoOut/src/VideoOutDriver.cpp | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 66df2fa8c..0bab9d5e5 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -20,6 +20,7 @@ #include "prx/libSceVideoOut/include/DisplayWindow.hpp" #include "prx/libSceVideoOut/include/BufferReuseTracker.hpp" #include "prx/libc/include/Shutdown.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" #include "SDL.h" #include "SceTypes.hpp" @@ -190,7 +191,7 @@ struct FlipRequest final : AgcDriver::IFlipRequest, std::enable_shared_from_this struct FlipQueue { std::mutex mutex; - std::condition_variable changed; + TimedWait::Condition changed; std::list> requests; std::atomic reservations = 0; std::exception_ptr failure; diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index 22b137f8e..7b3df5f18 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -29,6 +29,9 @@ namespace { +constexpr std::uint64_t FLIP_ROOM_WAIT_MICROS = 60000000; +constexpr std::uint64_t PRESENT_POLL_MICROS = 10000; + void require(bool condition, const char* reason) { if (!condition) throw std::runtime_error(std::string("VideoOut: ") + reason); } @@ -119,7 +122,7 @@ public: void WaitForFlipRoom() override { std::unique_lock queueLock(queue->mutex); - const bool room = queue->changed.wait_for(queueLock, std::chrono::seconds(60), [&] { + const bool room = queue->changed.WaitUntil(queueLock, TimedWait::DeadlineNanos(FLIP_ROOM_WAIT_MICROS), [&] { return queue->failure || queue->stopping || cfg->shutdownToken.stop_requested() || queue->reservations.load() < VIDEO_OUT_FLIP_QUEUE_CAPACITY; }); if (queue->failure) std::rethrow_exception(queue->failure); @@ -164,7 +167,7 @@ public: if (!cfg->failure) cfg->failure = error; cfg->vblankCond.notify_all(); } - queue->changed.notify_all(); + queue->changed.NotifyAll(); } private: @@ -222,7 +225,7 @@ void FlipRequest::GpuReady(const std::shared_ptr& frameT queue->requests.push_back(shared_from_this()); ready = true; } - queue->changed.notify_all(); + queue->changed.NotifyAll(); // The worker is done once the request is queued: hardware does not stall the command processor // on a flip, and the title observes completion through flipPendingNum, which processFlip drops // once the frame's GPU work and its presentation have completed (see Driver::Present). A @@ -241,7 +244,7 @@ void FlipRequest::Fail(std::exception_ptr error) noexcept { std::lock_guard lock(cfg->mutex); if (!cfg->failure) cfg->failure = error; ReleaseLocked(); - queue->changed.notify_all(); + queue->changed.NotifyAll(); } VideoOutDriver& VideoOutDriver::Get() { @@ -273,12 +276,12 @@ VideoOutDriver::VideoOutDriver() { } catch (...) { if (vblankThread.joinable()) { vblankThread.request_stop(); - flipQueue->changed.notify_all(); + flipQueue->changed.NotifyAll(); vblankThread.join(); } if (presentThread.joinable()) { presentThread.request_stop(); - flipQueue->changed.notify_all(); + flipQueue->changed.NotifyAll(); presentThread.join(); } SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); @@ -301,7 +304,7 @@ void VideoOutDriver::Shutdown() { } presentThread.request_stop(); vblankThread.request_stop(); - flipQueue->changed.notify_all(); + flipQueue->changed.NotifyAll(); presentThread.join(); vblankThread.join(); { @@ -377,7 +380,7 @@ bool VideoOutDriver::close(int handle) { cfg->outputModeEvents.clear(); cfg->vrrStatusEvents.clear(); cfg->vblankCond.notify_all(); - flipQueue->changed.notify_all(); + flipQueue->changed.NotifyAll(); return true; } @@ -570,7 +573,7 @@ void VideoOutDriver::presentLoop(std::stop_token token, std::promise& star while (!token.stop_requested()) { { std::unique_lock lock(flipQueue->mutex); - flipQueue->changed.wait_for(lock, std::chrono::milliseconds(10), [&] { return token.stop_requested() || flipQueue->failure || !flipQueue->requests.empty(); }); + flipQueue->changed.WaitUntil(lock, TimedWait::DeadlineNanos(PRESENT_POLL_MICROS), [&] { return token.stop_requested() || flipQueue->failure || !flipQueue->requests.empty(); }); if (flipQueue->failure) std::rethrow_exception(flipQueue->failure); if (token.stop_requested()) break; if (!flipQueue->requests.empty()) { @@ -611,7 +614,7 @@ void VideoOutDriver::presentLoop(std::stop_token token, std::promise& star } if (current) { current.reset(); - flipQueue->changed.notify_all(); + flipQueue->changed.NotifyAll(); } } } catch (const ProcessShutdown&) { From b41db48b7ae563c8011a2780f7e6bc8c2369db1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 11 Oct 2026 11:18:52 +0200 Subject: [PATCH 2/2] fix(videoout): build TimedWait into libSceVideoOut instead of importing it from libkernel libkernel.prx does not export TimedWait::*, so libSceVideoOut.prx had an unresolved import and titles stopped at load. Compile TimedWait.cpp into libSceVideoOut, and use NotifyAll for the notify added on main. --- core/libs/prx/libSceVideoOut/CMakeLists.txt | 1 + core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index e8f4694ff..3b7bb08b9 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(libSceVideoOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/MouseInput.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/KeyboardInput.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/InputMapping.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../libkernel/Time/TimedWait.cpp ) target_include_directories(libSceVideoOut PRIVATE diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index 7b3df5f18..5e7b5c1a3 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -200,7 +200,7 @@ void FlipRequest::Cancel() noexcept { std::lock_guard lock(cfg->mutex); ReleaseLocked(); } - queue->changed.notify_all(); + queue->changed.NotifyAll(); } void FlipRequest::GpuReady(const std::shared_ptr& frameTiming) {