mirror of
https://github.com/ScrelliCopter/NeHe-SDL_GPU.git
synced 2025-06-19 21:49:17 +10:00
187 lines
6.4 KiB
Python
Executable File
187 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
|
SPDX-License-Identifier: Zlib
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from collections import namedtuple
|
|
from pathlib import Path
|
|
import subprocess
|
|
import platform
|
|
from typing import Iterable
|
|
|
|
|
|
def compile_metal_shaders(
|
|
sources: list[str | Path], library: str | Path,
|
|
cflags: list[str] = None, sdk="macosx", debug: bool = False, cwd: Path | None = None) -> None:
|
|
"""Build a Metal shader library from a list of Metal shaders
|
|
|
|
:param sources: List of Metal shader source paths
|
|
:param library: Path to the Metal library to build
|
|
:param cflags: Optional list of additional compiler parameters
|
|
:param sdk: Name of the Xcode platform SDK to use (default: "macosx"),
|
|
can be "macosx", "iphoneos", "iphonesimulator", "appletvos", or "appletvsimulator"
|
|
:param debug: Generate a symbol file that can be used to debug the shader library
|
|
:param cwd: Optionally set the current working directory for the compiler
|
|
"""
|
|
if cflags is None:
|
|
cflags = []
|
|
|
|
def xcrun_find_program(name: str):
|
|
return subprocess.run(["xcrun", "-sdk", sdk, "-f", name],
|
|
capture_output=True, check=True).stdout.decode("utf-8").strip()
|
|
|
|
# Find Metal compilers
|
|
metal = xcrun_find_program("metal")
|
|
if debug:
|
|
metal_dsymutil = xcrun_find_program("metal-dsymutil")
|
|
else:
|
|
metallib = xcrun_find_program("metallib")
|
|
|
|
# Compile each source to an AIR (Apple Intermediate Representation)
|
|
cflags = cflags + ["-frecord-sources"]
|
|
air_objects = [f"{str(s).removesuffix('.metal')}.air" for s in sources]
|
|
for src, obj in zip(sources, air_objects):
|
|
subprocess.run([metal, *cflags, "-c", src, "-o", obj], cwd=cwd, check=True)
|
|
|
|
try:
|
|
# Build the Metal library
|
|
if debug:
|
|
subprocess.run([metal, "-frecord-sources", "-o", library, *air_objects], cwd=cwd, check=True)
|
|
subprocess.run([metal_dsymutil, "-flat", "-remove-source", library], cwd=cwd, check=True)
|
|
else:
|
|
subprocess.run([metallib, "-o", library, *air_objects], cwd=cwd, check=True)
|
|
finally:
|
|
# Clean up AIR objects
|
|
for obj in air_objects:
|
|
cwd.joinpath(obj).unlink()
|
|
|
|
|
|
Shader = namedtuple("Shader", ["source", "type", "output"])
|
|
|
|
|
|
def shaders_suffixes(shaders: list[Shader],
|
|
in_suffix: str | None, out_suffix: str | None) -> Iterable[Shader]:
|
|
"""Add file extensions to the source and outputs of a list of shaders
|
|
|
|
:param shaders: The list of Shader tuples
|
|
:param in_suffix: Optional file extension to append to source
|
|
:param out_suffix: Optional file extension to append to output
|
|
:return: Generator with the modified shaders
|
|
"""
|
|
for s in shaders:
|
|
yield Shader(
|
|
f"{s.source}.{in_suffix}" if in_suffix else s.source,
|
|
s.type,
|
|
f"{s.output}.{out_suffix}" if out_suffix else s.output)
|
|
|
|
|
|
def compile_spirv_shaders(shaders: Iterable[Shader],
|
|
flags: list[str] = None, glslang: str | None=None, cwd: Path | None = None) -> None:
|
|
"""Compile shaders to SPIR-V using glslang
|
|
|
|
:param shaders: The list of shader source paths to compile
|
|
:param flags: List of additional flags to pass to glslang
|
|
:param glslang: Optional path to glslang executable, if `None` defaults to "glslang"
|
|
:param cwd: Optionally set the current working directory for the compiler
|
|
"""
|
|
if glslang is None:
|
|
glslang = "glslang"
|
|
if flags is None:
|
|
flags = []
|
|
|
|
for shader in shaders:
|
|
sflags = [*flags, "-V", "-S", shader.type, "-o", shader.output, shader.source]
|
|
subprocess.run([glslang, *sflags], cwd=cwd, check=True)
|
|
|
|
|
|
def compile_dxil_shaders(shaders: Iterable[Shader], dxc: str | None = None, cwd: Path | None = None) -> None:
|
|
"""Compile HLSL shaders to DXIL using DXC
|
|
|
|
:param shaders: The list of shader source paths to compile
|
|
:param dxc: Optional path to dxc excutable, if `None` defaults to "dxc"
|
|
:param cwd: Optionally set the current working directory for the compiler
|
|
"""
|
|
if dxc is None:
|
|
dxc = "dxc"
|
|
for shader in shaders:
|
|
entry, shader_type = {
|
|
"vert": ("VertexMain", "vs_6_0"),
|
|
"frag": ("FragmentMain", "ps_6_0") }[shader.type]
|
|
cflags = ["-E", entry, "-T", shader_type]
|
|
subprocess.run([dxc, *cflags, "-Fo", shader.output, shader.source], cwd=cwd, check=True)
|
|
|
|
|
|
def compile_dxbc_shaders(shaders: Iterable[Shader], cwd: Path | None = None) -> None:
|
|
"""Compile HLSL shaders to DXBC using FXC
|
|
|
|
:param shaders: The list of shader source paths to compile
|
|
:param cwd: Optionally set the current working directory for the compiler
|
|
"""
|
|
for shader in shaders:
|
|
entry, shader_type = {
|
|
"vert": ("VertexMain", "vs_5_1"),
|
|
"frag": ("FragmentMain", "ps_5_1") }[shader.type]
|
|
cflags = ["/E", entry, "/T", shader_type]
|
|
subprocess.run(["fxc", *cflags, "/Fo", shader.output, shader.source], cwd=cwd, check=True)
|
|
|
|
|
|
def compile_shaders() -> None:
|
|
build_spirv = False
|
|
build_metal = True
|
|
build_dxil = False
|
|
build_dxbc = False
|
|
lessons = [ "lesson2", "lesson3", "lesson6", "lesson7", "lesson9" ]
|
|
src_dir = Path("src/shaders")
|
|
dest_dir = Path("data/shaders")
|
|
|
|
system = platform.system()
|
|
def add_shaders() -> Iterable[Shader]:
|
|
for lesson in lessons:
|
|
yield Shader(src_dir / f"{lesson}.vertex", "vert", dest_dir / f"{lesson}.vertex")
|
|
yield Shader(src_dir / f"{lesson}.fragment", "frag", dest_dir / f"{lesson}.fragment")
|
|
shaders = list(add_shaders())
|
|
|
|
root = Path(sys.argv[0]).resolve().parent.parent
|
|
root.joinpath(dest_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
# Try to find cross-platform shader compilers
|
|
glslang = shutil.which("glslang")
|
|
dxc = shutil.which("dxc", path=f"/opt/dxc/bin:{os.environ.get('PATH', os.defpath)}")
|
|
|
|
if build_spirv:
|
|
# Build SPIR-V shaders for Vulkan
|
|
compile_spirv_shaders(shaders_suffixes(shaders, "glsl", "spv"),
|
|
flags=["--quiet"], glslang=glslang, cwd=root)
|
|
|
|
if build_metal:
|
|
# Build Metal shaders on macOS
|
|
if system == "Darwin":
|
|
compile_platform = "macos"
|
|
sdk_platform = "macosx"
|
|
min_version = "10.11"
|
|
for lesson in lessons:
|
|
compile_metal_shaders(
|
|
sources=[src_dir / f"{lesson}.metal"],
|
|
library=dest_dir / f"{lesson}.metallib",
|
|
cflags=["-Wall", "-O3",
|
|
f"-std={compile_platform}-metal1.1",
|
|
f"-m{sdk_platform}-version-min={min_version}"],
|
|
sdk=sdk_platform,
|
|
cwd=root)
|
|
|
|
if build_dxil:
|
|
# Build HLSL shaders on Windows or when DXC is available
|
|
if system == "Windows" or dxc is not None:
|
|
compile_dxil_shaders(shaders_suffixes(shaders, "hlsl", "dxb"), dxc=dxc, cwd=root)
|
|
if build_dxbc:
|
|
if system == "Windows": # FXC is only available thru the Windows SDK
|
|
compile_dxbc_shaders(shaders_suffixes(shaders, "hlsl", "fxb"), cwd=root)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
compile_shaders()
|