mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-14 18:19:36 +01:00
More timing work
This commit is contained in:
1 parent
e31e6b1793
commit
cc75979f5b
10 files changed
+128
-16
No files matched your search
@@ -1,3 +1,4 @@
|
||||
|
||||
#include "Common/GPU/OpenGL/GLCommon.h"
|
||||
#include "Common/GPU/OpenGL/GLFrameData.h"
|
||||
#include "Common/GPU/OpenGL/GLRenderManager.h"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user