mirror of
https://github.com/doitsujin/dxvk.git
synced 2025-01-19 05:52:11 +01:00
[util] Move platform-specific sleep code to dedicated helper class
This commit is contained in:
parent
000a647c56
commit
c1ab09a048
@ -6,6 +6,7 @@ util_src = files([
|
||||
'util_luid.cpp',
|
||||
'util_matrix.cpp',
|
||||
'util_shared_res.cpp',
|
||||
'util_sleep.cpp',
|
||||
|
||||
'thread.cpp',
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "thread.h"
|
||||
#include "util_env.h"
|
||||
#include "util_fps_limiter.h"
|
||||
#include "util_sleep.h"
|
||||
#include "util_string.h"
|
||||
|
||||
#include "./log/log.h"
|
||||
@ -63,7 +64,7 @@ namespace dxvk {
|
||||
// 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;
|
||||
t1 = sleep(t1, sleepDuration);
|
||||
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
|
||||
@ -77,100 +78,9 @@ namespace dxvk {
|
||||
}
|
||||
|
||||
|
||||
FpsLimiter::TimePoint FpsLimiter::sleep(TimePoint t0, TimerDuration duration) {
|
||||
if (duration <= TimerDuration::zero())
|
||||
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.
|
||||
TimerDuration sleepThreshold = m_sleepThreshold;
|
||||
|
||||
if (m_sleepGranularity != TimerDuration::zero())
|
||||
sleepThreshold += duration / 6;
|
||||
|
||||
TimerDuration remaining = duration;
|
||||
TimePoint t1 = t0;
|
||||
|
||||
while (remaining > sleepThreshold) {
|
||||
TimerDuration sleepDuration = remaining - sleepThreshold;
|
||||
|
||||
performSleep(sleepDuration);
|
||||
|
||||
t1 = dxvk::high_resolution_clock::now();
|
||||
remaining -= std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
||||
t0 = t1;
|
||||
}
|
||||
|
||||
// Busy-wait until we have slept long enough
|
||||
while (remaining > TimerDuration::zero()) {
|
||||
t1 = dxvk::high_resolution_clock::now();
|
||||
remaining -= std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
||||
t0 = t1;
|
||||
}
|
||||
|
||||
return t1;
|
||||
}
|
||||
|
||||
|
||||
void FpsLimiter::initialize() {
|
||||
updateSleepGranularity();
|
||||
m_sleepThreshold = 4 * m_sleepGranularity;
|
||||
m_lastFrame = dxvk::high_resolution_clock::now();
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
void FpsLimiter::updateSleepGranularity() {
|
||||
#ifdef _WIN32
|
||||
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)) {
|
||||
m_sleepGranularity = TimerDuration(cur);
|
||||
|
||||
if (NtSetTimerResolution && !NtSetTimerResolution(max, TRUE, &cur)) {
|
||||
Logger::info(str::format("Setting timer interval to ", (double(max) / 10.0), " us"));
|
||||
m_sleepGranularity = TimerDuration(max);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assume 1ms sleep granularity by default
|
||||
m_sleepGranularity = TimerDuration(1ms);
|
||||
}
|
||||
#else
|
||||
// Assume 0.5ms sleep granularity by default
|
||||
m_sleepGranularity = TimerDuration(500us);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void FpsLimiter::performSleep(TimerDuration sleepDuration) {
|
||||
#ifdef _WIN32
|
||||
if (NtDelayExecution) {
|
||||
LARGE_INTEGER ticks;
|
||||
ticks.QuadPart = -sleepDuration.count();
|
||||
|
||||
NtDelayExecution(FALSE, &ticks);
|
||||
} else {
|
||||
std::this_thread::sleep_for(sleepDuration);
|
||||
}
|
||||
#else
|
||||
std::this_thread::sleep_for(sleepDuration);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,18 +49,7 @@ namespace dxvk {
|
||||
private:
|
||||
|
||||
using TimePoint = dxvk::high_resolution_clock::time_point;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, we use NtDelayExecution which has units of 100ns.
|
||||
using TimerDuration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
|
||||
using NtQueryTimerResolutionProc = UINT (WINAPI *) (ULONG*, ULONG*, ULONG*);
|
||||
using NtSetTimerResolutionProc = UINT (WINAPI *) (ULONG, BOOL, ULONG*);
|
||||
using NtDelayExecutionProc = UINT (WINAPI *) (BOOL, LARGE_INTEGER*);
|
||||
NtDelayExecutionProc NtDelayExecution = nullptr;
|
||||
#else
|
||||
// On other platforms, we use the std library, which calls through to nanosleep -- which is ns.
|
||||
using TimerDuration = std::chrono::nanoseconds;
|
||||
#endif
|
||||
|
||||
dxvk::mutex m_mutex;
|
||||
|
||||
@ -71,17 +60,8 @@ namespace dxvk {
|
||||
bool m_initialized = false;
|
||||
bool m_envOverride = false;
|
||||
|
||||
TimerDuration m_sleepGranularity = TimerDuration::zero();
|
||||
TimerDuration m_sleepThreshold = TimerDuration::zero();
|
||||
|
||||
TimePoint sleep(TimePoint t0, TimerDuration duration);
|
||||
|
||||
void initialize();
|
||||
|
||||
void updateSleepGranularity();
|
||||
|
||||
void performSleep(TimerDuration sleepDuration);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
125
src/util/util_sleep.cpp
Normal file
125
src/util/util_sleep.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "util_sleep.h"
|
||||
#include "util_string.h"
|
||||
|
||||
#include "./log/log.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace dxvk {
|
||||
|
||||
Sleep Sleep::s_instance;
|
||||
|
||||
|
||||
Sleep::Sleep() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
Sleep::~Sleep() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Sleep::initialize() {
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (m_initialized.load())
|
||||
return;
|
||||
|
||||
initializePlatformSpecifics();
|
||||
m_sleepThreshold = 4 * m_sleepGranularity;
|
||||
|
||||
m_initialized.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
void Sleep::initializePlatformSpecifics() {
|
||||
#ifdef _WIN32
|
||||
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)) {
|
||||
m_sleepGranularity = TimerDuration(cur);
|
||||
|
||||
if (NtSetTimerResolution && !NtSetTimerResolution(max, TRUE, &cur)) {
|
||||
Logger::info(str::format("Setting timer interval to ", (double(max) / 10.0), " us"));
|
||||
m_sleepGranularity = TimerDuration(max);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assume 1ms sleep granularity by default
|
||||
m_sleepGranularity = TimerDuration(1ms);
|
||||
}
|
||||
#else
|
||||
// Assume 0.5ms sleep granularity by default
|
||||
m_sleepGranularity = TimerDuration(500us);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Sleep::TimePoint Sleep::sleep(TimePoint t0, TimerDuration duration) {
|
||||
if (duration <= TimerDuration::zero())
|
||||
return t0;
|
||||
|
||||
// If necessary, initialize function pointers and some values
|
||||
if (!m_initialized.load(std::memory_order_acquire))
|
||||
initialize();
|
||||
|
||||
// Busy-wait for the last couple of milliseconds since sleeping
|
||||
// on Windows is highly inaccurate and inconsistent.
|
||||
TimerDuration sleepThreshold = m_sleepThreshold;
|
||||
|
||||
if (m_sleepGranularity != TimerDuration::zero())
|
||||
sleepThreshold += duration / 6;
|
||||
|
||||
TimerDuration remaining = duration;
|
||||
TimePoint t1 = t0;
|
||||
|
||||
while (remaining > sleepThreshold) {
|
||||
TimerDuration sleepDuration = remaining - sleepThreshold;
|
||||
|
||||
systemSleep(sleepDuration);
|
||||
|
||||
t1 = dxvk::high_resolution_clock::now();
|
||||
remaining -= std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
||||
t0 = t1;
|
||||
}
|
||||
|
||||
// Busy-wait until we have slept long enough
|
||||
while (remaining > TimerDuration::zero()) {
|
||||
t1 = dxvk::high_resolution_clock::now();
|
||||
remaining -= std::chrono::duration_cast<TimerDuration>(t1 - t0);
|
||||
t0 = t1;
|
||||
}
|
||||
|
||||
return t1;
|
||||
}
|
||||
|
||||
|
||||
void Sleep::systemSleep(TimerDuration duration) {
|
||||
#ifdef _WIN32
|
||||
if (NtDelayExecution) {
|
||||
LARGE_INTEGER ticks;
|
||||
ticks.QuadPart = -duration.count();
|
||||
|
||||
NtDelayExecution(FALSE, &ticks);
|
||||
} else {
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
#else
|
||||
std::this_thread::sleep_for(duration);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
78
src/util/util_sleep.h
Normal file
78
src/util/util_sleep.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "thread.h"
|
||||
#include "util_time.h"
|
||||
|
||||
namespace dxvk {
|
||||
|
||||
/**
|
||||
* \brief Utility class for accurate sleeping
|
||||
*/
|
||||
class Sleep {
|
||||
|
||||
public:
|
||||
|
||||
using TimePoint = dxvk::high_resolution_clock::time_point;
|
||||
|
||||
~Sleep();
|
||||
|
||||
/**
|
||||
* \brief Sleeps for a given period of time
|
||||
*
|
||||
* \param [in] t0 Current time
|
||||
* \param [in] duration Sleep duration
|
||||
* \returns Time after sleep has finished
|
||||
*/
|
||||
template<typename Rep, typename Period>
|
||||
static TimePoint sleepFor(TimePoint t0, std::chrono::duration<Rep, Period> duration) {
|
||||
return s_instance.sleep(t0, std::chrono::duration_cast<TimerDuration>(duration));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sleeps until a given time point
|
||||
*
|
||||
* Convenience function that sleeps for the
|
||||
* time difference between t1 and t0.
|
||||
* \param [in] t0 Current time
|
||||
* \param [in] t1 Target time
|
||||
* \returns Time after sleep has finished
|
||||
*/
|
||||
static TimePoint sleepUntil(TimePoint t0, TimePoint t1) {
|
||||
return sleepFor(t0, t1 - t0);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static Sleep s_instance;
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::atomic<bool> m_initialized = { false };
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, we use NtDelayExecution which has units of 100ns.
|
||||
using TimerDuration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
|
||||
using NtQueryTimerResolutionProc = UINT (WINAPI *) (ULONG*, ULONG*, ULONG*);
|
||||
using NtSetTimerResolutionProc = UINT (WINAPI *) (ULONG, BOOL, ULONG*);
|
||||
using NtDelayExecutionProc = UINT (WINAPI *) (BOOL, LARGE_INTEGER*);
|
||||
NtDelayExecutionProc NtDelayExecution = nullptr;
|
||||
#else
|
||||
// On other platforms, we use the std library, which calls through to nanosleep -- which is ns.
|
||||
using TimerDuration = std::chrono::nanoseconds;
|
||||
#endif
|
||||
|
||||
TimerDuration m_sleepGranularity = TimerDuration::zero();
|
||||
TimerDuration m_sleepThreshold = TimerDuration::zero();
|
||||
|
||||
Sleep();
|
||||
|
||||
void initialize();
|
||||
|
||||
void initializePlatformSpecifics();
|
||||
|
||||
TimePoint sleep(TimePoint t0, TimerDuration duration);
|
||||
|
||||
void systemSleep(TimerDuration duration);
|
||||
|
||||
};
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user