158 lines
5.2 KiB
Python
158 lines
5.2 KiB
Python
#!/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()
|