package meta import "core:fmt" import "core:os" import "core:strings" // Compiles all GLSL shaders in source_dir to both SPIR-V (.spv) and // Metal Shading Language (.metal), writing results to generated_dir. // Overwrites any previously generated files with matching names. // Requires `glslangValidator` and `spirv-cross` on PATH. gen_shaders :: proc(source_dir, generated_dir: string) -> (success: bool) { if !verify_shader_tool("glslangValidator") do return false if !verify_shader_tool("spirv-cross") do return false source_entries, read_err := os.read_all_directory_by_path(source_dir, context.temp_allocator) if read_err != nil { fmt.eprintfln("Failed to read shader source directory '%s': %v", source_dir, read_err) return false } shader_names := make([dynamic]string, len = 0, cap = 24, allocator = context.temp_allocator) for entry in source_entries { if strings.has_suffix(entry.name, ".vert") || strings.has_suffix(entry.name, ".frag") { append(&shader_names, entry.name) } } if len(shader_names) == 0 { fmt.eprintfln("No shader source files (.vert, .frag) found in '%s'.", source_dir) return false } if os.exists(generated_dir) { rmdir_err := os.remove_all(generated_dir) if rmdir_err != nil { fmt.eprintfln("Failed to remove old output directory '%s': %v", generated_dir, rmdir_err) return false } } mkdir_err := os.mkdir(generated_dir) if mkdir_err != nil { fmt.eprintfln("Failed to create output directory '%s': %v", generated_dir, mkdir_err) return false } compiled_count := 0 for shader_name in shader_names { source_path := fmt.tprintf("%s/%s", source_dir, shader_name) spv_path := fmt.tprintf("%s/%s.spv", generated_dir, shader_name) metal_path := fmt.tprintf("%s/%s.metal", generated_dir, shader_name) fmt.printfln("[GLSL -> SPIR-V] %s", shader_name) if !compile_glsl_to_spirv(source_path, spv_path) do continue fmt.printfln("[SPIR-V -> MSL] %s", shader_name) if !compile_spirv_to_msl(spv_path, metal_path) do continue compiled_count += 1 } total := len(shader_names) if compiled_count == total { fmt.printfln("Successfully compiled all %d shaders.", total) return true } fmt.eprintfln("%d of %d shaders failed to compile.", total - compiled_count, total) return false } verify_shader_tool :: proc(tool_name: string) -> bool { _, _, _, err := os.process_exec( os.Process_Desc{command = []string{tool_name, "--version"}}, context.temp_allocator, ) if err != nil { fmt.eprintfln("Required tool '%s' not found on PATH.", tool_name) if tool_name == "glslangValidator" { fmt.eprintln("\tInstall the Vulkan SDK or the glslang package:") fmt.eprintln("\t macOS: brew install glslang") fmt.eprintln("\t Arch: sudo pacman -S glslang") fmt.eprintln("\t Debian: sudo apt install glslang-tools") } else if tool_name == "spirv-cross" { fmt.eprintln("\tInstall SPIRV-Cross:") fmt.eprintln("\t macOS: brew install spirv-cross") fmt.eprintln("\t Arch: sudo pacman -S spirv-cross") fmt.eprintln("\t Debian: sudo apt install spirv-cross") } return false } return true } compile_glsl_to_spirv :: proc(source_path, output_path: string) -> bool { state, stdout_bytes, stderr_bytes, err := os.process_exec( os.Process_Desc{command = []string{"glslangValidator", "-V", source_path, "-o", output_path}}, context.temp_allocator, ) if err != nil { fmt.eprintfln("\tFailed to run glslangValidator for '%s': %v", source_path, err) return false } if !state.success { fmt.eprintfln("\tglslangValidator failed for '%s' (exit code %d):", source_path, state.exit_code) print_tool_output(stdout_bytes, stderr_bytes) return false } return true } compile_spirv_to_msl :: proc(spv_path, output_path: string) -> bool { state, stdout_bytes, stderr_bytes, err := os.process_exec( os.Process_Desc{command = []string{"spirv-cross", "--msl", spv_path, "--output", output_path}}, context.temp_allocator, ) if err != nil { fmt.eprintfln("\tFailed to run spirv-cross for '%s': %v", spv_path, err) return false } if !state.success { fmt.eprintfln("\tspirv-cross failed for '%s' (exit code %d):", spv_path, state.exit_code) print_tool_output(stdout_bytes, stderr_bytes) return false } return true } print_tool_output :: proc(stdout_bytes, stderr_bytes: []u8) { stderr_text := strings.trim_right_space(transmute(string)stderr_bytes) stdout_text := strings.trim_right_space(transmute(string)stdout_bytes) if len(stderr_text) > 0 do fmt.eprintfln("\t%s", stderr_text) if len(stdout_text) > 0 do fmt.eprintfln("\t%s", stdout_text) }