Skip to content
Permalink
Browse files
Merge pull request #11348 from Sam-Belliveau/improved-pacing
CoreTiming: Throttle Before Every Event Using std::chrono
  • Loading branch information
JMC47 committed Jan 14, 2023
2 parents f4f9439 + e849172 commit 8a1cac9
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 140 deletions.
@@ -162,7 +162,7 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples)
memset(samples, 0, num_samples * 2 * sizeof(short));

// TODO: Determine how emulation speed will be used in audio
// const float emulation_speed = std::roundf(g_perf_metrics.GetSpeed()) / 100.f;
// const float emulation_speed = g_perf_metrics.GetSpeed();
const float emulation_speed = m_config_emulation_speed;
const int timing_variance = m_config_timing_variance;
if (m_config_audio_stretch)
@@ -891,9 +891,9 @@ void Callback_NewField()

void UpdateTitle()
{
float FPS = g_perf_metrics.GetFPS();
float VPS = g_perf_metrics.GetVPS();
float Speed = g_perf_metrics.GetSpeed();
const double FPS = g_perf_metrics.GetFPS();
const double VPS = g_perf_metrics.GetVPS();
const double Speed = 100.0 * g_perf_metrics.GetSpeed();

// Settings are shown the same for both extended and summary info
const std::string SSettings = fmt::format(
@@ -22,6 +22,7 @@
#include "Core/System.h"

#include "VideoCommon/Fifo.h"
#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/VideoBackendBase.h"

namespace CoreTiming
@@ -99,6 +100,10 @@ void CoreTimingManager::Init()
// that slice.
m_is_global_timer_sane = true;

// Reset data used by the throttling system
m_throttle_last_cycle = 0;
m_throttle_deadline = Clock::now();

m_event_fifo_id = 0;
m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
}
@@ -305,6 +310,8 @@ void CoreTimingManager::Advance()
Event evt = std::move(m_event_queue.front());
std::pop_heap(m_event_queue.begin(), m_event_queue.end(), std::greater<Event>());
m_event_queue.pop_back();

Throttle(evt.time);
evt.type->callback(system, evt.userdata, m_globals.global_timer - evt.time);
}

@@ -326,6 +333,61 @@ void CoreTimingManager::Advance()
PowerPC::CheckExternalExceptions();
}

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 : Config::Get(Config::MAIN_EMULATION_SPEED);

if (0.0 < speed)
m_throttle_deadline +=
std::chrono::duration_cast<DT>(DT_s(cycles) / (speed * m_throttle_clock_per_sec));

// A maximum fallback is used to prevent the system from sleeping for
// too long or going full speed in an attempt to catch up to timings.
const DT max_fallback =
std::chrono::duration_cast<DT>(DT_ms(Config::Get(Config::MAIN_TIMING_VARIANCE)));

const TimePoint time = Clock::now();
const TimePoint min_deadline = time - max_fallback;
const TimePoint max_deadline = time + max_fallback;

if (m_throttle_deadline > max_deadline)
{
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;
}

// Only sleep if we are behind the deadline
if (time < m_throttle_deadline)
{
std::this_thread::sleep_until(m_throttle_deadline);

// Count amount of time sleeping for analytics
const TimePoint time_after_sleep = Clock::now();
g_perf_metrics.CountThrottleSleep(time_after_sleep - time);
}
}

TimePoint CoreTimingManager::GetCPUTimePoint(s64 cyclesLate) const
{
return TimePoint(std::chrono::duration_cast<DT>(DT_s(m_globals.global_timer - cyclesLate) /
m_throttle_clock_per_sec));
}

