From 4f144a2147817fc435229fc76f955141185f5f3c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Jul 2026 10:23:27 +0530 Subject: [PATCH] Automatically validate GLSL shaders after build --- kitty/shaders/slang.py | 6 ++- kitty/shaders/validate_shaders.py | 66 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100755 kitty/shaders/validate_shaders.py diff --git a/kitty/shaders/slang.py b/kitty/shaders/slang.py index 6463612b0..d326feff7 100644 --- a/kitty/shaders/slang.py +++ b/kitty/shaders/slang.py @@ -291,7 +291,8 @@ def commands_to_compile_to_glsl(sources: dict[str, SlangFile], build_dir: str, d extra_cmd = ['-line-directive-mode', 'none', '-target', 'glsl', '-profile', 'glsl_330'] for ep in sfile.entry_points: for sp in sfile.specializations: - dest = f'{base_dest}.{ep.stage.name}.glsl' + v = {Stage.vertex: 'vert', Stage.fragment: 'frag'}[ep.stage] + dest = f'{base_dest}.{v}.glsl' c = list(cmd) if sp.name: dest = f'{base_dest}{sp.filename_insert}.{ep.stage.name}.glsl' @@ -461,6 +462,9 @@ def main() -> None: needed.append(Command(desc, cmd, lambda: True)) parallel_run(needed) compile_builtin_shaders(sys.argv[-2], sys.argv[-1], prun) + if shutil.which('glslangValidator'): + from kitty.shaders.validate_shaders import validate_glsl + validate_glsl('shaders') def test_slang_build() -> None: diff --git a/kitty/shaders/validate_shaders.py b/kitty/shaders/validate_shaders.py new file mode 100755 index 000000000..a2c510543 --- /dev/null +++ b/kitty/shaders/validate_shaders.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +import subprocess +import sys +from pathlib import Path + + +def validate_glsl(directory_path: str = '.', verbose: bool = False) -> None: + ''' + Validates all GLSL shaders in the specified directory with names matching + name.vert.glsl or name.frag.glsl using glslangValidator. + ''' + target_dir = Path(directory_path) + + if not target_dir.is_dir(): + raise SystemExit(f"Error: Directory '{directory_path}' does not exist.") + + # Map the custom extensions to the required glslangValidator stage strings + stage_mapping = { + '.vert.glsl': 'vert', + '.frag.glsl': 'frag' + } + + # Find all files matching the patterns + shader_files: list[Path] = [] + for ext in stage_mapping.keys(): + shader_files.extend(target_dir.glob(f'*{ext}')) + + if not shader_files: + if verbose: + print(f"No matching shaders (*.vert.glsl or *.frag.glsl) found in '{target_dir}'.") + return + + error_count = 0 + print(f"Scanning directory: {target_dir.resolve()}\n" + "-" * 50) + + # Process each shader file + for file_path in sorted(shader_files): + # Identify extension matching suffix + matched_ext = next(ext for ext in stage_mapping if file_path.name.endswith(ext)) + stage = stage_mapping[matched_ext] + if verbose: + print(f'Validating: {file_path.name}') + result = subprocess.run(['glslangValidator', '-S', stage, str(file_path)],) + + # Check exit code + if result.returncode != 0: + error_count += 1 + print(f'❌ Failed: {file_path.name}', file=sys.stderr) + else: + if verbose: + print(f'✅ Passed: {file_path.name}') + + if verbose: + print('-' * 50) + + # Print execution summary + if error_count == 0: + if verbose: + print("Success: All shaders validated successfully!") + else: + raise SystemExit(f"Failure: {error_count} shader(s) failed validation.") + + +if __name__ == "__main__": + dir_to_scan = sys.argv[1] if len(sys.argv) > 1 else 'shaders' + validate_glsl(dir_to_scan, verbose=True)