diff --git a/src/common/timer.cpp b/src/common/timer.cpp index e1dfceeef..822958b3a 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -9,6 +9,7 @@ #ifdef _WIN32 #include "windows_headers.h" #else +#include #include #include #include @@ -97,19 +98,18 @@ void Timer::SleepUntil(Value value, bool exact) { if (exact) { - for (;;) + // Even with the high-precision timer, it's not precise enough to wake us up *exactly* when we want + // to. Dropping off the last 0.5ms and spinning for it seems enough on my system (Win11 22H2). + const Value wake_at = value - ConvertMillisecondsToValue(0.5); + Value current = GetCurrentValue(); + if (wake_at > current) + SleepUntil(wake_at, false); + + // And spin off whatever time is left. + do { - Value current = GetCurrentValue(); - if (current >= value) - break; - - // spin for the last 1ms - if ((value - current) > ConvertMillisecondsToValue(1)) - { - SleepUntil(value, false); - continue; - } - } + current = GetCurrentValue(); + } while (current < value); } else { @@ -187,40 +187,52 @@ void Timer::SleepUntil(Value value, bool exact) { if (exact) { - for (;;) + static constexpr Value min_sleep_time = static_cast(0.5 * 1000000); + const Value wake_at = value - min_sleep_time; + Value current = GetCurrentValue(); + if (wake_at > current) + SleepUntil(wake_at, false); + + // And spin off whatever time is left. + do { - Value current = GetCurrentValue(); - if (current >= value) - break; - - static constexpr Value min_sleep_time = 1 * 1000000; - - // spin for the last 1ms - if ((value - current) > min_sleep_time) - { - SleepUntil(value, false); - continue; - } - } + current = GetCurrentValue(); + } while (current < value); } else { // Apple doesn't have TIMER_ABSTIME, so fall back to nanosleep in such a case. #ifdef __APPLE__ - const Value current_time = GetCurrentValue(); - if (value <= current_time) - return; + for (;;) + { + const Value current_time = GetCurrentValue(); + if (value <= current_time) + return; - const Value diff = value - current_time; - struct timespec ts; - ts.tv_sec = diff / UINT64_C(1000000000); - ts.tv_nsec = diff % UINT64_C(1000000000); - nanosleep(&ts, nullptr); + const Value diff = value - current_time; + struct timespec ts; + ts.tv_sec = diff / UINT64_C(1000000000); + ts.tv_nsec = diff % UINT64_C(1000000000); + + // nanosleep() can return EINTR if interrupted by a signal. + if (nanosleep(&ts, nullptr) == EINTR) + continue; + else + break; + } #else struct timespec ts; ts.tv_sec = value / UINT64_C(1000000000); ts.tv_nsec = value % UINT64_C(1000000000); - clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr); + + for (;;) + { + // clock_nanosleep() can return EINTR if interrupted by a signal. + if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr) == EINTR) + continue; + else + break; + } #endif } } diff --git a/src/core/system.cpp b/src/core/system.cpp index cd7103d3a..02f7ec198 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1542,10 +1542,13 @@ void System::Execute() s_presents_since_last_update++; } - System::UpdatePerformanceCounters(); - if (s_throttler_enabled) System::Throttle(); + + // Update perf counters *after* throttling, we want to measure from start-of-frame + // to start-of-frame, not end-of-frame to end-of-frame (will be noisy due to different + // amounts of computation happening in each frame). + System::UpdatePerformanceCounters(); } } @@ -2189,7 +2192,7 @@ void System::Throttle() // If we're running too slow, advance the next frame time based on the time we lost. Effectively skips // running those frames at the intended time, because otherwise if we pause in the debugger, we'll run // hundreds of frames when we resume. - const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); if (current_time > s_next_frame_time) { const Common::Timer::Value diff = static_cast(current_time) - static_cast(s_next_frame_time); @@ -2197,7 +2200,13 @@ void System::Throttle() return; } - Common::Timer::SleepUntil(s_next_frame_time, true); + // Use a spinwait if we undersleep for all platforms except android.. don't want to burn battery. + // Linux also seems to do a much better job of waking up at the requested time. +#if defined(__linux__) || defined(__ANDROID__) + Common::Timer::SleepUntil(s_next_frame_time, g_settings.display_all_frames); +#else + Common::Timer::SleepUntil(s_next_frame_time, false); +#endif } void System::RunFrames()