From 7d589c44565c59cedfef47fcd99ce934c2a890b4 Mon Sep 17 00:00:00 2001 From: Jeremy Yao Date: Tue, 13 Oct 2026 22:29:21 -0400 Subject: [PATCH] fix(desktop): use Direct3D 12 on Windows instead of Vulkan (#226) On Windows, eframe offered wgpu every backend and wgpu ranked Vulkan first. On a laptop with two AMD GPUs whose drivers are years apart, the Vulkan driver faults (0xc0000005 inside amdvlk64.dll) right after the first frame, so the app died as its window opened. An access violation in a driver thread is not something the app can catch. Offer wgpu Direct3D 12 on Windows, with OpenGL behind it for hardware without a Direct3D 12 driver. WGPU_BACKEND still names the backend on every system, and other systems keep eframe's defaults. Co-authored-by: Claude Opus 5.5 --- apps/figcraft/src/graphics.rs | 34 +++++++++++++++++++++++++++++ apps/figcraft/src/graphics_tests.rs | 32 +++++++++++++++++++++++++++ apps/figcraft/src/main.rs | 6 ++++- 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 apps/figcraft/src/graphics.rs create mode 100644 apps/figcraft/src/graphics_tests.rs diff --git a/apps/figcraft/src/graphics.rs b/apps/figcraft/src/graphics.rs new file mode 100644 index 0000000..ee7b74f --- /dev/null +++ b/apps/figcraft/src/graphics.rs @@ -0,0 +1,34 @@ +//! Which graphics API draws the window. +//! +//! eframe offers wgpu every backend it was built with, and between equal GPUs wgpu takes Vulkan +//! first, on Windows too. A Vulkan driver that faults there takes the app down as the window opens: +//! an access violation inside the vendor's driver, which no guard of ours can catch (seen on a laptop +//! with two AMD GPUs whose drivers were years apart). Direct3D 12 is part of Windows, so on Windows +//! wgpu is offered Direct3D 12, and OpenGL for hardware without a Direct3D 12 driver. +//! +//! `WGPU_BACKEND` (`dx12`, `vulkan`, `gl`, `metal`) still names the backend on every system, and +//! systems other than Windows keep eframe's defaults. + +use eframe::egui_wgpu::{WgpuConfiguration, WgpuSetup}; +use eframe::wgpu::Backends; + +/// Direct3D 12, then OpenGL: wgpu ranks a hardware GPU on either ahead of Direct3D's software one. +const WINDOWS: Backends = Backends::DX12.union(Backends::GL); + +/// The backends wgpu chooses from: the ones `WGPU_BACKEND` asks for (`requested`), else [`WINDOWS`] +/// on Windows, else eframe's `defaults`. +pub fn backends(requested: Option, windows: bool, defaults: Backends) -> Backends { + match requested { + Some(requested) => requested, + None if windows => WINDOWS, + None => defaults, + } +} + +/// Offer wgpu [`backends`] instead of eframe's defaults. +pub fn configure(options: &mut WgpuConfiguration) { + if let WgpuSetup::CreateNew(create) = &mut options.wgpu_setup { + let offered = &mut create.instance_descriptor.backends; + *offered = backends(Backends::from_env(), cfg!(windows), *offered); + } +} diff --git a/apps/figcraft/src/graphics_tests.rs b/apps/figcraft/src/graphics_tests.rs new file mode 100644 index 0000000..e103d32 --- /dev/null +++ b/apps/figcraft/src/graphics_tests.rs @@ -0,0 +1,32 @@ +//! The backends offered to wgpu: no Vulkan on Windows unless it is asked for (a faulting Vulkan +//! driver crashed the app as its window opened), and nothing changed anywhere else. + +use eframe::wgpu::Backends; + +use crate::graphics::backends; + +/// What eframe offers when `WGPU_BACKEND` isn't set. +const DEFAULTS: Backends = Backends::PRIMARY.union(Backends::GL); + +#[test] +fn windows_is_offered_direct3d_and_not_vulkan() { + let offered = backends(None, true, DEFAULTS); + assert!(offered.contains(Backends::DX12), "Direct3D 12 draws the window"); + assert!(offered.contains(Backends::GL), "OpenGL is there for hardware without a Direct3D 12 driver"); + assert!(!offered.contains(Backends::VULKAN), "a Vulkan driver that faults can't be picked"); +} + +#[test] +fn wgpu_backend_names_the_backend_on_every_system() { + for windows in [true, false] { + for asked in [Backends::VULKAN, Backends::DX12, Backends::GL, Backends::VULKAN | Backends::GL] { + assert_eq!(backends(Some(asked), windows, DEFAULTS), asked, "windows: {windows}"); + } + } +} + +#[test] +fn other_systems_keep_eframes_defaults() { + assert_eq!(backends(None, false, DEFAULTS), DEFAULTS); + assert_eq!(backends(None, false, Backends::METAL), Backends::METAL); +} diff --git a/apps/figcraft/src/main.rs b/apps/figcraft/src/main.rs index 7689fb7..0332f7d 100644 --- a/apps/figcraft/src/main.rs +++ b/apps/figcraft/src/main.rs @@ -12,12 +12,15 @@ mod args; mod control_server; mod gamepad; +mod graphics; mod recovery; mod services; #[cfg(test)] mod args_tests; #[cfg(test)] +mod graphics_tests; +#[cfg(test)] mod recovery_online_tests; #[cfg(test)] mod recovery_tests; @@ -182,7 +185,8 @@ fn main() -> eframe::Result { if let Some(icon) = icon { viewport = viewport.with_icon(icon); } - let options = eframe::NativeOptions { viewport, renderer: eframe::Renderer::Wgpu, ..Default::default() }; + let mut options = eframe::NativeOptions { viewport, renderer: eframe::Renderer::Wgpu, ..Default::default() }; + graphics::configure(&mut options.wgpu_options); figcraft_ui_egui::windows::install(fig); eframe::run_native("FigCraft", options, Box::new(move |_cc| Ok(Box::new(App { queue, ctx_slot, title: String::new(), recovery })))) }