1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-02-20 19:54:19 +01:00

[util] Make frame rate limiter enablement heuristic more robust

Allow for more buffering to happen in order to not enable the limiter
too eagerly.
This commit is contained in:
Philip Rebohle 2024-08-29 14:19:56 +02:00 committed by Philip Rebohle
parent 38308d443e
commit 04d558cf2e
6 changed files with 51 additions and 36 deletions

View File

@ -353,7 +353,7 @@ namespace dxvk {
m_targetFrameRate = FrameRate;
if (m_presenter != nullptr)
m_presenter->setFrameRateLimit(m_targetFrameRate);
m_presenter->setFrameRateLimit(m_targetFrameRate, GetActualFrameLatency());
}
@ -506,7 +506,7 @@ namespace dxvk {
presenterDesc.fullScreenExclusive = PickFullscreenMode();
m_presenter = new Presenter(m_device, m_frameLatencySignal, presenterDesc);
m_presenter->setFrameRateLimit(m_targetFrameRate);
m_presenter->setFrameRateLimit(m_targetFrameRate, GetActualFrameLatency());
}

View File

@ -1120,7 +1120,7 @@ namespace dxvk {
if (SyncInterval && frameRateOption == 0.0)
frameRate = -m_displayRefreshRate / double(SyncInterval);
m_wctx->presenter->setFrameRateLimit(frameRate);
m_wctx->presenter->setFrameRateLimit(frameRate, GetActualFrameLatency());
}

View File

