mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
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>
90 lines
3.4 KiB
Python
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())
|