From f9c7bb0a1c4c7709dca38f8e4fac614bf2bc5829 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Tue, 6 Oct 2026 13:35:43 -0700 Subject: [PATCH] PGXP: carry precision through GTE-as-multiplier arithmetic and lwl/lwr word copies Fixes Ape Escape terrain seams: CPU-built subdivision midpoints and lwl/lwr-copied grid corners drew native next to precise neighbours. Adds pgxp_tri_ring/pgxp_store_ring. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/ENHANCEMENTS.md | 34 + generated/OpenBIOS.emitter.sha | 2 +- generated/SCPH1001.emitter.sha | 2 +- generated/SCPH1001_full.c | 209 ++++++ overlay_toolchain/include/overlay_api.h | 5 +- overlay_toolchain/include/pgxp.h | 71 ++ recompiler/src/pgxp_hook_emitter.cpp | 6 +- .../tests/emitter_directive_line_test.cpp | 27 + runtime/include/overlay_api.h | 5 +- runtime/include/pgxp.h | 71 ++ runtime/src/debug_server.c | 123 ++++ runtime/src/dirty_ram_interp.c | 1 + runtime/src/gpu.c | 22 +- runtime/src/gte.cpp | 16 + runtime/src/pgxp.cpp | 604 ++++++++++++++++-- runtime/src/psx_interpreter.c | 1 + runtime/tests/gpu_exec_stubs.inc | 1 + runtime/tests/test_gpu_gp0_history_guard.py | 2 + runtime/tests/test_pgxp.cpp | 133 ++++ tools/bios_emitter_fingerprint.cmake | 2 + 20 files changed, 1287 insertions(+), 50 deletions(-) diff --git a/docs/ENHANCEMENTS.md b/docs/ENHANCEMENTS.md index d33d4e48..2a7ab42c 100644 --- a/docs/ENHANCEMENTS.md +++ b/docs/ENHANCEMENTS.md @@ -1176,6 +1176,40 @@ default-on mod corrected textures, unticking it did nothing, and ticking it kept texture correction on after the player switched the mod off. Netplay then runs with PGXP fully off. +### G1.13 — CPU-built vertices: the GTE as a multiplier, and word copies (2026-10-06) + +**Symptom.** Ape Escape with the PGXP mod: thin light/dark lines along the +ground's grid edges at high internal resolution (bead beads-eio.3.274; present +on the shipped f7f0ad10 pin, not a regression). Isolation on one savestate: +PGXP off, or `geometry` off with texture correction on, removes them; texture +correction off, nearest filtering, frame smoothing off do not. The +`pgxp_tri_ring` showed whole ground cells drawn native next to precise +neighbours and decals (0.9 px apart on the same projected vertex). + +**Two producers dropped precision.** The `pgxp_store_ring` pinned both: +1. *Subdivided near cells* (0x8001DB74). Edge midpoints are the projection of + the 3D midpoint, computed on the CPU: CTC2 packs two tracked X (or Y) + halves into a light-matrix row, MVMVA weights them by the vertex depths, + MFC2 reads MAC, ADDU adds a rounding bias, DIV by the depth sum, MFLO, SH. + CTC2 / CFC2 had no hook and MULT/DIV recorded imprecise results. +2. *The terrain grid* (0x80044B00) copies projected corners with the + unaligned-copy idiom `lwl/lwr` + `swl/swr` on aligned addresses, which the + engine treated as untrackable. + +**Fix (cpu-mode).** A scalar tier: registers carry an optional full-range +precise value beside their half shadows. CTC2 / CFC2 are hooked (codegen 18; +the BIOS emitter fingerprint now covers `pgxp_hook_emitter`), MVMVA recomputes +its rows from the precise matrix halves / IR / translation and leaves scalars +on MAC1-3 / IR1-3, and MFC2, ADD/SUB(I)(U), shifts, MULT/DIV, MF/MT HI/LO and +OR-moves carry them; SH / SW turn a scalar that fits back into a coordinate +half. The guest rounded that arithmetic its own way, so such halves are +marked derived (`PGXP_F_DX/DY`): the GPU believes them within (-1, +2) px of +the guest integer and the tolerance clamp ignores the guest's own rounding +pixel. LWL at byte 3 / LWR at byte 0 (and SWL / SWR likewise) move the whole +word and copy its shadow. Result in the Fossil Field scene: every ground +triangle dataflow-precise, `tri_mixed` from ~10% of triangles to 474 of 2.05M, +seams gone (`test_pgxp` pins the sequence). + ## IR1 — Internal resolution presets (Native … 8K) and the GL scale ceiling (2026-09-26) **What the player gets.** Settings → Display → **Internal resolution**: Native, diff --git a/generated/OpenBIOS.emitter.sha b/generated/OpenBIOS.emitter.sha index 905ac3c9..7bc92a6e 100644 --- a/generated/OpenBIOS.emitter.sha +++ b/generated/OpenBIOS.emitter.sha @@ -1 +1 @@ -13265e961396ac707b39701fc5787ae62c34e9c92c4b8b7fe3ea69e1cc86791e +26dc9778f248831f353bb001d9cd64b928c57b566612c2df289ce1efc12db081 diff --git a/generated/SCPH1001.emitter.sha b/generated/SCPH1001.emitter.sha index 27a95c4f..cefa0439 100644 --- a/generated/SCPH1001.emitter.sha +++ b/generated/SCPH1001.emitter.sha @@ -1 +1 @@ -2b30598a6f3db8c1a0c03eaa1f121d241fa53bd69e789ae202caf4d51ebe7ab6 +f15f850430c9c2a540f0155f4c1cc049ce2b020ad0836f86d5f8e98d603ac397 diff --git a/generated/SCPH1001_full.c b/generated/SCPH1001_full.c index d6e21052..71473e05 100644 --- a/generated/SCPH1001_full.c +++ b/generated/SCPH1001_full.c @@ -661154,6 +661154,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 29, cpu->gpr[8]); + PGXP_COP2(0x48C8E800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B70Cu); #endif @@ -661186,6 +661187,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 30, cpu->gpr[8]); + PGXP_COP2(0x48C8F000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B718u); #endif @@ -661218,6 +661220,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 26, cpu->gpr[8]); + PGXP_COP2(0x48C8D000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B724u); #endif @@ -661250,6 +661253,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 27, cpu->gpr[8]); + PGXP_COP2(0x48C8D800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B730u); #endif @@ -661279,6 +661283,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[28] = cpu->gpr[8]; + PGXP_COP2(0x48C8E000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B73Cu); #endif @@ -661302,6 +661307,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[24] = cpu->gpr[0]; + PGXP_COP2(0x48C0C000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B744u); #endif @@ -661314,6 +661320,7 @@ label_BFC336F0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[25] = cpu->gpr[0]; + PGXP_COP2(0x48C0C800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004B748u); #endif @@ -666362,6 +666369,7 @@ label_BFC33DA4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[21] = cpu->gpr[8]; + PGXP_COP2(0x48C8A800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BDBCu); #endif @@ -666377,6 +666385,7 @@ label_BFC33DA4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[22] = cpu->gpr[9]; + PGXP_COP2(0x48C9B000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BDC0u); #endif @@ -666389,6 +666398,7 @@ label_BFC33DA4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[23] = cpu->gpr[10]; + PGXP_COP2(0x48CAB800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BDC4u); #endif @@ -666680,6 +666690,7 @@ label_BFC33E20: psx_gte_stall(cpu); #endif cpu->gte_ctrl[21] = cpu->gpr[8]; + PGXP_COP2(0x48C8A800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BE2Cu); #endif @@ -666695,6 +666706,7 @@ label_BFC33E20: psx_gte_stall(cpu); #endif cpu->gte_ctrl[22] = cpu->gpr[9]; + PGXP_COP2(0x48C9B000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BE30u); #endif @@ -667411,6 +667423,7 @@ label_BFC33F00: psx_gte_stall(cpu); #endif cpu->gte_ctrl[13] = cpu->gpr[4]; + PGXP_COP2(0x48C46800u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF0Cu); #endif @@ -667426,6 +667439,7 @@ label_BFC33F00: psx_gte_stall(cpu); #endif cpu->gte_ctrl[14] = cpu->gpr[5]; + PGXP_COP2(0x48C57000u, cpu->gpr[5], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF10u); #endif @@ -667438,6 +667452,7 @@ label_BFC33F00: psx_gte_stall(cpu); #endif cpu->gte_ctrl[15] = cpu->gpr[6]; + PGXP_COP2(0x48C67800u, cpu->gpr[6], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF14u); #endif @@ -667517,6 +667532,7 @@ label_BFC33F20: psx_gte_stall(cpu); #endif cpu->gte_ctrl[21] = cpu->gpr[4]; + PGXP_COP2(0x48C4A800u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF2Cu); #endif @@ -667532,6 +667548,7 @@ label_BFC33F20: psx_gte_stall(cpu); #endif cpu->gte_ctrl[22] = cpu->gpr[5]; + PGXP_COP2(0x48C5B000u, cpu->gpr[5], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF30u); #endif @@ -667544,6 +667561,7 @@ label_BFC33F20: psx_gte_stall(cpu); #endif cpu->gte_ctrl[23] = cpu->gpr[6]; + PGXP_COP2(0x48C6B800u, cpu->gpr[6], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF34u); #endif @@ -667855,6 +667873,7 @@ label_BFC33F80: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[4]; + PGXP_COP2(0x48C40000u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF80u); #endif @@ -667867,6 +667886,7 @@ label_BFC33F80: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[5]; + PGXP_COP2(0x48C51000u, cpu->gpr[5], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF84u); #endif @@ -667879,6 +667899,7 @@ label_BFC33F80: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[6]); + PGXP_COP2(0x48C62000u, cpu->gpr[6], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BF88u); #endif @@ -668090,6 +668111,7 @@ label_BFC33FB4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[24] = cpu->gpr[4]; + PGXP_COP2(0x48C4C000u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BFBCu); #endif @@ -668105,6 +668127,7 @@ label_BFC33FB4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[25] = cpu->gpr[5]; + PGXP_COP2(0x48C5C800u, cpu->gpr[5], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BFC0u); #endif @@ -668157,6 +668180,7 @@ label_BFC33FCC: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 26, cpu->gpr[4]); + PGXP_COP2(0x48C4D000u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BFCCu); #endif @@ -668212,6 +668236,7 @@ label_BFC33FD8: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 27, cpu->gpr[4]); + PGXP_COP2(0x48C4D800u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BFD8u); #endif @@ -668267,6 +668292,7 @@ label_BFC33FE4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[28] = cpu->gpr[4]; + PGXP_COP2(0x48C4E000u, cpu->gpr[4], 0u); #ifdef PSX_COSIM cosim_instr(0x8004BFE4u); #endif @@ -669226,6 +669252,7 @@ label_BFC340D8: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004C0D8u); #endif @@ -669281,6 +669308,7 @@ label_BFC340E4: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[24]; + PGXP_COP2(0x4848C000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004C0E4u); #endif @@ -669293,6 +669321,7 @@ label_BFC340E4: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[25]; + PGXP_COP2(0x4849C800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004C0E8u); #endif @@ -669387,6 +669416,7 @@ label_BFC34104: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 26); + PGXP_COP2(0x4842D000u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004C104u); #endif @@ -681211,6 +681241,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFB4u); #endif @@ -681223,6 +681254,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFB8u); #endif @@ -681235,6 +681267,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFBCu); #endif @@ -681250,6 +681283,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFC0u); #endif @@ -681262,6 +681296,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFC4u); #endif @@ -681274,6 +681309,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFC8u); #endif @@ -681286,6 +681322,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFCCu); #endif @@ -681301,6 +681338,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004CFD0u); #endif @@ -681828,6 +681866,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D098u); #endif @@ -681840,6 +681879,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D09Cu); #endif @@ -681855,6 +681895,7 @@ label_BFC34FA0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D0A0u); #endif @@ -682247,6 +682288,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D13Cu); #endif @@ -682262,6 +682304,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D140u); #endif @@ -682274,6 +682317,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D144u); #endif @@ -682286,6 +682330,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D148u); #endif @@ -682298,6 +682343,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D14Cu); #endif @@ -682313,6 +682359,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D150u); #endif @@ -682325,6 +682372,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D154u); #endif @@ -682337,6 +682385,7 @@ label_BFC35128: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D158u); #endif @@ -682994,6 +683043,7 @@ label_BFC3524C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D24Cu); #endif @@ -683009,6 +683059,7 @@ label_BFC3524C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D250u); #endif @@ -683021,6 +683072,7 @@ label_BFC3524C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D254u); #endif @@ -683711,6 +683763,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D35Cu); #endif @@ -683726,6 +683779,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D360u); #endif @@ -683738,6 +683792,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D364u); #endif @@ -683750,6 +683805,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D368u); #endif @@ -683762,6 +683818,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D36Cu); #endif @@ -683777,6 +683834,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D370u); #endif @@ -683789,6 +683847,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D374u); #endif @@ -683801,6 +683860,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D378u); #endif @@ -683813,6 +683873,7 @@ label_BFC35348: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D37Cu); #endif @@ -684503,6 +684564,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D484u); #endif @@ -684515,6 +684577,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D488u); #endif @@ -684527,6 +684590,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D48Cu); #endif @@ -684542,6 +684606,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D490u); #endif @@ -684554,6 +684619,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D494u); #endif @@ -684566,6 +684632,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D498u); #endif @@ -684578,6 +684645,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D49Cu); #endif @@ -684593,6 +684661,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D4A0u); #endif @@ -684605,6 +684674,7 @@ label_BFC35470: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D4A4u); #endif @@ -685262,6 +685332,7 @@ label_BFC35598: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D598u); #endif @@ -685274,6 +685345,7 @@ label_BFC35598: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D59Cu); #endif @@ -685289,6 +685361,7 @@ label_BFC35598: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D5A0u); #endif @@ -685988,6 +686061,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6ACu); #endif @@ -686003,6 +686077,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6B0u); #endif @@ -686015,6 +686090,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6B4u); #endif @@ -686027,6 +686103,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6B8u); #endif @@ -686039,6 +686116,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6BCu); #endif @@ -686054,6 +686132,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6C0u); #endif @@ -686066,6 +686145,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6C4u); #endif @@ -686078,6 +686158,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6C8u); #endif @@ -686090,6 +686171,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D6CCu); #endif @@ -686660,6 +686742,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[14]; + PGXP_COP2(0x48CE0000u, cpu->gpr[14], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7A4u); #endif @@ -686672,6 +686755,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[8]; + PGXP_COP2(0x48C80800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7A8u); #endif @@ -686684,6 +686768,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[9]; + PGXP_COP2(0x48C91000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7ACu); #endif @@ -686699,6 +686784,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[24]; + PGXP_COP2(0x48D81800u, cpu->gpr[24], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7B0u); #endif @@ -686711,6 +686797,7 @@ label_BFC35698: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[10]); + PGXP_COP2(0x48CA2000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7B4u); #endif @@ -686808,6 +686895,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7D8u); #endif @@ -686820,6 +686908,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7DCu); #endif @@ -686835,6 +686924,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7E0u); #endif @@ -686847,6 +686937,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7E4u); #endif @@ -686859,6 +686950,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7E8u); #endif @@ -686871,6 +686963,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7ECu); #endif @@ -686886,6 +686979,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7F0u); #endif @@ -686898,6 +686992,7 @@ label_BFC357C4: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D7F4u); #endif @@ -687070,6 +687165,7 @@ label_BFC3582C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[0]; + PGXP_COP2(0x48C02800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D82Cu); #endif @@ -687085,6 +687181,7 @@ label_BFC3582C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[0]; + PGXP_COP2(0x48C03000u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D830u); #endif @@ -687097,6 +687194,7 @@ label_BFC3582C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[0]; + PGXP_COP2(0x48C03800u, cpu->gpr[0], 0u); #ifdef PSX_COSIM cosim_instr(0x8004D834u); #endif @@ -688930,6 +689028,7 @@ label_BFC35AD8: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DAECu); #endif @@ -688945,6 +689044,7 @@ label_BFC35AD8: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DAF0u); #endif @@ -688957,6 +689057,7 @@ label_BFC35AD8: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[10]; + PGXP_COP2(0x48CA1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DAF4u); #endif @@ -688969,6 +689070,7 @@ label_BFC35AD8: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[11]; + PGXP_COP2(0x48CB1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DAF8u); #endif @@ -688981,6 +689083,7 @@ label_BFC35AD8: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[12]); + PGXP_COP2(0x48CC2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DAFCu); #endif @@ -689069,6 +689172,7 @@ label_BFC35B08: psx_gte_stall(cpu); #endif cpu->gte_ctrl[8] = cpu->gpr[8]; + PGXP_COP2(0x48C84000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB1Cu); #endif @@ -689084,6 +689188,7 @@ label_BFC35B08: psx_gte_stall(cpu); #endif cpu->gte_ctrl[9] = cpu->gpr[9]; + PGXP_COP2(0x48C94800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB20u); #endif @@ -689096,6 +689201,7 @@ label_BFC35B08: psx_gte_stall(cpu); #endif cpu->gte_ctrl[10] = cpu->gpr[10]; + PGXP_COP2(0x48CA5000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB24u); #endif @@ -689108,6 +689214,7 @@ label_BFC35B08: psx_gte_stall(cpu); #endif cpu->gte_ctrl[11] = cpu->gpr[11]; + PGXP_COP2(0x48CB5800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB28u); #endif @@ -689120,6 +689227,7 @@ label_BFC35B08: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 12, cpu->gpr[12]); + PGXP_COP2(0x48CC6000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB2Cu); #endif @@ -689208,6 +689316,7 @@ label_BFC35B38: psx_gte_stall(cpu); #endif cpu->gte_ctrl[16] = cpu->gpr[8]; + PGXP_COP2(0x48C88000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB4Cu); #endif @@ -689223,6 +689332,7 @@ label_BFC35B38: psx_gte_stall(cpu); #endif cpu->gte_ctrl[17] = cpu->gpr[9]; + PGXP_COP2(0x48C98800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB50u); #endif @@ -689235,6 +689345,7 @@ label_BFC35B38: psx_gte_stall(cpu); #endif cpu->gte_ctrl[18] = cpu->gpr[10]; + PGXP_COP2(0x48CA9000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB54u); #endif @@ -689247,6 +689358,7 @@ label_BFC35B38: psx_gte_stall(cpu); #endif cpu->gte_ctrl[19] = cpu->gpr[11]; + PGXP_COP2(0x48CB9800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB58u); #endif @@ -689259,6 +689371,7 @@ label_BFC35B38: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 20, cpu->gpr[12]); + PGXP_COP2(0x48CCA000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB5Cu); #endif @@ -689335,6 +689448,7 @@ label_BFC35B68: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[8]; + PGXP_COP2(0x48C82800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB74u); #endif @@ -689347,6 +689461,7 @@ label_BFC35B68: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[9]; + PGXP_COP2(0x48C93000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB78u); #endif @@ -689359,6 +689474,7 @@ label_BFC35B68: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[10]; + PGXP_COP2(0x48CA3800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DB7Cu); #endif @@ -689649,6 +689765,7 @@ label_BFC35BC4: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[0]; + PGXP_COP2(0x48480000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DBD0u); #endif @@ -689661,6 +689778,7 @@ label_BFC35BC4: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[1]; + PGXP_COP2(0x48490800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DBD4u); #endif @@ -689694,6 +689812,7 @@ label_BFC35BC4: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[2]; + PGXP_COP2(0x48481000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DBE0u); #endif @@ -689706,6 +689825,7 @@ label_BFC35BC4: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[3]; + PGXP_COP2(0x48491800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DBE4u); #endif @@ -689739,6 +689859,7 @@ label_BFC35BC4: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = gte_read_ctrl(cpu, 4); + PGXP_COP2(0x48482000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DBF0u); #endif @@ -689768,6 +689889,7 @@ label_BFC35BC4: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[5]; + PGXP_COP2(0x48482800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DBFCu); #endif @@ -689783,6 +689905,7 @@ label_BFC35BC4: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[6]; + PGXP_COP2(0x48493000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DC00u); #endif @@ -689795,6 +689918,7 @@ label_BFC35BC4: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[7]; + PGXP_COP2(0x484A3800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DC04u); #endif @@ -690178,6 +690302,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DC80u); #endif @@ -690190,6 +690315,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[1] = cpu->gpr[9]; + PGXP_COP2(0x48C90800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DC84u); #endif @@ -690217,6 +690343,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[8]; + PGXP_COP2(0x48C81000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DC90u); #endif @@ -690229,6 +690356,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[3] = cpu->gpr[9]; + PGXP_COP2(0x48C91800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DC94u); #endif @@ -690258,6 +690386,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[8]); + PGXP_COP2(0x48C82000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCA0u); #endif @@ -690299,6 +690428,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[5] = cpu->gpr[8]; + PGXP_COP2(0x48C82800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCB4u); #endif @@ -690311,6 +690441,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[6] = cpu->gpr[9]; + PGXP_COP2(0x48C93000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCB8u); #endif @@ -690323,6 +690454,7 @@ label_BFC35C60: psx_gte_stall(cpu); #endif cpu->gte_ctrl[7] = cpu->gpr[10]; + PGXP_COP2(0x48CA3800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCBCu); #endif @@ -690378,6 +690510,7 @@ label_BFC35CC8: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[0]; + PGXP_COP2(0x48480000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCC8u); #endif @@ -690390,6 +690523,7 @@ label_BFC35CC8: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[1]; + PGXP_COP2(0x48490800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCCCu); #endif @@ -690405,6 +690539,7 @@ label_BFC35CC8: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[2]; + PGXP_COP2(0x484A1000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCD0u); #endif @@ -690417,6 +690552,7 @@ label_BFC35CC8: psx_gte_read(cpu, 11); #endif cpu->gpr[11] = cpu->gte_ctrl[3]; + PGXP_COP2(0x484B1800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCD4u); #endif @@ -690429,6 +690565,7 @@ label_BFC35CC8: psx_gte_read(cpu, 12); #endif cpu->gpr[12] = gte_read_ctrl(cpu, 4); + PGXP_COP2(0x484C2000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCD8u); #endif @@ -690492,6 +690629,7 @@ label_BFC35CC8: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[5]; + PGXP_COP2(0x48482800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCF0u); #endif @@ -690504,6 +690642,7 @@ label_BFC35CC8: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[6]; + PGXP_COP2(0x48493000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCF4u); #endif @@ -690516,6 +690655,7 @@ label_BFC35CC8: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[7]; + PGXP_COP2(0x484A3800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DCF8u); #endif @@ -690598,6 +690738,7 @@ label_BFC35D10: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[8]; + PGXP_COP2(0x48484000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD10u); #endif @@ -690610,6 +690751,7 @@ label_BFC35D10: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[9]; + PGXP_COP2(0x48494800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD14u); #endif @@ -690622,6 +690764,7 @@ label_BFC35D10: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[10]; + PGXP_COP2(0x484A5000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD18u); #endif @@ -690634,6 +690777,7 @@ label_BFC35D10: psx_gte_read(cpu, 11); #endif cpu->gpr[11] = cpu->gte_ctrl[11]; + PGXP_COP2(0x484B5800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD1Cu); #endif @@ -690649,6 +690793,7 @@ label_BFC35D10: psx_gte_read(cpu, 12); #endif cpu->gpr[12] = gte_read_ctrl(cpu, 12); + PGXP_COP2(0x484C6000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD20u); #endif @@ -690709,6 +690854,7 @@ label_BFC35D10: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[13]; + PGXP_COP2(0x48486800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD38u); #endif @@ -690721,6 +690867,7 @@ label_BFC35D10: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[14]; + PGXP_COP2(0x48497000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD3Cu); #endif @@ -690736,6 +690883,7 @@ label_BFC35D10: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[15]; + PGXP_COP2(0x484A7800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD40u); #endif @@ -690818,6 +690966,7 @@ label_BFC35D58: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[16]; + PGXP_COP2(0x48488000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD58u); #endif @@ -690830,6 +690979,7 @@ label_BFC35D58: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[17]; + PGXP_COP2(0x48498800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD5Cu); #endif @@ -690845,6 +690995,7 @@ label_BFC35D58: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[18]; + PGXP_COP2(0x484A9000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD60u); #endif @@ -690857,6 +691008,7 @@ label_BFC35D58: psx_gte_read(cpu, 11); #endif cpu->gpr[11] = cpu->gte_ctrl[19]; + PGXP_COP2(0x484B9800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD64u); #endif @@ -690869,6 +691021,7 @@ label_BFC35D58: psx_gte_read(cpu, 12); #endif cpu->gpr[12] = gte_read_ctrl(cpu, 20); + PGXP_COP2(0x484CA000u, cpu->gpr[12], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD68u); #endif @@ -690932,6 +691085,7 @@ label_BFC35D58: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = cpu->gte_ctrl[21]; + PGXP_COP2(0x4848A800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD80u); #endif @@ -690944,6 +691098,7 @@ label_BFC35D58: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = cpu->gte_ctrl[22]; + PGXP_COP2(0x4849B000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD84u); #endif @@ -690956,6 +691111,7 @@ label_BFC35D58: psx_gte_read(cpu, 10); #endif cpu->gpr[10] = cpu->gte_ctrl[23]; + PGXP_COP2(0x484AB800u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DD88u); #endif @@ -691117,6 +691273,7 @@ label_BFC35DA0: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DDBCu); #endif @@ -691390,6 +691547,7 @@ label_BFC35DD4: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DE18u); #endif @@ -691570,6 +691728,7 @@ label_BFC35E30: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DE50u); #endif @@ -691736,6 +691895,7 @@ label_BFC35E64: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004DE84u); #endif @@ -695366,6 +695526,7 @@ label_BFC362D0: psx_gte_read(cpu, 13); #endif cpu->gpr[13] = cpu->gte_ctrl[0]; + PGXP_COP2(0x484D0000u, cpu->gpr[13], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E2D0u); #endif @@ -695378,6 +695539,7 @@ label_BFC362D0: psx_gte_read(cpu, 14); #endif cpu->gpr[14] = cpu->gte_ctrl[2]; + PGXP_COP2(0x484E1000u, cpu->gpr[14], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E2D4u); #endif @@ -695390,6 +695552,7 @@ label_BFC362D0: psx_gte_read(cpu, 15); #endif cpu->gpr[15] = gte_read_ctrl(cpu, 4); + PGXP_COP2(0x484F2000u, cpu->gpr[15], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E2D8u); #endif @@ -695423,6 +695586,7 @@ label_BFC362D0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E2E8u); #endif @@ -695435,6 +695599,7 @@ label_BFC362D0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[9]; + PGXP_COP2(0x48C91000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E2ECu); #endif @@ -695450,6 +695615,7 @@ label_BFC362D0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[10]); + PGXP_COP2(0x48CA2000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E2F0u); #endif @@ -695568,6 +695734,7 @@ label_BFC362D0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[13]; + PGXP_COP2(0x48CD0000u, cpu->gpr[13], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E318u); #endif @@ -695580,6 +695747,7 @@ label_BFC362D0: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[14]; + PGXP_COP2(0x48CE1000u, cpu->gpr[14], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E31Cu); #endif @@ -695595,6 +695763,7 @@ label_BFC362D0: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[15]); + PGXP_COP2(0x48CF2000u, cpu->gpr[15], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E320u); #endif @@ -695647,6 +695816,7 @@ label_BFC3632C: psx_gte_read(cpu, 13); #endif cpu->gpr[13] = cpu->gte_ctrl[0]; + PGXP_COP2(0x484D0000u, cpu->gpr[13], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E32Cu); #endif @@ -695662,6 +695832,7 @@ label_BFC3632C: psx_gte_read(cpu, 14); #endif cpu->gpr[14] = cpu->gte_ctrl[2]; + PGXP_COP2(0x484E1000u, cpu->gpr[14], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E330u); #endif @@ -695674,6 +695845,7 @@ label_BFC3632C: psx_gte_read(cpu, 15); #endif cpu->gpr[15] = gte_read_ctrl(cpu, 4); + PGXP_COP2(0x484F2000u, cpu->gpr[15], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E334u); #endif @@ -695707,6 +695879,7 @@ label_BFC3632C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[8]; + PGXP_COP2(0x48C80000u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E344u); #endif @@ -695719,6 +695892,7 @@ label_BFC3632C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[9]; + PGXP_COP2(0x48C91000u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E348u); #endif @@ -695731,6 +695905,7 @@ label_BFC3632C: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[10]); + PGXP_COP2(0x48CA2000u, cpu->gpr[10], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E34Cu); #endif @@ -695852,6 +696027,7 @@ label_BFC3632C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[0] = cpu->gpr[13]; + PGXP_COP2(0x48CD0000u, cpu->gpr[13], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E374u); #endif @@ -695864,6 +696040,7 @@ label_BFC3632C: psx_gte_stall(cpu); #endif cpu->gte_ctrl[2] = cpu->gpr[14]; + PGXP_COP2(0x48CE1000u, cpu->gpr[14], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E378u); #endif @@ -695876,6 +696053,7 @@ label_BFC3632C: psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, 4, cpu->gpr[15]); + PGXP_COP2(0x48CF2000u, cpu->gpr[15], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E37Cu); #endif @@ -696103,6 +696281,7 @@ label_BFC363A8: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E3BCu); #endif @@ -696142,6 +696321,7 @@ label_BFC363A8: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E3C8u); #endif @@ -696422,6 +696602,7 @@ label_BFC363F8: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E41Cu); #endif @@ -696488,6 +696669,7 @@ label_BFC363F8: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E430u); #endif @@ -696793,6 +696975,7 @@ label_BFC36458: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E494u); #endif @@ -696908,6 +697091,7 @@ label_BFC36458: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4848F800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E4C0u); #endif @@ -697175,6 +697359,7 @@ label_BFC364DC: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4848F800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E51Cu); #endif @@ -697454,6 +697639,7 @@ label_BFC3653C: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E578u); #endif @@ -697557,6 +697743,7 @@ label_BFC3653C: psx_gte_read(cpu, 8); #endif cpu->gpr[8] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4848F800u, cpu->gpr[8], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E5A0u); #endif @@ -697722,6 +697909,7 @@ label_BFC365C4: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E5D4u); #endif @@ -697734,6 +697922,7 @@ label_BFC365C4: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E5D8u); #endif @@ -697957,6 +698146,7 @@ label_BFC365F8: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E61Cu); #endif @@ -697972,6 +698162,7 @@ label_BFC365F8: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E620u); #endif @@ -698414,6 +698605,7 @@ label_BFC36684: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E6A8u); #endif @@ -698426,6 +698618,7 @@ label_BFC36684: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E6ACu); #endif @@ -698771,6 +698964,7 @@ label_BFC366E8: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E724u); #endif @@ -698986,6 +699180,7 @@ label_BFC36740: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E764u); #endif @@ -698998,6 +699193,7 @@ label_BFC36740: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E768u); #endif @@ -699461,6 +699657,7 @@ label_BFC367D4: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E7F8u); #endif @@ -699473,6 +699670,7 @@ label_BFC367D4: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E7FCu); #endif @@ -699997,6 +700195,7 @@ label_BFC36880: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E8A4u); #endif @@ -700009,6 +700208,7 @@ label_BFC36880: psx_gte_read(cpu, 3); #endif cpu->gpr[3] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E8A8u); #endif @@ -700312,6 +700512,7 @@ label_BFC368E0: psx_gte_read(cpu, 11); #endif cpu->gpr[11] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x484BF800u, cpu->gpr[11], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E910u); #endif @@ -700528,6 +700729,7 @@ label_BFC36940: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E958u); #endif @@ -700906,6 +701108,7 @@ label_BFC36998: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004E9E0u); #endif @@ -701300,6 +701503,7 @@ label_BFC36A3C: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004EA60u); #endif @@ -701312,6 +701516,7 @@ label_BFC36A3C: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004EA64u); #endif @@ -701964,6 +702169,7 @@ label_BFC36B14: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004EB38u); #endif @@ -701976,6 +702182,7 @@ label_BFC36B14: psx_gte_read(cpu, 9); #endif cpu->gpr[9] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4849F800u, cpu->gpr[9], 0u); #ifdef PSX_COSIM cosim_instr(0x8004EB3Cu); #endif @@ -702573,6 +702780,7 @@ label_BFC36BEC: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004EC00u); #endif @@ -702585,6 +702793,7 @@ label_BFC36BEC: psx_gte_read(cpu, 2); #endif cpu->gpr[2] = gte_read_ctrl(cpu, 31); + PGXP_COP2(0x4842F800u, cpu->gpr[2], 0u); #ifdef PSX_COSIM cosim_instr(0x8004EC04u); #endif diff --git a/overlay_toolchain/include/overlay_api.h b/overlay_toolchain/include/overlay_api.h index fca6c108..237b854b 100644 --- a/overlay_toolchain/include/overlay_api.h +++ b/overlay_toolchain/include/overlay_api.h @@ -198,8 +198,9 @@ * register-register frustum-edge emit; exact/keep NCLIP sites for every * sign-branch shape. * 17: the two lines merged (master exact-NCLIP emission, release keep-site - * shapes and cull-scale sites). */ -#define PSX_OVERLAY_CODEGEN_VER 17 + * shapes and cull-scale sites). + * 18: + PGXP_COP2 hooks on CFC2 / CTC2 (GTE-as-multiplier scalar tier). */ +#define PSX_OVERLAY_CODEGEN_VER 18 /* Auto codegen hash (generated by hash_codegen.cmake from the recompiler codegen * sources). Folded into the cache PATH next to cg (gcc//cg_/) diff --git a/overlay_toolchain/include/pgxp.h b/overlay_toolchain/include/pgxp.h index 6c06b9c8..755b9eb4 100644 --- a/overlay_toolchain/include/pgxp.h +++ b/overlay_toolchain/include/pgxp.h @@ -312,6 +312,77 @@ typedef struct PGXPWordMiss { /* Returns the total number of misses ever recorded. */ uint64_t pgxp_word_miss_ring(const PGXPWordMiss **ring, uint32_t *cap); +/* MVMVA as a multiplier (scalar tier, cpu-mode). gte.cpp reports every + * MVMVA with the operands exactly as it used them and the results it wrote; + * the engine recomputes the rows from the precise matrix elements (CTC2'd + * tracked halves), vector (IR scalars or tracked V halves) and translation, + * and leaves the results as scalars on MAC1..3 / IR1..3 for MFC2. */ +typedef struct PGXPMvmva { + uint32_t mx, vv, tv; /* matrix / vector / translation selectors */ + int shift; /* 12 when sf = 1, else 0 */ + int16_t m[3][3]; /* matrix elements as used */ + int16_t v[3]; /* vector as used */ + int64_t t[3]; /* translation as used (already << 12) */ + int32_t mac[3]; /* MAC1..3 written */ + int32_t ir[3]; /* IR1..3 written */ + uint32_t flag; /* FLAG after the op */ +} PGXPMvmva; +void pgxp_gte_mvmva(const PGXPMvmva *op); +/* Called after every GTE command (its function code): the IR / MAC scalars it + * overwrote end there. */ +void pgxp_gte_op_end(uint32_t func); + +/* Geometry-corrected triangles, newest at (seq - 1) % cap (TCP pgxp_tri_ring). + * Always-on: every triangle prepare_precise_triangle resolves, with each + * vertex's packet integers, the position handed to the rasterizer and where + * that position came from. Two triangles that share a packet vertex (same + * integers, same frame) but were handed different positions are a crack: + * the ring is what localizes a seam to the producer of its vertices. */ +#define PGXP_TRI_RING_CAP 65536u +typedef struct PGXPTriRecord { + uint64_t seq; + uint32_t frame; /* s_frame_count when the triangle was drawn */ + uint32_t src_addr; /* GP0 packet base address (0xFFFFFFFF = none) */ + uint8_t op; /* GP0 opcode */ + uint8_t pass; /* drawn inside a render-pass checkpoint (frame */ + /* smoothing redraws); set by the engine */ + uint8_t src[3]; /* PGXP_SRC_* per vertex */ + uint8_t vidx[3]; /* packet word index of each vertex */ + int16_t raw_x[3]; /* parsed 11-bit packet integers (pre-offset) */ + int16_t raw_y[3]; + uint32_t word[3]; /* the packet words */ + int32_t x16[3]; /* position handed to the rasterizer (16.16, */ + int32_t y16[3]; /* draw offset and widescreen already applied) */ +} PGXPTriRecord; +void pgxp_note_triangle_detail(const PGXPTriRecord *rec); +/* Returns the total number of triangles ever recorded. */ +uint64_t pgxp_tri_ring(const PGXPTriRecord **ring, uint32_t *cap); + +/* Shadowed memory writes, newest at (seq - 1) % cap (TCP pgxp_store_ring). + * Always-on while the engine is armed: every hooked SW / SH / SB / SWC2 into + * RAM or scratchpad, with the storing PC, the source register's shadow state + * and the shadow the destination word holds afterwards. Walking a packet + * word's provenance backwards (store PC -> source register -> the load that + * filled it) is how a precision loss is pinned to one instruction. */ +#define PGXP_STORE_RING_CAP 262144u +typedef struct PGXPStoreRecord { + uint64_t seq; + uint32_t frame; + uint32_t pc; /* g_debug_last_store_pc of the store */ + uint32_t addr; /* guest address written */ + uint32_t value; /* the value stored (half / byte for SH / SB) */ + uint8_t op; /* primary opcode (0x2B SW, 0x29 SH, 0x3A SWC2) */ + uint8_t reg; /* source GPR (or GTE data register for SWC2) */ + uint8_t src_live; /* source shadow belongs to this generation */ + uint8_t pad; + uint16_t src_flags; /* source shadow flags (1 X, 2 Y, 4 Z) */ + uint16_t dst_flags; /* destination word shadow flags afterwards */ + int32_t dst_x16, dst_y16; +} PGXPStoreRecord; +uint64_t pgxp_store_ring(const PGXPStoreRecord **ring, uint32_t *cap); +/* The host's store-PC breadcrumb and frame counter, for the rings above. */ +void pgxp_set_trace_sources(const uint32_t *store_pc, const uint64_t *frame); + /* Debug read of one shadow slot (TCP pgxp_shadow). `space`: 0 = guest * address (RAM / scratchpad), 1 = GPR index (32 = HI, 33 = LO), 2 = GTE data * register. Returns 0 when the slot does not exist; *live says whether it diff --git a/recompiler/src/pgxp_hook_emitter.cpp b/recompiler/src/pgxp_hook_emitter.cpp index 5f251f3e..fdb82035 100644 --- a/recompiler/src/pgxp_hook_emitter.cpp +++ b/recompiler/src/pgxp_hook_emitter.cpp @@ -124,8 +124,12 @@ void append_pgxp_hooks(uint32_t instr, std::string& code) { code, instr, reg_name(rt)); return; case 0x12: { /* COP2 register transfers */ + /* Control transfers too: engines pack tracked vertex halves into a + * matrix row with CTC2 and use MVMVA as a weighted-sum multiplier + * (the scalar tier in pgxp.cpp follows the result back out). */ const uint32_t cop_op = (instr >> 21) & 0x1F; - if ((cop_op == 0x00 && rt != 0) || cop_op == 0x04) /* MFC2 / MTC2 */ + if (((cop_op == 0x00 || cop_op == 0x02) && rt != 0) || /* MFC2 / CFC2 */ + cop_op == 0x04 || cop_op == 0x06) /* MTC2 / CTC2 */ code = fmt::format("{}\n PGXP_COP2(0x{:08X}u, {}, 0u);", code, instr, reg_name(rt)); return; diff --git a/recompiler/tests/emitter_directive_line_test.cpp b/recompiler/tests/emitter_directive_line_test.cpp index be747194..50278572 100644 --- a/recompiler/tests/emitter_directive_line_test.cpp +++ b/recompiler/tests/emitter_directive_line_test.cpp @@ -66,6 +66,33 @@ int main() { CHECK(!emission_ends_on_preprocessor_directive("x = 1; /* #endif in a comment */"), "a '#' that is not the line's first token is not a directive"); + /* COP2 register transfers all carry their PGXP_COP2 hook - the control + * forms too, so the engine can follow vertex halves packed into a matrix + * row (CTC2) through MVMVA and back (CFC2). A transfer to $zero writes no + * register and stays unhooked. */ + { + const auto hooked = [](uint32_t instr) { + std::string code = "stmt;"; + PSXRecomp::append_pgxp_hooks(instr, code); + return code; + }; + const uint32_t mfc2 = 0x4805C800u; /* mfc2 a1, $25 */ + const uint32_t cfc2 = 0x4845C800u; /* cfc2 a1, $25 */ + const uint32_t mtc2 = 0x48994800u; /* mtc2 t9, $9 */ + const uint32_t ctc2 = 0x48C34000u; /* ctc2 v1, $8 */ + const uint32_t cfc2_zero = 0x4840C800u; /* cfc2 zero, $25 */ + CHECK(hooked(mfc2).find("PGXP_COP2(0x4805C800u, cpu->gpr[5], 0u);") != std::string::npos, + "MFC2 carries PGXP_COP2 with its destination value"); + CHECK(hooked(cfc2).find("PGXP_COP2(0x4845C800u, cpu->gpr[5], 0u);") != std::string::npos, + "CFC2 carries PGXP_COP2 with its destination value"); + CHECK(hooked(mtc2).find("PGXP_COP2(0x48994800u, cpu->gpr[25], 0u);") != std::string::npos, + "MTC2 carries PGXP_COP2 with its source value"); + CHECK(hooked(ctc2).find("PGXP_COP2(0x48C34000u, cpu->gpr[3], 0u);") != std::string::npos, + "CTC2 carries PGXP_COP2 with its source value"); + CHECK(hooked(cfc2_zero) == "stmt;", + "a CFC2 to $zero writes no register and is not hooked"); + } + std::printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures); return failures ? 1 : 0; } diff --git a/runtime/include/overlay_api.h b/runtime/include/overlay_api.h index fca6c108..237b854b 100644 --- a/runtime/include/overlay_api.h +++ b/runtime/include/overlay_api.h @@ -198,8 +198,9 @@ * register-register frustum-edge emit; exact/keep NCLIP sites for every * sign-branch shape. * 17: the two lines merged (master exact-NCLIP emission, release keep-site - * shapes and cull-scale sites). */ -#define PSX_OVERLAY_CODEGEN_VER 17 + * shapes and cull-scale sites). + * 18: + PGXP_COP2 hooks on CFC2 / CTC2 (GTE-as-multiplier scalar tier). */ +#define PSX_OVERLAY_CODEGEN_VER 18 /* Auto codegen hash (generated by hash_codegen.cmake from the recompiler codegen * sources). Folded into the cache PATH next to cg (gcc//cg_/) diff --git a/runtime/include/pgxp.h b/runtime/include/pgxp.h index 6c06b9c8..755b9eb4 100644 --- a/runtime/include/pgxp.h +++ b/runtime/include/pgxp.h @@ -312,6 +312,77 @@ typedef struct PGXPWordMiss { /* Returns the total number of misses ever recorded. */ uint64_t pgxp_word_miss_ring(const PGXPWordMiss **ring, uint32_t *cap); +/* MVMVA as a multiplier (scalar tier, cpu-mode). gte.cpp reports every + * MVMVA with the operands exactly as it used them and the results it wrote; + * the engine recomputes the rows from the precise matrix elements (CTC2'd + * tracked halves), vector (IR scalars or tracked V halves) and translation, + * and leaves the results as scalars on MAC1..3 / IR1..3 for MFC2. */ +typedef struct PGXPMvmva { + uint32_t mx, vv, tv; /* matrix / vector / translation selectors */ + int shift; /* 12 when sf = 1, else 0 */ + int16_t m[3][3]; /* matrix elements as used */ + int16_t v[3]; /* vector as used */ + int64_t t[3]; /* translation as used (already << 12) */ + int32_t mac[3]; /* MAC1..3 written */ + int32_t ir[3]; /* IR1..3 written */ + uint32_t flag; /* FLAG after the op */ +} PGXPMvmva; +void pgxp_gte_mvmva(const PGXPMvmva *op); +/* Called after every GTE command (its function code): the IR / MAC scalars it + * overwrote end there. */ +void pgxp_gte_op_end(uint32_t func); + +/* Geometry-corrected triangles, newest at (seq - 1) % cap (TCP pgxp_tri_ring). + * Always-on: every triangle prepare_precise_triangle resolves, with each + * vertex's packet integers, the position handed to the rasterizer and where + * that position came from. Two triangles that share a packet vertex (same + * integers, same frame) but were handed different positions are a crack: + * the ring is what localizes a seam to the producer of its vertices. */ +#define PGXP_TRI_RING_CAP 65536u +typedef struct PGXPTriRecord { + uint64_t seq; + uint32_t frame; /* s_frame_count when the triangle was drawn */ + uint32_t src_addr; /* GP0 packet base address (0xFFFFFFFF = none) */ + uint8_t op; /* GP0 opcode */ + uint8_t pass; /* drawn inside a render-pass checkpoint (frame */ + /* smoothing redraws); set by the engine */ + uint8_t src[3]; /* PGXP_SRC_* per vertex */ + uint8_t vidx[3]; /* packet word index of each vertex */ + int16_t raw_x[3]; /* parsed 11-bit packet integers (pre-offset) */ + int16_t raw_y[3]; + uint32_t word[3]; /* the packet words */ + int32_t x16[3]; /* position handed to the rasterizer (16.16, */ + int32_t y16[3]; /* draw offset and widescreen already applied) */ +} PGXPTriRecord; +void pgxp_note_triangle_detail(const PGXPTriRecord *rec); +/* Returns the total number of triangles ever recorded. */ +uint64_t pgxp_tri_ring(const PGXPTriRecord **ring, uint32_t *cap); + +/* Shadowed memory writes, newest at (seq - 1) % cap (TCP pgxp_store_ring). + * Always-on while the engine is armed: every hooked SW / SH / SB / SWC2 into + * RAM or scratchpad, with the storing PC, the source register's shadow state + * and the shadow the destination word holds afterwards. Walking a packet + * word's provenance backwards (store PC -> source register -> the load that + * filled it) is how a precision loss is pinned to one instruction. */ +#define PGXP_STORE_RING_CAP 262144u +typedef struct PGXPStoreRecord { + uint64_t seq; + uint32_t frame; + uint32_t pc; /* g_debug_last_store_pc of the store */ + uint32_t addr; /* guest address written */ + uint32_t value; /* the value stored (half / byte for SH / SB) */ + uint8_t op; /* primary opcode (0x2B SW, 0x29 SH, 0x3A SWC2) */ + uint8_t reg; /* source GPR (or GTE data register for SWC2) */ + uint8_t src_live; /* source shadow belongs to this generation */ + uint8_t pad; + uint16_t src_flags; /* source shadow flags (1 X, 2 Y, 4 Z) */ + uint16_t dst_flags; /* destination word shadow flags afterwards */ + int32_t dst_x16, dst_y16; +} PGXPStoreRecord; +uint64_t pgxp_store_ring(const PGXPStoreRecord **ring, uint32_t *cap); +/* The host's store-PC breadcrumb and frame counter, for the rings above. */ +void pgxp_set_trace_sources(const uint32_t *store_pc, const uint64_t *frame); + /* Debug read of one shadow slot (TCP pgxp_shadow). `space`: 0 = guest * address (RAM / scratchpad), 1 = GPR index (32 = HI, 33 = LO), 2 = GTE data * register. Returns 0 when the slot does not exist; *live says whether it diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 695edb94..6f64b711 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -5467,6 +5467,124 @@ static void handle_pgxp_miss_ring(int id, const char *json) free(buf); } +/* pgxp_tri_ring — the always-on ring of geometry-corrected triangles: + * {"cmd":"pgxp_tri_ring","frame":F,"pass":0|1,"x0":..,"y0":..,"x1":.., + * "y1":..,"count":N}. Every filter is optional: frame keeps one guest frame, + * pass keeps game (0) or render-pass (1) triangles, the box keeps triangles + * with any packet vertex inside [x0,x1]x[y0,y1] (packet integers). Returns + * the newest N matches (default 4096) oldest first. Per triangle: packet + * address, opcode, and per vertex the packet word, its integers, the 16.16 + * position handed to the rasterizer and its source (0 native, 1 dataflow, + * 2 fallback). Join vertices by word across triangles to find cracks. */ +static void handle_pgxp_tri_ring(int id, const char *json) +{ + const PGXPTriRecord *ring; uint32_t cap; + const uint64_t total = pgxp_tri_ring(&ring, &cap); + const uint64_t avail = total < cap ? total : cap; + const int frame = json_get_int(json, "frame", -1); + const int pass = json_get_int(json, "pass", -1); + const int has_box = strstr(json, "\"x0\"") != NULL; + const int x0 = json_get_int(json, "x0", -2048), y0 = json_get_int(json, "y0", -2048); + const int x1 = json_get_int(json, "x1", 2047), y1 = json_get_int(json, "y1", 2047); + int count = json_get_int(json, "count", 4096); + if (count < 1) count = 1; + if (count > 16384) count = 16384; + /* Walk newest -> oldest collecting matches, then emit oldest first. */ + uint64_t *hits = (uint64_t *)malloc((size_t)count * sizeof(uint64_t)); + if (!hits) { send_err(id, "oom"); return; } + int n = 0; + for (uint64_t k = 0; k < avail && n < count; k++) { + const uint64_t seq = total - 1 - k; + const PGXPTriRecord *r = &ring[seq % cap]; + if (frame >= 0 && r->frame != (uint32_t)frame) continue; + if (pass >= 0 && r->pass != (uint8_t)pass) continue; + if (has_box) { + int in = 0; + for (int i = 0; i < 3; i++) + if (r->raw_x[i] >= x0 && r->raw_x[i] <= x1 && + r->raw_y[i] >= y0 && r->raw_y[i] <= y1) in = 1; + if (!in) continue; + } + hits[n++] = seq; + } + const size_t sz = (size_t)n * 400u + 256u; + char *buf = (char *)malloc(sz); + if (!buf) { free(hits); send_err(id, "oom"); return; } + size_t p = (size_t)snprintf(buf, sz, "{\"id\":%d,\"ok\":true,\"total\":%llu,\"count\":%d,\"tris\":[", + id, (unsigned long long)total, n); + for (int j = n - 1; j >= 0; j--) { + const PGXPTriRecord *r = &ring[hits[j] % cap]; + p += (size_t)snprintf(buf + p, sz - p, + "%s{\"seq\":%llu,\"f\":%u,\"a\":\"0x%08X\",\"op\":\"0x%02X\",\"pass\":%u,\"v\":[", + j == n - 1 ? "" : ",", (unsigned long long)r->seq, r->frame, r->src_addr, + r->op, r->pass); + for (int i = 0; i < 3; i++) + p += (size_t)snprintf(buf + p, sz - p, + "%s[%u,\"0x%08X\",%d,%d,%d,%d,%u]", i ? "," : "", r->vidx[i], r->word[i], + r->raw_x[i], r->raw_y[i], r->x16[i], r->y16[i], r->src[i]); + p += (size_t)snprintf(buf + p, sz - p, "]}"); + } + snprintf(buf + p, sz - p, "]}"); + debug_server_send_line(buf); + free(buf); + free(hits); +} + +/* pgxp_store_ring — the always-on ring of shadowed memory writes: + * {"cmd":"pgxp_store_ring","addr_lo":"0x..","addr_hi":"0x..","pc":"0x..", + * "frame":F,"count":N}. addr range is [lo, hi); pc keeps one storing + * instruction; frame keeps one guest frame. + * Returns the newest N matches oldest first: store PC, address, value, + * opcode, source register and its shadow (live, flags) and the destination + * shadow afterwards (flags, 16.16 halves). */ +static void handle_pgxp_store_ring(int id, const char *json) +{ + const PGXPStoreRecord *ring; uint32_t cap; + const uint64_t total = pgxp_store_ring(&ring, &cap); + if (!cap) { send_err(id, "pgxp store ring not allocated (engine never armed)"); return; } + const uint64_t avail = total < cap ? total : cap; + char s[32]; + uint32_t lo = 0, hi = 0xFFFFFFFFu, pc = 0; + int has_pc = 0; + if (json_get_str(json, "addr_lo", s, sizeof s)) lo = hex_to_u32(s); + if (json_get_str(json, "addr_hi", s, sizeof s)) hi = hex_to_u32(s); + if (json_get_str(json, "pc", s, sizeof s)) { pc = hex_to_u32(s); has_pc = 1; } + const int frame = json_get_int(json, "frame", -1); + int count = json_get_int(json, "count", 1024); + if (count < 1) count = 1; + if (count > 65536) count = 65536; + uint64_t *hits = (uint64_t *)malloc((size_t)count * sizeof(uint64_t)); + if (!hits) { send_err(id, "oom"); return; } + int n = 0; + for (uint64_t k = 0; k < avail && n < count; k++) { + const uint64_t seq = total - 1 - k; + const PGXPStoreRecord *r = &ring[seq % cap]; + if (frame >= 0 && r->frame != (uint32_t)frame) continue; + if (r->addr < lo || r->addr >= hi) continue; + if (has_pc && r->pc != pc) continue; + hits[n++] = seq; + } + const size_t sz = (size_t)n * 200u + 256u; + char *buf = (char *)malloc(sz); + if (!buf) { free(hits); send_err(id, "oom"); return; } + size_t p = (size_t)snprintf(buf, sz, "{\"id\":%d,\"ok\":true,\"total\":%llu,\"count\":%d,\"stores\":[", + id, (unsigned long long)total, n); + for (int j = n - 1; j >= 0; j--) { + const PGXPStoreRecord *r = &ring[hits[j] % cap]; + p += (size_t)snprintf(buf + p, sz - p, + "%s{\"seq\":%llu,\"f\":%u,\"pc\":\"0x%08X\",\"a\":\"0x%08X\",\"v\":\"0x%08X\"," + "\"op\":\"0x%02X\",\"reg\":%u,\"src_live\":%u,\"src_flags\":%u," + "\"dst_flags\":%u,\"x16\":%d,\"y16\":%d}", + j == n - 1 ? "" : ",", (unsigned long long)r->seq, r->frame, r->pc, r->addr, + r->value, r->op, r->reg, r->src_live, r->src_flags, r->dst_flags, + r->dst_x16, r->dst_y16); + } + snprintf(buf + p, sz - p, "]}"); + debug_server_send_line(buf); + free(buf); + free(hits); +} + /* pgxp — live-tune the value-propagation engine for one-toggle isolation runs * without a rebuild: {"cmd":"pgxp","cpu_mode":0|1,"tolerance":F, * "position_fallback":0|1,"preserve_projection":0|1,"culling":0|1}. Fields @@ -15045,6 +15163,8 @@ static const CmdEntry s_commands[] = { { "gpu_state", handle_gpu_state }, { "geom_correction", handle_geom_correction }, { "pgxp", handle_pgxp }, + { "pgxp_tri_ring", handle_pgxp_tri_ring }, + { "pgxp_store_ring", handle_pgxp_store_ring }, { "pgxp_shadow", handle_pgxp_shadow }, { "pgxp_miss_ring", handle_pgxp_miss_ring }, { "ws_aspect_cone_site", handle_ws_aspect_cone_site }, @@ -15676,6 +15796,9 @@ void debug_server_init(int port) } s_call_focus_seq = 0; + /* PGXP rings stamp each record with the store PC and frame. */ + pgxp_set_trace_sources(&g_debug_last_store_pc, &s_frame_count); + /* EvCB walk ring (~240 KB). */ if (!s_evcb_ring) s_evcb_ring = (EvCBSnapshot *)calloc(EVCB_RING_CAP, sizeof(EvCBSnapshot)); s_evcb_ring_seq = 0; diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 47093e54..d3e655dc 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -2344,6 +2344,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn, psx_gte_stall(cpu); #endif gte_write_ctrl(cpu, (uint8_t)rd, cpu->gpr[rt]); + psx_pgxp_cop2(cpu, insn, cpu->gpr[rt], 0); return 0; } if (cop_op & 0x10) { diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 008555b5..dbd827d0 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -4365,6 +4365,8 @@ static void prepare_precise_triangle(int i0, int i1, int i2, const int idx[3] = { i0, i1, i2 }; int32_t fx[3], fy[3]; int any_precise = 0, n_precise = 0; + PGXPTriRecord rec; + memset(&rec, 0, sizeof rec); for (int i = 0; i < 3; i++) { uint32_t word = gp0_cmd_buf[idx[i]]; int32_t raw_x, raw_y; @@ -4374,10 +4376,12 @@ static void prepare_precise_triangle(int i0, int i1, int i2, : gp0_cmd_source_addr + (uint32_t)idx[i] * 4u; int32_t px, py; uint16_t sz; + int src = PGXP_SRC_NATIVE; px = raw_x * 65536; py = raw_y * 65536; - if (geometry && pgxp_get_precise_vertex(addr, word, raw_x, raw_y, - &px, &py, &sz) != PGXP_SRC_NATIVE) { + if (geometry) + src = pgxp_get_precise_vertex(addr, word, raw_x, raw_y, &px, &py, &sz); + if (src != PGXP_SRC_NATIVE) { any_precise = 1; n_precise++; } @@ -4386,9 +4390,21 @@ static void prepare_precise_triangle(int i0, int i1, int i2, any_precise = 1; fx[i] = (int32_t)((int64_t)px + (int64_t)(vx[i] - raw_x) * 65536); fy[i] = (int32_t)((int64_t)py + (int64_t)(vy[i] - raw_y) * 65536); + rec.src[i] = (uint8_t)src; + rec.vidx[i] = (uint8_t)idx[i]; + rec.raw_x[i] = (int16_t)raw_x; + rec.raw_y[i] = (int16_t)raw_y; + rec.word[i] = word; + rec.x16[i] = fx[i]; + rec.y16[i] = fy[i]; } - if (geometry) + if (geometry) { pgxp_note_triangle(n_precise); /* G1.1 crack exposure: mixed share */ + rec.frame = (uint32_t)s_frame_count; + rec.src_addr = gp0_cmd_source_addr; + rec.op = (uint8_t)(gp0_cmd_buf[0] >> 24); + pgxp_note_triangle_detail(&rec); + } if (!any_precise) { gr_set_precise_triangle(0, 0,0, 0,0, 0,0); return; diff --git a/runtime/src/gte.cpp b/runtime/src/gte.cpp index 0c45fc00..6b2d4634 100644 --- a/runtime/src/gte.cpp +++ b/runtime/src/gte.cpp @@ -1686,6 +1686,21 @@ void gte_mvmva(GTEState* gte, uint32_t instr) { } gte->set_error_flag(); + + /* PGXP scalar tier: MVMVA used as a multiplier on tracked vertex halves + * (CTC2'd matrix rows) - hand the engine the operands as used and the + * results, so MFC2 of MAC / IR can carry the precise weighted sum. */ + if (!s_gte_replay_sandbox) { + PGXPMvmva op; + op.mx = mx; op.vv = vv; op.tv = tv; op.shift = shift; + std::memcpy(op.m, M, sizeof op.m); + std::memcpy(op.v, V, sizeof op.v); + std::memcpy(op.t, T, sizeof op.t); + op.mac[0] = gte->MAC1; op.mac[1] = gte->MAC2; op.mac[2] = gte->MAC3; + op.ir[0] = gte->IR1; op.ir[1] = gte->IR2; op.ir[2] = gte->IR3; + op.flag = gte->FLAG; + pgxp_gte_mvmva(&op); + } } // --------------------------------------------------------------------------- @@ -2239,6 +2254,7 @@ extern "C" void gte_execute(CPUState* cpu, uint32_t cmd) { } #endif gte_run_command(>e, cmd); + pgxp_gte_op_end(func); /* IR / MAC scalars the op overwrote */ #ifndef PSX_NO_DEBUG_TOOLS if (func == 0x01 || func == 0x30) gte_rtp_record(>e, cmd); diff --git a/runtime/src/pgxp.cpp b/runtime/src/pgxp.cpp index 9e9ac6ce..784d9e55 100644 --- a/runtime/src/pgxp.cpp +++ b/runtime/src/pgxp.cpp @@ -55,6 +55,12 @@ enum { PGXP_F_VY = 1u << 1, /* high half (screen Y) tracked */ PGXP_F_VZ = 1u << 2, /* projected depth rode along intact */ PGXP_F_PROJECTION = 1u << 3, + /* The tracked half was DERIVED by guest arithmetic that rounds (a CPU + * divide of a precise sum, see the scalar tier below), so the guest + * integer is that arithmetic's rounding of the value, not its floor: the + * precise value may sit up to one pixel below or two above it. */ + PGXP_F_DX = 1u << 4, + PGXP_F_DY = 1u << 5, PGXP_F_VXY = PGXP_F_VX | PGXP_F_VY, }; @@ -78,6 +84,26 @@ static PGXPValue *s_ram = nullptr; /* lazily allocated, 72 MiB 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 PGXPValue s_gtc[32]; /* GTE control registers (CTC2) */ + +/* Scalar tier (cpu-mode). Engines also use the GTE as a multiplier for + * vertex arithmetic: Ape Escape's terrain subdivider packs two projected X + * (or Y) halves into a light-matrix row with CTC2, weights them with MVMVA + * by their depths, reads MAC back with MFC2 and DIVs by the depth sum to get + * the screen position of an edge midpoint. A 16.16 half cannot hold those + * intermediate products, so registers carry an optional full-range precise + * value beside their half shadows: valid while `gen` is current and `value` + * still equals the register, killed by every hooked write that does not + * produce one. It turns back into a half shadow only when stored (SH / SW) + * as a 16-bit coordinate. */ +struct PGXPScalar { + double v; /* precise value of the 32-bit register */ + uint32_t value; /* the register value it describes */ + uint32_t gen; +}; +static PGXPScalar s_gpr_s[34]; /* GPRs + HI + LO */ +static PGXPScalar s_gte_s[32]; /* GTE data registers (IR, MAC) */ +static PGXPScalar s_gtc_s[32]; /* GTE control (TR / BK words) */ static uint32_t s_gen = 1; static int s_enabled = 0; @@ -97,6 +123,7 @@ static inline void recompute_active(void) { } static PGXPStats s_stats; +static PGXPStoreRecord *s_store_ring = nullptr; /* pgxp_store_ring; with s_ram */ /* ------------------------------------------------------------------------- */ /* Lifecycle */ @@ -113,12 +140,19 @@ extern "C" void pgxp_invalidate_all(void) { std::memset(s_scratch, 0, sizeof(s_scratch)); std::memset(s_gpr, 0, sizeof(s_gpr)); std::memset(s_gte, 0, sizeof(s_gte)); + std::memset(s_gtc, 0, sizeof(s_gtc)); + std::memset(s_gpr_s, 0, sizeof(s_gpr_s)); + std::memset(s_gte_s, 0, sizeof(s_gte_s)); + std::memset(s_gtc_s, 0, sizeof(s_gtc_s)); s_gen = 1; } } extern "C" void pgxp_set_enabled(int enabled) { int resized = 0; + if (enabled && !s_store_ring) + s_store_ring = (PGXPStoreRecord *)std::calloc(PGXP_STORE_RING_CAP, + sizeof(PGXPStoreRecord)); if (enabled && !s_ram) { s_ram = (PGXPValue *)std::calloc(PGXP_RAM_WORDS, sizeof(PGXPValue)); if (s_ram) @@ -220,6 +254,62 @@ extern "C" void pgxp_note_triangle(int precise) { else s_stats.tri_native++; } +static PGXPTriRecord s_tri_ring[PGXP_TRI_RING_CAP]; +static uint64_t s_tri_seq = 0; + +static int ck_in_pass(void); /* render-pass checkpoint open */ + +extern "C" void pgxp_note_triangle_detail(const PGXPTriRecord *rec) { + PGXPTriRecord *r = &s_tri_ring[s_tri_seq % PGXP_TRI_RING_CAP]; + *r = *rec; + r->seq = s_tri_seq++; + r->pass = ck_in_pass() ? 1u : 0u; +} + +extern "C" uint64_t pgxp_tri_ring(const PGXPTriRecord **ring, uint32_t *cap) { + *ring = s_tri_ring; + *cap = PGXP_TRI_RING_CAP; + return s_tri_seq; +} + +/* Store PC and frame counter belong to the host (debug server); it hands + * their addresses over once (pgxp_set_trace_sources) so the engine stays + * linkable on its own in the white-box tests. */ +static const uint32_t *s_trace_pc = nullptr; +static const uint64_t *s_trace_frame = nullptr; +static uint64_t s_store_seq = 0; + +extern "C" void pgxp_set_trace_sources(const uint32_t *store_pc, + const uint64_t *frame) { + s_trace_pc = store_pc; + s_trace_frame = frame; +} + +static void note_store(uint32_t op, uint32_t reg, const PGXPValue *src, + uint32_t addr, uint32_t value, const PGXPValue *dst) { + if (!s_store_ring) return; + PGXPStoreRecord *r = &s_store_ring[s_store_seq % PGXP_STORE_RING_CAP]; + r->seq = s_store_seq++; + r->frame = s_trace_frame ? (uint32_t)*s_trace_frame : 0u; + r->pc = s_trace_pc ? *s_trace_pc : 0u; + r->addr = addr; + r->value = value; + r->op = (uint8_t)op; + r->reg = (uint8_t)reg; + r->src_live = (src && src->gen == s_gen) ? 1u : 0u; + r->pad = 0; + r->src_flags = src ? src->flags : 0u; + r->dst_flags = (dst && dst->gen == s_gen) ? dst->flags : 0u; + r->dst_x16 = dst ? dst->x16 : 0; + r->dst_y16 = dst ? dst->y16 : 0; +} + +extern "C" uint64_t pgxp_store_ring(const PGXPStoreRecord **ring, uint32_t *cap) { + *ring = s_store_ring; + *cap = s_store_ring ? PGXP_STORE_RING_CAP : 0u; + return s_store_ring ? s_store_seq : 0u; +} + /* ------------------------------------------------------------------------- */ /* Address mapping + validation */ /* ------------------------------------------------------------------------- */ @@ -258,7 +348,8 @@ static PGXPValue *s_ck_ram = nullptr; /* s_ram the bits index */ static PGXPJournalEntry *s_ck_log = nullptr; static size_t s_ck_n = 0, s_ck_cap = 0; static int s_ck_lossy = 0; -static PGXPValue s_ck_gpr[34], s_ck_gte[32]; +static PGXPValue s_ck_gpr[34], s_ck_gte[32], s_ck_gtc[32]; +static PGXPScalar s_ck_gpr_s[34], s_ck_gte_s[32], s_ck_gtc_s[32]; static uint32_t s_ck_gen = 0, s_ck_suppress = 0; static int s_ck_deferred = 0; @@ -297,6 +388,8 @@ static void ck_wrapped(void) { if (s_ck_depth != 0) s_ck_lossy = 1; /* shadows were cleared */ } +static int ck_in_pass(void) { return s_ck_depth != 0; } + static inline PGXPValue *pgxp_ptr_w(uint32_t addr) { PGXPValue *pv = pgxp_ptr(addr); ck_note(pv); @@ -314,6 +407,10 @@ extern "C" void pgxp_checkpoint_begin(void) { s_ck_n = 0; std::memcpy(s_ck_gpr, s_gpr, sizeof s_gpr); std::memcpy(s_ck_gte, s_gte, sizeof s_gte); + std::memcpy(s_ck_gtc, s_gtc, sizeof s_gtc); + std::memcpy(s_ck_gpr_s, s_gpr_s, sizeof s_gpr_s); + std::memcpy(s_ck_gte_s, s_gte_s, sizeof s_gte_s); + std::memcpy(s_ck_gtc_s, s_gtc_s, sizeof s_gtc_s); s_ck_gen = s_gen; s_ck_suppress = s_suppress; s_ck_deferred = s_deferred_invalidate; @@ -331,6 +428,10 @@ extern "C" void pgxp_checkpoint_rollback(void) { s_ck_n = 0; std::memcpy(s_gpr, s_ck_gpr, sizeof s_gpr); std::memcpy(s_gte, s_ck_gte, sizeof s_gte); + std::memcpy(s_gtc, s_ck_gtc, sizeof s_gtc); + std::memcpy(s_gpr_s, s_ck_gpr_s, sizeof s_gpr_s); + std::memcpy(s_gte_s, s_ck_gte_s, sizeof s_gte_s); + std::memcpy(s_gtc_s, s_ck_gtc_s, sizeof s_gtc_s); s_gen = s_ck_gen; /* A watchdog abort can leave a suppress bracket open: the machine it * interrupted is gone, so its bracket is too. */ @@ -354,9 +455,9 @@ static inline void pv_validate(PGXPValue *pv, uint32_t actual) { uint32_t diff = pv->value ^ actual; if (diff) pv->flags &= (uint16_t)~PGXP_F_PROJECTION; if ((pv->flags & PGXP_F_VX) && (diff & 0x0000FFFFu)) - pv->flags &= (uint16_t)~(PGXP_F_VX | PGXP_F_VZ); + pv->flags &= (uint16_t)~(PGXP_F_VX | PGXP_F_DX | PGXP_F_VZ); if ((pv->flags & PGXP_F_VY) && (diff & 0xFFFF0000u)) - pv->flags &= (uint16_t)~(PGXP_F_VY | PGXP_F_VZ); + pv->flags &= (uint16_t)~(PGXP_F_VY | PGXP_F_DY | PGXP_F_VZ); pv->value = actual; } @@ -383,6 +484,67 @@ 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); } +/* ------------------------------------------------------------------------- */ +/* Scalar tier helpers */ +/* ------------------------------------------------------------------------- */ + +static inline void sc_kill(PGXPScalar *s) { s->gen = 0; } +static inline void sc_set(PGXPScalar *s, double v, uint32_t value) { + s->v = v; s->value = value; s->gen = s_gen; +} +static inline int sc_valid(const PGXPScalar *s, uint32_t value) { + return s->gen == s_gen && s->value == value; +} + +/* A GPR write that produces no scalar ends whatever scalar the register had. */ +static inline void gpr_written(uint32_t r) { if (r != 0) sc_kill(&s_gpr_s[r]); } + +/* A register holding one 16-bit half, sign- or zero-extended (LH / LHU, or an + * MTC2 / CTC2 of such a register), whose half shadow is tracked: its precise + * value as a number. */ +static inline int half_scalar(const PGXPValue *pv, uint32_t value, double *out) { + if (!pv || pv->gen != s_gen || pv->value != value) return 0; + if ((pv->flags & PGXP_F_VXY) != PGXP_F_VXY) return 0; + const int32_t sx = (int32_t)(int16_t)(value & 0xFFFFu); + const int32_t zx = (int32_t)(value & 0xFFFFu); + if ((int32_t)value != sx && (int32_t)value != zx) return 0; + if (pv->y16 != (((int32_t)value >> 16) << 16)) return 0; /* exact extension */ + double v = pv->x16 / 65536.0; /* the half read as int16 */ + if ((int32_t)value == zx && zx != sx) v += 65536.0; /* LHU of a negative half */ + *out = v; + return 1; +} + +/* The precise value of a register: its scalar, else its half shadow as a + * number, else the exact integer (*precise = 0). */ +static inline double reg_scalar(const PGXPScalar *s, const PGXPValue *pv, + uint32_t value, int is_unsigned, int *precise) { + double v; + if (s && sc_valid(s, value)) { *precise = 1; return s->v; } + if (pv && half_scalar(pv, value, &v)) { *precise = 1; return v; } + *precise = 0; + return is_unsigned ? (double)value : (double)(int32_t)value; +} + +static inline double gpr_scalar(uint32_t r, uint32_t value, int is_unsigned, + int *precise) { + if (r == 0) { *precise = 0; return 0.0; } + return reg_scalar(&s_gpr_s[r], &s_gpr[r], value, is_unsigned, precise); +} + +/* A scalar that is about to become a 16-bit coordinate half: accept it when + * it describes `half` (the stored integer) within the derived window, and + * say whether the guest integer is its floor (plain) or a rounding of it + * (derived). Returns the 16.16 value, or 0 with *ok = 0. */ +static inline int32_t scalar_to_half(double v, int32_t half, int *ok, int *derived) { + const double d = v - (double)half; + *ok = 0; *derived = 0; + if (!(d > -1.0 && d < 2.0)) return 0; /* also rejects NaN */ + *ok = 1; + *derived = std::floor(v) != (double)half; + return (int32_t)std::floor(v * 65536.0); +} + /* ------------------------------------------------------------------------- */ /* Memory-mode hooks: loads / stores */ /* ------------------------------------------------------------------------- */ @@ -401,8 +563,15 @@ extern "C" void psx_pgxp_load(struct CPUState *cpu, uint32_t instr, uint32_t rt = f_rt(instr); if (rt == 0) return; PGXPValue *dst = &s_gpr[rt]; + gpr_written(rt); /* memory carries no scalars */ - switch (f_op(instr)) { + uint32_t lop = f_op(instr); + /* LWL at byte 3 and LWR at byte 0 of a word each load the WHOLE word + * (little-endian R3000A): the unaligned-copy idiom lwl/lwr on an aligned + * address is a plain word load, so it carries the word's shadow. */ + if ((lop == 0x22 && (addr & 3u) == 3u) || (lop == 0x26 && (addr & 3u) == 0u)) + lop = 0x23; + switch (lop) { case 0x23: { /* LW */ PGXPValue *src = pgxp_ptr_w(addr); /* validation may mutate it */ if (src && src->gen == s_gen) { @@ -430,6 +599,8 @@ extern "C" void psx_pgxp_load(struct CPUState *cpu, uint32_t instr, if ((src->flags & want) && ((src->value ^ actual_half) & mask) == 0) { dst->x16 = hi_half ? src->y16 : src->x16; dst->flags |= PGXP_F_VX; + if (src->flags & (hi_half ? PGXP_F_DY : PGXP_F_DX)) + dst->flags |= PGXP_F_DX; } } return; @@ -449,14 +620,38 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, uint32_t rt = f_rt(instr); PGXPValue *src = (rt != 0) ? &s_gpr[rt] : nullptr; - switch (f_op(instr)) { + uint32_t sop = f_op(instr); + /* SWL at byte 3 and SWR at byte 0 each store the WHOLE word (the + * unaligned-copy idiom on an aligned address): a plain word store. */ + if ((sop == 0x2A && (addr & 3u) == 3u) || (sop == 0x2E && (addr & 3u) == 0u)) + sop = 0x2B; + switch (sop) { case 0x2B: { /* SW */ + PGXPValue before; + std::memset(&before, 0, sizeof before); + if (src) before = *src; if (src && src->gen == s_gen) { pv_validate(src, value); *dst = *src; } else { pv_reset(dst, value); } + /* A register holding one coordinate as a full word (a scalar that + * fits a half, e.g. a CPU-divided position) stores as that half, + * with an exact sign extension above it. */ + if (s_cpu_mode && rt != 0 && sc_valid(&s_gpr_s[rt], value) && + (int32_t)value == (int32_t)(int16_t)(value & 0xFFFFu)) { + int ok, derived; + int32_t v16 = scalar_to_half(s_gpr_s[rt].v, (int16_t)(value & 0xFFFFu), + &ok, &derived); + if (ok) { + pv_reset(dst, value); + dst->x16 = v16; + dst->y16 = ((int32_t)value >> 16) << 16; + dst->flags = (uint16_t)(PGXP_F_VXY | (derived ? PGXP_F_DX : 0)); + } + } + note_store(0x2B, rt, src ? &before : nullptr, addr, value, dst); return; } case 0x29: { /* SH */ @@ -468,13 +663,28 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, * 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 | PGXP_F_PROJECTION); + dst->flags &= (uint16_t)~((hi_half ? (PGXP_F_VY | PGXP_F_DY) : (PGXP_F_VX | PGXP_F_DX)) | + PGXP_F_VZ | PGXP_F_PROJECTION); 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; } + int ok = 0, derived = 0; + int32_t v16 = 0; + /* Hooks pass either the full register or just the stored half (the + * interpreters), so the scalar is matched on the stored half. */ + if (s_cpu_mode && rt != 0 && s_gpr_s[rt].gen == s_gen && + (s_gpr_s[rt].value & 0xFFFFu) == half) + v16 = scalar_to_half(s_gpr_s[rt].v, (int16_t)half, &ok, &derived); + if (ok) { + /* A scalar (e.g. a midpoint computed by MVMVA + DIV) becomes a + * coordinate half here. */ + if (hi_half) { dst->y16 = v16; dst->flags |= (uint16_t)(PGXP_F_VY | (derived ? PGXP_F_DY : 0)); } + else { dst->x16 = v16; dst->flags |= (uint16_t)(PGXP_F_VX | (derived ? PGXP_F_DX : 0)); } + } else if (src && src->gen == s_gen && (src->flags & PGXP_F_VX) && + ((src->value ^ value) & 0xFFFFu) == 0) { + const uint16_t d = (src->flags & PGXP_F_DX) ? (hi_half ? PGXP_F_DY : PGXP_F_DX) : 0; + if (hi_half) { dst->y16 = src->x16; dst->flags |= (uint16_t)(PGXP_F_VY | d); } + else { dst->x16 = src->x16; dst->flags |= (uint16_t)(PGXP_F_VX | d); } } + note_store(0x29, rt, src, addr, half, dst); return; } case 0x28: { /* SB */ @@ -482,7 +692,8 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, 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 | PGXP_F_PROJECTION); + dst->flags &= (uint16_t)~(((addr & 2u) ? (PGXP_F_VY | PGXP_F_DY) : (PGXP_F_VX | PGXP_F_DX)) | + PGXP_F_VZ | PGXP_F_PROJECTION); dst->z = 0; return; } @@ -511,6 +722,7 @@ extern "C" void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, } else { pv_reset(dst, value); } + sc_kill(&s_gte_s[f_rt(instr)]); /* memory carries no scalars */ gte_sxy2_mirror(f_rt(instr)); return; } @@ -518,12 +730,14 @@ extern "C" void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, PGXPValue *dst = pgxp_ptr_w(addr); if (!dst) return; PGXPValue *src = &s_gte[f_rt(instr)]; + const PGXPValue before = *src; if (src->gen == s_gen) { pv_validate(src, value); *dst = *src; } else { pv_reset(dst, value); } + note_store(0x3A, f_rt(instr), &before, addr, value, dst); return; } case 0x12: { /* COP2 register transfers */ @@ -538,29 +752,71 @@ extern "C" void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, } else { pv_reset(&s_gpr[rt], value); } + /* MFC2 of MAC / IR carries an MVMVA result's scalar. */ + if (s_cpu_mode && sc_valid(&s_gte_s[f_rd(instr)], value)) + s_gpr_s[rt] = s_gte_s[f_rd(instr)]; + else + gpr_written(rt); return; } case 0x04: { /* MTC2: gte[rd] <- gpr[rt] */ uint32_t rt = f_rt(instr); - PGXPValue *dst = &s_gte[f_rd(instr)]; + uint32_t rd = f_rd(instr); + PGXPValue *dst = &s_gte[rd]; if (rt != 0 && s_gpr[rt].gen == s_gen) { pv_validate(&s_gpr[rt], value); *dst = s_gpr[rt]; } else { pv_reset(dst, value); } + /* IR0..IR3 hold the low half sign-extended: a scalar that fits + * a half moves in as the register's precise value. */ + sc_kill(&s_gte_s[rd]); + if (s_cpu_mode && rd >= 8 && rd <= 11 && rt != 0 && + sc_valid(&s_gpr_s[rt], value) && + (int32_t)value == (int32_t)(int16_t)(value & 0xFFFFu)) + sc_set(&s_gte_s[rd], s_gpr_s[rt].v, value); /* 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)); + gte_sxy2_mirror(rd); return; } - case 0x02: { /* CFC2: control regs carry no - positions */ + case 0x02: { /* CFC2: gpr[rt] <- gtc[rd] */ uint32_t rt = f_rt(instr); - if (rt != 0) pv_reset(&s_gpr[rt], value); + if (rt == 0) return; + uint32_t rd = f_rd(instr); + if (s_cpu_mode && s_gtc[rd].gen == s_gen) { + pv_validate(&s_gtc[rd], value); + s_gpr[rt] = s_gtc[rd]; + } else { + pv_reset(&s_gpr[rt], value); + } + if (s_cpu_mode && sc_valid(&s_gtc_s[rd], value)) + s_gpr_s[rt] = s_gtc_s[rd]; + else + gpr_written(rt); return; } - default: /* CTC2: nothing shadowed */ + case 0x06: { /* CTC2: gtc[rd] <- gpr[rt] */ + /* Control registers are written only by CTC2, so their shadows + * stay exact until the next CTC2: matrix rows packed from two + * tracked halves (the GTE-as-multiplier idiom) and translation + * words carrying a scalar. */ + uint32_t rt = f_rt(instr); + uint32_t rd = f_rd(instr); + if (s_cpu_mode && rt != 0 && s_gpr[rt].gen == s_gen) { + pv_validate(&s_gpr[rt], value); + s_gtc[rd] = s_gpr[rt]; + } else { + pv_reset(&s_gtc[rd], value); + } + if (s_cpu_mode && rt != 0 && sc_valid(&s_gpr_s[rt], value)) + s_gtc_s[rd] = s_gpr_s[rt]; + else + sc_kill(&s_gtc_s[rd]); + return; + } + default: return; } } @@ -588,6 +844,18 @@ static inline int32_t comp16(const PGXPValue *pv, uint32_t value, int hi_half) { return ((int32_t)(int16_t)(hi_half ? (value >> 16) : value)) << 16; } +/* The derived-half marks (PGXP_F_DX / DY) an operand contributes through + * comp16: a tracked derived half keeps its rounding window in the sum. */ +static inline uint16_t derived_bits(const PGXPValue *pv, uint32_t value) { + if (!pv || pv->gen != s_gen) return 0; + uint16_t d = 0; + if ((pv->flags & PGXP_F_DX) && (pv->flags & PGXP_F_VX) && + ((pv->value ^ value) & 0x0000FFFFu) == 0) d |= PGXP_F_DX; + if ((pv->flags & PGXP_F_DY) && (pv->flags & PGXP_F_VY) && + ((pv->value ^ value) & 0xFFFF0000u) == 0) d |= PGXP_F_DY; + return d; +} + 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) @@ -621,9 +889,15 @@ static inline int halves_independent(uint32_t a, uint32_t b, uint32_t r, int sub static inline int half_exact(const PGXPValue *pv, uint32_t value, int hi_half) { if (!comp_tracked(pv, value, hi_half)) return 0; int32_t v16 = hi_half ? pv->y16 : pv->x16; + const int32_t half = (int32_t)(int16_t)(hi_half ? (value >> 16) : value); + /* A derived half keeps its rounding window around the integer. */ + if (pv->flags & (hi_half ? PGXP_F_DY : PGXP_F_DX)) { + const int64_t d = (int64_t)v16 - (int64_t)half * 65536; + return d > -65536 && d < 2 * 65536; + } /* A saturated projection shadow sits beyond the clamped word; it is not * the value of that half and must not be carried as one. */ - return (v16 >> 16) == (int32_t)(int16_t)(hi_half ? (value >> 16) : value); + return (v16 >> 16) == half; } static inline int carry_field(PGXPValue *dst, const PGXPValue *src, @@ -631,10 +905,15 @@ static inline int carry_field(PGXPValue *dst, const PGXPValue *src, if (!half_exact(src, sval, hi_half)) return 0; uint32_t sh = hi_half ? 16u : 0u; if ((((sval ^ result) >> sh) & 0x7FFu) != 0) return 0; - int32_t frac = (hi_half ? src->y16 : src->x16) & 0xFFFF; - int32_t v = (int32_t)((uint32_t)(int32_t)(int16_t)(result >> sh) << 16) | frac; - if (hi_half) { dst->y16 = v; dst->flags |= PGXP_F_VY; } - else { dst->x16 = v; dst->flags |= PGXP_F_VX; } + /* Keep the precise offset from the operand's integer and rebase it on + * the result half's integer (equal field, flag bits may differ). */ + const int32_t from = (int32_t)(int16_t)(sval >> sh); + const int32_t to = (int32_t)(int16_t)(result >> sh); + const int32_t v = (int32_t)((int64_t)(hi_half ? src->y16 : src->x16) + + ((int64_t)to - from) * 65536); + const uint16_t d = src->flags & (hi_half ? PGXP_F_DY : PGXP_F_DX); + if (hi_half) { dst->y16 = v; dst->flags |= (uint16_t)(PGXP_F_VY | d); } + else { dst->x16 = v; dst->flags |= (uint16_t)(PGXP_F_VX | d); } return 1; } @@ -687,11 +966,107 @@ static inline const PGXPValue *gpr_shadow(uint32_t r, PGXPValue *copy) { return copy; } +/* Scalar tier for one ALU op (cpu-mode): the destination register and the + * precise value of the result, when an operand carries a precise value and the + * op is exact arithmetic on it (no 32-bit wrap). Operands follow the hook + * convention: ADD-type s1 = rs, s2 = rt; immediate s1 = rs, s2 = imm; shifts + * s1 = rt, s2 = shamt (or rs for the variable forms); MF/MT HI/LO s1 = the + * moved value. Computed before the half tier resets the destination, which + * may alias a source. */ +static int alu_scalar(uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2, + int *dst_reg, double *out) { + const uint32_t op = f_op(instr); + int pa = 0, pb = 0; + double a, b; + *dst_reg = -1; + if (op == 0) { + const uint32_t funct = f_funct(instr); + const uint32_t rs = f_rs(instr), rt = f_rt(instr), rd = f_rd(instr); + switch (funct) { + case 0x10: case 0x12: { /* MFHI / MFLO */ + const PGXPScalar *hl = &s_gpr_s[funct == 0x10 ? PGXP_REG_HI : PGXP_REG_LO]; + *dst_reg = (int)rd; + if (!sc_valid(hl, result)) return 0; + *out = hl->v; + return 1; + } + case 0x11: case 0x13: /* MTHI / MTLO */ + *dst_reg = funct == 0x11 ? PGXP_REG_HI : PGXP_REG_LO; + a = gpr_scalar(rs, result, 0, &pa); + if (!pa) return 0; + *out = a; + return 1; + case 0x00: case 0x02: case 0x03: /* SLL / SRL / SRA */ + case 0x04: case 0x06: case 0x07: { /* SLLV / SRLV / SRAV */ + *dst_reg = (int)rd; + const uint32_t sh = s2 & 31u; /* shamt, or rs for the V forms */ + a = gpr_scalar(rt, s1, 0, &pa); + if (!pa) return 0; + const int left = (funct & 3u) == 0; + const int logical = (funct & 3u) == 2; + if (left) { + const int64_t exact = (int64_t)(int32_t)s1 * ((int64_t)1 << sh); + if (exact != (int64_t)(int32_t)result) return 0; /* wrapped */ + *out = std::ldexp(a, (int)sh); + return 1; + } + if (logical && (int32_t)s1 < 0) return 0; /* not a division */ + *out = std::ldexp(a, -(int)sh); /* floor(a/2^sh) is the guest */ + return 1; + } + case 0x20: case 0x21: case 0x22: case 0x23: { /* ADD(U) / SUB(U) */ + *dst_reg = (int)rd; + const int sub = funct >= 0x22; + a = gpr_scalar(rs, s1, 0, &pa); + b = gpr_scalar(rt, s2, 0, &pb); + if (!pa && !pb) return 0; + const int64_t exact = sub ? (int64_t)(int32_t)s1 - (int64_t)(int32_t)s2 + : (int64_t)(int32_t)s1 + (int64_t)(int32_t)s2; + if (exact != (int64_t)(int32_t)result) return 0; + *out = sub ? a - b : a + b; + return 1; + } + case 0x25: /* OR: only the MOVE idiom */ + *dst_reg = (int)rd; + if (rs != 0 && rt != 0) return 0; + a = gpr_scalar(rs != 0 ? rs : rt, result, 0, &pa); + if (!pa) return 0; + *out = a; + return 1; + default: + *dst_reg = (int)rd; + return 0; + } + } + *dst_reg = (int)f_rt(instr); + if (op == 0x08 || op == 0x09) { /* ADDI / ADDIU */ + a = gpr_scalar(f_rs(instr), s1, 0, &pa); + if (!pa) return 0; + const int64_t exact = (int64_t)(int32_t)s1 + (int64_t)f_simm(instr); + if (exact != (int64_t)(int32_t)result) return 0; + *out = a + (double)f_simm(instr); + return 1; + } + return 0; +} + +static void alu_halves(uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2); + 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; + int dst = -1; + double v = 0.0; + const int has = s_cpu_mode && alu_scalar(instr, result, s1, s2, &dst, &v); + alu_halves(instr, result, s1, s2); + if (dst > 0 && dst < 34) { + if (has) sc_set(&s_gpr_s[dst], v, result); + else sc_kill(&s_gpr_s[dst]); + } +} +static void alu_halves(uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2) { uint32_t op = f_op(instr); PGXPValue ca, cb; @@ -732,6 +1107,7 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, if (comp_tracked(src, s1, 0)) { dst->y16 = src->x16; dst->flags |= PGXP_F_VY; + if (src->flags & PGXP_F_DX) dst->flags |= PGXP_F_DY; } dst->x16 = 0; dst->flags |= PGXP_F_VX; /* low half exactly zero */ @@ -739,6 +1115,7 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, if (comp_tracked(src, s1, 1)) { dst->x16 = src->y16; dst->flags |= PGXP_F_VX; + if (src->flags & PGXP_F_DY) dst->flags |= PGXP_F_DX; } dst->y16 = ((int32_t)result >> 16) << 16; /* 0 or sign fill */ dst->flags |= PGXP_F_VY; @@ -788,7 +1165,7 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, : 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; + dst->flags = (uint16_t)(PGXP_F_VXY | derived_bits(a, s1) | derived_bits(b, s2)); /* A vertex plus / minus an offset is still that vertex. */ if (comp_tracked(a, s1, 0) && comp_tracked(a, s1, 1)) keep_depth(dst, a, b); @@ -839,7 +1216,7 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, 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; + dst->flags = (uint16_t)(PGXP_F_VXY | derived_bits(a, s1)); if (comp_tracked(a, s1, 0) && comp_tracked(a, s1, 1)) keep_depth(dst, a, nullptr); return; @@ -864,18 +1241,147 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, 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; + (void)cpu; 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. */ + * the half shadows of HI/LO are known-but-imprecise. The scalar tier + * (cpu-mode) does carry a product or quotient when an operand is precise: + * a weighted sum divided by its weight total IS a screen coordinate (the + * perspective-correct midpoint of an edge), and it turns back into a + * half when stored. */ pv_reset(&s_gpr[PGXP_REG_HI], hi); pv_reset(&s_gpr[PGXP_REG_LO], lo); + sc_kill(&s_gpr_s[PGXP_REG_HI]); + sc_kill(&s_gpr_s[PGXP_REG_LO]); + if (!s_cpu_mode) return; + const uint32_t funct = f_funct(instr); + const int is_unsigned = (funct & 1u) != 0; + if (is_unsigned && ((int32_t)s1 < 0 || (int32_t)s2 < 0)) + return; /* stay within the signed range */ + int pa = 0, pb = 0; + const double a = gpr_scalar(f_rs(instr), s1, 0, &pa); + const double b = gpr_scalar(f_rt(instr), s2, 0, &pb); + if (!pa && !pb) return; + if (funct == 0x18 || funct == 0x19) { /* MULT / MULTU */ + const int64_t exact = (int64_t)(int32_t)s1 * (int64_t)(int32_t)s2; + if (exact != (int64_t)(int32_t)lo) return; /* HI is not a sign fill */ + sc_set(&s_gpr_s[PGXP_REG_LO], a * b, lo); + return; + } + if (funct == 0x1A || funct == 0x1B) { /* DIV / DIVU */ + if (s2 == 0 || b == 0.0) return; /* divide by zero: no quotient */ + if (s1 == 0x80000000u && s2 == 0xFFFFFFFFu) return; /* overflow */ + sc_set(&s_gpr_s[PGXP_REG_LO], a / b, lo); + } } /* ------------------------------------------------------------------------- */ /* GTE producer */ /* ------------------------------------------------------------------------- */ +/* A tracked half as the precise value of the 16-bit integer the GTE used: + * the shadow must describe that integer (its half of `value`), and the + * precise value must floor to it, or lie in the derived window when the half + * came out of rounding guest arithmetic. */ +static inline int half_precise(const PGXPValue *pv, int hi, int16_t used, double *out) { + if (!pv || pv->gen != s_gen) return 0; + if (!(pv->flags & (hi ? PGXP_F_VY : PGXP_F_VX))) return 0; + const uint16_t half = hi ? (uint16_t)(pv->value >> 16) : (uint16_t)(pv->value & 0xFFFFu); + if ((int16_t)half != used) return 0; + const int32_t v16 = hi ? pv->y16 : pv->x16; + const double v = v16 / 65536.0, d = v - (double)used; + if (pv->flags & (hi ? PGXP_F_DY : PGXP_F_DX)) { + if (!(d > -1.0 && d < 2.0)) return 0; + } else if ((v16 >> 16) != (int32_t)used) { + return 0; + } + *out = v; + return 1; +} + +/* After a GTE command: every command except NCLIP / AVSZ3 / AVSZ4 rewrites + * IR0..3 / MAC1..3, so their scalars end there. MVMVA settles its own + * results (pgxp_gte_mvmva, before this runs). MAC0 never carries one. */ +extern "C" void pgxp_gte_op_end(uint32_t func) { + sc_kill(&s_gte_s[24]); + if (func == 0x06 || func == 0x2D || func == 0x2E || func == 0x12) return; + for (int r = 8; r <= 11; r++) sc_kill(&s_gte_s[r]); + for (int r = 25; r <= 27; r++) sc_kill(&s_gte_s[r]); +} + +static void mvmva_results_reset(const PGXPMvmva *op) { + /* The data-register shadows of the results describe the old values. */ + for (int r = 0; r < 3; r++) { + pv_reset(&s_gte[25 + r], (uint32_t)op->mac[r]); + pv_reset(&s_gte[9 + r], (uint32_t)op->ir[r]); + sc_kill(&s_gte_s[25 + r]); + sc_kill(&s_gte_s[9 + r]); + } +} + +extern "C" void pgxp_gte_mvmva(const PGXPMvmva *op) { + if (!g_pgxp_active) return; + if (!s_cpu_mode || op->mx > 2 || op->tv == 2) { /* reserved / FC bug */ + mvmva_results_reset(op); + return; + } + static const int kBase[3] = { 0, 8, 16 }; /* RT, LLM, LCM */ + double m[3][3], v[3], t[3]; + int any = 0; + for (int k = 0; k < 9; k++) { + const int r = k / 3, c = k % 3; + const PGXPValue *pv = &s_gtc[kBase[op->mx] + (k >> 1)]; + double p; + if (half_precise(pv, k & 1, op->m[r][c], &p)) { m[r][c] = p; any = 1; } + else m[r][c] = (double)op->m[r][c]; + } + for (int c = 0; c < 3; c++) { + double p; + int pr = 0; + if (op->vv < 3) { + const PGXPValue *pv = &s_gte[2 * op->vv + (c == 2 ? 1 : 0)]; + pr = half_precise(pv, c == 1, op->v[c], &p); + } else { + p = reg_scalar(&s_gte_s[9 + c], &s_gte[9 + c], + (uint32_t)(int32_t)op->v[c], 0, &pr); + } + v[c] = pr ? p : (double)op->v[c]; + any |= pr; + } + for (int r = 0; r < 3; r++) { + t[r] = (double)op->t[r]; + if (op->tv > 1) continue; + const int reg = (op->tv == 0 ? 5 : 13) + r; /* TR / BK words */ + int pr = 0; + const double p = reg_scalar(&s_gtc_s[reg], &s_gtc[reg], + (uint32_t)(int32_t)(op->t[r] >> 12), 0, &pr); + if (pr) { t[r] = p * 4096.0; any = 1; } + } + /* Operands are read (the vector may be IR itself); now the results. */ + mvmva_results_reset(op); + if (!any) return; + /* FLAG: MAC1/2/3 positive overflow bits 30/29/28, negative 27/26/25; + * IR1/2/3 saturation bits 24/23/22 (psx-spx GTE FLAG). */ + static const uint32_t kMacOverflow[3] = { (1u << 30) | (1u << 27), + (1u << 29) | (1u << 26), + (1u << 28) | (1u << 25) }; + static const uint32_t kIrSat[3] = { 1u << 24, 1u << 23, 1u << 22 }; + for (int r = 0; r < 3; r++) { + if (op->flag & kMacOverflow[r]) continue; + /* The integers reported must reproduce the guest MAC: proves the + * operands handed over are the ones the GTE used. */ + const int64_t exact = op->t[r] + (int64_t)op->m[r][0] * op->v[0] + + (int64_t)op->m[r][1] * op->v[1] + + (int64_t)op->m[r][2] * op->v[2]; + if ((exact >> op->shift) != (int64_t)op->mac[r]) continue; + const double p = (t[r] + m[r][0] * v[0] + m[r][1] * v[1] + m[r][2] * v[2]) / + (op->shift ? 4096.0 : 1.0); + sc_set(&s_gte_s[25 + r], p, (uint32_t)op->mac[r]); + if (!(op->flag & kIrSat[r]) && op->ir[r] == op->mac[r]) + sc_set(&s_gte_s[9 + r], p, (uint32_t)op->ir[r]); + } +} + 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, @@ -911,12 +1417,13 @@ extern "C" int pgxp_project_precise(int64_t mac1, int64_t mac2, int64_t mac3, } /* Defined below with the GPU consumer. */ -static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half); +static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half, + int derived); 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)) { + if (pgxp_agrees(x16, hx, hx, 0) && pgxp_agrees(y16, hy, hy, 0)) { s_stats.ppp_produced++; return 1; } @@ -979,7 +1486,7 @@ extern "C" int pgxp_get_gte_sxy_checked(uint32_t index, uint32_t expect, /* 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); + int32_t int_y, uint32_t word, uint16_t flags); extern "C" int pgxp_gte_nclip_precise(const uint32_t sxy[3], int64_t *cross) { if (!g_pgxp_active) return 0; @@ -993,7 +1500,7 @@ extern "C" int pgxp_gte_nclip_precise(const uint32_t sxy[3], int64_t *cross) { * 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) + (int16_t)(sxy[i] >> 16), sxy[i], pv->flags) != 0) return 0; x[i] = pv->x16; y[i] = pv->y16; @@ -1040,28 +1547,43 @@ extern "C" void pgxp_gte_reg_written(int reg, uint32_t value) { * 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) { +static inline int pgxp_agrees(int32_t p16, int32_t native, int16_t half, + int derived) { + const int64_t d = (int64_t)p16 - (int64_t)native * 65536; + /* A derived half (PGXP_F_DX/DY): the guest integer is the rounding its + * own arithmetic chose, so the value may sit one pixel either side of + * the truncation window - still bounded, still the same vertex. */ + if (derived) return d > -65536 && d < 2 * 65536; 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; } +/* Tolerance-clamp distance of one axis: how far the precise value moved the + * vertex from the guest integer. For a derived half the first pixel above + * the integer is the guest arithmetic's own rounding, not movement. */ +static inline float pgxp_moved(int32_t p16, int32_t native, int derived) { + const float d = (float)((int64_t)p16 - (int64_t)native * 65536) * (1.0f / 65536.0f); + if (!derived) return std::fabs(d); + return d < 0.0f ? -d : (d > 1.0f ? d - 1.0f : 0.0f); +} + /* 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. */ + * same vertices. `flags` are the shadow's (derived halves). */ 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))) + int32_t int_y, uint32_t word, uint16_t flags) { + const int dx_derived = (flags & PGXP_F_DX) != 0; + const int dy_derived = (flags & PGXP_F_DY) != 0; + if (!pgxp_agrees(px, int_x, (int16_t)(word & 0xFFFFu), dx_derived) || + !pgxp_agrees(py, int_y, (int16_t)(word >> 16), dy_derived)) 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) + if (pgxp_moved(px, int_x, dx_derived) > s_tolerance || + pgxp_moved(py, int_y, dy_derived) > s_tolerance) return 2; } return 0; @@ -1090,6 +1612,7 @@ static int pgxp_get_precise_vertex_impl(uint32_t addr, uint32_t packet_word, int32_t px = 0, py = 0; uint16_t pz = 0; + uint16_t pflags = 0; int have = 0; if (s_enabled && addr != 0xFFFFFFFFu) { @@ -1100,6 +1623,7 @@ static int pgxp_get_precise_vertex_impl(uint32_t addr, uint32_t packet_word, px = pv->x16; py = pv->y16; pz = (pv->flags & PGXP_F_VZ) ? pv->z : 0; + pflags = pv->flags; have = PGXP_SRC_DATAFLOW; } else { s_stats.value_mismatch++; @@ -1126,7 +1650,7 @@ static int pgxp_get_precise_vertex_impl(uint32_t addr, uint32_t packet_word, * 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); + const int why = pgxp_accept(px, py, int_x, int_y, packet_word, pflags); if (why == 1) { s_stats.trunc_reject++; have = 0; diff --git a/runtime/src/psx_interpreter.c b/runtime/src/psx_interpreter.c index 4f94d87e..cc2310a6 100644 --- a/runtime/src/psx_interpreter.c +++ b/runtime/src/psx_interpreter.c @@ -426,6 +426,7 @@ static void exec_one(CPUState* cpu) { break; case 0x06: /* CTC2 */ gte_write_ctrl(cpu, RD(insn), rt_val); + psx_pgxp_cop2(cpu, insn, rt_val, 0); break; default: if (cop_op & 0x10) { diff --git a/runtime/tests/gpu_exec_stubs.inc b/runtime/tests/gpu_exec_stubs.inc index ae5db385..43e7a138 100644 --- a/runtime/tests/gpu_exec_stubs.inc +++ b/runtime/tests/gpu_exec_stubs.inc @@ -162,6 +162,7 @@ int pgxp_probe_precise_vertex(uint32_t addr, uint32_t packet_word, return test_word_precise(addr) ? PGXP_SRC_DATAFLOW : PGXP_SRC_NATIVE; } void pgxp_note_triangle(int precise) { (void)precise; } +void pgxp_note_triangle_detail(const PGXPTriRecord *rec) { (void)rec; } void pgxp_note_rect_bypass(int all_precise) { if (all_precise) g_test_rect_bypass++; else g_test_rect_partial++; diff --git a/runtime/tests/test_gpu_gp0_history_guard.py b/runtime/tests/test_gpu_gp0_history_guard.py index 00877808..dd77a53c 100644 --- a/runtime/tests/test_gpu_gp0_history_guard.py +++ b/runtime/tests/test_gpu_gp0_history_guard.py @@ -192,6 +192,7 @@ STUBS_C = r""" #include "gpu.h" #include "gpu_render.h" #include "cpu_state.h" +#include "pgxp.h" #include "color_lut.h" #include "ws_aspect_cone_math.h" #include "ws_ui_group.h" @@ -263,6 +264,7 @@ int gte_precision_load_word(uint32_t addr, uint32_t packed, void pgxp_invalidate_all(void) {} void pgxp_invalidate_word(uint32_t addr) { (void)addr; } void pgxp_note_triangle(int precise) { (void)precise; } +void pgxp_note_triangle_detail(const PGXPTriRecord *rec) { (void)rec; } void pgxp_note_rect_bypass(int all_precise) { (void)all_precise; } int pgxp_probe_precise_vertex(uint32_t addr, uint32_t packet_word, int32_t int_x, int32_t int_y) diff --git a/runtime/tests/test_pgxp.cpp b/runtime/tests/test_pgxp.cpp index 57f3bcc9..3bbc8775 100644 --- a/runtime/tests/test_pgxp.cpp +++ b/runtime/tests/test_pgxp.cpp @@ -780,6 +780,139 @@ int main(void) { pgxp_set_projection_tracking(0); } + /* --- scalar tier: GTE-as-multiplier edge midpoint (Ape Escape terrain + * subdivision, 0x8001DB74): CTC2 two tracked X halves into a light- + * matrix row, MVMVA by the vertex depths, MFC2 MAC1, ADDU the rounding + * bias, DIV by the depth sum, MFLO, SH into the vertex table. --- */ + { + pgxp_set_cpu_mode(1); + pgxp_set_tolerance(1.0f); + const uint32_t TBL = 0x1F800280u; + /* v0 (100.9, 50.5) z 200, v1 (121.9, 60.25) z 300 */ + const uint32_t w0 = (50u << 16) | 100u, w1 = (60u << 16) | 121u; + pgxp_gte_push_sxy((100 << 16) | 0xE666, (50 << 16) | 0x8000, 200, w0); + psx_pgxp_cop2(nullptr, SWC2(14), w0, TBL + 0); + pgxp_gte_push_sxy((121 << 16) | 0xE666, (60 << 16) | 0x4000, 300, w1); + psx_pgxp_cop2(nullptr, SWC2(14), w1, TBL + 8); + + /* lh t0,0(s1); lh v0,8(s1); andi v1,t0,0xffff; sll a0,v0,16; + * or v1,v1,a0; ctc2 v1,$8 */ + psx_pgxp_load(nullptr, LH(17, 8), TBL + 0, 100u); + psx_pgxp_load(nullptr, LH(17, 2), TBL + 8, 121u); + psx_pgxp_alu(nullptr, ANDI(8, 3, 0xFFFF), 100u, 100u, 0xFFFFu); + psx_pgxp_alu(nullptr, SLL(2, 4, 16), 121u << 16, 121u, 16u); + psx_pgxp_alu(nullptr, OR(3, 4, 3), (121u << 16) | 100u, 100u, 121u << 16); + psx_pgxp_cop2(nullptr, enc_cop2(0x06, 3, 8), (121u << 16) | 100u, 0); + /* mtc2 t9(200),IR1; mtc2 t8(300),IR2; mtc2 zero,IR3 (exact) */ + psx_pgxp_alu(nullptr, ADDIU(0, 25, 200), 200u, 0u, 200u); + psx_pgxp_alu(nullptr, ADDIU(0, 24, 300), 300u, 0u, 300u); + psx_pgxp_cop2(nullptr, MTC2(25, 9), 200u, 0); + psx_pgxp_cop2(nullptr, MTC2(24, 10), 300u, 0); + psx_pgxp_cop2(nullptr, MTC2(0, 11), 0u, 0); + + /* MVMVA sf=0 mx=LLM v=IR cv=none: MAC1 = 100*200 + 121*300 = 56300 */ + PGXPMvmva op; + std::memset(&op, 0, sizeof op); + op.mx = 1; op.vv = 3; op.tv = 3; op.shift = 0; + op.m[0][0] = 100; op.m[0][1] = 121; + op.v[0] = 200; op.v[1] = 300; op.v[2] = 0; + op.mac[0] = 56300; op.ir[0] = 0x7FFF; + op.flag = 1u << 24; /* IR1 saturated */ + pgxp_gte_mvmva(&op); + pgxp_gte_op_end(0x12); + + /* mfc2 a1,MAC1; addu a1,a1,a3(0); addu v0,t9,t8; div a1,v0; mflo a1 */ + psx_pgxp_cop2(nullptr, MFC2(5, 25), 56300u, 0); + psx_pgxp_alu(nullptr, ADDIU(0, 7, 0), 0u, 0u, 0u); + psx_pgxp_alu(nullptr, ADDU(5, 7, 5), 56300u, 56300u, 0u); + psx_pgxp_alu(nullptr, ADDU(25, 24, 2), 500u, 200u, 300u); + psx_pgxp_muldiv(nullptr, enc_r(5, 2, 0, 0, 0x1A), 56300u % 500u, + 56300u / 500u, 56300u, 500u); + psx_pgxp_alu(nullptr, enc_r(0, 0, 5, 0, 0x12), 112u, 112u, 0u); + /* sh a1,0x48(s1) */ + psx_pgxp_store(nullptr, SH(17, 5), TBL + 0x48, 112u); + + /* precise midpoint: (100.9*200 + 121.9*300) / 500 = 113.5 */ + uint32_t live = 0, value = 0, flags = 0; int32_t sx = 0, sy = 0; uint16_t sz = 0; + int lv = 0; + CHECK(pgxp_debug_shadow(0, TBL + 0x48, &lv, &value, &flags, &sx, &sy, &sz)); + (void)live; + CHECK(lv == 1 && (flags & 1u) != 0); + CHECK(std::fabs(sx / 65536.0 - 113.5) < 1e-3); + CHECK((flags & 0x10u) != 0); /* derived: floor 113 != 112 */ + + /* y half from a non-derived value; then the whole word reaches a + * packet and the GPU believes it inside the derived window. */ + psx_pgxp_load(nullptr, LH(17, 6), TBL + 2, 50u); + psx_pgxp_store(nullptr, SH(17, 6), TBL + 0x4A, 50u); + const uint32_t mid = (50u << 16) | 112u; + psx_pgxp_cop2(nullptr, LWC2(12), mid, TBL + 0x48); + psx_pgxp_cop2(nullptr, SWC2(12), mid, ADDR_A); + int32_t x, y; uint16_t z; + CHECK(lookup(ADDR_A, mid, 112, 50, &x, &y, &z) == PGXP_SRC_DATAFLOW); + CHECK(std::fabs(x / 65536.0 - 113.5) < 1e-3 && y == ((50 << 16) | 0x8000)); + + /* tolerance clamps movement beyond the guest rounding pixel */ + pgxp_set_tolerance(0.25f); + CHECK(lookup(ADDR_A, mid, 112, 50, &x, &y, &z) == PGXP_SRC_NATIVE); + pgxp_set_tolerance(1.0f); + + /* a scalar does not survive a write that does not produce one: the + * register reloaded from memory stores imprecise */ + psx_pgxp_load(nullptr, LW(17, 5), TBL + 0x100, 112u); + psx_pgxp_store(nullptr, SH(17, 5), TBL + 0x58, 112u); + CHECK(pgxp_debug_shadow(0, TBL + 0x58, &lv, &value, &flags, &sx, &sy, &sz)); + CHECK((flags & 1u) == 0); + + /* the lwl/lwr + swl/swr word-copy idiom on an aligned address moves + * whole words, so it carries the vertex (Ape Escape's terrain grid + * copies its projected corners this way, 0x80044B00) */ + { + const uint32_t LWL = enc_i(0x22, 17, 11, 0), LWR = enc_i(0x26, 17, 11, 0); + const uint32_t SWL = enc_i(0x2A, 17, 11, 0), SWR = enc_i(0x2E, 17, 11, 0); + psx_pgxp_load(nullptr, LWL, TBL + 3, w0); + psx_pgxp_load(nullptr, LWR, TBL + 0, w0); + psx_pgxp_store(nullptr, SWL, TBL + 0x103, w0); + psx_pgxp_store(nullptr, SWR, TBL + 0x100, w0); + int32_t cx, cy; uint16_t cz; + CHECK(lookup(TBL + 0x100, w0, 100, 50, &cx, &cy, &cz) == PGXP_SRC_DATAFLOW); + CHECK(cx == ((100 << 16) | 0xE666) && cy == ((50 << 16) | 0x8000)); + /* a genuinely unaligned piece still drops the word */ + psx_pgxp_store(nullptr, enc_i(0x2A, 17, 11, 0), TBL + 0x101, w0); + CHECK(lookup(TBL + 0x100, w0, 100, 50, &cx, &cy, &cz) != PGXP_SRC_DATAFLOW); + } + + /* a MAC1 that overflowed (FLAG bit 27, negative) carries nothing; + * MAC2's flags (bits 29/26) leave MAC1 alone */ + { + PGXPMvmva o2 = op; + o2.flag = 1u << 27; + pgxp_gte_mvmva(&o2); + psx_pgxp_cop2(nullptr, MFC2(5, 25), 56300u, 0); + psx_pgxp_muldiv(nullptr, enc_r(5, 2, 0, 0, 0x1A), 300u, 112u, 56300u, 500u); + psx_pgxp_alu(nullptr, enc_r(0, 0, 5, 0, 0x12), 112u, 112u, 0u); + psx_pgxp_store(nullptr, SH(17, 5), TBL + 0x68, 112u); + CHECK(pgxp_debug_shadow(0, TBL + 0x68, &lv, &value, &flags, &sx, &sy, &sz)); + CHECK((flags & 1u) == 0); + o2.flag = (1u << 29) | (1u << 26); + pgxp_gte_mvmva(&o2); + psx_pgxp_cop2(nullptr, MFC2(5, 25), 56300u, 0); + psx_pgxp_muldiv(nullptr, enc_r(5, 2, 0, 0, 0x1A), 300u, 112u, 56300u, 500u); + psx_pgxp_alu(nullptr, enc_r(0, 0, 5, 0, 0x12), 112u, 112u, 0u); + psx_pgxp_store(nullptr, SH(17, 5), TBL + 0x70, 112u); + CHECK(pgxp_debug_shadow(0, TBL + 0x70, &lv, &value, &flags, &sx, &sy, &sz)); + CHECK((flags & 1u) != 0); + } + + /* the scalar tier is cpu-mode only */ + pgxp_set_cpu_mode(0); + psx_pgxp_cop2(nullptr, MFC2(5, 25), 56300u, 0); + psx_pgxp_store(nullptr, SH(17, 5), TBL + 0x60, 112u); + CHECK(pgxp_debug_shadow(0, TBL + 0x60, &lv, &value, &flags, &sx, &sy, &sz)); + CHECK((flags & 1u) == 0); + pgxp_set_tolerance(-1.0f); + } + /* --- stats sanity: dataflow hits were counted --- */ { PGXPStats st; diff --git a/tools/bios_emitter_fingerprint.cmake b/tools/bios_emitter_fingerprint.cmake index c324bb74..32050d6e 100644 --- a/tools/bios_emitter_fingerprint.cmake +++ b/tools/bios_emitter_fingerprint.cmake @@ -35,6 +35,8 @@ set(_files recompiler/src/full_function_emitter.cpp recompiler/src/full_function_emitter.h recompiler/src/strict_translator.cpp + recompiler/src/pgxp_hook_emitter.cpp + recompiler/src/pgxp_hook_emitter.h recompiler/src/main_bios.cpp recompiler/src/function_discovery.cpp recompiler/src/bios_address_model.cpp