From 27697d05083db7d7f95378386fa03da1afbd5b55 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 29 Sep 2020 23:29:28 +1000 Subject: [PATCH] System: Implement CPU overclocking [SAVEVERSION+] Partial credit to @CookiePLMonster as well. --- src/core/cdrom.cpp | 26 ++++++++++------ src/core/cdrom.h | 2 ++ src/core/cpu_core.cpp | 2 +- src/core/cpu_core.h | 2 +- src/core/gpu.cpp | 25 ++++++++++++---- src/core/gpu.h | 2 ++ src/core/host_interface.cpp | 8 +++++ src/core/memory_card.cpp | 12 +++++--- src/core/memory_card.h | 3 +- src/core/save_state_version.h | 2 +- src/core/settings.cpp | 30 +++++++++++++++++++ src/core/settings.h | 10 +++++++ src/core/spu.cpp | 38 ++++++++++++++++++++---- src/core/spu.h | 6 +++- src/core/system.cpp | 56 ++++++++++++++++++++++++++++++++++- src/core/system.h | 38 ++++++++++++++++++++++++ src/core/timers.cpp | 15 ++++++++-- src/core/timers.h | 5 +++- src/core/types.h | 3 -- 19 files changed, 249 insertions(+), 36 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index d1937d1e5..ed6fe40e7 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -298,6 +298,13 @@ void CDROM::SetUseReadThread(bool enabled) m_reader.StopThread(); } +void CDROM::CPUClockChanged() +{ + // reschedule the disc read event + if (IsReadingOrPlaying()) + m_drive_event->SetInterval(GetTicksForRead()); +} + u8 CDROM::ReadRegister(u32 offset) { switch (offset) @@ -612,23 +619,24 @@ TickCount CDROM::GetAckDelayForCommand(Command command) TickCount CDROM::GetTicksForRead() { - return m_mode.double_speed ? (MASTER_CLOCK / 150) : (MASTER_CLOCK / 75); + const TickCount tps = System::GetTicksPerSecond(); + return m_mode.double_speed ? (tps / 150) : (tps / 75); } TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) { + const TickCount tps = System::GetTicksPerSecond(); const CDImage::LBA current_lba = m_secondary_status.motor_on ? m_current_lba : 0; const u32 lba_diff = static_cast((new_lba > current_lba) ? (new_lba - current_lba) : (current_lba - new_lba)); // Formula from Mednafen. TickCount ticks = std::max( 20000, static_cast( - ((static_cast(lba_diff) * static_cast(MASTER_CLOCK) * static_cast(1000)) / (72 * 60 * 75)) / - 1000)); + ((static_cast(lba_diff) * static_cast(tps) * static_cast(1000)) / (72 * 60 * 75)) / 1000)); if (!m_secondary_status.motor_on) - ticks += MASTER_CLOCK; + ticks += tps; if (lba_diff >= 2550) - ticks += static_cast(u64(MASTER_CLOCK) * 300 / 1000); + ticks += static_cast((u64(tps) * 300) / 1000); else { // When paused, the CDC seems to keep reading the disc until it hits the position it's set to, then skip 10-15 @@ -644,7 +652,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) m_current_double_speed = m_mode.double_speed; // Approximate time for the motor to change speed? - ticks += static_cast(static_cast(MASTER_CLOCK) * 0.1); + ticks += static_cast(static_cast(tps) * 0.1); } Log_DevPrintf("Seek time for %u LBAs: %d", lba_diff, ticks); @@ -788,7 +796,7 @@ void CDROM::ExecuteCommand() SendACKAndStat(); m_drive_state = DriveState::ReadingTOC; - m_drive_event->Schedule(MASTER_CLOCK / 2); // half a second + m_drive_event->Schedule(System::GetTicksPerSecond() / 2); // half a second } EndCommand(); @@ -874,7 +882,7 @@ void CDROM::ExecuteCommand() m_async_command_parameter = session; m_drive_state = DriveState::ChangingSession; - m_drive_event->Schedule(MASTER_CLOCK / 2); // half a second + m_drive_event->Schedule(System::GetTicksPerSecond() / 2); // half a second } EndCommand(); @@ -1011,7 +1019,7 @@ void CDROM::ExecuteCommand() SendACKAndStat(); m_drive_state = DriveState::Resetting; - m_drive_event->Schedule(MASTER_CLOCK); + m_drive_event->Schedule(System::GetTicksPerSecond()); } EndCommand(); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 4311f900d..9c088c643 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -31,6 +31,8 @@ public: void InsertMedia(std::unique_ptr media); std::unique_ptr RemoveMedia(bool force = false); + void CPUClockChanged(); + // I/O u8 ReadRegister(u32 offset); void WriteRegister(u32 offset, u8 value); diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 30d7e1e3f..7a5ecf3e7 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -67,7 +67,7 @@ void Shutdown() void Reset() { g_state.pending_ticks = 0; - g_state.downcount = MAX_SLICE_SIZE; + g_state.downcount = 0; g_state.regs = {}; diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index 43c14c99a..2fb7be2ab 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -51,7 +51,7 @@ struct State { // ticks the CPU has executed TickCount pending_ticks = 0; - TickCount downcount = MAX_SLICE_SIZE; + TickCount downcount = 0; Registers regs = {}; Cop0Registers cop0_regs = {}; diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 850ff57d4..ebfd0559d 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -57,6 +57,11 @@ void GPU::UpdateSettings() UpdateCRTCDisplayParameters(); } +void GPU::CPUClockChanged() +{ + UpdateCRTCConfig(); +} + void GPU::UpdateResolutionScale() {} std::tuple GPU::GetEffectiveDisplayResolution() @@ -427,8 +432,9 @@ float GPU::ComputeHorizontalFrequency() const { const CRTCState& cs = m_crtc_state; TickCount fractional_ticks = 0; - return static_cast(static_cast(SystemTicksToCRTCTicks(MASTER_CLOCK, &fractional_ticks)) / - static_cast(cs.horizontal_total)); + return static_cast( + static_cast(SystemTicksToCRTCTicks(System::GetTicksPerSecond(), &fractional_ticks)) / + static_cast(cs.horizontal_total)); } float GPU::ComputeVerticalFrequency() const @@ -436,8 +442,9 @@ float GPU::ComputeVerticalFrequency() const const CRTCState& cs = m_crtc_state; const TickCount ticks_per_frame = cs.horizontal_total * cs.vertical_total; TickCount fractional_ticks = 0; - return static_cast(static_cast(SystemTicksToCRTCTicks(MASTER_CLOCK, &fractional_ticks)) / - static_cast(ticks_per_frame)); + return static_cast( + static_cast(SystemTicksToCRTCTicks(System::GetTicksPerSecond(), &fractional_ticks)) / + static_cast(ticks_per_frame)); } float GPU::GetDisplayAspectRatio() const @@ -459,7 +466,7 @@ void GPU::UpdateCRTCConfig() cs.current_scanline %= PAL_TOTAL_LINES; cs.horizontal_total = PAL_TICKS_PER_LINE; cs.horizontal_sync_start = PAL_HSYNC_TICKS; - cs.current_tick_in_scanline %= PAL_TICKS_PER_LINE; + cs.current_tick_in_scanline %= System::ScaleTicksToOverclock(PAL_TICKS_PER_LINE); } else { @@ -467,7 +474,7 @@ void GPU::UpdateCRTCConfig() cs.current_scanline %= NTSC_TOTAL_LINES; cs.horizontal_total = NTSC_TICKS_PER_LINE; cs.horizontal_sync_start = NTSC_HSYNC_TICKS; - cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; + cs.current_tick_in_scanline %= System::ScaleTicksToOverclock(NTSC_TICKS_PER_LINE); } cs.in_hblank = (cs.current_tick_in_scanline >= cs.horizontal_sync_start); @@ -498,6 +505,12 @@ void GPU::UpdateCRTCConfig() cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; } + cs.horizontal_display_start = + static_cast(System::ScaleTicksToOverclock(static_cast(cs.horizontal_display_start))); + cs.horizontal_display_end = + static_cast(System::ScaleTicksToOverclock(static_cast(cs.horizontal_display_end))); + cs.horizontal_total = static_cast(System::ScaleTicksToOverclock(static_cast(cs.horizontal_total))); + System::SetThrottleFrequency(ComputeVerticalFrequency()); UpdateCRTCDisplayParameters(); diff --git a/src/core/gpu.h b/src/core/gpu.h index 61721c911..45e08f9d8 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -132,6 +132,8 @@ public: // Render statistics debug window. void DrawDebugStateWindow(); + void CPUClockChanged(); + // MMIO access u32 ReadRegister(u32 offset); void WriteRegister(u32 offset, u32 value); diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index afacd4bb4..8fc1d44b0 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -543,6 +543,14 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) { if (System::IsValid()) { + if (g_settings.cpu_overclock_active != old_settings.cpu_overclock_active || + (g_settings.cpu_overclock_active && + (g_settings.cpu_overclock_numerator != old_settings.cpu_overclock_numerator || + g_settings.cpu_overclock_denominator != old_settings.cpu_overclock_denominator))) + { + System::UpdateOverclock(); + } + if (g_settings.gpu_renderer != old_settings.gpu_renderer || g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device) { diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp index 60666c63e..3a506ca38 100644 --- a/src/core/memory_card.cpp +++ b/src/core/memory_card.cpp @@ -13,9 +13,8 @@ MemoryCard::MemoryCard() { m_FLAG.no_write_yet = true; - m_save_event = - TimingEvents::CreateTimingEvent("Memory Card Host Flush", SAVE_DELAY_IN_SYSCLK_TICKS, SAVE_DELAY_IN_SYSCLK_TICKS, - std::bind(&MemoryCard::SaveIfChanged, this, true), false); + m_save_event = TimingEvents::CreateTimingEvent("Memory Card Host Flush", GetSaveDelayInTicks(), GetSaveDelayInTicks(), + std::bind(&MemoryCard::SaveIfChanged, this, true), false); } MemoryCard::~MemoryCard() @@ -23,6 +22,11 @@ MemoryCard::~MemoryCard() SaveIfChanged(false); } +TickCount MemoryCard::GetSaveDelayInTicks() +{ + return System::GetTicksPerSecond() * SAVE_DELAY_IN_SECONDS; +} + void MemoryCard::Reset() { ResetTransferState(); @@ -309,5 +313,5 @@ void MemoryCard::QueueFileSave() return; // save in one second, that should be long enough for everything to finish writing - m_save_event->Schedule(SAVE_DELAY_IN_SYSCLK_TICKS); + m_save_event->Schedule(GetSaveDelayInTicks()); } diff --git a/src/core/memory_card.h b/src/core/memory_card.h index 3f50e6c2e..c8cd6f014 100644 --- a/src/core/memory_card.h +++ b/src/core/memory_card.h @@ -35,7 +35,6 @@ private: { // save in three seconds, that should be long enough for everything to finish writing SAVE_DELAY_IN_SECONDS = 5, - SAVE_DELAY_IN_SYSCLK_TICKS = MASTER_CLOCK * SAVE_DELAY_IN_SECONDS, }; union FLAG @@ -74,6 +73,8 @@ private: WriteEnd, }; + static TickCount GetSaveDelayInTicks(); + bool LoadFromFile(); bool SaveIfChanged(bool display_osd_message); void QueueFileSave(); diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h index 9e67dffab..fdada360f 100644 --- a/src/core/save_state_version.h +++ b/src/core/save_state_version.h @@ -2,7 +2,7 @@ #include "types.h" static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; -static constexpr u32 SAVE_STATE_VERSION = 41; +static constexpr u32 SAVE_STATE_VERSION = 42; #pragma pack(push, 4) struct SAVE_STATE_HEADER diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 50fb0ce3e..e4abe54cb 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,6 +5,7 @@ #include "host_interface.h" #include #include +#include Settings g_settings; @@ -75,6 +76,28 @@ bool Settings::HasAnyPerGameMemoryCards() const }); } +void Settings::CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator) +{ + const u32 percent_gcd = std::gcd(percent, 100); + *numerator = percent / percent_gcd; + *denominator = 100u / percent_gcd; +} + +u32 Settings::CPUOverclockFractionToPercent(u32 numerator, u32 denominator) +{ + return (numerator * 100u) / denominator; +} + +void Settings::SetCPUOverclockPercent(u32 percent) +{ + CPUOverclockPercentToFraction(percent, &cpu_overclock_numerator, &cpu_overclock_denominator); +} + +u32 Settings::GetCPUOverclockPercent() const +{ + return CPUOverclockFractionToPercent(cpu_overclock_numerator, cpu_overclock_denominator); +} + void Settings::Load(SettingsInterface& si) { region = @@ -95,6 +118,10 @@ void Settings::Load(SettingsInterface& si) ParseCPUExecutionMode( si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str()) .value_or(DEFAULT_CPU_EXECUTION_MODE); + cpu_overclock_numerator = std::max(si.GetIntValue("CPU", "OverclockNumerator", 1), 1); + cpu_overclock_denominator = std::max(si.GetIntValue("CPU", "OverclockDenominator", 1), 1); + cpu_overclock_enable = si.GetBoolValue("CPU", "OverclockEnable", false); + cpu_overclock_active = (cpu_overclock_enable && (cpu_overclock_numerator != 1 || cpu_overclock_denominator != 1)); cpu_recompiler_memory_exceptions = si.GetBoolValue("CPU", "RecompilerMemoryExceptions", false); cpu_recompiler_icache = si.GetBoolValue("CPU", "RecompilerICache", false); @@ -218,6 +245,9 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Main", "AutoLoadCheats", auto_load_cheats); si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); + si.SetBoolValue("CPU", "OverclockEnable", cpu_overclock_enable); + si.SetIntValue("CPU", "OverclockNumerator", cpu_overclock_numerator); + si.SetIntValue("CPU", "OverclockDenominator", cpu_overclock_denominator); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); si.SetBoolValue("CPU", "RecompilerICache", cpu_recompiler_icache); diff --git a/src/core/settings.h b/src/core/settings.h index 690621c53..5d4c72b77 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -70,6 +70,10 @@ struct Settings ConsoleRegion region = ConsoleRegion::Auto; CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter; + u32 cpu_overclock_numerator = 1; + u32 cpu_overclock_denominator = 1; + bool cpu_overclock_enable = false; + bool cpu_overclock_active = false; bool cpu_recompiler_memory_exceptions = false; bool cpu_recompiler_icache = false; @@ -174,6 +178,12 @@ struct Settings bool HasAnyPerGameMemoryCards() const; + static void CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator); + static u32 CPUOverclockFractionToPercent(u32 numerator, u32 denominator); + + void SetCPUOverclockPercent(u32 percent); + u32 GetCPUOverclockPercent() const; + enum : u32 { DEFAULT_DMA_MAX_SLICE_TICKS = 1000, diff --git a/src/core/spu.cpp b/src/core/spu.cpp index ecb8b57a6..40d322db1 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -21,7 +21,10 @@ SPU::~SPU() = default; void SPU::Initialize() { - m_tick_event = TimingEvents::CreateTimingEvent("SPU Sample", SYSCLK_TICKS_PER_SPU_TICK, SYSCLK_TICKS_PER_SPU_TICK, + // (X * D) / N / 768 -> (X * D) / (N * 768) + m_cpu_ticks_per_spu_tick = System::ScaleTicksToOverclock(SYSCLK_TICKS_PER_SPU_TICK); + m_cpu_tick_divider = static_cast(g_settings.cpu_overclock_numerator * SYSCLK_TICKS_PER_SPU_TICK); + m_tick_event = TimingEvents::CreateTimingEvent("SPU Sample", m_cpu_ticks_per_spu_tick, m_cpu_ticks_per_spu_tick, std::bind(&SPU::Execute, this, std::placeholders::_1), false); m_transfer_event = TimingEvents::CreateTimingEvent("SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, @@ -30,6 +33,15 @@ void SPU::Initialize() Reset(); } +void SPU::CPUClockChanged() +{ + // (X * D) / N / 768 -> (X * D) / (N * 768) + m_cpu_ticks_per_spu_tick = System::ScaleTicksToOverclock(SYSCLK_TICKS_PER_SPU_TICK); + m_cpu_tick_divider = static_cast(g_settings.cpu_overclock_numerator * SYSCLK_TICKS_PER_SPU_TICK); + m_ticks_carry = 0; + UpdateEventInterval(); +} + void SPU::Shutdown() { m_tick_event.reset(); @@ -680,8 +692,19 @@ void SPU::IncrementCaptureBufferPosition() void SPU::Execute(TickCount ticks) { - u32 remaining_frames = static_cast((ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK); - m_ticks_carry = (ticks + m_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK; + u32 remaining_frames; + if (g_settings.cpu_overclock_active) + { + // (X * D) / N / 768 -> (X * D) / (N * 768) + const u64 num = (static_cast(ticks) * g_settings.cpu_overclock_denominator) + static_cast(m_ticks_carry); + remaining_frames = static_cast(num / m_cpu_tick_divider); + m_ticks_carry = static_cast(num % m_cpu_tick_divider); + } + else + { + remaining_frames = static_cast((ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK); + m_ticks_carry = (ticks + m_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK; + } while (remaining_frames > 0) { @@ -796,14 +819,19 @@ void SPU::UpdateEventInterval() // TODO: Make this predict how long until the interrupt will be hit instead... const u32 interval = (m_SPUCNT.enable && m_SPUCNT.irq9_enable) ? 1 : max_slice_frames; - const TickCount interval_ticks = static_cast(interval) * SYSCLK_TICKS_PER_SPU_TICK; + const TickCount interval_ticks = static_cast(interval) * m_cpu_ticks_per_spu_tick; if (m_tick_event->IsActive() && m_tick_event->GetInterval() == interval_ticks) return; // Ensure all pending ticks have been executed, since we won't get them back after rescheduling. m_tick_event->InvokeEarly(true); m_tick_event->SetInterval(interval_ticks); - m_tick_event->Schedule(interval_ticks - m_ticks_carry); + + TickCount downcount = interval_ticks; + if (!g_settings.cpu_overclock_active) + downcount -= m_ticks_carry; + + m_tick_event->Schedule(downcount); } void SPU::ExecuteTransfer(TickCount ticks) diff --git a/src/core/spu.h b/src/core/spu.h index 88f6fb613..2b88c1062 100644 --- a/src/core/spu.h +++ b/src/core/spu.h @@ -2,6 +2,7 @@ #include "common/bitfield.h" #include "common/fifo_queue.h" #include "types.h" +#include "system.h" #include #include @@ -20,6 +21,7 @@ public: ~SPU(); void Initialize(); + void CPUClockChanged(); void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -54,7 +56,7 @@ private: static constexpr u32 VOICE_ADDRESS_SHIFT = 3; static constexpr u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; static constexpr u32 SAMPLE_RATE = 44100; - static constexpr u32 SYSCLK_TICKS_PER_SPU_TICK = MASTER_CLOCK / SAMPLE_RATE; // 0x300 + static constexpr u32 SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE; // 0x300 static constexpr s16 ENVELOPE_MIN_VOLUME = 0; static constexpr s16 ENVELOPE_MAX_VOLUME = 0x7FFF; static constexpr u32 CAPTURE_BUFFER_SIZE_PER_CHANNEL = 0x400; @@ -370,6 +372,8 @@ private: std::unique_ptr m_transfer_event; std::unique_ptr m_dump_writer; TickCount m_ticks_carry = 0; + TickCount m_cpu_ticks_per_spu_tick = 0; + TickCount m_cpu_tick_divider = 0; SPUCNT m_SPUCNT = {}; SPUSTAT m_SPUSTAT = {}; diff --git a/src/core/system.cpp b/src/core/system.cpp index b861fada4..30cf5149b 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -68,6 +68,8 @@ static void UpdateRunningGame(const char* path, CDImage* image); static State s_state = State::Shutdown; static ConsoleRegion s_region = ConsoleRegion::NTSC_U; +TickCount g_ticks_per_second = MASTER_CLOCK; +static TickCount s_max_slice_ticks = MASTER_CLOCK / 10; static u32 s_frame_number = 1; static u32 s_internal_frame_number = 1; @@ -143,6 +145,22 @@ bool IsPALRegion() return s_region == ConsoleRegion::PAL; } +TickCount GetMaxSliceTicks() +{ + return s_max_slice_ticks; +} + +void UpdateOverclock() +{ + g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK); + s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10); + g_spu.CPUClockChanged(); + g_cdrom.CPUClockChanged(); + g_gpu->CPUClockChanged(); + g_timers.CPUClocksChanged(); + UpdateThrottlePeriod(); +} + u32 GetFrameNumber() { return s_frame_number; @@ -682,6 +700,8 @@ bool Boot(const SystemBootParameters& params) bool Initialize(bool force_software_renderer) { + g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK); + s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10); s_frame_number = 1; s_internal_frame_number = 1; @@ -725,6 +745,15 @@ bool Initialize(bool force_software_renderer) g_mdec.Initialize(); g_sio.Initialize(); + if (g_settings.cpu_overclock_active) + { + g_host_interface->AddFormattedOSDMessage( + 10.0f, + g_host_interface->TranslateString("OSDMessage", + "CPU clock speed is set to %u%% (%u / %u). This may result in instability."), + g_settings.GetCPUOverclockPercent(), g_settings.cpu_overclock_numerator, g_settings.cpu_overclock_denominator); + } + UpdateThrottlePeriod(); return true; } @@ -845,6 +874,31 @@ bool DoState(StateWrapper& sw) if (!sw.DoMarker("Events") || !TimingEvents::DoState(sw)) return false; + if (!sw.DoMarker("Overclock")) + return false; + + bool cpu_overclock_active = g_settings.cpu_overclock_active; + u32 cpu_overclock_numerator = g_settings.cpu_overclock_numerator; + u32 cpu_overclock_denominator = g_settings.cpu_overclock_denominator; + sw.Do(&cpu_overclock_active); + sw.Do(&cpu_overclock_numerator); + sw.Do(&cpu_overclock_denominator); + + if (sw.IsReading() && (cpu_overclock_active != g_settings.cpu_overclock_active || + (cpu_overclock_active && (g_settings.cpu_overclock_numerator != cpu_overclock_numerator || + g_settings.cpu_overclock_denominator != cpu_overclock_denominator)))) + { + g_host_interface->AddFormattedOSDMessage( + 10.0f, + g_host_interface->TranslateString("OSDMessage", + "WARNING: CPU overclock (%u%%) was different in save state (%u%%)."), + g_settings.cpu_overclock_enable ? g_settings.GetCPUOverclockPercent() : 100u, + cpu_overclock_active ? + Settings::CPUOverclockFractionToPercent(cpu_overclock_numerator, cpu_overclock_denominator) : + 100u); + UpdateOverclock(); + } + return !sw.HasError(); } @@ -1186,7 +1240,7 @@ void UpdatePerformanceCounters() s_fps = static_cast(s_internal_frame_number - s_last_internal_frame_number) / time; s_last_internal_frame_number = s_internal_frame_number; s_speed = static_cast(static_cast(global_tick_counter - s_last_global_tick_counter) / - (static_cast(MASTER_CLOCK) * time)) * + (static_cast(g_ticks_per_second) * time)) * 100.0f; s_last_global_tick_counter = global_tick_counter; s_fps_timer.Reset(); diff --git a/src/core/system.h b/src/core/system.h index 6b0085099..1971936a2 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -1,6 +1,7 @@ #pragma once #include "common/timer.h" #include "host_interface.h" +#include "settings.h" #include "timing_event.h" #include "types.h" #include @@ -40,6 +41,11 @@ enum : u32 MAX_SAVE_STATE_SIZE = 5 * 1024 * 1024 }; +enum : TickCount +{ + MASTER_CLOCK = 44100 * 0x300 // 33868800Hz or 33.8688MHz, also used as CPU clock +}; + enum class State { Shutdown, @@ -48,6 +54,8 @@ enum class State Paused }; +extern TickCount g_ticks_per_second; + /// Returns true if the filename is a PlayStation executable we can inject. bool IsExeFileName(const char* path); @@ -80,6 +88,36 @@ bool IsValid(); ConsoleRegion GetRegion(); bool IsPALRegion(); + +ALWAYS_INLINE TickCount GetTicksPerSecond() +{ + return g_ticks_per_second; +} + +ALWAYS_INLINE_RELEASE TickCount ScaleTicksToOverclock(TickCount ticks) +{ + if (!g_settings.cpu_overclock_active) + return ticks; + + return static_cast((static_cast(static_cast(ticks)) * g_settings.cpu_overclock_numerator) / + g_settings.cpu_overclock_denominator); +} + +ALWAYS_INLINE_RELEASE TickCount UnscaleTicksToOverclock(TickCount ticks, TickCount* remainder) +{ + if (!g_settings.cpu_overclock_active) + return ticks; + + const u64 num = + (static_cast(ticks) * static_cast(g_settings.cpu_overclock_denominator)) + static_cast(*remainder); + const TickCount t = static_cast(num / g_settings.cpu_overclock_numerator); + *remainder = static_cast(num % g_settings.cpu_overclock_numerator); + return t; +} + +TickCount GetMaxSliceTicks(); +void UpdateOverclock(); + u32 GetFrameNumber(); u32 GetInternalFrameNumber(); void FrameDone(); diff --git a/src/core/timers.cpp b/src/core/timers.cpp index a51b8bd7c..be25e13e1 100644 --- a/src/core/timers.cpp +++ b/src/core/timers.cpp @@ -41,6 +41,7 @@ void Timers::Reset() cs.irq_done = false; } + m_syclk_ticks_carry = 0; m_sysclk_div_8_carry = 0; UpdateSysClkEvent(); } @@ -59,6 +60,7 @@ bool Timers::DoState(StateWrapper& sw) sw.Do(&cs.irq_done); } + sw.Do(&m_syclk_ticks_carry); sw.Do(&m_sysclk_div_8_carry); if (sw.IsReading()) @@ -67,6 +69,11 @@ bool Timers::DoState(StateWrapper& sw) return !sw.HasError(); } +void Timers::CPUClocksChanged() +{ + m_syclk_ticks_carry = 0; +} + void Timers::SetGate(u32 timer, bool state) { CounterState& cs = m_states[timer]; @@ -157,6 +164,8 @@ void Timers::AddTicks(u32 timer, TickCount count) void Timers::AddSysClkTicks(TickCount sysclk_ticks) { + sysclk_ticks = System::UnscaleTicksToOverclock(sysclk_ticks, &m_syclk_ticks_carry); + if (!m_states[0].external_counting_enabled && m_states[0].counting_enabled) AddTicks(0, sysclk_ticks); if (!m_states[1].external_counting_enabled && m_states[1].counting_enabled) @@ -351,7 +360,9 @@ TickCount Timers::GetTicksUntilNextInterrupt() const min_ticks_for_this_timer = std::min(min_ticks_for_this_timer, static_cast(0xFFFF - cs.counter)); if (cs.external_counting_enabled) // sysclk/8 for timer 2 - min_ticks_for_this_timer = std::max(1, min_ticks_for_this_timer * 8); + min_ticks_for_this_timer = std::max(1, System::ScaleTicksToOverclock(min_ticks_for_this_timer * 8)); + else + min_ticks_for_this_timer = std::max(1, System::ScaleTicksToOverclock(min_ticks_for_this_timer)); min_ticks = std::min(min_ticks, min_ticks_for_this_timer); } @@ -364,7 +375,7 @@ void Timers::UpdateSysClkEvent() // Still update once every 100ms. If we get polled we'll execute sooner. const TickCount ticks = GetTicksUntilNextInterrupt(); if (ticks == std::numeric_limits::max()) - m_sysclk_event->Schedule(MAX_SLICE_SIZE); + m_sysclk_event->Schedule(System::GetMaxSliceTicks()); else m_sysclk_event->Schedule(ticks); } diff --git a/src/core/timers.h b/src/core/timers.h index f0c1348cf..39c2467e1 100644 --- a/src/core/timers.h +++ b/src/core/timers.h @@ -24,6 +24,8 @@ public: void DrawDebugStateWindow(); + void CPUClocksChanged(); + // dot clock/hblank/sysclk div 8 ALWAYS_INLINE bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; } @@ -92,7 +94,8 @@ private: std::unique_ptr m_sysclk_event; std::array m_states{}; - u32 m_sysclk_div_8_carry = 0; // partial ticks for timer 3 with sysclk/8 + TickCount m_syclk_ticks_carry = 0; // 0 unless overclocking is enabled + u32 m_sysclk_div_8_carry = 0; // partial ticks for timer 3 with sysclk/8 }; extern Timers g_timers; \ No newline at end of file diff --git a/src/core/types.h b/src/core/types.h index c288c1916..cfe80b78e 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -19,9 +19,6 @@ enum class MemoryAccessSize : u32 using TickCount = s32; -static constexpr TickCount MASTER_CLOCK = 44100 * 0x300; // 33868800Hz or 33.8688MHz, also used as CPU clock -static constexpr TickCount MAX_SLICE_SIZE = MASTER_CLOCK / 10; - enum class ConsoleRegion { Auto,