Preserve far-world projection through validated intact GTE words

This commit is contained in:
Shokunin
2026-10-21 10:15:19 -07:00
parent 9caf4c22bf
commit 32b4388a88
4 changed files with 72 additions and 14 deletions
+5 -1
View File
@@ -62,7 +62,8 @@ int pgxp_position_fallback(void);
* near geometry. Guest-visible SXY, MAC0 and FLAG are unchanged either way.
* Because that position can then differ from the guest integer by more than
* the fraction, truncation agreement becomes a window (PGXP_PPP_AGREE_*) and
* a vertex at the GTE's saturation limit must still agree exactly. One
* unclamped IR projections need an intact, value-validated GTE word; halves
* and cache fallbacks keep the strict saturation checks. One
* consumer is not visual: [widescreen] precise_nclip takes its branch sign
* from these shadows, so in a title that sets both, that (already
* non-faithful) widescreen sign follows the exact projection too. */
@@ -91,6 +92,7 @@ int pgxp_preserve_projection(void);
* when none). Returns 1 with the position in x16/y16 when the vertex
* qualifies (sf=1, IR1/IR2 not saturated, SZ3 not saturated and nonzero, and
* no divide overflow: H < 2*SZ3), else 0 and the caller keeps the IR path. */
/* Return 2 for an opt-in IR-unclamped projection, 1 for ordinary precision. */
int pgxp_project_precise(int64_t mac1, int64_t mac2, int64_t mac3, int shift,
int32_t ir1, int32_t ir2, uint32_t sz3, uint32_t h,
int32_t ofx, int32_t ofy, int64_t x_num, int64_t x_den,
@@ -164,6 +166,8 @@ void pgxp_suppress_end(void);
/* Called at the RTPS/RTPT projection with the pre-truncation 16.16 screen
* coordinates, the projected depth (SZ3), and the packed SXY word the guest
* sees. Shifts the shadow FIFO exactly like push_sxy (regs 12..15). */
/* Tag only immediately after push_sxy of a qualified unclamped projection. */
void pgxp_gte_mark_unclamped(void);
void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3, uint32_t packed);
/* Host-only homogeneous screen projection, before SZ/SXY/divider saturation.
+8 -4
View File
@@ -1232,6 +1232,7 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0, uint32_t instr)
int64_t cx16 = sx16 < -kLim ? -kLim : (sx16 > kLim - 1 ? kLim - 1 : sx16);
int64_t cy16 = sy16 < -kLim ? -kLim : (sy16 > kLim - 1 ? kLim - 1 : sy16);
int32_t px16 = (int32_t)cx16, py16 = (int32_t)cy16;
bool unclamped_projection = false;
/* Preserve projection precision (G1.11): shadow the exact projection
* instead when the vertex qualifies and it lies inside the GPU's
* truncation-agreement window; outside it the IR path's shadow is
@@ -1241,15 +1242,18 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0, uint32_t instr)
* passes), where the shadow would be dropped anyway. */
if (!proj_override && pgxp_preserve_projection() && pgxp_active()) {
int32_t ex16, ey16;
if (pgxp_project_precise(mac1, mac2, mac3, shift, gte->IR1, gte->IR2,
gte->SZ[3], gte_h_scaled(gte), gte->OFX, gte->OFY,
x_num, x_den, &ex16, &ey16) &&
pgxp_ppp_accept(ex16, ey16, (uint32_t)gte->SXY[2])) {
const int precise = pgxp_project_precise(mac1, mac2, mac3, shift,
gte->IR1, gte->IR2, gte->SZ[3], gte_h_scaled(gte),
gte->OFX, gte->OFY, x_num, x_den, &ex16, &ey16);
if (precise == 2 || (precise == 1 &&
pgxp_ppp_accept(ex16, ey16, (uint32_t)gte->SXY[2]))) {
unclamped_projection = precise == 2;
px16 = ex16;
py16 = ey16;
}
}
pgxp_gte_push_sxy(px16, py16, gte->SZ[3], (uint32_t)gte->SXY[2]);
if (unclamped_projection) pgxp_gte_mark_unclamped();
if (!proj_override && pgxp_projection_tracking() && shift == 12 && !lm) {
const double z = (double)(mac3 >> 12);
const double h_proj = (double)gte_h_scaled(gte);
+29 -9
View File
@@ -63,6 +63,7 @@ enum {
* 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_UNCLAMPED = 1u << 6, /* exact GTE projection; intact word only */
PGXP_F_VXY = PGXP_F_VX | PGXP_F_VY,
};
@@ -551,7 +552,7 @@ static inline int pv_live(const PGXPValue *pv) {
static inline void pv_validate(PGXPValue *pv, uint32_t actual) {
if (pv->gen != s_gen) return;
uint32_t diff = pv->value ^ actual;
if (diff) pv->flags &= (uint16_t)~PGXP_F_PROJECTION;
if (diff) pv->flags &= (uint16_t)~(PGXP_F_PROJECTION | PGXP_F_UNCLAMPED);
if ((pv->flags & PGXP_F_VX) && (diff & 0x0000FFFFu))
pv->flags &= (uint16_t)~(PGXP_F_VX | PGXP_F_DX | PGXP_F_VZ);
if ((pv->flags & PGXP_F_VY) && (diff & 0xFFFF0000u))
@@ -768,7 +769,7 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr,
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_DY) : (PGXP_F_VX | PGXP_F_DX)) |
PGXP_F_VZ | PGXP_F_PROJECTION);
PGXP_F_VZ | PGXP_F_PROJECTION | PGXP_F_UNCLAMPED);
dst->z = 0;
int ok = 0, derived = 0;
int32_t v16 = 0;
@@ -797,7 +798,7 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr,
dst->value = (dst->value & ~(0xFFu << shift)) |
((value & 0xFFu) << shift);
dst->flags &= (uint16_t)~(((addr & 2u) ? (PGXP_F_VY | PGXP_F_DY) : (PGXP_F_VX | PGXP_F_DX)) |
PGXP_F_VZ | PGXP_F_PROJECTION);
PGXP_F_VZ | PGXP_F_PROJECTION | PGXP_F_UNCLAMPED);
dst->z = 0;
return;
}
@@ -1504,11 +1505,20 @@ extern "C" int pgxp_project_precise(int64_t mac1, int64_t mac2, int64_t mac3,
int64_t x_num, int64_t x_den,
int32_t *x16, int32_t *y16) {
/* Qualify exactly the vertices whose guest projection is the plain
* IR * (H / SZ3) of the continuous one: sf=1, no IR1/IR2 clamp (the
* stored IR equals MAC >> 12), SZ3 unclamped and nonzero, and no UNR
* IR * (H / SZ3) of the continuous one: sf=1, verified IR1/IR2 conversion, SZ3 unclamped and nonzero, and no UNR
* divide overflow (the GTE saturates the quotient when H >= 2*SZ3). */
if (shift != 12 || x_den <= 0) return 0;
if ((mac1 >> 12) != (int64_t)ir1 || (mac2 >> 12) != (int64_t)ir2) return 0;
const int64_t ix = mac1 >> 12, iy = mac2 >> 12;
const bool unclamped = ix != ir1 || iy != ir2;
if (unclamped) {
/* Only the opt-in exact projection may bypass IR saturation. Reject
* wrapped accumulators, lm=1 sign clamps and every other mismatch. */
const int64_t lo = -(INT64_C(1) << 43), hi = (INT64_C(1) << 43) - 1;
const int64_t cx = ix < -32768 ? -32768 : ix > 32767 ? 32767 : ix;
const int64_t cy = iy < -32768 ? -32768 : iy > 32767 ? 32767 : iy;
if (!s_preserve_projection || mac1 < lo || mac1 > hi ||
mac2 < lo || mac2 > hi || cx != ir1 || cy != ir2) return 0;
}
const int64_t z_int = mac3 >> 12;
if (z_int <= 0 || z_int > 0xFFFF || (uint32_t)z_int != sz3) return 0;
if ((int64_t)h >= 2 * z_int) return 0;
@@ -1528,7 +1538,7 @@ extern "C" int pgxp_project_precise(int64_t mac1, int64_t mac2, int64_t mac3,
if (fy > lim - 1.0) fy = lim - 1.0;
*x16 = (int32_t)std::floor(fx);
*y16 = (int32_t)std::floor(fy);
return 1;
return unclamped ? 2 : 1;
}
/* Defined below with the GPU consumer. */
@@ -1562,6 +1572,12 @@ extern "C" void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3,
s_gte[15] = *pv; /* SXYP mirrors SXY2 */
}
extern "C" void pgxp_gte_mark_unclamped(void) {
if (!g_pgxp_active) return;
s_gte[14].flags |= PGXP_F_UNCLAMPED;
s_gte[15] = s_gte[14];
}
extern "C" int pgxp_get_gte_sxy(uint32_t index, int32_t *x16, int32_t *y16) {
return pgxp_get_gte_sxy_checked(index, 0u, 0, x16, y16);
}
@@ -1695,8 +1711,12 @@ static inline int pgxp_accept(int32_t px, int32_t py, int32_t int_x,
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))
/* This tag can only ride an intact, value-validated GTE word. Arithmetic
* constructs new flags and loses it; the position-cache tier never has it.
* Clamped IR is an integer hardware limit, not the enhanced visual vertex. */
if (!(s_preserve_projection && (flags & PGXP_F_UNCLAMPED)) &&
(!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) {
if (pgxp_moved(px, int_x, dx_derived) > s_tolerance ||
+30
View File
@@ -622,6 +622,36 @@ int main(void) {
&x, &y) == 1);
}
/* Far-world projection: exact dataflow may recover IR saturation, while
* stale words, lm sign clamps and half writes must stay rejected. */
{
const int saved_preserve = pgxp_preserve_projection();
pgxp_set_preserve_projection(1);
pgxp_set_tolerance(-1.0f);
int32_t x, y;
CHECK(pgxp_project_precise(INT64_C(40000)*4096, INT64_C(-35000)*4096,
INT64_C(60000)*4096, 12, 32767, -32768, 60000, 300,
160<<16, 120<<16, 1, 1, &x, &y) == 2);
CHECK(x == 360*65536 && y == -55*65536);
const uint32_t packed = (uint16_t)323 | ((uint32_t)(uint16_t)-44 << 16);
pgxp_gte_push_sxy(x, y, 60000, packed);
pgxp_gte_mark_unclamped();
pgxp_store_gte_reg(0x80005000, 14);
int32_t px, py; uint16_t z;
CHECK(pgxp_get_precise_vertex(0x80005000, packed, 323, -44, &px, &py, &z));
CHECK(px == x && py == y && z == 60000);
CHECK(!pgxp_get_precise_vertex(0x80005000, packed^1u, 322, -44, &px, &py, &z));
psx_pgxp_cop2(nullptr, MFC2(2, 14), packed, 0);
psx_pgxp_store(nullptr, SH(1, 2), 0x80005000, packed);
CHECK(!pgxp_get_precise_vertex(0x80005000, packed, 323, -44, &px, &py, &z));
CHECK(pgxp_project_precise(-4096, 0, INT64_C(60000)*4096, 12, 0, 0,
60000, 300, 0, 0, 1, 1, &x, &y) == 0);
pgxp_set_preserve_projection(0);
CHECK(pgxp_project_precise(INT64_C(40000)*4096, 0, INT64_C(60000)*4096,
12, 32767, 0, 60000, 300, 0, 0, 1, 1, &x, &y) == 0);
pgxp_set_preserve_projection(saved_preserve);
}
/* --- probe: the same decision, not counted --- */
{
produce_at(ADDR_A);