GPU startup: CPU mode on Linux falls back to Vulkan to present the window (#1337) (#2206)

CPU rendering mode limited the window's wgpu instance to GL on Linux.
With NVIDIA's EGL the GL adapter can't present to the window (wgpu:
"EGL says it can present to the window but not natively") and llvmpipe
isn't offered, so the adapter selector found nothing and every start
exited with "no graphics adapter found", on X11 and Wayland alike. The
user couldn't reach Preferences to undo it.

CPU mode now creates the instance with GL and Vulkan. GL adapters still
rank first (llvmpipe, then hardware GL), so nothing changes where GL can
present; Vulkan is used only to draw the window when no GL adapter can.
Image compositing stays on the CPU.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
MrCloudy2
2026-10-10 04:12:17 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent d4ec2db0a7
commit dd2ce3da33
+34 -6
View File
@@ -16,7 +16,9 @@
//! overrides everything, and `--safe-gpu` forces the CPU path for one launch.
//!
//! "CPU" composites on the CPU and draws the window with a software adapter where the platform
//! has one (WARP on Windows, llvmpipe over GL elsewhere).
//! has one (WARP on Windows, llvmpipe over GL elsewhere). On Linux a GL adapter that can't present
//! (NVIDIA's EGL, which also hides llvmpipe) falls back to a Vulkan adapter for the window only
//! (#1337); before, CPU mode then couldn't start at all.
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
@@ -189,7 +191,9 @@ pub fn backends(plan: &Plan, os: Os) -> Option<wgpu::Backends> {
GpuBackend::Cpu => match os {
Os::Windows => wgpu::Backends::DX12,
Os::Mac => wgpu::Backends::METAL,
Os::Other => wgpu::Backends::GL,
// Vulkan only for the window when no GL adapter can present (`rank`): with NVIDIA's
// EGL the GL adapter can't, and CPU mode failed every start (#1337).
Os::Other => wgpu::Backends::GL | wgpu::Backends::VULKAN,
},
})
}
@@ -222,8 +226,10 @@ pub struct Candidate {
/// Rank of an adapter (lower is better): high-performance device types first (egui's power
/// preference) or software adapters first for `cpu`; then backends, with DX12 before Vulkan for
/// Intel on Windows.
fn rank(a: &Candidate, backend: GpuBackend, os: Os) -> (u8, u8) {
/// Intel on Windows. For `cpu`, any GL adapter comes before Vulkan, which is only the fallback
/// for presenting the window (see [`backends`]).
fn rank(a: &Candidate, backend: GpuBackend, os: Os) -> (u8, u8, u8) {
let fallback = u8::from(backend == GpuBackend::Cpu && a.backend == wgpu::Backend::Vulkan);
let ty = match a.device_type {
wgpu::DeviceType::DiscreteGpu => 0,
wgpu::DeviceType::IntegratedGpu => 1,
@@ -243,7 +249,7 @@ fn rank(a: &Candidate, backend: GpuBackend, os: Os) -> (u8, u8) {
wgpu::Backend::Gl => 3,
wgpu::Backend::BrowserWebGpu | wgpu::Backend::Noop => 4,
};
(ty, be)
(fallback, ty, be)
}
/// The adapter to use among `adapters` (`None` when there is none).
@@ -529,7 +535,7 @@ mod tests {
let p = plan(Vulkan, Some(&crashed("vulkan", "vulkan")), None, true, Os::Other);
assert_eq!((p.backend, p.remember), (Cpu, false));
assert_eq!(backends(&p, Os::Windows), Some(wgpu::Backends::DX12));
assert_eq!(backends(&p, Os::Other), Some(wgpu::Backends::GL));
assert_eq!(backends(&p, Os::Other), Some(wgpu::Backends::GL | wgpu::Backends::VULKAN));
assert_eq!(backends(&p, Os::Mac), Some(wgpu::Backends::METAL));
}
@@ -569,6 +575,28 @@ mod tests {
assert_eq!(pick(&a[..1], Cpu, Os::Windows), Some(0));
}
/// #1337: on Linux with NVIDIA, the only GL adapter (NVIDIA's EGL) can't present, so CPU mode
/// found no adapter and never started. GL still wins whenever it can present.
#[test]
fn cpu_on_linux_falls_back_to_vulkan_only_when_gl_cannot_present() {
use wgpu::{Backend as B, DeviceType as T};
const NVIDIA: u32 = 0x10de;
let llvmpipe = cand(0x10005, T::Cpu, B::Gl);
let nvidia_gl = cand(NVIDIA, T::DiscreteGpu, B::Gl);
let nvidia_vk = cand(NVIDIA, T::DiscreteGpu, B::Vulkan);
let lavapipe = cand(0x10005, T::Cpu, B::Vulkan);
// GL presents: same choice as GL-only, software first, then hardware GL over any Vulkan.
assert_eq!(pick(&[nvidia_vk, lavapipe, llvmpipe], Cpu, Os::Other), Some(2));
assert_eq!(pick(&[nvidia_vk, lavapipe, nvidia_gl], Cpu, Os::Other), Some(2));
// GL can't present: Vulkan for the window, software first.
let mut gl = nvidia_gl;
gl.surface_ok = false;
assert_eq!(pick(&[gl, nvidia_vk, lavapipe], Cpu, Os::Other), Some(2));
assert_eq!(pick(&[gl, nvidia_vk], Cpu, Os::Other), Some(1));
// Other backends rank as before.
assert_eq!(pick(&[nvidia_gl, nvidia_vk], Auto, Os::Other), Some(1));
}
#[test]
fn adapters_must_present_even_when_none_support_the_surface() {
let mut a = cand(INTEL, wgpu::DeviceType::IntegratedGpu, wgpu::Backend::Dx12);