@@ -45,6 +45,7 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
#include " Core/HW/SystemTimers.h"
#include < cfloat>
#include < cmath>
#include < cstdlib>
@@ -68,51 +69,58 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
namespace SystemTimers
{
static CoreTiming::EventType* et_Dec;
static CoreTiming::EventType* et_VI;
static CoreTiming::EventType* et_AudioDMA;
static CoreTiming::EventType* et_DSP;
static CoreTiming::EventType* et_IPC_HLE;
namespace
{
CoreTiming::EventType* et_Dec;
CoreTiming::EventType* et_VI;
CoreTiming::EventType* et_AudioDMA;
CoreTiming::EventType* et_DSP;
CoreTiming::EventType* et_IPC_HLE;
// PatchEngine updates every 1/60th of a second by default
static CoreTiming::EventType* et_PatchEngine;
static CoreTiming::EventType* et_Throttle;
CoreTiming::EventType* et_PatchEngine;
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.
// Feel free to experiment. Set them in Init below.
// 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,
// we can just increase this number.
static int s_ipc_hle_period;
int s_ipc_hle_period;
// Custom RTC
static s64 s_localtime_rtc_offset = 0 ;
s64 s_localtime_rtc_offset = 0 ;
u32 GetTicksPerSecond ()
{
return s_cpu_core_clock;
}
// 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.
static void DSPCallback (u64 userdata, s64 cyclesLate)
void DSPCallback (u64 userdata, s64 cyclesLate)
{
// splits up the cycle budget in case lle is used
// for hle, just gives all of the slice to hle
DSP::UpdateDSPSlice (static_cast <int >(DSP::GetDSPEmulator ()->DSP_UpdateRate () - cyclesLate));
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 );
DSP::UpdateAudioDMA (); // Push audio to speakers.
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 )
{
@@ -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);
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.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 ()
{
u32 decValue = PowerPC::ppcState.spr [SPR_DEC];
@@ -170,57 +248,29 @@ s64 GetLocalTimeRTCOffset()
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.
// 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 ())
u64 ts_now, ts_before; // In microseconds
{
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;
}
std::lock_guard<std::mutex> 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 () :
s_emu_to_real_time_index - 1 ;
size_t index_before = s_emu_to_real_time_index;
CoreTiming::ScheduleEvent (next_schedule, et_PatchEngine, cycles_pruned);
}
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 ();
u32 time = Common::Timer::GetTimeMs ();
ts_now = s_emu_to_real_time_ring_buffer[index_now];
ts_before = s_emu_to_real_time_ring_buffer[index_before];
}
int diff = (u32)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 (ts_before == 0 )
{
if (config.m_EmulationSpeed != 1 .0f )
next_event = u32 (next_event * config.m_EmulationSpeed );
const int max_fallback = config.iTimingVariance ;
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 > 0 )
Common::SleepCurrentThread (diff);
// 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 ;
}
CoreTiming::ScheduleEvent (next_event - cyclesLate, et_Throttle, last_time + 1 );
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
@@ -282,12 +332,14 @@ void Init()
CoreTiming::ScheduleEvent (VideoInterface::GetTicksPerHalfLine (), et_VI);
CoreTiming::ScheduleEvent (0 , et_DSP);
CoreTiming::ScheduleEvent (s_audio_dma_period, et_AudioDMA);
CoreTiming::ScheduleEvent (0 , et_Throttle, Common::Timer::GetTimeMs ());
CoreTiming::ScheduleEvent (0 , et_Throttle, Common::Timer::GetTimeUs ());
CoreTiming::ScheduleEvent (VideoInterface::GetTicksPerField (), et_PatchEngine);
if (SConfig::GetInstance ().bWii )
CoreTiming::ScheduleEvent (s_ipc_hle_period, et_IPC_HLE);
s_emu_to_real_time_ring_buffer.fill (0 );
}
void Shutdown ()
@@ -296,4 +348,4 @@ void Shutdown()
s_localtime_rtc_offset = 0 ;
}
} // namespace
} // namespace SystemTimers