// Frame timing1//2// A frame on the main thread should look a bit like this:3//4// 1. -- Wait for the right time to start the frame (alternatively, see this is step 8).5// 2. Sample inputs (on some platforms, this is done continouously during step 3)6// 3. Run CPU7// 4. Submit GPU commands (there's no reason to ever wait before this).8// 5. -- Wait for the right time to present9// 6. Send Present command10// 7. Do other end-of-frame stuff11//12// To minimize latency, we should *maximize* 1 and *minimize* 5 (while still keeping some margin to soak up hitches).13// Additionally, if too many completed frames have been buffered up, we need a feedback mechanism, so we can temporarily14// artificially increase 1 in order to "catch the CPU up".15//16// There are some other things that can influence the frame timing:17// * Unthrottling. If vsync is off or the backend can change present mode dynamically, we can simply disable all waits during unthrottle.18// * Frame skipping. This gets complicated.19// * The game not actually asking for flips, like in static loading screens2021#include "ppsspp_config.h"22#include "Common/Profiler/Profiler.h"23#include "Common/Log.h"24#include "Common/System/Display.h"25#include "Common/TimeUtil.h"2627#include "Core/RetroAchievements.h"28#include "Core/CoreParameter.h"29#include "Core/Core.h"30#include "Core/System.h"31#include "Core/Config.h"32#include "Core/HW/Display.h"33#include "Core/HLE/sceNet.h"34#include "Core/FrameTiming.h"3536FrameTiming g_frameTiming;3738void WaitUntil(double now, double timestamp, const char *reason) {39#if 140// Use precise timing.41sleep_precise(timestamp - now, reason);42#else4344#if PPSSPP_PLATFORM(WINDOWS)45// Old method. TODO: Should we make an option?46while (time_now_d() < timestamp) {47sleep_ms(1, reason); // Sleep for 1ms on this thread48}49#else50const double left = timestamp - now;51if (left > 0.0 && left < 3.0) {52usleep((long)(left * 1000000));53}54#endif5556#endif57}5859void FrameTiming::DeferWaitUntil(double until, double *curTimePtr) {60_dbg_assert_(until > 0.0);61waitUntil_ = until;62curTimePtr_ = curTimePtr;63}6465void FrameTiming::PostSubmit() {66if (waitUntil_ != 0.0) {67WaitUntil(time_now_d(), waitUntil_, "post-submit");68if (curTimePtr_) {69*curTimePtr_ = waitUntil_;70curTimePtr_ = nullptr;71}72waitUntil_ = 0.0;73}74}7576void FrameTiming::ComputePresentMode(Draw::DrawContext *draw, bool fastForward) {77if (!draw) {78// This happens in headless mode.79fastForwardSkipFlip_ = true;80presentMode_ = Draw::PresentMode::FIFO;81return;82}8384_dbg_assert_(draw->GetDeviceCaps().presentModesSupported != (Draw::PresentMode)0);8586if (draw->GetDeviceCaps().presentModesSupported == Draw::PresentMode::FIFO) {87// Only FIFO mode is supported (like on iOS and some GLES backends).88fastForwardSkipFlip_ = true;89presentMode_ = Draw::PresentMode::FIFO;90return;91}9293// More than one present mode is supported. Use careful logic.9495// The user has requested vsync off.96if (!g_Config.bVSync) {97if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::IMMEDIATE) {98// Use immediate mode, whether fast-forwarding or not.99presentMode_ = Draw::PresentMode::IMMEDIATE;100fastForwardSkipFlip_ = false;101return;102}103// Inconsistent state - vsync is off but immediate mode is not supported.104// We will simply force on VSync.105g_Config.bVSync = true;106}107108// At this point, vsync is always on. What decides now is whether MAILBOX or IMMEDIATE is available,109// and also if we need an unsynced mode.110111// OK, vsync is requested (or immediate is not available). If mailbox is supported, it works the same as IMMEDIATE above.112113if (g_Config.bLowLatencyPresent) {114// Use mailbox if available. It works fine for both fast-forward and normal.115if (draw->GetDeviceCaps().presentModesSupported & Draw::PresentMode::MAILBOX) {116presentMode_ = Draw::PresentMode::MAILBOX;117fastForwardSkipFlip_ = false;118return;119}120// We could force off lowLatencyPresent here, but it's no good when changing between backends.121// So let's leave it on in the background, maybe the user just went from Vulkan to OpenGL.122}123124// At this point, low-latency mode is not available, and vsync is on. We see if we can use INSTANT125// mode for fast-forwarding, or if we need to resort to frameskipping.126if (draw->GetDeviceCaps().presentInstantModeChange) {127if (fastForward) {128presentMode_ = Draw::PresentMode::IMMEDIATE;129fastForwardSkipFlip_ = false;130return;131}132}133134// Finally, fallback to FIFO mode, with skip-flip in fast-forward.135presentMode_ = Draw::PresentMode::FIFO;136fastForwardSkipFlip_ = true;137}138139140