SystemTimers: export performance index from the throttler callback

This commit is contained in:
Pierre Bourdon 2018-08-27 02:34:09 +02:00
parent eadb4a66a5
commit 64e04eb38c
2 changed files with 128 additions and 67 deletions

View File

@ -45,6 +45,7 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
#include "Core/HW/SystemTimers.h" #include "Core/HW/SystemTimers.h"
#include <cfloat>
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
@ -68,36 +69,43 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
namespace SystemTimers namespace SystemTimers
{ {
static CoreTiming::EventType* et_Dec; namespace
static CoreTiming::EventType* et_VI; {
static CoreTiming::EventType* et_AudioDMA; CoreTiming::EventType* et_Dec;
static CoreTiming::EventType* et_DSP; CoreTiming::EventType* et_VI;
static CoreTiming::EventType* et_IPC_HLE; CoreTiming::EventType* et_AudioDMA;
CoreTiming::EventType* et_DSP;
CoreTiming::EventType* et_IPC_HLE;
// PatchEngine updates every 1/60th of a second by default // PatchEngine updates every 1/60th of a second by default
static CoreTiming::EventType* et_PatchEngine; CoreTiming::EventType* et_PatchEngine;
static CoreTiming::EventType* et_Throttle; CoreTiming::EventType* et_Throttle;
static u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!) u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!)
// These two are badly educated guesses. // These two are badly educated guesses.
// Feel free to experiment. Set them in Init below. // Feel free to experiment. Set them in Init below.
// This is a fixed value, don't change it // This is a fixed value, don't change it
static int s_audio_dma_period; int s_audio_dma_period;
// This is completely arbitrary. If we find that we need lower latency, // This is completely arbitrary. If we find that we need lower latency,
// we can just increase this number. // we can just increase this number.
static int s_ipc_hle_period; int s_ipc_hle_period;
// Custom RTC // Custom RTC
static s64 s_localtime_rtc_offset = 0; s64 s_localtime_rtc_offset = 0;
u32 GetTicksPerSecond() // 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.
return s_cpu_core_clock; 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. // DSP/CPU timeslicing.
static void DSPCallback(u64 userdata, s64 cyclesLate) void DSPCallback(u64 userdata, s64 cyclesLate)
{ {
// splits up the cycle budget in case lle is used // splits up the cycle budget in case lle is used
// for hle, just gives all of the slice to hle // for hle, just gives all of the slice to hle
@ -105,14 +113,14 @@ static void DSPCallback(u64 userdata, s64 cyclesLate)
CoreTiming::ScheduleEvent(DSP::GetDSPEmulator()->DSP_UpdateRate() - cyclesLate, et_DSP); CoreTiming::ScheduleEvent(DSP::GetDSPEmulator()->DSP_UpdateRate() - cyclesLate, et_DSP);
} }
static void AudioDMACallback(u64 userdata, s64 cyclesLate) void AudioDMACallback(u64 userdata, s64 cyclesLate)
{ {
int period = s_cpu_core_clock / (AudioInterface::GetAIDSampleRate() * 4 / 32); int period = s_cpu_core_clock / (AudioInterface::GetAIDSampleRate() * 4 / 32);
DSP::UpdateAudioDMA(); // Push audio to speakers. DSP::UpdateAudioDMA(); // Push audio to speakers.
CoreTiming::ScheduleEvent(period - cyclesLate, et_AudioDMA); CoreTiming::ScheduleEvent(period - cyclesLate, et_AudioDMA);
} }
static void IPC_HLE_UpdateCallback(u64 userdata, s64 cyclesLate) void IPC_HLE_UpdateCallback(u64 userdata, s64 cyclesLate)
{ {
if (SConfig::GetInstance().bWii) if (SConfig::GetInstance().bWii)
{ {
@ -121,18 +129,88 @@ static void IPC_HLE_UpdateCallback(u64 userdata, s64 cyclesLate)
} }
} }
static void VICallback(u64 userdata, s64 cyclesLate) void VICallback(u64 userdata, s64 cyclesLate)
{ {
VideoInterface::Update(CoreTiming::GetTicks() - cyclesLate); VideoInterface::Update(CoreTiming::GetTicks() - cyclesLate);
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerHalfLine() - cyclesLate, et_VI); CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerHalfLine() - cyclesLate, et_VI);
} }
static void DecrementerCallback(u64 userdata, s64 cyclesLate) void DecrementerCallback(u64 userdata, s64 cyclesLate)
{ {
PowerPC::ppcState.spr[SPR_DEC] = 0xFFFFFFFF; PowerPC::ppcState.spr[SPR_DEC] = 0xFFFFFFFF;
PowerPC::ppcState.Exceptions |= EXCEPTION_DECREMENTER; PowerPC::ppcState.Exceptions |= EXCEPTION_DECREMENTER;
} }
void PatchEngineCallback(u64 userdata, s64 cycles_late)
{
// We have 2 periods, a 1000 cycle error period and the VI period.
// We have to carefully combine these together so that we stay on the VI period without drifting.
u32 vi_interval = VideoInterface::GetTicksPerField();
s64 cycles_pruned = (userdata + cycles_late) % vi_interval;
s64 next_schedule = 0;
// Try to patch mem and run the Action Replay
if (PatchEngine::ApplyFramePatches())
{
next_schedule = vi_interval - cycles_pruned;
cycles_pruned = 0;
}
else
{
// The patch failed, usually because the CPU is in an inappropriate state (interrupt handler).
// We'll try again after 1000 cycles.
next_schedule = 1000;
cycles_pruned += next_schedule;
}
CoreTiming::ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned);
}
void ThrottleCallback(u64 last_time, s64 cyclesLate)
{
// Allow the GPU thread to sleep. Setting this flag here limits the wakeups to 1 kHz.
Fifo::GpuMaySleep();
u64 time = Common::Timer::GetTimeUs();
s64 diff = last_time - time;
const SConfig& config = SConfig::GetInstance();
bool frame_limiter = config.m_EmulationSpeed > 0.0f && !Core::GetIsThrottlerTempDisabled();
u32 next_event = GetTicksPerSecond() / 1000;
{
std::lock_guard<std::mutex> 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 (config.m_EmulationSpeed != 1.0f)
next_event = u32(next_event * config.m_EmulationSpeed);
const s64 max_fallback = config.iTimingVariance * 1000;
if (abs(diff) > max_fallback)
{
DEBUG_LOG(COMMON, "system too %s, %ld ms skipped", diff < 0 ? "slow" : "fast",
abs(diff) - max_fallback);
last_time = time - max_fallback;
}
else if (diff > 1000)
{
Common::SleepCurrentThread(diff / 1000);
s_time_spent_sleeping += Common::Timer::GetTimeUs() - time;
}
}
CoreTiming::ScheduleEvent(next_event - cyclesLate, et_Throttle, last_time + 1000);
}
} // namespace
u32 GetTicksPerSecond()
{
return s_cpu_core_clock;
}
void DecrementerSet() void DecrementerSet()
{ {
u32 decValue = PowerPC::ppcState.spr[SPR_DEC]; u32 decValue = PowerPC::ppcState.spr[SPR_DEC];
@ -170,57 +248,29 @@ s64 GetLocalTimeRTCOffset()
return s_localtime_rtc_offset; return s_localtime_rtc_offset;
} }
static void PatchEngineCallback(u64 userdata, s64 cycles_late) double GetEstimatedEmulationPerformance()
{ {
// We have 2 periods, a 1000 cycle error period and the VI period. u64 ts_now, ts_before; // In microseconds
// We have to carefully combine these together so that we stay on the VI period without drifting. {
u32 vi_interval = VideoInterface::GetTicksPerField(); std::lock_guard<std::mutex> lk(s_emu_to_real_time_mutex);
s64 cycles_pruned = (userdata + cycles_late) % vi_interval; size_t index_now = s_emu_to_real_time_index == 0 ? s_emu_to_real_time_ring_buffer.size() :
s64 next_schedule = 0; s_emu_to_real_time_index - 1;
size_t index_before = s_emu_to_real_time_index;
// Try to patch mem and run the Action Replay ts_now = s_emu_to_real_time_ring_buffer[index_now];
if (PatchEngine::ApplyFramePatches()) ts_before = s_emu_to_real_time_ring_buffer[index_before];
{
next_schedule = vi_interval - cycles_pruned;
cycles_pruned = 0;
}
else
{
// The patch failed, usually because the CPU is in an inappropriate state (interrupt handler).
// We'll try again after 1000 cycles.
next_schedule = 1000;
cycles_pruned += next_schedule;
} }
CoreTiming::ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned); if (ts_before == 0)
}
static void ThrottleCallback(u64 last_time, s64 cyclesLate)
{
// Allow the GPU thread to sleep. Setting this flag here limits the wakeups to 1 kHz.
Fifo::GpuMaySleep();
u64 time = Common::Timer::GetTimeUs();
s64 diff = last_time - time;
const SConfig& config = SConfig::GetInstance();
bool frame_limiter = config.m_EmulationSpeed > 0.0f && !Core::GetIsThrottlerTempDisabled();
u32 next_event = GetTicksPerSecond() / 1000;
if (frame_limiter)
{ {
if (config.m_EmulationSpeed != 1.0f) // Not enough data yet to estimate. We could technically provide an estimate based on a shorter
next_event = u32(next_event * config.m_EmulationSpeed); // time horizon, but it's not really worth it.
const s64 max_fallback = config.iTimingVariance * 1000; return 1.0;
if (abs(diff) > max_fallback)
{
DEBUG_LOG(COMMON, "system too %s, %d ms skipped", diff < 0 ? "slow" : "fast",
abs(diff) - max_fallback);
last_time = time - max_fallback;
}
else if ((diff / 1000) > 0)
Common::SleepCurrentThread(diff / 1000);
} }
CoreTiming::ScheduleEvent(next_event - cyclesLate, et_Throttle, last_time + 1000);
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;
} }
// split from Init to break a circular dependency between VideoInterface::Init and // split from Init to break a circular dependency between VideoInterface::Init and
@ -288,6 +338,8 @@ void Init()
if (SConfig::GetInstance().bWii) if (SConfig::GetInstance().bWii)
CoreTiming::ScheduleEvent(s_ipc_hle_period, et_IPC_HLE); CoreTiming::ScheduleEvent(s_ipc_hle_period, et_IPC_HLE);
s_emu_to_real_time_ring_buffer.fill(0);
} }
void Shutdown() void Shutdown()
@ -296,4 +348,4 @@ void Shutdown()
s_localtime_rtc_offset = 0; s_localtime_rtc_offset = 0;
} }
} // namespace } // namespace SystemTimers

View File

@ -53,4 +53,13 @@ void TimeBaseSet();
u64 GetFakeTimeBase(); u64 GetFakeTimeBase();
// Custom RTC // Custom RTC
s64 GetLocalTimeRTCOffset(); s64 GetLocalTimeRTCOffset();
}
// Returns an estimate of how fast/slow the emulation is running (excluding throttling induced sleep
// time). The estimate is computed over the last 1s of emulated time. Example values:
//
// - 0.5: the emulator is running at 50% speed (falling behind).
// - 1.0: the emulator is running at 100% speed.
// - 2.0: the emulator is running at 200% speed (or 100% speed but sleeping half of the time).
double GetEstimatedEmulationPerformance();
} // namespace SystemTimers