From b4e45e865a7778b36e255a671e436223353ad066 Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Sun, 7 Jun 2020 17:36:45 +1000
Subject: [PATCH] GPU: Use correct clocks for NTSC region

Fixes sync drift in Bust-a-Move 1/2.
---
 src/core/gpu.cpp              | 80 ++++++++++++++++++++++++++++++-----
 src/core/gpu.h                | 10 ++---
 src/core/save_state_version.h |  2 +-
 src/core/system.cpp           |  1 +
 4 files changed, 76 insertions(+), 17 deletions(-)

diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp
index cd97e8ffd..6b09d453a 100644
--- a/src/core/gpu.cpp
+++ b/src/core/gpu.cpp
@@ -35,6 +35,8 @@ bool GPU::Initialize(HostDisplay* host_display, System* system, DMA* dma, Interr
     m_system->CreateTimingEvent("GPU Tick", 1, 1, std::bind(&GPU::Execute, this, std::placeholders::_1), true);
   m_fifo_size = system->GetSettings().gpu_fifo_size;
   m_max_run_ahead = system->GetSettings().gpu_max_run_ahead;
+  m_console_is_pal = system->IsPALRegion();
+  UpdateCRTCConfig();
   return true;
 }
 
@@ -46,9 +48,10 @@ void GPU::UpdateSettings()
   m_fifo_size = settings.gpu_fifo_size;
   m_max_run_ahead = settings.gpu_max_run_ahead;
 
-  if (m_force_ntsc_timings != settings.gpu_force_ntsc_timings)
+  if (m_force_ntsc_timings != settings.gpu_force_ntsc_timings || m_console_is_pal != m_system->IsPALRegion())
   {
     m_force_ntsc_timings = settings.gpu_force_ntsc_timings;
+    m_console_is_pal = m_system->IsPALRegion();
     UpdateCRTCConfig();
   }
 
@@ -129,6 +132,7 @@ bool GPU::DoState(StateWrapper& sw)
   sw.Do(&m_drawing_offset.y);
   sw.Do(&m_drawing_offset.x);
 
+  sw.Do(&m_console_is_pal);
   sw.Do(&m_set_texture_disable_mask);
 
   sw.Do(&m_crtc_state.regs.display_address_start);
@@ -353,6 +357,42 @@ void GPU::DMAWrite(const u32* words, u32 word_count)
   }
 }
 
+/**
+ * NTSC GPU clock 53.693175 MHz
+ * PAL GPU clock 53.203425 MHz
+ * courtesy of @ggrtk
+ *
+ * NTSC - sysclk * 715909 / 451584
+ * PAL - sysclk * 709379 / 451584
+ */
+
+TickCount GPU::GPUTicksToSystemTicks(TickCount gpu_ticks, TickCount fractional_ticks) const
+{
+  // convert to master clock, rounding up as we want to overshoot not undershoot
+  if (!m_console_is_pal)
+    return static_cast<TickCount>((u64(gpu_ticks) * u64(451584) + fractional_ticks + u64(715908)) / u64(715909));
+  else
+    return static_cast<TickCount>((u64(gpu_ticks) * u64(451584) + fractional_ticks + u64(709378)) / u64(709379));
+}
+
+TickCount GPU::SystemTicksToGPUTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const
+{
+  if (!m_console_is_pal)
+  {
+    const u64 mul = u64(sysclk_ticks) * u64(715909) + u64(*fractional_ticks);
+    const TickCount ticks = static_cast<TickCount>(mul / u64(451584));
+    *fractional_ticks = static_cast<TickCount>(mul % u64(451584));
+    return ticks;
+  }
+  else
+  {
+    const u64 mul = u64(sysclk_ticks) * u64(709379) + u64(*fractional_ticks);
+    const TickCount ticks = static_cast<TickCount>(mul / u64(451584));
+    *fractional_ticks = static_cast<TickCount>(mul % u64(451584));
+    return ticks;
+  }
+}
+
 void GPU::AddCommandTicks(TickCount ticks)
 {
   if (m_command_ticks != 0)
@@ -364,7 +404,7 @@ void GPU::AddCommandTicks(TickCount ticks)
   m_command_ticks = GetPendingGPUTicks() + ticks;
 
   // reschedule GPU tick event if it would execute later than this command finishes
-  const TickCount sysclk_ticks = GPUTicksToSystemTicks(ticks);
+  const TickCount sysclk_ticks = GPUTicksToSystemTicks(ticks, 0);
   if (m_tick_event->GetTicksUntilNextExecution() > sysclk_ticks)
     m_tick_event->Schedule(sysclk_ticks);
 }
@@ -374,6 +414,23 @@ void GPU::Synchronize()
   m_tick_event->InvokeEarly();
 }
 
+float GPU::ComputeHorizontalFrequency() const
+{
+  const CRTCState& cs = m_crtc_state;
+  TickCount fractional_ticks = 0;
+  return static_cast<float>(static_cast<double>(SystemTicksToGPUTicks(MASTER_CLOCK, &fractional_ticks)) /
+                            static_cast<double>(cs.horizontal_total));
+}
+
+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<float>(static_cast<double>(SystemTicksToGPUTicks(MASTER_CLOCK, &fractional_ticks)) /
+                            static_cast<double>(ticks_per_frame));
+}
+
 void GPU::UpdateCRTCConfig()
 {
   static constexpr std::array<u16, 8> dot_clock_dividers = {{10, 8, 5, 4, 7, 7, 7, 7}};
@@ -420,10 +477,7 @@ void GPU::UpdateCRTCConfig()
     cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE;
   }
 
