Files
ProSepandClaude Opus 5.5 007786d487 Add HDR10 output
With hdr = 1 the game's tonemap shader is replaced by one that keeps highlights up to the display's peak, the present
reads the float picture the game copies into its 8-bit window texture, and a compute pass encodes it as PQ in Rec. 2020
for an HDR10 swapchain. Peak, paper white and HUD brightness are live settings in the menu and totk.ini. The approach
follows the RenoDX TotK mod by souperman9 and Musa; no code was copied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:17:12 +01:00

90 lines
3.4 KiB
Python

"""Compile the runtime's own GLSL shaders (runtime/shaders/*.comp, *.vert, *.frag) into C headers of SPIR-V words.
Each runtime/shaders/<name>.comp (or .vert, .frag: one name per shader) 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 os
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
BIN = Path(r'C:\msys64\ucrt64\bin') # Windows; elsewhere glslc and spirv-val come from PATH
WIN = os.name == 'nt'
TARGET = 'vulkan1.1'
STAGES = {'.comp': 'compute', '.vert': 'vertex', '.frag': 'fragment'}
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 sources(runtime):
return sorted(p for p in (runtime / 'shaders').iterdir() if p.suffix in STAGES)
def stale(runtime):
"""Shader sources under runtime/shaders whose header is missing or records another source hash."""
out = []
for src in sources(runtime):
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') if WIN else 'glslc', '-O', f'--target-env={TARGET}', f'-fshader-stage={STAGES[src.suffix]}',
'-o', str(spv), str(src)], check=True)
subprocess.run([str(BIN / 'spirv-val.exe') if WIN else 'spirv-val', 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 sources(args.runtime):
compile_one(src)
return 0
if __name__ == '__main__':
sys.exit(main())