Add comments, move some stuff around, get rid of some indentation. No functionality change.

This commit is contained in:
Henrik Rydgård 2023-08-15 15:20:01 +02:00
parent 8e013b310c
commit ef2d7a810a
4 changed files with 139 additions and 107 deletions

View File

@ -1,3 +1,24 @@
// Frame timing
//
// A frame on the main thread should look a bit like this:
//
// 1. -- Wait for the right time to start the frame (alternatively, see this is step 8).
// 2. Sample inputs (on some platforms, this is done continouously during step 3)
// 3. Run CPU
// 4. Submit GPU commands (there's no reason to ever wait before this).
// 5. -- Wait for the right time to present
// 6. Send Present command
// 7. Do other end-of-frame stuff
//
// To minimize latency, we should *maximize* 1 and *minimize* 5 (while still keeping some margin to soak up hitches).
// Additionally, if too many completed frames have been buffered up, we need a feedback mechanism, so we can temporarily
// artificially increase 1 in order to "catch the CPU up".
//
// There are some other things that can influence the frame timing:
// * Unthrottling. If vsync is off or the backend can change present mode dynamically, we can simply disable all waits during unthrottle.
// * Frame skipping. This gets complicated.
// * The game not actually asking for flips, like in static loading screens
#include "Common/Profiler/Profiler.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
@ -20,7 +41,7 @@ inline Draw::PresentMode GetBestImmediateMode(Draw::PresentMode supportedModes)
}
void FrameTiming::Reset(Draw::DrawContext *draw) {
if (g_Config.bVSync || !(draw->GetDeviceCaps().presentModesSupported & (Draw::PresentMode::MAILBOX| Draw::PresentMode::IMMEDIATE))) {
if (g_Config.bVSync || !(draw->GetDeviceCaps().presentModesSupported & (Draw::PresentMode::MAILBOX | Draw::PresentMode::IMMEDIATE))) {
presentMode = Draw::PresentMode::FIFO;
presentInterval = 1;
} else {

View File

@ -2,6 +2,8 @@
#include "Common/GPU/thin3d.h"
// See big comment in the CPP file.
namespace Draw {
class DrawContext;
}

View File

@ -349,6 +349,7 @@ void __DisplaySetWasPaused() {
wasPaused = true;
}
// TOOD: Should return 59.997?
static int FrameTimingLimit() {
bool challenge = Achievements::ChallengeModeActive();
@ -388,12 +389,11 @@ static void DoFrameDropLogging(float scaledTimestep) {
}
}
// Let's collect all the throttling and frameskipping logic here.
static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
// 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) {
PROFILE_THIS_SCOPE("timing");
int fpsLimit = FrameTimingLimit();
throttle = FrameTimingThrottled();
skipFrame = false;
*skipFrame = false;
// Check if the frameskipping code should be enabled. If neither throttling or frameskipping is on,
// we have nothing to do here.
@ -401,11 +401,6 @@ static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
if (!throttle && !doFrameSkip)
return;
float scaledTimestep = timestep;
if (fpsLimit > 0 && fpsLimit != framerate) {
scaledTimestep *= (float)framerate / fpsLimit;
}
if (lastFrameTime == 0.0 || wasPaused) {
nextFrameTime = time_now_d() + scaledTimestep;
} else {
@ -427,19 +422,16 @@ static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
// autoframeskip
// Argh, we are falling behind! Let's skip a frame and see if we catch up.
if (curFrameTime > nextFrameTime && doFrameSkip) {
skipFrame = true;
*skipFrame = true;
}
} else if (frameSkipNum >= 1) {
// fixed frameskip
if (numSkippedFrames >= frameSkipNum)
skipFrame = false;
*skipFrame = false;
else
skipFrame = true;
*skipFrame = true;
}
// TODO: This is NOT where we should wait, really! We should mark each outgoing frame with the desired
// timestamp to push it to display, and sleep in the render thread to achieve that.
if (curFrameTime < nextFrameTime && throttle) {
// If time gap is huge just jump (somebody fast-forwarded)
if (nextFrameTime - curFrameTime > 2*scaledTimestep) {
@ -503,7 +495,6 @@ static void DoFrameIdleTiming() {
}
}
void hleEnterVblank(u64 userdata, int cyclesLate) {
int vbCount = userdata;
@ -552,6 +543,26 @@ void hleEnterVblank(u64 userdata, int cyclesLate) {
}
}
static void NotifyUserIfSlow() {
// Let the user know if we're running slow, so they know to adjust settings.
// Sometimes users just think the sound emulation is broken.
static bool hasNotifiedSlow = false;
if (!g_Config.bHideSlowWarnings &&
!hasNotifiedSlow &&
PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL &&
DisplayIsRunningSlow()) {
#ifndef _DEBUG
auto err = GetI18NCategory(I18NCat::ERRORS);
if (g_Config.bSoftwareRendering) {
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"), 5.0f);
} else {
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: try frameskip, sound is choppy when slow"));
}
#endif
hasNotifiedSlow = true;
}
}
void __DisplayFlip(int cyclesLate) {
flippedThisFrame = true;
// We flip only if the framebuffer was dirty. This eliminates flicker when using
@ -589,96 +600,92 @@ void __DisplayFlip(int cyclesLate) {
const bool fbDirty = gpu->FramebufferDirty();
if (fbDirty || noRecentFlip || postEffectRequiresFlip) {
int frameSleepPos = DisplayGetSleepPos();
double frameSleepStart = time_now_d();
DisplayFireFlip();
// Let the user know if we're running slow, so they know to adjust settings.
// Sometimes users just think the sound emulation is broken.
static bool hasNotifiedSlow = false;
if (!g_Config.bHideSlowWarnings &&
!hasNotifiedSlow &&
PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL &&
DisplayIsRunningSlow()) {
#ifndef _DEBUG
auto err = GetI18NCategory(I18NCat::ERRORS);
if (g_Config.bSoftwareRendering) {
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"), 5.0f);
} else {
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: try frameskip, sound is choppy when slow"));
}
#endif
hasNotifiedSlow = true;
}
bool forceNoFlip = false;
float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
// Avoid skipping on devices that have 58 or 59 FPS, except when alternate speed is set.
bool refreshRateNeedsSkip = FrameTimingLimit() != framerate && FrameTimingLimit() > refreshRate;
// Alternative to frameskip fast-forward, where we draw everything.
// Useful if skipping a frame breaks graphics or for checking drawing speed.
if (fastForwardSkipFlip && (!FrameTimingThrottled() || refreshRateNeedsSkip)) {
static double lastFlip = 0;
double now = time_now_d();
if ((now - lastFlip) < 1.0f / refreshRate) {
forceNoFlip = true;
} else {
lastFlip = now;
}
}
// Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap.
const bool fbReallyDirty = gpu->FramebufferReallyDirty();
if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) {
// Check first though, might've just quit / been paused.
if (!forceNoFlip && Core_NextFrame()) {
gpu->CopyDisplayToOutput(fbReallyDirty);
if (fbReallyDirty) {
DisplayFireActualFlip();
}
}
}
if (fbDirty) {
gpuStats.numFlips++;
}
bool throttle, skipFrame;
DoFrameTiming(throttle, skipFrame, (float)numVBlanksSinceFlip * timePerVblank);
int maxFrameskip = 8;
int frameSkipNum = DisplayCalculateFrameSkip();
if (throttle) {
// 4 here means 1 drawn, 4 skipped - so 12 fps minimum.
maxFrameskip = frameSkipNum;
}
if (numSkippedFrames >= maxFrameskip || GPURecord::IsActivePending()) {
skipFrame = false;
}
if (skipFrame) {
gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
numSkippedFrames++;
} else {
gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;
numSkippedFrames = 0;
}
// Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame).
// Right after, we regain control for a little bit in hleAfterFlip. I think that's a great
// place to do housekeeping.
CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);
numVBlanksSinceFlip = 0;
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {
// Track how long we sleep (whether vsync or sleep_ms.)
DisplayNotifySleep(time_now_d() - frameSleepStart, frameSleepPos);
}
} else {
// Okay, there's no new frame to draw. But audio may be playing, so we need to time still.
bool needFlip = fbDirty || noRecentFlip || postEffectRequiresFlip;
if (!needFlip) {
// Okay, there's no new frame to draw, game might be sitting in a static loading screen
// or similar, and not long enough to trigger noRecentFlip. But audio may be playing, so we need to time still.
DoFrameIdleTiming();
return;
}
// Debugger integration
int frameSleepPos = DisplayGetSleepPos();
double frameSleepStart = time_now_d();
DisplayFireFlip();
NotifyUserIfSlow();
bool forceNoFlip = false;
float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
// Avoid skipping on devices that have 58 or 59 FPS, except when alternate speed is set.
bool refreshRateNeedsSkip = FrameTimingLimit() != framerate && FrameTimingLimit() > refreshRate;
// Alternative to frameskip fast-forward, where we draw everything.
// Useful if skipping a frame breaks graphics or for checking drawing speed.
if (fastForwardSkipFlip && (!FrameTimingThrottled() || refreshRateNeedsSkip)) {
static double lastFlip = 0;
double now = time_now_d();
if ((now - lastFlip) < 1.0f / refreshRate) {
forceNoFlip = true;
} else {
lastFlip = now;
}
}
// Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap.
const bool fbReallyDirty = gpu->FramebufferReallyDirty();
if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) {
// Check first though, might've just quit / been paused.
if (!forceNoFlip && Core_NextFrame()) {
gpu->CopyDisplayToOutput(fbReallyDirty);
if (fbReallyDirty) {
DisplayFireActualFlip();
}
}
}
if (fbDirty) {
gpuStats.numFlips++;
}
bool throttle = FrameTimingThrottled();
int fpsLimit = FrameTimingLimit();
float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank;
if (fpsLimit > 0 && fpsLimit != framerate) {
scaledTimestep *= (float)framerate / fpsLimit;
}
bool skipFrame;
DoFrameTiming(throttle, &skipFrame, scaledTimestep);
int maxFrameskip = 8;
int frameSkipNum = DisplayCalculateFrameSkip();
if (throttle) {
// 4 here means 1 drawn, 4 skipped - so 12 fps minimum.
maxFrameskip = frameSkipNum;
}
if (numSkippedFrames >= maxFrameskip || GPURecord::IsActivePending()) {
skipFrame = false;
}
if (skipFrame) {
// Tell the emulated GPU to skip the next frame.
gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
numSkippedFrames++;
} else {
gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;
numSkippedFrames = 0;
}
// Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame).
// Right after, we regain control for a little bit in hleAfterFlip. I think that's a great
// place to do housekeeping.
CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);
numVBlanksSinceFlip = 0;
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {
// Track how long we sleep (whether vsync or sleep_ms.)
DisplayNotifySleep(time_now_d() - frameSleepStart, frameSleepPos);
}
}

View File

@ -301,6 +301,8 @@ static int ScreenRefreshRateHz() {
lpDevMode.dmSize = sizeof(DEVMODE);
lpDevMode.dmDriverExtra = 0;
// TODO: Use QueryDisplayConfig instead (Win7+) so we can get fractional refresh rates correctly.
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &lpDevMode) == 0) {
return 60; // default value
} else {