ba74bbd5aa
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>
63 lines
3.1 KiB
Python
63 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""retroDE_ps2 — Ch356: composition == isolated RTL bit-for-bit check.
|
|
|
|
Renders each epoch ALONE (tb +ONLY=<k> +FBDUMP) over a precleared-black FB, then composites the N isolation dumps in
|
|
paint order (epoch 0, 1, ..., N-1; DECAL/overwrite) and asserts the result equals the joint ALL-mode render BIT-FOR-BIT.
|
|
This is Codex's Ch356 accumulation-correctness proof: the scheduler drawing the epochs together must produce exactly the
|
|
same framebuffer as compositing the individually-rendered epochs (no cache bleed, no stale pixels, correct rebind).
|
|
|
|
Composition operator (matches the RTL: precleared black bg, each epoch overwrites where it draws):
|
|
compose[px] = the LAST epoch (highest k) that DREW px (isolation dump non-zero); else black.
|
|
Note: an epoch is DECAL/opaque, so its written value replaces whatever is underneath. A genuine opaque-BLACK texel
|
|
(dump==0 where the epoch drew) is indistinguishable from unwritten — but the joint ALL render is likewise black there
|
|
(black-over-anything = black in this content), so nonzero-wins composition still matches ALL bit-for-bit. Do NOT use
|
|
reference coverage to pick the owner: the RTL edge coverage differs slightly from the float reference, and a
|
|
reference-covered-but-RTL-unwritten pixel must fall through to the epoch that ACTUALLY drew it.
|
|
|
|
Usage: compose_sched.py <fb_ALL.hex> <fb_0.hex> <fb_1.hex> ...
|
|
Each *.hex file = W*H lines of 8-hex-digit PSMCT32 words (row-major), as emitted by +FBDUMP.
|
|
"""
|
|
import sys, os
|
|
|
|
def load(fn):
|
|
out=[]
|
|
with open(fn) as f:
|
|
for ln in f:
|
|
s=ln.strip()
|
|
if not s or s.startswith("//"): continue
|
|
out.append(int(s,16)&0xFFFFFFFF)
|
|
return out
|
|
|
|
def main(argv):
|
|
a=argv[1:]
|
|
W=384
|
|
if "--width" in a: i=a.index("--width"); W=int(a[i+1]); del a[i:i+2]
|
|
if len(a)<3: sys.exit("usage: compose_sched.py <fb_ALL.hex> <fb_0.hex> <fb_1.hex> ...")
|
|
all_fb=load(a[0]); eps=[load(x) for x in a[1:]]; N=len(eps)
|
|
npx=len(all_fb)
|
|
for k,e in enumerate(eps):
|
|
if len(e)!=npx: sys.exit(f"[compose] epoch {k} dump {len(e)} px != ALL {npx}")
|
|
|
|
comp=[0]*npx
|
|
for px in range(npx):
|
|
for k in range(N-1,-1,-1):
|
|
if eps[k][px]!=0: comp[px]=eps[k][px]; break # last epoch that actually DREW (DECAL nonzero-wins)
|
|
|
|
mism=[px for px in range(npx) if comp[px]!=all_fb[px]]
|
|
black_all=sum(1 for px in mism if all_fb[px]==0)
|
|
black_cmp=sum(1 for px in mism if comp[px]==0)
|
|
print(f"[compose] {N} epochs, {npx} px. composition vs ALL: {npx-len(mism)} exact, {len(mism)} mismatch "
|
|
f"({100.0*(npx-len(mism))/npx:.4f}% exact)")
|
|
if mism:
|
|
print(f"[compose] of the {len(mism)} mismatches: ALL==black:{black_all} compose==black:{black_cmp}")
|
|
for px in mism[:12]:
|
|
print(f"[compose] px {px} (x={px%W},y={px//W}): compose={comp[px]:08x} ALL={all_fb[px]:08x}")
|
|
if not mism:
|
|
print("[compose] PASS: joint ALL render == composited isolation dumps BIT-FOR-BIT (accumulation exact)")
|
|
return 0
|
|
print("[compose] FAIL: composition differs from ALL")
|
|
return 1
|
|
|
|
if __name__=="__main__":
|
|
raise SystemExit(main(sys.argv))
|