Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch349 step 4: software reference image for an ACTUAL SH3 draw.
|
||||
|
||||
Rasterizes the chosen draw's REAL geometry (its TRI_STRIP, real per-vertex screen XY + perspective S/T/Q +
|
||||
vertex RGBA) sampling the texture RECONSTRUCTED from GS local memory (gs_sh3_recon / gs_localmem). Perspective-
|
||||
correct: S,T,Q (= s/w, t/w, 1/w) interpolate linearly in screen space, then u=(S/Q)*TW, v=(T/Q)*TH; texel ->
|
||||
CLUT -> ABGR; TFX=MODULATE applies the vertex color (×2/255-ish, GS 128=1.0). This is the host-first reference
|
||||
that must pixel-check against the real PCSX2 frame BEFORE anything goes to feeder/board.
|
||||
|
||||
Usage: gs_sh3_draw_ref.py <dump.gs.zst> [--draw-idx N] [--tbp T --cbp C --tbw W --tw 512 --th 512]
|
||||
[--clut-order grid|linear] [--modulate 0|1] [--out DIR]
|
||||
"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import gs_sh3_draw_census as C
|
||||
import gs_sh3_recon as RC
|
||||
|
||||
SW, SH = 640, 480
|
||||
|
||||
def edge(ax, ay, bx, by, px, py):
|
||||
return (px-ax)*(by-ay) - (py-ay)*(bx-ax)
|
||||
|
||||
def raster_strip(verts, prim_type, tex_idx, pal, tw, th, modulate):
|
||||
"""Rasterize a TRI_STRIP/TRI_FAN/TRIANGLE list of verts into an SW*SH RGBA buffer with a z-buffer."""
|
||||
fb = [(0,0,0,0)]*(SW*SH)
|
||||
zb = [-1]*(SW*SH)
|
||||
def tri(i0, i1, i2):
|
||||
v0, v1, v2 = verts[i0], verts[i1], verts[i2]
|
||||
x0,y0 = v0["x"],v0["y"]; x1,y1 = v1["x"],v1["y"]; x2,y2 = v2["x"],v2["y"]
|
||||
minx = max(0, int(min(x0,x1,x2))); maxx = min(SW-1, int(max(x0,x1,x2))+1)
|
||||
miny = max(0, int(min(y0,y1,y2))); maxy = min(SH-1, int(max(y0,y1,y2))+1)
|
||||
area = edge(x0,y0, x1,y1, x2,y2)
|
||||
if abs(area) < 1e-6: return
|
||||
inv = 1.0/area
|
||||
for py in range(miny, maxy+1):
|
||||
for px in range(minx, maxx+1):
|
||||
cx, cy = px+0.5, py+0.5
|
||||
w0 = edge(x1,y1, x2,y2, cx,cy)
|
||||
w1 = edge(x2,y2, x0,y0, cx,cy)
|
||||
w2 = edge(x0,y0, x1,y1, cx,cy)
|
||||
# inside test (either winding)
|
||||
if not ((w0>=0 and w1>=0 and w2>=0) or (w0<=0 and w1<=0 and w2<=0)):
|
||||
continue
|
||||
b0, b1, b2 = w0*inv, w1*inv, w2*inv
|
||||
S = b0*v0["s"] + b1*v1["s"] + b2*v2["s"]
|
||||
T = b0*v0["t"] + b1*v1["t"] + b2*v2["t"]
|
||||
Q = b0*v0["q"] + b1*v1["q"] + b2*v2["q"]
|
||||
if abs(Q) < 1e-12: continue
|
||||
u = (S/Q)*tw; v = (T/Q)*th
|
||||
tx = int(u) % tw; ty = int(v) % th # REPEAT wrap (SH3 CLAMP default)
|
||||
if tx < 0: tx += tw
|
||||
if ty < 0: ty += th
|
||||
pidx = tex_idx[ty*tw + tx]
|
||||
p = pal[pidx & 0xFF]
|
||||
r,g,b = p&0xFF, (p>>8)&0xFF, (p>>16)&0xFF
|
||||
if modulate:
|
||||
vr = b0*((v0["rgba"])&0xFF)+b1*((v1["rgba"])&0xFF)+b2*((v2["rgba"])&0xFF)
|
||||
vg = b0*((v0["rgba"]>>8)&0xFF)+b1*((v1["rgba"]>>8)&0xFF)+b2*((v2["rgba"]>>8)&0xFF)
|
||||
vb = b0*((v0["rgba"]>>16)&0xFF)+b1*((v1["rgba"]>>16)&0xFF)+b2*((v2["rgba"]>>16)&0xFF)
|
||||
r = min(255, int(r*vr/128.0)); g = min(255, int(g*vg/128.0)); b = min(255, int(b*vb/128.0))
|
||||
z = int(b0*v0["z"] + b1*v1["z"] + b2*v2["z"])
|
||||
o = py*SW + px
|
||||
if z >= zb[o]:
|
||||
zb[o] = z; fb[o] = (r,g,b,255)
|
||||
n = len(verts)
|
||||
if prim_type == 3: # TRIANGLE list
|
||||
for i in range(0, n-2, 3): tri(i,i+1,i+2)
|
||||
elif prim_type == 4: # TRI_STRIP
|
||||
for i in range(2, n): tri(i-2, i-1, i)
|
||||
elif prim_type == 5: # TRI_FAN
|
||||
for i in range(2, n): tri(0, i-1, i)
|
||||
return fb
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 2:
|
||||
print(__doc__); return 2
|
||||
dump = argv[1]
|
||||
def opt(n, dv=None): return argv[argv.index(n)+1] if n in argv else dv
|
||||
draw_idx = int(opt("--draw-idx","89761"))
|
||||
tbp = int(opt("--tbp","9216")); cbp = int(opt("--cbp","13952"))
|
||||
fbw = int(opt("--tbw","8")); tw = int(opt("--tw","512")); th = int(opt("--th","512"))
|
||||
order = opt("--clut-order","grid"); modulate = int(opt("--modulate","1"))
|
||||
outdir = opt("--out", os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"captures","gs","silenthill3","extracted","recon"))
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
|
||||
dr = C.get_draw(dump, draw_idx)
|
||||
if dr is None:
|
||||
print(f"draw first_idx={draw_idx} not found"); return 1
|
||||
t0 = dr["tex0"]
|
||||
print(f"[step4] draw f{dr['frame']} idx{draw_idx} {C.PRIMT[dr['prim']['type']]} nprim={dr['nprim']} "
|
||||
f"nvert={dr['nvert']} TEX0 tbp={t0['tbp']} psm=0x{t0['psm']:02x} {t0['tw']}x{t0['th']}")
|
||||
|
||||
mem, replayed, uploads, events, vram = RC.build_localmem_to(dump, draw_idx)
|
||||
if mem is None: print("VRAM absent"); return 1
|
||||
tex_idx = mem.read_psmt8(tbp, fbw, tw, th)
|
||||
pal = RC.read_clut32(mem, cbp, order=order)
|
||||
print(f"[step4] reconstructed tex {tw}x{th} ({len(set(tex_idx))} idx), CLUT {order} ({len(set(pal))} colors)")
|
||||
|
||||
fb = raster_strip(dr["verts"], dr["prim"]["type"], tex_idx, pal, tw, th, modulate)
|
||||
painted = sum(1 for p in fb if p[3])
|
||||
RC.save_png(os.path.join(outdir, f"draw_ref_{draw_idx}_{order}.png"), SW, SH, fb)
|
||||
print(f"[step4] painted {painted} px -> draw_ref_{draw_idx}_{order}.png ({outdir})")
|
||||
# crop to the draw's on-screen bbox for easier visual compare with the PCSX2 frame
|
||||
x0=max(0,int(dr['xmin'])); x1=min(SW,int(dr['xmax'])+1); y0=max(0,int(dr['ymin'])); y1=min(SH,int(dr['ymax'])+1)
|
||||
if x1>x0 and y1>y0:
|
||||
crop = [fb[y*SW+x] for y in range(y0,y1) for x in range(x0,x1)]
|
||||
RC.save_png(os.path.join(outdir, f"draw_ref_{draw_idx}_{order}_crop.png"), x1-x0, y1-y0, crop)
|
||||
print(f"[step4] bbox crop x[{x0}..{x1}] y[{y0}..{y1}] -> draw_ref_{draw_idx}_{order}_crop.png")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Reference in New Issue
Block a user