mirror of
https://github.com/doitsujin/dxvk.git
synced 2025-02-07 16:54:14 +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_luid.cpp',
|
||||||
'util_matrix.cpp',
|
'util_matrix.cpp',
|
||||||
'util_shared_res.cpp',
|
'util_shared_res.cpp',
|
||||||
|
'util_sleep.cpp',
|
||||||
|
|
||||||
'thread.cpp',
|
'thread.cpp',
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "util_env.h"
|
#include "util_env.h"
|
||||||
#include "util_fps_limiter.h"
|
#include "util_fps_limiter.h"
|
||||||
|
#include "util_sleep.h"
|
||||||
#include "util_string.h"
|
#include "util_string.h"
|
||||||
|
|
||||||
#include "./log/log.h"
|
#include "./log/log.h"
|
||||||
@ -63,7 +64,7 @@ namespace dxvk {
|
|||||||
// Don't call sleep if the amount of time to sleep is shorter
|
// Don't call sleep if the amount of time to sleep is shorter
|
||||||
// than the time the function calls are likely going to take
|
// than the time the function calls are likely going to take
|
||||||
TimerDuration sleepDuration = m_targetInterval - m_deviation - frameTime;
|
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
|
// Compensate for any sleep inaccuracies in the next frame, and
|
||||||
// limit cumulative deviation in order to avoid stutter in case we
|
// 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() {
|
void FpsLimiter::initialize() {
|
||||||
updateSleepGranularity();
|
|
||||||
m_sleepThreshold = 4 * m_sleepGranularity;
|
|
||||||
m_lastFrame = dxvk::high_resolution_clock::now();
|
m_lastFrame = dxvk::high_resolution_clock::now();
|
||||||
m_initialized = true;
|
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:
|
private:
|
||||||
|
|
||||||
using TimePoint = dxvk::high_resolution_clock::time_point;
|
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;
|
using TimerDuration = std::chrono::nanoseconds;
|
||||||
#endif
|
|
||||||
|
|
||||||
dxvk::mutex m_mutex;
|
dxvk::mutex m_mutex;
|
||||||
|
|
||||||
@ -71,17 +60,8 @@ namespace dxvk {
|
|||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
bool m_envOverride = false;
|
bool m_envOverride = false;
|
||||||
|
|
||||||
TimerDuration m_sleepGranularity = TimerDuration::zero();
|
|
||||||
TimerDuration m_sleepThreshold = TimerDuration::zero();
|
|
||||||
|
|
||||||
TimePoint sleep(TimePoint t0, TimerDuration duration);
|
|
||||||
|
|
||||||
void initialize();
|
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