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>
206 lines
7.7 KiB
Python
206 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare captured RTL Z fragments against the fixture's software Z planes.
|
|
|
|
This is intentionally per-fragment, not per-final-frame. It reconstructs the
|
|
same clipped triangles used by the ZSCHED feeder fixture, then asks whether each
|
|
captured RTL fragment's Z is close to the expected screen-linear interpolated Z
|
|
at that pixel.
|
|
|
|
Usage:
|
|
tools/diagnose_zsched_frag_z.py [sim/traces/rtl/zsched_frags.txt]
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
ROOT = os.path.normpath(os.path.join(HERE, ".."))
|
|
DATA = os.path.join(ROOT, "sim", "data", "top_psmct32_raster_demo")
|
|
sys.path.insert(0, HERE)
|
|
|
|
import gs_make_sh3_multidraw_fixture as MD
|
|
|
|
|
|
def load_epochs(tag="zsched"):
|
|
path = os.path.join(DATA, f"sh3_{tag}_epochs.txt")
|
|
out = []
|
|
with open(path) as f:
|
|
for ln in f:
|
|
s = ln.strip()
|
|
if not s or s.startswith("#") or s.startswith("META"):
|
|
continue
|
|
p = s.split()
|
|
out.append(int(p[1]))
|
|
return out
|
|
|
|
|
|
def find_dump():
|
|
import glob
|
|
c = glob.glob(os.path.join(ROOT, "captures", "gs", "silenthill3", "*224139*.gs.zst"))
|
|
if not c:
|
|
raise SystemExit("[fragz] no SH3 224139 dump found")
|
|
return c[0]
|
|
|
|
|
|
def edge(ax, ay, bx, by, px, py):
|
|
return (px - ax) * (by - ay) - (py - ay) * (bx - ax)
|
|
|
|
|
|
def clip_rect_z(tri, w, h):
|
|
def lerp(p1, p2, al):
|
|
return {k: (p1[k] + al * (p2[k] - p1[k])) for k in ("x", "y", "z", "s", "t", "q")}
|
|
|
|
def clip_edge(poly, inside, isect):
|
|
out = []
|
|
n = len(poly)
|
|
for i in range(n):
|
|
a = poly[i]
|
|
b = poly[(i + 1) % n]
|
|
ina = inside(a)
|
|
inb = inside(b)
|
|
if ina:
|
|
out.append(a)
|
|
if ina != inb:
|
|
out.append(isect(a, b))
|
|
return out
|
|
|
|
poly = [dict(x=v["x"], y=v["y"], z=v.get("z", 0.0), s=v["s"], t=v["t"], q=v["q"]) for v in tri]
|
|
poly = clip_edge(poly, lambda p: p["x"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["x"]) / (b["x"] - a["x"])))
|
|
if not poly:
|
|
return []
|
|
poly = clip_edge(poly, lambda p: p["x"] <= w, lambda a, b: lerp(a, b, (w - a["x"]) / (b["x"] - a["x"])))
|
|
if not poly:
|
|
return []
|
|
poly = clip_edge(poly, lambda p: p["y"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["y"]) / (b["y"] - a["y"])))
|
|
if not poly:
|
|
return []
|
|
poly = clip_edge(poly, lambda p: p["y"] <= h, lambda a, b: lerp(a, b, (h - a["y"]) / (b["y"] - a["y"])))
|
|
if len(poly) < 3:
|
|
return []
|
|
return [(poly[0], poly[k], poly[k + 1]) for k in range(1, len(poly) - 1)]
|
|
|
|
|
|
def tri_z_at(tri, x, y):
|
|
v0, v1, v2 = tri
|
|
ar = edge(v0["x"], v0["y"], v1["x"], v1["y"], v2["x"], v2["y"])
|
|
if abs(ar) < 1e-9:
|
|
return None
|
|
inv = 1.0 / ar
|
|
w0 = edge(v1["x"], v1["y"], v2["x"], v2["y"], x, y) * inv
|
|
w1 = edge(v2["x"], v2["y"], v0["x"], v0["y"], x, y) * inv
|
|
w2 = 1.0 - w0 - w1
|
|
if w0 < -0.001 or w1 < -0.001 or w2 < -0.001:
|
|
return None
|
|
return w0 * v0["z"] + w1 * v1["z"] + w2 * v2["z"]
|
|
|
|
|
|
def build_epoch_tris(draw_idxs, fbw, fbh):
|
|
got, _ = MD.load_draws(find_dump(), draw_idxs)
|
|
eps = [got[i] for i in draw_idxs]
|
|
ox = int(min(min(v["x"] for v in e["verts"]) for e in eps))
|
|
oy = int(min(min(v["y"] for v in e["verts"]) for e in eps))
|
|
out = []
|
|
for e in eps:
|
|
fv = [dict(x=v["x"] - ox, y=v["y"] - oy, z=v["z"], s=v["s"], t=v["t"], q=v["q"]) for v in e["verts"]]
|
|
raw = [(fv[i - 2], fv[i - 1], fv[i]) for i in range(2, len(fv))]
|
|
tris = []
|
|
for tri in raw:
|
|
tris.extend(clip_rect_z(tri, fbw, fbh))
|
|
out.append(tris)
|
|
return out
|
|
|
|
|
|
def best_expected(tris, x, y, sample_center):
|
|
sx = x + 0.5 if sample_center else float(x)
|
|
sy = y + 0.5 if sample_center else float(y)
|
|
vals = []
|
|
for tri in tris:
|
|
z = tri_z_at(tri, sx, sy)
|
|
if z is not None:
|
|
vals.append(z)
|
|
return vals
|
|
|
|
|
|
def main(argv):
|
|
frags = argv[1] if len(argv) > 1 else os.path.join(ROOT, "sim", "traces", "rtl", "zsched_frags.txt")
|
|
draw_idxs = load_epochs("zsched")
|
|
fbw, fbh = 256, 210
|
|
tris_by_ep = build_epoch_tris(draw_idxs, fbw, fbh)
|
|
|
|
stats = {
|
|
"n": 0, "nocov_center": 0, "nocov_corner": 0,
|
|
"center_le0": 0, "center_le1": 0, "center_le16": 0, "center_le256": 0,
|
|
"corner_le0": 0, "corner_le1": 0, "corner_le16": 0, "corner_le256": 0,
|
|
}
|
|
abs_sum_center = 0.0
|
|
abs_sum_corner = 0.0
|
|
ratios = []
|
|
examples = []
|
|
|
|
with open(frags) as f:
|
|
for ln in f:
|
|
p = ln.split()
|
|
if len(p) < 5:
|
|
continue
|
|
ep, x, y, rz = int(p[0]), int(p[1]), int(p[2]), int(p[3])
|
|
stats["n"] += 1
|
|
cvals = best_expected(tris_by_ep[ep], x, y, True)
|
|
kvals = best_expected(tris_by_ep[ep], x, y, False)
|
|
if not cvals:
|
|
stats["nocov_center"] += 1
|
|
cdiff = None
|
|
else:
|
|
cz = min(cvals, key=lambda z: abs(rz - z))
|
|
cdiff = abs(rz - cz)
|
|
abs_sum_center += cdiff
|
|
if cz:
|
|
ratios.append(rz / cz)
|
|
if cdiff <= 0.5:
|
|
stats["center_le0"] += 1
|
|
if cdiff <= 1:
|
|
stats["center_le1"] += 1
|
|
if cdiff <= 16:
|
|
stats["center_le16"] += 1
|
|
if cdiff <= 256:
|
|
stats["center_le256"] += 1
|
|
if not kvals:
|
|
stats["nocov_corner"] += 1
|
|
kdiff = None
|
|
else:
|
|
kz = min(kvals, key=lambda z: abs(rz - z))
|
|
kdiff = abs(rz - kz)
|
|
abs_sum_corner += kdiff
|
|
if kdiff <= 0.5:
|
|
stats["corner_le0"] += 1
|
|
if kdiff <= 1:
|
|
stats["corner_le1"] += 1
|
|
if kdiff <= 16:
|
|
stats["corner_le16"] += 1
|
|
if kdiff <= 256:
|
|
stats["corner_le256"] += 1
|
|
if len(examples) < 16 and ((cdiff is None or cdiff > 256) and (kdiff is None or kdiff > 256)):
|
|
examples.append((ep, x, y, rz, cdiff, kdiff, cvals[:3], kvals[:3]))
|
|
|
|
n = stats["n"]
|
|
def pct(k):
|
|
return 100.0 * stats[k] / n if n else 0.0
|
|
|
|
print(f"[fragz] fragments={n} draw_idxs={draw_idxs}")
|
|
print(f"[fragz] center coverage misses={stats['nocov_center']} corner coverage misses={stats['nocov_corner']}")
|
|
print(f"[fragz] center absdiff: <=0.5 {stats['center_le0']} ({pct('center_le0'):.2f}%) <=1 {stats['center_le1']} ({pct('center_le1'):.2f}%) <=16 {stats['center_le16']} ({pct('center_le16'):.2f}%) <=256 {stats['center_le256']} ({pct('center_le256'):.2f}%)")
|
|
print(f"[fragz] corner absdiff: <=0.5 {stats['corner_le0']} ({pct('corner_le0'):.2f}%) <=1 {stats['corner_le1']} ({pct('corner_le1'):.2f}%) <=16 {stats['corner_le16']} ({pct('corner_le16'):.2f}%) <=256 {stats['corner_le256']} ({pct('corner_le256'):.2f}%)")
|
|
if n - stats["nocov_center"] > 0:
|
|
print(f"[fragz] mean center absdiff={abs_sum_center / (n - stats['nocov_center']):.2f}")
|
|
if n - stats["nocov_corner"] > 0:
|
|
print(f"[fragz] mean corner absdiff={abs_sum_corner / (n - stats['nocov_corner']):.2f}")
|
|
if ratios:
|
|
ratios.sort()
|
|
mid = ratios[len(ratios) // 2]
|
|
print(f"[fragz] raw/expected center ratio: p10={ratios[len(ratios)//10]:.4f} median={mid:.4f} p90={ratios[(len(ratios)*9)//10]:.4f}")
|
|
for ep, x, y, rz, cdiff, kdiff, cvals, kvals in examples:
|
|
print(f"[fragz] bad ep{ep} ({x},{y}) rtl_z={rz} center_diff={cdiff} corner_diff={kdiff} center_z={[round(v,1) for v in cvals]} corner_z={[round(v,1) for v in kvals]}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv))
|