Files
psxrecomp/runtime/src/pgxp.cpp
T
Matthew Stanley c564ccd1f1 runtime: route every RAM consumer through one live 2/8 MiB geometry
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 06:31:33 -07:00

821 lines
33 KiB
C++

/* 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 <cstdlib>
#include <cstring>
/* 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 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" 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_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;
}
/* ------------------------------------------------------------------------- */
/* 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" 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;
}
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 */
/* ------------------------------------------------------------------------- */
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 && 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. */
if ((px >> 16) != int_x || (py >> 16) != int_y) {
s_stats.trunc_reject++;
have = 0;
} else if (s_tolerance >= 0.0f) {
float dx = (float)(px - (int_x << 16)) * (1.0f / 65536.0f);
float dy = (float)(py - (int_y << 16)) * (1.0f / 65536.0f);
if (dx > s_tolerance || dy > s_tolerance) {
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;
}
/* ------------------------------------------------------------------------- */
/* 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;
}