GPU: Use correct clocks for NTSC region

Fixes sync drift in Bust-a-Move 1/2.
This commit is contained in:
Connor McLaughlin 2020-06-07 17:36:45 +10:00
parent abc627de9c
commit b4e45e865a
4 changed files with 76 additions and 17 deletions

View File

@ -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_system->CreateTimingEvent("GPU Tick", 1, 1, std::bind(&GPU::Execute, this, std::placeholders::_1), true);
m_fifo_size = system->GetSettings().gpu_fifo_size; m_fifo_size = system->GetSettings().gpu_fifo_size;
m_max_run_ahead = system->GetSettings().gpu_max_run_ahead; m_max_run_ahead = system->GetSettings().gpu_max_run_ahead;
m_console_is_pal = system->IsPALRegion();
UpdateCRTCConfig();
return true; return true;
} }
@ -46,9 +48,10 @@ void GPU::UpdateSettings()
m_fifo_size = settings.gpu_fifo_size; m_fifo_size = settings.gpu_fifo_size;
m_max_run_ahead = settings.gpu_max_run_ahead; 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_force_ntsc_timings = settings.gpu_force_ntsc_timings;
m_console_is_pal = m_system->IsPALRegion();
UpdateCRTCConfig(); UpdateCRTCConfig();
} }
@ -129,6 +132,7 @@ bool GPU::DoState(StateWrapper& sw)
sw.Do(&m_drawing_offset.y); sw.Do(&m_drawing_offset.y);
sw.Do(&m_drawing_offset.x); sw.Do(&m_drawing_offset.x);
sw.Do(&m_console_is_pal);
sw.Do(&m_set_texture_disable_mask); sw.Do(&m_set_texture_disable_mask);
sw.Do(&m_crtc_state.regs.display_address_start); 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) void GPU::AddCommandTicks(TickCount ticks)
{ {
if (m_command_ticks != 0) if (m_command_ticks != 0)
@ -364,7 +404,7 @@ void GPU::AddCommandTicks(TickCount ticks)
m_command_ticks = GetPendingGPUTicks() + ticks; m_command_ticks = GetPendingGPUTicks() + ticks;
// reschedule GPU tick event if it would execute later than this command finishes // 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) if (m_tick_event->GetTicksUntilNextExecution() > sysclk_ticks)
m_tick_event->Schedule(sysclk_ticks); m_tick_event->Schedule(sysclk_ticks);
} }
@ -374,6 +414,23 @@ void GPU::Synchronize()
m_tick_event->InvokeEarly(); 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() void GPU::UpdateCRTCConfig()
{ {
static constexpr std::array<u16, 8> dot_clock_dividers = {{10, 8, 5, 4, 7, 7, 7, 7}}; 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; cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE;
} }
const TickCount ticks_per_frame = cs.horizontal_total * cs.vertical_total; m_system->SetThrottleFrequency(ComputeVerticalFrequency());
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);
UpdateCRTCDisplayParameters(); UpdateCRTCDisplayParameters();
UpdateSliceTicks(); UpdateSliceTicks();
@ -571,7 +625,8 @@ void GPU::UpdateCRTCDisplayParameters()
TickCount GPU::GetPendingGPUTicks() const TickCount GPU::GetPendingGPUTicks() const
{ {
const TickCount pending_sysclk_ticks = m_tick_event->GetTicksSinceLastExecution(); 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() void GPU::UpdateSliceTicks()
@ -595,7 +650,8 @@ void GPU::UpdateSliceTicks()
#endif #endif
m_tick_event->Schedule( 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 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 // 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 = SystemTicksToGPUTicks(ticks, &m_crtc_state.fractional_ticks);
const TickCount gpu_ticks = ticks_mul_11 / 7;
m_crtc_state.fractional_ticks = ticks_mul_11 % 7;
m_crtc_state.current_tick_in_scanline += gpu_ticks; m_crtc_state.current_tick_in_scanline += gpu_ticks;
// handle blits // handle blits
@ -1326,6 +1380,10 @@ void GPU::DrawDebugStateWindow()
if (ImGui::CollapsingHeader("CRTC", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("CRTC", ImGuiTreeNodeFlags_DefaultOpen))
{ {
const auto& cs = m_crtc_state; 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("Dot Clock Divider: %u", cs.dot_clock_divider);
ImGui::Text("Vertical Interlace: %s (%s field)", m_GPUSTAT.vertical_interlace ? "Yes" : "No", ImGui::Text("Vertical Interlace: %s (%s field)", m_GPUSTAT.vertical_interlace ? "Yes" : "No",
m_crtc_state.interlaced_field ? "odd" : "even"); m_crtc_state.interlaced_field ? "odd" : "even");

View File

@ -170,11 +170,8 @@ public:
bool ConvertScreenCoordinatesToBeamTicksAndLines(s32 window_x, s32 window_y, u32* out_tick, u32* out_line) const; bool ConvertScreenCoordinatesToBeamTicksAndLines(s32 window_x, s32 window_y, u32* out_tick, u32* out_line) const;
protected: protected:
static constexpr TickCount GPUTicksToSystemTicks(TickCount gpu_ticks) TickCount GPUTicksToSystemTicks(TickCount gpu_ticks, TickCount fractional_ticks) const;
{ TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
// 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);
}
// Helper/format conversion functions. // Helper/format conversion functions.
static constexpr u8 Convert5To8(u8 x5) { return (x5 << 3) | (x5 & 7); } static constexpr u8 Convert5To8(u8 x5) { return (x5 << 3) | (x5 & 7); }
@ -326,6 +323,8 @@ protected:
void SoftReset(); void SoftReset();
// Sets dots per scanline // Sets dots per scanline
float ComputeHorizontalFrequency() const;
float ComputeVerticalFrequency() const;
void UpdateCRTCConfig(); void UpdateCRTCConfig();
void UpdateCRTCDisplayParameters(); void UpdateCRTCDisplayParameters();
@ -572,6 +571,7 @@ protected:
s32 y; s32 y;
} m_drawing_offset = {}; } m_drawing_offset = {};
bool m_console_is_pal = false;
bool m_set_texture_disable_mask = false; bool m_set_texture_disable_mask = false;
bool m_drawing_area_changed = false; bool m_drawing_area_changed = false;
bool m_force_progressive_scan = false; bool m_force_progressive_scan = false;

View File

@ -2,7 +2,7 @@
#include "types.h" #include "types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; 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) #pragma pack(push, 4)
struct SAVE_STATE_HEADER struct SAVE_STATE_HEADER

View File

@ -260,6 +260,7 @@ void System::InitializeComponents()
m_timers->Initialize(this, m_interrupt_controller.get(), m_gpu.get()); m_timers->Initialize(this, m_interrupt_controller.get(), m_gpu.get());
m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get()); m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get());
m_mdec->Initialize(this, m_dma.get()); m_mdec->Initialize(this, m_dma.get());
m_gpu->UpdateSettings();
UpdateThrottlePeriod(); UpdateThrottlePeriod();
} }