Files
mirage/tools/utils.py
2025-11-27 23:44:24 +08:00

154 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
工具函数模块
包含着色器文件发现、分组、元数据加载等辅助功能。
"""
from __future__ import annotations
import argparse
import logging
from pathlib import Path
from typing import Dict, List, Optional
from .constants import SHADER_EXTENSIONS
from .types import ShaderMetadata
LOG = logging.getLogger("glsl2spirv")
# 项目根目录
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUTPUT_DIR = ROOT / "build" / "generated" / "shaders"
# ============ Shader Discovery ============
def discover_shaders(directories: List[Path]) -> List[Path]:
"""递归扫描目录,发现所有.glsl着色器文件"""
shaders: List[Path] = []
for base_dir in directories:
if not base_dir.exists():
LOG.warning("Shader directory %s does not exist (skipping).", base_dir)
continue
for glsl_file in base_dir.rglob("*.glsl"):
if glsl_file.name.startswith("lib_"):
if LOG.isEnabledFor(logging.DEBUG):
LOG.debug("Skipping library file: %s", glsl_file)
continue
shaders.append(glsl_file.resolve())
return sorted(shaders)
def group_shader_files(shader_files: List[Path]) -> Dict[str, List[Path]]:
"""按着色器组进行分组(同一个基础名称的所有阶段)"""
groups: Dict[str, List[Path]] = {}
for shader_file in shader_files:
for ext, stage_name in SHADER_EXTENSIONS.items():
if shader_file.name.endswith(f"{ext}.glsl"):
base_name = shader_file.name[: -len(f"{ext}.glsl")]
if base_name not in groups:
groups[base_name] = []
groups[base_name].append(shader_file)
break
return groups
# ============ Metadata Loading ============
def load_shader_metadata(shader_path: Path, args: argparse.Namespace) -> ShaderMetadata:
"""从文件名推导着色器配置元数据"""
stem = shader_path.stem
# 检查是否存在JSON文件并警告
json_candidates = [
shader_path.with_suffix(".glsl.json"),
shader_path.with_suffix(".json"),
]
for candidate in json_candidates:
if candidate.exists():
LOG.warning("JSON metadata file %s found but ignored. Using SPIR-V reflection.", candidate)
break
# 从文件名推断shader_type
shader_type = "compute"
for ext in SHADER_EXTENSIONS:
if shader_path.name.endswith(f"{ext}.glsl"):
if ext in ('.vert', '.frag'):
shader_type = "graphics"
else:
shader_type = "compute"
break
# 默认入口点
if shader_type == "compute":
entry_points = {"compute": "main"}
else:
entry_points = {"vertex": "main", "fragment": "main"}
# 输出头文件名
base_stem = stem
for ext in SHADER_EXTENSIONS:
suffix = ext[1:] # 移除点
if base_stem.endswith(f".{suffix}"):
base_stem = base_stem[:-len(f".{suffix}")]
break
if shader_type == "compute":
ext_suffix = "_comp"
else:
ext_suffix = "_vert_frag"
output_header = f"{base_stem}{ext_suffix}.h"
namespace = "mirage::shaders"
# include路径默认为shader所在目录的lib子目录
include_paths: List[Path] = []
lib_path = shader_path.parent / "lib"
if lib_path.exists():
include_paths.append(lib_path)
parent_lib = shader_path.parent.parent / "lib"
if parent_lib.exists():
include_paths.append(parent_lib)
return ShaderMetadata(
name=base_stem,
shader_type=shader_type,
entry_points=entry_points,
output_header=output_header,
namespace=namespace,
bindings=[], # 将从SPIR-V反射填充
include_paths=include_paths,
defines={},
)
# ============ Directory List Reading ============
def read_directory_list(list_file: Optional[str]) -> List[Path]:
"""读取着色器目录列表文件"""
if not list_file:
return []
path = Path(list_file)
if not path.exists():
LOG.warning("Shader directory list %s does not exist", path)
return []
directories: List[Path] = []
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
dir_path = Path(line)
if dir_path.exists():
directories.append(dir_path)
else:
LOG.warning("Shader directory %s does not exist (skipping).", dir_path)
return directories