From de564bc24f1d227a9e3f5c512a717dd92efd155e Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 10 Oct 2026 07:18:26 +0700 Subject: [PATCH] Fix #806: Windows doesn't load Vulkan drivers unless asked Creating a Vulkan instance loads every installed Vulkan driver into the process, and a faulty one (Intel's igvk64.dll) crashed VectorCraft before its window appeared. On Windows the window now starts with Direct3D 12, falling back to OpenGL, as PdfCraft does; WGPU_BACKEND (e.g. vulkan) still chooses others. Co-Authored-By: Claude Opus 5.5 --- apps/vectorcraft/src/gpu.rs | 23 ++++++++++++++++++++++- apps/vectorcraft/src/main.rs | 1 + docs/development.md | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/vectorcraft/src/gpu.rs b/apps/vectorcraft/src/gpu.rs index d2044e0d..7cab923c 100644 --- a/apps/vectorcraft/src/gpu.rs +++ b/apps/vectorcraft/src/gpu.rs @@ -31,7 +31,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, OnceLock}; use eframe::egui_wgpu::NativeAdapterSelectorMethod; -use eframe::wgpu::{self, Backend, DeviceType, PowerPreference}; +use eframe::wgpu::{self, Backend, Backends, DeviceType, PowerPreference}; /// The adapters a restart leaves out, those the window failed on, as comma-separated [`key`]s /// (`Vulkan:1002:164e`, `Metal:Intel Iris Pro Graphics`). Set by the app itself when it starts @@ -81,6 +81,15 @@ pub fn preferred_displays(pref: Option<&str>, env: Option) -> V if automatic(pref, env) { display_gpus() } else { Vec::new() } } +/// The graphics backends the window starts with: on Windows Direct3D 12, falling back to OpenGL, +/// unless `WGPU_BACKEND` names others (`env_set`; `default` then holds them). Creating a Vulkan +/// instance loads every installed Vulkan driver into the process, and a faulty one (Intel's +/// `igvk64.dll`, #806) crashed the app before its window appeared. DX12 is Windows' own backend, +/// as in PdfCraft. Elsewhere, `default`. +pub fn backends(default: Backends, env_set: bool) -> Backends { + if cfg!(windows) && !env_set { Backends::DX12 | Backends::GL } else { default } +} + /// One adapter, as [`adapter_order`] sees it. #[derive(Clone, Debug)] pub struct Candidate { @@ -809,6 +818,18 @@ mod tests { assert_eq!(adapter_order(&c, PowerPreference::LowPower, &[], None, &[c[1].key.clone()]), [0, 3, 2], "DX12 failed: Vulkan next"); } + #[test] + fn windows_never_loads_vulkan_drivers_unless_asked() { + let all = Backends::all(); + let start = backends(all, false); + if cfg!(windows) { + assert_eq!(start, Backends::DX12 | Backends::GL, "#806"); + } else { + assert_eq!(start, all); + } + assert_eq!(backends(Backends::VULKAN, true), Backends::VULKAN, "WGPU_BACKEND=vulkan still picks it"); + } + #[test] fn power_preference_follows_the_environment_then_the_preference() { assert_eq!(power_preference(None, None), AUTOMATIC); diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index 0eb82005..f446c543 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -467,6 +467,7 @@ fn main() -> std::process::ExitCode { // Metal on an Iris Pro, #651). `WGPU_VALIDATION_INDIRECT_CALL=1` turns it back on. create.instance_descriptor.flags = (eframe::wgpu::InstanceFlags::from_build_config() - eframe::wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL).with_env(); + create.instance_descriptor.backends = gpu::backends(create.instance_descriptor.backends, std::env::var_os("WGPU_BACKEND").is_some()); } options }; diff --git a/docs/development.md b/docs/development.md index 9b146825..09abdcc1 100644 --- a/docs/development.md +++ b/docs/development.md @@ -45,7 +45,8 @@ How the web shell (`apps/vectorcraft-web/src/web.rs`) differs from desktop: The canvas is rasterized on the CPU (`vectorcraft-render`, vello_cpu); the GPU (wgpu, through eframe) only composites the canvas texture and draws the UI, so any GPU that can show the window will do. The desktop app picks the window's adapter itself (`apps/vectorcraft/src/gpu.rs`, eframe's `native_adapter_selector`) and logs every adapter it found and the one it uses. -- **Order:** adapters that report they can't present to the window are never tried; then the one `WGPU_ADAPTER_NAME` names; hardware before software (llvmpipe, WARP); the native backends (Vulkan, Metal, DX12) before OpenGL; with Automatic, the GPU that drives a display, and among those the one that drives the primary display; then the power preference; then, on Windows, where each GPU is listed under both, a GPU's DX12 adapter before its Vulkan one (Intel's Vulkan driver made the whole window flicker black, #545), keeping the system's order among equals. `WGPU_BACKEND=vulkan` still picks Vulkan. +- **Order:** adapters that report they can't present to the window are never tried; then the one `WGPU_ADAPTER_NAME` names; hardware before software (llvmpipe, WARP); the native backends (Vulkan, Metal, DX12) before OpenGL; with Automatic, the GPU that drives a display, and among those the one that drives the primary display; then the power preference; then a GPU's DX12 adapter before its Vulkan one (Intel's Vulkan driver made the whole window flicker black, #545), keeping the system's order among equals. +- **Windows backends:** Direct3D 12, falling back to OpenGL. Vulkan isn't started at all unless `WGPU_BACKEND` asks for it (`WGPU_BACKEND=vulkan`): creating a Vulkan instance loads every installed Vulkan driver into the process, and a faulty one (Intel's `igvk64.dll`, #806) crashed the app before its window appeared. - **The GPU that drives the display.** A GPU without a monitor is the wrong one even when it says it can show the window: on a desktop with a Ryzen's integrated GPU (no monitor) and an NVIDIA card driving both monitors, power saving picked the integrated GPU, Windows had to present every frame across adapters, and the integrated GPU's driver reset under that: the graphics device was lost, or the desktop's compositor went down and every monitor stayed black for minutes (pdfcraft#378). So Automatic asks the system which GPU drives which display and puts that GPU first; the power preference only orders the rest, and decides alone where the system can't say. - **Windows:** `EnumDisplayDevices` (the display devices attached to the desktop, and the primary one), through the `winsafe` crate; the PCI ids in each device id are matched against wgpu's adapters. The registry doesn't tell a user which adapter drives a display, and WMI reports a resolution for a GPU without a monitor, so neither is used. - **Linux:** the connected connectors in `/sys/class/drm` and their cards' PCI ids; a built-in panel (`eDP`, `LVDS`, `DSI`) counts as the primary display.