Experiment with nudging the present timing to eliminate time gaps

This commit is contained in:
Henrik Rydgård 2023-08-15 21:32:19 +02:00
parent 864528303a
commit fb4c167d37
11 changed files with 44 additions and 20 deletions

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/Data/Collections/FastVec.h"
// Flags and structs shared between backends that haven't found a good home. // Flags and structs shared between backends that haven't found a good home.
@ -34,4 +35,6 @@ struct FrameTimeData {
double earliestPresentTime; double earliestPresentTime;
double presentMargin; double presentMargin;
}; };
constexpr size_t FRAME_TIME_HISTORY_LENGTH = 32; constexpr size_t FRAME_TIME_HISTORY_LENGTH = 32;
typedef HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> FrameHistoryBuffer;

View File

@ -37,7 +37,7 @@ GLRTexture::~GLRTexture() {
} }
} }
GLRenderManager::GLRenderManager(HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory) : frameTimeHistory_(frameTimeHistory) { GLRenderManager::GLRenderManager(FrameHistoryBuffer &frameTimeHistory) : frameTimeHistory_(frameTimeHistory) {
// size_t sz = sizeof(GLRRenderData); // size_t sz = sizeof(GLRRenderData);
// _dbg_assert_(sz == 88); // _dbg_assert_(sz == 88);
} }

View File

@ -227,7 +227,7 @@ struct GLRRenderThreadTask {
// directly in the destructor. // directly in the destructor.
class GLRenderManager { class GLRenderManager {
public: public:
GLRenderManager(HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory); GLRenderManager(FrameHistoryBuffer &frameTimeHistory);
~GLRenderManager(); ~GLRenderManager();
GLRenderManager(GLRenderManager &) = delete; GLRenderManager(GLRenderManager &) = delete;
@ -912,5 +912,5 @@ private:
InvalidationCallback invalidationCallback_; InvalidationCallback invalidationCallback_;
uint64_t frameIdGen_ = FRAME_TIME_HISTORY_LENGTH; uint64_t frameIdGen_ = FRAME_TIME_HISTORY_LENGTH;
HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory_; FrameHistoryBuffer &frameTimeHistory_;
}; };

View File

@ -248,7 +248,7 @@ bool VKRComputePipeline::CreateAsync(VulkanContext *vulkan) {
return true; return true;
} }
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread, HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory) VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread, FrameHistoryBuffer &frameTimeHistory)
: vulkan_(vulkan), queueRunner_(vulkan), : vulkan_(vulkan), queueRunner_(vulkan),
initTimeMs_("initTimeMs"), initTimeMs_("initTimeMs"),
totalGPUTimeMs_("totalGPUTimeMs"), totalGPUTimeMs_("totalGPUTimeMs"),

View File

@ -182,7 +182,7 @@ struct CompileQueueEntry {
class VulkanRenderManager { class VulkanRenderManager {
public: public:
VulkanRenderManager(VulkanContext *vulkan, bool useThread, HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory); VulkanRenderManager(VulkanContext *vulkan, bool useThread, FrameHistoryBuffer &frameTimeHistory);
~VulkanRenderManager(); ~VulkanRenderManager();
// Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again.
@ -542,5 +542,5 @@ private:
std::function<void(InvalidationCallbackFlags)> invalidationCallback_; std::function<void(InvalidationCallbackFlags)> invalidationCallback_;
uint64_t frameIdGen_ = FRAME_TIME_HISTORY_LENGTH; uint64_t frameIdGen_ = FRAME_TIME_HISTORY_LENGTH;
HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory_; FrameHistoryBuffer &frameTimeHistory_;
}; };

View File

@ -858,12 +858,12 @@ public:
return ""; return "";
} }
const HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &FrameTimeHistory() const { const FrameHistoryBuffer &FrameTimeHistory() const {
return frameTimeHistory_; return frameTimeHistory_;
} }
protected: protected:
HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> frameTimeHistory_; FrameHistoryBuffer frameTimeHistory_;
ShaderModule *vsPresets_[VS_MAX_PRESET]; ShaderModule *vsPresets_[VS_MAX_PRESET];
ShaderModule *fsPresets_[FS_MAX_PRESET]; ShaderModule *fsPresets_[FS_MAX_PRESET];

View File

