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
#include "Common/Common.h"
#include "Common/Data/Collections/FastVec.h"
// Flags and structs shared between backends that haven't found a good home.
@ -34,4 +35,6 @@ struct FrameTimeData {
double earliestPresentTime;
double presentMargin;
};
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);
// _dbg_assert_(sz == 88);
}

View File

@ -227,7 +227,7 @@ struct GLRRenderThreadTask {
// directly in the destructor.
class GLRenderManager {
public:
GLRenderManager(HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory);
GLRenderManager(FrameHistoryBuffer &frameTimeHistory);
~GLRenderManager();
GLRenderManager(GLRenderManager &) = delete;
@ -912,5 +912,5 @@ private:
InvalidationCallback invalidationCallback_;
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;
}
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),
initTimeMs_("initTimeMs"),
totalGPUTimeMs_("totalGPUTimeMs"),

View File

@ -182,7 +182,7 @@ struct CompileQueueEntry {
class VulkanRenderManager {
public:
VulkanRenderManager(VulkanContext *vulkan, bool useThread, HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory);
VulkanRenderManager(VulkanContext *vulkan, bool useThread, FrameHistoryBuffer &frameTimeHistory);
~VulkanRenderManager();
// 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_;
uint64_t frameIdGen_ = FRAME_TIME_HISTORY_LENGTH;
HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory_;
FrameHistoryBuffer &frameTimeHistory_;
};

View File

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

View File

@ -96,8 +96,26 @@ Draw::PresentMode ComputePresentMode(Draw::DrawContext *draw, int *interval) {
return mode;
}
void FrameTiming::BeforeCPUSlice() {
void FrameTiming::BeforeCPUSlice(const FrameHistoryBuffer &frameHistory) {
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) {
@ -106,9 +124,9 @@ void FrameTiming::SetTimeStep(float scaledTimeStep) {
double now = time_now_d();
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) {
lastPresentTime = now;
}
@ -128,7 +146,7 @@ void FrameTiming::BeforePresent() {
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;
nextPresentTime = lastPresentTime + this->timeStep + nudge_;
while (true) {
double remaining = nextPresentTime - time_now_d();
if (remaining <= 0.0)

View File

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

View File

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

View File

@ -115,12 +115,13 @@ 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) (%s)\n"
"CPU time: %0.1fms\n"
"Timestep: %0.1fms\n"
"Postsleep: %0.1fms\n",
Draw::PresentModeToString(g_frameTiming.presentMode),
g_frameTiming.presentInterval,
g_frameTiming.usePresentTiming ? "new" : "old",
g_frameTiming.cpuTime * 1000.0,
g_frameTiming.timeStep * 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_frameTiming.BeforeCPUSlice();
g_frameTiming.BeforeCPUSlice(g_draw->FrameTimeHistory());
// All actual rendering happen in here.
g_screenManager->render();
if (g_screenManager->getUIContext()->Text()) {