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>
114 lines
6.9 KiB
Python
114 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""retroDE_ps2 — Ch347 authentic SH3 board fixture: bootlet GIF payload that uploads the real SH3 PSMT8
|
|
texture + real CLUT and draws it as a DECAL sprite. Built directly on the Ch296 PSMT8+CLUT board template
|
|
(bake.build_clut8_demo_payload), scaled to 128x128 + a 256-entry CLUT.
|
|
|
|
Mirrors gs_make_cube_fixture.py: imports bake.py helpers, reads the dump-derived assets emitted by
|
|
gs_extract_sh3_clut.py (sh3_tex_idx.mem + sh3_clut.mem), and writes the LOCAL/gitignored board fixtures:
|
|
payload_sh3_clut.mem GIF payload: BITBLT CLUT (256) + BITBLT PSMT8 tex (128x128) + TEX0(PSMT8,CLD=1,CSM2
|
|
linear) + one 128x128 DECAL SPRITE
|
|
bios_sh3_clut.mem one-shot bootlet, QWC = payload qword count, DISPLAY1 = 128x128
|
|
|
|
DECAL/opaque (TFX=1) so the authentic art is visible; the real CLUT alpha (~0x04) is kept BYTE-FOR-BYTE in
|
|
the palette, just ignored by the render mode (Codex guardrail). CLUT declared CSM2/linear (the order
|
|
validated against the authentic render in tools/gs_extract_sh3_clut.py).
|
|
"""
|
|
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")
|
|
sys.path.insert(0, DATA); import bake # reuse giftag/aplusd/*_pack/bootlet helpers
|
|
|
|
# Ch347 (ii): the 64x64 authentic CROP — fits the PROVEN 64x64 bram CLUT config (no new 128 KiB/scanout
|
|
# integration). Full 128x128 is the follow-up rung (i). Swap sh3_tex_idx64->sh3_tex_idx + W/H/FBW for (i).
|
|
W = H = 64
|
|
FBW = 1 # 64 px / 64
|
|
TBP0 = 64 # texture base byte = 64*256 = 0x4000 (just past the 64x64 PSMCT32 FB = 16 KiB)
|
|
TBW = 1 # 64 texels/row stride
|
|
CBP = 80 # CLUT base byte = 80*256 = 0x5000 (past the 4 KiB texture)
|
|
RAM_QWORDS = 512 # 8 KiB EE RAM payload; SH3 64x64 payload ~340 qw fits
|
|
|
|
def read_mem(name, n):
|
|
vals=[]
|
|
for ln in open(os.path.join(DATA,name)):
|
|
ln=ln.strip()
|
|
if ln and not ln.startswith("//"): vals.append(int(ln,16))
|
|
if len(vals) < n: sys.exit(f"{name}: {len(vals)} < {n} (run tools/gs_extract_sh3_clut.py first)")
|
|
return vals
|
|
|
|
tex_words = read_mem("sh3_tex_idx64.mem", W*H//4) # 1024 packed words (4 indices each) — the 64x64 crop
|
|
clut = read_mem("sh3_clut.mem", 256) # 256 PSMCT32 entries
|
|
|
|
# TEX0_1: PSMT8 128x128 (TW=TH=7), TFX=DECAL, + CLUT side (CBP, CPSM=PSMCT32, CSM2 linear, CLD=1 -> load)
|
|
def tex0_sh3():
|
|
v = bake.tex0_pack(TBP0, TBW, psm=0x13, tw=6, th=6, tfx=1) # 64x64 (TW=TH=6)
|
|
v |= (CBP & 0x3FFF) << 37
|
|
v |= (0 & 0xF) << 51 # CPSM = PSMCT32
|
|
v |= (1 & 0x1) << 55 # CSM = CSM2 (linear)
|
|
v |= (0 & 0x1F) << 56 # CSA = 0
|
|
v |= (1 & 0x7) << 61 # CLD = 1 (fire VRAM->CLUT load on commit)
|
|
return v
|
|
|
|
def build_payload():
|
|
qw=[]
|
|
# U1: BITBLT the CLUT (256 PSMCT32 entries, 16x16) -> VRAM[CBP*256]
|
|
qw.append(bake.giftag(1,0,0,4,0x0000_0000_0000_EEEE))
|
|
qw.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(CBP, 1, 0))) # DPSM=PSMCT32
|
|
qw.append(bake.aplusd(bake.R_TRXPOS, bake.trxpos_pack(0,0)))
|
|
qw.append(bake.aplusd(bake.R_TRXREG, bake.trxreg_pack(256,1))) # 256 entries as ONE CONTIGUOUS
|
|
# row — PSMCT32 row_stride = DBW*256, so a 16x16 upload would scatter entries (16/256-B row) while
|
|
# clut_loader reads them contiguously; 256x1 keeps them packed (the Ch296 8-entry demo used 8x1).
|
|
qw.append(bake.aplusd(bake.R_TRXDIR, bake.trxdir_pack(0)))
|
|
qw.append(bake.giftag(256//4,0,2,0,0)) # IMAGE: 4 entries/qword
|
|
for q in range(256//4):
|
|
word=0
|
|
for lane in range(4): word |= (clut[q*4+lane] & 0xFFFFFFFF) << (32*lane)
|
|
qw.append(word)
|
|
# U2: upload the PSMT8 index texture as a PSMCT32 transfer (full 32-bit WORD writes — NOT PSMT8 byte
|
|
# writes, which RMW the 32-bit VRAM word and corrupt one word on 4 consecutive same-word byte-writes).
|
|
# The bytes land identically (TBP*256 + w*4); the TEX0 still READS them as PSMT8. This is exactly the
|
|
# "upload as PSMCT32, bind as PSMT8" trick SH3 itself uses. Upload as (W*H/4)x1 = 1024x1 PSMCT32.
|
|
qw.append(bake.giftag(1,0,0,4,0x0000_0000_0000_EEEE))
|
|
qw.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(TBP0, 16, 0x00))) # DPSM=PSMCT32, DBW=16 (1 row)
|
|
qw.append(bake.aplusd(bake.R_TRXPOS, bake.trxpos_pack(0,0)))
|
|
qw.append(bake.aplusd(bake.R_TRXREG, bake.trxreg_pack(W*H//4, 1))) # 1024 PSMCT32 words x 1 row
|
|
qw.append(bake.aplusd(bake.R_TRXDIR, bake.trxdir_pack(0)))
|
|
qw.append(bake.giftag(W*H//16,0,2,0,0)) # 256 qwords, 4 PSMCT32 words each (= the packed indices)
|
|
for q in range(W*H//16):
|
|
word=0
|
|
for k in range(4): word |= (tex_words[q*4+k] & 0xFFFFFFFF) << (32*k)
|
|
qw.append(word)
|
|
# U3: PRIM(SPRITE+TME) + FRAME + TEX0(PSMT8+CLUT load) + RGBAQ + UV0/XYZ2_0
|
|
frame_val = bake.frame_1_psmct32(FBW)
|
|
qw.append(bake.giftag(1,0,0,6,0x0000_0000_00EE_EEEE))
|
|
qw.append(bake.aplusd(bake.R_PRIM, bake.prim_sprite_tme()))
|
|
qw.append(bake.aplusd(bake.R_FRAME_1, frame_val))
|
|
qw.append(bake.aplusd(bake.R_TEX0_1, tex0_sh3()))
|
|
qw.append(bake.aplusd(bake.R_RGBAQ, bake.rgbaq_data(0x80,0x80,0x80)))
|
|
qw.append(bake.aplusd(bake.R_UV, bake.uv_data(0,0)))
|
|
qw.append(bake.aplusd(bake.R_XYZ2, bake.xyz2_data(0,0)))
|
|
# U4: UV1/XYZ2_1 closing the 128x128 textured sprite. EOP.
|
|
qw.append(bake.giftag(1,1,0,2,0x0000_0000_0000_00EE))
|
|
qw.append(bake.aplusd(bake.R_UV, bake.uv_data(W-1,H-1)))
|
|
qw.append(bake.aplusd(bake.R_XYZ2, bake.xyz2_data(W-1,H-1)))
|
|
return qw
|
|
|
|
payload = build_payload()
|
|
qwc = len(payload)
|
|
if 16 + qwc > RAM_QWORDS: sys.exit(f"payload {qwc} qw + 16 > {RAM_QWORDS} (bump RAM_QWORDS / RAM_SIZE_BYTES)")
|
|
|
|
# DISPLAY1 hi = (DH<<12)|DW for 64x64 (magh=magv=1): DW=DH=63
|
|
disp_hi = (63 << 12) | 63
|
|
with open(os.path.join(DATA,"payload_sh3_clut.mem"),"w") as f:
|
|
f.write(f"// Ch347 LOCAL authentic SH3 64x64-CROP PSMT8+CLUT board payload (tex @ TBP={TBP0}, CLUT @ CBP={CBP}).\n")
|
|
f.write(f"// SH3-derived -> gitignored. qw 0..15 zero; qw 16.. = GIF (BITBLT CLUT + tex + DECAL sprite). QWC={qwc}.\n")
|
|
for _ in range(16): f.write(f"{0:032x}\n")
|
|
for w in payload: f.write(f"{w & ((1<<128)-1):032x}\n")
|
|
for _ in range(RAM_QWORDS-16-qwc): f.write(f"{0:032x}\n")
|
|
bake.write_bios_mem(
|
|
"bios_sh3_clut.mem",
|
|
bake.build_textured_demo_bootlet_disp(qwc, disp_hi, FBW),
|
|
f"Ch347 LOCAL authentic SH3 64x64-crop PSMT8+CLUT setup bootlet (QWC={qwc}, DISPLAY1={W}x{H}). SH3-derived -> gitignored.")
|
|
print(f"[Ch347] payload_sh3_clut.mem: {qwc} qw (BITBLT 256-CLUT + {W}x{H} PSMT8 tex + DECAL sprite, 8 KiB RAM)")
|
|
print(f"[Ch347] bios_sh3_clut.mem : bootlet QWC={qwc}, DISPLAY1={W}x{H} (disp_hi=0x{disp_hi:x}), FBW={FBW}")
|
|
print(f"[Ch347] VRAM layout: FB {W}x{H} PSMCT32 @0 ({W*H*4//1024}KiB) + PSMT8 tex @TBP={TBP0} ({W*H//1024}KiB) + CLUT @CBP={CBP} (1KiB) -> fits the proven 64x64 bram config")
|