diff --git a/Core/FrameTiming.cpp b/Core/FrameTiming.cpp index 8a9b10e63dae..f926cae09577 100644 --- a/Core/FrameTiming.cpp +++ b/Core/FrameTiming.cpp @@ -32,6 +32,19 @@ 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; + if (left > 0.0) { + usleep((long)(left * 1000000)); + } +#endif +} + inline Draw::PresentMode GetBestImmediateMode(Draw::PresentMode supportedModes) { if (supportedModes & Draw::PresentMode::MAILBOX) { return Draw::PresentMode::MAILBOX; @@ -50,6 +63,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 2b3d4b1f7d65..38e877810aa9 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,16 +438,15 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep) nextFrameTime = curFrameTime; } else { // Wait until we've caught up. - 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 + // 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; @@ -637,9 +636,14 @@ 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()) { + if (!forceNoFlip) + nextFrame = Core_NextFrame(); + if (nextFrame) { gpu->CopyDisplayToOutput(fbReallyDirty); if (fbReallyDirty) { DisplayFireActualFlip(); @@ -659,7 +663,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(); 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.