Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)

Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder
XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog
TBs, fidelity attribution tooling. Functional baseline before removing the dead
bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 19:56:46 -04:00
parent ec82764bef
commit ba74bbd5aa
476 changed files with 696247 additions and 130119 deletions
+48
View File
@@ -13,6 +13,9 @@ Address convention matches the codebase: VRAM byte base = PTR*256 (TBP0/DBP/CBP
and PTRs are page-aligned (multiple of 32) so page_index*8192 composes correctly off that base.
"""
import os
import re
# block grid is shared by PSMCT32 and PSMT8 (4 rows x 8 cols), value = block index within page
BLOCK = [
[ 0, 1, 4, 5,16,17,20,21],
@@ -52,6 +55,40 @@ def psmt8_addr(tbp, fbw, x, y):
block_idx = BLOCK[(y >> 4) & 3][(x >> 4) & 7]
return tbp*256 + page_index*8192 + block_idx*256 + COL8[y & 15][x & 15]
_COL4 = None
_BLOCK4 = (
(0,2,8,10), (1,3,9,11), (4,6,12,14), (5,7,13,15),
(16,18,24,26), (17,19,25,27), (20,22,28,30), (21,23,29,31),
)
def _col4_table():
"""Load the canonical PSMT4 permutation from the RTL contract."""
global _COL4
if _COL4 is not None:
return _COL4
rtl = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"rtl", "gif_gs", "gs_swizzle_psmt4_stub.sv")
text = open(rtl, encoding="utf-8").read()
lo = text.index("function automatic logic [8:0] col_idx_psmt4")
hi = text.index("endfunction", lo)
tab = [None] * 512
for key, value in re.findall(r"9'd(\d+)\s*:\s*return\s+9'd(\d+)", text[lo:hi]):
tab[int(key)] = int(value)
tab[511] = 511 # explicit default arm in the RTL function
if any(v is None for v in tab) or sorted(tab) != list(range(512)):
raise RuntimeError("PSMT4 RTL column table is incomplete or non-bijective")
_COL4 = tuple(tab)
return _COL4
def psmt4_addr(tbp, fbw, x, y):
"""Return (byte address, high-nibble select) for a PSMT4 texel."""
if fbw & 1:
raise ValueError(f"PSMT4 TBW must be even, got {fbw}")
page_index = (y >> 7) * (fbw >> 1) + (x >> 7)
block_idx = _BLOCK4[(y >> 4) & 7][(x >> 5) & 3]
nib = _col4_table()[((y & 15) << 5) | (x & 31)]
return tbp*256 + page_index*8192 + block_idx*256 + (nib >> 1), bool(nib & 1)
class LocalMem:
"""4 MiB GS VRAM. Seed from the dump's initial snapshot, then replay host->local uploads in order."""
SIZE = 0x400000
@@ -82,6 +119,17 @@ class LocalMem:
out[r+x] = self.m[a] if 0 <= a < self.SIZE else 0
return out
def read_psmt4(self, tbp, fbw, tw, th):
"""Return one unpacked 0..15 index byte per raster-order texel."""
out = bytearray(tw*th)
for y in range(th):
r = y*tw
for x in range(tw):
a, hi = psmt4_addr(tbp, fbw, x, y)
b = self.m[a] if 0 <= a < self.SIZE else 0
out[r+x] = ((b >> 4) & 0xF) if hi else (b & 0xF)
return out
def read_ct32_word(self, dbp, dbw, x, y):
a = ct32_addr(dbp, dbw, x, y)
return int.from_bytes(self.m[a:a+4], "little") if a+4 <= self.SIZE else 0