8mb support

This commit is contained in:
Alex Vanderveen
2026-08-16 17:59:20 -04:00
committed by Codex
parent a11e38314d
commit d1bfb6f09e
10 changed files with 212 additions and 53 deletions
+15 -10
View File
@@ -2629,11 +2629,23 @@ GeneratedFunction CodeGenerator::generate_function(
}
body_ss << "#endif\n";
// Cadence / RAM-prep mod hooks must run on CPS mid-function re-entry too:
// AOT interior blocks still `lw` guest RAM (e.g. SimVblankGate skip). Other
// entry hooks (debug log, widescreen) stay AFTER the switch so they only
// fire on a true prologue entry (cpu->pc == 0).
if (config_.mod_function_entry_funcs.count(func.start_addr)) {
body_ss << config_.indent
<< fmt::format(
"psx_mod_function_entry(cpu, 0x{:08X}u);"
" /* trusted opt-in game-mod hook (pre-CPS) */\n",
func.start_addr);
}
// CPS entry-switch: when the unified flat trampoline dispatches a
// continuation address (a callee published cpu->pc = $ra back to us), route
// into the owning block. Emitted BEFORE the entry hooks so a continuation
// re-entry bypasses debug_server_log_call_entry / widescreen entry hooks
// (those must run only on a true function entry, cpu->pc == 0).
// into the owning block. Emitted before debug/widescreen entry hooks so a
// continuation re-entry bypasses those (true-entry only). mod_function_entry
// is intentionally above so cadence plugins still run.
// Overlay mode must emit the cpu->pc guard even when the continuation set
// is EMPTY: a range re-entry can still request an interior PC of a
// single-block function, and without the guard the body runs from its top
@@ -2711,13 +2723,6 @@ GeneratedFunction CodeGenerator::generate_function(
" /* data-shard: replay or arm capture */\n",
func.start_addr);
}
if (config_.mod_function_entry_funcs.count(func.start_addr)) {
body_ss << config_.indent
<< fmt::format(
"psx_mod_function_entry(cpu, 0x{:08X}u);"
" /* trusted opt-in game-mod hook */\n",
func.start_addr);
}
if (config_.ws_sprite_tag_funcs.count(func.start_addr)) {
body_ss << config_.indent
<< "psx_ws_sprite_tag(cpu); /* widescreen: record prim ($a0) + anchor */\n";
+20
View File
@@ -43,6 +43,26 @@ void gpu_get_display_info(GpuDisplayInfo* out);
* packed RGB888 lives in the CPU mirror; treating A0 rects as 1555 FBO uploads
* both wastes bandwidth and force-flushes when UP_RECTS_MAX is hit (MotK FMV). */
int gpu_display_is_depth24(void);
/* GP1(08h) bit 3: 0 = NTSC, 1 = PAL. */
int gpu_display_is_pal(void);
/* Guest CPU cycles per CRTC frame (33.8688 MHz / 60 NTSC, / 50 PAL). */
#define PSX_VBLANK_CYCLES_NTSC 564480u
#define PSX_VBLANK_CYCLES_PAL 677376u
uint32_t gpu_vblank_period_cycles(void);
/*
* Force guest cycles per VBlank regardless of GP1(08h) PAL/NTSC. 0 clears.
* Used so NTSC display mode can keep a 50 Hz IRQ (25 fps with stock skip)
* for audio-safe ports of PAL titles. Still divided by the Nx multiplier.
*/
void gpu_set_crtc_vblank_period_override(uint32_t cycles);
uint32_t gpu_get_crtc_vblank_period_override(void);
/*
* Experimental enhancement: GooseStation-style Nx video timing. Multiplier 2
* halves the guest VBlank period (NTSC 120 Hz / PAL 100 Hz CRTC). Default 1.
* Host wall-clock pacing is separate (psx_mod_set_native_vblank_rate).
*/
void gpu_set_crtc_refresh_multiplier(uint32_t multiplier);
uint32_t gpu_get_crtc_refresh_multiplier(void);
void gpu_display_pixel_rgb(const GpuDisplayInfo* di, uint32_t x, uint32_t y,
uint8_t* r, uint8_t* g, uint8_t* b);
uint32_t gpu_display_pixel_argb(const GpuDisplayInfo* di, uint32_t x, uint32_t y);
+2 -1
View File
@@ -76,7 +76,8 @@ void psx_check_interrupts_dispatch_entry(struct CPUState* cpu, uint32_t resume_p
* Called from psx_advance_cycles() so the VBlank rate is gated on
* guest cycles (correct PSX timing) rather than block-dispatch
* count (which was 5-6x too fast and squeezed game-time to ~60% of
* real). One real-PSX VBlank = 564480 cycles (33.8688 MHz / 60). */
* real). Period follows GP1(08h) video mode: 564480 cycles NTSC
* (33.8688 MHz / 60) or 677376 PAL (33.8688 MHz / 50). */
void interrupts_advance_cycles(uint32_t cycles);
void interrupts_service_scheduled_events(void);
uint32_t interrupts_cycles_to_vblank(void);
+14
View File
@@ -103,6 +103,20 @@ int psx_mod_set_adaptive_display_aspect(uint32_t max_numerator,
*/
int psx_mod_set_native_vblank_rate(uint32_t frames_per_second);
/*
* GooseStation-style Nx CRTC: divide guest cycles per VBlank by `multiplier`
* (1 = stock, 2 = NTSC 120 Hz / PAL 100 Hz). Pair with
* psx_mod_set_native_vblank_rate(base_hz * multiplier) so wall-clock and
* guest time stay aligned; otherwise audio and realtime speed drift.
*/
int psx_mod_set_crtc_refresh_multiplier(uint32_t multiplier);
/*
* Force the guest VBlank IRQ period to a fixed CRTC rate (50 or 60 Hz), or
* pass 0 to follow GP1(08h) again. Does not change the GPU video-mode bit —
* NTSC display programming can keep a 50 Hz IRQ for audio-safe PAL ports.
*/
int psx_mod_set_crtc_vblank_hz(uint32_t hz);
/*
* Enable presentation-only frame interpolation while leaving guest VBlank,
* game logic, timers, and audio at their stock cadence. The OpenGL presenter
+4 -4
View File
@@ -13,6 +13,7 @@
#include "cdrom.h"
#include "cdrom_irq.h"
#include "dma.h"
#include "gpu.h"
#include "spu.h"
#include "event_ring.h"
#include "audio_trace.h"
@@ -340,8 +341,7 @@ uint32_t g_cd_overwrite_last_frame = 0;
* frames always advance. Paired with the per-source exception-progress
* guarantee in interrupts.c (the real anti-starvation fix); this floor keeps
* the exception VOLUME sane so the framerate doesn't collapse. Tunable via the
* MAX_PER_FRAME knob. */
#define VBLANK_CYCLES_NTSC 564480 /* 33.8688 MHz / 60 Hz; matches interrupts.c */
* MAX_PER_FRAME knob. PAL uses the live CRTC period from gpu_vblank_period_cycles(). */
#define CDROM_INSTANT_MAX_PER_FRAME_DEFAULT 32
#define CDROM_SINGLE_SPEED_SECTOR_CYCLES 451584
@@ -511,14 +511,14 @@ void cdrom_set_instant_rate(int per_frame) {
int cdrom_get_instant_rate(void) { return g_instant_max_per_frame; }
static int instant_period(void) {
int p = VBLANK_CYCLES_NTSC / g_instant_max_per_frame;
int p = (int)(gpu_vblank_period_cycles() / (uint32_t)g_instant_max_per_frame);
return p < CDROM_MIN_DELAY ? CDROM_MIN_DELAY : p;
}
static int warm_route_period(void) {
const CDROMWarmRoute *route = warm_route_current();
int rate = route ? route->rate : CDROM_INSTANT_MAX_PER_FRAME_DEFAULT;
int p = VBLANK_CYCLES_NTSC / rate;
int p = (int)(gpu_vblank_period_cycles() / (uint32_t)rate);
return p < CDROM_MIN_DELAY ? CDROM_MIN_DELAY : p;
}
+37
View File
@@ -3095,6 +3095,43 @@ int gpu_display_is_depth24(void) {
return (int)(display_depth & 1u);
}
int gpu_display_is_pal(void) {
return (int)(video_mode & 1u);
}
/* Opt-in Nx CRTC (mod plugins). 1 = stock; 2 = half-period VBlank. */
static uint32_t s_crtc_refresh_multiplier = 1u;
/* 0 = derive from GP1 video_mode; else absolute period before Nx divide. */
static uint32_t s_crtc_period_override = 0u;
void gpu_set_crtc_refresh_multiplier(uint32_t multiplier) {
if (multiplier < 1u)
multiplier = 1u;
if (multiplier > 8u)
multiplier = 8u;
s_crtc_refresh_multiplier = multiplier;
}
uint32_t gpu_get_crtc_refresh_multiplier(void) {
return s_crtc_refresh_multiplier ? s_crtc_refresh_multiplier : 1u;
}
void gpu_set_crtc_vblank_period_override(uint32_t cycles) {
s_crtc_period_override = cycles;
}
uint32_t gpu_get_crtc_vblank_period_override(void) {
return s_crtc_period_override;
}
uint32_t gpu_vblank_period_cycles(void) {
const uint32_t base = s_crtc_period_override
? s_crtc_period_override
: (video_mode ? PSX_VBLANK_CYCLES_PAL : PSX_VBLANK_CYCLES_NTSC);
const uint32_t mult = gpu_get_crtc_refresh_multiplier();
return base / mult;
}
void gpu_get_display_info(GpuDisplayInfo* out) {
out->display_x = display_area_x;
out->display_y = display_area_y;
+16 -10
View File
@@ -177,8 +177,10 @@ void psx_irq_raise(uint32_t bit, uint32_t detail)
/* Dispatch counter for vblank scheduling. */
#define VBLANK_INTERVAL 50000 /* legacy: dispatch-count fallback (unused for VBlank gating now) */
#define VBLANK_CYCLES 564480u /* 33.8688 MHz / 60 Hz — real PSX NTSC VBlank period */
#define VBLANK_DEFER_STALE_CYCLES (VBLANK_CYCLES * 10ull)
static uint32_t vblank_cycles(void) {
return gpu_vblank_period_cycles();
}
#define VBLANK_DEFER_STALE_CYCLES_FRAMES 10ull
static uint32_t dispatch_count;
static uint64_t total_checks;
static uint32_t cycles_since_vblank; /* incremented by interrupts_advance_cycles */
@@ -356,7 +358,7 @@ static int should_defer_vblank_for_sio(void) {
uint64_t since_progress = now >= last_sio_progress_cycle
? now - last_sio_progress_cycle
: 0;
return since_progress < VBLANK_DEFER_STALE_CYCLES;
return since_progress < ((uint64_t)vblank_cycles() * VBLANK_DEFER_STALE_CYCLES_FRAMES);
}
/* ---- Mid-dispatch audio pump -------------------------------------------
@@ -390,13 +392,14 @@ static void fire_vblank_edge(void) {
/* Subtract one VBlank period rather than reset to 0 so cycle overshoot
* carries forward. Prevents long-running blocks from rounding multiple
* VBlanks together. */
cycles_since_vblank -= VBLANK_CYCLES;
const uint32_t period = vblank_cycles();
cycles_since_vblank -= period;
dispatch_count = 0;
/* DEQUEUE: this VBlank fired. ENQUEUE: next VBlank scheduled one period out. */
event_ring_record_aux(EV_DEQ, (uint8_t)SRC_VBLANK,
(uint32_t)psx_get_cycle_count());
event_ring_record_aux(EV_ENQ, (uint8_t)SRC_VBLANK,
(uint32_t)(psx_get_cycle_count() + VBLANK_CYCLES));
(uint32_t)(psx_get_cycle_count() + period));
psx_irq_raise(IRQ_VBLANK, 0);
g_vblank_raise_count++;
event_ring_record(EV_ISTAT_RAISE, IRQ_VBLANK);
@@ -412,15 +415,17 @@ static void fire_vblank_edge(void) {
void interrupts_service_scheduled_events(void) {
note_sio_progress_cycle();
if (in_exception) return;
while (cycles_since_vblank >= VBLANK_CYCLES) {
const uint32_t period = vblank_cycles();
while (cycles_since_vblank >= period) {
if (should_defer_vblank_for_sio()) return;
fire_vblank_edge();
}
}
uint32_t interrupts_cycles_to_vblank(void) {
if (cycles_since_vblank >= VBLANK_CYCLES) return 0;
return VBLANK_CYCLES - cycles_since_vblank;
const uint32_t period = vblank_cycles();
if (cycles_since_vblank >= period) return 0;
return period - cycles_since_vblank;
}
uint32_t interrupts_get_cycles_since_vblank(void) {
@@ -896,8 +901,9 @@ uint32_t cycles_to_next_event(void) {
* card-SIO case only pushes VBlank LATER, so this estimate stays a safe
* under-estimate. */
if (i_mask & (1u << IRQ_VBLANK)) {
uint32_t d = (cycles_since_vblank >= VBLANK_CYCLES)
? 0u : (VBLANK_CYCLES - cycles_since_vblank);
const uint32_t period = vblank_cycles();
uint32_t d = (cycles_since_vblank >= period)
? 0u : (period - cycles_since_vblank);
if (d < best) best = d;
}
uint32_t t = timers_cycles_to_irq(i_mask); if (t < best) best = t;
+92 -19
View File
@@ -1170,10 +1170,33 @@ static_assert((int)PSX_MOD_CONTROLLER_ANALOG ==
static_assert((int)PSX_MOD_CONTROLLER_DIGITAL ==
(int)PSXRecompV4::PAD_MODE_DIGITAL);
static double g_host_refresh_hz = 0.0;
static constexpr double PSX_FRAME_PERIOD_MS = 1000.0 / 59.94;
static constexpr double PSX_FRAME_HZ_NTSC = 59.94;
static constexpr double PSX_FRAME_HZ_PAL = 50.0;
static constexpr double PSX_FRAME_PERIOD_MS = 1000.0 / PSX_FRAME_HZ_NTSC;
static double g_frame_period_ms = PSX_FRAME_PERIOD_MS;
static bool g_mod_native_vblank_rate = false;
static uint32_t g_mod_native_vblank_fps = 0;
static double psx_crtc_frame_hz(void) {
const uint32_t period = gpu_vblank_period_cycles();
if (period == 0u)
return PSX_FRAME_HZ_NTSC;
/* 33.8688 MHz CPU clock / cycles-per-vblank. */
return 33868800.0 / (double)period;
}
static void refresh_frame_pacer_period(void) {
if (g_mod_native_vblank_rate)
return;
const double psx_hz = psx_crtc_frame_hz();
g_frame_period_ms = 1000.0 / psx_hz;
if (g_host_refresh_hz > 0.0) {
const double lo = psx_hz * 0.98;
const double hi = psx_hz * 1.02;
if (g_host_refresh_hz >= lo && g_host_refresh_hz <= hi)
g_frame_period_ms = 1000.0 / g_host_refresh_hz;
}
}
/* Activation-time request. -1 means no enabled mod owns load acceleration. */
static int g_mod_load_wall_multiplier = -1;
static int g_mod_load_release_frames = -1;
@@ -1277,7 +1300,7 @@ extern "C" int psx_mod_set_adaptive_display_aspect(
extern "C" int psx_mod_set_native_vblank_rate(
uint32_t frames_per_second) {
if (frames_per_second != 0 &&
(frames_per_second < 60 || frames_per_second > 1000)) {
(frames_per_second < 50 || frames_per_second > 1000)) {
std::fprintf(stderr,
"psxrecomp: mod rejected invalid native VBlank rate %u FPS\n",
(unsigned)frames_per_second);
@@ -1307,6 +1330,46 @@ extern "C" int psx_mod_set_native_vblank_rate(
return 1;
}
extern "C" int psx_mod_set_crtc_refresh_multiplier(
uint32_t multiplier) {
if (multiplier < 1u || multiplier > 8u) {
std::fprintf(stderr,
"psxrecomp: mod rejected invalid CRTC refresh multiplier %u\n",
(unsigned)multiplier);
return 0;
}
gpu_set_crtc_refresh_multiplier(multiplier);
std::fprintf(stdout,
"psxrecomp: mod selected CRTC refresh multiplier %u "
"(guest VBlank period / %u)\n",
(unsigned)multiplier, (unsigned)multiplier);
return 1;
}
extern "C" int psx_mod_set_crtc_vblank_hz(uint32_t hz) {
if (hz == 0u) {
gpu_set_crtc_vblank_period_override(0u);
std::fprintf(stdout,
"psxrecomp: mod cleared CRTC VBlank period override "
"(follow GP1 video mode)\n");
return 1;
}
if (hz != 50u && hz != 60u) {
std::fprintf(stderr,
"psxrecomp: mod rejected invalid CRTC VBlank rate %u Hz "
"(use 50, 60, or 0)\n",
(unsigned)hz);
return 0;
}
const uint32_t cycles =
(hz == 50u) ? PSX_VBLANK_CYCLES_PAL : PSX_VBLANK_CYCLES_NTSC;
gpu_set_crtc_vblank_period_override(cycles);
std::fprintf(stdout,
"psxrecomp: mod forced CRTC VBlank to %u Hz (%u guest cycles)\n",
(unsigned)hz, (unsigned)cycles);
return 1;
}
extern "C" int psx_mod_set_frame_interpolation(
uint32_t frames_per_second) {
if (frames_per_second != 0 &&
@@ -2524,8 +2587,10 @@ static void apply_netplay_local_viewport_aspect(bool netplay_enabled) {
* wall-clock pacer double-blocks the vblank callback (present is before the
* guest resumes), which shows up as MotK FMV ~30–40 FPS in netplay vs ~50+
* offline. Force immediate swaps for the session; restore on soft-exit. */
static int host_refresh_is_approx_60hz(void) {
return g_host_refresh_hz >= 58.8 && g_host_refresh_hz <= 61.2;
static int host_refresh_matches_crtc(void) {
const double psx_hz = psx_crtc_frame_hz();
return g_host_refresh_hz >= psx_hz * 0.98 &&
g_host_refresh_hz <= psx_hz * 1.02;
}
static int present_vsync_owns_cadence(void) {
@@ -2537,7 +2602,7 @@ static int present_vsync_owns_cadence(void) {
return 0;
if (g_netplay_vsync_forced_off || psx_netplay_active())
return 0;
return host_refresh_is_approx_60hz();
return host_refresh_matches_crtc();
}
static int present_effective_swap_interval(void) {
@@ -5924,7 +5989,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
} else if (frequency && now - s_fps_last_time >= frequency) {
const double seconds = (double)(now - s_fps_last_time) / (double)frequency;
const double fps = (double)(s_frame_count - s_fps_last_frame) / seconds;
const double speed = fps / 59.94;
const double speed = fps / psx_crtc_frame_hz();
double display_fps = 0.0;
if (g_frame_interpolation && g_gl_active) {
display_fps = g_frame_interpolation_fps > 0
@@ -6418,12 +6483,14 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
}
/* Offline wall-clock pacing before present. Skipped when driver vsync
* owns cadence (~60 Hz panel) so the two waits cannot double-block.
* owns cadence (panel near the live CRTC rate) so the two waits cannot
* double-block.
* Netplay paces in the epilogue AFTER present so Swap overlaps the
* peer's guest quantum. Self-check replay runs uncapped like netplay
* resim. */
if (!psx_netplay_active() && !psx_selfcheck_resim_active()) {
uint64_t perf_start = runtime_perf_section_begin();
refresh_frame_pacer_period();
if (!manual_turbo_active && !turbo_load_paced && present_should_wall_pace())
frame_pacer_wait(&s_frame_pacer, g_frame_period_ms);
runtime_perf_section_end(perf_start, &g_runtime_perf.pacer_ticks);
@@ -6944,6 +7011,7 @@ static void sdl_vblank_present(void) {
return;
}
uint64_t perf_start = runtime_perf_section_begin();
refresh_frame_pacer_period();
if (present_should_wall_pace())
frame_pacer_wait(&s_frame_pacer, g_frame_period_ms);
runtime_perf_section_end(perf_start, &g_runtime_perf.pacer_ticks);
@@ -12726,23 +12794,28 @@ session_reboot:
}
psx_apply_window_icon(sdl_window, argv[0]);
/* Host refresh: if the panel is within ~2% of 60 Hz, record it so driver
* vsync can own cadence (pacer skipped). Non-~60 Hz and unknown refresh
* (common on Wayland) keep PSX 59.94 Hz pacing and force swap interval 0
* — vsync as the clock would run the sim at the panel rate. */
/* Host refresh: if the panel is within ~2% of the live CRTC rate (NTSC
* 59.94 / PAL 50), record it so driver vsync can own cadence. Otherwise
* keep the PSX CRTC period and force swap interval 0. Mods that own
* native VBlank keep their cadence. */
{
SDL_DisplayMode dm;
int disp_idx = SDL_GetWindowDisplayIndex(sdl_window);
if (disp_idx >= 0 && SDL_GetCurrentDisplayMode(disp_idx, &dm) == 0 && dm.refresh_rate > 0) {
double host_hz = (double)dm.refresh_rate;
g_host_refresh_hz = host_hz;
if (host_hz >= 58.8 && host_hz <= 61.2) {
g_frame_period_ms = 1000.0 / host_hz;
if (disp_idx >= 0 && SDL_GetCurrentDisplayMode(disp_idx, &dm) == 0 && dm.refresh_rate > 0)
g_host_refresh_hz = (double)dm.refresh_rate;
refresh_frame_pacer_period();
if (!g_mod_native_vblank_rate) {
const double psx_hz = psx_crtc_frame_hz();
if (host_refresh_matches_crtc()) {
std::printf("psxrecomp: sync-to-host-refresh: pacing to %d Hz panel "
"(%.4f ms/frame)\n", dm.refresh_rate, g_frame_period_ms);
"(%.4f ms/frame, %s CRTC)\n",
(int)(g_host_refresh_hz + 0.5), g_frame_period_ms,
gpu_display_is_pal() ? "PAL" : "NTSC");
} else {
std::printf("psxrecomp: host panel %d Hz not ~60 Hz; keeping PSX "
"59.94 Hz pacing\n", dm.refresh_rate);
std::printf("psxrecomp: host panel %.0f Hz; keeping %s %.2f Hz pacing\n",
g_host_refresh_hz,
gpu_display_is_pal() ? "PAL" : "NTSC",
psx_hz);
}
}
}
+6 -5
View File
@@ -17,6 +17,7 @@
#include "memcard.h"
#include "debug_server.h"
#include "event_ring.h"
#include "gpu.h"
#include <stdlib.h>
#include <string.h>
@@ -424,7 +425,7 @@ static int sio_txn_open = 0;
static void sio_arm_card_ct_defer_guard(void) {
extern uint64_t psx_get_cycle_count(void);
uint64_t now = psx_get_cycle_count();
uint64_t until = now + 564480ull * 2ull;
uint64_t until = now + (uint64_t)gpu_vblank_period_cycles() * 2ull;
if (until > s_card_ct_defer_until_cyc)
s_card_ct_defer_until_cyc = until;
}
@@ -547,7 +548,7 @@ static void ape_card_unstick_maybe(int allow_b4e38_synth) {
psx_irq_raise(IRQ_SIO0, 0);
/* ~2ms between pulses — faster than one VB/8 so two pops can land
* before BIOS clears I_MASK.7 for good. */
s_ape_unstick_cool_cyc = now + 564480ull / 32ull;
s_ape_unstick_cool_cyc = now + (uint64_t)gpu_vblank_period_cycles() / 32ull;
if (s_card_handoff_armed)
card_handoff_push(6, (uint8_t)(a6 & 0xffu));
if (++s_ape_torn_pulses >= 128)
@@ -597,15 +598,15 @@ int sio_card_handoff_cap(void) { return CARD_HANDOFF_CAP; }
int sio_card_handoff_armed(void) { return s_card_handoff_armed; }
int sio_hold_present_for_card(void) {
/* NTSC VBlank period — matches interrupts.c VBLANK_CYCLES. */
/* Current CRTC VBlank period — matches interrupts.c. */
enum { SIO_PRESENT_HOLD_STALE_VB = 10 };
static const uint64_t stale_cycles =
564480ull * (uint64_t)SIO_PRESENT_HOLD_STALE_VB;
static uint32_t s_hold_seq;
static uint64_t s_hold_progress_cyc;
static int s_hold_armed;
uint32_t seq;
uint64_t now;
const uint64_t stale_cycles =
(uint64_t)gpu_vblank_period_cycles() * (uint64_t)SIO_PRESENT_HOLD_STALE_VB;
if (!sio_card_protocol_active()) {
s_hold_armed = 0;
+6 -4
View File
@@ -16,6 +16,7 @@
#include "timers.h"
#include "event_ring.h"
#include "gpu.h"
#include <string.h>
/* Mode register bit definitions */
@@ -210,7 +211,7 @@ void timers_advance(uint32_t cycles) {
timer_advance_counts(t, cycles);
} else if (t == 1) {
/* Timer 1 HBlank clock. NTSC has about 263 HBlanks per frame. */
timer_advance_divided(t, cycles, 2146);
timer_advance_divided(t, cycles, gpu_display_is_pal() ? 2157u : 2146u);
} else if (t == 0) {
/* Timer 0 dotclock approximation; exact divider depends on GPU mode. */
timer_advance_divided(t, cycles, 5);
@@ -223,7 +224,7 @@ static uint32_t timer_divisor(int t) {
int src = (timers[t].mode >> 8) & 3;
if (t == 2 && (src == 2 || src == 3)) return 8; /* sysclk/8 */
if (timer_uses_sysclk(t)) return 1; /* 1 cycle = 1 tick */
if (t == 1) return 2146; /* HBlank approximation */
if (t == 1) return gpu_display_is_pal() ? 2157u : 2146u;
if (t == 0) return 5; /* dotclock approximation */
return 1;
}
@@ -334,9 +335,10 @@ void timers_write(uint32_t addr, uint32_t value) {
}
void timers_tick(int cycles) {
/* Timer 1 in HBlank mode: ~263 HBlanks per NTSC frame */
/* Timer 1 in HBlank mode: one pulse per scanline this frame. */
if (!timer_uses_sysclk(1)) {
for (int h = 0; h < 263; h++)
const int lines = gpu_display_is_pal() ? 314 : 263;
for (int h = 0; h < lines; h++)
timer_tick_one(1);
}