From e5c8935acc7fe72fe1d3331caf16c49354afc003 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 14 Mar 2025 18:16:39 -0500 Subject: [PATCH] Common: Create a PrecisionTimer class. --- Source/Core/Common/Timer.cpp | 71 +++++++++++++++++++++++++++++++++++- Source/Core/Common/Timer.h | 21 +++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/Timer.cpp b/Source/Core/Common/Timer.cpp index fb81164390..29febef6c5 100644 --- a/Source/Core/Common/Timer.cpp +++ b/Source/Core/Common/Timer.cpp @@ -4,6 +4,7 @@ #include "Common/Timer.h" #include +#include #ifdef _WIN32 #include @@ -13,6 +14,7 @@ #endif #include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" namespace Common { @@ -91,6 +93,10 @@ u64 Timer::GetLocalTimeSinceJan1970() #endif } +// This is requested by Timer::IncreaseResolution on Windows. +// On Linux/other we hope/assume we already have 1ms scheduling granularity. +static constexpr int TIMER_RESOLUTION_MS = 1; + void Timer::IncreaseResolution() { #ifdef _WIN32 @@ -110,15 +116,76 @@ void Timer::IncreaseResolution() sizeof(PowerThrottling)); // Not actually sure how useful this is these days.. :') - timeBeginPeriod(1); + timeBeginPeriod(TIMER_RESOLUTION_MS); #endif } void Timer::RestoreResolution() { #ifdef _WIN32 - timeEndPeriod(1); + timeEndPeriod(TIMER_RESOLUTION_MS); #endif } +PrecisionTimer::PrecisionTimer() +{ +#if defined(_WIN32) + // "TIMER_HIGH_RESOLUTION" requires Windows 10, version 1803, and later. + m_timer_handle = + CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + + if (m_timer_handle == NULL) + { + ERROR_LOG_FMT(COMMON, "CREATE_WAITABLE_TIMER_HIGH_RESOLUTION: Error:{}", GetLastError()); + + // Create a normal timer if "HIGH_RESOLUTION" isn't available. + m_timer_handle = CreateWaitableTimerExW(NULL, NULL, 0, TIMER_ALL_ACCESS); + if (m_timer_handle == NULL) + ERROR_LOG_FMT(COMMON, "CreateWaitableTimerExW: Error:{}", GetLastError()); + } +#endif +} + +PrecisionTimer::~PrecisionTimer() +{ +#if defined(_WIN32) + CloseHandle(m_timer_handle); +#endif +} + +void PrecisionTimer::SleepUntil(Clock::time_point target) +{ + constexpr auto SPIN_TIME = + std::chrono::milliseconds{TIMER_RESOLUTION_MS} + std::chrono::microseconds{20}; + +#if defined(_WIN32) + while (true) + { + // SetWaitableTimerEx takes time in "100 nanosecond intervals". + using TimerDuration = std::chrono::duration::type>; + + // Apparently waiting longer than the timer resolution gives terrible accuracy. + // We'll wait in steps of 95% of the time period. + constexpr auto MAX_TICKS = + duration_cast(std::chrono::milliseconds{TIMER_RESOLUTION_MS}) * 95 / 100; + + const auto wait_time = target - Clock::now() - SPIN_TIME; + const auto ticks = std::min(duration_cast(wait_time), MAX_TICKS).count(); + if (ticks <= 0) + break; + + const LARGE_INTEGER due_time{.QuadPart = -ticks}; + SetWaitableTimerEx(m_timer_handle, &due_time, 0, NULL, NULL, NULL, 0); + WaitForSingleObject(m_timer_handle, INFINITE); + } +#else + // Sleeping on Linux generally isn't as terrible as it is on Windows. + std::this_thread::sleep_until(target - SPIN_TIME); +#endif + + // Spin for the remaining time. + while (Clock::now() < target) + std::this_thread::yield(); +} + } // Namespace Common diff --git a/Source/Core/Common/Timer.h b/Source/Core/Common/Timer.h index 7298f4a368..7f36308e78 100644 --- a/Source/Core/Common/Timer.h +++ b/Source/Core/Common/Timer.h @@ -5,6 +5,10 @@ #include "Common/CommonTypes.h" +#ifdef _WIN32 +#include +#endif + namespace Common { class Timer @@ -32,4 +36,21 @@ private: bool m_running{false}; }; +class PrecisionTimer +{ +public: + PrecisionTimer(); + ~PrecisionTimer(); + + PrecisionTimer(const PrecisionTimer&) = delete; + PrecisionTimer& operator=(const PrecisionTimer&) = delete; + + void SleepUntil(Clock::time_point); + +private: +#ifdef _WIN32 + HANDLE m_timer_handle; +#endif +}; + } // Namespace Common