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"
|
|
|
|
#include "util_string.h"
|
|
|
|
|
|
|
|
#include "./log/log.h"
|
|
|
|
|
|
|
|
namespace dxvk {
|
|
|
|
|
|
|
|
FpsLimiter::FpsLimiter() {
|
2021-06-10 23:06:40 +02:00
|
|
|
std::string env = env::getEnvVar("DXVK_FRAME_RATE");
|
|
|
|
|
|
|
|
if (!env.empty()) {
|
|
|
|
try {
|
|
|
|
setTargetFrameRate(std::stod(env));
|
|
|
|
m_envOverride = true;
|
|
|
|
} catch (const std::invalid_argument&) {
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
}
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FpsLimiter::~FpsLimiter() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FpsLimiter::setTargetFrameRate(double frameRate) {
|
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) {
|
|
|
|
m_targetInterval = frameRate > 0.0
|
2022-08-16 10:46:40 +02:00
|
|
|
? TimerDuration(int64_t(double(TimerDuration::period::den) / frameRate))
|
|
|
|
: TimerDuration::zero();
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2021-06-10 23:06:40 +02:00
|
|
|
if (isEnabled() && !m_initialized)
|
|
|
|
initialize();
|
|
|
|
}
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FpsLimiter::setDisplayRefreshRate(double refreshRate) {
|
2021-06-28 19:19:29 +02:00
|
|
|
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
|
|
|
m_refreshInterval = refreshRate > 0.0
|
2022-08-16 10:46:40 +02:00
|
|
|
? TimerDuration(int64_t(double(TimerDuration::period::den) / refreshRate))
|
|
|
|
: TimerDuration::zero();
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FpsLimiter::delay(bool vsyncEnabled) {
|
2021-06-28 19:19:29 +02:00
|
|
|
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
|
|
|
if (!isEnabled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If the swap chain is known to have vsync enabled and the
|
|
|
|
// refresh rate is similar to the target frame rate, disable
|
|
|
|
// the limiter so it does not screw up frame times
|
2021-06-10 23:06:40 +02:00
|
|
|
if (vsyncEnabled && !m_envOverride
|
|
|
|
&& m_refreshInterval * 100 > m_targetInterval * 97)
|
2021-06-09 03:43:19 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
auto t0 = m_lastFrame;
|
|
|
|
auto t1 = dxvk::high_resolution_clock::now();
|
|
|
|
|
2022-08-16 10:46:40 +02:00
|
|
|
auto frameTime = std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
|
|
|
if (frameTime * 100 > m_targetInterval * 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
|
2022-08-16 10:46:40 +02:00
|
|
|
m_deviation = TimerDuration::zero();
|
2021-06-09 03:43:19 +02:00
|
|
|
} 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
|
2022-08-16 10:46:40 +02:00
|
|
|
TimerDuration sleepDuration = m_targetInterval - m_deviation - frameTime;
|
2021-06-09 03:43:19 +02:00
|
|
|
t1 = sleep(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.
|
2022-08-16 10:46:40 +02:00
|
|
|
frameTime = std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
2021-06-09 03:43:19 +02:00
|
|
|
m_deviation += frameTime - m_targetInterval;
|
|
|
|
m_deviation = std::min(m_deviation, m_targetInterval / 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_lastFrame = t1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-16 10:46:40 +02:00
|
|
|
FpsLimiter::TimePoint FpsLimiter::sleep(TimePoint t0, TimerDuration duration) {
|
|
|
|
if (duration <= TimerDuration::zero())
|
2021-06-09 03:43:19 +02:00
|
|
|
return t0;
|
|
|
|
|
|
|
|
// On wine, we can rely on NtDelayExecution waiting for more or
|
|
|
|
// less exactly the desired amount of time, and we want to avoid
|
|
|
|
// spamming QueryPerformanceCounter for performance reasons.
|
|
|
|
// On Windows, we busy-wait for the last couple of milliseconds
|
|
|
|
// since sleeping is highly inaccurate and inconsistent.
|
2022-08-16 10:46:40 +02:00
|
|
|
TimerDuration sleepThreshold = m_sleepThreshold;
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2022-08-16 10:46:40 +02:00
|
|
|
if (m_sleepGranularity != TimerDuration::zero())
|
2021-06-09 03:43:19 +02:00
|
|
|
sleepThreshold += duration / 6;
|
|
|
|
|
2022-08-16 10:46:40 +02:00
|
|
|
TimerDuration remaining = duration;
|
2021-06-09 03:43:19 +02:00
|
|
|
TimePoint t1 = t0;
|
|
|
|
|
|
|
|
while (remaining > sleepThreshold) {
|
2022-08-16 10:46:40 +02:00
|
|
|
TimerDuration sleepDuration = remaining - sleepThreshold;
|
2021-06-09 03:43:19 +02:00
|
|
|
|
2022-08-16 10:55:39 +02:00
|
|
|
performSleep(sleepDuration);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
|
|
|
t1 = dxvk::high_resolution_clock::now();
|
2022-08-16 10:46:40 +02:00
|
|
|
remaining -= std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
2021-06-09 03:43:19 +02:00
|
|
|
t0 = t1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Busy-wait until we have slept long enough
|
2022-08-16 10:46:40 +02:00
|
|
|
while (remaining > TimerDuration::zero()) {
|
2021-06-09 03:43:19 +02:00
|
|
|
t1 = dxvk::high_resolution_clock::now();
|
2022-08-16 10:46:40 +02:00
|
|
|
remaining -= std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
2021-06-09 03:43:19 +02:00
|
|
|
t0 = t1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return t1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FpsLimiter::initialize() {
|
2022-08-16 10:52:24 +02:00
|
|
|
updateSleepGranularity();
|
|
|
|
m_sleepThreshold = 4 * m_sleepGranularity;
|
|
|
|
m_lastFrame = dxvk::high_resolution_clock::now();
|
|
|
|
m_initialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FpsLimiter::updateSleepGranularity() {
|
2021-06-09 03:43:19 +02:00
|
|
|
HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
|
|
|
|
|
|
|
|
if (ntdll) {
|
|
|
|
NtDelayExecution = reinterpret_cast<NtDelayExecutionProc>(
|
|
|
|
::GetProcAddress(ntdll, "NtDelayExecution"));
|
|
|
|
auto NtQueryTimerResolution = reinterpret_cast<NtQueryTimerResolutionProc>(
|
|
|
|
::GetProcAddress(ntdll, "NtQueryTimerResolution"));
|
|
|
|
auto NtSetTimerResolution = reinterpret_cast<NtSetTimerResolutionProc>(
|
|
|
|
::GetProcAddress(ntdll, "NtSetTimerResolution"));
|
|
|
|
|
|
|
|
ULONG min, max, cur;
|
|
|
|
|
|
|
|
// Wine's implementation of these functions is a stub as of 6.10, which is fine
|
|
|
|
// since it uses select() in NtDelayExecution. This is only relevant for Windows.
|
|
|
|
if (NtQueryTimerResolution && !NtQueryTimerResolution(&min, &max, &cur)) {
|
2022-08-16 10:46:40 +02:00
|
|
|
m_sleepGranularity = TimerDuration(cur);
|
2021-06-09 03:43:19 +02:00
|
|
|
|
|
|
|
if (NtSetTimerResolution && !NtSetTimerResolution(max, TRUE, &cur)) {
|
|
|
|
Logger::info(str::format("Setting timer interval to ", (double(max) / 10.0), " us"));
|
2022-08-16 10:46:40 +02:00
|
|
|
m_sleepGranularity = TimerDuration(max);
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Assume 1ms sleep granularity by default
|
2022-08-16 10:46:40 +02:00
|
|
|
m_sleepGranularity = TimerDuration(10000);
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-16 10:55:39 +02:00
|
|
|
|
|
|
|
void FpsLimiter::performSleep(TimerDuration sleepDuration) {
|
|
|
|
if (NtDelayExecution) {
|
|
|
|
LARGE_INTEGER ticks;
|
|
|
|
ticks.QuadPart = -sleepDuration.count();
|
|
|
|
|
|
|
|
NtDelayExecution(FALSE, &ticks);
|
|
|
|
} else {
|
|
|
|
std::this_thread::sleep_for(sleepDuration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-09 03:43:19 +02:00
|
|
|
}
|