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 <noreply@anthropic.com>
This commit is contained in:
Mohd Faour 2026-10-08 17:46:08 +02:00 committed by Adrià Franch
parent 0490ca18dd
commit 96890dee37
2 changed files with 15 additions and 11 deletions

View File

@ -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<std::shared_ptr<FlipRequest>> requests;
std::atomic<std::size_t> reservations = 0;
std::exception_ptr failure;

View File

@ -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<AgcDriver::FrameTiming>& 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<void>& 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<void>& star
}
if (current) {
current.reset();
flipQueue->changed.notify_all();
flipQueue->changed.NotifyAll();
}
}
} catch (const ProcessShutdown&) {