2021-06-09 03:43:19 +02:00
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
#include "thread.h"
|
2021-06-10 23:06:40 +02:00
|
|
|
#include "util_env.h"
|
2021-06-09 03:43:19 +02:00
|
|
|
#include "util_fps_limiter.h"
|
2022-09-15 13:41:03 +02:00
|
|
|
#include "util_sleep.h"
|
2021-06-09 03:43:19 +02:00
|
|
|
#include "util_string.h"
|
|
|
|
|
|
|
|
#include "./log/log.h"
|
|
|
|
|
2022-08-16 08:56:01 +00:00
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
2021-06-09 03:43:19 +02:00
|
|
|
namespace dxvk {
|
|
|
|
|
|
|
|
FpsLimiter::FpsLimiter() {
|
2021-06-10 23:06:40 +02:00
|
|
|
std::string env = env::getEnvVar("DXVK_FRAME_RATE");
|
|
|
|
|
|
|
|
if (!env.empty()) {
|
|
|
|
try {
|
2024-08-29 14:19:56 +02:00
|
|
|
setTargetFrameRate(std::stod(env), 0);
|
2021-06-10 23:06:40 +02:00
|
|
|
m_envOverride = true;
|
|
|
|
} catch (const std::invalid_argument&) {
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
}
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FpsLimiter::~FpsLimiter() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
void FpsLimiter::setTargetFrameRate(double frameRate, uint32_t maxLatency) {
|
2021-06-28 19:19:29 +02:00
|
|
|
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2021-06-10 23:06:40 +02:00
|
|
|
if (!m_envOverride) {
|
2024-06-06 10:31:20 +02:00
|
|
|
TimerDuration interval = frameRate != 0.0
|
2022-08-16 08:46:40 +00:00
|
|
|
? TimerDuration(int64_t(double(TimerDuration::period::den) / frameRate))
|
|
|
|
: TimerDuration::zero();
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2024-06-06 10:31:20 +02:00
|
|
|
if (m_targetInterval != interval) {
|
|
|
|
m_targetInterval = interval;
|
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
m_heuristicFrameTime = TimePoint();
|
2024-06-06 10:31:20 +02:00
|
|
|
m_heuristicFrameCount = 0;
|
|
|
|
m_heuristicEnable = false;
|
2024-08-29 14:19:56 +02:00
|
|
|
|
|
|
|
m_maxLatency = maxLatency;
|
2024-06-06 10:31:20 +02:00
|
|
|
}
|
2021-06-10 23:06:40 +02:00
|
|
|
}
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-06-06 09:20:04 +02:00
|
|
|
void FpsLimiter::delay() {
|
2024-06-06 10:31:20 +02:00
|
|
|
std::unique_lock<dxvk::mutex> lock(m_mutex);
|
|
|
|
auto interval = m_targetInterval;
|
2024-08-29 14:19:56 +02:00
|
|
|
auto latency = m_maxLatency;
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2024-06-10 19:36:17 +02:00
|
|
|
if (interval == TimerDuration::zero()) {
|
|
|
|
m_nextFrame = TimePoint();
|
2021-06-09 03:43:19 +02:00
|
|
|
return;
|
2024-06-10 19:36:17 +02:00
|
|
|
}
|
2021-06-09 03:43:19 +02:00
|
|
|
|
|
|
|
auto t1 = dxvk::high_resolution_clock::now();
|
|
|
|
|
2024-06-06 10:31:20 +02:00
|
|
|
if (interval < TimerDuration::zero()) {
|
|
|
|
interval = -interval;
|
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
if (!testRefreshHeuristic(interval, t1, latency))
|
2024-06-06 10:31:20 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subsequent code must not access any class members
|
|
|
|
// that can be written by setTargetFrameRate
|
|
|
|
lock.unlock();
|
|
|
|
|
2024-06-10 19:36:17 +02:00
|
|
|
if (t1 < m_nextFrame)
|
|
|
|
Sleep::sleepUntil(t1, m_nextFrame);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2024-06-10 19:36:17 +02:00
|
|
|
m_nextFrame = (t1 < m_nextFrame + interval)
|
|
|
|
? m_nextFrame + interval
|
|
|
|
: t1 + interval;
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
bool FpsLimiter::testRefreshHeuristic(TimerDuration interval, TimePoint now, uint32_t maxLatency) {
|
2024-06-06 10:31:20 +02:00
|
|
|
if (m_heuristicEnable)
|
|
|
|
return true;
|
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
constexpr static uint32_t MinWindowSize = 8;
|
|
|
|
constexpr static uint32_t MaxWindowSize = 128;
|
2024-06-06 10:31:20 +02:00
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
if (m_heuristicFrameCount >= MinWindowSize) {
|
|
|
|
TimerDuration windowTotalTime = now - m_heuristicFrameTime;
|
|
|
|
TimerDuration windowExpectedTime = m_heuristicFrameCount * interval;
|
2024-06-06 10:31:20 +02:00
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
uint32_t minFrameCount = m_heuristicFrameCount - 1;
|
|
|
|
uint32_t maxFrameCount = m_heuristicFrameCount + maxLatency;
|
2024-06-06 10:31:20 +02:00
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
// 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()));
|
2024-06-06 10:31:20 +02:00
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-06-06 10:31:20 +02:00
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
// 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;
|
|
|
|
}
|
2024-06-06 10:31:20 +02:00
|
|
|
}
|
|
|
|
|
2024-08-29 14:19:56 +02:00
|
|
|
if (!m_heuristicFrameCount)
|
|
|
|
m_heuristicFrameTime = now;
|
|
|
|
|
|
|
|
m_heuristicFrameCount += 1;
|
|
|
|
return false;
|
2024-06-06 10:31:20 +02:00
|
|
|
}
|
|
|
|
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|