diff --git a/README.md b/README.md index bc69f2df3..d273d490a 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,13 @@ 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. +**If the app quits right after its window appears with "Parent device is lost" in the log:** +WordCraft compiles DirectX 12 shaders with FXC, the compiler built into Windows, so it needs no +`dxcompiler.dll`/`dxil.dll` and ignores any copy another program put on `PATH` (#603). +`WGPU_DX12_COMPILER` (`fxc`, `dxc`, `auto`) overrides this; with `dxc`, a matching +`dxcompiler.dll` *and* `dxil.dll` must sit next to `wordcraft.exe`. `WGPU_BACKEND = "vulkan"` +also avoids DirectX 12 altogether. + **If the app is slow on an older Windows PC:** when DirectX 12 finds no graphics card with a driver for it (for example Intel 4th-generation graphics on Windows 11), it only offers Microsoft's software renderer, which draws every frame on the processor. In that case WordCraft uses OpenGL diff --git a/ROADMAP.md b/ROADMAP.md index 47be1a016..cf2e68306 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -131,6 +131,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | What landed | |---|---| +| 2026-10-11 | Windows: DirectX 12 shaders compile with FXC, built into Windows, so the app no longer quits at launch with "Parent device is lost" when no matching `dxcompiler.dll` + `dxil.dll` pair is around (#603) | | 2026-10-11 | Insert › SmartArt (#497): list, process, cycle, hierarchy, pyramid, radial, matrix and Venn graphics laid out by our own code, Text Pane, SmartArt Design tab (Add Shape, Promote, Demote, layouts, colours, Reset); saved as DOCX diagram parts with the drawing. Catalog 410/441 (93.0%) | | 2026-10-11 | Outline view (#493): Outlining tab, outline symbols and indents, collapse/expand, Show Level, first line only, plain text; catalog 428/450 → 441/463 (95.2%) | | 2026-10-11 | Vertical sections (#491): Layout › Text Direction turns the section (or the cell or text box the caret is in); lines run top to bottom, right to left, ideographs upright; `w:textDirection` in `w:sectPr` read and written | @@ -177,6 +178,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-11 | trivial | Progress log: Windows DX12 shaders compiled with FXC (#603) | | 2026-10-11 | trivial | Progress log: Insert › SmartArt (#497); catalog 429/451 → 437/458 (95.4%) | | 2026-10-11 | trivial | Progress log: Outline view (#493) | | 2026-10-11 | trivial | Progress log: vertical sections (#491) | diff --git a/apps/wordcraft/src/graphics.rs b/apps/wordcraft/src/graphics.rs index 85aa43303..3ef515797 100644 --- a/apps/wordcraft/src/graphics.rs +++ b/apps/wordcraft/src/graphics.rs @@ -1,9 +1,10 @@ -//! Windows graphics instance configuration, applied before wgpu can initialize any driver. +//! Windows graphics instance configuration (backends and the DirectX 12 shader compiler), applied +//! before wgpu can initialize any driver. use eframe::{ NativeOptions, egui_wgpu::WgpuSetup, - wgpu::{Backends, DeviceType}, + wgpu::{Backends, DeviceType, Dx12Compiler}, }; /// What the start-up probe learned about one DirectX 12 adapter. @@ -55,11 +56,38 @@ pub fn configure_backends(options: &mut NativeOptions, backends: Backends) { } } +/// The DirectX 12 shader compiler: FXC (`d3dcompiler_47.dll`, part of Windows) unless `env` +/// (normally `WGPU_DX12_COMPILER`, wgpu's own variable: `fxc`, `dxc`, `auto`…) names another. +/// +/// #603: wgpu's default (`Auto`) uses DXC, loading whichever `dxcompiler.dll` the DLL search path +/// finds first. WordCraft ships none, so that copy belongs to another program. Without a matching +/// `dxil.dll` beside it, DXC warns that the DXIL is unsigned; wgpu takes the warning for a compile +/// error, its own indirect-validation pipeline fails, the device is lost and eframe exits before +/// the first frame. FXC is always there and compiles everything egui's shaders need. +pub fn dx12_compiler(env: Option<&str>) -> Dx12Compiler { + let Some(value) = env else { return Dx12Compiler::Fxc }; + value.parse().unwrap_or_else(|e| { + log::warn!("graphics: WGPU_DX12_COMPILER={value:?} ignored ({e}), using FXC"); + Dx12Compiler::Fxc + }) +} + +/// Makes the wgpu instance eframe creates compile DirectX 12 shaders with `compiler`. +pub fn configure_dx12_compiler(options: &mut NativeOptions, compiler: Dx12Compiler) { + if let WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup { + create.instance_descriptor.backend_options.dx12.shader_compiler = compiler; + } +} + /// Chooses the backends ([`choose_backends`]) and applies them to `options`. Without a /// `backend_override` this first probes DirectX 12 with a short-lived instance of its own, which /// is dropped before eframe creates the real one. #[cfg(target_os = "windows")] pub fn configure(options: &mut NativeOptions, backend_override: Option) { + // First, so the probe's instance doesn't load a stray `dxcompiler.dll` either. + let compiler = dx12_compiler(std::env::var("WGPU_DX12_COMPILER").ok().as_deref()); + log::info!("graphics: DirectX 12 shader compiler {compiler:?}"); + configure_dx12_compiler(options, compiler); let backends = match backend_override { Some(backends) => { log::info!("graphics: WGPU_BACKEND override, backends {backends:?}"); @@ -87,7 +115,11 @@ fn probe_dx12(options: &NativeOptions) -> Vec { let WgpuSetup::CreateNew(create) = &options.wgpu_options.wgpu_setup else { return Vec::new(); }; - let instance = Instance::new(InstanceDescriptor { backends: Backends::DX12, ..InstanceDescriptor::new_without_display_handle() }); + let instance = Instance::new(InstanceDescriptor { + backends: Backends::DX12, + backend_options: create.instance_descriptor.backend_options.clone(), + ..InstanceDescriptor::new_without_display_handle() + }); pollster::block_on(instance.enumerate_adapters(Backends::DX12)) .iter() .map(|adapter| { @@ -139,6 +171,23 @@ mod tests { } } + /// #603: DirectX 12 shaders compile with FXC, never a `dxcompiler.dll` found on the search + /// path, unless `WGPU_DX12_COMPILER` asks for it. + #[test] + fn dx12_compiles_with_fxc_unless_asked() { + assert!(matches!(dx12_compiler(None), Dx12Compiler::Fxc)); + assert!(matches!(dx12_compiler(Some("bogus")), Dx12Compiler::Fxc)); + assert!(matches!(dx12_compiler(Some("FXC")), Dx12Compiler::Fxc)); + assert!(matches!(dx12_compiler(Some("dxc")), Dx12Compiler::DynamicDxc { .. })); + assert!(matches!(dx12_compiler(Some("auto")), Dx12Compiler::Auto)); + let mut options = NativeOptions::default(); + configure_dx12_compiler(&mut options, dx12_compiler(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 gl_when_dx12_has_no_usable_gpu() { // #316: no DirectX 12 driver, so DirectX 12 offers only WARP (or nothing) and lags. diff --git a/apps/wordcraft/src/main.rs b/apps/wordcraft/src/main.rs index d9fed95e3..8b39ae927 100644 --- a/apps/wordcraft/src/main.rs +++ b/apps/wordcraft/src/main.rs @@ -207,7 +207,8 @@ fn main() -> eframe::Result { options.viewport = options.viewport.with_icon(icon); } // Before eframe creates the wgpu instance: default Windows to DirectX 12 only, or OpenGL when - // DirectX 12 has no GPU driver (see graphics.rs). + // DirectX 12 has no GPU driver, and compile DirectX 12 shaders with FXC, which needs no + // DLL of ours (#603; see graphics.rs). #[cfg(target_os = "windows")] graphics::configure(&mut options, eframe::wgpu::Backends::from_env()); let restored = saved_window();