diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 931e21d396..9987ac84e1 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -18,7 +18,6 @@ #include "Core/AchievementManager.h" #include "Core/CPUThreadConfigCallback.h" -#include "Core/Config/AchievementSettings.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" #include "Core/PowerPC/PowerPC.h" @@ -333,8 +332,6 @@ void CoreTimingManager::Advance() Event evt = std::move(m_event_queue.front()); std::ranges::pop_heap(m_event_queue, std::ranges::greater{}); m_event_queue.pop_back(); - - Throttle(evt.time); evt.type->callback(m_system, evt.userdata, m_globals.global_timer - evt.time); } @@ -356,15 +353,40 @@ void CoreTimingManager::Advance() power_pc.CheckExternalExceptions(); } +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 + { + return Clock::now(); + } +} + +void CoreTimingManager::SleepUntil(TimePoint time_point) +{ + const TimePoint time = Clock::now(); + + std::this_thread::sleep_until(time_point); + + if (Core::IsCPUThread()) + { + // Count amount of time sleeping for analytics + const TimePoint time_after_sleep = Clock::now(); + g_perf_metrics.CountThrottleSleep(time_after_sleep - time); + } +} + 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; - - // Prevent any throttling code if the amount of time passed is < ~0.122ms - if (cycles < m_throttle_min_clock_per_sleep) - return; - m_throttle_last_cycle = target_cycle; const double speed = Core::GetIsThrottlerTempDisabled() ? 0.0 : m_emulation_speed; @@ -442,7 +464,6 @@ void CoreTimingManager::LogPendingEvents() const void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock) { m_throttle_clock_per_sec = new_ppc_clock; - m_throttle_min_clock_per_sleep = new_ppc_clock / 1200; for (Event& ev : m_event_queue) { diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index f35812ba1a..23bd4c3239 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -16,7 +16,6 @@ // inside callback: // ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever") -#include #include #include #include @@ -99,6 +98,7 @@ public: // doing something evil u64 GetTicks() const; u64 GetIdleTicks() const; + TimePoint GetTargetHostTime(s64 target_cycle); void RefreshConfig(); @@ -156,10 +156,11 @@ public: Globals& GetGlobals() { return m_globals; } // Throttle the CPU to the specified target cycle. - // Never used outside of CoreTiming, however it remains public - // in order to allow custom throttling implementations to be tested. void Throttle(const s64 target_cycle); + // May be used from any thread. + void SleepUntil(TimePoint time_point); + TimePoint GetCPUTimePoint(s64 cyclesLate) const; // Used by Dolphin Analytics bool GetVISkip() const; // Used By VideoInterface @@ -203,7 +204,6 @@ private: s64 m_throttle_last_cycle = 0; TimePoint m_throttle_deadline = Clock::now(); s64 m_throttle_clock_per_sec = 0; - s64 m_throttle_min_clock_per_sleep = 0; bool m_throttle_disable_vi_int = false; DT m_max_fallback = {}; diff --git a/Source/Core/Core/HW/SystemTimers.cpp b/Source/Core/Core/HW/SystemTimers.cpp index 022b94bf4d..508b445288 100644 --- a/Source/Core/Core/HW/SystemTimers.cpp +++ b/Source/Core/Core/HW/SystemTimers.cpp @@ -125,6 +125,8 @@ void SystemTimersManager::GPUSleepCallback(Core::System& system, u64 userdata, s void SystemTimersManager::PerfTrackerCallback(Core::System& system, u64 userdata, s64 cycles_late) { auto& core_timing = system.GetCoreTiming(); + // Throttle for accurate performance metrics. + core_timing.Throttle(core_timing.GetTicks() - cycles_late); g_perf_metrics.CountPerformanceMarker(system, cycles_late); // Call this performance tracker again in 1/100th of a second. diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index bfd9b872c1..40f91e52f9 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -840,6 +840,11 @@ void VideoInterfaceManager::EndField(FieldType field, u64 ticks) if (!Config::Get(Config::GFX_HACK_EARLY_XFB_OUTPUT)) OutputField(field, ticks); + // Note: We really only need to Throttle prior to to presentation, + // but it is needed here if we want accurate "VBlank" statistics, + // when using GPU-on-Thread or Early/Immediate XFB. + m_system.GetCoreTiming().Throttle(ticks); + g_perf_metrics.CountVBlank(); VIEndFieldEvent::Trigger(); Core::OnFrameEnd(m_system); @@ -889,10 +894,13 @@ void VideoInterfaceManager::Update(u64 ticks) if (is_at_field_boundary) Core::Callback_NewField(m_system); - // If an SI poll is scheduled to happen on this half-line, do it! + auto& core_timing = m_system.GetCoreTiming(); + // If an SI poll is scheduled to happen on this half-line, do it! if (m_half_line_count == m_half_line_of_next_si_poll) { + // Throttle before SI poll so user input is taken just before needed. (lower input latency) + core_timing.Throttle(ticks); Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT), Config::Get(Config::MAIN_LOCK_CURSOR)); auto& si = m_system.GetSerialInterface(); @@ -918,7 +926,6 @@ void VideoInterfaceManager::Update(u64 ticks) m_half_line_count = 0; } - auto& core_timing = m_system.GetCoreTiming(); if (!(m_half_line_count & 1)) { m_ticks_last_line_start = core_timing.GetTicks(); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp index a63ca59ba9..8e89cc927f 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp @@ -347,10 +347,13 @@ void BluetoothEmuDevice::Update() wiimote->Update(); const u64 interval = GetSystem().GetSystemTimers().GetTicksPerSecond() / Wiimote::UPDATE_FREQ; - const u64 now = GetSystem().GetCoreTiming().GetTicks(); + auto& core_timing = GetSystem().GetCoreTiming(); + const u64 now = core_timing.GetTicks(); if (now - m_last_ticks > interval) { + // Throttle before Wii Remote update so input is taken just before needed. (lower input latency) + core_timing.Throttle(now); g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth); g_controller_interface.UpdateInput(); diff --git a/Source/Core/VideoCommon/AsyncRequests.cpp b/Source/Core/VideoCommon/AsyncRequests.cpp index e79ba51482..81b44ca042 100644 --- a/Source/Core/VideoCommon/AsyncRequests.cpp +++ b/Source/Core/VideoCommon/AsyncRequests.cpp @@ -125,7 +125,7 @@ void AsyncRequests::HandleEvent(const AsyncRequests::Event& e) case Event::SWAP_EVENT: g_presenter->ViSwap(e.swap_event.xfbAddr, e.swap_event.fbWidth, e.swap_event.fbStride, - e.swap_event.fbHeight, e.time); + e.swap_event.fbHeight, e.time, e.swap_event.presentation_time); break; case Event::BBOX_READ: diff --git a/Source/Core/VideoCommon/AsyncRequests.h b/Source/Core/VideoCommon/AsyncRequests.h index 1df6d91f0a..74d73c6cca 100644 --- a/Source/Core/VideoCommon/AsyncRequests.h +++ b/Source/Core/VideoCommon/AsyncRequests.h @@ -18,6 +18,8 @@ class AsyncRequests public: struct Event { + Event() {} + enum Type { EFB_POKE_COLOR, @@ -54,6 +56,7 @@ public: u32 fbWidth; u32 fbStride; u32 fbHeight; + TimePoint presentation_time; } swap_event; struct diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 8f88c90588..1d1a77b4ca 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -5,6 +5,7 @@ #include "Common/ChunkFile.h" #include "Core/Config/GraphicsSettings.h" +#include "Core/CoreTiming.h" #include "Core/HW/VideoInterface.h" #include "Core/Host.h" #include "Core/System.h" @@ -17,7 +18,6 @@ #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/OnScreenUI.h" #include "VideoCommon/PostProcessing.h" -#include "VideoCommon/Statistics.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/VideoEvents.h" @@ -157,7 +157,8 @@ bool Presenter::FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_heigh return old_xfb_id == m_last_xfb_id; } -void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks) +void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks, + TimePoint presentation_time) { bool is_duplicate = FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); @@ -198,7 +199,7 @@ void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs) { - Present(); + Present(presentation_time); ProcessFrameDumping(ticks); AfterPresentEvent::Trigger(present_info); @@ -814,7 +815,7 @@ void Presenter::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, } } -void Presenter::Present() +void Presenter::Present(std::optional presentation_time) { m_present_count++; @@ -867,6 +868,10 @@ void Presenter::Present() // Present to the window system. { std::lock_guard guard(m_swap_mutex); + + if (presentation_time.has_value()) + Core::System::GetInstance().GetCoreTiming().SleepUntil(*presentation_time); + g_gfx->PresentBackbuffer(); } diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index 3f8f43a687..a355af4385 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -14,7 +14,6 @@ #include #include #include -#include #include class AbstractTexture; @@ -36,10 +35,11 @@ public: Presenter(); virtual ~Presenter(); - void ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); + void ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks, + TimePoint presentation_time); void ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks); - void Present(); + void Present(std::optional presentation_time = std::nullopt); void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits::max(); } bool Initialize(); diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index ba63b335de..8150120610 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -19,6 +19,7 @@ #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" +#include "Core/CoreTiming.h" #include "Core/DolphinAnalytics.h" #include "Core/System.h" @@ -105,6 +106,7 @@ void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride e.swap_event.fbWidth = fb_width; e.swap_event.fbStride = fb_stride; e.swap_event.fbHeight = fb_height; + e.swap_event.presentation_time = system.GetCoreTiming().GetTargetHostTime(ticks); AsyncRequests::GetInstance()->PushEvent(e, false); } }