CultLib/packages/cultmath/tools/glsl/wrappers.py
Meta Vens ae838b60fd glsl build: the capacity edit names the site arrays by a sentinel length, with tests for the tool refusals
wrappers.py compiles the library with CULTMATH_MAX_SITES defined as a sentinel no shader file spells, so the three
site arrays get an array length id of their own and spvedit.py converts exactly that constant to the overridable
specialization constant (default from the HLSL guard). A library array of literal length 128 keeps its own id and
stays a plain constant. build-glsl.sh gains the pipeline function and scratch variants: a macro outside a declarator,
a folded expression reaching the capacity id, and a non-site float4 x[128] parameter that a consumer's override must
not resize. The [128] shape arm goes: a non-site 128 array is legitimate output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:59:55 +02:00

61 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Build-only export wrappers: one exported, uniquely named wrapper per top-level cultmath_ definition, so dxc keeps
every overload as its own function. The shaders are copied unchanged; the unique names exist only in <outdir>.
usage: wrappers.py <shaders> <outdir>
Writes <outdir>/cm_wrappers.hlsl, <outdir>/wrappers.tsv (wrapper, name, file, params, capacity) and
<outdir>/capacity.tsv (the guard default, the sentinel). cm_wrappers.hlsl defines CULTMATH_MAX_SITES as the sentinel,
a number no shader file spells, so the site arrays get an array length id of their own and no other array shares it. An argument is the
parameter's declarator name with any [..] suffix removed; capacity lists the indices of the parameters declared
[CULTMATH_MAX_SITES]. Definitions with a Texture or Sampler parameter get no wrapper. Refuses a file where the
definition-looking lines differ from the parsed ones, and a file that names CULTMATH_MAX_SITES anywhere but its
#ifndef/#define/#endif guard and array declarators."""
import os, re, shutil, sys
FILES = ['CultMath.hlsl', 'CultMath.Interval.hlsl', 'CultMath.Affine.hlsl', 'CultMath.Cycles.hlsl', 'CultMath.Phacelle.hlsl']
SIG = re.compile(r'(?m)^(\w+)[ \t]+(cultmath_\w+)[ \t]*\(([^)]*)\)')
LOOKS = re.compile(r'(?m)^\w+ cultmath_\w+\(')
GUARD = re.compile(r'#ifndef CULTMATH_MAX_SITES\n#define CULTMATH_MAX_SITES (\d+)\n#endif\n')
COMMENT = re.compile(r'//[^\n]*|/\*.*?\*/', re.S)
CAP = '[CULTMATH_MAX_SITES]'
def main(shaders, out):
shutil.copytree(shaders, out, dirs_exist_ok=True)
wrappers, rows, spelled, default = [], [], '', None
for fn in FILES:
text = open(os.path.join(shaders, fn)).read().replace('\r\n', '\n')
spelled += COMMENT.sub('', text)
default = default or GUARD.search(text)
stray = GUARD.sub('', COMMENT.sub('', text)).replace(CAP, '')
if 'CULTMATH_MAX_SITES' in stray:
sys.exit('refuse: %s names CULTMATH_MAX_SITES outside its guard and array declarators' % fn)
parsed = list(SIG.finditer(text))
looks = len(LOOKS.findall(text))
if looks != len(parsed):
sys.exit('refuse: %s has %d definition-looking lines but %d parsed' % (fn, looks, len(parsed)))
for m in parsed:
ret, name, ps = m.groups()
if re.search(r'Texture|Sampler', ps):
continue
params = [p.strip() for p in ps.split(',') if p.strip()]
args = [re.split(r'\s+', p)[-1].split('[')[0] for p in params]
cap = ','.join(str(i) for i, p in enumerate(params) if p.endswith(CAP))
wn = 'cmw%03d_%s' % (len(rows), name)
call = '%s(%s)' % (name, ', '.join(args))
wrappers.append('export %s %s(%s) { %s%s; }' % (ret, wn, ', '.join(params), '' if ret == 'void' else 'return ', call))
rows.append('\t'.join([wn, name, fn, ', '.join(params), cap]))
if not default:
sys.exit('refuse: no shader file carries the CULTMATH_MAX_SITES guard')
sentinel = int(default.group(1)) + 1
while re.search(r'(?<![\w.])%d(?![\w.])' % sentinel, spelled):
sentinel += 1
open(os.path.join(out, 'capacity.tsv'), 'w').write('%s\t%d\n' % (default.group(1), sentinel))
wrappers.insert(0, '#define CULTMATH_MAX_SITES %d\n#include "CultMath.hlsl"' % sentinel)
open(os.path.join(out, 'cm_wrappers.hlsl'), 'w').write('\n'.join(wrappers) + '\n')
open(os.path.join(out, 'wrappers.tsv'), 'w').write('\n'.join(rows) + '\n')
print('wrappers', len(rows))
if __name__ == '__main__':
main(*sys.argv[1:3])