mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-13 02:41:02 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
"""Compile the runtime's own GLSL shaders (runtime/shaders/*.comp) into C headers of SPIR-V words.
|
|
|
|
Each runtime/shaders/<name>.comp becomes runtime/<name>_spv.h, which declares `static const uint32_t <name>_spv[]`
|
|
and records the SHA-256 of the source it came from. The headers are committed, so building totk.exe needs no shader
|
|
compiler; tools/build_game.py refuses to build when a header no longer matches its source. Run this after editing a
|
|
shader. Needs glslc and spirv-val from MSYS2 UCRT64 (package mingw-w64-ucrt-x86_64-shaderc, spirv-tools).
|
|
|
|
python tools/build_shaders.py [--check]
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
BIN = Path(r'C:\msys64\ucrt64\bin')
|
|
TARGET = 'vulkan1.1'
|
|
|
|
|
|
def source_hash(src):
|
|
return hashlib.sha256(src.read_bytes().replace(b'\r\n', b'\n')).hexdigest() # the same on a CRLF checkout
|
|
|
|
|
|
def header_of(src, words):
|
|
name = src.stem
|
|
lines = [f'// Generated by tools/build_shaders.py from runtime/shaders/{src.name}; do not edit.',
|
|
f'// source sha256 {source_hash(src)}',
|
|
'#pragma once',
|
|
'#include <stdint.h>',
|
|
f'static const uint32_t {name}_spv[] = {{']
|
|
for i in range(0, len(words), 8):
|
|
lines.append(' ' + ', '.join(f'0x{w:08x}' for w in words[i:i + 8]) + ',')
|
|
lines.append('};')
|
|
return '\n'.join(lines) + '\n'
|
|
|
|
|
|
def header_path(src):
|
|
return src.parent.parent / f'{src.stem}_spv.h'
|
|
|
|
|
|
def stale(runtime):
|
|
"""Shader sources under runtime/shaders whose header is missing or records another source hash."""
|
|
out = []
|
|
for src in sorted((runtime / 'shaders').glob('*.comp')):
|
|
h = header_path(src)
|
|
if not h.exists() or f'// source sha256 {source_hash(src)}' not in h.read_text(encoding='utf-8'):
|
|
out.append(src)
|
|
return out
|
|
|
|
|
|
def compile_one(src):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
spv = Path(tmp) / 'out.spv'
|
|
subprocess.run([str(BIN / 'glslc.exe'), '-O', f'--target-env={TARGET}', '-fshader-stage=compute',
|
|
'-o', str(spv), str(src)], check=True)
|
|
subprocess.run([str(BIN / 'spirv-val.exe'), f'--target-env', TARGET, str(spv)], check=True)
|
|
data = spv.read_bytes()
|
|
words = [int.from_bytes(data[i:i + 4], 'little') for i in range(0, len(data), 4)]
|
|
header_path(src).write_text(header_of(src, words), encoding='utf-8', newline='\n')
|
|
print(f'{header_path(src).relative_to(ROOT)}: {len(words)} words')
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
ap.add_argument('--check', action='store_true', help='only report headers that do not match their source')
|
|
ap.add_argument('--runtime', type=Path, default=ROOT / 'runtime')
|
|
args = ap.parse_args()
|
|
if args.check:
|
|
bad = stale(args.runtime)
|
|
for s in bad:
|
|
print(f'{header_path(s)}: stale, run tools/build_shaders.py')
|
|
return 1 if bad else 0
|
|
for src in sorted((args.runtime / 'shaders').glob('*.comp')):
|
|
compile_one(src)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|