#!/usr/bin/env python3
"""Embed a dual-compiled .inc as a GLSL string in a generated C++ header.
A .inc under src/shared/ is written in the subset GLSL and C++ agree on. C++
compiles it directly through a shim; the GPU has to compile it as GLSL, and a
GDExtension cannot read a source file off disk at run time. So the text is
embedded in a generated header at build time.
Generated, not checked in and hand-synced, for the obvious reason: a
checked-in copy is a copy, and copies of these algorithms are what the whole
exercise exists to remove. If this file is stale the build is stale; there is
no path where the shader and the C++ silently disagree.
Usage: gen_glsl_inc.py <input.inc> <output.gen.h> <SYMBOL>
Was gen_erosion_glsl.py, which knew the erosion filter's name. The hash layer
became the second .inc on 2026-09-05 and a second copy of this script would
have been the same mistake one level up.
"""
import re
import sys
def main(src_path: str, dst_path: str, symbol: str) -> int:
if not re.fullmatch(r"[A-Z][A-Z0-9_]*", symbol):
raise SystemExit("gen_glsl_inc: symbol %r is not a C identifier" % symbol)
with open(src_path, "r", encoding="utf-8") as f:
text = f.read()
# ⚠ A C++ RAW-STRING DELIMITER MAY BE AT MOST 16 CHARACTERS. [lex.string]
# says so, and exceeding it does not fail where you are looking: the
# generated header stops being valid C++, every declaration after it is
# swallowed, and the compiler reports an unrelated incomplete type 700
# lines away in whatever was included next. That is exactly how this went
# wrong when the delimiter was first derived from the symbol —
# EROSION_FILTER_GLSL is 19 characters — so the delimiter is a short
# constant and the symbol is free to be as descriptive as it likes.
delim = "INCGLSL"
assert len(delim) <= 16
# The delimiter must also not appear in the payload, or the generated
# header silently ends early and takes half the algorithm with it. Check
# rather than hope.
if ")" + delim + '"' in text:
raise SystemExit(
"gen_glsl_inc: %s contains the raw-string terminator )%s\" — "
"pick a different delimiter" % (src_path, delim)
)
guard = "PROC_NODES_%s_GEN_H" % symbol
out = [
"// GENERATED by tools/gen_glsl_inc.py from %s\n" % src_path,
"// DO NOT EDIT. Edit the .inc; this is rebuilt from it.\n",
"//\n",
"// This is the same text the C++ side compiles through its shim. That is\n",
"// the entire point: the GPU and the CPU evaluate one algorithm, not two\n",
"// that agree by review.\n",
"#ifndef %s\n" % guard,
"#define %s\n" % guard,
"\n",
'static const char *%s = R"%s(\n' % (symbol, delim),
text,
')%s";\n' % delim,
"\n",
"#endif // %s\n" % guard,
]
with open(dst_path, "w", encoding="utf-8") as f:
f.write("".join(out))
return 0
if __name__ == "__main__":
if len(sys.argv) != 4:
raise SystemExit(__doc__)
sys.exit(main(sys.argv[1], sys.argv[2], sys.argv[3]))