@ -415,8 +415,8 @@ namespace dxvk {
}
void Presenter::setFrameRateLimit(double frameRate) {
m_fpsLimiter.setTargetFrameRate(frameRate);
void Presenter::setFrameRateLimit(double frameRate, uint32_t maxLatency) {
m_fpsLimiter.setTargetFrameRate(frameRate, maxLatency);
}

View File

@ -195,7 +195,7 @@ namespace dxvk {
* \param [in] frameRate Target frame rate. Set
* to 0 in order to disable the limiter.
*/
void setFrameRateLimit(double frameRate);
void setFrameRateLimit(double frameRate, uint32_t maxLatency);
/**
* \brief Checks whether a Vulkan swap chain exists

View File

@ -17,7 +17,7 @@ namespace dxvk {
if (!env.empty()) {
try {
setTargetFrameRate(std::stod(env));
setTargetFrameRate(std::stod(env), 0);
m_envOverride = true;
} catch (const std::invalid_argument&) {
// no-op
@ -31,7 +31,7 @@ namespace dxvk {
}
void FpsLimiter::setTargetFrameRate(double frameRate) {
void FpsLimiter::setTargetFrameRate(double frameRate, uint32_t maxLatency) {
std::lock_guard<dxvk::mutex> lock(m_mutex);
if (!m_envOverride) {
@ -42,8 +42,11 @@ namespace dxvk {
if (m_targetInterval != interval) {
m_targetInterval = interval;
m_heuristicFrameTime = TimePoint();
m_heuristicFrameCount = 0;
m_heuristicEnable = false;
m_maxLatency = maxLatency;
}
}
}
@ -52,6 +55,7 @@ namespace dxvk {
void FpsLimiter::delay() {
std::unique_lock<dxvk::mutex> lock(m_mutex);
auto interval = m_targetInterval;
auto latency = m_maxLatency;
if (interval == TimerDuration::zero()) {
m_nextFrame = TimePoint();
@ -63,7 +67,7 @@ namespace dxvk {
if (interval < TimerDuration::zero()) {
interval = -interval;
if (!testRefreshHeuristic(interval, t1))
if (!testRefreshHeuristic(interval, t1, latency))
return;
}
@ -80,38 +84,48 @@ namespace dxvk {
}
bool FpsLimiter::testRefreshHeuristic(TimerDuration interval, TimePoint now) {
bool FpsLimiter::testRefreshHeuristic(TimerDuration interval, TimePoint now, uint32_t maxLatency) {
if (m_heuristicEnable)
return true;
// Use a sliding window to determine whether the current
// frame rate is higher than the targeted refresh rate
uint32_t heuristicWindow = m_heuristicFrameTimes.size();
auto windowStart = m_heuristicFrameTimes[m_heuristicFrameCount % heuristicWindow];
auto windowDuration = std::chrono::duration_cast<TimerDuration>(now - windowStart);
constexpr static uint32_t MinWindowSize = 8;
constexpr static uint32_t MaxWindowSize = 128;
m_heuristicFrameTimes[m_heuristicFrameCount % heuristicWindow] = now;
m_heuristicFrameCount += 1;
if (m_heuristicFrameCount >= MinWindowSize) {
TimerDuration windowTotalTime = now - m_heuristicFrameTime;
TimerDuration windowExpectedTime = m_heuristicFrameCount * interval;
// The first window of frames may contain faster frames as the
// internal swap chain queue fills up, so we should ignore it.
if (m_heuristicFrameCount < 2 * heuristicWindow)
return false;
uint32_t minFrameCount = m_heuristicFrameCount - 1;
uint32_t maxFrameCount = m_heuristicFrameCount + maxLatency;
// Test whether we should engage the frame rate limiter. It will
// stay enabled until the refresh rate or vsync enablement change.
m_heuristicEnable = (103 * windowDuration) < (100 * heuristicWindow) * interval;
// Enable frame rate limiter if frames have been delivered faster than
// the desired refresh rate even accounting for swap chain buffering.
if ((maxFrameCount * windowTotalTime) < (m_heuristicFrameCount * windowExpectedTime)) {
double got = (double(m_heuristicFrameCount) * double(TimerDuration::period::den))
/ (double(windowTotalTime.count()) * double(TimerDuration::period::num));
double refresh = double(TimerDuration::period::den) / (double(TimerDuration::period::num) * double(interval.count()));
if (m_heuristicEnable) {
double got = (double(heuristicWindow) * double(TimerDuration::period::den))
/ (double(windowDuration.count()) * double(TimerDuration::period::num));
double refresh = double(TimerDuration::period::den) / (double(TimerDuration::period::num) * double(interval.count()));
Logger::info(str::format("Detected frame rate (~", uint32_t(got), ") higher than selected refresh rate of ~",
uint32_t(refresh), " Hz.\n", "Engaging frame rate limiter."));
Logger::info(str::format("Detected frame rate (~", uint32_t(got), ") higher than selected refresh rate of ~",
uint32_t(refresh), " Hz.\n", "Engaging frame rate limiter."));
m_heuristicEnable = true;
return true;
}
// Reset heuristics if frames have been delivered slower than the refresh rate.
if (((minFrameCount * windowTotalTime) > (m_heuristicFrameCount * windowExpectedTime))
|| (m_heuristicFrameCount >= MaxWindowSize)) {
m_heuristicFrameCount = 1;
m_heuristicFrameTime = now;
return false;
}
}
return m_heuristicEnable;
if (!m_heuristicFrameCount)
m_heuristicFrameTime = now;
m_heuristicFrameCount += 1;
return false;
}
}

View File

@ -28,7 +28,7 @@ namespace dxvk {
* \brief Sets target frame rate
* \param [in] frameRate Target frame rate
*/
void setTargetFrameRate(double frameRate);
void setTargetFrameRate(double frameRate, uint32_t maxLatency);
/**
* \brief Stalls calling thread as necessary
@ -48,14 +48,15 @@ namespace dxvk {
TimerDuration m_targetInterval = TimerDuration::zero();
TimePoint m_nextFrame = TimePoint();
uint32_t m_maxLatency = 0;
bool m_envOverride = false;
uint32_t m_heuristicFrameCount = 0;
std::array<TimePoint, 16> m_heuristicFrameTimes = { };
bool m_heuristicEnable = false;
uint32_t m_heuristicFrameCount = 0;
TimePoint m_heuristicFrameTime = TimePoint();
bool m_heuristicEnable = false;
bool testRefreshHeuristic(TimerDuration interval, TimePoint now);
bool testRefreshHeuristic(TimerDuration interval, TimePoint now, uint32_t maxLatency);
};