mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-28 01:49:33 -06:00
Merge pull request #13387 from jordan-woyak/frame-pacing
CoreTiming: Improve frame pacing
This commit is contained in:
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include "Core/AchievementManager.h"
|
#include "Core/AchievementManager.h"
|
||||||
#include "Core/CPUThreadConfigCallback.h"
|
#include "Core/CPUThreadConfigCallback.h"
|
||||||
#include "Core/Config/AchievementSettings.h"
|
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
@ -333,8 +332,6 @@ void CoreTimingManager::Advance()
|
|||||||
Event evt = std::move(m_event_queue.front());
|
Event evt = std::move(m_event_queue.front());
|
||||||
std::ranges::pop_heap(m_event_queue, std::ranges::greater{});
|
std::ranges::pop_heap(m_event_queue, std::ranges::greater{});
|
||||||
m_event_queue.pop_back();
|
m_event_queue.pop_back();
|
||||||
|
|
||||||
Throttle(evt.time);
|
|
||||||
evt.type->callback(m_system, evt.userdata, m_globals.global_timer - 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();
|
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>(
|
||||||
|
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)
|
void CoreTimingManager::Throttle(const s64 target_cycle)
|
||||||
{
|
{
|
||||||
// Based on number of cycles and emulation speed, increase the target deadline
|
// Based on number of cycles and emulation speed, increase the target deadline
|
||||||
const s64 cycles = target_cycle - m_throttle_last_cycle;
|
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;
|
m_throttle_last_cycle = target_cycle;
|
||||||
|
|
||||||
const double speed = Core::GetIsThrottlerTempDisabled() ? 0.0 : m_emulation_speed;
|
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)
|
void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
|
||||||
{
|
{
|
||||||
m_throttle_clock_per_sec = new_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)
|
for (Event& ev : m_event_queue)
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
// inside callback:
|
// inside callback:
|
||||||
// ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
|
// ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
|
||||||
|
|
||||||
#include <compare>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@ -99,6 +98,7 @@ public:
|
|||||||
// doing something evil
|
// doing something evil
|
||||||
u64 GetTicks() const;
|
u64 GetTicks() const;
|
||||||
u64 GetIdleTicks() const;
|
u64 GetIdleTicks() const;
|
||||||
|
TimePoint GetTargetHostTime(s64 target_cycle);
|
||||||
|
|
||||||
void RefreshConfig();
|
void RefreshConfig();
|
||||||
|
|
||||||
@ -156,10 +156,11 @@ public:
|
|||||||
Globals& GetGlobals() { return m_globals; }
|
Globals& GetGlobals() { return m_globals; }
|
||||||
|
|
||||||
// Throttle the CPU to the specified target cycle.
|
// 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);
|
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
|
TimePoint GetCPUTimePoint(s64 cyclesLate) const; // Used by Dolphin Analytics
|
||||||
bool GetVISkip() const; // Used By VideoInterface
|
bool GetVISkip() const; // Used By VideoInterface
|
||||||
|
|
||||||
@ -203,7 +204,6 @@ private:
|
|||||||
s64 m_throttle_last_cycle = 0;
|
s64 m_throttle_last_cycle = 0;
|
||||||
TimePoint m_throttle_deadline = Clock::now();
|
TimePoint m_throttle_deadline = Clock::now();
|
||||||
s64 m_throttle_clock_per_sec = 0;
|
s64 m_throttle_clock_per_sec = 0;
|
||||||
s64 m_throttle_min_clock_per_sleep = 0;
|
|
||||||
bool m_throttle_disable_vi_int = false;
|
bool m_throttle_disable_vi_int = false;
|
||||||
|
|
||||||
DT m_max_fallback = {};
|
DT m_max_fallback = {};
|
||||||
|
@ -125,6 +125,8 @@ void SystemTimersManager::GPUSleepCallback(Core::System& system, u64 userdata, s
|
|||||||
void SystemTimersManager::PerfTrackerCallback(Core::System& system, u64 userdata, s64 cycles_late)
|
void SystemTimersManager::PerfTrackerCallback(Core::System& system, u64 userdata, s64 cycles_late)
|
||||||
{
|
{
|
||||||
auto& core_timing = system.GetCoreTiming();
|
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);
|
g_perf_metrics.CountPerformanceMarker(system, cycles_late);
|
||||||
|
|
||||||
// Call this performance tracker again in 1/100th of a second.
|
// Call this performance tracker again in 1/100th of a second.
|
||||||
|
@ -840,6 +840,11 @@ void VideoInterfaceManager::EndField(FieldType field, u64 ticks)
|
|||||||
if (!Config::Get(Config::GFX_HACK_EARLY_XFB_OUTPUT))
|
if (!Config::Get(Config::GFX_HACK_EARLY_XFB_OUTPUT))
|
||||||
OutputField(field, ticks);
|
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();
|
g_perf_metrics.CountVBlank();
|
||||||
VIEndFieldEvent::Trigger();
|
VIEndFieldEvent::Trigger();
|
||||||
Core::OnFrameEnd(m_system);
|
Core::OnFrameEnd(m_system);
|
||||||
@ -889,10 +894,13 @@ void VideoInterfaceManager::Update(u64 ticks)
|
|||||||
if (is_at_field_boundary)
|
if (is_at_field_boundary)
|
||||||
Core::Callback_NewField(m_system);
|
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)
|
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),
|
Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT),
|
||||||
Config::Get(Config::MAIN_LOCK_CURSOR));
|
Config::Get(Config::MAIN_LOCK_CURSOR));
|
||||||
auto& si = m_system.GetSerialInterface();
|
auto& si = m_system.GetSerialInterface();
|
||||||
@ -918,7 +926,6 @@ void VideoInterfaceManager::Update(u64 ticks)
|
|||||||
m_half_line_count = 0;
|
m_half_line_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& core_timing = m_system.GetCoreTiming();
|
|
||||||
if (!(m_half_line_count & 1))
|
if (!(m_half_line_count & 1))
|
||||||
{
|
{
|
||||||
m_ticks_last_line_start = core_timing.GetTicks();
|
m_ticks_last_line_start = core_timing.GetTicks();
|
||||||
|
@ -347,10 +347,13 @@ void BluetoothEmuDevice::Update()
|
|||||||
wiimote->Update();
|
wiimote->Update();
|
||||||
|
|
||||||
const u64 interval = GetSystem().GetSystemTimers().GetTicksPerSecond() / Wiimote::UPDATE_FREQ;
|
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)
|
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.SetCurrentInputChannel(ciface::InputChannel::Bluetooth);
|
||||||
g_controller_interface.UpdateInput();
|
g_controller_interface.UpdateInput();
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ void AsyncRequests::HandleEvent(const AsyncRequests::Event& e)
|
|||||||
|
|
||||||
case Event::SWAP_EVENT:
|
case Event::SWAP_EVENT:
|
||||||
g_presenter->ViSwap(e.swap_event.xfbAddr, e.swap_event.fbWidth, e.swap_event.fbStride,
|
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;
|
break;
|
||||||
|
|
||||||
case Event::BBOX_READ:
|
case Event::BBOX_READ:
|
||||||
|
@ -18,6 +18,8 @@ class AsyncRequests
|
|||||||
public:
|
public:
|
||||||
struct Event
|
struct Event
|
||||||
{
|
{
|
||||||
|
Event() {}
|
||||||
|
|
||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
EFB_POKE_COLOR,
|
EFB_POKE_COLOR,
|
||||||
@ -54,6 +56,7 @@ public:
|
|||||||
u32 fbWidth;
|
u32 fbWidth;
|
||||||
u32 fbStride;
|
u32 fbStride;
|
||||||
u32 fbHeight;
|
u32 fbHeight;
|
||||||
|
TimePoint presentation_time;
|
||||||
} swap_event;
|
} swap_event;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Core/Config/GraphicsSettings.h"
|
#include "Core/Config/GraphicsSettings.h"
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/HW/VideoInterface.h"
|
#include "Core/HW/VideoInterface.h"
|
||||||
#include "Core/Host.h"
|
#include "Core/Host.h"
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
@ -17,7 +18,6 @@
|
|||||||
#include "VideoCommon/FramebufferManager.h"
|
#include "VideoCommon/FramebufferManager.h"
|
||||||
#include "VideoCommon/OnScreenUI.h"
|
#include "VideoCommon/OnScreenUI.h"
|
||||||
#include "VideoCommon/PostProcessing.h"
|
#include "VideoCommon/PostProcessing.h"
|
||||||
#include "VideoCommon/Statistics.h"
|
|
||||||
#include "VideoCommon/VertexManagerBase.h"
|
#include "VideoCommon/VertexManagerBase.h"
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
#include "VideoCommon/VideoEvents.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;
|
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);
|
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)
|
if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs)
|
||||||
{
|
{
|
||||||
Present();
|
Present(presentation_time);
|
||||||
ProcessFrameDumping(ticks);
|
ProcessFrameDumping(ticks);
|
||||||
|
|
||||||
AfterPresentEvent::Trigger(present_info);
|
AfterPresentEvent::Trigger(present_info);
|
||||||
@ -814,7 +815,7 @@ void Presenter::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Presenter::Present()
|
void Presenter::Present(std::optional<TimePoint> presentation_time)
|
||||||
{
|
{
|
||||||
m_present_count++;
|
m_present_count++;
|
||||||
|
|
||||||
@ -867,6 +868,10 @@ void Presenter::Present()
|
|||||||
// Present to the window system.
|
// Present to the window system.
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||||
|
|
||||||
|
if (presentation_time.has_value())
|
||||||
|
Core::System::GetInstance().GetCoreTiming().SleepUntil(*presentation_time);
|
||||||
|
|
||||||
g_gfx->PresentBackbuffer();
|
g_gfx->PresentBackbuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <span>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
class AbstractTexture;
|
class AbstractTexture;
|
||||||
@ -36,10 +35,11 @@ public:
|
|||||||
Presenter();
|
Presenter();
|
||||||
virtual ~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 ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
|
||||||
|
|
||||||
void Present();
|
void Present(std::optional<TimePoint> presentation_time = std::nullopt);
|
||||||
void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits<u64>::max(); }
|
void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits<u64>::max(); }
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/DolphinAnalytics.h"
|
#include "Core/DolphinAnalytics.h"
|
||||||
#include "Core/System.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.fbWidth = fb_width;
|
||||||
e.swap_event.fbStride = fb_stride;
|
e.swap_event.fbStride = fb_stride;
|
||||||
e.swap_event.fbHeight = fb_height;
|
e.swap_event.fbHeight = fb_height;
|
||||||
|
e.swap_event.presentation_time = system.GetCoreTiming().GetTargetHostTime(ticks);
|
||||||
AsyncRequests::GetInstance()->PushEvent(e, false);
|
AsyncRequests::GetInstance()->PushEvent(e, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user