void CoreTimingManager::LogPendingEvents() const
{
auto clone = m_event_queue;
@@ -340,6 +402,9 @@ 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)
{
m_throttle_clock_per_sec = new_ppc_clock;
m_throttle_min_clock_per_sleep = new_ppc_clock / 1200;

for (Event& ev : m_event_queue)
{
const s64 ticks = (ev.time - m_globals.global_timer) * new_ppc_clock / old_ppc_clock;
@@ -140,6 +140,13 @@ class CoreTimingManager
// Directly accessed by the JIT.
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);

TimePoint GetCPUTimePoint(s64 cyclesLate) const; // Used by Dolphin Analytics

private:
Globals m_globals;

@@ -173,6 +180,11 @@ class CoreTimingManager
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;
s64 m_throttle_min_clock_per_sleep;

int DowncountToCycles(int downcount) const;
int CyclesToDowncount(int cycles) const;
};
@@ -67,6 +67,7 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/PerformanceMetrics.h"

namespace SystemTimers
{
@@ -77,9 +78,10 @@ CoreTiming::EventType* et_VI;
CoreTiming::EventType* et_AudioDMA;
CoreTiming::EventType* et_DSP;
CoreTiming::EventType* et_IPC_HLE;
CoreTiming::EventType* et_GPU_sleeper;
CoreTiming::EventType* et_perf_tracker;
// PatchEngine updates every 1/60th of a second by default
CoreTiming::EventType* et_PatchEngine;
CoreTiming::EventType* et_Throttle;

u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!)

@@ -90,16 +92,6 @@ int s_ipc_hle_period;
// Custom RTC
s64 s_localtime_rtc_offset = 0;

// For each emulated milliseconds, what was the real time timestamp (excluding sleep time). This is
// a "special" ring buffer where we only need to read the first and last value.
std::array<u64, 1000> s_emu_to_real_time_ring_buffer;
size_t s_emu_to_real_time_index;
std::mutex s_emu_to_real_time_mutex;

// How much time was spent sleeping since the emulator started. Note: this does not need to be reset
// at initialization (or ever), since only the "derivative" of that value really matters.
u64 s_time_spent_sleeping;

// DSP/CPU timeslicing.
void DSPCallback(Core::System& system, u64 userdata, s64 cyclesLate)
{
@@ -132,6 +124,26 @@ void IPC_HLE_UpdateCallback(Core::System& system, u64 userdata, s64 cyclesLate)
}
}

void GPUSleepCallback(Core::System& system, u64 userdata, s64 cyclesLate)
{
auto& core_timing = system.GetCoreTiming();
system.GetFifo().GpuMaySleep();

// We want to call GpuMaySleep at about 1000hz so
// that the thread can sleep while not doing anything.
core_timing.ScheduleEvent(GetTicksPerSecond() / 1000 - cyclesLate, et_GPU_sleeper);
}

void PerfTrackerCallback(Core::System& system, u64 userdata, s64 cyclesLate)
{
auto& core_timing = system.GetCoreTiming();
g_perf_metrics.CountPerformanceMarker(system, cyclesLate);

// Call this performance tracker again in 1/64th of a second.
// The tracker stores 256 values so this will let us summarize the last 4 seconds.
core_timing.ScheduleEvent(GetTicksPerSecond() / 64 - cyclesLate, et_perf_tracker);
}

void VICallback(Core::System& system, u64 userdata, s64 cyclesLate)
{
auto& core_timing = system.GetCoreTiming();
@@ -169,50 +181,6 @@ void PatchEngineCallback(Core::System& system, u64 userdata, s64 cycles_late)

system.GetCoreTiming().ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned);
}

