Files
iHaiDeeZ--SSBU/scripts/pack-shadercache.py
T
ShiroKlein 73d34e1e5c v00.16: the shader pack - every shader of the game compiled on the PC by a copy of the console's RADV, read on the console instead of compiled
v00.15's match: 940 ms of compiling a shader, up to 370 waiting, their draws skipped (the stage
half drawn, the sky black then white), three cores busy compiling (frame times 15-100 ms).

- scripts/build-mesa-host.sh: RADV for the PC with the console's identity (Mesa's
  ac_ps4_info.h, AC_PS4_HOST_IDENTITY: the GPU the queries describe, DRM 3.61, address32_hi
  0x44, conditional rendering off), same build id and btver2 target (no FMA): the same cache
  folder and pipeline keys as the console's (checked against the console's cache)
- scripts/build-dxvk-host.sh: DXVK for the PC as the PS4 build is (native_ps4)
- tools/shaderpack + scripts/build-shaderpack.sh: the game's 45,653 DXBC shaders made through
  them (8.5 min on 20 threads): RADV's cache (ssbu_pack.foz, 331 MB) and DXVK's IR cache
- scripts/pack-shadercache.py: the DXBC cache folder as one file (dump/shadercache.pak), read
  by the runtime instead of 45,000 files (the renderer waited in stat 30% of its time)
- the port reads ssbu_pack as a read-only Mesa cache (MESA_DISK_CACHE_READ_ONLY_FOZ_DBS)
- the PS4 DXVK rebuilt from the committed tree: its cache version matches the PC's
2026-10-21 19:38:57 +03:00

62 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""The game's shader disk cache (DXBC files) in one file, for the PS4: <out> = shadercache.pak, read by the runtime in
place of the folder (runtime/gpu_d3d11.c pack_open: on the PS4 a look-up in a folder of 45,000 files is slow).
pack-shadercache.py <out.pak> <folder> [<folder>...] (a later folder's file wins over an earlier one's)
Layout (little-endian): "SSBUDXBC", u32 version 1, u32 count; count entries {u64 FNV-1a of the file's name, u64 offset,
u32 size, u32 0}, sorted by the hash; then the files' bytes, each at a multiple of 16."""
import os
import struct
import sys
def fnv1a(data):
h = 1469598103934665603
for b in data:
h = ((h ^ b) * 1099511628211) & 0xFFFFFFFFFFFFFFFF
return h
def main():
if len(sys.argv) < 3:
sys.exit(__doc__)
out, folders = sys.argv[1], sys.argv[2:]
files = {}
for folder in folders:
for name in os.listdir(folder):
if name.startswith("v") and name.endswith(".dxbc"):
files[name] = os.path.join(folder, name)
by_hash = {}
for name in files:
h = fnv1a(name.encode())
if h in by_hash:
sys.exit(f"{name} and {by_hash[h]}: the same hash")
by_hash[h] = name
order = sorted(by_hash)
sizes = {h: os.path.getsize(files[by_hash[h]]) for h in order}
at = 16 + 24 * len(order)
places = {}
for h in order:
at = (at + 15) & ~15
places[h] = at
at += sizes[h]
tmp = out + ".part"
with open(tmp, "wb") as f:
f.write(b"SSBUDXBC" + struct.pack("<II", 1, len(order)))
for h in order:
f.write(struct.pack("<QQII", h, places[h], sizes[h], 0))
for h in order:
f.write(b"\0" * (places[h] - f.tell()))
with open(files[by_hash[h]], "rb") as src:
data = src.read()
if len(data) != sizes[h]:
sys.exit(f"{files[by_hash[h]]} changed size")
f.write(data)
os.replace(tmp, out)
print(f"{out}: {len(order)} files, {at / 1048576:.0f} MiB")
if __name__ == "__main__":
main()