yum/demomo

A graphics engine built on wgpu.

git clone https://git.yummers.dev/yum/demomo

yumWire up Camera50fe57a

master
4.7 KiB145 linesraw
1use anyhow::Context;
2use std::fs;
3use std::fs::File;
4use std::fs::OpenOptions;
5use std::io::Write;
6use std::path::{Path, PathBuf};
7use std::process::{Command, Output};
8
9// Tools for use in build.rs.
10// Example usage:
11//   fn main() -> Result<(), Box<dyn std::error::Error>> {
12//     // Compile all .slang files in shaders/ directory.
13//     build_lib::compile_shaders("shaders")?;
14//     Ok(())
15//   }
16
17fn say(message: &str) {
18    let file = OpenOptions::new().write(true).open("/dev/tty");
19    if file.is_ok() {
20        let _ = writeln!(file.unwrap(), "gpu_fft: {message}");
21    }
22}
23
24fn run_cmd(cmd: &str, dir: &Path) -> anyhow::Result<Output> {
25    println!("cargo::warning=Running {cmd}...");
26    let output = Command::new("sh")
27        .arg("-c")
28        .arg(cmd)
29        .current_dir(dir)
30        .output()
31        .with_context(|| format!("failed to execute `{cmd}`"))?;
32
33    if !output.status.success() {
34        anyhow::bail!(
35            "Command failed `{cmd}`:\nstdout:{}\nstderr:\n{}",
36            String::from_utf8_lossy(&output.stdout),
37            String::from_utf8_lossy(&output.stderr)
38        );
39    }
40
41    Ok(output)
42}
43
44fn ensure_slang(slang_dir: &Path) -> Result<PathBuf, anyhow::Error> {
45    let sentinel = slang_dir.join(".did_build");
46    let slangc = slang_dir.join("build/RelWithDebInfo/bin/slangc");
47    if sentinel.exists() {
48        // Already built - short-circuit.
49        return Ok(slangc);
50    }
51
52    say(concat!(
53        "The slang compiler is not present, or is missing the\n",
54        "sentinel file. We need the slang compiler in order to translate\n",
55        ".slang shaders into .wgsl, which wgpu (webGPU) uses. We're going to\n",
56        "acquire the slang compiler, then build it. This will take a few\n",
57        "minutes and use a lot of CPU. This only needs to run once."
58    ));
59
60    // Assume we're in some weird intermediate state and start fresh.
61    if slang_dir.exists() {
62        say("Removing old slang compiler...");
63        fs::remove_dir_all(slang_dir)?;
64    }
65
66    say("cargo::warning=Acquiring slang compiler...");
67    let clone_ret = Command::new("git")
68        .args([
69            "clone",
70            "--recursive",
71            "https://github.com/shader-slang/slang",
72        ])
73        .arg(slang_dir)
74        .status()?;
75    anyhow::ensure!(clone_ret.success(), "Failed to clone slang");
76    run_cmd("mkdir build", slang_dir)?;
77
78    say("Building slang compiler...");
79    run_cmd("cmake -B build --preset default", slang_dir)?;
80    run_cmd("cmake --build --preset releaseWithDebugInfo", slang_dir)?;
81    File::create(sentinel)?;
82    Ok(slangc)
83}
84
85// Convert slang shaders in shaders/ to wgsl. (I refuse to use the term "transpile" - it is
86// redundant and ignores the fact that all compilation is merely a translation from one language to
87// another.)
88fn compile_shader_files(
89    slangc: &PathBuf,
90    slang_dir: &Path,
91    engine_shader_dir: &Path,
92    wgsl_dir: &PathBuf,
93) -> Result<(), anyhow::Error> {
94    if Path::new(wgsl_dir).exists() {
95        fs::remove_dir_all(wgsl_dir)?;
96    }
97    fs::create_dir_all(wgsl_dir)
98        .with_context(|| format!("Failed to create output directory {wgsl_dir:?}"))?;
99
100    for entry in fs::read_dir(slang_dir)? {
101        let entry = entry?.path();
102        let Some(ext) = entry.extension() else {
103            // No extension.
104            continue;
105        };
106        if ext != "slang" {
107            continue;
108        }
109        let stem = entry
110            .file_stem()
111            .with_context(|| format!("Failed to get file stem for {}", entry.display()))?;
112        let new_path = Path::new(wgsl_dir).join(stem).with_extension("wgsl");
113        let slang_dir = entry.parent().ok_or(anyhow::anyhow!(
114            "Failed to get parent directory for {}",
115            entry.display()
116        ))?;
117        let compile_ret = Command::new(slangc)
118            .arg(&entry)
119            .arg("-o")
120            .arg(&new_path)
121            .arg("-no-mangle")
122            .arg("-I")
123            .arg(slang_dir)
124            .arg("-I")
125            .arg(engine_shader_dir)
126            .status()?;
127        anyhow::ensure!(compile_ret.success(), "Failed to compile shader");
128    }
129
130    Ok(())
131}
132
133pub fn compile_shaders(shader_dir: impl AsRef<Path>) -> anyhow::Result<()> {
134    let shader_dir = shader_dir.as_ref();
135    // Resolve against demomo, even when called from another crate's build script.
136    let engine_shader_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("shaders");
137    let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
138    let slangc = ensure_slang(&out.join("slang"))?;
139
140    println!("cargo:rerun-if-changed={}", shader_dir.display());
141    println!("cargo:rerun-if-changed={}", engine_shader_dir.display());
142    compile_shader_files(&slangc, shader_dir, &engine_shader_dir, &out.join("shaders"))?;
143
144    Ok(())
145}