diff options
author | 2024-03-24 08:04:28 +0100 | |
---|---|---|
committer | 2024-03-24 08:04:28 +0100 | |
commit | 4f5b63f1f4cd7d3ab72289c697f4abc767114eca (patch) | |
tree | 876f9502936b24f63ef21bb2b41e16b222750444 | |
parent | 441e9237cd1c9c9b61d9b144b5b4dafa236ace28 (diff) | |
download | iced-4f5b63f1f4cd7d3ab72289c697f4abc767114eca.tar.gz iced-4f5b63f1f4cd7d3ab72289c697f4abc767114eca.tar.bz2 iced-4f5b63f1f4cd7d3ab72289c697f4abc767114eca.zip |
Reintroduce backend selection through `ICED_BACKEND` env var
-rw-r--r-- | graphics/src/compositor.rs | 15 | ||||
-rw-r--r-- | graphics/src/error.rs | 27 | ||||
-rw-r--r-- | graphics/src/lib.rs | 2 | ||||
-rw-r--r-- | renderer/src/fallback.rs | 51 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 21 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 1 | ||||
-rw-r--r-- | wgpu/src/settings.rs | 24 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 142 |
8 files changed, 198 insertions, 85 deletions
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 8c67cd16..86472a58 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -20,6 +20,18 @@ pub trait Compositor: Sized { fn new<W: Window + Clone>( settings: Settings, compatible_window: W, + ) -> impl Future<Output = Result<Self, Error>> { + Self::with_backend(settings, compatible_window, None) + } + + /// Creates a new [`Compositor`] with a backend preference. + /// + /// If the backend does not match the preference, it will return + /// [`Error::GraphicsAdapterNotFound`]. + fn with_backend<W: Window + Clone>( + _settings: Settings, + _compatible_window: W, + _backend: Option<&str>, ) -> impl Future<Output = Result<Self, Error>>; /// Creates a [`Self::Renderer`] for the [`Compositor`]. @@ -130,9 +142,10 @@ impl Compositor for () { type Renderer = (); type Surface = (); - async fn new<W: Window + Clone>( + async fn with_backend<W: Window + Clone>( _settings: Settings, _compatible_window: W, + _preffered_backend: Option<&str>, ) -> Result<Self, Error> { Ok(()) } diff --git a/graphics/src/error.rs b/graphics/src/error.rs index c6ea98a3..6ea1d3a4 100644 --- a/graphics/src/error.rs +++ b/graphics/src/error.rs @@ -1,5 +1,7 @@ +//! See what can go wrong when creating graphical backends. + /// An error that occurred while creating an application's graphical context. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum Error { /// The requested backend version is not supported. #[error("the requested backend version is not supported")] @@ -11,9 +13,30 @@ pub enum Error { /// A suitable graphics adapter or device could not be found. #[error("a suitable graphics adapter or device could not be found")] - GraphicsAdapterNotFound, + GraphicsAdapterNotFound { + /// The name of the backend where the error happened + backend: &'static str, + /// The reason why this backend could not be used + reason: Reason, + }, /// An error occurred in the context's internal backend #[error("an error occurred in the context's internal backend")] BackendError(String), + + /// Multiple errors occurred + #[error("multiple errors occurred: {0:?}")] + List(Vec<Self>), +} + +/// The reason why a graphics adapter could not be found +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Reason { + /// The backend did not match the preference + DidNotMatch { + /// The preferred backend + preferred_backend: String, + }, + /// The request to create the backend failed + RequestFailed(String), } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 2e476f8c..d7f2f439 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -18,7 +18,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; mod cached; -mod error; mod primitive; mod settings; mod viewport; @@ -27,6 +26,7 @@ pub mod backend; pub mod color; pub mod compositor; pub mod damage; +pub mod error; pub mod gradient; pub mod mesh; pub mod renderer; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index ca445746..ef9cc9a9 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -204,18 +204,55 @@ where type Renderer = Renderer<L::Renderer, R::Renderer>; type Surface = Surface<L::Surface, R::Surface>; - async fn new<W: compositor::Window + Clone>( + async fn with_backend<W: compositor::Window + Clone>( settings: graphics::Settings, compatible_window: W, + backend: Option<&str>, ) -> Result<Self, graphics::Error> { - if let Ok(left) = L::new(settings, compatible_window.clone()) - .await - .map(Self::Left) - { - return Ok(left); + use std::env; + + let backends = backend + .map(str::to_owned) + .or_else(|| env::var("ICED_BACKEND").ok()); + + let mut candidates: Vec<_> = backends + .map(|backends| { + backends + .split(',') + .filter(|candidate| !candidate.is_empty()) + .map(str::to_owned) + .map(Some) + .collect() + }) + .unwrap_or_default(); + + if candidates.is_empty() { + candidates.push(None); } - R::new(settings, compatible_window).await.map(Self::Right) + let mut errors = vec![]; + + for backend in candidates.iter().map(Option::as_deref) { + match L::with_backend(settings, compatible_window.clone(), backend) + .await + { + Ok(compositor) => return Ok(Self::Left(compositor)), + Err(error) => { + errors.push(error); + } + } + + match R::with_backend(settings, compatible_window.clone(), backend) + .await + { + Ok(compositor) => return Ok(Self::Right(compositor)), + Err(error) => { + errors.push(error); + } + } + } + + Err(graphics::Error::List(errors)) } fn create_renderer(&self) -> Self::Renderer { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 0c08097b..25c57dc1 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,11 +1,11 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; -use crate::graphics::{self, Error, Viewport}; +use crate::graphics::error::{self, Error}; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; -use std::future::{self, Future}; use std::num::NonZeroU32; pub struct Compositor { @@ -28,11 +28,22 @@ impl crate::graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = Surface; - fn new<W: compositor::Window>( + async fn with_backend<W: compositor::Window>( settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - future::ready(Ok(new(settings.into(), compatible_window))) + backend: Option<&str>, + ) -> Result<Self, Error> { + match backend { + None | Some("tiny-skia") | Some("tiny_skia") => { + Ok(new(settings.into(), compatible_window)) + } + Some(backend) => Err(Error::GraphicsAdapterNotFound { + backend: "tiny-skia", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4a0d89f0..f6162e0f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,6 +32,7 @@ glyphon.workspace = true guillotiere.workspace = true log.workspace = true once_cell.workspace = true +thiserror.workspace = true wgpu.workspace = true lyon.workspace = true diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 9943aa3e..828d9e09 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -29,30 +29,6 @@ pub struct Settings { pub antialiasing: Option<Antialiasing>, } -impl Settings { - /// Creates new [`Settings`] using environment configuration. - /// - /// Specifically: - /// - /// - The `internal_backend` can be configured using the `WGPU_BACKEND` - /// environment variable. If the variable is not set, the primary backend - /// will be used. The following values are allowed: - /// - `vulkan` - /// - `metal` - /// - `dx12` - /// - `dx11` - /// - `gl` - /// - `webgpu` - /// - `primary` - pub fn from_env() -> Self { - Settings { - internal_backend: wgpu::util::backend_bits_from_env() - .unwrap_or(wgpu::Backends::all()), - ..Self::default() - } - } -} - impl Default for Settings { fn default() -> Settings { Settings { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 74dfadba..7b769923 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -2,11 +2,10 @@ use crate::core::{Color, Size}; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{self, Error, Viewport}; +use crate::graphics::error; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; -use std::future::Future; - /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] pub struct Compositor { @@ -19,6 +18,32 @@ pub struct Compositor { alpha_mode: wgpu::CompositeAlphaMode, } +/// A compositor error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// The surface creation failed. + #[error("the surface creation failed: {0}")] + SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), + /// The surface is not compatible. + #[error("the surface is not compatible")] + IncompatibleSurface, + /// No adapter was found for the options requested. + #[error("no adapter was found for the options requested: {0:?}")] + NoAdapterFound(String), + /// No device request succeeded. + #[error("no device request succeeded: {0:?}")] + RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), +} + +impl From<Error> for graphics::Error { + fn from(error: Error) -> Self { + Self::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::RequestFailed(error.to_string()), + } + } +} + impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// @@ -26,7 +51,7 @@ impl Compositor { pub async fn request<W: compositor::Window>( settings: Settings, compatible_window: Option<W>, - ) -> Option<Self> { + ) -> Result<Self, Error> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: settings.internal_backend, ..Default::default() @@ -48,23 +73,27 @@ impl Compositor { let compatible_surface = compatible_window .and_then(|window| instance.create_surface(window).ok()); + let adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), + compatible_surface: compatible_surface.as_ref(), + force_fallback_adapter: false, + }; + let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::util::power_preference_from_env() - .unwrap_or(if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }), - compatible_surface: compatible_surface.as_ref(), - force_fallback_adapter: false, - }) - .await?; + .request_adapter(&adapter_options) + .await + .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; log::info!("Selected: {:#?}", adapter.get_info()); - let (format, alpha_mode) = - compatible_surface.as_ref().and_then(|surface| { + let (format, alpha_mode) = compatible_surface + .as_ref() + .and_then(|surface| { let capabilities = surface.get_capabilities(&adapter); let mut formats = capabilities.formats.iter().copied(); @@ -96,7 +125,8 @@ impl Compositor { }; format.zip(Some(preferred_alpha)) - })?; + }) + .ok_or(Error::IncompatibleSurface)?; log::info!( "Selected format: {format:?} with alpha mode: {alpha_mode:?}" @@ -110,39 +140,46 @@ impl Compositor { let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - let mut limits = limits.into_iter().map(|limits| wgpu::Limits { + let limits = limits.into_iter().map(|limits| wgpu::Limits { max_bind_groups: 2, ..limits }); - let (device, queue) = - loop { - let required_limits = limits.next()?; - let device = adapter.request_device( + let mut errors = Vec::new(); + + for required_limits in limits { + let result = adapter + .request_device( &wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", ), required_features: wgpu::Features::empty(), - required_limits, + required_limits: required_limits.clone(), }, None, - ).await.ok(); - - if let Some(device) = device { - break Some(device); + ) + .await; + + match result { + Ok((device, queue)) => { + return Ok(Compositor { + instance, + settings, + adapter, + device, + queue, + format, + alpha_mode, + }) } - }?; - - Some(Compositor { - instance, - settings, - adapter, - device, - queue, - format, - alpha_mode, - }) + Err(error) => { + errors.push((required_limits, error)); + } + } + } + + Err(Error::RequestDeviceFailed(errors)) } /// Creates a new rendering [`Backend`] for this [`Compositor`]. @@ -163,9 +200,7 @@ pub async fn new<W: compositor::Window>( settings: Settings, compatible_window: W, ) -> Result<Compositor, Error> { - Compositor::request(settings, Some(compatible_window)) - .await - .ok_or(Error::GraphicsAdapterNotFound) + Compositor::request(settings, Some(compatible_window)).await } /// Presents the given primitives with the given [`Compositor`] and [`Backend`]. @@ -227,11 +262,28 @@ impl graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = wgpu::Surface<'static>; - fn new<W: compositor::Window>( + async fn with_backend<W: compositor::Window>( settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - new(settings.into(), compatible_window) + backend: Option<&str>, + ) -> Result<Self, graphics::Error> { + match backend { + None | Some("wgpu") => Ok(new( + Settings { + internal_backend: wgpu::util::backend_bits_from_env() + .unwrap_or(wgpu::Backends::all()), + ..settings.into() + }, + compatible_window, + ) + .await?), + Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { |