mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
2641f7c59b
Framework fixes surfaced while bringing up a title that stresses overlays, HD texture replacement and PGXP at a doubled CRTC rate. All are general; the title's own configuration stays in its game repository. CORRECTNESS * Overlay DLL shim was deleted by the preprocessor. compile_overlays.py injected overlay_dispatch_preamble.c.inc after the syntactically LAST #include. A conditional i-cache include moved that anchor inside an #else, and DLL builds define PSX_OVERLAY_DLL_BUILD, so the callback definitions preprocessed away and every overlay DLL failed to link. Any title using overlays would silently fall back to per-instruction interpretation. The shim is now anchored to the unconditional #include "psx_runtime.h" and raises if that anchor is absent. * Overlay capture kept stale PC bitmaps when executed code later became data, so one variant's PC set could be compiled against another variant's bytes. overlay_watch_note_write now retires the whole page's execution/dispatch epoch when any executed word is overwritten; static demand is keyed by (load_addr, size, bytes) and resolved only against matching range identities. * VRAM-to-VRAM copies only invalidated the destination and never registered it as a replacement candidate, so textures arriving by copy could not be dumped or replaced at all. gr_copy_rect now calls tex_pack_on_copy with the same (texture,palette) key semantics as CPU uploads. * PGXP shared-edge seams: a per-primitive all-or-nothing gate cannot stop two adjacent primitives disagreeing about a shared endpoint, so the edge is submitted at two sub-pixel positions. Adds a frame-stamped canonical endpoint cache keyed by final integer screen position: the first use fixes the coordinates and every later occurrence reuses them. * PGXP provenance: PGXPValue carries per-component X/Y/Z projection ids, so a value assembled from different projections can no longer arm correction. Destructive AND/XOR/NOR/SLT, link and COP0 writes now invalidate shadows, and the SXYP shadow FIFO shifts with guest SXY. * Precise NCLIP no longer replaces guest MAC0. It is telemetry only, so face visibility stays bit-exact with the integer GTE. * Removed two counters that were reported as correctness evidence but were never incremented anywhere. PERFORMANCE * PSX_ENABLE_LTO plus a noinline guard on the shared cycle wrappers. Unguarded ThinLTO clones hot interlock bodies and REGRESSES the hot path; guarded, it becomes a win. Adds runtime/tests/bench_psx_runtime_hotpath.c, which verifies variant equivalence by digest so codegen changes are measurable. * Cycle accounting: emitters select specialized arity-0/1/2/3 helpers at generation time instead of a generic dependency-mask loop; main-RAM loads combine base/wait/completion into one exact charge. Exactness is covered by pipeline-state, load-state, deadline-crossing and cross-device causality tests. * HD texture invalidation replaced a linear scan of up to 8192 upload records per VRAM write with a tile-refcount index; out-of-domain writes early-out. * Translation/text-substitution dispatch is gated behind a monotonic mutation epoch instead of running a callback on every dispatch. * card_data_writes_check no longer runs on every RAM write. CAPABILITY * fixed_outer_aspect: the outer presentation aspect becomes a function of user configuration only. Scene classification selects content transforms, never the outer viewport, so menus/FMV stay centred undistorted inside a wide canvas. VERIFICATION AND LIMITS docs/UPSTREAM_2026-08-20.md audits each change against the diff, marks unsupported claims UNKNOWN, and lists a suggested PR split and review focus. Unit and structural tests accompany each item, but this body of work has NOT been validated end-to-end against a fixed reproducible workload -- reviewers should treat the timing-exactness and PGXP changes as the highest-risk areas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
150 lines
7.0 KiB
C
150 lines
7.0 KiB
C
#ifndef PSXRECOMP_INTERRUPTS_H
|
|
#define PSXRECOMP_INTERRUPTS_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct CPUState;
|
|
|
|
/* IRQ bit positions in I_STAT/I_MASK */
|
|
#define IRQ_VBLANK 0
|
|
#define IRQ_GPU 1
|
|
#define IRQ_CDROM 2
|
|
#define IRQ_DMA 3
|
|
#define IRQ_TIMER0 4
|
|
#define IRQ_TIMER1 5
|
|
#define IRQ_TIMER2 6
|
|
#define IRQ_SIO0 7
|
|
#define IRQ_SIO1 8
|
|
#define IRQ_SPU 9
|
|
#define IRQ_PIO 10
|
|
|
|
void interrupts_init(void);
|
|
|
|
/* Save-state restore: re-anchor host-only IRQ pacing after psx_cycle_count is
|
|
* overwritten from a snapshot. Clears absolute-cycle cooldowns / deferred
|
|
* switches that would otherwise strand delivery until the restored clock
|
|
* catches up to the pre-load host timeline (warm-reload picture freeze). */
|
|
void interrupts_resync_after_restore(void);
|
|
|
|
/* Netplay diag: last VBLANK irqctx exit/restore (+ v0) for peer diff. */
|
|
void interrupts_log_last_vblank_irqctx(const char *tag);
|
|
|
|
/* Central IRQ-raise choke point: sets the I_STAT bit AND records the raise edge
|
|
* into the always-on device-event ring (device_trace) with the guest cycle.
|
|
* Every hardware source (VBLANK/GPU/CDROM/DMA/timers/SIO/SPU) funnels its raise
|
|
* through here so the device-timing audit sees every event from one place.
|
|
* `detail` is source-specific (DMA channel, timer index) or 0. */
|
|
void psx_irq_raise(uint32_t bit, uint32_t detail);
|
|
/* Single writer for the cached INTC output line and COP0 Cause.IP2 mirror.
|
|
* Call after every direct I_STAT/I_MASK mutation. */
|
|
extern uint32_t g_psx_irq_hw_pending;
|
|
void psx_irq_refresh_cause_ip2(void);
|
|
|
|
/* Called from the dispatch loop after each function returns.
|
|
* Services due scheduled edges, checks (i_stat & i_mask), and if pending + COP0
|
|
* allows, dispatches the exception handler. */
|
|
void psx_check_interrupts(struct CPUState* cpu);
|
|
|
|
/* Cheap block-edge predicate for generated/native code. Device events are
|
|
* raised by psx_advance_cycles(); this reports whether the comparatively
|
|
* expensive delivery/scheduler path can have an architectural effect now. */
|
|
int psx_interrupt_delivery_needed(const struct CPUState* cpu);
|
|
void psx_interrupt_delivery_diag(uint64_t *need_defer, uint64_t *need_irq,
|
|
uint64_t *skip_none, uint64_t *skip_sr,
|
|
uint64_t *skip_cooldown, uint64_t *skip_nested);
|
|
|
|
/* Hot-path counters for psx_check_interrupts (monotonic). Any out_* may be NULL.
|
|
* entry = every call
|
|
* fast_sr = early return: pending I_STAT but IEc/IM2 clear
|
|
* fast_none = early return: nothing pending
|
|
* mid = total_checks (past fast paths; toward / into deliver eval)
|
|
* eval = reached irq_deliver_eval label
|
|
* irq_deliv = g_irq_deliver_count (any HW IRQ exception taken) */
|
|
void psx_interrupt_check_path_diag(uint64_t *entry, uint64_t *fast_sr,
|
|
uint64_t *fast_none, uint64_t *mid,
|
|
uint64_t *eval, uint64_t *irq_deliv);
|
|
/* Interrupt check with the compiled guest PC to resume if a game-installed
|
|
* handler later RFEs to the sentinel outside the synchronous host window. */
|
|
void psx_check_interrupts_at(struct CPUState* cpu, uint32_t resume_pc);
|
|
int psx_interrupts_checked_at_current_cycle(uint32_t resume_pc);
|
|
/* Dispatch-entry check for re-enterable compiled PCs. De-dupes an immediately
|
|
* preceding check at the same guest PC/cycle so generated transfers can keep
|
|
* their explicit checks while dirty/interp-to-static entries get a boundary. */
|
|
void psx_check_interrupts_dispatch_entry(struct CPUState* cpu, uint32_t resume_pc);
|
|
|
|
/* Accumulate emitted PSX cycles toward the next VBlank trigger.
|
|
* 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). 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);
|
|
/* VBlank phase within the current frame (0 .. VBLANK_CYCLES-1). Persisted in
|
|
* BS_SEC_IRQ (and selfcheck's out-of-band latch) so resim keeps the snap's
|
|
* phase. Legacy 8-byte IRQ sections still rebase to 0 on load. */
|
|
uint32_t interrupts_get_cycles_since_vblank(void);
|
|
void interrupts_set_cycles_since_vblank(uint32_t v);
|
|
|
|
/* Cycle-budgeted precise event slicing: minimum guest-CPU-cycle distance to the
|
|
* next DELIVERABLE hardware interrupt (source raises its I_STAT bit AND that bit
|
|
* is unmasked in i_mask). UINT32_MAX if none scheduled. Single source of truth
|
|
* for the two-tier block executor's fast/slice decision. See PRECISE_IRQ_SLICE.md. */
|
|
uint32_t cycles_to_next_event(void);
|
|
|
|
/* Query whether we are currently inside an exception handler dispatch. */
|
|
int psx_get_in_exception(void);
|
|
|
|
/* Most recent block-leader IRQ-check guest PC / compiled resume latch.
|
|
* Used by the post-savestate freeze probe (vblank-time "where was the game"). */
|
|
uint32_t psx_last_irq_check_pc(void);
|
|
uint32_t psx_compiled_irq_resume_pc(void);
|
|
/* Soft-return / netplay BYE: drop sticky resume latches so rematch dig0 snaps
|
|
* cannot inherit a prior-match game PC (see pick_snap_resume_pc). */
|
|
void psx_irq_clear_resume_latches(void);
|
|
/* Arm compiled IRQ resume before top-level flush_resume / savestate dispatch
|
|
* so a latched I_STAT cannot take LEGACY_SENTINEL with take_pc=0. */
|
|
void psx_irq_arm_compiled_resume_pc(uint32_t pc);
|
|
uint64_t psx_last_irq_check_cycle(void);
|
|
uint64_t psx_interrupt_total_checks(void);
|
|
uint32_t psx_interrupt_fast_maintenance(void);
|
|
|
|
/* Snapshot internal counters for the freeze_check diagnostic. Any out_*
|
|
* pointer may be NULL. All counters are monotonically non-decreasing
|
|
* (dispatch_count resets each VBlank). */
|
|
void psx_get_freeze_diag(uint64_t *out_total_checks,
|
|
uint32_t *out_dispatch_count,
|
|
int *out_in_exception,
|
|
int *out_post_exc_cooldown,
|
|
uint64_t *out_exc_entries,
|
|
uint64_t *out_exc_reentry_blocks);
|
|
|
|
/* longjmp back to psx_check_interrupts, unwinding the exception handler.
|
|
* Called by ReturnFromException (B0:0x17 or SYSCALL(3)) when inside
|
|
* the exception handler to model real hardware's RFE+JR $k0 unwind. */
|
|
void psx_exception_longjmp(void);
|
|
|
|
/* longjmp back to psx_check_interrupts for RestoreState redirection.
|
|
*
|
|
* On real hardware, RestoreState (A0:0x14) restores all GPRs from a
|
|
* save buffer and does `jr $ra`, which jumps to the restored $ra —
|
|
* abandoning the current call stack. In our model, a plain `return;`
|
|
* would go back to the C caller (the chain walker), not to $ra.
|
|
*
|
|
* This function longjmps with code 2 (vs. 1 for ReturnFromException),
|
|
* so psx_check_interrupts can re-dispatch to cpu->pc while still in
|
|
* exception context. The redirected code (e.g., VSync callback loop)
|
|
* will eventually call ReturnFromException (longjmp code 1) to exit. */
|
|
void psx_restore_state_escape(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSXRECOMP_INTERRUPTS_H */
|