Windows: compile DX12 shaders with FXC (#173)

wgpu's default DX12 compiler (Auto) loads whichever dxcompiler.dll the DLL
search path finds. The launcher ships none, and without a matching dxil.dll
the unsigned-DXIL warning fails wgpu's indirect-validation pipeline, the
device is lost and the launcher exits before the first frame. Pin the
compiler to FXC, which is part of Windows; WGPU_DX12_COMPILER still
overrides it. GL stays in the Windows backends: it is the deliberate
fallback for machines whose D3D12 adapters can't present to the window.

Fixes #126

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Fabio Lussi
2026-10-12 07:56:24 +02:00
committed by GitHub
parent 41ded15851
commit 0c3e639b4d
+37 -2
View File
@@ -148,11 +148,12 @@ fn native_options(hidden: bool) -> eframe::NativeOptions {
/// integrated and discrete GPUs run different driver packages (common on OEM machines, where the
/// Adrenalin installer leaves the integrated GPU on the OEM driver) it crashes inside
/// `amdvlk64.dll` before the first frame. `WGPU_BACKEND` still overrides the default, and then
/// wgpu picks the adapter itself.
/// wgpu picks the adapter itself. Direct3D 12 shaders compile with FXC ([`pin_dx12_compiler`]).
#[cfg(windows)]
fn wgpu_options() -> eframe::WgpuConfiguration {
use eframe::wgpu::{Backends, PowerPreference};
use eframe::wgpu::{Backends, Dx12Compiler, PowerPreference};
let mut config = eframe::WgpuConfiguration::default();
pin_dx12_compiler(&mut config, Dx12Compiler::from_env());
if let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &mut config.wgpu_setup {
if let Some(backends) = Backends::from_env() {
setup.instance_descriptor.backends = backends;
@@ -175,6 +176,19 @@ fn wgpu_options() -> eframe::WgpuConfiguration {
config
}
/// Compiles Direct3D 12 shaders with FXC (`d3dcompiler_47.dll`, part of Windows) unless
/// `env_choice` (normally `Dx12Compiler::from_env()`, i.e. `WGPU_DX12_COMPILER`) names another.
/// wgpu's default (`Auto`) loads whichever `dxcompiler.dll` the DLL search path finds. The launcher
/// ships none, and without a matching `dxil.dll` DXC warns that the DXIL is unsigned; wgpu takes
/// the warning for a compile error, its indirect-validation pipeline fails, the device is lost
/// and the launcher exits before the first frame (#126).
#[cfg(any(windows, test))]
fn pin_dx12_compiler(config: &mut eframe::WgpuConfiguration, env_choice: Option<eframe::wgpu::Dx12Compiler>) {
if let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &mut config.wgpu_setup {
setup.instance_descriptor.backend_options.dx12.shader_compiler = env_choice.unwrap_or(eframe::wgpu::Dx12Compiler::Fxc);
}
}
/// wgpu's own adapter order (by device type, then enumeration order), except that any Direct3D 12
/// adapter, the WARP software one included, comes before GL. wgpu ranks WARP last, so on GPUs with
/// no D3D12 driver (Intel HD 4000, Radeon TeraScale) it picked the GL one, whose legacy driver
@@ -896,6 +910,27 @@ mod tests {
assert!(!shell.launcher.closed_to_tray);
}
#[test]
fn dx12_shaders_compile_with_fxc_unless_asked() {
use eframe::egui_wgpu::WgpuSetup;
use eframe::wgpu::Dx12Compiler;
fn compiler(env_choice: Option<Dx12Compiler>) -> Dx12Compiler {
let mut config = eframe::WgpuConfiguration::default();
if let WgpuSetup::CreateNew(setup) = &mut config.wgpu_setup {
// wgpu's default: whichever dxcompiler.dll the DLL search path finds (#126).
setup.instance_descriptor.backend_options.dx12.shader_compiler = Dx12Compiler::Auto;
}
pin_dx12_compiler(&mut config, env_choice);
match config.wgpu_setup {
WgpuSetup::CreateNew(setup) => setup.instance_descriptor.backend_options.dx12.shader_compiler,
WgpuSetup::Existing(_) => panic!("the default configuration creates its own instance"),
}
}
assert!(matches!(compiler(None), Dx12Compiler::Fxc));
assert!(matches!(compiler(Some(Dx12Compiler::Auto)), Dx12Compiler::Auto));
assert!(matches!(compiler(Some(Dx12Compiler::default_dynamic_dxc())), Dx12Compiler::DynamicDxc { .. }));
}
#[test]
fn warp_beats_a_gl_gpu_but_not_a_d3d12_gpu() {
use eframe::wgpu::{Backend, DeviceType};