CultLib/packages/cultmath/tools/device/gen.py
Meta Vens 7c47cbaffd Merge origin/main (phasor-field-rename a085c0b3) into hands/cultmath-glsl-build-dxc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:32:08 +02:00

140 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Spell one compute shader per work item, in HLSL and in GLSL ES 3.10. It compares and judges nothing.
usage: gen.py <workdir> <cultmath-root>
<workdir>/manifest.txt (written by GlslMirrorTests.WriteDeviceWork) has one tab-separated line per item:
tag, fixture index, glsl signature, n, words in, words out. For each item this writes <tag>.hlsl (cs_6_0,
includes CultMath.hlsl) and <tag>.comp (#version 310 es, the committed GLSL files, then the body). An item
reads n argument tuples of 32-bit words from binding 0 and writes n result tuples to binding 1. Struct
arguments and results are built and flattened in field order, from the struct definitions in the HLSL."""
import glob
import os
import re
import sys
HLSL_VEC = re.compile(r'(float|int|uint)([234])$')
GLSL_VEC = re.compile(r'(i|u|)vec([234])$')
KIND = {'float': 'f', 'int': 'i', 'uint': 'u'}
ARRAY = re.compile(r'(\w+)\[(\d+)\]$') # an array leaf, vec4[128]: N elements of one leaf type, N x its words
def structs(root):
"""struct name -> [(field, type, hlsl)] from every shader's struct definitions, GLSL type names."""
found = {}
for path in sorted(glob.glob(os.path.join(root, 'shaders', '*.hlsl'))):
for name, body in re.findall(r'(?m)^struct (\w+)\s*\{(.*?)\};', open(path).read(), re.S):
found[name] = [(f, t) for t, f in re.findall(r'(\w+)\s+(\w+);', body)]
return found
def leaves(t, defs, path=''):
"""Flatten a type (GLSL spelling for scalars and vectors, HLSL for struct fields) to scalar leaves:
(accessor path, kind) in field order. A vector's components are path[i]; a mat3 is read row by row."""
if t in KIND:
return [(path, KIND[t], None)]
m = HLSL_VEC.match(t) or GLSL_VEC.match(t)
if m:
k = {'float': 'f', 'int': 'i', 'uint': 'u', '': 'f', 'i': 'i', 'u': 'u'}[m.group(1)]
return [(path, k, i) for i in range(int(m.group(2)))]
if t in ('mat3', 'float3x3'):
return [(path, 'f', (i, j)) for i in range(3) for j in range(3)]
if t in defs:
out = []
for field, ft in defs[t]:
out += leaves(ft, defs, path + '.' + field)
return out
raise SystemExit('unsupported type ' + t)
def access(leaf, glsl):
path, _, index = leaf
if index is None:
return path
if isinstance(index, tuple): # row i, column j; GLSL matrices index the column first
return '%s[%d][%d]' % ((path,) + ((index[1], index[0]) if glsl else index))
return '%s[%d]' % (path, index)
def read(kind, glsl, word):
if glsl:
return {'f': 'uintBitsToFloat(%s)', 'i': 'int(%s)', 'u': '%s'}[kind] % word
return {'f': 'asfloat(%s)', 'i': 'asint(%s)', 'u': '%s'}[kind] % word
def write(kind, glsl, value):
if glsl:
return {'f': 'floatBitsToUint(%s)', 'i': 'uint(%s)', 'u': '%s'}[kind] % value
return {'f': 'asuint(%s)', 'i': 'asuint(%s)', 'u': '%s'}[kind] % value
def hlsl_type(t):
m = GLSL_VEC.match(t)
if m:
return {'': 'float', 'i': 'int', 'u': 'uint'}[m.group(1)] + m.group(2)
return 'float3x3' if t == 'mat3' else t
def body(sig, n, win, wout, defs, glsl):
m = re.fullmatch(r'(\w+)\s+(\w+)\((.*)\)', sig)
ret, name, params = m.group(1), m.group(2), [p.strip() for p in m.group(3).split(',') if p.strip()]
lines, offset, args = [], 0, []
for j, p in enumerate(params):
array = ARRAY.match(p)
if array:
elem, size = array.group(1), int(array.group(2))
words = leaves(elem, defs)
lines.append(' %s a%d[%d];' % (elem if glsl else hlsl_type(elem), j, size))
lines.append(' for (uint e = 0u; e < %du; e++) {' % size)
for k, leaf in enumerate(words):
word = ('In.a[b + %du + e * %du]' if glsl else 'In[b + %du + e * %du]') % (offset + k, len(words))
lines.append(' a%d[e]%s = %s;' % (j, access(leaf, glsl), read(leaf[1], glsl, word)))
lines.append(' }')
offset += size * len(words)
args.append('a%d' % j)
continue
ptype = p if glsl else hlsl_type(p)
lines.append(' %s a%d;' % (ptype, j))
for leaf in leaves(p, defs):
word = ('In.a[b + %du]' if glsl else 'In[b + %d]') % offset
lines.append(' a%d%s = %s;' % (j, access(leaf, glsl), read(leaf[1], glsl, word)))
offset += 1
args.append('a%d' % j)
rtype = ret if glsl else hlsl_type(ret)
lines.append(' %s r = %s(%s);' % (rtype, name, ', '.join(args)))
produced = leaves(ret, defs)
for k, leaf in enumerate(produced):
target = ('Out.a[o + %du]' if glsl else 'Out[o + %d]') % k
lines.append(' %s = %s;' % (target, write(leaf[1], glsl, 'r' + access(leaf, glsl))))
if offset != win or len(produced) != wout:
raise SystemExit('%s: %d words in and %d out, the manifest says %d and %d' % (sig, offset, len(produced), win, wout))
head = 'uint i = id.x; if (i >= %du) return; uint b = i * %du; uint o = i * %du;' % (n, win, wout)
return head, '\n'.join(lines)
def main(work, root):
defs = structs(root)
shaders = os.path.join(root, 'shaders')
built = os.environ.get('GLSL_DIR')
parts = [os.path.join(built, f) for f in ('core.glsl', 'cycles.glsl', 'phacelle.glsl')] if built else [os.path.join(shaders, f) for f in ('CultMath.glsl', 'CultMath.Cycles.glsl', 'CultMath.Phacelle.glsl')]
library = ''.join(open(p).read() for p in parts)
count = 0
for line in open(os.path.join(work, 'manifest.txt')):
tag, _, sig, n, win, wout = line.rstrip('\n').split('\t')
n, win, wout = int(n), int(win), int(wout)
head, code = body(sig, n, win, wout, defs, False)
open(os.path.join(work, tag + '.hlsl'), 'w').write(
'#include "CultMath.hlsl"\n[[vk::binding(0)]] StructuredBuffer<uint> In;\n[[vk::binding(1)]] RWStructuredBuffer<uint> Out;\n'
'[numthreads(64, 1, 1)] void main(uint3 id : SV_DispatchThreadID) {\n %s\n%s\n}\n' % (head, code))
head, code = body(sig, n, win, wout, defs, True)
open(os.path.join(work, tag + '.comp'), 'w').write(
'#version 310 es\nprecision highp float;\nprecision highp int;\n' + library +
'layout(local_size_x = 64) in;\nlayout(std430, binding = 0) readonly buffer IB { uint a[]; } In;\n'
'layout(std430, binding = 1) buffer OB { uint a[]; } Out;\n'
'void main() {\n uint i = gl_GlobalInvocationID.x; %s\n%s\n}\n' % (head.replace('uint i = id.x; ', ''), code))
count += 1
print('generated', count, 'work items')
if __name__ == '__main__':
main(sys.argv[1], sys.argv[2])