diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 82d0db9f7c..b0c8f809d1 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -81,12 +81,6 @@ void CoreTimingManager::UnregisterAllEvents() void CoreTimingManager::Init() { - m_registered_config_callback_id = - CPUThreadConfigCallback::AddConfigChangedCallback([this]() { RefreshConfig(); }); - RefreshConfig(); - - m_last_oc_factor = m_config_oc_factor; - m_globals.last_OC_factor_inverted = m_config_oc_inv_factor; m_system.GetPPCState().downcount = CyclesToDowncount(MAX_SLICE_LENGTH); m_globals.slice_length = MAX_SLICE_LENGTH; m_globals.global_timer = 0; @@ -103,6 +97,13 @@ void CoreTimingManager::Init() m_event_fifo_id = 0; m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback); + + m_registered_config_callback_id = + CPUThreadConfigCallback::AddConfigChangedCallback([this]() { RefreshConfig(); }); + RefreshConfig(); + + m_last_oc_factor = m_config_oc_factor; + m_globals.last_OC_factor_inverted = m_config_oc_inv_factor; } void CoreTimingManager::Shutdown() @@ -132,11 +133,10 @@ void CoreTimingManager::RefreshConfig() Config::Get(Config::MAIN_EMULATION_SPEED) > 0.0f) { Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0f); - m_emulation_speed = 1.0f; OSD::AddMessage("Minimum speed is 100% in Hardcore Mode"); } - m_emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED); + UpdateSpeedLimit(GetTicks(), Config::Get(Config::MAIN_EMULATION_SPEED)); m_use_precision_timer = Config::Get(Config::MAIN_PRECISION_FRAME_TIMING); } @@ -355,20 +355,24 @@ void CoreTimingManager::Advance() power_pc.CheckExternalExceptions(); } +TimePoint CoreTimingManager::CalculateTargetHostTimeInternal(s64 target_cycle) +{ + const s64 elapsed_cycles = target_cycle - m_throttle_reference_cycle; + return m_throttle_reference_time + + Clock::duration{std::chrono::seconds{elapsed_cycles}} / m_throttle_adj_clock_per_sec; +} + +bool CoreTimingManager::IsSpeedUnlimited() const +{ + return m_throttle_adj_clock_per_sec == 0 || Core::GetIsThrottlerTempDisabled(); +} + TimePoint CoreTimingManager::GetTargetHostTime(s64 target_cycle) { - const double speed = Core::GetIsThrottlerTempDisabled() ? 0.0 : m_emulation_speed; - - if (speed > 0) - { - const s64 cycles = target_cycle - m_throttle_last_cycle; - return m_throttle_deadline + std::chrono::duration_cast
( - DT_s(cycles) / (m_emulation_speed * m_throttle_clock_per_sec)); - } - else - { + if (IsSpeedUnlimited()) return Clock::now(); - } + + return CalculateTargetHostTimeInternal(target_cycle); } void CoreTimingManager::SleepUntil(TimePoint time_point) @@ -399,44 +403,70 @@ void CoreTimingManager::SleepUntil(TimePoint time_point) void CoreTimingManager::Throttle(const s64 target_cycle) { - // Based on number of cycles and emulation speed, increase the target deadline - const s64 cycles = target_cycle - m_throttle_last_cycle; - m_throttle_last_cycle = target_cycle; + if (IsSpeedUnlimited()) + { + ResetThrottle(target_cycle); + m_throttle_disable_vi_int = false; + return; + } - const double speed = Core::GetIsThrottlerTempDisabled() ? 0.0 : m_emulation_speed; + // Push throttle reference values forward by exact seconds. + // This avoids drifting from cumulative rounding errors. + { + const s64 sec_adj = (target_cycle - m_throttle_reference_cycle) / m_throttle_adj_clock_per_sec; + const s64 cycle_adj = sec_adj * m_throttle_adj_clock_per_sec; - if (0.0 < speed) - m_throttle_deadline += - std::chrono::duration_cast
(DT_s(cycles) / (speed * m_throttle_clock_per_sec)); + m_throttle_reference_cycle += cycle_adj; + m_throttle_reference_time += std::chrono::seconds{sec_adj}; + } + + TimePoint target_time = CalculateTargetHostTimeInternal(target_cycle); const TimePoint time = Clock::now(); - const TimePoint min_deadline = time - m_max_fallback; - const TimePoint max_deadline = time + m_max_fallback; - if (m_throttle_deadline > max_deadline) + const TimePoint min_target = time - m_max_fallback; + if (target_time < min_target) { - m_throttle_deadline = max_deadline; - } - else if (m_throttle_deadline < min_deadline) - { - DEBUG_LOG_FMT(COMMON, "System can not to keep up with timings! [relaxing timings by {} us]", - DT_us(min_deadline - m_throttle_deadline).count()); - m_throttle_deadline = min_deadline; - } + // Core is running too slow.. i.e. CPU bottleneck. + const DT adjustment = min_target - target_time; + DEBUG_LOG_FMT(CORE, "Core can not keep up with timings! [relaxing timings by {} us]", + DT_us(adjustment).count()); - const TimePoint vi_deadline = time - std::min(m_max_fallback, m_max_variance) / 2; + m_throttle_reference_time += adjustment; + target_time += adjustment; + } // Skip the VI interrupt if the CPU is lagging by a certain amount. // It doesn't matter what amount of lag we skip VI at, as long as it's constant. - m_throttle_disable_vi_int = 0.0 < speed && m_throttle_deadline < vi_deadline; + const TimePoint vi_target = time - std::min(m_max_fallback, m_max_variance) / 2; + m_throttle_disable_vi_int = target_time < vi_target; - SleepUntil(m_throttle_deadline); + SleepUntil(target_time); +} + +void CoreTimingManager::UpdateSpeedLimit(s64 cycle, double new_speed) +{ + m_emulation_speed = new_speed; + + const u32 new_clock_per_sec = + std::lround(m_system.GetSystemTimers().GetTicksPerSecond() * new_speed); + + const bool was_limited = m_throttle_adj_clock_per_sec != 0; + if (was_limited) + { + // Adjust throttle reference for graceful clock speed transition. + const s64 ticks = cycle - m_throttle_reference_cycle; + const s64 new_ticks = ticks * new_clock_per_sec / m_throttle_adj_clock_per_sec; + m_throttle_reference_cycle = cycle - new_ticks; + } + + m_throttle_adj_clock_per_sec = new_clock_per_sec; } void CoreTimingManager::ResetThrottle(s64 cycle) { - m_throttle_last_cycle = cycle; - m_throttle_deadline = Clock::now(); + m_throttle_reference_cycle = cycle; + m_throttle_reference_time = Clock::now(); } bool CoreTimingManager::GetVISkip() const @@ -463,14 +493,16 @@ void CoreTimingManager::LogPendingEvents() const // Should only be called from the CPU thread after the PPC clock has changed void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock) { - g_perf_metrics.AdjustClockSpeed(m_globals.global_timer, new_ppc_clock, old_ppc_clock); + const s64 ticks = m_globals.global_timer; - m_throttle_clock_per_sec = new_ppc_clock; + UpdateSpeedLimit(ticks, m_emulation_speed); + + g_perf_metrics.AdjustClockSpeed(ticks, new_ppc_clock, old_ppc_clock); for (Event& ev : m_event_queue) { - const s64 ticks = (ev.time - m_globals.global_timer) * new_ppc_clock / old_ppc_clock; - ev.time = m_globals.global_timer + ticks; + const s64 ev_ticks = (ev.time - ticks) * new_ppc_clock / old_ppc_clock; + ev.time = ticks + ev_ticks; } } diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index f0ed02ea64..9de1282e04 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -202,16 +202,19 @@ private: float m_config_oc_inv_factor = 0.0f; bool m_config_sync_on_skip_idle = false; - s64 m_throttle_last_cycle = 0; - TimePoint m_throttle_deadline = Clock::now(); - s64 m_throttle_clock_per_sec = 0; + s64 m_throttle_reference_cycle = 0; + TimePoint m_throttle_reference_time = Clock::now(); + u32 m_throttle_adj_clock_per_sec = 0; bool m_throttle_disable_vi_int = false; DT m_max_fallback = {}; DT m_max_variance = {}; double m_emulation_speed = 1.0; + bool IsSpeedUnlimited() const; + void UpdateSpeedLimit(s64 cycle, double new_speed); void ResetThrottle(s64 cycle); + TimePoint CalculateTargetHostTimeInternal(s64 target_cycle); int DowncountToCycles(int downcount) const; int CyclesToDowncount(int cycles) const;