From 0dab7ecc59cc7fd32efdb81195912fdc11293e5e Mon Sep 17 00:00:00 2001 From: Junzhuo Zhou Date: Wed, 17 Jun 2026 17:57:12 +0800 Subject: [PATCH 1/2] Reject software wgpu adapters to allow renderer fallback --- wgpu/src/lib.rs | 17 +++++++++ wgpu/src/window/compositor.rs | 65 ++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6c88aa8e76..b9101f2715 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -65,6 +65,19 @@ use crate::graphics::mesh; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::{Shell, Viewport}; +fn adapter_is_software(information: &wgpu::AdapterInfo) -> bool { + matches!(information.device_type, wgpu::DeviceType::Cpu) + || known_software_adapter(&information.name) +} + +fn known_software_adapter(name: &str) -> bool { + let name = name.to_ascii_lowercase(); + + ["llvmpipe", "lavapipe", "softpipe", "swiftshader"] + .iter() + .any(|software| name.contains(software)) +} + /// A [`wgpu`] graphics renderer for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -911,6 +924,10 @@ impl renderer::Headless for Renderer { .await .ok()?; + if adapter_is_software(&adapter.get_info()) { + return None; + } + let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: Some("iced_wgpu [headless]"), diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 33476aab4d..ebc604d002 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -29,6 +29,9 @@ pub enum Error { /// No adapter was found for the options requested. #[error("no adapter was found for the options requested: {0:?}")] NoAdapterFound(String), + /// The selected adapter renders in software. + #[error("the selected adapter renders in software: {0:#?}")] + SoftwareAdapter(wgpu::AdapterInfo), /// No device request succeeded. #[error("no device request succeeded: {0:?}")] RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), @@ -43,6 +46,14 @@ impl From for backend::Error { } } +fn reject_software_adapter(information: &wgpu::AdapterInfo) -> Result<(), Error> { + if crate::adapter_is_software(information) { + Err(Error::SoftwareAdapter(information.clone())) + } else { + Ok(()) + } +} + impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// @@ -94,7 +105,11 @@ impl Compositor { .await .map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?; - log::info!("Selected: {:#?}", adapter.get_info()); + let adapter_info = adapter.get_info(); + + log::info!("Selected: {adapter_info:#?}"); + + reject_software_adapter(&adapter_info)?; let (format, alpha_mode) = compatible_surface .as_ref() @@ -442,3 +457,51 @@ pub fn present_mode_from_env() -> Option { _ => None, } } + +#[cfg(test)] +mod tests { + use super::{Error, reject_software_adapter}; + + fn adapter_info(name: &str, device_type: wgpu::DeviceType) -> wgpu::AdapterInfo { + wgpu::AdapterInfo { + name: name.to_owned(), + vendor: 0, + device: 0, + device_type, + device_pci_bus_id: String::new(), + driver: String::new(), + driver_info: String::new(), + backend: wgpu::Backend::Gl, + subgroup_min_size: 4, + subgroup_max_size: 128, + transient_saves_memory: false, + } + } + + #[test] + fn llvmpipe_adapter_is_rejected_before_requesting_device() { + let information = adapter_info("llvmpipe (LLVM 17.0.2, 256 bits)", wgpu::DeviceType::Cpu); + + assert!(matches!( + reject_software_adapter(&information), + Err(Error::SoftwareAdapter(adapter)) if adapter.name == information.name + )); + } + + #[test] + fn llvmpipe_name_is_rejected_even_if_device_type_is_unknown() { + let information = adapter_info("llvmpipe (LLVM 17.0.2, 256 bits)", wgpu::DeviceType::Other); + + assert!(matches!( + reject_software_adapter(&information), + Err(Error::SoftwareAdapter(adapter)) if adapter.name == information.name + )); + } + + #[test] + fn hardware_adapter_is_accepted() { + let information = adapter_info("NVIDIA GeForce RTX 4090", wgpu::DeviceType::IntegratedGpu); + + assert!(reject_software_adapter(&information).is_ok()); + } +} From b1041dbfa31afe96b714e8965f8077d7e853f36c Mon Sep 17 00:00:00 2001 From: Junzhuo Zhou Date: Wed, 17 Jun 2026 20:31:28 +0800 Subject: [PATCH 2/2] Gate software adapter fallback behind tiny-skia --- renderer/Cargo.toml | 2 +- wgpu/Cargo.toml | 1 + wgpu/src/lib.rs | 2 +- wgpu/src/window/compositor.rs | 19 ++++++++++++++++--- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index f025927a54..dae8848e7c 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [features] wgpu = ["iced_wgpu/default"] wgpu-bare = ["iced_wgpu"] -tiny-skia = ["iced_tiny_skia"] +tiny-skia = ["iced_tiny_skia", "iced_wgpu?/software-fallback"] image = ["iced_tiny_skia?/image", "iced_wgpu?/image"] svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"] geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 8bc71152b4..e5cae6ce8d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -25,6 +25,7 @@ svg = ["iced_graphics/svg", "resvg/text"] web-colors = ["iced_graphics/web-colors"] webgl = ["wgpu/webgl"] strict-assertions = [] +software-fallback = [] [dependencies] iced_debug.workspace = true diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b9101f2715..bcde2cf217 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -924,7 +924,7 @@ impl renderer::Headless for Renderer { .await .ok()?; - if adapter_is_software(&adapter.get_info()) { + if cfg!(feature = "software-fallback") && adapter_is_software(&adapter.get_info()) { return None; } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index ebc604d002..afaf1242fc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -47,7 +47,7 @@ impl From for backend::Error { } fn reject_software_adapter(information: &wgpu::AdapterInfo) -> Result<(), Error> { - if crate::adapter_is_software(information) { + if cfg!(feature = "software-fallback") && crate::adapter_is_software(information) { Err(Error::SoftwareAdapter(information.clone())) } else { Ok(()) @@ -460,7 +460,10 @@ pub fn present_mode_from_env() -> Option { #[cfg(test)] mod tests { - use super::{Error, reject_software_adapter}; + use super::reject_software_adapter; + + #[cfg(feature = "software-fallback")] + use super::Error; fn adapter_info(name: &str, device_type: wgpu::DeviceType) -> wgpu::AdapterInfo { wgpu::AdapterInfo { @@ -478,6 +481,7 @@ mod tests { } } + #[cfg(feature = "software-fallback")] #[test] fn llvmpipe_adapter_is_rejected_before_requesting_device() { let information = adapter_info("llvmpipe (LLVM 17.0.2, 256 bits)", wgpu::DeviceType::Cpu); @@ -488,6 +492,7 @@ mod tests { )); } + #[cfg(feature = "software-fallback")] #[test] fn llvmpipe_name_is_rejected_even_if_device_type_is_unknown() { let information = adapter_info("llvmpipe (LLVM 17.0.2, 256 bits)", wgpu::DeviceType::Other); @@ -498,9 +503,17 @@ mod tests { )); } + #[cfg(not(feature = "software-fallback"))] + #[test] + fn llvmpipe_adapter_is_accepted_without_software_fallback() { + let information = adapter_info("llvmpipe (LLVM 17.0.2, 256 bits)", wgpu::DeviceType::Cpu); + + assert!(reject_software_adapter(&information).is_ok()); + } + #[test] fn hardware_adapter_is_accepted() { - let information = adapter_info("NVIDIA GeForce RTX 4090", wgpu::DeviceType::IntegratedGpu); + let information = adapter_info("NVIDIA GeForce RTX 4090", wgpu::DeviceType::DiscreteGpu); assert!(reject_software_adapter(&information).is_ok()); }