Reintroduce backend selection through ICED_BACKEND env var

This commit is contained in:
Héctor Ramón Jiménez 2024-03-24 08:04:28 +01:00
parent 441e9237cd
commit 4f5b63f1f4
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
8 changed files with 196 additions and 83 deletions

View file

@ -20,6 +20,18 @@ pub trait Compositor: Sized {
fn new<W: Window + Clone>( fn new<W: Window + Clone>(
settings: Settings, settings: Settings,
compatible_window: W, 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>>; ) -> impl Future<Output = Result<Self, Error>>;
/// Creates a [`Self::Renderer`] for the [`Compositor`]. /// Creates a [`Self::Renderer`] for the [`Compositor`].
@ -130,9 +142,10 @@ impl Compositor for () {
type Renderer = (); type Renderer = ();
type Surface = (); type Surface = ();
async fn new<W: Window + Clone>( async fn with_backend<W: Window + Clone>(
_settings: Settings, _settings: Settings,
_compatible_window: W, _compatible_window: W,
_preffered_backend: Option<&str>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(()) Ok(())
} }

View file

@ -1,5 +1,7 @@
//! See what can go wrong when creating graphical backends.
/// An error that occurred while creating an application's graphical context. /// 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 { pub enum Error {
/// The requested backend version is not supported. /// The requested backend version is not supported.
#[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. /// A suitable graphics adapter or device could not be found.
#[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 /// An error occurred in the context's internal backend
#[error("an error occurred in the context's internal backend")] #[error("an error occurred in the context's internal backend")]
BackendError(String), 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),
} }

View file

@ -18,7 +18,6 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod antialiasing; mod antialiasing;
mod cached; mod cached;
mod error;
mod primitive; mod primitive;
mod settings; mod settings;
mod viewport; mod viewport;
@ -27,6 +26,7 @@ pub mod backend;
pub mod color; pub mod color;
pub mod compositor; pub mod compositor;
pub mod damage; pub mod damage;
pub mod error;
pub mod gradient; pub mod gradient;
pub mod mesh; pub mod mesh;
pub mod renderer; pub mod renderer;

View file

@ -204,18 +204,55 @@ where
type Renderer = Renderer<L::Renderer, R::Renderer>; type Renderer = Renderer<L::Renderer, R::Renderer>;
type Surface = Surface<L::Surface, R::Surface>; 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, settings: graphics::Settings,
compatible_window: W, compatible_window: W,
backend: Option<&str>,
) -> Result<Self, graphics::Error> { ) -> Result<Self, graphics::Error> {
if let Ok(left) = L::new(settings, compatible_window.clone()) use std::env;
.await
.map(Self::Left) let backends = backend
{ .map(str::to_owned)
return Ok(left); .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 { fn create_renderer(&self) -> Self::Renderer {

View file

@ -1,11 +1,11 @@
use crate::core::{Color, Rectangle, Size}; use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information}; use crate::graphics::compositor::{self, Information};
use crate::graphics::damage; 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 crate::{Backend, Primitive, Renderer, Settings};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::future::{self, Future};
use std::num::NonZeroU32; use std::num::NonZeroU32;
pub struct Compositor { pub struct Compositor {
@ -28,11 +28,22 @@ impl crate::graphics::Compositor for Compositor {
type Renderer = Renderer; type Renderer = Renderer;
type Surface = Surface; type Surface = Surface;
fn new<W: compositor::Window>( async fn with_backend<W: compositor::Window>(
settings: graphics::Settings, settings: graphics::Settings,
compatible_window: W, compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> { backend: Option<&str>,
future::ready(Ok(new(settings.into(), compatible_window))) ) -> 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 { fn create_renderer(&self) -> Self::Renderer {

View file

@ -32,6 +32,7 @@ glyphon.workspace = true
guillotiere.workspace = true guillotiere.workspace = true
log.workspace = true log.workspace = true
once_cell.workspace = true once_cell.workspace = true
thiserror.workspace = true
wgpu.workspace = true wgpu.workspace = true
lyon.workspace = true lyon.workspace = true

View file

@ -29,30 +29,6 @@ pub struct Settings {
pub antialiasing: Option<Antialiasing>, 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 { impl Default for Settings {
fn default() -> Settings { fn default() -> Settings {
Settings { Settings {

View file

@ -2,11 +2,10 @@
use crate::core::{Color, Size}; use crate::core::{Color, Size};
use crate::graphics::color; use crate::graphics::color;
use crate::graphics::compositor; 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 crate::{Backend, Primitive, Renderer, Settings};
use std::future::Future;
/// A window graphics backend for iced powered by `wgpu`. /// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Compositor { pub struct Compositor {
@ -19,6 +18,32 @@ pub struct Compositor {
alpha_mode: wgpu::CompositeAlphaMode, 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 { impl Compositor {
/// Requests a new [`Compositor`] with the given [`Settings`]. /// Requests a new [`Compositor`] with the given [`Settings`].
/// ///
@ -26,7 +51,7 @@ impl Compositor {
pub async fn request<W: compositor::Window>( pub async fn request<W: compositor::Window>(
settings: Settings, settings: Settings,
compatible_window: Option<W>, compatible_window: Option<W>,
) -> Option<Self> { ) -> Result<Self, Error> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: settings.internal_backend, backends: settings.internal_backend,
..Default::default() ..Default::default()
@ -48,23 +73,27 @@ impl Compositor {
let compatible_surface = compatible_window let compatible_surface = compatible_window
.and_then(|window| instance.create_surface(window).ok()); .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 let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&adapter_options)
power_preference: wgpu::util::power_preference_from_env() .await
.unwrap_or(if settings.antialiasing.is_none() { .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
wgpu::PowerPreference::LowPower
} else {
wgpu::PowerPreference::HighPerformance
}),
compatible_surface: compatible_surface.as_ref(),
force_fallback_adapter: false,
})
.await?;
log::info!("Selected: {:#?}", adapter.get_info()); log::info!("Selected: {:#?}", adapter.get_info());
let (format, alpha_mode) = let (format, alpha_mode) = compatible_surface
compatible_surface.as_ref().and_then(|surface| { .as_ref()
.and_then(|surface| {
let capabilities = surface.get_capabilities(&adapter); let capabilities = surface.get_capabilities(&adapter);
let mut formats = capabilities.formats.iter().copied(); let mut formats = capabilities.formats.iter().copied();
@ -96,7 +125,8 @@ impl Compositor {
}; };
format.zip(Some(preferred_alpha)) format.zip(Some(preferred_alpha))
})?; })
.ok_or(Error::IncompatibleSurface)?;
log::info!( log::info!(
"Selected format: {format:?} with alpha mode: {alpha_mode:?}" "Selected format: {format:?} with alpha mode: {alpha_mode:?}"
@ -110,39 +140,46 @@ impl Compositor {
let limits = let limits =
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; [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, max_bind_groups: 2,
..limits ..limits
}); });
let (device, queue) = let mut errors = Vec::new();
loop {
let required_limits = limits.next()?; for required_limits in limits {
let device = adapter.request_device( let result = adapter
.request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: Some( label: Some(
"iced_wgpu::window::compositor device descriptor", "iced_wgpu::window::compositor device descriptor",
), ),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits, required_limits: required_limits.clone(),
}, },
None, None,
).await.ok(); )
.await;
if let Some(device) = device { match result {
break Some(device); Ok((device, queue)) => {
return Ok(Compositor {
instance,
settings,
adapter,
device,
queue,
format,
alpha_mode,
})
} }
}?; Err(error) => {
errors.push((required_limits, error));
}
}
}
Some(Compositor { Err(Error::RequestDeviceFailed(errors))
instance,
settings,
adapter,
device,
queue,
format,
alpha_mode,
})
} }
/// Creates a new rendering [`Backend`] for this [`Compositor`]. /// Creates a new rendering [`Backend`] for this [`Compositor`].
@ -163,9 +200,7 @@ pub async fn new<W: compositor::Window>(
settings: Settings, settings: Settings,
compatible_window: W, compatible_window: W,
) -> Result<Compositor, Error> { ) -> Result<Compositor, Error> {
Compositor::request(settings, Some(compatible_window)) Compositor::request(settings, Some(compatible_window)).await
.await
.ok_or(Error::GraphicsAdapterNotFound)
} }
/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. /// Presents the given primitives with the given [`Compositor`] and [`Backend`].
@ -227,11 +262,28 @@ impl graphics::Compositor for Compositor {
type Renderer = Renderer; type Renderer = Renderer;
type Surface = wgpu::Surface<'static>; type Surface = wgpu::Surface<'static>;
fn new<W: compositor::Window>( async fn with_backend<W: compositor::Window>(
settings: graphics::Settings, settings: graphics::Settings,
compatible_window: W, compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> { backend: Option<&str>,
new(settings.into(), compatible_window) ) -> 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 { fn create_renderer(&self) -> Self::Renderer {