GPU: Use correct clocks for NTSC region
Fixes sync drift in Bust-a-Move 1/2.
This commit is contained in:
parent
abc627de9c
commit
b4e45e865a
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue