More timing work

This commit is contained in:
Henrik Rydgård committed 2023-08-16 13:56:08 +02:00
1 parent e31e6b1793
commit cc75979f5b
10 files changed
+128 -16

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLFrameData.h"
#include "Common/GPU/OpenGL/GLRenderManager.h"
+3
View File
@@ -8,6 +8,8 @@
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/Data/Collections/Hashmaps.h"
#define USE_PRESENT_WAIT 0
enum {
MAX_TIMESTAMP_QUERIES = 128,
};
@@ -30,6 +32,7 @@ struct QueueProfileContext {
double descWriteTime;
};
class VKRFramebuffer;
struct ReadbackKey {
+1 -3
View File
@@ -11,6 +11,7 @@
#include "Common/GPU/Vulkan/VulkanAlloc.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
#include "Common/GPU/Vulkan/VulkanFrameData.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/VR/PPSSPPVR.h"
@@ -25,9 +26,6 @@
#define UINT64_MAX 0xFFFFFFFFFFFFFFFFULL
#endif
#define USE_PRESENT_WAIT 0
using namespace PPSSPP_VK;
// renderPass is an example of the "compatibility class" or RenderPassType type.
+8
View File
@@ -149,6 +149,14 @@ void sleep_ms(int ms) {
#endif
}
void sleep_s(double s) {
#if defined(_WIN32) || defined(__EMSCRIPTEN__) || defined(HAVE_LIBNX)
sleep_ms((int)(s * 1000.0));
#else
usleep((int)(s * 1000000));
#endif
}
// Return the current time formatted as Minutes:Seconds:Milliseconds
// in the form 00:00:000.
void GetTimeFormatted(char formattedTime[13]) {
+4
View File
@@ -16,6 +16,10 @@ double from_time_raw_relative(uint64_t raw_time);
// Sleep. Does not necessarily have millisecond granularity, especially on Windows.
void sleep_ms(int ms);
// Sleep with floating point second wait. Can have higher precision than millisections if the
// platform supports it.
void sleep_s(double ms);
// Yield. Signals that this thread is busy-waiting but wants to allow other hyperthreads to run.
void yield();
+48
View File
@@ -94,3 +94,51 @@ Draw::PresentMode ComputePresentMode(Draw::DrawContext *draw, int *interval) {
*interval = (mode == Draw::PresentMode::FIFO) ? 1 : 0;
return mode;
}
void FrameTiming::BeforeCPUSlice() {
cpuSliceStartTime = time_now_d();
}
void FrameTiming::EndOfCPUSlice(float scaledTimeStep) {
_dbg_assert_(usePresentTiming);
double now = time_now_d();
cpuTime = now - cpuSliceStartTime;
this->timeStep = scaledTimeStep;
// Sync up lastPresentTime with the current time if it's way off.
if (lastPresentTime < now - 0.5f) {
lastPresentTime = now;
}
}
void FrameTiming::BeforePresent() {
if (!usePresentTiming)
return;
// Wait until we hit the next present time. Ideally we'll be fairly close here due to the previous AfterPresent wait.
nextPresentTime = lastPresentTime + this->timeStep;
while (true) {
double remaining = nextPresentTime - time_now_d();
if (remaining <= 0.0)
break;
sleep_s(remaining);
}
lastPresentTime = nextPresentTime;
}
void FrameTiming::AfterPresent() {
// Sleep slightly less time than all of the available room, in case of a CPU spike.
// This should be a tweakable.
const double margin = 2.0 * 0.001; // 4 ms
postSleep = this->timeStep - margin - cpuTime;
if (postSleep > 0.0) {
sleep_s(postSleep);
}
if (!usePresentTiming)
return;
}
+16
View File
@@ -13,7 +13,23 @@ struct FrameTiming {
Draw::PresentMode presentMode;
int presentInterval;
// The new timing method.
bool usePresentTiming;
double cpuSliceStartTime;
double cpuTime;
double timeStep;
double postSleep;
double lastPresentTime;
double nextPresentTime;
void Reset(Draw::DrawContext *draw);
void BeforeCPUSlice();
void EndOfCPUSlice(float scaledTimeStep);
void BeforePresent();
void AfterPresent();
};
extern FrameTiming g_frameTiming;
+30 -8
View File
@@ -454,6 +454,7 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep)
wasPaused = false;
}
// This is used even with the old timing method, for now.
static void DoFrameIdleTiming() {
PROFILE_THIS_SCOPE("timing");
if (!FrameTimingThrottled() || !g_Config.bEnableSound || wasPaused) {
@@ -602,6 +603,7 @@ void __DisplayFlip(int cyclesLate) {
bool needFlip = fbDirty || noRecentFlip || postEffectRequiresFlip;
if (!needFlip) {
// TODO: Maybe ignore this path and flip anyway, unless Skip Buffer Effects is on?
// 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();
@@ -617,8 +619,9 @@ void __DisplayFlip(int cyclesLate) {
bool forceNoFlip = false;
float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
float frameTimingLimit = FrameTimingLimit();
// Avoid skipping on devices that have 58 or 59 FPS, except when alternate speed is set.
bool refreshRateNeedsSkip = FrameTimingLimit() != framerate && FrameTimingLimit() > refreshRate;
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)) {
@@ -631,6 +634,30 @@ void __DisplayFlip(int cyclesLate) {
}
}
bool throttle = FrameTimingThrottled();
int fpsLimit = FrameTimingLimit();
float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank;
if (fpsLimit > 0 && fpsLimit != framerate) {
scaledTimestep *= (float)framerate / fpsLimit;
}
// If the ideal case, use the new timing path.
g_frameTiming.usePresentTiming = g_Config.iFrameSkip == 0 && !refreshRateNeedsSkip;
if (g_frameTiming.usePresentTiming) {
if (Core_NextFrame()) {
gpu->CopyDisplayToOutput(true);
DisplayFireActualFlip();
g_frameTiming.EndOfCPUSlice(scaledTimestep);
gpuStats.numFlips++;
goto finishUp;
} else {
// This should only happen if we're stepping in the debugger.
// Go to the old path.
g_frameTiming.usePresentTiming = false;
}
}
// Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap.
const bool fbReallyDirty = gpu->FramebufferReallyDirty();
if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) {
@@ -647,13 +674,6 @@ void __DisplayFlip(int cyclesLate) {
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);
@@ -680,6 +700,8 @@ void __DisplayFlip(int cyclesLate) {
// Right after, we regain control for a little bit in hleAfterFlip. I think that's a great
// place to do housekeeping.
finishUp:
CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);
numVBlanksSinceFlip = 0;
+9 -2
View File
@@ -115,9 +115,16 @@ static void DrawFrameTiming(UIContext *ctx, const Bounds &bounds) {
ctx->Draw()->SetFontScale(0.5f, 0.5f);
snprintf(statBuf, sizeof(statBuf),
"Mode (interval): %s (%d)",
"Mode (interval): %s (%d)"
"CPU time: %0.1fms\n"
"Timestep: %0.1fms\n"
"Postsleep: %0.1fms\n",
Draw::PresentModeToString(g_frameTiming.presentMode),
g_frameTiming.presentInterval);
g_frameTiming.presentInterval,
g_frameTiming.cpuTime * 1000.0,
g_frameTiming.timeStep * 1000.0,
g_frameTiming.postSleep * 1000.0
);
ctx->Draw()->DrawTextRect(ubuntu24, statBuf, bounds.x + 10, bounds.y + 50, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
+8 -3
View File
@@ -1165,6 +1165,7 @@ void NativeFrame(GraphicsContext *graphicsContext) {
g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation);
// All actual rendering happen in here.
g_frameTiming.BeforeCPUSlice();
g_screenManager->render();
if (g_screenManager->getUIContext()->Text()) {
g_screenManager->getUIContext()->Text()->OncePerFrame();
@@ -1175,14 +1176,15 @@ void NativeFrame(GraphicsContext *graphicsContext) {
g_draw->EndFrame();
// 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..
if (renderCounter < 10 && ++renderCounter == 10) {
// We're rendering fine, clear out failure info.
ClearFailedGPUBackends();
}
// 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.BeforePresent();
int interval;
Draw::PresentMode presentMode = ComputePresentMode(g_draw, &interval);
g_draw->Present(presentMode, interval);
@@ -1218,6 +1220,9 @@ void NativeFrame(GraphicsContext *graphicsContext) {
// INFO_LOG(G3D, "Polling graphics context");
graphicsContext->Poll();
}
// TODO: Possibly this should move elsewhere, depending on the platform.
g_frameTiming.AfterPresent();
}
void HandleGlobalMessage(const std::string &msg, const std::string &value) {