diff --git a/runtime/include/spu.h b/runtime/include/spu.h index badec733..31bf7b96 100644 --- a/runtime/include/spu.h +++ b/runtime/include/spu.h @@ -81,6 +81,11 @@ typedef struct SpuGlobalState { void spu_get_voice_state(int voice, SpuVoiceState* out); void spu_get_global_state(SpuGlobalState* out); +/* SPUCNT (0x1F801DAA) alone. The sample-event scheduler gates on one ctrl bit + * per device-service call; building the full SpuGlobalState there (register + * sweep + three 24-voice loops) was ~40% of emu-thread time during the Capcom + * FMV's MMIO-polling loop (gdb-sampled 2026-09-01). */ +uint16_t spu_ctrl_read(void); /* Debug peek into SPU sample RAM (spu_ram TCP command). */ uint32_t spu_ram_peek(uint32_t addr, uint8_t *out, uint32_t len); diff --git a/runtime/src/interrupts.c b/runtime/src/interrupts.c index 86ddd691..c8338220 100644 --- a/runtime/src/interrupts.c +++ b/runtime/src/interrupts.c @@ -418,7 +418,6 @@ static int spu_sample_event_mode(void) { } uint32_t psx_spu_sample_event_cycles_to_next(void) { - SpuGlobalState state; g_spu_sample_deadline_queries++; if (!spu_sample_event_mode()) { g_spu_sample_mode_rejects++; @@ -428,8 +427,10 @@ uint32_t psx_spu_sample_event_cycles_to_next(void) { g_spu_sample_pump_null_rejects++; return UINT32_MAX; } - spu_get_global_state(&state); - if ((state.ctrl & 0x0040u) == 0) { + /* Gate on SPUCNT alone — this runs on every deadline recompute, and the + * full SpuGlobalState snapshot here dominated the emu thread under an + * MMIO-polling guest loop (Capcom FMV, gdb-sampled 2026-09-01). */ + if ((spu_ctrl_read() & 0x0040u) == 0) { g_spu_sample_ctrl_rejects++; return UINT32_MAX; } @@ -449,9 +450,9 @@ void psx_spu_sample_event_service(void) { g_spu_sample_service_checks++; if (!spu_sample_event_mode() || !s_midframe_audio_pump) return; - SpuGlobalState state; - spu_get_global_state(&state); - if ((state.ctrl & 0x0040u) != 0) { + /* SPUCNT alone — same hot-gate rationale as the deadline query above. */ + const uint16_t ctrl = spu_ctrl_read(); + if ((ctrl & 0x0040u) != 0) { g_spu_sample_enabled_services++; g_spu_sample_last_service_phase = (uint32_t)(psx_cycle_count % 768u); } @@ -460,7 +461,7 @@ void psx_spu_sample_event_service(void) { if ((psx_get_cycle_count() % 768u) != 0) g_spu_sample_deferred_mismatches++; } - if ((state.ctrl & 0x0040u) != 0 && + if ((ctrl & 0x0040u) != 0 && (psx_get_cycle_count() % 768u) == 0) { g_spu_sample_service_pumps++; s_midframe_audio_pump(); diff --git a/runtime/src/spu.c b/runtime/src/spu.c index dc358955..0e3c0340 100644 --- a/runtime/src/spu.c +++ b/runtime/src/spu.c @@ -1601,6 +1601,10 @@ uint32_t spu_ram_peek(uint32_t addr, uint8_t *out, uint32_t len) { return len; } +uint16_t spu_ctrl_read(void) { + return spu_regs[reg_index(0x1F801DAAu)]; +} + void spu_get_global_state(SpuGlobalState* out) { if (!out) return; memset(out, 0, sizeof(*out));