void ThrottleCallback(Core::System& system, u64 deadline, s64 cyclesLate)
{
// Allow the GPU thread to sleep. Setting this flag here limits the wakeups to 1 kHz.
system.GetFifo().GpuMaySleep();

const u64 time = Common::Timer::NowUs();

if (deadline == 0)
deadline = time;

const s64 diff = deadline - time;
const float emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
const bool frame_limiter = emulation_speed > 0.0f && !Core::GetIsThrottlerTempDisabled();
u32 next_event = GetTicksPerSecond() / 1000;

{
std::lock_guard lk(s_emu_to_real_time_mutex);
s_emu_to_real_time_ring_buffer[s_emu_to_real_time_index] = time - s_time_spent_sleeping;
s_emu_to_real_time_index =
(s_emu_to_real_time_index + 1) % s_emu_to_real_time_ring_buffer.size();
}

if (frame_limiter)
{
if (emulation_speed != 1.0f)
next_event = u32(next_event * emulation_speed);
const s64 max_fallback = Config::Get(Config::MAIN_TIMING_VARIANCE) * 1000;
if (std::abs(diff) > max_fallback)
{
DEBUG_LOG_FMT(COMMON, "system too {}, {} us skipped", diff < 0 ? "slow" : "fast",
std::abs(diff) - max_fallback);
deadline = time - max_fallback;
}
else if (diff > 1000)
{
Common::SleepCurrentThread(diff / 1000);
s_time_spent_sleeping += Common::Timer::NowUs() - time;
}
}
// reschedule 1ms (possibly scaled by emulation_speed) into future on ppc
// add 1ms to the deadline
system.GetCoreTiming().ScheduleEvent(next_event - cyclesLate, et_Throttle, deadline + 1000);
}
} // namespace

u32 GetTicksPerSecond()
@@ -268,27 +236,7 @@ s64 GetLocalTimeRTCOffset()

double GetEstimatedEmulationPerformance()
{
u64 ts_now, ts_before; // In microseconds
{
std::lock_guard lk(s_emu_to_real_time_mutex);
size_t index_now = s_emu_to_real_time_index == 0 ? s_emu_to_real_time_ring_buffer.size() - 1 :
s_emu_to_real_time_index - 1;
size_t index_before = s_emu_to_real_time_index;

ts_now = s_emu_to_real_time_ring_buffer[index_now];
ts_before = s_emu_to_real_time_ring_buffer[index_before];
}

if (ts_before == 0)
{
// Not enough data yet to estimate. We could technically provide an estimate based on a shorter
// time horizon, but it's not really worth it.
return 1.0;
}

u64 delta_us = ts_now - ts_before;
double emulated_us = s_emu_to_real_time_ring_buffer.size() * 1000.0; // For each emulated ms.
return delta_us == 0 ? DBL_MAX : emulated_us / delta_us;
return g_perf_metrics.GetMaxSpeed();
}

// split from Init to break a circular dependency between VideoInterface::Init and
@@ -345,20 +293,20 @@ void Init()
et_DSP = core_timing.RegisterEvent("DSPCallback", DSPCallback);
et_AudioDMA = core_timing.RegisterEvent("AudioDMACallback", AudioDMACallback);
et_IPC_HLE = core_timing.RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback);
et_GPU_sleeper = core_timing.RegisterEvent("GPUSleeper", GPUSleepCallback);
et_perf_tracker = core_timing.RegisterEvent("PerfTracker", PerfTrackerCallback);
et_PatchEngine = core_timing.RegisterEvent("PatchEngine", PatchEngineCallback);
et_Throttle = core_timing.RegisterEvent("Throttle", ThrottleCallback);

core_timing.ScheduleEvent(0, et_perf_tracker);
core_timing.ScheduleEvent(0, et_GPU_sleeper);
core_timing.ScheduleEvent(VideoInterface::GetTicksPerHalfLine(), et_VI);
core_timing.ScheduleEvent(0, et_DSP);
core_timing.ScheduleEvent(GetAudioDMACallbackPeriod(), et_AudioDMA);
core_timing.ScheduleEvent(0, et_Throttle, 0);

core_timing.ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine);

if (SConfig::GetInstance().bWii)
core_timing.ScheduleEvent(s_ipc_hle_period, et_IPC_HLE);

s_emu_to_real_time_ring_buffer.fill(0);
}

void Shutdown()

0 comments on commit 8a1cac9

Please sign in to comment.