@ -96,8 +96,26 @@ Draw::PresentMode ComputePresentMode(Draw::DrawContext *draw, int *interval) {
return mode; return mode;
} }
void FrameTiming::BeforeCPUSlice() { void FrameTiming::BeforeCPUSlice(const FrameHistoryBuffer &frameHistory) {
cpuSliceStartTime = time_now_d(); cpuSliceStartTime = time_now_d();
// Here we can examine the frame history for anomalies to correct.
nudge_ = 0.0;
const FrameTimeData &oldData = frameHistory[3];
if (oldData.queuePresent == 0.0) {
// No data to look at.
return;
}
if (oldData.afterFenceWait - oldData.frameBegin > 0.001) {
nudge_ = (oldData.afterFenceWait - oldData.frameBegin) * 0.1;
}
if (oldData.firstSubmit - oldData.afterFenceWait > cpuTime) {
// Not sure how this grows so large sometimes.
nudge_ = (oldData.firstSubmit - oldData.afterFenceWait - cpuTime) * 0.1;
}
} }
void FrameTiming::SetTimeStep(float scaledTimeStep) { void FrameTiming::SetTimeStep(float scaledTimeStep) {
@ -106,9 +124,9 @@ void FrameTiming::SetTimeStep(float scaledTimeStep) {
double now = time_now_d(); double now = time_now_d();
cpuTime = now - cpuSliceStartTime; cpuTime = now - cpuSliceStartTime;
this->timeStep = scaledTimeStep + nudge_; this->timeStep = scaledTimeStep;
// Sync up lastPresentTime with the current time if it's way off. // Sync up lastPresentTime with the current time if it's way off. TODO: This should probably drift.
if (lastPresentTime < now - 0.5f) { if (lastPresentTime < now - 0.5f) {
lastPresentTime = now; lastPresentTime = now;
} }
@ -128,7 +146,7 @@ void FrameTiming::BeforePresent() {
return; return;
// Wait until we hit the next present time. Ideally we'll be fairly close here due to the previous AfterPresent wait. // 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; nextPresentTime = lastPresentTime + this->timeStep + nudge_;
while (true) { while (true) {
double remaining = nextPresentTime - time_now_d(); double remaining = nextPresentTime - time_now_d();
if (remaining <= 0.0) if (remaining <= 0.0)

View File

@ -27,7 +27,7 @@ public:
void Reset(Draw::DrawContext *draw); void Reset(Draw::DrawContext *draw);
void BeforeCPUSlice(); void BeforeCPUSlice(const FrameHistoryBuffer &frameHistory);
void SetTimeStep(float scaledTimeStep); void SetTimeStep(float scaledTimeStep);
void AfterCPUSlice(); void AfterCPUSlice();
void BeforePresent(); void BeforePresent();

View File

@ -442,7 +442,7 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep)
#ifdef _WIN32 #ifdef _WIN32
sleep_ms(1); // Sleep for 1ms on this thread sleep_ms(1); // Sleep for 1ms on this thread
#else #else
const double left = g_frameTiming.nextFrameTime - g_frameTiming.curFrameTime; const double left = nextFrameTime - curFrameTime;
usleep((long)(left * 1000000)); usleep((long)(left * 1000000));
#endif #endif
} }
@ -637,9 +637,13 @@ void __DisplayFlip(int cyclesLate) {
bool throttle = FrameTimingThrottled(); bool throttle = FrameTimingThrottled();
int fpsLimit = FrameTimingLimit(); int fpsLimit = FrameTimingLimit();
float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank; float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank;
const bool fbReallyDirty = gpu->FramebufferReallyDirty();
if (fpsLimit > 0 && fpsLimit != framerate) { if (fpsLimit > 0 && fpsLimit != framerate) {
scaledTimestep *= (float)framerate / fpsLimit; scaledTimestep *= (float)framerate / fpsLimit;
} }
bool skipFrame;
int maxFrameskip;
int frameSkipNum;
// If the ideal case, use the new timing path. // If the ideal case, use the new timing path.
g_frameTiming.usePresentTiming = g_Config.iFrameSkip == 0 && !refreshRateNeedsSkip; g_frameTiming.usePresentTiming = g_Config.iFrameSkip == 0 && !refreshRateNeedsSkip;
@ -659,7 +663,6 @@ void __DisplayFlip(int cyclesLate) {
} }
// Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap. // Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap.
const bool fbReallyDirty = gpu->FramebufferReallyDirty();
if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) { if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) {
// Check first though, might've just quit / been paused. // Check first though, might've just quit / been paused.
if (!forceNoFlip && Core_NextFrame()) { if (!forceNoFlip && Core_NextFrame()) {
@ -674,11 +677,10 @@ void __DisplayFlip(int cyclesLate) {
gpuStats.numFlips++; gpuStats.numFlips++;
} }
bool skipFrame;
DoFrameTiming(throttle, &skipFrame, scaledTimestep); DoFrameTiming(throttle, &skipFrame, scaledTimestep);
int maxFrameskip = 8; maxFrameskip = 8;
int frameSkipNum = DisplayCalculateFrameSkip(); frameSkipNum = DisplayCalculateFrameSkip();
if (throttle) { if (throttle) {
// 4 here means 1 drawn, 4 skipped - so 12 fps minimum. // 4 here means 1 drawn, 4 skipped - so 12 fps minimum.
maxFrameskip = frameSkipNum; maxFrameskip = frameSkipNum;

View File

@ -115,12 +115,13 @@ static void DrawFrameTiming(UIContext *ctx, const Bounds &bounds) {
ctx->Draw()->SetFontScale(0.5f, 0.5f); ctx->Draw()->SetFontScale(0.5f, 0.5f);
snprintf(statBuf, sizeof(statBuf), snprintf(statBuf, sizeof(statBuf),
"Mode (interval): %s (%d)" "Mode (interval): %s (%d) (%s)\n"
"CPU time: %0.1fms\n" "CPU time: %0.1fms\n"
"Timestep: %0.1fms\n" "Timestep: %0.1fms\n"
"Postsleep: %0.1fms\n", "Postsleep: %0.1fms\n",
Draw::PresentModeToString(g_frameTiming.presentMode), Draw::PresentModeToString(g_frameTiming.presentMode),
g_frameTiming.presentInterval, g_frameTiming.presentInterval,
g_frameTiming.usePresentTiming ? "new" : "old",
g_frameTiming.cpuTime * 1000.0, g_frameTiming.cpuTime * 1000.0,
g_frameTiming.timeStep * 1000.0, g_frameTiming.timeStep * 1000.0,
g_frameTiming.postSleep * 1000.0 g_frameTiming.postSleep * 1000.0

View File

@ -1164,7 +1164,7 @@ void NativeFrame(GraphicsContext *graphicsContext) {
g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation); g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation);
g_frameTiming.BeforeCPUSlice(); g_frameTiming.BeforeCPUSlice(g_draw->FrameTimeHistory());
// All actual rendering happen in here. // All actual rendering happen in here.
g_screenManager->render(); g_screenManager->render();
if (g_screenManager->getUIContext()->Text()) { if (g_screenManager->getUIContext()->Text()) {