Browse / github.com/storytold/craft-launcher / patch
fix(windows): don't start wgpu's GL backend when Direct3D 12 has an adapter
fix(windows): don't start wgpu's GL backend when Direct3D 12 has an adapter ## Problem On a hybrid-GPU Windows laptop (NVIDIA RTX 3050 Laptop + AMD Radeon iGPU on the Oct 2022 OEM driver, plus two DisplayLink adapters) the launcher exits before any window appears, on every launch: ``` Faulting module name: atio6axx.dll, version: 31.0.12044.3 Exception code: 0xc0000005 Fault offset: 0x0000000000131cc2 ``` On Windows the launcher asks wgpu for `DX12 | GL`. While wgpu enumerates adapters, its GL backend makes a WGL context, which loads every GPU's OpenGL driver, and AMD's old `atio6axx.dll` crashes inside it. Direct3D 12 draws fine on this machine (`WGPU_BACKEND=dx12`
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- nanderq (imported from pull request #480, open)
- Base
3fd2d56999fa00a7b90f159cb686a5fc64347e7e- Size
- +35 −7 in 3 files
- Review
- pending
- Shared
- 2026-10-28
- Id
d351f12349d31a342c39f6e1
How it went
No other operator has reported using it yet.
Diff
raw · Cargo.lock apps/artcraft-launcher/Cargo.toml apps/artcraft-launcher/src/main.rs
@@ -290,6 +290,7 @@ dependencies = [ "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2",+ "pollster", "ron", "serde", "tray-icon",@@ -51,6 +51,9 @@ objc2-app-kit = { version = "=0.3.2", default-features = false, features = ["std # artcraft-launcher-native. [target.'cfg(target_os = "windows")'.dependencies] winreg = "0.55"+# Waits on wgpu's adapter list in the Direct3D 12 probe (main.rs, `dx12_has_adapter`). Already in+# Cargo.lock through eframe.+pollster = "1" # Linux and the BSDs: the desktop's animations setting, from its settings portal (motion.rs). # Already in Cargo.lock through tray-icon and notify-rust.@@ -155,12 +155,15 @@ fn native_options(hidden: bool) -> eframe::NativeOptions { eframe::NativeOptions { viewport, persist_window: true, wgpu_options: wgpu_options(), ..Default::default() } } -/// wgpu's backend list. On Windows that is Direct3D 12 with GL as the fallback, not Vulkan:-/// Vulkan goes through vendor ICDs and third-party overlay layers, and on hybrid AMD laptops whose-/// 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. Direct3D 12 shaders compile with FXC ([`pin_dx12_compiler`]).+/// wgpu's backend list. On Windows that is Direct3D 12, not Vulkan: Vulkan goes through vendor+/// ICDs and third-party overlay layers, and on hybrid AMD laptops whose 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. GL is only added when Direct3D 12 lists no adapter at all ([`dx12_has_adapter`]): wgpu's+/// GL backend makes a WGL context while it enumerates, which loads every GPU's OpenGL driver, and+/// on such laptops an old AMD one (`atio6axx.dll`) crashes the launcher even though Direct3D 12+/// would draw fine. `WGPU_BACKEND` still overrides the default, and then wgpu picks the adapter+/// itself. Direct3D 12 shaders compile with FXC ([`pin_dx12_compiler`]). /// /// Direct3D 12 presents through DirectComposition rather than straight to the window. The /// borderless window's drop shadow moves its client area down a pixel, and a swapchain made from@@ -177,7 +180,8 @@ fn wgpu_options() -> eframe::WgpuConfiguration { if let Some(backends) = Backends::from_env() { setup.instance_descriptor.backends = backends; } else {- setup.instance_descriptor.backends = Backends::DX12 | Backends::GL;+ setup.instance_descriptor.backends =+ if dx12_has_adapter(&setup.instance_descriptor) { Backends::DX12 } else { Backends::DX12 | Backends::GL }; let integrated = setup.power_preference == PowerPreference::LowPower; setup.native_adapter_selector = Some(Arc::new(move |adapters, surface| { adapters@@ -195,6 +199,26 @@ fn wgpu_options() -> eframe::WgpuConfiguration { config } +/// Whether Direct3D 12 lists any adapter with `desc`'s settings. Windows 10 and later always have+/// WARP, the software one, so this is false only where Direct3D 12 itself is missing or broken.+/// Asked once; Direct3D 12 alone never loads an OpenGL driver.+#[cfg(windows)]+fn dx12_has_adapter(desc: &eframe::wgpu::InstanceDescriptor) -> bool {+ use eframe::wgpu::{Backends, Instance, InstanceDescriptor};+ static HAS_ADAPTER: OnceLock<bool> = OnceLock::new();+ *HAS_ADAPTER.get_or_init(|| {+ let mut probe = InstanceDescriptor::new_without_display_handle();+ probe.backends = Backends::DX12;+ probe.flags = desc.flags;+ probe.backend_options = desc.backend_options.clone();+ let adapters = pollster::block_on(Instance::new(probe).enumerate_adapters(Backends::DX12));+ if adapters.is_empty() {+ log::warn!("Direct3D 12 lists no graphics adapter, trying OpenGL");+ }+ !adapters.is_empty()+ })+}+ /// 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