From e102ed36567a9d3418b9436e1923494d2e4d22da Mon Sep 17 00:00:00 2001 From: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:37:56 +0200 Subject: [PATCH] Windows: compile DX12 shaders with FXC wgpu's default DX12 compiler (Auto) loads whichever dxcompiler.dll the DLL search path finds. SoundCraft ships none, and without a matching dxil.dll the unsigned-DXIL warning fails wgpu's indirect-validation pipeline, the device is lost and the app exits before the first frame. Pin the compiler to FXC, which is part of Windows; WGPU_DX12_COMPILER still overrides it. Fixes #185 Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +++ apps/soundcraft/src/graphics.rs | 56 ++++++++++++++++++++++++++++++--- apps/soundcraft/src/main.rs | 4 +-- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 33d08f1..b297d75 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,10 @@ Remove-Item Env:WGPU_BACKEND # restore the default for later An explicit `gl` override can bring the driver crash back on affected systems. The macOS, Linux and web backend defaults are unchanged. +DirectX 12 shaders are compiled with FXC, which is part of Windows, so no `dxcompiler.dll` or +`dxil.dll` is needed ("Parent device is lost" at start, #185). `$env:WGPU_DX12_COMPILER = "dxc"` +(or `"auto"`) selects wgpu's DXC handling again for troubleshooting. + ### macOS | Build | File | Notes | diff --git a/apps/soundcraft/src/graphics.rs b/apps/soundcraft/src/graphics.rs index ccd0c45..2a932a6 100644 --- a/apps/soundcraft/src/graphics.rs +++ b/apps/soundcraft/src/graphics.rs @@ -1,10 +1,14 @@ //! Windows graphics instance configuration, applied before wgpu can initialize any driver. -use eframe::{NativeOptions, egui_wgpu::WgpuSetup, wgpu::Backends}; +use eframe::{ + NativeOptions, + egui_wgpu::WgpuSetup, + wgpu::{Backends, Dx12Compiler}, +}; -/// Restrict the wgpu instance to DirectX 12 unless `backend_override` (normally -/// `Backends::from_env()`, i.e. `WGPU_BACKEND`) names other backends. -pub fn configure(options: &mut NativeOptions, backend_override: Option) { +/// Default to DirectX 12 with FXC before creating the wgpu instance. Explicit +/// `WGPU_BACKEND` and `WGPU_DX12_COMPILER` choices, read by the caller, take precedence. +pub fn configure(options: &mut NativeOptions, backend_override: Option, compiler_override: Option) { if let WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup { // eframe's default includes GL. Creating that backend can crash inside AMD's // atio6axx.dll before adapter selection or any Rust error handling runs, so the @@ -12,6 +16,16 @@ pub fn configure(options: &mut NativeOptions, backend_override: Option // out of the instance avoids it. This matches the verified WGPU_BACKEND=dx12 // workaround without mutating process environment variables. create.instance_descriptor.backends = backend_override.unwrap_or(Backends::DX12); + // Auto loads whichever dxcompiler.dll the DLL search path finds; SoundCraft ships + // none, and without a matching dxil.dll the unsigned-DXIL warning fails wgpu's + // indirect-validation pipeline and loses the device before the first frame + // (issue #185). FXC uses the compiler that is part of Windows. + create.instance_descriptor.backend_options.dx12.shader_compiler = compiler_override.unwrap_or(Dx12Compiler::Fxc); + log::info!( + "Windows graphics: backends={:?}, DX12 compiler={:?}", + create.instance_descriptor.backends, + create.instance_descriptor.backend_options.dx12.shader_compiler + ); } } @@ -21,7 +35,7 @@ mod tests { fn configured_backends(backend_override: Option) -> Backends { let mut options = NativeOptions::default(); - configure(&mut options, backend_override); + configure(&mut options, backend_override, None); let WgpuSetup::CreateNew(create) = options.wgpu_options.wgpu_setup else { panic!("default native options must create a wgpu instance"); }; @@ -38,6 +52,38 @@ mod tests { assert_eq!(configured_backends(None), Backends::DX12); } + #[test] + fn windows_default_uses_fxc_instead_of_searching_for_dxc() { + let mut options = NativeOptions::default(); + if let WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup { + // Auto can find an unrelated DXC without its matching DXIL runtime (issue #185). + create.instance_descriptor.backend_options.dx12.shader_compiler = Dx12Compiler::Auto; + } + configure(&mut options, None, None); + let WgpuSetup::CreateNew(create) = options.wgpu_options.wgpu_setup else { + panic!("default native options must create a wgpu instance"); + }; + assert!(matches!(create.instance_descriptor.backend_options.dx12.shader_compiler, Dx12Compiler::Fxc)); + } + + #[test] + fn explicit_compiler_override_is_preserved() { + for compiler in [Dx12Compiler::Fxc, Dx12Compiler::Auto, Dx12Compiler::DynamicDxc { dxc_path: "custom/dxcompiler.dll".into() }] { + let mut options = NativeOptions::default(); + configure(&mut options, None, Some(compiler.clone())); + let WgpuSetup::CreateNew(create) = options.wgpu_options.wgpu_setup else { + panic!("default native options must create a wgpu instance"); + }; + match (compiler, create.instance_descriptor.backend_options.dx12.shader_compiler) { + (Dx12Compiler::Fxc, Dx12Compiler::Fxc) | (Dx12Compiler::Auto, Dx12Compiler::Auto) => {} + (Dx12Compiler::DynamicDxc { dxc_path: expected }, Dx12Compiler::DynamicDxc { dxc_path: actual }) => { + assert_eq!(actual, expected); + } + (expected, actual) => panic!("compiler override changed from {expected:?} to {actual:?}"), + } + } + } + #[test] fn explicit_backend_override_is_preserved() { for backends in [Backends::VULKAN, Backends::GL, Backends::DX12, Backends::VULKAN | Backends::DX12, Backends::empty()] { diff --git a/apps/soundcraft/src/main.rs b/apps/soundcraft/src/main.rs index a54e7e1..8004acf 100644 --- a/apps/soundcraft/src/main.rs +++ b/apps/soundcraft/src/main.rs @@ -225,9 +225,9 @@ fn main() -> eframe::Result { if let Some(icon) = app_icon() { options.viewport = options.viewport.with_icon(icon); } - // Before eframe creates the wgpu instance: default Windows to DirectX 12 only (see graphics.rs). + // Before eframe creates the wgpu instance: default Windows to DirectX 12 with FXC (see graphics.rs). #[cfg(target_os = "windows")] - graphics::configure(&mut options, eframe::wgpu::Backends::from_env()); + graphics::configure(&mut options, eframe::wgpu::Backends::from_env(), eframe::wgpu::Dx12Compiler::from_env()); eframe::run_native( "SoundCraft", options,