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>
88 lines
4.4 KiB
Python
88 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""retroDE_ps2 — Ch349 GS local-memory model (the bridge Codex framed).
|
|
|
|
A faithful byte-level model of GS local memory (4 MiB VRAM) so a draw's SAMPLED texture can be reconstructed
|
|
as the real hardware sees it — even when the asset is STREAMED in one PSM (e.g. PSMCT32, fast word writes)
|
|
and SAMPLED in another (e.g. PSMT8 indexed). Both operations address the SAME physical bytes through their
|
|
respective GS swizzles; modeling that crossover is the whole point.
|
|
|
|
Swizzle math is ported verbatim from the project's own RTL (which is in turn locked to PCSX2 GSTables.cpp):
|
|
- PSMCT32 write/read <- rtl/gif_gs/gs_swizzle_psmct32_stub.sv (block grid + byte_in_block = yb*32+xb*4)
|
|
- PSMT8 read <- rtl/gif_gs/gs_swizzle_psmt8_stub.sv (block grid + 16x16 columnTable8)
|
|
Address convention matches the codebase: VRAM byte base = PTR*256 (TBP0/DBP/CBP are 14-bit, *256 == 4 MiB),
|
|
and PTRs are page-aligned (multiple of 32) so page_index*8192 composes correctly off that base.
|
|
"""
|
|
|
|
# block grid is shared by PSMCT32 and PSMT8 (4 rows x 8 cols), value = block index within page
|
|
BLOCK = [
|
|
[ 0, 1, 4, 5,16,17,20,21],
|
|
[ 2, 3, 6, 7,18,19,22,23],
|
|
[ 8, 9,12,13,24,25,28,29],
|
|
[10,11,14,15,26,27,30,31],
|
|
]
|
|
# PSMT8 within-block 16x16 -> byte permutation (columnTable8)
|
|
COL8 = [
|
|
[ 0, 4, 16, 20, 32, 36, 48, 52, 2, 6, 18, 22, 34, 38, 50, 54],
|
|
[ 8, 12, 24, 28, 40, 44, 56, 60, 10, 14, 26, 30, 42, 46, 58, 62],
|
|
[ 33, 37, 49, 53, 1, 5, 17, 21, 35, 39, 51, 55, 3, 7, 19, 23],
|
|
[ 41, 45, 57, 61, 9, 13, 25, 29, 43, 47, 59, 63, 11, 15, 27, 31],
|
|
[ 96,100,112,116, 64, 68, 80, 84, 98,102,114,118, 66, 70, 82, 86],
|
|
[104,108,120,124, 72, 76, 88, 92,106,110,122,126, 74, 78, 90, 94],
|
|
[ 65, 69, 81, 85, 97,101,113,117, 67, 71, 83, 87, 99,103,115,119],
|
|
[ 73, 77, 89, 93,105,109,121,125, 75, 79, 91, 95,107,111,123,127],
|
|
[128,132,144,148,160,164,176,180,130,134,146,150,162,166,178,182],
|
|
[136,140,152,156,168,172,184,188,138,142,154,158,170,174,186,190],
|
|
[161,165,177,181,129,133,145,149,163,167,179,183,131,135,147,151],
|
|
[169,173,185,189,137,141,153,157,171,175,187,191,139,143,155,159],
|
|
[224,228,240,244,192,196,208,212,226,230,242,246,194,198,210,214],
|
|
[232,236,248,252,200,204,216,220,234,238,250,254,202,206,218,222],
|
|
[193,197,209,213,225,229,241,245,195,199,211,215,227,231,243,247],
|
|
[201,205,217,221,233,237,249,253,203,207,219,223,235,239,251,255],
|
|
]
|
|
|
|
def ct32_addr(dbp, dbw, x, y):
|
|
"""Byte address of PSMCT32 pixel (x,y) in a buffer based at dbp (256-byte units), width dbw (64px units)."""
|
|
page_index = (y >> 5) * dbw + (x >> 6)
|
|
block_idx = BLOCK[(y >> 3) & 3][(x >> 3) & 7]
|
|
return dbp*256 + page_index*8192 + block_idx*256 + (y & 7)*32 + (x & 7)*4
|
|
|
|
def psmt8_addr(tbp, fbw, x, y):
|
|
"""Byte address of PSMT8 pixel (x,y) in a buffer based at tbp (256-byte units), width fbw (64px units)."""
|
|
page_index = (y >> 6) * (fbw >> 1) + (x >> 7)
|
|
block_idx = BLOCK[(y >> 4) & 3][(x >> 4) & 7]
|
|
return tbp*256 + page_index*8192 + block_idx*256 + COL8[y & 15][x & 15]
|
|
|
|
class LocalMem:
|
|
"""4 MiB GS VRAM. Seed from the dump's initial snapshot, then replay host->local uploads in order."""
|
|
SIZE = 0x400000
|
|
def __init__(self, init_bytes=None):
|
|
if init_bytes is not None and len(init_bytes) >= self.SIZE:
|
|
self.m = bytearray(init_bytes[:self.SIZE])
|
|
else:
|
|
self.m = bytearray(self.SIZE)
|
|
|
|
def write_image_ct32(self, dbp, dbw, dsax, dsay, w, h, words):
|
|
"""Host->local upload in PSMCT32: raster-order words fill (dsax..+w)x(dsay..+h) via ct32 swizzle.
|
|
`words` may be shorter than w*h (partial transfer); fill stops when exhausted (GS behaviour)."""
|
|
n = len(words); i = 0
|
|
for py in range(h):
|
|
for px in range(w):
|
|
if i >= n: return
|
|
a = ct32_addr(dbp, dbw, dsax+px, dsay+py)
|
|
if 0 <= a and a+4 <= self.SIZE:
|
|
self.m[a:a+4] = (words[i] & 0xFFFFFFFF).to_bytes(4, "little")
|
|
i += 1
|
|
|
|
def read_psmt8(self, tbp, fbw, tw, th):
|
|
out = bytearray(tw*th)
|
|
for y in range(th):
|
|
r = y*tw
|
|
for x in range(tw):
|
|
a = psmt8_addr(tbp, fbw, x, y)
|
|
out[r+x] = self.m[a] if 0 <= a < self.SIZE else 0
|
|
return out
|
|
|
|
def read_ct32_word(self, dbp, dbw, x, y):
|
|
a = ct32_addr(dbp, dbw, x, y)
|
|
return int.from_bytes(self.m[a:a+4], "little") if a+4 <= self.SIZE else 0
|