#!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse import subprocess import sys from pathlib import Path from typing import List, Tuple, Iterator # 支持的后端列表 BACKENDS = [ "glsl", # SG_BACKEND_GLCORE "essl", # SG_BACKEND_GLES3 "hlsl", # SG_BACKEND_D3D11 "metal", # SG_BACKEND_METAL_IOS, SG_BACKEND_METAL_MACOS, SG_BACKEND_METAL_SIMULATOR "wgsl", # SG_BACKEND_WGPU ] SHADER_EXTENSIONS = { 'glsl': 'glsl', 'essl': 'glsl', 'hlsl': 'dxil', 'metal': 'metallib', 'wgsl': 'wgsl' } TARGET_PROFILES = { # 'glsl': ['-profile', 'glsl_460'], # 'essl': ['-profile', 'glsl_300es'], # 'hlsl': ['-profile', 'sm_5_0'], # 'metal': ['-capability', 'metallib'], # 'wgsl': ['-profile', 'wgsl'] } def need_recompile(input_file: Path, output_path: Path) -> bool: """检查是否需要重新编译""" # 着色器输出文件名的格式为input.file.stem + '.' + backend + '.h' # 所以需要检查是否有input.file.stem + '.*.h'文件存在 for backend in BACKENDS: output_file = output_path / f"{input_file.stem}.{backend}.h" if not output_file.exists(): continue else: input_time = input_file.stat().st_mtime output_time = output_file.stat().st_mtime return input_time > output_time return True def find_shader_files(input_dir: Path, extensions: List[str]) -> Iterator[Path]: """递归查找指定目录下的着色器文件""" for file_path in Path(input_dir).rglob('*'): if file_path.suffix in extensions: yield file_path def create_compiler_command( input_file: Path, output_file: Path, target_type: str, args: argparse.Namespace ) -> List[str]: """生成着色器编译命令""" cmd = [args.shdc, str(input_file), "-o", str(output_file), "-t", target_type, ] if args.debug: cmd.append('-d') if target_type in TARGET_PROFILES: cmd.extend(TARGET_PROFILES[target_type]) return cmd def compile_shader( input_file: Path, target_types: List[Tuple[str, bool]], args: argparse.Namespace ) -> bool: """编译单个着色器文件并读取二进制数据""" try: base = input_file.stem output_dir = input_file.parent success = True for target_type, enabled in target_types: if not enabled: continue if not need_recompile(input_file, output_dir): print(f"**跳过**: {input_file} 已经是最新的") continue cmd = create_compiler_command(input_file, output_dir, target_type, args) try: # 修改这里: 明确指定编码为utf-8,并添加errors参数处理无法解码的字符 subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8', errors='replace') print(f"**成功**: 编译 {input_file}") except subprocess.CalledProcessError as e: print(f"**错误**: 编译 {input_file} 失败") print(e.stderr) success = False continue return success except Exception as e: print(f"**错误**: 处理 {input_file} 时发生异常: {e}") return False def main(): parser = argparse.ArgumentParser(description='编译slang着色器为C++头文件') parser.add_argument('--shdc', help='着色器编译器路径') parser.add_argument('--shader_list', help='着色器目录列表文件路径') parser.add_argument("--hlsl", action="store_true", help="编译HLSL着色器") parser.add_argument("--glsl", action="store_true", help="编译GLSL着色器") parser.add_argument("--essl", action="store_true", help="编译ESSL着色器") parser.add_argument("--metal", action="store_true", help="编译Metal着色器") parser.add_argument("--wgsl", action="store_true", help="编译WGSL着色器") parser.add_argument("--debug", action="store_true", help="编译调试版本") args = parser.parse_args() # 确定要编译的目标后端 target_types: List[Tuple[str, bool]] = [ ('glsl', args.glsl), ('essl', args.essl), ('hlsl', args.hlsl), ('metal', args.metal), ('wgsl', args.wgsl), ] # 如果没有指定任何后端,默认启用所有后端 if not any(enabled for _, enabled in target_types): target_types = [(backend, True) for backend, _ in target_types] shader_list = Path(args.shader_list or "shader_paths.txt") try: shader_paths = shader_list.read_text(encoding="utf-8").splitlines() except Exception as e: print(f"**错误**: 读取着色器列表文件 {shader_list} 失败: {e}") sys.exit(1) all_success = True for shader_path in shader_paths: shader_path = shader_path.strip() if not shader_path: continue for file in find_shader_files(Path(shader_path), ['.slang']): if not compile_shader(file, target_types, args): all_success = False if not all_success: sys.exit(1) if __name__ == "__main__": main()