diff options
author | 2024-03-25 21:36:44 +0100 | |
---|---|---|
committer | 2024-03-25 21:36:44 +0100 | |
commit | a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286 (patch) | |
tree | e6c24928a42e23ff91eea0fc30b4fbbcb6da024b /wgpu | |
parent | 3013463baa71504488a20436beb3db87ecb66df0 (diff) | |
parent | 6a4f5ac2081699f7cf20c917b367366ab49eeef1 (diff) | |
download | iced-a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286.tar.gz iced-a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286.tar.bz2 iced-a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286.zip |
Merge pull request #2351 from iced-rs/custom-renderer-injection
Type-Driven Renderer Fallback
Diffstat (limited to 'wgpu')
-rw-r--r-- | wgpu/Cargo.toml | 1 | ||||
-rw-r--r-- | wgpu/src/backend.rs | 13 | ||||
-rw-r--r-- | wgpu/src/geometry.rs | 492 | ||||
-rw-r--r-- | wgpu/src/primitive.rs | 8 | ||||
-rw-r--r-- | wgpu/src/primitive/pipeline.rs | 4 | ||||
-rw-r--r-- | wgpu/src/settings.rs | 37 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 146 |
7 files changed, 349 insertions, 352 deletions
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/backend.rs b/wgpu/src/backend.rs index 09ddbe4d..5019191c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive}; use crate::quad; use crate::text; use crate::triangle; +use crate::window; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -371,8 +372,9 @@ impl Backend { } } -impl crate::graphics::Backend for Backend { +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { @@ -397,3 +399,12 @@ impl backend::Svg for Backend { self.image_pipeline.viewport_dimensions(handle) } } + +#[cfg(feature = "geometry")] +impl crate::graphics::geometry::Backend for Backend { + type Frame = crate::geometry::Frame; + + fn new_frame(&self, size: Size) -> Self::Frame { + crate::geometry::Frame::new(size) + } +} diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f4e0fbda..ba56c59d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,7 +6,7 @@ use crate::core::{ use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ - LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, + self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; @@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; + use std::borrow::Cow; /// A frame for drawing some geometry. @@ -27,191 +28,87 @@ pub struct Frame { stroke_tessellator: tessellation::StrokeTessellator, } -enum Buffer { - Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), - Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), -} - -struct BufferStack { - stack: Vec<Buffer>, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(_) => match self.stack.last() { - Some(Buffer::Gradient(_)) => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - )); - } +impl Frame { + /// Creates a new [`Frame`] with the given [`Size`]. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform(lyon::math::Transform::identity()), }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), } - - self.stack.last_mut().unwrap() } - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) + fn into_primitives(mut self) -> Vec<Primitive> { + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )); + } + } + Buffer::Gradient(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )); + } + } } - _ => unreachable!(), } - } - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) - } - _ => unreachable!(), - } + self.primitives } } -#[derive(Debug)] -struct Transforms { - previous: Vec<Transform>, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { - fn is_identity(&self) -> bool { - self.0 == lyon::math::Transform::identity() - } - - fn is_scale_translation(&self) -> bool { - self.0.m12.abs() < 2.0 * f32::EPSILON - && self.0.m21.abs() < 2.0 * f32::EPSILON - } - - fn scale(&self) -> (f32, f32) { - (self.0.m11, self.0.m22) - } - - fn transform_point(&self, point: Point) -> Point { - let transformed = self - .0 - .transform_point(euclid::Point2D::new(point.x, point.y)); - - Point { - x: transformed.x, - y: transformed.y, - } - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - match &mut gradient { - Gradient::Linear(linear) => { - linear.start = self.transform_point(linear.start); - linear.end = self.transform_point(linear.end); - } - } - - gradient - } -} +impl geometry::frame::Backend for Frame { + type Geometry = Primitive; -impl Frame { /// Creates a new empty [`Frame`] with the given dimensions. /// /// The default coordinate system of a [`Frame`] has its origin at the /// top-left corner of its bounds. - pub fn new(size: Size) -> Frame { - Frame { - size, - buffers: BufferStack::new(), - primitives: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } - /// Returns the width of the [`Frame`]. #[inline] - pub fn width(&self) -> f32 { + fn width(&self) -> f32 { self.size.width } - /// Returns the height of the [`Frame`]. #[inline] - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - /// Returns the dimensions of the [`Frame`]. #[inline] - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - /// Returns the coordinate of the center of the [`Frame`]. #[inline] - pub fn center(&self) -> Point { + fn center(&self) -> Point { Point::new(self.size.width / 2.0, self.size.height / 2.0) } - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { + fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { let Fill { style, rule } = fill.into(); let mut buffer = self @@ -239,9 +136,7 @@ impl Frame { .expect("Tessellate path."); } - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - pub fn fill_rectangle( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -276,9 +171,7 @@ impl Frame { .expect("Fill rectangle"); } - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { let stroke = stroke.into(); let mut buffer = self @@ -315,20 +208,7 @@ impl Frame { .expect("Stroke path"); } - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a `Canvas`. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - pub fn fill_text(&mut self, text: impl Into<Text>) { + fn fill_text(&mut self, text: impl Into<Text>) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -384,57 +264,55 @@ impl Frame { } } - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. #[inline] - pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result + fn translate(&mut self, translation: Vector) { + self.transforms.current.0 = + self.transforms + .current + .0 + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); } - /// Pushes the current transform in the transform stack. - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); + #[inline] + fn rotate(&mut self, angle: impl Into<Radians>) { + self.transforms.current.0 = self + .transforms + .current + .0 + .pre_rotate(lyon::math::Angle::radians(angle.into().0)); } - /// Pops a transform from the transform stack and sets it as the current transform. - pub fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); + #[inline] + fn scale(&mut self, scale: impl Into<f32>) { + let scale = scale.into(); + + self.scale_nonuniform(Vector { x: scale, y: scale }); } - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. #[inline] - pub fn with_clip<R>( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Frame) -> R, - ) -> R { - let mut frame = Frame::new(region.size()); + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + let scale = scale.into(); - let result = f(&mut frame); + self.transforms.current.0 = + self.transforms.current.0.pre_scale(scale.x, scale.y); + } - let origin = Point::new(region.x, region.y); + fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } - self.clip(frame, origin); + fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } - result + fn draft(&mut self, size: Size) -> Frame { + Frame::new(size) } - /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn clip(&mut self, frame: Frame, at: Point) { + fn paste(&mut self, frame: Frame, at: Point) { let size = frame.size(); let primitives = frame.into_primitives(); let transformation = Transformation::translate(at.x, at.y); @@ -462,89 +340,151 @@ impl Frame { }); } - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - self.transforms.current.0 = - self.transforms - .current - .0 - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); + fn into_geometry(self) -> Self::Geometry { + Primitive::Group { + primitives: self.into_primitives(), + } } +} - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: impl Into<Radians>) { - self.transforms.current.0 = self - .transforms - .current - .0 - .pre_rotate(lyon::math::Angle::radians(angle.into().0)); +enum Buffer { + Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), + Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), +} + +struct BufferStack { + stack: Vec<Buffer>, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } } - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into<f32>) { - let scale = scale.into(); + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + )); + } + }, + } - self.scale_nonuniform(Vector { x: scale, y: scale }); + self.stack.last_mut().unwrap() } - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { - let scale = scale.into(); + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } + } - self.transforms.current.0 = - self.transforms.current.0.pre_scale(scale.x, scale.y); + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } } +} - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), +#[derive(Debug)] +struct Transforms { + previous: Vec<Transform>, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform(lyon::math::Transform); + +impl Transform { + fn is_identity(&self) -> bool { + self.0 == lyon::math::Transform::identity() + } + + fn is_scale_translation(&self) -> bool { + self.0.m12.abs() < 2.0 * f32::EPSILON + && self.0.m21.abs() < 2.0 * f32::EPSILON + } + + fn scale(&self) -> (f32, f32) { + (self.0.m11, self.0.m22) + } + + fn transform_point(&self, point: Point) -> Point { + let transformed = self + .0 + .transform_point(euclid::Point2D::new(point.x, point.y)); + + Point { + x: transformed.x, + y: transformed.y, } } - fn into_primitives(mut self) -> Vec<Primitive> { - for buffer in self.buffers.stack { - match buffer { - Buffer::Solid(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Custom( - primitive::Custom::Mesh(Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }), - )); - } - } - Buffer::Gradient(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Custom( - primitive::Custom::Mesh(Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }), - )); - } - } + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) } } + } - self.primitives + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + match &mut gradient { + Gradient::Linear(linear) => { + linear.start = self.transform_point(linear.start); + linear.end = self.transform_point(linear.end); + } + } + + gradient } } - struct GradientVertex2DBuilder { gradient: gradient::Packed, } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index fff927ea..ee9af93c 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -28,3 +28,11 @@ impl Damage for Custom { } } } + +impl TryFrom<Mesh> for Custom { + type Error = &'static str; + + fn try_from(mesh: Mesh) -> Result<Self, Self::Error> { + Ok(Custom::Mesh(mesh)) + } +} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index c6b7c5e2..814440ba 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,5 +1,5 @@ //! Draw primitives using custom pipelines. -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use std::any::{Any, TypeId}; use std::collections::HashMap; @@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static { } /// A renderer than can draw custom pipeline primitives. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// Draws a custom pipeline primitive. fn draw_pipeline_primitive( &mut self, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index c9338fec..828d9e09 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,6 @@ //! Configure a renderer. use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; +use crate::graphics::{self, Antialiasing}; /// The settings of a [`Backend`]. /// @@ -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 { @@ -64,3 +40,14 @@ impl Default for Settings { } } } + +impl From<graphics::Settings> for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..Settings::default() + } + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index fa6b9373..9a3e3b34 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,13 +1,11 @@ //! Connect a window with a renderer. use crate::core::{Color, Size}; -use crate::graphics; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{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 { @@ -20,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`]. /// @@ -27,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() @@ -49,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(); @@ -101,7 +129,8 @@ impl Compositor { }; format.zip(Some(preferred_alpha)) - })?; + }) + .ok_or(Error::IncompatibleSurface)?; log::info!( "Selected format: {format:?} with alpha mode: {alpha_mode:?}" @@ -115,39 +144,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`]. @@ -168,9 +204,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`]. @@ -229,15 +263,31 @@ pub fn present<T: AsRef<str>>( } impl graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface<'static>; - fn new<W: compositor::Window>( - settings: Self::Settings, + async fn with_backend<W: compositor::Window>( + settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - new(settings, 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 { |