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>
260 lines
18 KiB
Python
260 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""retroDE_ps2 — Ch355 Brick 1: MULTI-TEXTURE composition.
|
|
|
|
Two authentic SH3 draw groups with DIFFERENT TEX0/CLUT accumulate into ONE LPDDR framebuffer via SCENE-LEVEL texture
|
|
rebind + staged-list retriggering (Codex). Dump order (authentic): A={19562} tbp=11264/cbp=14080 THEN B={89761}
|
|
tbp=9216/cbp=13952. NOT in Brick 1: per-primitive TEX0, multi-resident cache, 640x480, cross-draw Z.
|
|
|
|
Preflight fail-CLOSED gates (this file, run first):
|
|
- both draws same frame; overlap on screen; DIFFERENT texture keys.
|
|
- each group's texture + CLUT independently RESIDENT at ITS draw-time epoch (per-draw local-memory reconstruction).
|
|
- the two textures are DIFFERENT content, and the two CLUTs are DIFFERENT content (real multi-texture, not aliasing).
|
|
- CLUTs RELOCATED to distinct, non-overlapping BRAM CBPs (CBP_A/CBP_B); reported for the TEX0-selects-CBP check.
|
|
|
|
Usage: gs_make_sh3_multitex_fixture.py [dump.gs.zst] [--emit] (draws A/B fixed to the authentic pair)
|
|
LOCAL/gitignored. Reuses gs_make_sh3_multidraw_fixture.load_draws (same event-walk + full-state capture).
|
|
"""
|
|
import sys, os
|
|
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")
|
|
import gs_sh3_recon as RC
|
|
import gs_make_sh3_multidraw_fixture as MD # reuse load_draws + edge + clip_rect
|
|
sys.path.insert(0, DATA); import bake
|
|
|
|
DRAW_A = 19562 # dump order: A first
|
|
DRAW_B = 89761
|
|
TEX_BYTES = 512*512
|
|
# relocated CLUT bases (distinct, non-overlapping). CLUT = 256 words = 1 KiB = 4 VRAM blocks (256 B each).
|
|
CBP_A = 0x1E000//256 # 480 (bytes 0x1E000..0x1E3FF)
|
|
CBP_B = 0x1E400//256 # 484 (bytes 0x1E400..0x1E7FF) — +1 KiB, no overlap with CBP_A
|
|
|
|
def bbox(dr):
|
|
xs=[v["x"] for v in dr["verts"]]; ys=[v["y"] for v in dr["verts"]]
|
|
return (min(xs),min(ys),max(xs),max(ys))
|
|
|
|
def main(argv):
|
|
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
|
if dump is None:
|
|
import glob; c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
|
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
|
dump=c[0]
|
|
print(f"[Ch355] dump={os.path.basename(dump)} A=idx{DRAW_A} (first) -> B=idx{DRAW_B}")
|
|
|
|
got, vram = MD.load_draws(dump, [DRAW_A, DRAW_B])
|
|
for i in (DRAW_A, DRAW_B):
|
|
if i not in got: sys.exit(f"[Ch355] FAIL: draw idx{i} not found as a textured draw")
|
|
drA, drB = got[DRAW_A], got[DRAW_B]
|
|
tA, tB = drA["state"]["tex0"], drB["state"]["tex0"]
|
|
|
|
# ---- gate: authentic dump order (A before B) ----
|
|
if not (DRAW_A < DRAW_B): sys.exit(f"[Ch355] FAIL: A idx{DRAW_A} not before B idx{DRAW_B} (dump order)")
|
|
# ---- gate: same frame ----
|
|
if drA["frame"]!=drB["frame"]: sys.exit(f"[Ch355] FAIL: A frame {drA['frame']} != B frame {drB['frame']}")
|
|
# ---- gate: DIFFERENT texture keys ----
|
|
if drA["key"]==drB["key"]: sys.exit(f"[Ch355] FAIL: A and B share the same texture key {drA['key']} — not multi-texture")
|
|
print(f"[Ch355] A: TEX0 tbp={tA['tbp']} cbp={tA['cbp']} psm=0x{tA['psm']:02x} {tA['tw']}x{tA['th']} key={drA['key']}")
|
|
print(f"[Ch355] B: TEX0 tbp={tB['tbp']} cbp={tB['cbp']} psm=0x{tB['psm']:02x} {tB['tw']}x{tB['th']} key={drB['key']}")
|
|
# ---- gate: overlap on screen ----
|
|
ax0,ay0,ax1,ay1=bbox(drA); bx0,by0,bx1,by1=bbox(drB)
|
|
ox0=max(ax0,bx0); oy0=max(ay0,by0); ox1=min(ax1,bx1); oy1=min(ay1,by1)
|
|
if not (ox1>ox0 and oy1>oy0): sys.exit(f"[Ch355] FAIL: A bbox {bbox(drA)} and B bbox {bbox(drB)} do not overlap on screen")
|
|
print(f"[Ch355] frame f{drA['frame']}; screen A[{ax0:.0f}..{ax1:.0f}]x[{ay0:.0f}..{ay1:.0f}] B[{bx0:.0f}..{bx1:.0f}]x[{by0:.0f}..{by1:.0f}] -> overlap [{ox0:.0f}..{ox1:.0f}]x[{oy0:.0f}..{oy1:.0f}]")
|
|
|
|
# ---- gate: each group's texture + CLUT INDEPENDENTLY RESIDENT at its draw-time epoch; the two are DIFFERENT ----
|
|
memA,*_ = RC.build_localmem_to(dump, DRAW_A)
|
|
memB,*_ = RC.build_localmem_to(dump, DRAW_B)
|
|
if memA is None or memB is None: sys.exit("[Ch355] FAIL: VRAM snapshot absent for a draw")
|
|
texA=bytes(memA.m[tA['tbp']*256:tA['tbp']*256+TEX_BYTES]); clutA=bytes(memA.m[tA['cbp']*256:tA['cbp']*256+1024])
|
|
texB=bytes(memB.m[tB['tbp']*256:tB['tbp']*256+TEX_BYTES]); clutB=bytes(memB.m[tB['cbp']*256:tB['cbp']*256+1024])
|
|
def nz(b): return any(x for x in b)
|
|
if not nz(texA): sys.exit(f"[Ch355] FAIL: texture A @tbp={tA['tbp']} not resident at idx{DRAW_A}")
|
|
if not nz(clutA): sys.exit(f"[Ch355] FAIL: CLUT A @cbp={tA['cbp']} not resident at idx{DRAW_A}")
|
|
if not nz(texB): sys.exit(f"[Ch355] FAIL: texture B @tbp={tB['tbp']} not resident at idx{DRAW_B}")
|
|
if not nz(clutB): sys.exit(f"[Ch355] FAIL: CLUT B @cbp={tB['cbp']} not resident at idx{DRAW_B}")
|
|
if texA==texB: sys.exit("[Ch355] FAIL: texture A == texture B (aliasing, not multi-texture)")
|
|
if clutA==clutB: sys.exit("[Ch355] FAIL: CLUT A == CLUT B (aliasing)")
|
|
crcA=sum(int.from_bytes(texA[i*4:i*4+4],'little') for i in range(TEX_BYTES//4))&0xFFFFFFFF
|
|
crcB=sum(int.from_bytes(texB[i*4:i*4+4],'little') for i in range(TEX_BYTES//4))&0xFFFFFFFF
|
|
print(f"[Ch355] texture A resident (sum32=0x{crcA:08x}) + CLUT A resident; texture B resident (sum32=0x{crcB:08x}) + CLUT B resident; A!=B (real multi-texture)")
|
|
|
|
# ---- gate: CLUT relocation to distinct, non-overlapping BRAM CBPs ----
|
|
if abs(CBP_A-CBP_B)<4: sys.exit(f"[Ch355] FAIL: relocated CBP_A={CBP_A} CBP_B={CBP_B} overlap (<4 blocks = 1 KiB)")
|
|
print(f"[Ch355] CLUT relocation: A -> CBP={CBP_A} (0x{CBP_A*256:x}), B -> CBP={CBP_B} (0x{CBP_B*256:x}); distinct, non-overlapping (each 1 KiB)")
|
|
|
|
# ---- gate: relocated CLUT ranges within BRAM (VRAM 128 KiB = block 0..511; each CLUT = 4 blocks) ----
|
|
VRAM_BLOCKS = (128*1024)//256
|
|
for nm,cb in (("A",CBP_A),("B",CBP_B)):
|
|
if cb+4 > VRAM_BLOCKS: sys.exit(f"[Ch355] FAIL: relocated CLUT {nm} @CBP={cb} (+4 blocks) exceeds VRAM {VRAM_BLOCKS} blocks")
|
|
print(f"[Ch355] both relocated CLUTs within BRAM (VRAM {VRAM_BLOCKS} blocks; A@{CBP_A}+4, B@{CBP_B}+4)")
|
|
|
|
print("[Ch355] PREFLIGHT PASS: authentic order, same frame, on-screen overlap, two independently-resident "
|
|
"textures+CLUTs (different content), relocatable to distinct in-BRAM CBPs.")
|
|
if "--emit" not in a:
|
|
print("[Ch355] (preflight only; --emit stage pending)"); return 0
|
|
|
|
# ================= --emit: two feeder lists + two textures/CLUTs + independent A->B reference =================
|
|
NEW_TBP=0x40000//256; TEX_VRAM_BASE=NEW_TBP*256; LPDDR_TEX_BASE=0x00200000; VRAM_BYTES=0x20000
|
|
PERSP_FRAC=bake.PERSP_FRAC; PSCALE=4096; TW=512; TH=512; TW_LOG=TH_LOG=9; TBW_TEX=8; S24_MAX=(1<<23)-1
|
|
edge=MD.edge; clip_rect=MD.clip_rect
|
|
def tex0_word(cbp):
|
|
v=bake.tex0_pack(NEW_TBP,TBW_TEX,psm=0x13,tw=TW_LOG,th=TH_LOG,tfx=1)
|
|
v|=(cbp&0x3FFF)<<37; v|=(0&0xF)<<51; v|=(0&0x1)<<55; v|=(0&0x1F)<<56; v|=(1&0x7)<<61
|
|
return v
|
|
# union bbox over BOTH groups (Codex: ~288x381 -> FBW=5/320x381)
|
|
allx=[v["x"] for d in (drA,drB) for v in d["verts"]]; ally=[v["y"] for d in (drA,drB) for v in d["verts"]]
|
|
OX=int(min(allx)); OY=int(min(ally)); W=int(max(allx))-OX+1; H=int(max(ally))-OY+1
|
|
FBW=(W+63)//64; FBPXW=FBW*64; STRIDE=FBPXW*4
|
|
print(f"[Ch355] UNION origin=({OX},{OY}) content={W}x{H} -> stored FB {FBPXW}x{H} (FBW={FBW}) stride={STRIDE}B "
|
|
f"size={FBPXW*H*4} (0x{FBPXW*H*4:x}) scanout={STRIDE//32*H} beats/frame; HDMI shows all {FBPXW} cols "
|
|
f"({FBPXW-W} right cols precleared-black beyond the {W}px content)")
|
|
|
|
def vert_words(v):
|
|
s_fp=round(v["s"]*TW*(1<<PERSP_FRAC)*PSCALE); t_fp=round(v["t"]*TH*(1<<PERSP_FRAC)*PSCALE); q_fp=round(v["q"]*(1<<PERSP_FRAC)*PSCALE)
|
|
if abs(s_fp)>S24_MAX or abs(t_fp)>S24_MAX: sys.exit(f"[Ch355] ST overflow {s_fp},{t_fp}")
|
|
if abs(q_fp)>0x7FFFFFFF: sys.exit(f"[Ch355] Q overflow {q_fp}")
|
|
sx=max(0,min(FBPXW-1,int(round(v["x"])))); sy=max(0,min(H-1,int(round(v["y"]))))
|
|
return [bake.rgbaq_with_q(0,0,0,q_fp&0xFFFFFFFF), bake.st_data(s_fp&0xFFFFFF,t_fp&0xFFFFFF), bake.xyz2_dataz(sx,sy,0x0000_5000)]
|
|
def group_tris(dr):
|
|
fv=[dict(x=v["x"]-OX,y=v["y"]-OY,s=v["s"],t=v["t"],q=v["q"]) for v in dr["verts"]]
|
|
raw=[(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))]
|
|
out=[]
|
|
for tri in raw: out += clip_rect(tri, FBPXW, H)
|
|
return out
|
|
# Ch355 (Codex): the RTL perspective reciprocal is UNSIGNED — negative Q is unsupported. Canonicalize each triangle
|
|
# to positive Q BEFORE packing: all-Q-positive unchanged; all-Q-negative -> negate every vertex's S,T,Q (EXACT:
|
|
# (-s)/(-q)=s/q and the negation of a linear attribute interpolates identically); mixed-sign/zero Q -> FAIL CLOSED.
|
|
def canon_tri(v0,v1,v2):
|
|
qs=[v0["q"],v1["q"],v2["q"]]
|
|
if all(q>0 for q in qs): return (v0,v1,v2)
|
|
if all(q<0 for q in qs):
|
|
neg=lambda v: dict(x=v["x"],y=v["y"],s=-v["s"],t=-v["t"],q=-v["q"])
|
|
return (neg(v0),neg(v1),neg(v2))
|
|
sys.exit(f"[Ch355] FAIL-CLOSED: mixed-sign or zero Q in a triangle (qs={qs}) — signed-Q not supported (later platform extension)")
|
|
def feeder_list(dr, cbp):
|
|
tris=group_tris(dr); stg=[len(tris)|(1<<32), bake.frame_1_psmct32(FBW), bake.alpha_pack(0,1,0,1), 0,
|
|
bake.zbuf1_pack(0,zmsk=1), tex0_word(cbp), 3|(1<<4)]
|
|
for tri in tris:
|
|
cv0,cv1,cv2=canon_tri(*tri)
|
|
for v in (cv0,cv1,cv2): stg += vert_words(v)
|
|
return stg, len(tris)
|
|
# SYNTHETIC GATE (Codex): a positive-Q triangle and its ALL-NEGATED twin must canonicalize to BIT-IDENTICAL staging.
|
|
def _pack(tri):
|
|
c=canon_tri(*tri); out=[]
|
|
for v in c: out+=vert_words(v)
|
|
return out
|
|
_tp=[dict(x=10,y=20,s=0.00030,t=0.00040,q=0.0020), dict(x=30,y=25,s=0.00050,t=0.00010,q=0.0030), dict(x=15,y=40,s=0.00020,t=0.00060,q=0.0025)]
|
|
_tn=[dict(x=v["x"],y=v["y"],s=-v["s"],t=-v["t"],q=-v["q"]) for v in _tp]
|
|
if _pack(_tp)!=_pack(_tn): sys.exit("[Ch355] FAIL: canonicalization not bit-exact (positive-Q tri vs its all-negated twin differ)")
|
|
print("[Ch355] canonicalization self-test PASS: positive-Q triangle and its all-negated twin pack BIT-IDENTICALLY (exact ratio/interp preservation)")
|
|
|
|
STG_WORDS=2048
|
|
stgA,ntA = feeder_list(drA, CBP_A); stgB,ntB = feeder_list(drB, CBP_B)
|
|
for nm,stg in (("A",stgA),("B",stgB)):
|
|
if len(stg)>STG_WORDS: sys.exit(f"[Ch355] list {nm} {len(stg)} > STG_WORDS {STG_WORDS}")
|
|
if max(ntA,ntB)*3 >= (1<<12): sys.exit(f"[Ch355] staging addr exceeds 12-bit bridge range")
|
|
print(f"[Ch355] list A: {ntA} tris -> {len(stgA)} words (max addr {len(stgA)-1}); list B: {ntB} tris -> {len(stgB)} words (max addr {len(stgB)-1}); both < 12-bit {1<<12}")
|
|
|
|
# per-group de-swizzled texture (LINEAR indices -> LPDDR) + CRC ; relocated CLUT bytes + de-gridded palette
|
|
idxA=memA.read_psmt8(tA['tbp'], tA['tbw'], TW, TH); idxB=memB.read_psmt8(tB['tbp'], tB['tbw'], TW, TH)
|
|
palA=RC.read_clut32(memA, tA['cbp'], order="grid"); palB=RC.read_clut32(memB, tB['cbp'], order="grid")
|
|
idxwA=[idxA[i*4]|(idxA[i*4+1]<<8)|(idxA[i*4+2]<<16)|(idxA[i*4+3]<<24) for i in range(TW*TH//4)]
|
|
idxwB=[idxB[i*4]|(idxB[i*4+1]<<8)|(idxB[i*4+2]<<16)|(idxB[i*4+3]<<24) for i in range(TW*TH//4)]
|
|
crcTA=sum(idxwA)&0xFFFFFFFF; crcTB=sum(idxwB)&0xFFFFFFFF
|
|
print(f"[Ch355] LPDDR texture A sum32=0x{crcTA:08x} ; texture B sum32=0x{crcTB:08x} (the two cache-fill CRCs)")
|
|
|
|
# independent A->B composed reference (dump order, paint-order DECAL). refmap: [31]cov [30]int [28]overlap [17:9]tu [8:0]tv
|
|
# ALSO per-group refmaps (refmapA/refmapB) so the isolated single-group RTL renders can be scored independently.
|
|
refmap=[0]*(FBPXW*H); refpix=[(0,0,0)]*(FBPXW*H); ndraw=[0]*(FBPXW*H)
|
|
refmapA=[0]*(FBPXW*H); refmapB=[0]*(FBPXW*H)
|
|
for gi,(dr,idxg,palg) in enumerate([(drA,idxA,palA),(drB,idxB,palB)]):
|
|
for (v0,v1,v2) in group_tris(dr):
|
|
x0,y0=v0["x"],v0["y"]; x1,y1=v1["x"],v1["y"]; x2,y2=v2["x"],v2["y"]; ar=edge(x0,y0,x1,y1,x2,y2)
|
|
if abs(ar)<1e-9: continue
|
|
inv=1.0/ar
|
|
minx=max(0,int(min(x0,x1,x2))); maxx=min(FBPXW-1,int(max(x0,x1,x2))+1)
|
|
miny=max(0,int(min(y0,y1,y2))); maxy=min(H-1,int(max(y0,y1,y2))+1)
|
|
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)*inv; w1=edge(x2,y2,x0,y0,cx,cy)*inv; w2=1.0-w0-w1
|
|
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
|
a0=edge(x1,y1,x2,y2,float(px),float(py))*inv; a1=edge(x2,y2,x0,y0,float(px),float(py))*inv; a2=1.0-a0-a1
|
|
S=a0*v0["s"]+a1*v1["s"]+a2*v2["s"]; T=a0*v0["t"]+a1*v1["t"]+a2*v2["t"]; Q=a0*v0["q"]+a1*v1["q"]+a2*v2["q"]
|
|
if abs(Q)<1e-12: continue
|
|
tu=int((S/Q)*TW)%TW; tv=int((T/Q)*TH)%TH
|
|
if tu<0: tu+=TW
|
|
if tv<0: tv+=TH
|
|
o=py*FBPXW+px; mw=min(w0,w1,w2); interior=1 if mw>0.04 else 0
|
|
prev_cov = refmap[o]>>31
|
|
rm=(1<<31)|(interior<<30)|((tu&0x1FF)<<9)|(tv&0x1FF)
|
|
if gi==0: refmapA[o]=rm
|
|
else: refmapB[o]=rm
|
|
refmap[o]=rm # later group (B) overwrites (paint order)
|
|
p=palg[idxg[tv*TW+tu]&0xFF]; refpix[o]=(p&0xFF,(p>>8)&0xFF,(p>>16)&0xFF)
|
|
if gi==1 and prev_cov: refmap[o]|=(1<<28); ndraw[o]=2 # B over A -> overlap
|
|
overlap_px=sum(1 for o in range(FBPXW*H) if refmap[o]&(1<<28))
|
|
covered=sum(1 for w in refmap if w>>31)
|
|
print(f"[Ch355] independent A->B reference: {covered} covered px, {overlap_px} A&B overlap px [bit28]")
|
|
if overlap_px<500: sys.exit(f"[Ch355] FAIL: only {overlap_px} overlap px — multi-texture accumulation not exercised")
|
|
|
|
# ---- emit ----
|
|
def wmem(name, words, banner):
|
|
with open(os.path.join(DATA,name),"w") as f:
|
|
f.write(f"// {banner}\n")
|
|
for x in words: f.write(f"{x&0xFFFFFFFF:08x}\n")
|
|
wmem("sh3_mtA_tex_lpddr.mem", idxwA, "Ch355 LOCAL texture A (idx19562/tbp=11264) LINEAR -> LPDDR. gitignored.")
|
|
wmem("sh3_mtB_tex_lpddr.mem", idxwB, "Ch355 LOCAL texture B (idx89761/tbp=9216) LINEAR -> LPDDR. gitignored.")
|
|
wmem("sh3_mtA_idx.mem", idxwA, "Ch355 LOCAL idx A. gitignored."); wmem("sh3_mtB_idx.mem", idxwB, "Ch355 LOCAL idx B. gitignored.")
|
|
wmem("sh3_mtA_pal.mem", [p&0xFFFFFFFF for p in palA], "Ch355 LOCAL de-gridded palette A. gitignored.")
|
|
wmem("sh3_mtB_pal.mem", [p&0xFFFFFFFF for p in palB], "Ch355 LOCAL de-gridded palette B. gitignored.")
|
|
wmem("sh3_mt_refmap.mem", refmap, "Ch355 LOCAL A->B composed per-pixel covered|interior|overlap|tu|tv reference. gitignored.")
|
|
wmem("sh3_mtA_refmap.mem", refmapA, "Ch355 LOCAL A-only (idx19562) per-pixel covered|interior|tu|tv reference. gitignored.")
|
|
wmem("sh3_mtB_refmap.mem", refmapB, "Ch355 LOCAL B-only (idx89761) per-pixel covered|interior|tu|tv reference. gitignored.")
|
|
bake.write_feeder_stg_mem("feeder_sh3_mtA.mem", stgA, f"Ch355 LOCAL list A (idx{DRAW_A}, TEX0 CBP={CBP_A}) {ntA} tris. gitignored.", total=STG_WORDS)
|
|
bake.write_feeder_stg_mem("feeder_sh3_mtB.mem", stgB, f"Ch355 LOCAL list B (idx{DRAW_B}, TEX0 CBP={CBP_B}) {ntB} tris. gitignored.", total=STG_WORDS)
|
|
|
|
# bootlet: upload BOTH relocated CLUTs (A@CBP_A, B@CBP_B) via two 256x1 BITBLTs; DISPLAY1 = FBPXW x H
|
|
clutwA=[int.from_bytes(clutA[i*4:i*4+4],'little') for i in range(256)]
|
|
clutwB=[int.from_bytes(clutB[i*4:i*4+4],'little') for i in range(256)]
|
|
RAM_QWORDS=512; pay=[]
|
|
for cbp,clutw in ((CBP_A,clutwA),(CBP_B,clutwB)):
|
|
pay.append(bake.giftag(1,0,0,4,int('E'*4,16)))
|
|
pay.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(cbp,1,0x00)))
|
|
pay.append(bake.aplusd(bake.R_TRXPOS, bake.trxpos_pack(0,0)))
|
|
pay.append(bake.aplusd(bake.R_TRXREG, bake.trxreg_pack(256,1)))
|
|
pay.append(bake.aplusd(bake.R_TRXDIR, bake.trxdir_pack(0)))
|
|
pay.append(bake.giftag(256//4,1,2,0,0))
|
|
for q in range(256//4):
|
|
word=0
|
|
for lane in range(4): word|=(clutw[q*4+lane]&0xFFFFFFFF)<<(32*lane)
|
|
pay.append(word)
|
|
qwc=len(pay); disp_hi=((H-1)<<12)|(FBPXW-1)
|
|
with open(os.path.join(DATA,"payload_sh3_mt.mem"),"w") as f:
|
|
f.write(f"// Ch355 LOCAL two-CLUT setup payload (A->CBP={CBP_A}, B->CBP={CBP_B}). gitignored. QWC={qwc}.\n")
|
|
for _ in range(16): f.write(f"{0:032x}\n")
|
|
for x in pay: f.write(f"{x&((1<<128)-1):032x}\n")
|
|
for _ in range(RAM_QWORDS-16-qwc): f.write(f"{0:032x}\n")
|
|
bake.write_bios_mem("bios_sh3_mt.mem", bake.build_textured_demo_bootlet_disp(qwc, disp_hi, FBW),
|
|
f"Ch355 LOCAL two-CLUT bootlet (QWC={qwc}, DISPLAY1={FBPXW}x{H}). gitignored.")
|
|
|
|
with open(os.path.join(DATA,"sh3_mt_params.vh"),"w") as f:
|
|
f.write("// Ch355 LOCAL generated params for the multi-texture integration TB. gitignored.\n")
|
|
for k,v in (("FBW",FBW),("FBPXW",FBPXW),("FBH",H),("VRAM_BYTES_P",VRAM_BYTES),("CBP_A",CBP_A),("CBP_B",CBP_B),
|
|
("NEW_TBP",NEW_TBP),("TEX_VRAM_BASE",TEX_VRAM_BASE),("TEX_BYTES",TW*TH),("N_BEATS",TW*TH//32),
|
|
("STG_WORDS",STG_WORDS),("TW",TW),("TH",TH),("NTRIS_A",ntA),("NTRIS_B",ntB),
|
|
("UNION_OX",OX),("UNION_OY",OY),("CONTENT_W",W)):
|
|
f.write(f"localparam int {k:<14}= {v};\n")
|
|
f.write(f"localparam [29:0] LPDDR_TEX_BASE = 30'h{LPDDR_TEX_BASE:07x};\n")
|
|
f.write(f"localparam [31:0] CRC_TEX_A = 32'h{crcTA:08x};\n")
|
|
f.write(f"localparam [31:0] CRC_TEX_B = 32'h{crcTB:08x};\n")
|
|
try:
|
|
from PIL import Image
|
|
im=Image.new("RGB",(FBPXW,H)); im.putdata(refpix); im.save(os.path.join(ROOT,"captures","gs","silenthill3","extracted","recon","sh3_mt_ref.png"))
|
|
print("[Ch355] wrote sh3_mt_ref.png")
|
|
except Exception as ex: print("(PIL skipped:", ex, ")")
|
|
print(f"[Ch355] emitted multi-texture fixtures -> {DATA}. FB {FBPXW}x{H} stride {STRIDE}; texA crc=0x{crcTA:08x} texB crc=0x{crcTB:08x}")
|
|
return 0
|
|
|
|
if __name__=="__main__":
|
|
raise SystemExit(main(sys.argv))
|