diff --git a/runtime/include/cpu_state.h b/runtime/include/cpu_state.h index d03b97a1..31b44d6b 100644 --- a/runtime/include/cpu_state.h +++ b/runtime/include/cpu_state.h @@ -229,6 +229,11 @@ extern uint32_t gte_geometry_correction_hits(void); extern void gte_geometry_correction_stats(uint32_t *lookups, uint32_t *hits, uint32_t *miss_unrecorded, uint32_t *miss_ambiguous); +/* Same position-cache lookup without changing geometry-correction counters; + * reserved for non-rendering probes that inspect a vertex before drawing. */ +extern int gte_geometry_correction_lookup_probe(uint32_t packed, + int32_t *x16, + int32_t *y16); /* Exact NCLIP audit coverage: precise = all three SXY shadows are coherent and * word-validated; fallback = native integer-only; disagreements counts cases diff --git a/runtime/src/gte.cpp b/runtime/src/gte.cpp index e591a58b..0bc1c843 100644 --- a/runtime/src/gte.cpp +++ b/runtime/src/gte.cpp @@ -360,25 +360,45 @@ extern "C" uint32_t gte_geometry_correction_hits(void) { return s_geom_hits; } -extern "C" int gte_geometry_correction_lookup(uint32_t packed, - int32_t *x16, int32_t *y16) { +static int gte_geometry_correction_lookup_impl(uint32_t packed, + int32_t *x16, int32_t *y16, + int count_stats) { if (s_speculative_depth != 0 || !s_geom_enabled || !s_geom_cache) return 0; int64_t slot = geom_slot(packed); if (slot < 0) return 0; - s_geom_lookups++; + if (count_stats) s_geom_lookups++; const GeomVertex &entry = s_geom_cache[slot]; - if (entry.generation != s_geom_generation) { s_geom_miss_unrec++; return 0; } + if (entry.generation != s_geom_generation) { + if (count_stats) s_geom_miss_unrec++; + return 0; + } /* Ambiguity gate, as in both references (beetle gFlags == 1): if two * DIFFERENT sub-pixel positions rounded to this same pixel, we cannot tell * which one this packet means, and guessing is what makes a vertex inherit * a neighbour's fraction. Fall back to the faithful integer position. */ - if (entry.ambiguous) { s_geom_miss_ambig++; return 0; } + if (entry.ambiguous) { + if (count_stats) s_geom_miss_ambig++; + return 0; + } if (x16) *x16 = entry.x16; if (y16) *y16 = entry.y16; - s_geom_hits++; + if (count_stats) s_geom_hits++; return 1; } +extern "C" int gte_geometry_correction_lookup(uint32_t packed, + int32_t *x16, int32_t *y16) { + return gte_geometry_correction_lookup_impl(packed, x16, y16, 1); +} + +/* Read-only variant used by instrumentation probes. A probe must not change + * the geometry-correction census that describes rendered vertices. */ +extern "C" int gte_geometry_correction_lookup_probe(uint32_t packed, + int32_t *x16, + int32_t *y16) { + return gte_geometry_correction_lookup_impl(packed, x16, y16, 0); +} + /* Render-pass checkpoint for the gte.cpp side of precision tracking: the * position cache entries a pass writes are journaled and put back, and the * speculative bracket depth is restored (a watchdog abort can leave one open). diff --git a/runtime/src/pgxp.cpp b/runtime/src/pgxp.cpp index 98199904..9e9ac6ce 100644 --- a/runtime/src/pgxp.cpp +++ b/runtime/src/pgxp.cpp @@ -43,6 +43,8 @@ /* gte.cpp — position-cache fallback tier (ambiguity-gated, G1.4 exact table) */ extern "C" int gte_geometry_correction_lookup(uint32_t packed, int32_t *x16, int32_t *y16); +extern "C" int gte_geometry_correction_lookup_probe(uint32_t packed, + int32_t *x16, int32_t *y16); /* ------------------------------------------------------------------------- */ /* Shadow storage */ @@ -1080,10 +1082,10 @@ static inline int32_t field_rebase(int32_t v16, uint32_t half, int32_t parsed) { return (int32_t)((int64_t)v16 + ((int64_t)as11 - as16) * 65536); } -extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word, - int32_t int_x, int32_t int_y, - int32_t *x16, int32_t *y16, - uint16_t *sz) { +static int pgxp_get_precise_vertex_impl(uint32_t addr, uint32_t packet_word, + int32_t int_x, int32_t int_y, + int32_t *x16, int32_t *y16, + uint16_t *sz, int probe) { s_stats.lookups++; int32_t px = 0, py = 0; @@ -1105,8 +1107,10 @@ extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word, } } - if (!have && s_position_fallback && - gte_geometry_correction_lookup(packet_word, &px, &py)) { + const int fallback_hit = !have && s_position_fallback && + (probe ? gte_geometry_correction_lookup_probe(packet_word, &px, &py) + : gte_geometry_correction_lookup(packet_word, &px, &py)); + if (fallback_hit) { pz = 0; /* fallback never carries depth */ have = PGXP_SRC_FALLBACK; } @@ -1149,13 +1153,21 @@ extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word, return have; } +extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word, + int32_t int_x, int32_t int_y, + int32_t *x16, int32_t *y16, + uint16_t *sz) { + return pgxp_get_precise_vertex_impl(addr, packet_word, int_x, int_y, + x16, y16, sz, 0); +} + extern "C" int pgxp_probe_precise_vertex(uint32_t addr, uint32_t packet_word, int32_t int_x, int32_t int_y) { const PGXPStats saved = s_stats; int32_t x16, y16; uint16_t sz; - const int src = pgxp_get_precise_vertex(addr, packet_word, int_x, int_y, - &x16, &y16, &sz); + const int src = pgxp_get_precise_vertex_impl(addr, packet_word, int_x, int_y, + &x16, &y16, &sz, 1); s_stats = saved; return src; } diff --git a/runtime/tests/test_gte_register_access.cpp b/runtime/tests/test_gte_register_access.cpp index 5df26aee..acae23ff 100644 --- a/runtime/tests/test_gte_register_access.cpp +++ b/runtime/tests/test_gte_register_access.cpp @@ -998,6 +998,51 @@ int test_precision_speculative_transaction() { return 0; } +int test_pgxp_probe_does_not_count_geometry_lookup() { + constexpr uint32_t packed = (80u << 16) | 160u; + constexpr int32_t x16 = (160 << 16) + 0x4000; + constexpr int32_t y16 = (80 << 16) + 0x2000; + const int fallback_was_enabled = pgxp_position_fallback(); + gte_geometry_correction_set(1); + gte_test_seed_geometry(packed, x16, y16); + pgxp_set_position_fallback(1); + + uint32_t lookups0 = 0, hits0 = 0, miss_unrecorded0 = 0, miss_ambiguous0 = 0; + gte_geometry_correction_stats(&lookups0, &hits0, &miss_unrecorded0, + &miss_ambiguous0); + if (pgxp_probe_precise_vertex(0xFFFFFFFFu, packed, 160, 80) != + PGXP_SRC_FALLBACK) + return fail_value("PGXP fallback probe source", 0, 0, packed, + PGXP_SRC_FALLBACK, PGXP_SRC_NATIVE); + uint32_t lookups1 = 0, hits1 = 0, miss_unrecorded1 = 0, miss_ambiguous1 = 0; + gte_geometry_correction_stats(&lookups1, &hits1, &miss_unrecorded1, + &miss_ambiguous1); + if (lookups1 != lookups0 || hits1 != hits0 || + miss_unrecorded1 != miss_unrecorded0 || + miss_ambiguous1 != miss_ambiguous0) + return fail_value("PGXP probe leaves geometry counters unchanged", 0, 0, + packed, lookups0, lookups1); + + int32_t got_x = 0, got_y = 0; + uint16_t got_z = 1; + if (pgxp_get_precise_vertex(0xFFFFFFFFu, packed, 160, 80, + &got_x, &got_y, &got_z) != PGXP_SRC_FALLBACK || + got_x != x16 || got_y != y16 || got_z != 0) + return fail_value("PGXP draw uses geometry fallback", 0, 0, packed, + static_cast(x16), static_cast(got_x)); + uint32_t lookups2 = 0, hits2 = 0, miss_unrecorded2 = 0, miss_ambiguous2 = 0; + gte_geometry_correction_stats(&lookups2, &hits2, &miss_unrecorded2, + &miss_ambiguous2); + gte_geometry_correction_set(0); + pgxp_set_position_fallback(fallback_was_enabled); + if (lookups2 != lookups1 + 1u || hits2 != hits1 + 1u || + miss_unrecorded2 != miss_unrecorded1 || + miss_ambiguous2 != miss_ambiguous1) + return fail_value("draw increments geometry counters", 0, 0, packed, + lookups1 + 1u, lookups2); + return 0; +} + } // namespace /* Preserve projection precision (docs/ENHANCEMENTS.md G1.11) changes the @@ -1124,6 +1169,7 @@ int main() { if (int rc = test_precise_nclip_is_title_scoped()) return rc; if (int rc = test_saturated_nclip_keeps_architectural_result()) return rc; if (int rc = test_precision_speculative_transaction()) return rc; + if (int rc = test_pgxp_probe_does_not_count_geometry_lookup()) return rc; if (int rc = test_preserve_projection_is_shadow_only()) return rc; if (int rc = test_pgxp_culling()) return rc; /* Signed host projection spans the camera plane without changing any diff --git a/runtime/tests/test_pgxp.cpp b/runtime/tests/test_pgxp.cpp index c53081dd..57f3bcc9 100644 --- a/runtime/tests/test_pgxp.cpp +++ b/runtime/tests/test_pgxp.cpp @@ -26,9 +26,22 @@ static int g_failures = 0; static uint32_t g_fb_packed = 0; static int32_t g_fb_x16 = 0, g_fb_y16 = 0; static int g_fb_valid = 0; +static uint32_t g_fb_probe_lookups = 0; +static uint32_t g_fb_render_lookups = 0; extern "C" int gte_geometry_correction_lookup(uint32_t packed, int32_t *x16, int32_t *y16) { + ++g_fb_render_lookups; + if (!g_fb_valid || packed != g_fb_packed) return 0; + if (x16) *x16 = g_fb_x16; + if (y16) *y16 = g_fb_y16; + return 1; +} + +extern "C" int gte_geometry_correction_lookup_probe(uint32_t packed, + int32_t *x16, + int32_t *y16) { + ++g_fb_probe_lookups; if (!g_fb_valid || packed != g_fb_packed) return 0; if (x16) *x16 = g_fb_x16; if (y16) *y16 = g_fb_y16; @@ -555,6 +568,30 @@ int main(void) { pgxp_get_stats(&b); CHECK(b.rect_bypass == a.rect_bypass + 1); CHECK(b.rect_partial == a.rect_partial + 2); + + const int fallback_was_enabled = pgxp_position_fallback(); + pgxp_set_position_fallback(1); + g_fb_valid = 1; + g_fb_packed = PACKED; + g_fb_x16 = 160 << 16; + g_fb_y16 = 80 << 16; + const uint32_t render_lookups = g_fb_render_lookups; + const uint32_t probe_lookups = g_fb_probe_lookups; + pgxp_get_stats(&a); + CHECK(pgxp_probe_precise_vertex(0xFFFFFFFFu, PACKED, 160, 80) == + PGXP_SRC_FALLBACK); + pgxp_get_stats(&b); + CHECK(std::memcmp(&a, &b, sizeof a) == 0); + CHECK(g_fb_render_lookups == render_lookups); + CHECK(g_fb_probe_lookups == probe_lookups + 1u); + int32_t x16, y16; + uint16_t sz; + CHECK(pgxp_get_precise_vertex(0xFFFFFFFFu, PACKED, 160, 80, + &x16, &y16, &sz) == PGXP_SRC_FALLBACK); + CHECK(g_fb_render_lookups == render_lookups + 1u); + CHECK(g_fb_probe_lookups == probe_lookups + 1u); + g_fb_valid = 0; + pgxp_set_position_fallback(fallback_was_enabled); } /* --- preserve projection: the RTPS-side window check (G1.11) ---------- */