Skip to main content

damascene_ash/
naga_compile.rs

1//! WGSL to SPIR-V compilation via `naga`.
2//!
3//! The dependency is pinned to the same major version as the other
4//! Damascene GPU backends so custom WGSL parses consistently across
5//! `wgpu`, `vulkano`, and `ash`.
6
7use naga::back::spv;
8use naga::front::wgsl;
9use naga::valid;
10
11/// Errors surfaced while compiling a WGSL shader to SPIR-V.
12#[derive(Debug)]
13pub enum CompileError {
14    Parse { name: String, message: String },
15    Validate { name: String, message: String },
16    SpirVWrite { name: String, message: String },
17}
18
19impl std::fmt::Display for CompileError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            CompileError::Parse { name, message } => {
23                write!(f, "WGSL parse error in `{name}`: {message}")
24            }
25            CompileError::Validate { name, message } => {
26                write!(f, "WGSL validation error in `{name}`: {message}")
27            }
28            CompileError::SpirVWrite { name, message } => {
29                write!(f, "SPIR-V write error in `{name}`: {message}")
30            }
31        }
32    }
33}
34
35impl std::error::Error for CompileError {}
36
37/// Compile a WGSL source string to SPIR-V words suitable for
38/// `ash::Device::create_shader_module`.
39pub fn wgsl_to_spirv(name: &str, source: &str) -> std::result::Result<Vec<u32>, CompileError> {
40    let module = wgsl::parse_str(source).map_err(|e| CompileError::Parse {
41        name: name.to_string(),
42        message: e.emit_to_string(source),
43    })?;
44
45    let info = valid::Validator::new(valid::ValidationFlags::all(), valid::Capabilities::all())
46        .validate(&module)
47        .map_err(|e| CompileError::Validate {
48            name: name.to_string(),
49            message: e.emit_to_string(source),
50        })?;
51
52    let options = spv::Options::default();
53    spv::write_vec(&module, &info, &options, None).map_err(|e| CompileError::SpirVWrite {
54        name: name.to_string(),
55        message: e.to_string(),
56    })
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use damascene_core::shader::stock_wgsl;
63
64    #[test]
65    fn rounded_rect_compiles() {
66        let words = wgsl_to_spirv("rounded_rect", stock_wgsl::ROUNDED_RECT)
67            .expect("rounded_rect WGSL should compile");
68        assert_eq!(words.first().copied(), Some(0x0723_0203));
69    }
70
71    #[test]
72    fn surface_compiles() {
73        let words =
74            wgsl_to_spirv("surface", stock_wgsl::SURFACE).expect("surface WGSL should compile");
75        assert_eq!(words.first().copied(), Some(0x0723_0203));
76    }
77
78    #[test]
79    fn parse_error_carries_name() {
80        let err =
81            wgsl_to_spirv("broken", "not valid wgsl @@@").expect_err("invalid WGSL must fail");
82        assert!(matches!(err, CompileError::Parse { .. }));
83        assert!(err.to_string().contains("broken"));
84    }
85}