From 0d4e73ce8d64e25de076a49b91690952fd0ffb4c Mon Sep 17 00:00:00 2001 From: Matthias Grob Date: Tue, 18 Aug 2026 13:54:14 +0200 Subject: [PATCH] fix: retry detecting discrete GPU to resolve boot race where nvidia driver doesn't yet report being discrete to Vulkan/EGL probes which cardwired is already querying. Misclassifying nvidia discrete GPU as integrated and disabling Integrated/Smart modes. --- .../src/core/gpu/enumerator.rs | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/crates/cardwire-daemon/src/core/gpu/enumerator.rs b/crates/cardwire-daemon/src/core/gpu/enumerator.rs index 8259ec48..0a056f87 100644 --- a/crates/cardwire-daemon/src/core/gpu/enumerator.rs +++ b/crates/cardwire-daemon/src/core/gpu/enumerator.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, io, sync::Arc + collections::{BTreeMap, HashMap}, io, sync::Arc, time::Duration }; use log::{error, info, warn}; @@ -123,17 +123,7 @@ impl GpuEnumerator { } }; - // Skip the EGL probe for unavailable GPUs: the render node is unknown (u32::MAX) and the - // lookup would always fail on a phantom /dev/dri/renderD4294967295 path - let discrete = self.is_discrete_vulkan(device.pci_address()) - || (available - && match is_discrete_egl(render) { - Ok(discrete) => discrete, - Err(err) => { - warn!("{}: EGL discrete check failed: {}", device_name, err); - false - } - }); + let discrete = self.is_discrete(device.pci_address(), render, available, &device_name); Ok(GpuDevice::new( device_name, @@ -158,6 +148,54 @@ impl GpuEnumerator { false } + /// Determine whether a GPU is discrete, retrying both the Vulkan and EGL probes. + /// + /// At daemon startup these probes can race the NVIDIA/AMD driver stack coming up (the + /// GPU's DRM nodes may only just have appeared, see `drm_node_ids`'s own retry loop), so a + /// single failed attempt right after boot doesn't mean the GPU truly isn't discrete - it + /// means the ICD/EGL driver wasn't ready yet. Re-enumerate Vulkan fresh on each attempt since + /// the cached `vlk_physical_devices` snapshot was taken even earlier, at enumerator + /// construction time. + fn is_discrete(&self, pci_id: &str, render: u32, available: bool, device_name: &str) -> bool { + const MAX_RETRIES: u32 = 10; + const RETRY_INTERVAL: Duration = Duration::from_millis(500); + + if self.is_discrete_vulkan(pci_id) { + return true; + } + + // Skip the EGL probe for unavailable GPUs: the render node is unknown (u32::MAX) and the + // lookup would always fail on a phantom /dev/dri/renderD4294967295 path + if !available { + return false; + } + + for attempt in 1..=MAX_RETRIES { + if attempt > 1 + && let Some(vlk_dev) = vlk_enumerate().and_then(|map| map.get(pci_id).cloned()) + && vlk_dev.properties().device_type == PhysicalDeviceType::DiscreteGpu + { + return true; + } + + match is_discrete_egl(render) { + Ok(discrete) => return discrete, + Err(err) if attempt < MAX_RETRIES => { + warn!( + "{}: EGL discrete check failed, attempt {}/{MAX_RETRIES}, retrying in 500ms...: {}", + device_name, attempt, err + ); + std::thread::sleep(RETRY_INTERVAL); + } + Err(err) => { + warn!("{}: EGL discrete check failed: {}", device_name, err); + return false; + } + } + } + + false + } /// Detect virtual GPUs (e.g. virtio-gpu in qemu) through Vulkan when available, falling /// back to the virtio PCI vendor id. fn is_virtual_gpu(&self, device: &PciDevice) -> bool {