import os import re import argparse def expand_includes(shader, input_dir): """ Replace #include "file" lines in the text with the contents of that file. Searches for files relative to input_dir. """ include_pattern = re.compile(r'^\s*#include\s+"([^"]+)"\s*$', re.MULTILINE) def replacer(match): fname = match.group(1) file_path = os.path.join(input_dir, fname) if not os.path.exists(file_path): raise FileNotFoundError(f"Included file not found: {file_path}") with open(file_path, "r", encoding="utf-8") as f: included_code = f.read() # Recursively expand includes inside the included file return expand_includes(included_code, input_dir) return include_pattern.sub(replacer, shader) def chunk_shader(shader_code, max_chunk_len=60000): """Split shader_code into safe raw-string sized chunks.""" return [shader_code[i : i + max_chunk_len] for i in range(0, len(shader_code), max_chunk_len)] def raw_delim(shader_code): """Pick a raw-string delimiter that does not appear in the shader.""" delim = "wgsl" while f"){delim}\"" in shader_code: delim += "_x" return delim def write_shader(shader_name, shader_code, output_dir, outfile, input_dir): shader_code = expand_includes(shader_code, input_dir) if output_dir: wgsl_filename = os.path.join(output_dir, f"{shader_name}.wgsl") with open(wgsl_filename, "w", encoding="utf-8") as f_out: f_out.write(shader_code) delim = raw_delim(shader_code) chunks = chunk_shader(shader_code) if len(chunks) == 1: outfile.write(f'const char* wgsl_{shader_name} = R"{delim}({shader_code}){delim}";\n\n') else: for idx, chunk in enumerate(chunks): outfile.write(f'static const char wgsl_{shader_name}_part{idx}[] = R"{delim}({chunk}){delim}";\n\n') outfile.write(f'static const std::string& wgsl_{shader_name}_str() {{\n') outfile.write(' static const std::string s = []{\n') outfile.write(' std::string tmp;\n') outfile.write(f' tmp.reserve({len(shader_code)});\n') for idx in range(len(chunks)): outfile.write(f' tmp.append(wgsl_{shader_name}_part{idx});\n') outfile.write(' return tmp;\n') outfile.write(' }();\n') outfile.write(' return s;\n') outfile.write('}\n') outfile.write(f'const char* wgsl_{shader_name} = wgsl_{shader_name}_str().c_str();\n\n') def main(): parser = argparse.ArgumentParser() parser.add_argument("--input_dir", required=True) parser.add_argument("--output_file", required=True) args = parser.parse_args() with open(args.output_file, "w", encoding="utf-8") as out: out.write("// Auto-generated shader embedding\n") out.write("#include \n\n") for fname in sorted(os.listdir(args.input_dir)): if fname.endswith(".wgsl"): shader_path = os.path.join(args.input_dir, fname) shader_name = fname.replace(".wgsl", "") with open(shader_path, "r", encoding="utf-8") as f: shader_code = f.read() write_shader(shader_name, shader_code, None, out, args.input_dir) if __name__ == "__main__": main()