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);
}
}