PGXP: shift the SXY FIFO shadows on an SXYP write

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-02 12:35:39 -07:00
parent dae2493af3
commit 227cb93500
3 changed files with 63 additions and 15 deletions
+8 -12
View File
@@ -2138,10 +2138,10 @@ extern "C" void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val) {
cpu->gte_data[29] = packed;
}
break;
/* Guest writes to the SXY FIFO drop the affected register shadows:
* we never model FIFO side effects on shadows — the shifted regs 12/13
* now describe words their shadows no longer match, and validation
* drops those on next use. */
/* Guest writes to the SXY FIFO: regs 12/13 overwrite their shadow, a
* reg 14 write overwrites SXY2 and its SXYP mirror, and a reg 15
* write is the FIFO push, which pgxp_gte_reg_written applies to the
* shadows exactly as the registers move. */
case 12: case 13:
cpu->gte_data[reg] = val;
if (!PSXRecomp::GTE::s_gte_replay_sandbox)
@@ -2150,20 +2150,16 @@ extern "C" void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val) {
case 14:
cpu->gte_data[14] = val;
cpu->gte_data[15] = val;
if (!PSXRecomp::GTE::s_gte_replay_sandbox) {
pgxp_gte_reg_written(14, val);
pgxp_gte_reg_written(15, val);
}
if (!PSXRecomp::GTE::s_gte_replay_sandbox)
pgxp_gte_reg_written(14, val); /* resets the SXYP mirror too */
break;
case 15:
cpu->gte_data[12] = cpu->gte_data[13];
cpu->gte_data[13] = cpu->gte_data[14];
cpu->gte_data[14] = val;
cpu->gte_data[15] = val;
if (!PSXRecomp::GTE::s_gte_replay_sandbox) {
pgxp_gte_reg_written(14, val);
pgxp_gte_reg_written(15, val);
}
if (!PSXRecomp::GTE::s_gte_replay_sandbox)
pgxp_gte_reg_written(15, val); /* FIFO push on the shadows */
break;
case 28: {
cpu->gte_data[9] = (val & 0x1Fu) << 7;
+23 -3
View File
@@ -206,6 +206,13 @@ static inline int32_t f_simm(uint32_t i) { return (int32_t)(int16_t)(i & 0xFFF
/* Memory-mode hooks: loads / stores */
/* ------------------------------------------------------------------------- */
/* SXY2 and SXYP are one register seen at two addresses: whichever the hook
* just filled, the other describes the same word. */
static inline void gte_sxy2_mirror(uint32_t reg) {
if (reg == 14) s_gte[15] = s_gte[14];
else if (reg == 15) s_gte[14] = s_gte[15];
}
extern "C" void psx_pgxp_load(struct CPUState *cpu, uint32_t instr,
uint32_t addr, uint32_t value) {
(void)cpu;
@@ -323,6 +330,7 @@ extern "C" void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr,
} else {
pv_reset(dst, value);
}
gte_sxy2_mirror(f_rt(instr));
return;
}
case 0x3A: { /* SWC2: [addr] <- gte[rt] */
@@ -360,9 +368,9 @@ extern "C" void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr,
} 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. */
/* An SXYP write (rd==15) already shifted the FIFO shadows
* (pgxp_gte_reg_written, from the GTE register write). */
gte_sxy2_mirror(f_rd(instr));
return;
}
case 0x02: { /* CFC2: control regs carry no
@@ -727,8 +735,20 @@ extern "C" void pgxp_gte_reg_written(int reg, uint32_t value) {
* machine state back, so its writes must not stick to the shadows). */
if (s_suppress != 0) return;
if (reg < 0 || reg > 31) return;
if (reg == 15) {
/* SXYP is a FIFO push: SXY1 moves to SXY0, SXY2 to SXY1, the value
* lands in SXY2 (psx-spx GTE "SXYP"). Engines draw quads from a
* projected-vertex table this way - load three corners, NCLIP, push
* the fourth through SXYP, NCLIP, store SXY0..2 into the packet - so
* the shadows shift with the registers. The MTC2 / LWC2 hook then
* copies the source shadow into the pushed slot. */
s_gte[12] = s_gte[13];
s_gte[13] = s_gte[14];
reg = 14;
}
pv_kill(&s_gte[reg]);
pv_reset(&s_gte[reg], value);
gte_sxy2_mirror(reg); /* SXY2 and SXYP: one register */
}
/* ------------------------------------------------------------------------- */
+32
View File
@@ -259,6 +259,38 @@ int main(void) {
}
pgxp_set_cpu_mode(0);
/* --- quad from a projected-vertex table (Spider-Man 0x8007C5D4):
* lwc2 SXY0..2 <- table; NCLIP; lwc2 SXYP <- table; NCLIP;
* swc2 SXY0..2 -> packet
* The SXYP write pushes the FIFO; the stored words are corners 1..3. --- */
{
const uint32_t TABLE = 0x80180100u, PKT = 0x80180200u;
uint32_t words[4];
for (uint32_t i = 0; i < 4; i++) {
words[i] = ((80u + i) << 16) | (160u + i);
pgxp_gte_push_sxy((int32_t)((160 + i) << 16) | 0x8000,
(int32_t)((80 + i) << 16) | 0x4000,
(uint16_t)(100 + i), words[i]);
psx_pgxp_cop2(nullptr, SWC2(14), words[i], TABLE + i * 4u);
}
for (uint32_t i = 0; i < 3; i++) { /* lwc2 SXY0..2 */
pgxp_gte_reg_written((int)(12 + i), words[i]);
psx_pgxp_cop2(nullptr, enc_i(0x32, 1, 12 + i, 0), words[i],
TABLE + i * 4u);
}
pgxp_gte_reg_written(15, words[3]); /* lwc2 SXYP: push */
psx_pgxp_cop2(nullptr, enc_i(0x32, 1, 15, 0), words[3], TABLE + 12u);
for (uint32_t i = 0; i < 3; i++)
psx_pgxp_cop2(nullptr, SWC2(12 + i), words[i + 1], PKT + i * 4u);
for (uint32_t i = 0; i < 3; i++) {
int32_t x, y; uint16_t z;
CHECK(lookup(PKT + i * 4u, words[i + 1], 161 + (int32_t)i,
81 + (int32_t)i, &x, &y, &z) == PGXP_SRC_DATAFLOW);
CHECK(x == ((int32_t)((161 + i) << 16) | 0x8000));
CHECK(z == 101 + i);
}
}
/* --- in-place ops read their source before the destination is reset --- */
pgxp_set_cpu_mode(1);
produce_at(ADDR_A);