mirror of
https://github.com/doitsujin/dxvk.git
synced 2025-02-20 19:54:19 +01:00
[util] Implement refresh rate heuristic for frame rate limiter
This commit is contained in:
parent
b4c4c9e683
commit
379346751a
@ -35,49 +35,103 @@ namespace dxvk {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
|
||||
if (!m_envOverride) {
|
||||
m_targetInterval = frameRate != 0.0
|
||||
TimerDuration interval = frameRate != 0.0
|
||||
? TimerDuration(int64_t(double(TimerDuration::period::den) / frameRate))
|
||||
: TimerDuration::zero();
|
||||
|
||||
if (isEnabled() && !m_initialized)
|
||||
initialize();
|
||||
if (m_targetInterval != interval) {
|
||||
m_targetInterval = interval;
|
||||
|
||||
m_heuristicFrameCount = 0;
|
||||
m_heuristicEnable = false;
|
||||
|
||||
if (m_targetInterval != TimerDuration::zero() && !m_initialized)
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FpsLimiter::delay() {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
std::unique_lock<dxvk::mutex> lock(m_mutex);
|
||||
auto interval = m_targetInterval;
|
||||
|
||||
if (!isEnabled())
|
||||
if (interval == TimerDuration::zero())
|
||||
return;
|
||||
|
||||
auto t0 = m_lastFrame;
|
||||
auto t1 = dxvk::high_resolution_clock::now();
|
||||
|
||||
if (interval < TimerDuration::zero()) {
|
||||
interval = -interval;
|
||||
|
||||
if (!testRefreshHeuristic(interval, t1))
|
||||
return;
|
||||
}
|
||||
|
||||
// Subsequent code must not access any class members
|
||||
// that can be written by setTargetFrameRate
|
||||
lock.unlock();
|
||||
|
||||
auto frameTime = std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
||||
|
||||
if (frameTime * 100 > m_targetInterval * 103 - m_deviation * 100) {
|
||||
if (frameTime * 100 > interval * 103 - m_deviation * 100) {
|
||||
// If we have a slow frame, reset the deviation since we
|
||||
// do not want to compensate for low performance later on
|
||||
m_deviation = TimerDuration::zero();
|
||||
} else {
|
||||
// Don't call sleep if the amount of time to sleep is shorter
|
||||
// than the time the function calls are likely going to take
|
||||
TimerDuration sleepDuration = m_targetInterval - m_deviation - frameTime;
|
||||
TimerDuration sleepDuration = interval - m_deviation - frameTime;
|
||||
t1 = Sleep::sleepFor(t1, sleepDuration);
|
||||
|
||||
// Compensate for any sleep inaccuracies in the next frame, and
|
||||
// limit cumulative deviation in order to avoid stutter in case we
|
||||
// have a number of slow frames immediately followed by a fast one.
|
||||
frameTime = std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
||||
m_deviation += frameTime - m_targetInterval;
|
||||
m_deviation = std::min(m_deviation, m_targetInterval / 16);
|
||||
m_deviation += frameTime - interval;
|
||||
m_deviation = std::min(m_deviation, interval / 16);
|
||||
}
|
||||
|
||||
m_lastFrame = t1;
|
||||
}
|
||||
|
||||
|
||||
bool FpsLimiter::testRefreshHeuristic(TimerDuration interval, TimePoint now) {
|
||||
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);
|
||||
|
||||
m_heuristicFrameTimes[m_heuristicFrameCount % heuristicWindow] = now;
|
||||
m_heuristicFrameCount += 1;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
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."));
|
||||
}
|
||||
|
||||
return m_heuristicEnable;
|
||||
}
|
||||
|
||||
|
||||
void FpsLimiter::initialize() {
|
||||
m_lastFrame = dxvk::high_resolution_clock::now();
|
||||
m_initialized = true;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "thread.h"
|
||||
#include "util_time.h"
|
||||
|
||||
@ -37,14 +39,6 @@ namespace dxvk {
|
||||
*/
|
||||
void delay();
|
||||
|
||||
/**
|
||||
* \brief Checks whether the frame rate limiter is enabled
|
||||
* \returns \c true if the target frame rate is non-zero.
|
||||
*/
|
||||
bool isEnabled() const {
|
||||
return m_targetInterval != TimerDuration::zero();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
using TimePoint = dxvk::high_resolution_clock::time_point;
|
||||
@ -54,11 +48,17 @@ namespace dxvk {
|
||||
|
||||
TimerDuration m_targetInterval = TimerDuration::zero();
|
||||
TimerDuration m_deviation = TimerDuration::zero();
|
||||
TimePoint m_lastFrame;
|
||||
TimePoint m_lastFrame = TimePoint();
|
||||
|
||||
bool m_initialized = false;
|
||||
bool m_envOverride = false;
|
||||
|
||||
uint32_t m_heuristicFrameCount = 0;
|
||||
std::array<TimePoint, 16> m_heuristicFrameTimes = { };
|
||||
bool m_heuristicEnable = false;
|
||||
|
||||
bool testRefreshHeuristic(TimerDuration interval, TimePoint now);
|
||||
|
||||
void initialize();
|
||||
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user