Files
2026-09-30 02:42:45 -07:00

93 lines
2.8 KiB
Python

import os
import shutil
import struct
import subprocess
# Mixed into DevCommand (bin/dev), which provides _project_root.
class ShadersCommand:
SHADER_EXTENSIONS = (".vert", ".frag", ".comp", ".geom", ".tesc", ".tese")
def shaders(self, *args, **kwargs):
shaders_dir = os.path.join(self._project_root(), "engine", "src", "shaders")
if not os.path.isdir(shaders_dir):
raise ValueError(f"Shaders directory not found: {shaders_dir}")
glslc = self._find_glslc()
shader_paths = sorted(
os.path.join(root, filename)
for root, _dirs, files in os.walk(shaders_dir)
for filename in files
if filename.endswith(self.SHADER_EXTENSIONS)
)
if not shader_paths:
print(f"No shaders found in {shaders_dir}")
return
for shader_path in shader_paths:
self._generate_shader_header(glslc, shader_path)
def _find_glslc(self):
glslc = shutil.which("glslc")
if glslc is None:
raise RuntimeError(
"glslc not found on PATH (install the Vulkan SDK)"
)
return glslc
def _generate_shader_header(self, glslc, shader_path):
relative_path = os.path.relpath(shader_path, self._project_root())
print(f"Compiling shader: {relative_path}")
result = subprocess.run(
[glslc, shader_path, "-o", "-"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if result.returncode != 0:
raise RuntimeError(
f"failed to compile {shader_path}: {result.stderr.decode()}"
)
spirv = result.stdout
if len(spirv) % 4 != 0:
raise RuntimeError(
f"invalid SPIR-V output for {shader_path} (size not a multiple of 4)"
)
word_count = len(spirv) // 4
words = struct.unpack(f"<{word_count}I", spirv)
filename = os.path.basename(shader_path)
variable_name = f"{filename.replace('.', '_')}_spv"
header_path = shader_path + ".spv.h"
body_lines = [
" " + ", ".join(f"0x{word:08x}" for word in words[i:i + 4]) + ","
for i in range(0, len(words), 4)
]
header = "\n".join([
"#pragma once",
"",
"#include <array>",
"#include <cstdint>",
"",
"namespace sophia::shaders {",
"",
f"inline constexpr std::array<uint32_t, {word_count}> {variable_name} = {{",
*body_lines,
"};",
"",
"} // namespace sophia::shaders",
"",
])
with open(header_path, "w") as f:
f.write(header)
print(f"Wrote {os.path.relpath(header_path, self._project_root())} "
f"({word_count * 4} bytes)")