-  const TickCount ticks_per_frame = cs.horizontal_total * cs.vertical_total;
-  const float vertical_frequency =
-    static_cast<float>(static_cast<double>((u64(MASTER_CLOCK) * 11) / 7) / static_cast<double>(ticks_per_frame));
-  m_system->SetThrottleFrequency(vertical_frequency);
+  m_system->SetThrottleFrequency(ComputeVerticalFrequency());
 
   UpdateCRTCDisplayParameters();
   UpdateSliceTicks();
@@ -571,7 +625,8 @@ void GPU::UpdateCRTCDisplayParameters()
 TickCount GPU::GetPendingGPUTicks() const
 {
   const TickCount pending_sysclk_ticks = m_tick_event->GetTicksSinceLastExecution();
-  return ((pending_sysclk_ticks * 11) + m_crtc_state.fractional_ticks) / 7;
+  TickCount fractional_ticks = m_crtc_state.fractional_ticks;
+  return SystemTicksToGPUTicks(pending_sysclk_ticks, &fractional_ticks);
 }
 
 void GPU::UpdateSliceTicks()
@@ -595,7 +650,8 @@ void GPU::UpdateSliceTicks()
 #endif
 
   m_tick_event->Schedule(
-    GPUTicksToSystemTicks((m_command_ticks > 0) ? std::min(m_command_ticks, ticks_until_event) : ticks_until_event));
+    GPUTicksToSystemTicks((m_command_ticks > 0) ? std::min(m_command_ticks, ticks_until_event) : ticks_until_event,
+                          m_crtc_state.fractional_ticks));
 }
 
 bool GPU::IsRasterScanlinePending() const
@@ -614,9 +670,7 @@ void GPU::Execute(TickCount ticks)
 {
   // convert cpu/master clock to GPU ticks, accounting for partial cycles because of the non-integer divider
   {
-    const TickCount ticks_mul_11 = (ticks * 11) + m_crtc_state.fractional_ticks;
-    const TickCount gpu_ticks = ticks_mul_11 / 7;
-    m_crtc_state.fractional_ticks = ticks_mul_11 % 7;
+    const TickCount gpu_ticks = SystemTicksToGPUTicks(ticks, &m_crtc_state.fractional_ticks);
     m_crtc_state.current_tick_in_scanline += gpu_ticks;
 
     // handle blits
@@ -1326,6 +1380,10 @@ void GPU::DrawDebugStateWindow()
   if (ImGui::CollapsingHeader("CRTC", ImGuiTreeNodeFlags_DefaultOpen))
   {
     const auto& cs = m_crtc_state;
+    ImGui::Text("Clock: %s", (m_console_is_pal ? (m_GPUSTAT.pal_mode ? "PAL-on-PAL" : "NTSC-on-PAL") :
+                                                 (m_GPUSTAT.pal_mode ? "PAL-on-NTSC" : "NTSC-on-NTSC")));
+    ImGui::Text("Horizontal Frequency: %.3f KHz", ComputeHorizontalFrequency() / 1000.0f);
+    ImGui::Text("Vertical Frequency: %.3f Hz", ComputeVerticalFrequency());
     ImGui::Text("Dot Clock Divider: %u", cs.dot_clock_divider);
     ImGui::Text("Vertical Interlace: %s (%s field)", m_GPUSTAT.vertical_interlace ? "Yes" : "No",
                 m_crtc_state.interlaced_field ? "odd" : "even");
diff --git a/src/core/gpu.h b/src/core/gpu.h
index 5cc83a751..423bdbc93 100644
--- a/src/core/gpu.h
+++ b/src/core/gpu.h
@@ -170,11 +170,8 @@ public:
   bool ConvertScreenCoordinatesToBeamTicksAndLines(s32 window_x, s32 window_y, u32* out_tick, u32* out_line) const;
 
 protected:
-  static constexpr TickCount GPUTicksToSystemTicks(TickCount gpu_ticks)
-  {
-    // convert to master clock, rounding up as we want to overshoot not undershoot
-    return static_cast<TickCount>((static_cast<u32>(gpu_ticks) * 7u + 10u) / 11u);
-  }
+  TickCount GPUTicksToSystemTicks(TickCount gpu_ticks, TickCount fractional_ticks) const;
+  TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
 
   // Helper/format conversion functions.
   static constexpr u8 Convert5To8(u8 x5) { return (x5 << 3) | (x5 & 7); }
@@ -326,6 +323,8 @@ protected:
   void SoftReset();
 
   // Sets dots per scanline
+  float ComputeHorizontalFrequency() const;
+  float ComputeVerticalFrequency() const;
   void UpdateCRTCConfig();
   void UpdateCRTCDisplayParameters();
 
@@ -572,6 +571,7 @@ protected:
     s32 y;
   } m_drawing_offset = {};
 
+  bool m_console_is_pal = false;
   bool m_set_texture_disable_mask = false;
   bool m_drawing_area_changed = false;
   bool m_force_progressive_scan = false;
diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h
index 2d5f06b30..b7cfb1c5b 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 = 36;
+static constexpr u32 SAVE_STATE_VERSION = 37;
 
 #pragma pack(push, 4)
 struct SAVE_STATE_HEADER
diff --git a/src/core/system.cpp b/src/core/system.cpp
index 294f3df79..cb5b328b7 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -260,6 +260,7 @@ void System::InitializeComponents()
   m_timers->Initialize(this, m_interrupt_controller.get(), m_gpu.get());
   m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get());
   m_mdec->Initialize(this, m_dma.get());
+  m_gpu->UpdateSettings();
 
   UpdateThrottlePeriod();
 }