/* pgxp.cpp — PGXP value-propagation engine (docs/ENHANCEMENTS.md G1.2/G1.3). * * CLEAN-ROOM implementation of the publicly documented PGXP technique * (psx-spx GTE docs + public design write-ups + our own G1 measurements). * The vendored duckstation/ (CC BY-NC-ND) and beetle-psx/ (GPL) trees are * black-box behavioral oracles only — no code from them appears here. * * Model * ----- * Every 32-bit word of guest RAM/scratchpad, every GPR (plus HI/LO), and * every GTE data register owns a shadow slot recording the sub-pixel screen * position that word carries (16.16 X/Y + projected SZ depth), the exact * guest word it describes (`value`), and per-half validity flags. RTPS/RTPT * fill the SXY shadow FIFO with the pre-truncation projection; the * psx_pgxp_* hooks copy shadows along with the data (loads, stores, COP2 * transfers, and — in cpu-mode — the arithmetic games use to repack vertex * halves); the GPU asks for the precise position of a GP0 vertex word by the * packet's RAM address. * * The single safety invariant: a shadow is only ever BELIEVED after * validation against the actual guest word it claims to describe. Anything * that writes guest state without a hook (DMA, memcpy loaders, un-hooked * instructions) simply leaves a stale shadow behind, and the next validation * drops it. We never model side effects — overwrite and validate only. The * one accepted hole (shared with the reference implementations): an untracked * writer storing the byte-identical word keeps the shadow alive, which is * harmless because the position it describes is still that word. * * Everything here is host-only and visual-only: guest-visible state is never * read back from shadows, shadows are dropped on savestate/rewind, and the * speculative native-validation bracket suppresses all recording. */ #include "pgxp.h" #include "pgxp_hooks.h" #include "cpu_state.h" #include "psx_memory.h" #include #include #include /* gte.cpp — position-cache fallback tier (ambiguity-gated, G1.4 exact table) */ extern "C" int gte_geometry_correction_lookup(uint32_t packed, int32_t *x16, int32_t *y16); /* ------------------------------------------------------------------------- */ /* Shadow storage */ /* ------------------------------------------------------------------------- */ enum { PGXP_F_VX = 1u << 0, /* low half (screen X) tracked */ PGXP_F_VY = 1u << 1, /* high half (screen Y) tracked */ PGXP_F_VZ = 1u << 2, /* projected depth rode along intact */ PGXP_F_VXY = PGXP_F_VX | PGXP_F_VY, }; struct PGXPValue { int32_t x16, y16; /* sub-pixel screen coords, 16.16 */ uint16_t z; /* projected SZ depth (perspective source), 0 = none */ uint16_t flags; uint32_t value; /* the guest word this shadow describes */ uint32_t gen; /* valid iff == s_gen (O(1) invalidate-all) */ }; /* Shadow covers the host RAM backing so the opt-in 8 MB map tracks its high * banks; retail sessions only ever touch the low 2 MiB of it. */ #define PGXP_RAM_WORDS (PSX_MAIN_RAM_BACKING_BYTES >> 2) #define PGXP_SCRATCH_WORDS (0x400u >> 2) /* 1 KB scratchpad */ #define PGXP_REG_HI 32 #define PGXP_REG_LO 33 static PGXPValue *s_ram = nullptr; /* lazily allocated, ~40 MB VA */ static PGXPValue s_scratch[PGXP_SCRATCH_WORDS]; static PGXPValue s_gpr[34]; /* 32 GPRs + HI + LO */ static PGXPValue s_gte[32]; /* GTE data registers */ static uint32_t s_gen = 1; static int s_enabled = 0; static int s_cpu_mode = 0; static float s_tolerance = 0.5f; /* user-validated seam clamp (G1.10) */ static int s_position_fallback = 1; /* G1.4 cache tier (G1.11 switch) */ static int s_preserve_projection = 0; /* exact-projection shadows (G1.11) */ static int s_culling = 0; /* precise NCLIP sign (G1.12) */ static uint32_t s_suppress = 0; static int s_deferred_invalidate = 0; /* Single hot-path gate for every hook. */ static int g_pgxp_active = 0; static inline void recompute_active(void) { g_pgxp_active = (s_enabled && s_suppress == 0) ? 1 : 0; } static PGXPStats s_stats; /* ------------------------------------------------------------------------- */ /* Lifecycle */ /* ------------------------------------------------------------------------- */ extern "C" void pgxp_invalidate_all(void) { if (s_suppress != 0) { s_deferred_invalidate = 1; return; } if (++s_gen == 0) { /* generation wrapped: physically clear so stale slots can't revive */ if (s_ram) std::memset(s_ram, 0, PGXP_RAM_WORDS * sizeof(PGXPValue)); std::memset(s_scratch, 0, sizeof(s_scratch)); std::memset(s_gpr, 0, sizeof(s_gpr)); std::memset(s_gte, 0, sizeof(s_gte)); s_gen = 1; } } extern "C" void pgxp_set_enabled(int enabled) { int resized = 0; if (enabled && !s_ram) { s_ram = (PGXPValue *)std::calloc(PGXP_RAM_WORDS, sizeof(PGXPValue)); if (s_ram) resized = 1; else enabled = 0; /* fail closed: stay faithful */ } /* Re-applying configuration must not invalidate every live shadow. */ if (s_enabled == (enabled ? 1 : 0) && !resized) return; s_enabled = enabled ? 1 : 0; pgxp_invalidate_all(); recompute_active(); } extern "C" int pgxp_enabled(void) { return s_enabled; } extern "C" int pgxp_active(void) { return g_pgxp_active; } extern "C" void pgxp_set_cpu_mode(int enabled) { s_cpu_mode = enabled ? 1 : 0; } extern "C" int pgxp_cpu_mode(void) { return s_cpu_mode; } extern "C" void pgxp_set_tolerance(float pixels) { s_tolerance = pixels; } extern "C" float pgxp_tolerance(void) { return s_tolerance; } extern "C" void pgxp_set_position_fallback(int enabled) { s_position_fallback = enabled ? 1 : 0; } extern "C" int pgxp_position_fallback(void) { return s_position_fallback; } extern "C" void pgxp_set_preserve_projection(int enabled) { s_preserve_projection = enabled ? 1 : 0; } extern "C" int pgxp_preserve_projection(void) { return s_preserve_projection; } extern "C" void pgxp_set_culling(int enabled) { s_culling = enabled ? 1 : 0; } extern "C" int pgxp_culling(void) { return s_culling; } static int s_mod_request = 0; static int s_mod_request_cpu = 0; static int s_mod_request_cull = 0; extern "C" void pgxp_mod_request(int enabled, int cpu_mode, int culling) { s_mod_request = enabled ? 1 : 0; s_mod_request_cpu = (enabled && cpu_mode) ? 1 : 0; s_mod_request_cull = (enabled && culling) ? 1 : 0; } extern "C" int pgxp_mod_requested(int *cpu_mode, int *culling) { if (cpu_mode) *cpu_mode = s_mod_request_cpu; if (culling) *culling = s_mod_request_cull; return s_mod_request; } extern "C" int pgxp_mod_request_take(int *cpu_mode, int *culling) { const int requested = pgxp_mod_requested(cpu_mode, culling); pgxp_mod_request(0, 0, 0); return requested; } extern "C" void pgxp_suppress_begin(void) { ++s_suppress; recompute_active(); } extern "C" void pgxp_suppress_end(void) { if (s_suppress != 0 && --s_suppress == 0) { if (s_deferred_invalidate) { s_deferred_invalidate = 0; pgxp_invalidate_all(); } recompute_active(); } } extern "C" void pgxp_get_stats(PGXPStats *out) { if (out) *out = s_stats; } extern "C" void pgxp_note_rect_bypass(int all_precise) { if (all_precise) s_stats.rect_bypass++; else s_stats.rect_partial++; } extern "C" void pgxp_note_nclip(int disagree, int corrected) { if (disagree) s_stats.nclip_disagree++; if (corrected) s_stats.nclip_corrected++; } extern "C" void pgxp_note_triangle(int precise) { if (precise >= 3) s_stats.tri_precise++; else if (precise > 0) s_stats.tri_mixed++; else s_stats.tri_native++; } /* ------------------------------------------------------------------------- */ /* Address mapping + validation */ /* ------------------------------------------------------------------------- */ /* Guest address -> shadow slot, or NULL for BIOS/MMIO/KSEG2 (untrackable). */ static inline PGXPValue *pgxp_ptr(uint32_t addr) { uint32_t m = addr & 0x1FFFFFFFu; if (m < PSX_MAIN_RAM_WINDOW_BYTES) /* RAM + its mirrors (live map) */ return s_ram ? &s_ram[psx_ram_canonical_offset(m) >> 2] : nullptr; if ((m & 0xFFFFFC00u) == 0x1F800000u) /* scratchpad */ return &s_scratch[(m & 0x3FCu) >> 2]; return nullptr; } static inline int pv_live(const PGXPValue *pv) { return pv && pv->gen == s_gen && (pv->flags & PGXP_F_VXY) != 0; } /* Drop whichever tracked halves no longer match the actual guest word; the * depth belongs to the whole vertex, so any half going stale kills it too. */ static inline void pv_validate(PGXPValue *pv, uint32_t actual) { if (pv->gen != s_gen) return; uint32_t diff = pv->value ^ actual; if ((pv->flags & PGXP_F_VX) && (diff & 0x0000FFFFu)) pv->flags &= (uint16_t)~(PGXP_F_VX | PGXP_F_VZ); if ((pv->flags & PGXP_F_VY) && (diff & 0xFFFF0000u)) pv->flags &= (uint16_t)~(PGXP_F_VY | PGXP_F_VZ); pv->value = actual; } /* Mark a slot as "known word, no precision" — keeps `value` current so later * half-merges stay keyed correctly. */ static inline void pv_reset(PGXPValue *pv, uint32_t value) { pv->x16 = 0; pv->y16 = 0; pv->z = 0; pv->flags = 0; pv->value = value; pv->gen = s_gen; } static inline void pv_kill(PGXPValue *pv) { pv->gen = 0; } /* ------------------------------------------------------------------------- */ /* Instruction field helpers */ /* ------------------------------------------------------------------------- */ static inline uint32_t f_op(uint32_t i) { return i >> 26; } static inline uint32_t f_rs(uint32_t i) { return (i >> 21) & 31u; } static inline uint32_t f_rt(uint32_t i) { return (i >> 16) & 31u; } static inline uint32_t f_rd(uint32_t i) { return (i >> 11) & 31u; } static inline uint32_t f_shamt(uint32_t i) { return (i >> 6) & 31u; } static inline uint32_t f_funct(uint32_t i) { return i & 63u; } static inline int32_t f_simm(uint32_t i) { return (int32_t)(int16_t)(i & 0xFFFFu); } /* ------------------------------------------------------------------------- */ /* Memory-mode hooks: loads / stores */ /* ------------------------------------------------------------------------- */ extern "C" void psx_pgxp_load(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) { (void)cpu; if (!g_pgxp_active) return; uint32_t rt = f_rt(instr); if (rt == 0) return; PGXPValue *dst = &s_gpr[rt]; switch (f_op(instr)) { case 0x23: { /* LW */ PGXPValue *src = pgxp_ptr(addr); if (src && src->gen == s_gen) { pv_validate(src, value); *dst = *src; } else { pv_reset(dst, value); } return; } case 0x21: /* LH */ case 0x25: { /* LHU */ PGXPValue *src = pgxp_ptr(addr); int hi_half = (addr >> 1) & 1; pv_reset(dst, value); /* The high half of the extended GPR is a known-exact constant * (0/-1 for LH, 0 for LHU): track it so re-packing via sll/or in * cpu-mode keeps working. */ dst->y16 = ((int32_t)value >> 16) << 16; dst->flags = PGXP_F_VY; if (src && src->gen == s_gen) { uint32_t actual_half = (value & 0xFFFFu) << (hi_half ? 16 : 0); uint32_t mask = hi_half ? 0xFFFF0000u : 0x0000FFFFu; uint16_t want = hi_half ? PGXP_F_VY : PGXP_F_VX; if ((src->flags & want) && ((src->value ^ actual_half) & mask) == 0) { dst->x16 = hi_half ? src->y16 : src->x16; dst->flags |= PGXP_F_VX; } } return; } default: /* LB/LBU/LWL/LWR: untrackable */ pv_reset(dst, value); return; } } extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) { (void)cpu; if (!g_pgxp_active) return; PGXPValue *dst = pgxp_ptr(addr); if (!dst) return; uint32_t rt = f_rt(instr); PGXPValue *src = (rt != 0) ? &s_gpr[rt] : nullptr; switch (f_op(instr)) { case 0x2B: { /* SW */ if (src && src->gen == s_gen) { pv_validate(src, value); *dst = *src; } else { pv_reset(dst, value); } return; } case 0x29: { /* SH */ int hi_half = (addr >> 1) & 1; uint32_t half = value & 0xFFFFu; if (dst->gen != s_gen) pv_reset(dst, half << (hi_half ? 16 : 0)); /* Patch the stored half into the tracked word; the other half's * validity (if any) survives untouched. Depth never survives a * half-write — the vertex it described no longer exists whole. */ if (hi_half) dst->value = (dst->value & 0x0000FFFFu) | (half << 16); else dst->value = (dst->value & 0xFFFF0000u) | half; dst->flags &= (uint16_t)~((hi_half ? PGXP_F_VY : PGXP_F_VX) | PGXP_F_VZ); dst->z = 0; if (src && src->gen == s_gen && (src->flags & PGXP_F_VX) && ((src->value ^ value) & 0xFFFFu) == 0) { if (hi_half) { dst->y16 = src->x16; dst->flags |= PGXP_F_VY; } else { dst->x16 = src->x16; dst->flags |= PGXP_F_VX; } } return; } case 0x28: { /* SB */ if (dst->gen != s_gen) return; /* nothing tracked: stay dead */ uint32_t shift = (addr & 3u) * 8u; dst->value = (dst->value & ~(0xFFu << shift)) | ((value & 0xFFu) << shift); dst->flags &= (uint16_t)~(((addr & 2u) ? PGXP_F_VY : PGXP_F_VX) | PGXP_F_VZ); dst->z = 0; return; } default: /* SWL/SWR: forget the word */ pv_kill(dst); return; } } /* ------------------------------------------------------------------------- */ /* Memory-mode hooks: COP2 transfers */ /* ------------------------------------------------------------------------- */ extern "C" void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, uint32_t value, uint32_t addr) { (void)cpu; if (!g_pgxp_active) return; switch (f_op(instr)) { case 0x32: { /* LWC2: gte[rt] <- [addr] */ PGXPValue *src = pgxp_ptr(addr); PGXPValue *dst = &s_gte[f_rt(instr)]; if (src && src->gen == s_gen) { pv_validate(src, value); *dst = *src; } else { pv_reset(dst, value); } return; } case 0x3A: { /* SWC2: [addr] <- gte[rt] */ PGXPValue *dst = pgxp_ptr(addr); if (!dst) return; PGXPValue *src = &s_gte[f_rt(instr)]; if (src->gen == s_gen) { pv_validate(src, value); *dst = *src; } else { pv_reset(dst, value); } return; } case 0x12: { /* COP2 register transfers */ switch (f_rs(instr)) { case 0x00: { /* MFC2: gpr[rt] <- gte[rd] */ uint32_t rt = f_rt(instr); if (rt == 0) return; PGXPValue *src = &s_gte[f_rd(instr)]; if (src->gen == s_gen) { pv_validate(src, value); s_gpr[rt] = *src; } else { pv_reset(&s_gpr[rt], value); } return; } case 0x04: { /* MTC2: gte[rd] <- gpr[rt] */ uint32_t rt = f_rt(instr); PGXPValue *dst = &s_gte[f_rd(instr)]; if (rt != 0 && s_gpr[rt].gen == s_gen) { pv_validate(&s_gpr[rt], value); *dst = s_gpr[rt]; } else { pv_reset(dst, value); } /* An SXYP write (rd==15) shifts the hardware FIFO; we do not * model it — regs 12..14 now describe words they no longer * match, and validation drops them on next use. */ return; } case 0x02: { /* CFC2: control regs carry no positions */ uint32_t rt = f_rt(instr); if (rt != 0) pv_reset(&s_gpr[rt], value); return; } default: /* CTC2: nothing shadowed */ return; } } default: return; } } /* ------------------------------------------------------------------------- */ /* CPU-mode hooks: arithmetic (tier 2, default off) */ /* ------------------------------------------------------------------------- */ /* One 16-bit operand component in 16.16: a tracked half contributes its * sub-pixel value; an untracked half contributes its exact integer value * (adding an exact offset to a tracked coordinate keeps the fraction). */ static inline int32_t comp16(const PGXPValue *pv, uint32_t value, int hi_half) { if (pv && pv->gen == s_gen) { if (!hi_half && (pv->flags & PGXP_F_VX) && ((pv->value ^ value) & 0x0000FFFFu) == 0) return pv->x16; if (hi_half && (pv->flags & PGXP_F_VY) && ((pv->value ^ value) & 0xFFFF0000u) == 0) return pv->y16; } return ((int32_t)(int16_t)(hi_half ? (value >> 16) : value)) << 16; } static inline int comp_tracked(const PGXPValue *pv, uint32_t value, int hi_half) { if (!pv || pv->gen != s_gen) return 0; if (!hi_half) return (pv->flags & PGXP_F_VX) && ((pv->value ^ value) & 0x0000FFFFu) == 0; return (pv->flags & PGXP_F_VY) && ((pv->value ^ value) & 0xFFFF0000u) == 0; } /* Half-wise add/sub is only meaningful when the guest result shows no carry * crossed the half boundary; otherwise the halves did not combine * independently and the shadow must not pretend they did. */ static inline int halves_independent(uint32_t a, uint32_t b, uint32_t r, int sub) { uint32_t lo = sub ? ((a & 0xFFFFu) - (b & 0xFFFFu)) : ((a & 0xFFFFu) + (b & 0xFFFFu)); return ((lo ^ r) & 0xFFFFu) == 0 && ((((sub ? a - b : a + b)) ^ r) == 0) && ((lo >> 16) == 0); /* no carry/borrow out of low */ } extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2) { (void)cpu; if (!g_pgxp_active) return; uint32_t op = f_op(instr); uint32_t dst_reg; PGXPValue *a = nullptr, *b = nullptr; /* source shadows (may be 0) */ if (op == 0) { /* SPECIAL */ dst_reg = f_rd(instr); uint32_t rs = f_rs(instr), rt = f_rt(instr); uint32_t funct = f_funct(instr); if (funct == 0x11 || funct == 0x13) { /* MTHI / MTLO */ PGXPValue *hl = &s_gpr[funct == 0x11 ? PGXP_REG_HI : PGXP_REG_LO]; if (s_cpu_mode && rs != 0 && s_gpr[rs].gen == s_gen) { pv_validate(&s_gpr[rs], result); *hl = s_gpr[rs]; } else pv_reset(hl, result); return; } if (dst_reg == 0) return; PGXPValue *dst = &s_gpr[dst_reg]; switch (funct) { case 0x10: /* MFHI */ if (s_cpu_mode && s_gpr[PGXP_REG_HI].gen == s_gen) { pv_validate(&s_gpr[PGXP_REG_HI], result); *dst = s_gpr[PGXP_REG_HI]; } else pv_reset(dst, result); return; case 0x12: /* MFLO */ if (s_cpu_mode && s_gpr[PGXP_REG_LO].gen == s_gen) { pv_validate(&s_gpr[PGXP_REG_LO], result); *dst = s_gpr[PGXP_REG_LO]; } else pv_reset(dst, result); return; case 0x00: case 0x02: case 0x03: /* SLL / SRL / SRA */ case 0x04: case 0x06: case 0x07: { /* SLLV / SRLV / SRAV */ uint32_t sh = (funct < 4) ? f_shamt(instr) : (s2 & 31u); uint32_t src_reg = rt; PGXPValue *src = (src_reg != 0) ? &s_gpr[src_reg] : nullptr; int right = (funct & 3u) != 0; if (!s_cpu_mode || sh != 16 || !src || src->gen != s_gen) { pv_reset(dst, result); return; } pv_validate(src, s1); pv_reset(dst, result); if (!right) { /* << 16: low comp -> Y */ if (src->flags & PGXP_F_VX) { dst->y16 = src->x16; dst->flags |= PGXP_F_VY; } dst->x16 = 0; dst->flags |= PGXP_F_VX; /* low half exactly zero */ } else { /* >> 16: high comp -> X */ if (src->flags & PGXP_F_VY) { dst->x16 = src->y16; dst->flags |= PGXP_F_VX; } dst->y16 = ((int32_t)result >> 16) << 16; /* 0 or sign fill */ dst->flags |= PGXP_F_VY; } return; } case 0x20: case 0x21: /* ADD / ADDU */ case 0x22: case 0x23: /* SUB / SUBU */ case 0x25: { /* OR */ /* MOVE idioms first — they matter even without full cpu-mode * arithmetic, and they are exact. */ int is_or = (funct == 0x25); int is_sub = (funct == 0x22 || funct == 0x23); if ((is_or || !is_sub) && rt == 0) { /* op rd, rs, $zero */ if (rs != 0 && s_gpr[rs].gen == s_gen) { pv_validate(&s_gpr[rs], result); *dst = s_gpr[rs]; } else pv_reset(dst, result); return; } if ((is_or || !is_sub) && rs == 0) { /* op rd, $zero, rt */ if (rt != 0 && s_gpr[rt].gen == s_gen) { pv_validate(&s_gpr[rt], result); *dst = s_gpr[rt]; } else pv_reset(dst, result); return; } if (!s_cpu_mode) { pv_reset(dst, result); return; } a = (rs != 0) ? &s_gpr[rs] : nullptr; b = (rt != 0) ? &s_gpr[rt] : nullptr; if (is_or) { /* Merge disjoint halves (the lhu/sll/or repack pattern). */ if ((s1 & s2) != 0) { pv_reset(dst, result); return; } pv_reset(dst, result); const PGXPValue *xsrc; uint32_t xval; if ((s1 & 0xFFFFu) != 0) { xsrc = a; xval = s1; } else { xsrc = b; xval = s2; } const PGXPValue *ysrc; uint32_t yval; if ((s1 & 0xFFFF0000u) != 0) { ysrc = a; yval = s1; } else { ysrc = b; yval = s2; } if (comp_tracked(xsrc, xval, 0)) { dst->x16 = xsrc->x16; dst->flags |= PGXP_F_VX; } else if ((result & 0xFFFFu) == 0) { dst->x16 = 0; dst->flags |= PGXP_F_VX; } if (comp_tracked(ysrc, yval, 1)) { dst->y16 = ysrc->y16; dst->flags |= PGXP_F_VY; } else if ((result & 0xFFFF0000u) == 0) { dst->y16 = 0; dst->flags |= PGXP_F_VY; } return; } /* add/sub: require at least one tracked component and halves * that combined independently (no cross-half carry). */ if (!halves_independent(s1, s2, result, is_sub) || (!comp_tracked(a, s1, 0) && !comp_tracked(a, s1, 1) && !comp_tracked(b, s2, 0) && !comp_tracked(b, s2, 1))) { pv_reset(dst, result); return; } pv_reset(dst, result); dst->x16 = is_sub ? comp16(a, s1, 0) - comp16(b, s2, 0) : comp16(a, s1, 0) + comp16(b, s2, 0); dst->y16 = is_sub ? comp16(a, s1, 1) - comp16(b, s2, 1) : comp16(a, s1, 1) + comp16(b, s2, 1); dst->flags = PGXP_F_VXY; return; } default: pv_reset(dst, result); return; } } /* immediates */ dst_reg = f_rt(instr); if (dst_reg == 0) return; PGXPValue *dst = &s_gpr[dst_reg]; uint32_t rs = f_rs(instr); switch (op) { case 0x0F: { /* LUI: both halves exact */ pv_reset(dst, result); dst->x16 = 0; dst->y16 = ((int32_t)result >> 16) << 16; dst->flags = PGXP_F_VXY; return; } case 0x08: case 0x09: { /* ADDI / ADDIU */ int32_t imm = f_simm(instr); if (imm == 0) { /* MOVE idiom */ if (rs != 0 && s_gpr[rs].gen == s_gen) { pv_validate(&s_gpr[rs], result); *dst = s_gpr[rs]; } else pv_reset(dst, result); return; } a = (rs != 0) ? &s_gpr[rs] : nullptr; /* A negative immediate is a subtraction of its magnitude: checking * carry on the sign-extended form would reject nearly every -N. */ { int neg = imm < 0; uint32_t mag = (uint32_t)(neg ? -imm : imm); if (!s_cpu_mode || !halves_independent(s1, mag, result, neg) || (!comp_tracked(a, s1, 0) && !comp_tracked(a, s1, 1))) { pv_reset(dst, result); return; } int32_t dx = (int32_t)(mag & 0xFFFFu) << 16; pv_reset(dst, result); dst->x16 = comp16(a, s1, 0) + (neg ? -dx : dx); dst->y16 = comp16(a, s1, 1); /* no carry crossed: Y untouched */ dst->flags = PGXP_F_VXY; } return; } case 0x0D: { /* ORI */ uint32_t imm = instr & 0xFFFFu; if (imm == 0 || (s_cpu_mode && (s1 & 0xFFFFu) == 0)) { a = (rs != 0) ? &s_gpr[rs] : nullptr; pv_reset(dst, result); if (comp_tracked(a, s1, 1)) { dst->y16 = a->y16; dst->flags |= PGXP_F_VY; } if (imm == 0) { if (comp_tracked(a, s1, 0)) { dst->x16 = a->x16; dst->flags |= PGXP_F_VX; } } else { /* exact constant low half */ dst->x16 = ((int32_t)(int16_t)imm) << 16; dst->flags |= PGXP_F_VX; } return; } pv_reset(dst, result); return; } default: /* ANDI/XORI/SLTI/... */ pv_reset(dst, result); return; } } extern "C" void psx_pgxp_muldiv(struct CPUState *cpu, uint32_t instr, uint32_t hi, uint32_t lo, uint32_t s1, uint32_t s2) { (void)cpu; (void)instr; (void)s1; (void)s2; if (!g_pgxp_active) return; /* Products/quotients of screen coordinates are not screen coordinates: * record the results as known-but-imprecise so MFHI/MFLO stay honest. */ pv_reset(&s_gpr[PGXP_REG_HI], hi); pv_reset(&s_gpr[PGXP_REG_LO], lo); } /* ------------------------------------------------------------------------- */ /* GTE producer */ /* ------------------------------------------------------------------------- */ extern "C" int pgxp_project_precise(int64_t mac1, int64_t mac2, int64_t mac3, int shift, int32_t ir1, int32_t ir2, uint32_t sz3, uint32_t h, int32_t ofx, int32_t ofy, int64_t x_num, int64_t x_den, int32_t *x16, int32_t *y16) { /* Qualify exactly the vertices whose guest projection is the plain * IR * (H / SZ3) of the continuous one: sf=1, no IR1/IR2 clamp (the * stored IR equals MAC >> 12), SZ3 unclamped and nonzero, and no UNR * divide overflow (the GTE saturates the quotient when H >= 2*SZ3). */ if (shift != 12 || x_den <= 0) return 0; if ((mac1 >> 12) != (int64_t)ir1 || (mac2 >> 12) != (int64_t)ir2) return 0; const int64_t z_int = mac3 >> 12; if (z_int <= 0 || z_int > 0xFFFF || (uint32_t)z_int != sz3) return 0; if ((int64_t)h >= 2 * z_int) return 0; /* (MAC/4096) * H / (MAC3/4096): the 4096s cancel. Double keeps ~52 bits, * far below the 1/65536 px the shadow carries. Host-only and visual-only, * so cross-platform bit equality of this value is not required. */ const double k = (double)h * 65536.0 / (double)mac3; double fx = (double)ofx + (double)mac1 * k * (double)x_num / (double)x_den; double fy = (double)ofy + (double)mac2 * k; /* Same transport bound as the IR path (gte.cpp): an extreme value must * not wrap an int32 and masquerade as an on-screen shadow. */ const double lim = 4096.0 * 65536.0; if (fx < -lim) fx = -lim; if (fx > lim - 1.0) fx = lim - 1.0; if (fy < -lim) fy = -lim; if (fy > lim - 1.0) fy = lim - 1.0; *x16 = (int32_t)std::floor(fx); *y16 = (int32_t)std::floor(fy); return 1; } /* Defined below with the GPU consumer. */ static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half); extern "C" int pgxp_ppp_accept(int32_t x16, int32_t y16, uint32_t packed) { const int16_t hx = (int16_t)(packed & 0xFFFFu); const int16_t hy = (int16_t)(packed >> 16); if (pgxp_agrees(x16, hx, hx) && pgxp_agrees(y16, hy, hy)) { s_stats.ppp_produced++; return 1; } s_stats.ppp_window_fallback++; return 0; } extern "C" void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3, uint32_t packed) { if (!g_pgxp_active) return; s_stats.produced++; s_gte[12] = s_gte[13]; s_gte[13] = s_gte[14]; PGXPValue *pv = &s_gte[14]; pv->x16 = x16; pv->y16 = y16; pv->z = sz3; pv->flags = (uint16_t)(PGXP_F_VXY | (sz3 != 0 ? PGXP_F_VZ : 0)); pv->value = packed; pv->gen = s_gen; s_gte[15] = *pv; /* SXYP mirrors SXY2 */ } extern "C" int pgxp_get_gte_sxy(uint32_t index, int32_t *x16, int32_t *y16) { return pgxp_get_gte_sxy_checked(index, 0u, 0, x16, y16); } extern "C" int pgxp_get_gte_sxy_checked(uint32_t index, uint32_t expect, int check, int32_t *x16, int32_t *y16) { if (index >= 4) return 0; const PGXPValue *pv = &s_gte[12 + index]; if (!pv_live(pv) || (pv->flags & PGXP_F_VXY) != PGXP_F_VXY) return 0; if (check && pv->value != expect) return 0; if (x16) *x16 = pv->x16; if (y16) *y16 = pv->y16; return 1; } /* Defined below with the GPU consumer. */ static inline int pgxp_accept(int32_t px, int32_t py, int32_t int_x, int32_t int_y, uint32_t word); extern "C" int pgxp_gte_nclip_precise(const uint32_t sxy[3], int64_t *cross) { if (!g_pgxp_active) return 0; int64_t x[3], y[3]; for (int i = 0; i < 3; i++) { const PGXPValue *pv = &s_gte[12 + i]; if (!pv_live(pv) || (pv->flags & PGXP_F_VXY) != PGXP_F_VXY || pv->value != sxy[i]) return 0; /* The GPU parses the packet half as the same signed integer for every * value the GTE can produce (its saturation limits are -0x400 and * 0x3FF), so the register half stands in for the native parse. */ if (pgxp_accept(pv->x16, pv->y16, (int16_t)(sxy[i] & 0xFFFFu), (int16_t)(sxy[i] >> 16), sxy[i]) != 0) return 0; x[i] = pv->x16; y[i] = pv->y16; } /* NCLIP's determinant, sx0*(sy1-sy2) + sx1*(sy2-sy0) + sx2*(sy0-sy1), * on 16.16 positions. Positions are bounded to +-2^28 (the transport * clamp), so every product fits in 64 bits. */ *cross = (x[1] - x[0]) * (y[2] - y[0]) - (y[1] - y[0]) * (x[2] - x[0]); s_stats.nclip_precise++; return 1; } extern "C" void pgxp_gte_reg_written(int reg, uint32_t value) { /* Invalidation-class bookkeeping: runs even with the engine disarmed so * seeded/leftover shadows can never outlive a guest register write. Only * the suppression bracket skips it (the speculative pass rolls the * machine state back, so its writes must not stick to the shadows). */ if (s_suppress != 0) return; if (reg < 0 || reg > 31) return; pv_kill(&s_gte[reg]); pv_reset(&s_gte[reg], value); } /* ------------------------------------------------------------------------- */ /* GPU consumer */ /* ------------------------------------------------------------------------- */ /* Truncation agreement for one axis. `native` is the GPU's parse of the * packet half, `half` the raw 16-bit half in the word. Exact (integer part * equals the native parse) unless preserve-projection is on; then the precise * position may sit a bounded distance from it (PGXP_PPP_AGREE_*), except * when the half is at or beyond the GTE saturation limits: there the guest * integer is a clamp that says nothing about the vertex, or (beyond them) a * CPU-modified word the GPU's 11-bit parse wraps. */ static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half) { if (!s_preserve_projection || half <= -0x400 || half >= 0x3FF) return (p16 >> 16) == native; const int64_t d = (int64_t)p16 - (int64_t)native * 65536; return d > -(int64_t)PGXP_PPP_AGREE_BELOW * 65536 && d < (int64_t)PGXP_PPP_AGREE_ABOVE * 65536; } /* The consumer-side safeguards on one candidate position: truncation * agreement on both axes, then the tolerance clamp. Returns 0 when accepted, * 1 for a truncation reject, 2 for a tolerance reject. Shared by the GPU * lookup and the precise NCLIP so culling and drawing believe exactly the * same vertices. */ static inline int pgxp_accept(int32_t px, int32_t py, int32_t int_x, int32_t int_y, uint32_t word) { if (!pgxp_agrees(px, int_x, (int16_t)(word & 0xFFFFu)) || !pgxp_agrees(py, int_y, (int16_t)(word >> 16))) return 1; if (s_tolerance >= 0.0f) { float dx = (float)((int64_t)px - (int64_t)int_x * 65536) * (1.0f / 65536.0f); float dy = (float)((int64_t)py - (int64_t)int_y * 65536) * (1.0f / 65536.0f); if (std::fabs(dx) > s_tolerance || std::fabs(dy) > s_tolerance) return 2; } return 0; } extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word, int32_t int_x, int32_t int_y, int32_t *x16, int32_t *y16, uint16_t *sz) { s_stats.lookups++; int32_t px = 0, py = 0; uint16_t pz = 0; int have = 0; if (s_enabled && addr != 0xFFFFFFFFu) { PGXPValue *pv = pgxp_ptr(addr); if (pv && pv->gen == s_gen && (pv->flags & PGXP_F_VXY) != 0) { if (pv->value == packet_word && (pv->flags & PGXP_F_VXY) == PGXP_F_VXY) { px = pv->x16; py = pv->y16; pz = (pv->flags & PGXP_F_VZ) ? pv->z : 0; have = PGXP_SRC_DATAFLOW; } else { s_stats.value_mismatch++; } } } if (!have && s_position_fallback && gte_geometry_correction_lookup(packet_word, &px, &py)) { pz = 0; /* fallback never carries depth */ have = PGXP_SRC_FALLBACK; } if (have) { /* Truncation agreement: the GPU parsed 11-bit integers out of the * packet; a precise position whose integer part disagrees (a * wrapped/CPU-modified coordinate) must not be believed. Then the * tolerance clamp. */ const int why = pgxp_accept(px, py, int_x, int_y, packet_word); if (why == 1) { s_stats.trunc_reject++; have = 0; } else if (why == 2) { s_stats.tolerance_reject++; have = 0; } } if (!have) { s_stats.native++; *x16 = int_x << 16; *y16 = int_y << 16; *sz = 0; return PGXP_SRC_NATIVE; } if (have == PGXP_SRC_DATAFLOW) s_stats.dataflow_hit++; else s_stats.fallback_hit++; if (pz != 0) s_stats.w_valid++; *x16 = px; *y16 = py; *sz = pz; return have; } extern "C" int pgxp_probe_precise_vertex(uint32_t addr, uint32_t packet_word, int32_t int_x, int32_t int_y) { const PGXPStats saved = s_stats; int32_t x16, y16; uint16_t sz; const int src = pgxp_get_precise_vertex(addr, packet_word, int_x, int_y, &x16, &y16, &sz); s_stats = saved; return src; } /* ------------------------------------------------------------------------- */ /* Legacy v14 SWC2 tracker — kept as the base-flavour feed */ /* ------------------------------------------------------------------------- */ /* Emitted at every swc2 since ABI v14 (all flavours, all backends, overlay * DLLs). In the pgxp flavour psx_pgxp_cop2 supersedes it for the same swc2 — * the double write is idempotent (same source shadow, same destination). */ extern "C" void pgxp_store_gte_reg(uint32_t addr, uint8_t reg) { if (!g_pgxp_active) return; PGXPValue *dst = pgxp_ptr(addr); if (!dst) return; const PGXPValue *src = &s_gte[reg & 31u]; if (src->gen != s_gen) return; s_stats.swc2_stores++; *dst = *src; } /* ------------------------------------------------------------------------- */ /* Test accessors */ /* ------------------------------------------------------------------------- */ extern "C" void pgxp_test_seed_gte_sxy(uint32_t index, uint32_t packed, int32_t x16, int32_t y16, uint16_t z, int valid) { if (index >= 4) return; PGXPValue *pv = &s_gte[12 + index]; pv->x16 = x16; pv->y16 = y16; pv->z = z; pv->value = packed; pv->flags = (uint16_t)(PGXP_F_VXY | (z != 0 ? PGXP_F_VZ : 0)); pv->gen = valid ? s_gen : 0; } extern "C" void pgxp_test_get_gte_sxy(uint32_t index, uint32_t *packed, int32_t *x16, int32_t *y16, uint16_t *z, uint8_t *valid) { if (index >= 4) return; const PGXPValue *pv = &s_gte[12 + index]; if (packed) *packed = pv->value; if (x16) *x16 = pv->x16; if (y16) *y16 = pv->y16; if (z) *z = pv->z; if (valid) *valid = pv_live(pv) ? 1 : 0; } extern "C" uint32_t pgxp_test_generation(void) { return s_gen; } extern "C" uint32_t pgxp_test_suppress_depth(void) { return s_suppress; } extern "C" int pgxp_test_active(void) { return g_pgxp_active; } extern "C" void pgxp_test_set_generation(uint32_t gen) { s_gen = gen; } /* Address-keyed depth lookup for the shipped perspective-texturing path * (gpu.c prepare_texture_triangle). Same contract as the retired hashed * table: hit only when the tracked word matches the packet word exactly. */ extern "C" int pgxp_load_precise_word(uint32_t addr, uint32_t packed, int32_t *x16, int32_t *y16, uint16_t *z) { if (!s_enabled) return 0; PGXPValue *pv = pgxp_ptr(addr); if (!pv || pv->gen != s_gen || pv->value != packed || (pv->flags & PGXP_F_VXY) != PGXP_F_VXY) return 0; if (x16) *x16 = pv->x16; if (y16) *y16 = pv->y16; if (z) *z = (pv->flags & PGXP_F_VZ) ? pv->z : 0; return (pv->flags & PGXP_F_VZ) && pv->z != 0; }