mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-26 23:10:38 +00:00
Add comments, move some stuff around, get rid of some indentation. No functionality change.
This commit is contained in:
parent
8e013b310c
commit
ef2d7a810a
@ -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 {
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "Common/GPU/thin3d.h"
|
||||
|
||||
// See big comment in the CPP file.
|
||||
|
||||
namespace Draw {
|
||||
class DrawContext;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user