// Copyright 2008 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Common/Timer.h" #include #include #ifdef _WIN32 #include #include #else #include #endif #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" namespace Common { template static typename Clock::rep time_now() { return std::chrono::time_point_cast(Clock::now()).time_since_epoch().count(); } template static auto steady_time_now() { return time_now(); } u64 Timer::NowUs() { return steady_time_now(); } u64 Timer::NowMs() { return steady_time_now(); } void Timer::Start() { m_start_ms = NowMs(); m_end_ms = 0; m_running = true; } void Timer::StartWithOffset(u64 offset) { Start(); m_start_ms -= offset; } void Timer::Stop() { m_end_ms = NowMs(); m_running = false; } u64 Timer::ElapsedMs() const { const u64 end = m_running ? NowMs() : m_end_ms; // Can handle up to 1 rollover event (underflow produces correct result) // If Start() has never been called, will return 0 return end - m_start_ms; } u64 Timer::GetLocalTimeSinceJan1970() { #ifdef _MSC_VER std::chrono::zoned_seconds seconds( std::chrono::current_zone(), std::chrono::time_point_cast(std::chrono::system_clock::now())); return seconds.get_local_time().time_since_epoch().count(); #else time_t sysTime, tzDiff, tzDST; time(&sysTime); tm* gmTime = localtime(&sysTime); // Account for DST where needed if (gmTime->tm_isdst == 1) tzDST = 3600; else tzDST = 0; // Lazy way to get local time in sec gmTime = gmtime(&sysTime); tzDiff = sysTime - mktime(gmTime); return static_cast(sysTime + tzDiff + tzDST); #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 // Disable execution speed and timer resolution throttling process-wide. // This mainly will keep Dolphin marked as high performance if it's in the background. The OS // should make it high performance if it's in the foreground anyway (or for some specific // threads e.g. audio). // This is best-effort (i.e. the call may fail on older versions of Windows, where such throttling // doesn't exist, anyway), and we don't bother reverting once set. // This adjusts behavior on CPUs with "performance" and "efficiency" cores PROCESS_POWER_THROTTLING_STATE PowerThrottling{}; PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; PowerThrottling.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION; PowerThrottling.StateMask = 0; SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &PowerThrottling, sizeof(PowerThrottling)); // Not actually sure how useful this is these days.. :') timeBeginPeriod(TIMER_RESOLUTION_MS); #endif } void Timer::RestoreResolution() { #ifdef _WIN32 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