Skip to main content

qualia_core_db/wgsl_forge/emit/
dxc.rs

1use crate::wgsl_forge::ForgeError;
2#[cfg(feature = "dxc")]
3use std::process::Command;
4
5/// Compiles HLSL source to SPIR-V bytecodes using the DirectXShaderCompiler (DXC).
6#[cfg(feature = "dxc")]
7pub fn compile_hlsl_to_spirv(hlsl_source: &str, entry_point: &str) -> Result<Vec<u8>, ForgeError> {
8    // Use temp path strings (not NamedTempFile) for both input and output —
9    // NamedTempFile holds exclusive locks on Windows that prevent DXC from
10    // accessing the files.
11    let temp_dir = std::env::temp_dir();
12    let in_path = temp_dir.join(format!(
13        "qualia_dxc_in_{}_{}.hlsl",
14        std::process::id(),
15        entry_point
16    ));
17    let out_path = temp_dir.join(format!(
18        "qualia_dxc_out_{}_{}.spv",
19        std::process::id(),
20        entry_point
21    ));
22
23    std::fs::write(&in_path, hlsl_source)
24        .map_err(|e| ForgeError::Emission(format!("Failed to write HLSL temp file: {:?}", e)))?;
25
26    let in_path_str = in_path.to_str().unwrap();
27    let out_path_str = out_path.to_str().unwrap();
28
29    // Resolve DXC CLI. Priority:
30    //   1. QUALIA_DXC_CLI_PATH — explicit path to dxc.exe (CLI tool, not the DLL)
31    //   2. Vendored dxc.exe beside the current executable (build.rs stages dxcompiler.dll
32    //      there; dxc.exe lives in the same vendor/dxc/bin/<arch>/ directory)
33    //   3. "dxc" on PATH
34    //
35    // NOTE: QUALIA_DXC_PATH is intentionally NOT used here — that env var points
36    // to dxcompiler.dll (the dynamic library wgpu loads for DX12 compilation),
37    // not the dxc.exe CLI. Using it would try to execute a DLL as a program.
38    let dxc_path = resolve_dxc_cli_path();
39
40    // Note: -fspv-flatten-composite-loads is omitted because the vendored DXC
41    // build doesn't support it. It's an optional optimization.
42    let output = Command::new(&dxc_path)
43        .arg("-spirv")
44        .arg("-fspv-target-env=vulkan1.1")
45        .arg("-fvk-use-dx-layout")
46        .arg("-O3")
47        .arg("-T")
48        .arg("cs_6_0")
49        .arg("-E")
50        .arg(entry_point)
51        .arg(in_path_str)
52        .arg("-Fo")
53        .arg(out_path_str)
54        .output()
55        .map_err(|e| {
56            ForgeError::Emission(format!(
57                "Failed to execute DXC CLI at {}: {:?}",
58                dxc_path, e
59            ))
60        })?;
61
62    if !output.status.success() {
63        let _ = std::fs::remove_file(&in_path);
64        let _ = std::fs::remove_file(&out_path);
65        let err_str = String::from_utf8_lossy(&output.stderr);
66        let out_str = String::from_utf8_lossy(&output.stdout);
67        return Err(ForgeError::Emission(format!(
68            "DXC Compilation Failed:\nStdout: {}\nStderr: {}",
69            out_str, err_str
70        )));
71    }
72
73    let spirv_blob = std::fs::read(&out_path)
74        .map_err(|e| ForgeError::Emission(format!("Failed to read SPIRV output: {:?}", e)))?;
75    // Clean up temp files.
76    let _ = std::fs::remove_file(&in_path);
77    let _ = std::fs::remove_file(&out_path);
78    Ok(spirv_blob)
79}
80
81/// Resolve the DXC CLI executable path.
82///
83/// Uses `QUALIA_DXC_CLI_PATH` if set, otherwise tries to find `dxc.exe` beside
84/// the current executable (where `build.rs` stages vendored DXC DLLs), falling
85/// back to `dxc` on PATH. Does NOT use `QUALIA_DXC_PATH` (that points to
86/// `dxcompiler.dll` for wgpu's DX12 backend — a different file).
87#[cfg(feature = "dxc")]
88fn resolve_dxc_cli_path() -> String {
89    if let Ok(p) = std::env::var("QUALIA_DXC_CLI_PATH") {
90        if !p.trim().is_empty() {
91            return p;
92        }
93    }
94    // Try vendored dxc.exe beside the executable.
95    if let Ok(exe) = std::env::current_exe() {
96        if let Some(dir) = exe.parent() {
97            let dxc_exe = dir.join("dxc.exe");
98            if dxc_exe.exists() {
99                return dxc_exe.to_string_lossy().into_owned();
100            }
101        }
102    }
103    "dxc".to_string()
104}
105
106/// Fallback when DXC feature is disabled.
107#[cfg(not(feature = "dxc"))]
108pub fn compile_hlsl_to_spirv(
109    _hlsl_source: &str,
110    _entry_point: &str,
111) -> Result<Vec<u8>, ForgeError> {
112    Err(ForgeError::Emission(
113        "HLSL compilation requires the 'dxc' feature".to_string(),
114    ))
115}