ec82764bef
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>
95 lines
4.7 KiB
Python
95 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""retroDE_ps2 — SPRITE census (Ch344 sprite-ingestion scoping).
|
|
|
|
Consumes gs_parse.parse_dump()'s event stream and characterises every SPRITE primitive in a .gs dump:
|
|
prim-type split, per-sprite TEX0 PSM, TME/ABE/FST, the ALPHA blend equation, TEX1 mag/min filter, and an
|
|
XYOFFSET-corrected size histogram. Output is AGGREGATE (counts/histograms) only — no copyrighted pixel
|
|
content — so the report is committable per captures/gs/.gitignore policy. This is census/scoping ONLY;
|
|
it renders nothing and asserts nothing.
|
|
|
|
Usage: gs_census_sprites.py <dump.gs[.xz|.zst]> [--report out.txt]
|
|
"""
|
|
import sys, os
|
|
from collections import Counter
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from gs_parse import parse_dump
|
|
|
|
PSM = {0x00:"PSMCT32",0x01:"PSMCT24",0x02:"PSMCT16",0x0A:"PSMCT16S",0x13:"PSMT8",0x14:"PSMT4",
|
|
0x1B:"PSMT8H",0x24:"PSMT4HL",0x2C:"PSMT4HH"}
|
|
PRIMT = {0:"POINT",1:"LINE",2:"LINE_STRIP",3:"TRI",4:"TRISTRIP",5:"TRIFAN",6:"SPRITE",7:"INVALID"}
|
|
ABCD = {0:"Cs",1:"Cd",2:"0"}; C_SEL = {0:"As",1:"Ad",2:"FIX"}
|
|
FILT = {0:"NEAREST",1:"LINEAR",2:"N_MIPN",3:"L_MIPN",4:"N_MIPL",5:"L_MIPL"}
|
|
|
|
def dec_tex0(v):
|
|
return (v>>20)&0x3F, 1<<((v>>26)&0xF), 1<<((v>>30)&0xF), v&0x3FFF, (v>>35)&3, (v>>34)&1
|
|
def dec_alpha(v):
|
|
a=v&3; b=(v>>2)&3; c=(v>>4)&3; d=(v>>6)&3; fix=(v>>32)&0xFF
|
|
return f"({ABCD[a]}-{ABCD[b]})*{C_SEL[c]}>>7+{ABCD[d]}" + (f" FIX={fix}" if c==2 else "")
|
|
|
|
def census(path):
|
|
h, events = parse_dump(path)
|
|
cur = {"type":None,"tme":0,"abe":0,"fst":0}
|
|
tex0 = {1:None, 2:None}; xyoff = {1:(0,0), 2:(0,0)}; tex1 = {1:None, 2:None}
|
|
alpha = {1:None, 2:None}; ctxt = 0
|
|
kicks = {}; xyz = []
|
|
spr = {"n":0, "psm":Counter(), "abe":Counter(), "fst":Counter(), "tme":Counter(),
|
|
"alpha":Counter(), "magfilt":Counter(), "sizes":Counter()}
|
|
for e in events:
|
|
if e.kind != "GSREG": continue
|
|
a, v = e.addr, e.value
|
|
if a == 0x00: # PRIM
|
|
cur = {"type":v&7,"tme":(v>>4)&1,"abe":(v>>6)&1,"fst":(v>>8)&1}; ctxt=(v>>9)&1; xyz=[]
|
|
elif a in (0x06,0x07): tex0[1 if a==0x06 else 2] = dec_tex0(v)
|
|
elif a in (0x14,0x15): tex1[1 if a==0x14 else 2] = (v>>5)&7 # MMAG bit5 -> mag filter (1bit here: 0/1)
|
|
elif a in (0x18,0x19): xyoff[1 if a==0x18 else 2] = (v&0xFFFF, (v>>32)&0xFFFF)
|
|
elif a in (0x42,0x43): alpha[1 if a==0x42 else 2] = v
|
|
elif a == 0x05: # XYZ2 vertex kick
|
|
t = cur["type"]
|
|
if t is None: continue
|
|
kicks[t] = kicks.get(t,0)+1
|
|
if t == 6:
|
|
xyz.append(v)
|
|
if len(xyz) == 2:
|
|
cx = 1 if ctxt==0 else 2
|
|
ox, oy = xyoff[cx]
|
|
x0=(xyz[0]&0xFFFF)-ox; y0=((xyz[0]>>16)&0xFFFF)-oy
|
|
x1=(xyz[1]&0xFFFF)-ox; y1=((xyz[1]>>16)&0xFFFF)-oy
|
|
w=abs(x1-x0)//16; ht=abs(y1-y0)//16 # 12.4 fixed -> pixels
|
|
spr["n"]+=1; spr["sizes"][(w,ht)]+=1
|
|
spr["abe"][cur["abe"]]+=1; spr["fst"][cur["fst"]]+=1; spr["tme"][cur["tme"]]+=1
|
|
tx = tex0[cx]
|
|
spr["psm"][("(untextured)" if not cur["tme"] else
|
|
"(no TEX0)" if tx is None else PSM.get(tx[0],f"0x{tx[0]:02x}"))] += 1
|
|
if cur["abe"]:
|
|
al = alpha[cx]
|
|
spr["alpha"][dec_alpha(al) if al is not None else "(no ALPHA set)"] += 1
|
|
if cur["tme"]:
|
|
f1 = tex1[cx]
|
|
spr["magfilt"][("NEAREST" if f1==0 else "LINEAR" if f1==1 else "(unset)") ] += 1
|
|
xyz=[]
|
|
return h, kicks, spr
|
|
|
|
def fmt(h, kicks, spr):
|
|
L = []
|
|
L.append(f"SPRITE CENSUS — serial={h.serial!r} crc=0x{h.crc:08x}")
|
|
L.append(f"prim-kicks by type: {dict((PRIMT[k],v) for k,v in sorted(kicks.items(),key=lambda x:-x[1]))}")
|
|
L.append(f"SPRITES: {spr['n']} rectangles")
|
|
L.append(f" TEX0 PSM : {dict(spr['psm'].most_common())}")
|
|
L.append(f" TME(textured) : {dict(spr['tme'])} FST(0=STQ,1=UV): {dict(spr['fst'])}")
|
|
L.append(f" ABE(alpha) : {dict(spr['abe'])}")
|
|
L.append(f" ALPHA eqn : {dict(spr['alpha'].most_common())}")
|
|
L.append(f" mag filter : {dict(spr['magfilt'].most_common())}")
|
|
L.append(f" top sizes WxH : {[(f'{w}x{ht}',n) for (w,ht),n in spr['sizes'].most_common(8)]}")
|
|
return "\n".join(L)
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print(__doc__); sys.exit(2)
|
|
h, kicks, spr = census(sys.argv[1])
|
|
out = fmt(h, kicks, spr)
|
|
print(out)
|
|
if "--report" in sys.argv:
|
|
p = sys.argv[sys.argv.index("--report")+1]
|
|
with open(p, "w") as f: f.write(out + "\n")
|
|
print(f"\nwrote report -> {p}")
|