From 91dbde939522db8c8268a97b1907edc0af9068bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 15 Dec 2023 09:25:59 +0100 Subject: [PATCH 1/4] Small refactor to enable upcoming commits --- Core/HLE/sceDisplay.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 2b3d4b1f7d65..dbcfc15f409d 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -391,7 +391,7 @@ static void DoFrameDropLogging(float scaledTimestep) { // All the throttling and frameskipping logic is here. // This is called just before we drop out of the main loop, in order to allow the submit and present to happen. -static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep) { +static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep, bool endOfFrame) { PROFILE_THIS_SCOPE("timing"); *skipFrame = false; @@ -438,6 +438,9 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep) nextFrameTime = curFrameTime; } else { // Wait until we've caught up. + // TODO: This is the wait we actually move to after the frame. + // But watch out, curFrameTime below must be updated correctly - I think. + while (time_now_d() < nextFrameTime) { #ifdef _WIN32 sleep_ms(1); // Sleep for 1ms on this thread @@ -637,9 +640,13 @@ void __DisplayFlip(int cyclesLate) { // Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap. const bool fbReallyDirty = gpu->FramebufferReallyDirty(); + + bool nextFrame = false; + if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) { // Check first though, might've just quit / been paused. - if (!forceNoFlip && Core_NextFrame()) { + nextFrame = Core_NextFrame(); + if (!forceNoFlip && nextFrame) { gpu->CopyDisplayToOutput(fbReallyDirty); if (fbReallyDirty) { DisplayFireActualFlip(); @@ -659,7 +666,7 @@ void __DisplayFlip(int cyclesLate) { scaledTimestep *= (float)framerate / fpsLimit; } bool skipFrame; - DoFrameTiming(throttle, &skipFrame, scaledTimestep); + DoFrameTiming(throttle, &skipFrame, scaledTimestep, nextFrame); int maxFrameskip = 8; int frameSkipNum = DisplayCalculateFrameSkip(); From 7f075dc9fe75dfce88acbace1c52069daf0f3404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 18 Dec 2023 17:14:47 +0100 Subject: [PATCH 2/4] Logic fix, oops. --- Core/HLE/sceDisplay.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index dbcfc15f409d..234d7e5d1cda 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -645,8 +645,9 @@ void __DisplayFlip(int cyclesLate) { if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) { // Check first though, might've just quit / been paused. - nextFrame = Core_NextFrame(); - if (!forceNoFlip && nextFrame) { + if (!forceNoFlip) + nextFrame = Core_NextFrame(); + if (nextFrame) { gpu->CopyDisplayToOutput(fbReallyDirty); if (fbReallyDirty) { DisplayFireActualFlip(); From 97e0f6dc944cf4d31f1cef189a8888e8988c792e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 18 Dec 2023 13:44:32 +0100 Subject: [PATCH 3/4] Prepare for deferred waits --- Core/FrameTiming.cpp | 27 +++++++++++++++++++++++++++ Core/FrameTiming.h | 13 +++++++++++-- Core/HLE/sceDisplay.cpp | 10 +--------- UI/NativeApp.cpp | 1 + 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Core/FrameTiming.cpp b/Core/FrameTiming.cpp index 8a9b10e63dae..f89119daef39 100644 --- a/Core/FrameTiming.cpp +++ b/Core/FrameTiming.cpp @@ -32,6 +32,17 @@ FrameTiming g_frameTiming; +void WaitUntil(double now, double timestamp) { +#ifdef _WIN32 + while (time_now_d() < timestamp) { + sleep_ms(1); // Sleep for 1ms on this thread + } +#else + const double left = timestamp - now; + usleep((long)(left * 1000000)); +#endif +} + inline Draw::PresentMode GetBestImmediateMode(Draw::PresentMode supportedModes) { if (supportedModes & Draw::PresentMode::MAILBOX) { return Draw::PresentMode::MAILBOX; @@ -50,6 +61,22 @@ void FrameTiming::Reset(Draw::DrawContext *draw) { } } +void FrameTiming::DeferWaitUntil(double until, double *curTimePtr) { + _dbg_assert_(until > 0.0); + waitUntil_ = until; + curTimePtr_ = curTimePtr; +} + +void FrameTiming::PostSubmit() { + if (waitUntil_ != 0.0) { + WaitUntil(time_now_d(), waitUntil_); + if (curTimePtr_) { + *curTimePtr_ = waitUntil_; + } + waitUntil_ = 0.0; + } +} + Draw::PresentMode ComputePresentMode(Draw::DrawContext *draw, int *interval) { Draw::PresentMode mode = Draw::PresentMode::FIFO; diff --git a/Core/FrameTiming.h b/Core/FrameTiming.h index 04e938e898fc..3a3048db507f 100644 --- a/Core/FrameTiming.h +++ b/Core/FrameTiming.h @@ -8,14 +8,23 @@ namespace Draw { class DrawContext; } -struct FrameTiming { +class FrameTiming { +public: + void DeferWaitUntil(double until, double *curTimePtr); + void PostSubmit(); + void Reset(Draw::DrawContext *draw); + // Some backends won't allow changing this willy nilly. Draw::PresentMode presentMode; int presentInterval; - void Reset(Draw::DrawContext *draw); +private: + double waitUntil_; + double *curTimePtr_; }; extern FrameTiming g_frameTiming; Draw::PresentMode ComputePresentMode(Draw::DrawContext *draw, int *interval); + +void WaitUntil(double now, double timestamp); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 234d7e5d1cda..671b096589bf 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -440,15 +440,7 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep, // Wait until we've caught up. // TODO: This is the wait we actually move to after the frame. // But watch out, curFrameTime below must be updated correctly - I think. - - while (time_now_d() < nextFrameTime) { -#ifdef _WIN32 - sleep_ms(1); // Sleep for 1ms on this thread -#else - const double left = nextFrameTime - curFrameTime; - usleep((long)(left * 1000000)); -#endif - } + WaitUntil(curFrameTime, nextFrameTime); } curFrameTime = time_now_d(); } diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 6d9ca17bf8ea..63e19abed1c1 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1117,6 +1117,7 @@ void NativeFrame(GraphicsContext *graphicsContext) { // This, between EndFrame and Present, is where we should actually wait to do present time management. // There might not be a meaningful distinction here for all backends.. + g_frameTiming.PostSubmit(); if (renderCounter < 10 && ++renderCounter == 10) { // We're rendering fine, clear out failure info. From e1f1af62231d1843bda49ae7b64c3bc9984dddeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 18 Dec 2023 13:56:34 +0100 Subject: [PATCH 4/4] Defer waits. Add sanity check --- Core/FrameTiming.cpp | 4 +++- Core/HLE/sceDisplay.cpp | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Core/FrameTiming.cpp b/Core/FrameTiming.cpp index f89119daef39..f926cae09577 100644 --- a/Core/FrameTiming.cpp +++ b/Core/FrameTiming.cpp @@ -39,7 +39,9 @@ void WaitUntil(double now, double timestamp) { } #else const double left = timestamp - now; - usleep((long)(left * 1000000)); + if (left > 0.0) { + usleep((long)(left * 1000000)); + } #endif } diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 671b096589bf..38e877810aa9 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -438,11 +438,15 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep, nextFrameTime = curFrameTime; } else { // Wait until we've caught up. - // TODO: This is the wait we actually move to after the frame. - // But watch out, curFrameTime below must be updated correctly - I think. - WaitUntil(curFrameTime, nextFrameTime); + // If we're ending the frame here, we'll defer the sleep until after the command buffers + // have been handed off to the render thread, for some more overlap. + if (endOfFrame) { + g_frameTiming.DeferWaitUntil(nextFrameTime, &curFrameTime); + } else { + WaitUntil(curFrameTime, nextFrameTime); + curFrameTime = time_now_d(); // I guess we could also just set it to nextFrameTime... + } } - curFrameTime = time_now_d(); } lastFrameTime = nextFrameTime;