From 188db4da48954b95a3fe79bcd22689ffc3a661e0 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 21 Mar 2024 05:52:48 +0100
Subject: Draft support for dynamic custom renderer injection

---
 core/src/image.rs              |   4 +-
 core/src/renderer.rs           |   2 +-
 core/src/svg.rs                |   9 ++-
 graphics/src/renderer.rs       |   8 +-
 renderer/src/compositor.rs     |  25 +++++++
 renderer/src/custom.rs         | 164 +++++++++++++++++++++++++++++++++++++++++
 renderer/src/geometry.rs       |  33 +++++++--
 renderer/src/geometry/cache.rs |   9 +++
 renderer/src/lib.rs            |  74 ++++++++++++++++---
 widget/src/image.rs            |  10 ++-
 widget/src/image/viewer.rs     |   7 +-
 widget/src/svg.rs              |   6 +-
 12 files changed, 316 insertions(+), 35 deletions(-)
 create mode 100644 renderer/src/custom.rs

diff --git a/core/src/image.rs b/core/src/image.rs
index e5fdcd83..32b95f03 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -186,11 +186,11 @@ pub trait Renderer: crate::Renderer {
     type Handle: Clone + Hash;
 
     /// Returns the dimensions of an image for the given [`Handle`].
-    fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
+    fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
 
     /// Draws an image with the given [`Handle`] and inside the provided
     /// `bounds`.
-    fn draw(
+    fn draw_image(
         &mut self,
         handle: Self::Handle,
         filter_method: FilterMethod,
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 1139b41c..47b09d32 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -10,7 +10,7 @@ use crate::{
 };
 
 /// A component that can be used by widgets to draw themselves on a screen.
-pub trait Renderer: Sized {
+pub trait Renderer {
     /// Draws the primitives recorded in the given closure in a new layer.
     ///
     /// The layer will clip its contents to the provided `bounds`.
diff --git a/core/src/svg.rs b/core/src/svg.rs
index d63e3c95..ab207cca 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -91,8 +91,13 @@ impl std::fmt::Debug for Data {
 /// [renderer]: crate::renderer
 pub trait Renderer: crate::Renderer {
     /// Returns the default dimensions of an SVG for the given [`Handle`].
-    fn dimensions(&self, handle: &Handle) -> Size<u32>;
+    fn measure_svg(&self, handle: &Handle) -> Size<u32>;
 
     /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
-    fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
+    fn draw_svg(
+        &mut self,
+        handle: Handle,
+        color: Option<Color>,
+        bounds: Rectangle,
+    );
 }
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 143f348b..e7154385 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -211,11 +211,11 @@ where
 {
     type Handle = image::Handle;
 
-    fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
+    fn measure_image(&self, handle: &image::Handle) -> Size<u32> {
         self.backend().dimensions(handle)
     }
 
-    fn draw(
+    fn draw_image(
         &mut self,
         handle: image::Handle,
         filter_method: image::FilterMethod,
@@ -233,11 +233,11 @@ impl<B> svg::Renderer for Renderer<B>
 where
     B: Backend + backend::Svg,
 {
-    fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
+    fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
         self.backend().viewport_dimensions(handle)
     }
 
-    fn draw(
+    fn draw_svg(
         &mut self,
         handle: svg::Handle,
         color: Option<Color>,
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index c23a814c..058fa36d 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -1,4 +1,5 @@
 use crate::core::Color;
+use crate::custom;
 use crate::graphics::compositor::{Information, SurfaceError, Window};
 use crate::graphics::{Error, Viewport};
 use crate::{Renderer, Settings};
@@ -10,12 +11,14 @@ pub enum Compositor {
     TinySkia(iced_tiny_skia::window::Compositor),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::window::Compositor),
+    Custom(Box<dyn custom::Compositor>),
 }
 
 pub enum Surface {
     TinySkia(iced_tiny_skia::window::Surface),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::window::Surface<'static>),
+    Custom(Box<dyn custom::Surface>),
 }
 
 impl crate::graphics::Compositor for Compositor {
@@ -56,6 +59,9 @@ impl crate::graphics::Compositor for Compositor {
             Compositor::Wgpu(compositor) => {
                 Renderer::Wgpu(compositor.create_renderer())
             }
+            Compositor::Custom(compositor) => {
+                Renderer::Custom(compositor.create_renderer())
+            }
         }
     }
 
@@ -73,6 +79,9 @@ impl crate::graphics::Compositor for Compositor {
             Self::Wgpu(compositor) => {
                 Surface::Wgpu(compositor.create_surface(window, width, height))
             }
+            Self::Custom(compositor) => Surface::Custom(
+                compositor.create_surface(Box::new(window), width, height),
+            ),
         }
     }
 
@@ -90,6 +99,9 @@ impl crate::graphics::Compositor for Compositor {
             (Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
                 compositor.configure_surface(surface, width, height);
             }
+            (Self::Custom(compositor), Surface::Custom(surface)) => {
+                compositor.configure_surface(surface, width, height);
+            }
             #[allow(unreachable_patterns)]
             _ => panic!(
                 "The provided surface is not compatible with the compositor."
@@ -102,6 +114,7 @@ impl crate::graphics::Compositor for Compositor {
             Self::TinySkia(compositor) => compositor.fetch_information(),
             #[cfg(feature = "wgpu")]
             Self::Wgpu(compositor) => compositor.fetch_information(),
+            Self::Custom(compositor) => compositor.fetch_information(),
         }
     }
 
@@ -144,6 +157,18 @@ impl crate::graphics::Compositor for Compositor {
                     overlay,
                 )
             }),
+
+            #[cfg(feature = "wgpu")]
+            (
+                Self::Custom(compositor),
+                crate::Renderer::Custom(renderer),
+                Surface::Custom(surface),
+            ) => renderer.present(
+                surface.as_mut(),
+                viewport,
+                background_color,
+                compositor.as_mut(),
+            ),
             #[allow(unreachable_patterns)]
             _ => panic!(
                 "The provided renderer or surface are not compatible \
diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs
new file mode 100644
index 00000000..680aa0b5
--- /dev/null
+++ b/renderer/src/custom.rs
@@ -0,0 +1,164 @@
+use crate::core::image;
+use crate::core::renderer;
+use crate::core::svg;
+use crate::core::text::Text;
+use crate::core::{
+    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+};
+use crate::graphics::compositor;
+use crate::graphics::text::{Editor, Paragraph};
+use crate::graphics::{Mesh, Viewport};
+
+#[cfg(feature = "geometry")]
+use crate::graphics::geometry::{self, Fill, Path, Stroke};
+
+use std::borrow::Cow;
+
+pub trait Renderer {
+    fn draw_mesh(&mut self, mesh: Mesh);
+
+    fn start_layer(&mut self);
+
+    fn end_layer(&mut self, bounds: Rectangle);
+
+    fn start_transformation(&mut self);
+
+    fn end_transformation(&mut self, transformation: Transformation);
+
+    fn fill_quad(&mut self, quad: renderer::Quad, background: Background);
+
+    fn clear(&mut self);
+
+    fn default_font(&self) -> Font;
+
+    fn default_size(&self) -> Pixels;
+
+    fn load_font(&mut self, bytes: Cow<'static, [u8]>);
+
+    fn fill_paragraph(
+        &mut self,
+        paragraph: &Paragraph,
+        position: Point,
+        color: Color,
+        clip_bounds: Rectangle,
+    );
+
+    fn fill_editor(
+        &mut self,
+        editor: &Editor,
+        position: Point,
+        color: Color,
+        clip_bounds: Rectangle,
+    );
+
+    fn fill_text(
+        &mut self,
+        text: Text<'_, Font>,
+        position: Point,
+        color: Color,
+        clip_bounds: Rectangle,
+    );
+
+    fn measure_image(&self, handle: &image::Handle) -> Size<u32>;
+
+    fn draw_image(
+        &mut self,
+        handle: image::Handle,
+        filter_method: image::FilterMethod,
+        bounds: Rectangle,
+    );
+
+    fn measure_svg(&self, handle: &svg::Handle) -> Size<u32>;
+
+    fn draw_svg(
+        &mut self,
+        handle: crate::core::svg::Handle,
+        color: Option<crate::core::Color>,
+        bounds: Rectangle,
+    );
+
+    #[cfg(feature = "geometry")]
+    fn new_frame(&self, size: Size) -> Box<dyn Frame>;
+
+    #[cfg(feature = "geometry")]
+    fn draw_geometry(&mut self, geometry: Box<dyn Geometry>);
+
+    fn present(
+        &mut self,
+        surface: &mut dyn Surface,
+        viewport: &Viewport,
+        background_color: Color,
+        compositor: &mut dyn Compositor,
+    ) -> Result<(), compositor::SurfaceError>;
+}
+
+#[cfg(feature = "geometry")]
+pub trait Frame: std::any::Any {
+    fn new(&self, size: Size) -> Box<dyn Frame>;
+
+    fn width(&self) -> f32;
+
+    fn height(&self) -> f32;
+
+    fn size(&self) -> Size;
+
+    fn center(&self) -> Point;
+
+    fn fill(&mut self, path: &Path, fill: Fill);
+
+    fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill);
+
+    fn stroke<'a>(&mut self, path: &Path, stroke: Stroke<'a>);
+
+    fn fill_text(&mut self, text: geometry::Text);
+
+    fn translate(&mut self, translation: crate::core::Vector);
+
+    fn rotate(&mut self, angle: crate::core::Radians);
+
+    fn scale(&mut self, scale: f32);
+
+    fn scale_nonuniform(&mut self, scale: crate::core::Vector);
+
+    fn push_transform(&mut self);
+
+    fn pop_transform(&mut self);
+
+    fn clip(&mut self, frame: Box<dyn Frame>, origin: Point);
+
+    fn into_geometry(self: Box<Self>) -> Box<dyn Geometry>;
+}
+
+#[cfg(feature = "geometry")]
+pub trait Geometry: std::any::Any + std::fmt::Debug {
+    fn transform(
+        self: Box<Self>,
+        transformation: Transformation,
+    ) -> Box<dyn Geometry>;
+
+    fn cache(self: Box<Self>) -> std::sync::Arc<dyn Geometry>;
+
+    fn load(self: std::sync::Arc<Self>) -> Box<dyn Geometry>;
+}
+
+pub trait Compositor: std::any::Any {
+    fn create_renderer(&self) -> Box<dyn Renderer>;
+
+    fn create_surface(
+        &mut self,
+        window: Box<dyn compositor::Window>,
+        width: u32,
+        height: u32,
+    ) -> Box<dyn Surface>;
+
+    fn configure_surface(
+        &mut self,
+        surface: &mut Box<dyn Surface>,
+        width: u32,
+        height: u32,
+    );
+
+    fn fetch_information(&self) -> compositor::Information;
+}
+
+pub trait Surface: std::any::Any {}
diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs
index 36435148..bf70c7fa 100644
--- a/renderer/src/geometry.rs
+++ b/renderer/src/geometry.rs
@@ -3,6 +3,7 @@ mod cache;
 pub use cache::Cache;
 
 use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector};
+use crate::custom;
 use crate::graphics::geometry::{Fill, Path, Stroke, Text};
 use crate::Renderer;
 
@@ -12,6 +13,7 @@ macro_rules! delegate {
             Self::TinySkia($name) => $body,
             #[cfg(feature = "wgpu")]
             Self::Wgpu($name) => $body,
+            Self::Custom($name) => $body,
         }
     };
 }
@@ -20,6 +22,7 @@ pub enum Geometry {
     TinySkia(iced_tiny_skia::Primitive),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::Primitive),
+    Custom(Box<dyn custom::Geometry>),
 }
 
 impl Geometry {
@@ -32,6 +35,9 @@ impl Geometry {
             Self::Wgpu(primitive) => {
                 Self::Wgpu(primitive.transform(transformation))
             }
+            Self::Custom(geometry) => {
+                Self::Custom(geometry.transform(transformation))
+            }
         }
     }
 }
@@ -40,6 +46,7 @@ pub enum Frame {
     TinySkia(iced_tiny_skia::geometry::Frame),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::geometry::Frame),
+    Custom(Box<dyn custom::Frame>),
 }
 
 impl Frame {
@@ -52,6 +59,9 @@ impl Frame {
             Renderer::Wgpu(_) => {
                 Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
             }
+            Renderer::Custom(renderer) => {
+                Frame::Custom(renderer.new_frame(size))
+            }
         }
     }
 
@@ -82,7 +92,7 @@ impl Frame {
     /// 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>) {
-        delegate!(self, frame, frame.fill(path, fill));
+        delegate!(self, frame, frame.fill(path, fill.into()));
     }
 
     /// Draws an axis-aligned rectangle given its top-left corner coordinate and
@@ -93,13 +103,17 @@ impl Frame {
         size: Size,
         fill: impl Into<Fill>,
     ) {
-        delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
+        delegate!(
+            self,
+            frame,
+            frame.fill_rectangle(top_left, size, fill.into())
+        );
     }
 
     /// 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>>) {
-        delegate!(self, frame, frame.stroke(path, stroke));
+        delegate!(self, frame, frame.stroke(path, stroke.into()));
     }
 
     /// Draws the characters of the given [`Text`] on the [`Frame`], filling
@@ -116,7 +130,7 @@ impl Frame {
     /// Support for vectorial text is planned, and should address all these
     /// limitations.
     pub fn fill_text(&mut self, text: impl Into<Text>) {
-        delegate!(self, frame, frame.fill_text(text));
+        delegate!(self, frame, frame.fill_text(text.into()));
     }
 
     /// Stores the current transform of the [`Frame`] and executes the given
@@ -155,6 +169,7 @@ impl Frame {
             Self::Wgpu(_) => {
                 Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size()))
             }
+            Self::Custom(frame) => Self::Custom(frame.new(region.size())),
         };
 
         let result = f(&mut frame);
@@ -169,6 +184,9 @@ impl Frame {
             (Self::Wgpu(target), Self::Wgpu(frame)) => {
                 target.clip(frame, origin);
             }
+            (Self::Custom(target), Self::Custom(frame)) => {
+                target.clip(frame, origin);
+            }
             #[allow(unreachable_patterns)]
             _ => unreachable!(),
         };
@@ -185,19 +203,19 @@ impl Frame {
     /// Applies a rotation in radians to the current transform of the [`Frame`].
     #[inline]
     pub fn rotate(&mut self, angle: impl Into<Radians>) {
-        delegate!(self, frame, frame.rotate(angle));
+        delegate!(self, frame, frame.rotate(angle.into()));
     }
 
     /// Applies a uniform scaling to the current transform of the [`Frame`].
     #[inline]
     pub fn scale(&mut self, scale: impl Into<f32>) {
-        delegate!(self, frame, frame.scale(scale));
+        delegate!(self, frame, frame.scale(scale.into()));
     }
 
     /// Applies a non-uniform scaling to the current transform of the [`Frame`].
     #[inline]
     pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
-        delegate!(self, frame, frame.scale_nonuniform(scale));
+        delegate!(self, frame, frame.scale_nonuniform(scale.into()));
     }
 
     pub fn into_geometry(self) -> Geometry {
@@ -205,6 +223,7 @@ impl Frame {
             Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()),
             #[cfg(feature = "wgpu")]
             Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()),
+            Self::Custom(frame) => Geometry::Custom(frame.into_geometry()),
         }
     }
 }
diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs
index 3aff76b9..6a43ddb6 100644
--- a/renderer/src/geometry/cache.rs
+++ b/renderer/src/geometry/cache.rs
@@ -1,4 +1,5 @@
 use crate::core::Size;
+use crate::custom;
 use crate::geometry::{Frame, Geometry};
 use crate::Renderer;
 
@@ -29,6 +30,7 @@ enum Internal {
     TinySkia(Arc<iced_tiny_skia::Primitive>),
     #[cfg(feature = "wgpu")]
     Wgpu(Arc<iced_wgpu::Primitive>),
+    Custom(Arc<dyn custom::Geometry>),
 }
 
 impl Cache {
@@ -82,6 +84,9 @@ impl Cache {
                             content: primitive.clone(),
                         });
                     }
+                    Internal::Custom(geometry) => {
+                        return Geometry::Custom(geometry.clone().load())
+                    }
                 }
             }
         }
@@ -100,6 +105,9 @@ impl Cache {
                 Geometry::Wgpu(primitive) => {
                     Internal::Wgpu(Arc::new(primitive))
                 }
+                Geometry::Custom(geometry) => {
+                    Internal::Custom(geometry.cache())
+                }
             }
         };
 
@@ -120,6 +128,7 @@ impl Cache {
                     content: primitive,
                 })
             }
+            Internal::Custom(geometry) => Geometry::Custom(geometry.load()),
         }
     }
 }
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index 757c264d..70741356 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -5,6 +5,7 @@
 pub use iced_wgpu as wgpu;
 
 pub mod compositor;
+pub mod custom;
 
 #[cfg(feature = "geometry")]
 pub mod geometry;
@@ -38,6 +39,7 @@ pub enum Renderer {
     TinySkia(iced_tiny_skia::Renderer),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::Renderer),
+    Custom(Box<dyn custom::Renderer>),
 }
 
 macro_rules! delegate {
@@ -46,6 +48,7 @@ macro_rules! delegate {
             Self::TinySkia($name) => $body,
             #[cfg(feature = "wgpu")]
             Self::Wgpu($name) => $body,
+            Self::Custom($name) => $body,
         }
     };
 }
@@ -62,6 +65,9 @@ impl Renderer {
                     iced_wgpu::primitive::Custom::Mesh(mesh),
                 ));
             }
+            Self::Custom(renderer) => {
+                renderer.draw_mesh(mesh);
+            }
         }
     }
 }
@@ -96,6 +102,18 @@ impl core::Renderer for Renderer {
                     _ => unreachable!(),
                 }
             }
+            Self::Custom(renderer) => {
+                renderer.start_layer();
+
+                f(self);
+
+                match self {
+                    Self::Custom(renderer) => {
+                        renderer.end_layer(bounds);
+                    }
+                    _ => unreachable!(),
+                }
+            }
         }
     }
 
@@ -132,6 +150,18 @@ impl core::Renderer for Renderer {
                     _ => unreachable!(),
                 }
             }
+            Self::Custom(renderer) => {
+                renderer.start_transformation();
+
+                f(self);
+
+                match self {
+                    Self::Custom(renderer) => {
+                        renderer.end_transformation(transformation);
+                    }
+                    _ => unreachable!(),
+                }
+            }
         }
     }
 
@@ -140,7 +170,7 @@ impl core::Renderer for Renderer {
         quad: renderer::Quad,
         background: impl Into<Background>,
     ) {
-        delegate!(self, renderer, renderer.fill_quad(quad, background));
+        delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
     }
 
     fn clear(&mut self) {
@@ -216,36 +246,43 @@ impl text::Renderer for Renderer {
 impl crate::core::image::Renderer for Renderer {
     type Handle = crate::core::image::Handle;
 
-    fn dimensions(
+    fn measure_image(
         &self,
         handle: &crate::core::image::Handle,
     ) -> core::Size<u32> {
-        delegate!(self, renderer, renderer.dimensions(handle))
+        delegate!(self, renderer, renderer.measure_image(handle))
     }
 
-    fn draw(
+    fn draw_image(
         &mut self,
         handle: crate::core::image::Handle,
         filter_method: crate::core::image::FilterMethod,
         bounds: Rectangle,
     ) {
-        delegate!(self, renderer, renderer.draw(handle, filter_method, bounds));
+        delegate!(
+            self,
+            renderer,
+            renderer.draw_image(handle, filter_method, bounds)
+        );
     }
 }
 
 #[cfg(feature = "svg")]
 impl crate::core::svg::Renderer for Renderer {
-    fn dimensions(&self, handle: &crate::core::svg::Handle) -> core::Size<u32> {
-        delegate!(self, renderer, renderer.dimensions(handle))
+    fn measure_svg(
+        &self,
+        handle: &crate::core::svg::Handle,
+    ) -> core::Size<u32> {
+        delegate!(self, renderer, renderer.measure_svg(handle))
     }
 
-    fn draw(
+    fn draw_svg(
         &mut self,
         handle: crate::core::svg::Handle,
         color: Option<crate::core::Color>,
         bounds: Rectangle,
     ) {
-        delegate!(self, renderer, renderer.draw(handle, color, bounds));
+        delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
     }
 }
 
@@ -263,6 +300,7 @@ impl crate::graphics::geometry::Renderer for Renderer {
                         }
                         #[cfg(feature = "wgpu")]
                         crate::Geometry::Wgpu(_) => unreachable!(),
+                        crate::Geometry::Custom(_) => unreachable!(),
                     }
                 }
             }
@@ -274,6 +312,19 @@ impl crate::graphics::geometry::Renderer for Renderer {
                             renderer.draw_primitive(primitive);
                         }
                         crate::Geometry::TinySkia(_) => unreachable!(),
+                        crate::Geometry::Custom(_) => unreachable!(),
+                    }
+                }
+            }
+            Self::Custom(renderer) => {
+                for layer in layers {
+                    match layer {
+                        crate::Geometry::Custom(geometry) => {
+                            renderer.draw_geometry(geometry);
+                        }
+                        crate::Geometry::TinySkia(_) => unreachable!(),
+                        #[cfg(feature = "wgpu")]
+                        crate::Geometry::Wgpu(_) => unreachable!(),
                     }
                 }
             }
@@ -297,6 +348,11 @@ impl iced_wgpu::primitive::pipeline::Renderer for Renderer {
             Self::Wgpu(renderer) => {
                 renderer.draw_pipeline_primitive(bounds, primitive);
             }
+            Self::Custom(_renderer) => {
+                log::warn!(
+                    "Custom shader primitive is unavailable with custom renderer."
+                );
+            }
         }
     }
 }
diff --git a/widget/src/image.rs b/widget/src/image.rs
index ccf1f175..f673c7b3 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -93,7 +93,7 @@ where
 {
     // The raw w/h of the underlying image
     let image_size = {
-        let Size { width, height } = renderer.dimensions(handle);
+        let Size { width, height } = renderer.measure_image(handle);
 
         Size::new(width as f32, height as f32)
     };
@@ -130,7 +130,7 @@ pub fn draw<Renderer, Handle>(
     Renderer: image::Renderer<Handle = Handle>,
     Handle: Clone + Hash,
 {
-    let Size { width, height } = renderer.dimensions(handle);
+    let Size { width, height } = renderer.measure_image(handle);
     let image_size = Size::new(width as f32, height as f32);
 
     let bounds = layout.bounds();
@@ -148,7 +148,11 @@ pub fn draw<Renderer, Handle>(
             ..bounds
         };
 
-        renderer.draw(handle.clone(), filter_method, drawing_bounds + offset);
+        renderer.draw_image(
+            handle.clone(),
+            filter_method,
+            drawing_bounds + offset,
+        );
     };
 
     if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index 2e3fd713..5f7bb345 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -117,7 +117,7 @@ where
         renderer: &Renderer,
         limits: &layout::Limits,
     ) -> layout::Node {
-        let Size { width, height } = renderer.dimensions(&self.handle);
+        let Size { width, height } = renderer.measure_image(&self.handle);
 
         let mut size = limits.resolve(
             self.width,
@@ -335,8 +335,7 @@ where
 
         renderer.with_layer(bounds, |renderer| {
             renderer.with_translation(translation, |renderer| {
-                image::Renderer::draw(
-                    renderer,
+                renderer.draw_image(
                     self.handle.clone(),
                     self.filter_method,
                     Rectangle {
@@ -421,7 +420,7 @@ pub fn image_size<Renderer>(
 where
     Renderer: image::Renderer,
 {
-    let Size { width, height } = renderer.dimensions(handle);
+    let Size { width, height } = renderer.measure_image(handle);
 
     let (width, height) = {
         let dimensions = (width as f32, height as f32);
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index 1ac07ade..53c45bcb 100644
--- a/widget/src/svg.rs
+++ b/widget/src/svg.rs
@@ -108,7 +108,7 @@ where
         limits: &layout::Limits,
     ) -> layout::Node {
         // The raw w/h of the underlying image
-        let Size { width, height } = renderer.dimensions(&self.handle);
+        let Size { width, height } = renderer.measure_svg(&self.handle);
         let image_size = Size::new(width as f32, height as f32);
 
         // The size to be available to the widget prior to `Shrink`ing
@@ -142,7 +142,7 @@ where
         cursor: mouse::Cursor,
         _viewport: &Rectangle,
     ) {
-        let Size { width, height } = renderer.dimensions(&self.handle);
+        let Size { width, height } = renderer.measure_svg(&self.handle);
         let image_size = Size::new(width as f32, height as f32);
 
         let bounds = layout.bounds();
@@ -169,7 +169,7 @@ where
 
             let appearance = (self.style)(theme, status);
 
-            renderer.draw(
+            renderer.draw_svg(
                 self.handle.clone(),
                 appearance.color,
                 drawing_bounds + offset,
-- 
cgit 


From 9171df1e356530410a7ceadadd78fd3dcf150dfd Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 21 Mar 2024 06:00:55 +0100
Subject: Gate `Custom` variants in `iced_renderer` behind `custom` feature

---
 renderer/Cargo.toml            |  1 +
 renderer/src/compositor.rs     | 13 +++++++++----
 renderer/src/geometry.rs       | 13 ++++++++++---
 renderer/src/geometry/cache.rs |  7 +++++--
 renderer/src/lib.rs            |  9 +++++++++
 5 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
index 5cce2427..18e9e8f8 100644
--- a/renderer/Cargo.toml
+++ b/renderer/Cargo.toml
@@ -19,6 +19,7 @@ tracing = ["iced_wgpu?/tracing"]
 web-colors = ["iced_wgpu?/web-colors"]
 webgl = ["iced_wgpu?/webgl"]
 fira-sans = ["iced_graphics/fira-sans"]
+custom = []
 
 [dependencies]
 iced_graphics.workspace = true
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index 058fa36d..012ad3c0 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -1,5 +1,4 @@
 use crate::core::Color;
-use crate::custom;
 use crate::graphics::compositor::{Information, SurfaceError, Window};
 use crate::graphics::{Error, Viewport};
 use crate::{Renderer, Settings};
@@ -11,14 +10,16 @@ pub enum Compositor {
     TinySkia(iced_tiny_skia::window::Compositor),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::window::Compositor),
-    Custom(Box<dyn custom::Compositor>),
+    #[cfg(feature = "custom")]
+    Custom(Box<dyn crate::custom::Compositor>),
 }
 
 pub enum Surface {
     TinySkia(iced_tiny_skia::window::Surface),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::window::Surface<'static>),
-    Custom(Box<dyn custom::Surface>),
+    #[cfg(feature = "custom")]
+    Custom(Box<dyn crate::custom::Surface>),
 }
 
 impl crate::graphics::Compositor for Compositor {
@@ -59,6 +60,7 @@ impl crate::graphics::Compositor for Compositor {
             Compositor::Wgpu(compositor) => {
                 Renderer::Wgpu(compositor.create_renderer())
             }
+            #[cfg(feature = "custom")]
             Compositor::Custom(compositor) => {
                 Renderer::Custom(compositor.create_renderer())
             }
@@ -79,6 +81,7 @@ impl crate::graphics::Compositor for Compositor {
             Self::Wgpu(compositor) => {
                 Surface::Wgpu(compositor.create_surface(window, width, height))
             }
+            #[cfg(feature = "custom")]
             Self::Custom(compositor) => Surface::Custom(
                 compositor.create_surface(Box::new(window), width, height),
             ),
@@ -99,6 +102,7 @@ impl crate::graphics::Compositor for Compositor {
             (Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
                 compositor.configure_surface(surface, width, height);
             }
+            #[cfg(feature = "custom")]
             (Self::Custom(compositor), Surface::Custom(surface)) => {
                 compositor.configure_surface(surface, width, height);
             }
@@ -114,6 +118,7 @@ impl crate::graphics::Compositor for Compositor {
             Self::TinySkia(compositor) => compositor.fetch_information(),
             #[cfg(feature = "wgpu")]
             Self::Wgpu(compositor) => compositor.fetch_information(),
+            #[cfg(feature = "custom")]
             Self::Custom(compositor) => compositor.fetch_information(),
         }
     }
@@ -158,7 +163,7 @@ impl crate::graphics::Compositor for Compositor {
                 )
             }),
 
-            #[cfg(feature = "wgpu")]
+            #[cfg(feature = "custom")]
             (
                 Self::Custom(compositor),
                 crate::Renderer::Custom(renderer),
diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs
index bf70c7fa..a16cecd5 100644
--- a/renderer/src/geometry.rs
+++ b/renderer/src/geometry.rs
@@ -3,7 +3,6 @@ mod cache;
 pub use cache::Cache;
 
 use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector};
-use crate::custom;
 use crate::graphics::geometry::{Fill, Path, Stroke, Text};
 use crate::Renderer;
 
@@ -13,6 +12,7 @@ macro_rules! delegate {
             Self::TinySkia($name) => $body,
             #[cfg(feature = "wgpu")]
             Self::Wgpu($name) => $body,
+            #[cfg(feature = "custom")]
             Self::Custom($name) => $body,
         }
     };
@@ -22,7 +22,8 @@ pub enum Geometry {
     TinySkia(iced_tiny_skia::Primitive),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::Primitive),
-    Custom(Box<dyn custom::Geometry>),
+    #[cfg(feature = "custom")]
+    Custom(Box<dyn crate::custom::Geometry>),
 }
 
 impl Geometry {
@@ -35,6 +36,7 @@ impl Geometry {
             Self::Wgpu(primitive) => {
                 Self::Wgpu(primitive.transform(transformation))
             }
+            #[cfg(feature = "custom")]
             Self::Custom(geometry) => {
                 Self::Custom(geometry.transform(transformation))
             }
@@ -46,7 +48,8 @@ pub enum Frame {
     TinySkia(iced_tiny_skia::geometry::Frame),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::geometry::Frame),
-    Custom(Box<dyn custom::Frame>),
+    #[cfg(feature = "custom")]
+    Custom(Box<dyn crate::custom::Frame>),
 }
 
 impl Frame {
@@ -59,6 +62,7 @@ impl Frame {
             Renderer::Wgpu(_) => {
                 Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
             }
+            #[cfg(feature = "custom")]
             Renderer::Custom(renderer) => {
                 Frame::Custom(renderer.new_frame(size))
             }
@@ -169,6 +173,7 @@ impl Frame {
             Self::Wgpu(_) => {
                 Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size()))
             }
+            #[cfg(feature = "custom")]
             Self::Custom(frame) => Self::Custom(frame.new(region.size())),
         };
 
@@ -184,6 +189,7 @@ impl Frame {
             (Self::Wgpu(target), Self::Wgpu(frame)) => {
                 target.clip(frame, origin);
             }
+            #[cfg(feature = "custom")]
             (Self::Custom(target), Self::Custom(frame)) => {
                 target.clip(frame, origin);
             }
@@ -223,6 +229,7 @@ impl Frame {
             Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()),
             #[cfg(feature = "wgpu")]
             Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()),
+            #[cfg(feature = "custom")]
             Self::Custom(frame) => Geometry::Custom(frame.into_geometry()),
         }
     }
diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs
index 6a43ddb6..20f73f22 100644
--- a/renderer/src/geometry/cache.rs
+++ b/renderer/src/geometry/cache.rs
@@ -1,5 +1,4 @@
 use crate::core::Size;
-use crate::custom;
 use crate::geometry::{Frame, Geometry};
 use crate::Renderer;
 
@@ -30,7 +29,8 @@ enum Internal {
     TinySkia(Arc<iced_tiny_skia::Primitive>),
     #[cfg(feature = "wgpu")]
     Wgpu(Arc<iced_wgpu::Primitive>),
-    Custom(Arc<dyn custom::Geometry>),
+    #[cfg(feature = "custom")]
+    Custom(Arc<dyn crate::custom::Geometry>),
 }
 
 impl Cache {
@@ -84,6 +84,7 @@ impl Cache {
                             content: primitive.clone(),
                         });
                     }
+                    #[cfg(feature = "custom")]
                     Internal::Custom(geometry) => {
                         return Geometry::Custom(geometry.clone().load())
                     }
@@ -105,6 +106,7 @@ impl Cache {
                 Geometry::Wgpu(primitive) => {
                     Internal::Wgpu(Arc::new(primitive))
                 }
+                #[cfg(feature = "custom")]
                 Geometry::Custom(geometry) => {
                     Internal::Custom(geometry.cache())
                 }
@@ -128,6 +130,7 @@ impl Cache {
                     content: primitive,
                 })
             }
+            #[cfg(feature = "custom")]
             Internal::Custom(geometry) => Geometry::Custom(geometry.load()),
         }
     }
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index 70741356..67096115 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -39,6 +39,7 @@ pub enum Renderer {
     TinySkia(iced_tiny_skia::Renderer),
     #[cfg(feature = "wgpu")]
     Wgpu(iced_wgpu::Renderer),
+    #[cfg(feature = "custom")]
     Custom(Box<dyn custom::Renderer>),
 }
 
@@ -48,6 +49,7 @@ macro_rules! delegate {
             Self::TinySkia($name) => $body,
             #[cfg(feature = "wgpu")]
             Self::Wgpu($name) => $body,
+            #[cfg(feature = "custom")]
             Self::Custom($name) => $body,
         }
     };
@@ -65,6 +67,7 @@ impl Renderer {
                     iced_wgpu::primitive::Custom::Mesh(mesh),
                 ));
             }
+            #[cfg(feature = "custom")]
             Self::Custom(renderer) => {
                 renderer.draw_mesh(mesh);
             }
@@ -102,6 +105,7 @@ impl core::Renderer for Renderer {
                     _ => unreachable!(),
                 }
             }
+            #[cfg(feature = "custom")]
             Self::Custom(renderer) => {
                 renderer.start_layer();
 
@@ -150,6 +154,7 @@ impl core::Renderer for Renderer {
                     _ => unreachable!(),
                 }
             }
+            #[cfg(feature = "custom")]
             Self::Custom(renderer) => {
                 renderer.start_transformation();
 
@@ -300,6 +305,7 @@ impl crate::graphics::geometry::Renderer for Renderer {
                         }
                         #[cfg(feature = "wgpu")]
                         crate::Geometry::Wgpu(_) => unreachable!(),
+                        #[cfg(feature = "custom")]
                         crate::Geometry::Custom(_) => unreachable!(),
                     }
                 }
@@ -312,10 +318,12 @@ impl crate::graphics::geometry::Renderer for Renderer {
                             renderer.draw_primitive(primitive);
                         }
                         crate::Geometry::TinySkia(_) => unreachable!(),
+                        #[cfg(feature = "custom")]
                         crate::Geometry::Custom(_) => unreachable!(),
                     }
                 }
             }
+            #[cfg(feature = "custom")]
             Self::Custom(renderer) => {
                 for layer in layers {
                     match layer {
@@ -348,6 +356,7 @@ impl iced_wgpu::primitive::pipeline::Renderer for Renderer {
             Self::Wgpu(renderer) => {
                 renderer.draw_pipeline_primitive(bounds, primitive);
             }
+            #[cfg(feature = "custom")]
             Self::Custom(_renderer) => {
                 log::warn!(
                     "Custom shader primitive is unavailable with custom renderer."
-- 
cgit 


From 7e4ae8450e1f28c15717ca5ca9748981af9c9541 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 21 Mar 2024 06:03:31 +0100
Subject: Use `&mut dyn Surface` instead of `&mut Box<dyn Surface>`

---
 renderer/src/compositor.rs | 2 +-
 renderer/src/custom.rs     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index 012ad3c0..3d0b3ad0 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -104,7 +104,7 @@ impl crate::graphics::Compositor for Compositor {
             }
             #[cfg(feature = "custom")]
             (Self::Custom(compositor), Surface::Custom(surface)) => {
-                compositor.configure_surface(surface, width, height);
+                compositor.configure_surface(surface.as_mut(), width, height);
             }
             #[allow(unreachable_patterns)]
             _ => panic!(
diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs
index 680aa0b5..04090ccb 100644
--- a/renderer/src/custom.rs
+++ b/renderer/src/custom.rs
@@ -153,7 +153,7 @@ pub trait Compositor: std::any::Any {
 
     fn configure_surface(
         &mut self,
-        surface: &mut Box<dyn Surface>,
+        surface: &mut dyn Surface,
         width: u32,
         height: u32,
     );
-- 
cgit 


From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 21 Mar 2024 22:27:17 +0100
Subject: Implement composable, type-safe renderer fallback

---
 core/src/renderer.rs                      |  28 +-
 core/src/renderer/null.rs                 |  13 +-
 examples/arc/src/main.rs                  |  10 +-
 examples/bezier_tool/src/main.rs          |  33 +-
 examples/clock/src/main.rs                |   8 +-
 examples/color_palette/src/main.rs        |  10 +-
 examples/game_of_life/src/main.rs         |  18 +-
 examples/geometry/src/main.rs             |   4 +-
 examples/layout/src/main.rs               |  10 +-
 examples/loading_spinners/src/circular.rs |   6 +-
 examples/multitouch/src/main.rs           |  10 +-
 examples/sierpinski_triangle/src/main.rs  |   8 +-
 examples/solar_system/src/main.rs         |   7 +-
 examples/vectorial_text/src/main.rs       |   8 +-
 graphics/src/backend.rs                   |   3 +-
 graphics/src/geometry.rs                  | 272 ++++++++++++++-
 graphics/src/lib.rs                       |   4 +-
 graphics/src/mesh.rs                      |   6 +-
 graphics/src/renderer.rs                  | 106 +++---
 renderer/src/compositor.rs                | 299 ----------------
 renderer/src/custom.rs                    |   4 +-
 renderer/src/fallback.rs                  | 562 ++++++++++++++++++++++++++++++
 renderer/src/lib.rs                       | 368 ++-----------------
 renderer/src/settings.rs                  |  21 ++
 tiny_skia/src/backend.rs                  |   9 +
 tiny_skia/src/geometry.rs                 |  60 ++--
 tiny_skia/src/primitive.rs                |  10 +-
 wgpu/src/backend.rs                       |   9 +
 wgpu/src/geometry.rs                      | 493 ++++++++++++--------------
 wgpu/src/primitive.rs                     |   8 +
 widget/src/canvas.rs                      |  19 +-
 widget/src/canvas/program.rs              |  19 +-
 widget/src/qr_code.rs                     |   9 +-
 winit/src/application.rs                  |   4 +-
 winit/src/multi_window.rs                 |   8 +-
 35 files changed, 1365 insertions(+), 1101 deletions(-)
 create mode 100644 renderer/src/fallback.rs

diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 47b09d32..406b33f3 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -11,17 +11,41 @@ use crate::{
 
 /// A component that can be used by widgets to draw themselves on a screen.
 pub trait Renderer {
+    /// Starts recording a new layer.
+    fn start_layer(&mut self);
+
+    /// Ends recording a new layer.
+    ///
+    /// The new layer will clip its contents to the provided `bounds`.
+    fn end_layer(&mut self, bounds: Rectangle);
+
     /// Draws the primitives recorded in the given closure in a new layer.
     ///
     /// The layer will clip its contents to the provided `bounds`.
-    fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
+    fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
+        self.start_layer();
+        f(self);
+        self.end_layer(bounds);
+    }
+
+    /// Starts recording with a new [`Transformation`].
+    fn start_transformation(&mut self);
+
+    /// Ends recording a new layer.
+    ///
+    /// The new layer will clip its contents to the provided `bounds`.
+    fn end_transformation(&mut self, transformation: Transformation);
 
     /// Applies a [`Transformation`] to the primitives recorded in the given closure.
     fn with_transformation(
         &mut self,
         transformation: Transformation,
         f: impl FnOnce(&mut Self),
-    );
+    ) {
+        self.start_transformation();
+        f(self);
+        self.end_transformation(transformation);
+    }
 
     /// Applies a translation to the primitives recorded in the given closure.
     fn with_translation(
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 83688ff7..0d7b7c14 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -21,14 +21,13 @@ impl Null {
 }
 
 impl Renderer for Null {
-    fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
+    fn start_layer(&mut self) {}
 
-    fn with_transformation(
-        &mut self,
-        _transformation: Transformation,
-        _f: impl FnOnce(&mut Self),
-    ) {
-    }
+    fn end_layer(&mut self, _bounds: Rectangle) {}
+
+    fn start_transformation(&mut self) {}
+
+    fn end_transformation(&mut self, _transformation: Transformation) {}
 
     fn clear(&mut self) {}
 
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
index 4576404f..a7893efa 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -1,9 +1,7 @@
 use std::{f32::consts::PI, time::Instant};
 
 use iced::mouse;
-use iced::widget::canvas::{
-    self, stroke, Cache, Canvas, Geometry, Path, Stroke,
-};
+use iced::widget::canvas::{self, stroke, Cache, Canvas, Frame, Path, Stroke};
 use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme};
 
 pub fn main() -> iced::Result {
@@ -57,11 +55,11 @@ impl<Message> canvas::Program<Message> for Arc {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<Geometry> {
+    ) {
         let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
             let palette = theme.palette();
 
@@ -104,6 +102,6 @@ impl<Message> canvas::Program<Message> for Arc {
             );
         });
 
-        vec![geometry]
+        renderer.draw_geometry([geometry]);
     }
 }
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index cf70bd40..e51f2a31 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -52,7 +52,7 @@ impl Example {
 mod bezier {
     use iced::mouse;
     use iced::widget::canvas::event::{self, Event};
-    use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
+    use iced::widget::canvas::{self, frame, Canvas, Frame, Path, Stroke};
     use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
 
     #[derive(Default)]
@@ -138,30 +138,25 @@ mod bezier {
         fn draw(
             &self,
             state: &Self::State,
-            renderer: &Renderer,
+            renderer: &mut Renderer,
             _theme: &Theme,
             bounds: Rectangle,
             cursor: mouse::Cursor,
-        ) -> Vec<Geometry> {
-            let content = self.state.cache.draw(
-                renderer,
-                bounds.size(),
-                |frame: &mut Frame| {
+        ) {
+            let content =
+                self.state.cache.draw(renderer, bounds.size(), |frame| {
                     Curve::draw_all(self.curves, frame);
 
                     frame.stroke(
                         &Path::rectangle(Point::ORIGIN, frame.size()),
                         Stroke::default().with_width(2.0),
                     );
-                },
-            );
+                });
 
-            if let Some(pending) = state {
-                let pending_curve = pending.draw(renderer, bounds, cursor);
+            renderer.draw_geometry([content]);
 
-                vec![content, pending_curve]
-            } else {
-                vec![content]
+            if let Some(pending) = state {
+                pending.draw(renderer, bounds, cursor);
             }
         }
 
@@ -187,7 +182,7 @@ mod bezier {
     }
 
     impl Curve {
-        fn draw_all(curves: &[Curve], frame: &mut Frame) {
+        fn draw_all(curves: &[Curve], frame: &mut impl Frame) {
             let curves = Path::new(|p| {
                 for curve in curves {
                     p.move_to(curve.from);
@@ -208,11 +203,11 @@ mod bezier {
     impl Pending {
         fn draw(
             &self,
-            renderer: &Renderer,
+            renderer: &mut Renderer,
             bounds: Rectangle,
             cursor: mouse::Cursor,
-        ) -> Geometry {
-            let mut frame = Frame::new(renderer, bounds.size());
+        ) {
+            let mut frame = frame(renderer, bounds.size());
 
             if let Some(cursor_position) = cursor.position_in(bounds) {
                 match *self {
@@ -232,7 +227,7 @@ mod bezier {
                 };
             }
 
-            frame.into_geometry()
+            renderer.draw_geometry([frame]);
         }
     }
 }
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index 897f8f1b..9f78903c 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,6 +1,6 @@
 use iced::alignment;
 use iced::mouse;
-use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
+use iced::widget::canvas::{stroke, Cache, Frame, LineCap, Path, Stroke};
 use iced::widget::{canvas, container};
 use iced::{
     Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
@@ -82,11 +82,11 @@ impl<Message> canvas::Program<Message> for Clock {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<Geometry> {
+    ) {
         let clock = self.clock.draw(renderer, bounds.size(), |frame| {
             let palette = theme.extended_palette();
 
@@ -163,7 +163,7 @@ impl<Message> canvas::Program<Message> for Clock {
             });
         });
 
-        vec![clock]
+        renderer.draw_geometry([clock]);
     }
 }
 
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index d9325edb..400766ff 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -1,6 +1,6 @@
 use iced::alignment::{self, Alignment};
 use iced::mouse;
-use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
+use iced::widget::canvas::{self, Canvas, Frame, Path};
 use iced::widget::{column, row, text, Slider};
 use iced::{
     Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size,
@@ -156,7 +156,7 @@ impl Theme {
             .into()
     }
 
-    fn draw(&self, frame: &mut Frame, text_color: Color) {
+    fn draw(&self, frame: &mut impl Frame, text_color: Color) {
         let pad = 20.0;
 
         let box_size = Size {
@@ -252,18 +252,18 @@ impl<Message> canvas::Program<Message> for Theme {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         theme: &iced::Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<Geometry> {
+    ) {
         let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
             let palette = theme.extended_palette();
 
             self.draw(frame, palette.background.base.text);
         });
 
-        vec![theme]
+        renderer.draw_geometry([theme]);
     }
 }
 
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index 2b0fae0b..f681b4cc 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -193,7 +193,7 @@ mod grid {
     use iced::touch;
     use iced::widget::canvas;
     use iced::widget::canvas::event::{self, Event};
-    use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
+    use iced::widget::canvas::{frame, Cache, Canvas, Frame, Path, Text};
     use iced::{
         Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
     };
@@ -516,11 +516,11 @@ mod grid {
         fn draw(
             &self,
             _interaction: &Interaction,
-            renderer: &Renderer,
+            renderer: &mut Renderer,
             _theme: &Theme,
             bounds: Rectangle,
             cursor: mouse::Cursor,
-        ) -> Vec<Geometry> {
+        ) {
             let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
 
             let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
@@ -546,7 +546,7 @@ mod grid {
             });
 
             let overlay = {
-                let mut frame = Frame::new(renderer, bounds.size());
+                let mut frame = frame(renderer, bounds.size());
 
                 let hovered_cell = cursor.position_in(bounds).map(|position| {
                     Cell::at(self.project(position, frame.size()))
@@ -599,12 +599,10 @@ mod grid {
                     ..text
                 });
 
-                frame.into_geometry()
+                frame.into()
             };
 
-            if self.scaling < 0.2 || !self.show_lines {
-                vec![life, overlay]
-            } else {
+            if self.scaling >= 0.2 && self.show_lines {
                 let grid =
                     self.grid_cache.draw(renderer, bounds.size(), |frame| {
                         frame.translate(center);
@@ -640,7 +638,9 @@ mod grid {
                         }
                     });
 
-                vec![life, grid, overlay]
+                renderer.draw_geometry([life, grid, overlay]);
+            } else {
+                renderer.draw_geometry([life, overlay]);
             }
         }
 
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 63efcbdd..16cdb86f 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -44,7 +44,9 @@ mod rainbow {
             cursor: mouse::Cursor,
             _viewport: &Rectangle,
         ) {
-            use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
+            use iced::advanced::graphics::mesh::{
+                self, Mesh, Renderer as _, SolidVertex2D,
+            };
             use iced::advanced::Renderer as _;
 
             let bounds = layout.bounds();
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index 713e2b70..198237f5 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -292,12 +292,14 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
         fn draw(
             &self,
             _state: &Self::State,
-            renderer: &Renderer,
+            renderer: &mut Renderer,
             theme: &Theme,
             bounds: Rectangle,
             _cursor: mouse::Cursor,
-        ) -> Vec<canvas::Geometry> {
-            let mut frame = canvas::Frame::new(renderer, bounds.size());
+        ) {
+            use canvas::Frame;
+
+            let mut frame = canvas::frame(renderer, bounds.size());
 
             let palette = theme.extended_palette();
 
@@ -307,7 +309,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
                 palette.background.strong.color,
             );
 
-            vec![frame.into_geometry()]
+            renderer.draw_geometry([frame]);
         }
     }
 
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
index 12670ed1..306988af 100644
--- a/examples/loading_spinners/src/circular.rs
+++ b/examples/loading_spinners/src/circular.rs
@@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
 use iced::event;
 use iced::mouse;
 use iced::time::Instant;
-use iced::widget::canvas;
+use iced::widget::canvas::{self, Frame};
 use iced::window::{self, RedrawRequest};
 use iced::{
     Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
@@ -356,9 +356,7 @@ where
         renderer.with_translation(
             Vector::new(bounds.x, bounds.y),
             |renderer| {
-                use iced::advanced::graphics::geometry::Renderer as _;
-
-                renderer.draw(vec![geometry]);
+                renderer.draw_geometry([geometry]);
             },
         );
     }
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index 2453c7f5..41bd0151 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -5,7 +5,7 @@ use iced::mouse;
 use iced::touch;
 use iced::widget::canvas::event;
 use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::{self, Canvas, Geometry};
+use iced::widget::canvas::{self, Canvas};
 use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme};
 
 use std::collections::HashMap;
@@ -83,11 +83,13 @@ impl canvas::Program<Message> for Multitouch {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         _theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<Geometry> {
+    ) {
+        use canvas::Frame;
+
         let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
             if self.fingers.len() < 2 {
                 return;
@@ -154,6 +156,6 @@ impl canvas::Program<Message> for Multitouch {
             }
         });
 
-        vec![fingerweb]
+        renderer.draw_geometry([fingerweb]);
     }
 }
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index 07ae05d6..b440b8b4 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -107,11 +107,13 @@ impl canvas::Program<Message> for SierpinskiGraph {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         _theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<canvas::Geometry> {
+    ) {
+        use canvas::Frame;
+
         let geom = self.cache.draw(renderer, bounds.size(), |frame| {
             frame.stroke(
                 &canvas::Path::rectangle(Point::ORIGIN, frame.size()),
@@ -139,7 +141,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
             });
         });
 
-        vec![geom]
+        renderer.draw_geometry([geom]);
     }
 }
 
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index b5228f09..dd36b711 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -126,11 +126,12 @@ impl<Message> canvas::Program<Message> for State {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         _theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<canvas::Geometry> {
+    ) {
+        use canvas::Frame;
         use std::f32::consts::PI;
 
         let background =
@@ -197,7 +198,7 @@ impl<Message> canvas::Program<Message> for State {
             });
         });
 
-        vec![background, system]
+        renderer.draw_geometry([background, system]);
     }
 }
 
diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs
index a7391e23..9f5baac8 100644
--- a/examples/vectorial_text/src/main.rs
+++ b/examples/vectorial_text/src/main.rs
@@ -124,11 +124,13 @@ impl<Message> canvas::Program<Message> for State {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) -> Vec<canvas::Geometry> {
+    ) {
+        use canvas::Frame;
+
         let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
             let palette = theme.palette();
             let center = bounds.center();
@@ -153,7 +155,7 @@ impl<Message> canvas::Program<Message> for State {
             });
         });
 
-        vec![geometry]
+        renderer.draw_geometry([geometry]);
     }
 }
 
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index 10eb337f..e394c956 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -2,6 +2,7 @@
 use crate::core::image;
 use crate::core::svg;
 use crate::core::Size;
+use crate::Mesh;
 
 use std::borrow::Cow;
 
@@ -10,7 +11,7 @@ use std::borrow::Cow;
 /// [`Renderer`]: crate::Renderer
 pub trait Backend {
     /// The custom kind of primitives this [`Backend`] supports.
-    type Primitive;
+    type Primitive: TryFrom<Mesh, Error = &'static str>;
 }
 
 /// A graphics backend that supports text rendering.
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index d7d6a0aa..cd4c9267 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -14,11 +14,277 @@ pub use text::Text;
 
 pub use crate::gradient::{self, Gradient};
 
+use crate::core::{Point, Radians, Rectangle, Size, Vector};
+use crate::Primitive;
+
+use std::cell::RefCell;
+use std::sync::Arc;
+
+pub fn frame<Renderer>(renderer: &Renderer, size: Size) -> Renderer::Frame
+where
+    Renderer: self::Renderer,
+{
+    renderer.new_frame(size)
+}
+
 /// A renderer capable of drawing some [`Self::Geometry`].
 pub trait Renderer: crate::core::Renderer {
     /// The kind of geometry this renderer can draw.
-    type Geometry;
+    type Geometry: Geometry;
+
+    /// The kind of [`Frame`] this renderer supports.
+    type Frame: Frame<Geometry = Self::Geometry>;
+
+    fn new_frame(&self, size: Size) -> Self::Frame;
+
+    /// Draws the given [`Self::Geometry`].
+    fn draw_geometry(&mut self, geometry: Self::Geometry);
+}
+
+pub trait Backend {
+    /// The kind of [`Frame`] this backend supports.
+    type Frame: Frame;
+
+    fn new_frame(&self, size: Size) -> Self::Frame;
+}
+
+pub trait Frame: Sized + Into<Self::Geometry> {
+    /// The kind of geometry this frame can draw.
+    type Geometry: Geometry;
+
+    /// Returns the width of the [`Frame`].
+    fn width(&self) -> f32;
+
+    /// Returns the height of the [`Frame`].
+    fn height(&self) -> f32;
+
+    /// Returns the dimensions of the [`Frame`].
+    fn size(&self) -> Size;
+
+    /// Returns the coordinate of the center of the [`Frame`].
+    fn center(&self) -> Point;
+
+    /// Draws the given [`Path`] on the [`Frame`] by filling it with the
+    /// provided style.
+    fn fill(&mut self, path: &Path, fill: impl Into<Fill>);
+
+    /// Draws an axis-aligned rectangle given its top-left corner coordinate and
+    /// its `Size` on the [`Frame`] by filling it with the provided style.
+    fn fill_rectangle(
+        &mut self,
+        top_left: Point,
+        size: Size,
+        fill: impl Into<Fill>,
+    );
+
+    /// Draws the stroke of the given [`Path`] on the [`Frame`] with the
+    /// provided style.
+    fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
+
+    /// 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.
+    fn fill_text(&mut self, text: impl Into<Text>);
+
+    /// 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]
+    fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
+        self.push_transform();
+
+        let result = f(self);
+
+        self.pop_transform();
+
+        result
+    }
+
+    /// Pushes the current transform in the transform stack.
+    fn push_transform(&mut self);
+
+    /// Pops a transform from the transform stack and sets it as the current transform.
+    fn pop_transform(&mut self);
+
+    /// 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]
+    fn with_clip<R>(
+        &mut self,
+        region: Rectangle,
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
+        let mut frame = self.draft(region.size());
+
+        let result = f(&mut frame);
+
+        let origin = Point::new(region.x, region.y);
+
+        self.paste(frame, origin);
+
+        result
+    }
+
+    /// Creates a new [`Frame`] with the given [`Size`].
+    ///
+    /// Draw its contents back to this [`Frame`] with [`paste`].
+    ///
+    /// [`paste`]: Self::paste
+    fn draft(&mut self, size: Size) -> Self;
+
+    /// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
+    fn paste(&mut self, frame: Self, at: Point);
+
+    /// Applies a translation to the current transform of the [`Frame`].
+    fn translate(&mut self, translation: Vector);
+
+    /// Applies a rotation in radians to the current transform of the [`Frame`].
+    fn rotate(&mut self, angle: impl Into<Radians>);
+
+    /// Applies a uniform scaling to the current transform of the [`Frame`].
+    fn scale(&mut self, scale: impl Into<f32>);
+
+    /// Applies a non-uniform scaling to the current transform of the [`Frame`].
+    fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
+}
+
+pub trait Geometry: Sized {
+    type Cache;
+
+    fn load(cache: &Self::Cache) -> Self;
+
+    fn cache(self) -> Self::Cache;
+}
+
+/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
+///
+/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
+/// change or it is explicitly cleared.
+pub struct Cache<Renderer>
+where
+    Renderer: self::Renderer,
+{
+    state: RefCell<State<Renderer::Geometry>>,
+}
+
+impl<Renderer> Cache<Renderer>
+where
+    Renderer: self::Renderer,
+{
+    /// Creates a new empty [`Cache`].
+    pub fn new() -> Self {
+        Cache {
+            state: RefCell::new(State::Empty),
+        }
+    }
+
+    /// Clears the [`Cache`], forcing a redraw the next time it is used.
+    pub fn clear(&self) {
+        *self.state.borrow_mut() = State::Empty;
+    }
+
+    /// Draws [`Geometry`] using the provided closure and stores it in the
+    /// [`Cache`].
+    ///
+    /// The closure will only be called when
+    /// - the bounds have changed since the previous draw call.
+    /// - the [`Cache`] is empty or has been explicitly cleared.
+    ///
+    /// Otherwise, the previously stored [`Geometry`] will be returned. The
+    /// [`Cache`] is not cleared in this case. In other words, it will keep
+    /// returning the stored [`Geometry`] if needed.
+    pub fn draw(
+        &self,
+        renderer: &Renderer,
+        bounds: Size,
+        draw_fn: impl FnOnce(&mut Renderer::Frame),
+    ) -> Renderer::Geometry {
+        use std::ops::Deref;
+
+        if let State::Filled {
+            bounds: cached_bounds,
+            geometry,
+        } = self.state.borrow().deref()
+        {
+            if *cached_bounds == bounds {
+                return Geometry::load(geometry);
+            }
+        }
+
+        let mut frame = frame(renderer, bounds);
+        draw_fn(&mut frame);
+
+        let geometry = frame.into().cache();
+        let result = Geometry::load(&geometry);
+
+        *self.state.borrow_mut() = State::Filled { bounds, geometry };
+
+        result
+    }
+}
+
+impl<Renderer> std::fmt::Debug for Cache<Renderer>
+where
+    Renderer: self::Renderer,
+{
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let state = self.state.borrow();
+
+        match *state {
+            State::Empty => write!(f, "Cache::Empty"),
+            State::Filled { bounds, .. } => {
+                write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
+            }
+        }
+    }
+}
+
+impl<Renderer> Default for Cache<Renderer>
+where
+    Renderer: self::Renderer,
+{
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+enum State<Geometry>
+where
+    Geometry: self::Geometry,
+{
+    Empty,
+    Filled {
+        bounds: Size,
+        geometry: Geometry::Cache,
+    },
+}
+
+impl<T> Geometry for Primitive<T> {
+    type Cache = Arc<Self>;
+
+    fn load(cache: &Arc<Self>) -> Self {
+        Self::Cache {
+            content: cache.clone(),
+        }
+    }
 
-    /// Draws the given layers of [`Self::Geometry`].
-    fn draw(&mut self, layers: Vec<Self::Geometry>);
+    fn cache(self) -> Arc<Self> {
+        Arc::new(self)
+    }
 }
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index aa9d00e8..6d0862ad 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -9,8 +9,8 @@
 )]
 #![forbid(rust_2018_idioms)]
 #![deny(
-    missing_debug_implementations,
-    missing_docs,
+    // missing_debug_implementations,
+    // missing_docs,
     unsafe_code,
     unused_results,
     rustdoc::broken_intra_doc_links
diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs
index 041986cf..5be3ee5b 100644
--- a/graphics/src/mesh.rs
+++ b/graphics/src/mesh.rs
@@ -1,6 +1,6 @@
 //! Draw triangles!
 use crate::color;
-use crate::core::{Rectangle, Size};
+use crate::core::{self, Rectangle, Size};
 use crate::gradient;
 use crate::Damage;
 
@@ -74,3 +74,7 @@ pub struct GradientVertex2D {
     /// The packed vertex data of the gradient.
     pub gradient: gradient::Packed,
 }
+
+pub trait Renderer: core::Renderer {
+    fn draw_mesh(&mut self, mesh: Mesh);
+}
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index e7154385..3b21aa11 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -8,8 +8,9 @@ use crate::core::text::Text;
 use crate::core::{
     Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
 };
+use crate::mesh;
 use crate::text;
-use crate::Primitive;
+use crate::{Mesh, Primitive};
 
 use std::borrow::Cow;
 
@@ -20,6 +21,7 @@ pub struct Renderer<B: Backend> {
     default_font: Font,
     default_text_size: Pixels,
     primitives: Vec<Primitive<B::Primitive>>,
+    stack: Vec<Vec<Primitive<B::Primitive>>>,
 }
 
 impl<B: Backend> Renderer<B> {
@@ -34,6 +36,7 @@ impl<B: Backend> Renderer<B> {
             default_font,
             default_text_size,
             primitives: Vec::new(),
+            stack: Vec::new(),
         }
     }
 
@@ -56,59 +59,45 @@ impl<B: Backend> Renderer<B> {
         f(&mut self.backend, &self.primitives)
     }
 
-    /// Starts recording a new layer.
-    pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> {
-        std::mem::take(&mut self.primitives)
-    }
-
-    /// Ends the recording of a layer.
-    pub fn end_layer(
-        &mut self,
-        primitives: Vec<Primitive<B::Primitive>>,
-        bounds: Rectangle,
-    ) {
-        let layer = std::mem::replace(&mut self.primitives, primitives);
-
-        self.primitives.push(Primitive::group(layer).clip(bounds));
-    }
-
-    /// Starts recording a translation.
-    pub fn start_transformation(&mut self) -> Vec<Primitive<B::Primitive>> {
-        std::mem::take(&mut self.primitives)
-    }
-
-    /// Ends the recording of a translation.
-    pub fn end_transformation(
+    #[cfg(feature = "geometry")]
+    pub fn draw_geometry<Geometry>(
         &mut self,
-        primitives: Vec<Primitive<B::Primitive>>,
-        transformation: Transformation,
-    ) {
-        let layer = std::mem::replace(&mut self.primitives, primitives);
-
-        self.primitives
-            .push(Primitive::group(layer).transform(transformation));
+        layers: impl IntoIterator<Item = Geometry>,
+    ) where
+        Geometry: Into<Primitive<B::Primitive>>,
+    {
+        for layer in layers {
+            self.draw_primitive(layer.into());
+        }
     }
 }
 
 impl<B: Backend> iced_core::Renderer for Renderer<B> {
-    fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
-        let current = self.start_layer();
+    fn start_layer(&mut self) {
+        self.stack.push(std::mem::take(&mut self.primitives));
+    }
 
-        f(self);
+    fn end_layer(&mut self, bounds: Rectangle) {
+        let layer = std::mem::replace(
+            &mut self.primitives,
+            self.stack.pop().expect("a layer should be recording"),
+        );
 
-        self.end_layer(current, bounds);
+        self.primitives.push(Primitive::group(layer).clip(bounds));
     }
 
-    fn with_transformation(
-        &mut self,
-        transformation: Transformation,
-        f: impl FnOnce(&mut Self),
-    ) {
-        let current = self.start_transformation();
+    fn start_transformation(&mut self) {
+        self.stack.push(std::mem::take(&mut self.primitives));
+    }
 
-        f(self);
+    fn end_transformation(&mut self, transformation: Transformation) {
+        let layer = std::mem::replace(
+            &mut self.primitives,
+            self.stack.pop().expect("a layer should be recording"),
+        );
 
-        self.end_transformation(current, transformation);
+        self.primitives
+            .push(Primitive::group(layer).transform(transformation));
     }
 
     fn fill_quad(
@@ -250,3 +239,34 @@ where
         });
     }
 }
+
+impl<B: Backend> mesh::Renderer for Renderer<B> {
+    fn draw_mesh(&mut self, mesh: Mesh) {
+        match B::Primitive::try_from(mesh) {
+            Ok(primitive) => {
+                self.draw_primitive(Primitive::Custom(primitive));
+            }
+            Err(error) => {
+                log::warn!("mesh primitive could not be drawn: {error:?}");
+            }
+        }
+    }
+}
+
+#[cfg(feature = "geometry")]
+impl<B> crate::geometry::Renderer for Renderer<B>
+where
+    B: Backend + crate::geometry::Backend,
+    B::Frame: crate::geometry::Frame<Geometry = Primitive<B::Primitive>>,
+{
+    type Frame = B::Frame;
+    type Geometry = Primitive<B::Primitive>;
+
+    fn new_frame(&self, size: Size) -> Self::Frame {
+        self.backend.new_frame(size)
+    }
+
+    fn draw_geometry(&mut self, geometry: Self::Geometry) {
+        self.draw_primitive(geometry);
+    }
+}
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index 3d0b3ad0..8b137891 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -1,300 +1 @@
-use crate::core::Color;
-use crate::graphics::compositor::{Information, SurfaceError, Window};
-use crate::graphics::{Error, Viewport};
-use crate::{Renderer, Settings};
 
-use std::env;
-use std::future::Future;
-
-pub enum Compositor {
-    TinySkia(iced_tiny_skia::window::Compositor),
-    #[cfg(feature = "wgpu")]
-    Wgpu(iced_wgpu::window::Compositor),
-    #[cfg(feature = "custom")]
-    Custom(Box<dyn crate::custom::Compositor>),
-}
-
-pub enum Surface {
-    TinySkia(iced_tiny_skia::window::Surface),
-    #[cfg(feature = "wgpu")]
-    Wgpu(iced_wgpu::window::Surface<'static>),
-    #[cfg(feature = "custom")]
-    Custom(Box<dyn crate::custom::Surface>),
-}
-
-impl crate::graphics::Compositor for Compositor {
-    type Settings = Settings;
-    type Renderer = Renderer;
-    type Surface = Surface;
-
-    fn new<W: Window + Clone>(
-        settings: Self::Settings,
-        compatible_window: W,
-    ) -> impl Future<Output = Result<Self, Error>> {
-        let candidates =
-            Candidate::list_from_env().unwrap_or(Candidate::default_list());
-
-        async move {
-            let mut error = Error::GraphicsAdapterNotFound;
-
-            for candidate in candidates {
-                match candidate.build(settings, compatible_window.clone()).await
-                {
-                    Ok(compositor) => return Ok(compositor),
-                    Err(new_error) => {
-                        error = new_error;
-                    }
-                }
-            }
-
-            Err(error)
-        }
-    }
-
-    fn create_renderer(&self) -> Self::Renderer {
-        match self {
-            Compositor::TinySkia(compositor) => {
-                Renderer::TinySkia(compositor.create_renderer())
-            }
-            #[cfg(feature = "wgpu")]
-            Compositor::Wgpu(compositor) => {
-                Renderer::Wgpu(compositor.create_renderer())
-            }
-            #[cfg(feature = "custom")]
-            Compositor::Custom(compositor) => {
-                Renderer::Custom(compositor.create_renderer())
-            }
-        }
-    }
-
-    fn create_surface<W: Window + Clone>(
-        &mut self,
-        window: W,
-        width: u32,
-        height: u32,
-    ) -> Surface {
-        match self {
-            Self::TinySkia(compositor) => Surface::TinySkia(
-                compositor.create_surface(window, width, height),
-            ),
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(compositor) => {
-                Surface::Wgpu(compositor.create_surface(window, width, height))
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(compositor) => Surface::Custom(
-                compositor.create_surface(Box::new(window), width, height),
-            ),
-        }
-    }
-
-    fn configure_surface(
-        &mut self,
-        surface: &mut Surface,
-        width: u32,
-        height: u32,
-    ) {
-        match (self, surface) {
-            (Self::TinySkia(compositor), Surface::TinySkia(surface)) => {
-                compositor.configure_surface(surface, width, height);
-            }
-            #[cfg(feature = "wgpu")]
-            (Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
-                compositor.configure_surface(surface, width, height);
-            }
-            #[cfg(feature = "custom")]
-            (Self::Custom(compositor), Surface::Custom(surface)) => {
-                compositor.configure_surface(surface.as_mut(), width, height);
-            }
-            #[allow(unreachable_patterns)]
-            _ => panic!(
-                "The provided surface is not compatible with the compositor."
-            ),
-        }
-    }
-
-    fn fetch_information(&self) -> Information {
-        match self {
-            Self::TinySkia(compositor) => compositor.fetch_information(),
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(compositor) => compositor.fetch_information(),
-            #[cfg(feature = "custom")]
-            Self::Custom(compositor) => compositor.fetch_information(),
-        }
-    }
-
-    fn present<T: AsRef<str>>(
-        &mut self,
-        renderer: &mut Self::Renderer,
-        surface: &mut Self::Surface,
-        viewport: &Viewport,
-        background_color: Color,
-        overlay: &[T],
-    ) -> Result<(), SurfaceError> {
-        match (self, renderer, surface) {
-            (
-                Self::TinySkia(_compositor),
-                crate::Renderer::TinySkia(renderer),
-                Surface::TinySkia(surface),
-            ) => renderer.with_primitives(|backend, primitives| {
-                iced_tiny_skia::window::compositor::present(
-                    backend,
-                    surface,
-                    primitives,
-                    viewport,
-                    background_color,
-                    overlay,
-                )
-            }),
-            #[cfg(feature = "wgpu")]
-            (
-                Self::Wgpu(compositor),
-                crate::Renderer::Wgpu(renderer),
-                Surface::Wgpu(surface),
-            ) => renderer.with_primitives(|backend, primitives| {
-                iced_wgpu::window::compositor::present(
-                    compositor,
-                    backend,
-                    surface,
-                    primitives,
-                    viewport,
-                    background_color,
-                    overlay,
-                )
-            }),
-
-            #[cfg(feature = "custom")]
-            (
-                Self::Custom(compositor),
-                crate::Renderer::Custom(renderer),
-                Surface::Custom(surface),
-            ) => renderer.present(
-                surface.as_mut(),
-                viewport,
-                background_color,
-                compositor.as_mut(),
-            ),
-            #[allow(unreachable_patterns)]
-            _ => panic!(
-                "The provided renderer or surface are not compatible \
-                    with the compositor."
-            ),
-        }
-    }
-
-    fn screenshot<T: AsRef<str>>(
-        &mut self,
-        renderer: &mut Self::Renderer,
-        surface: &mut Self::Surface,
-        viewport: &Viewport,
-        background_color: Color,
-        overlay: &[T],
-    ) -> Vec<u8> {
-        match (self, renderer, surface) {
-            (
-                Self::TinySkia(_compositor),
-                Renderer::TinySkia(renderer),
-                Surface::TinySkia(surface),
-            ) => renderer.with_primitives(|backend, primitives| {
-                iced_tiny_skia::window::compositor::screenshot(
-                    surface,
-                    backend,
-                    primitives,
-                    viewport,
-                    background_color,
-                    overlay,
-                )
-            }),
-            #[cfg(feature = "wgpu")]
-            (
-                Self::Wgpu(compositor),
-                Renderer::Wgpu(renderer),
-                Surface::Wgpu(_),
-            ) => renderer.with_primitives(|backend, primitives| {
-                iced_wgpu::window::compositor::screenshot(
-                    compositor,
-                    backend,
-                    primitives,
-                    viewport,
-                    background_color,
-                    overlay,
-                )
-            }),
-            #[allow(unreachable_patterns)]
-            _ => panic!(
-                "The provided renderer or backend are not compatible \
-                with the compositor."
-            ),
-        }
-    }
-}
-
-enum Candidate {
-    Wgpu,
-    TinySkia,
-}
-
-impl Candidate {
-    fn default_list() -> Vec<Self> {
-        vec![
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu,
-            Self::TinySkia,
-        ]
-    }
-
-    fn list_from_env() -> Option<Vec<Self>> {
-        let backends = env::var("ICED_BACKEND").ok()?;
-
-        Some(
-            backends
-                .split(',')
-                .map(str::trim)
-                .map(|backend| match backend {
-                    "wgpu" => Self::Wgpu,
-                    "tiny-skia" => Self::TinySkia,
-                    _ => panic!("unknown backend value: \"{backend}\""),
-                })
-                .collect(),
-        )
-    }
-
-    async fn build<W: Window>(
-        self,
-        settings: Settings,
-        _compatible_window: W,
-    ) -> Result<Compositor, Error> {
-        match self {
-            Self::TinySkia => {
-                let compositor = iced_tiny_skia::window::compositor::new(
-                    iced_tiny_skia::Settings {
-                        default_font: settings.default_font,
-                        default_text_size: settings.default_text_size,
-                    },
-                    _compatible_window,
-                );
-
-                Ok(Compositor::TinySkia(compositor))
-            }
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu => {
-                let compositor = iced_wgpu::window::compositor::new(
-                    iced_wgpu::Settings {
-                        default_font: settings.default_font,
-                        default_text_size: settings.default_text_size,
-                        antialiasing: settings.antialiasing,
-                        ..iced_wgpu::Settings::from_env()
-                    },
-                    _compatible_window,
-                )
-                .await?;
-
-                Ok(Compositor::Wgpu(compositor))
-            }
-            #[cfg(not(feature = "wgpu"))]
-            Self::Wgpu => {
-                panic!("`wgpu` feature was not enabled in `iced_renderer`")
-            }
-        }
-    }
-}
diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs
index 04090ccb..4addeb86 100644
--- a/renderer/src/custom.rs
+++ b/renderer/src/custom.rs
@@ -94,8 +94,6 @@ pub trait Renderer {
 
 #[cfg(feature = "geometry")]
 pub trait Frame: std::any::Any {
-    fn new(&self, size: Size) -> Box<dyn Frame>;
-
     fn width(&self) -> f32;
 
     fn height(&self) -> f32;
@@ -108,7 +106,7 @@ pub trait Frame: std::any::Any {
 
     fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill);
 
-    fn stroke<'a>(&mut self, path: &Path, stroke: Stroke<'a>);
+    fn stroke(&mut self, path: &Path, stroke: Stroke<'_>);
 
     fn fill_text(&mut self, text: geometry::Text);
 
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
new file mode 100644
index 00000000..a4c725c0
--- /dev/null
+++ b/renderer/src/fallback.rs
@@ -0,0 +1,562 @@
+use crate::core::image;
+use crate::core::renderer;
+use crate::core::svg;
+use crate::core::{
+    self, Background, Color, Point, Rectangle, Size, Transformation,
+};
+use crate::graphics;
+use crate::graphics::compositor;
+use crate::graphics::mesh;
+
+pub enum Renderer<L, R>
+where
+    L: core::Renderer,
+    R: core::Renderer,
+{
+    Left(L),
+    Right(R),
+}
+
+macro_rules! delegate {
+    ($renderer:expr, $name:ident, $body:expr) => {
+        match $renderer {
+            Self::Left($name) => $body,
+            Self::Right($name) => $body,
+        }
+    };
+}
+
+impl<L, R> Renderer<L, R>
+where
+    L: core::Renderer,
+    R: core::Renderer,
+{
+    #[cfg(feature = "geometry")]
+    pub fn draw_geometry<Geometry>(
+        &mut self,
+        layers: impl IntoIterator<Item = Geometry>,
+    ) where
+        L: graphics::geometry::Renderer,
+        R: graphics::geometry::Renderer,
+
+        Geometry: Into<geometry::Geometry<L::Geometry, R::Geometry>>,
+    {
+        use graphics::geometry::Renderer;
+
+        for layer in layers {
+            <Self as Renderer>::draw_geometry(self, layer.into());
+        }
+    }
+}
+
+impl<L, R> core::Renderer for Renderer<L, R>
+where
+    L: core::Renderer,
+    R: core::Renderer,
+{
+    fn fill_quad(
+        &mut self,
+        quad: renderer::Quad,
+        background: impl Into<Background>,
+    ) {
+        delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
+    }
+
+    fn clear(&mut self) {
+        delegate!(self, renderer, renderer.clear());
+    }
+
+    fn start_layer(&mut self) {
+        delegate!(self, renderer, renderer.start_layer());
+    }
+
+    fn end_layer(&mut self, bounds: Rectangle) {
+        delegate!(self, renderer, renderer.end_layer(bounds));
+    }
+
+    fn start_transformation(&mut self) {
+        delegate!(self, renderer, renderer.start_transformation());
+    }
+
+    fn end_transformation(&mut self, transformation: Transformation) {
+        delegate!(self, renderer, renderer.end_transformation(transformation));
+    }
+}
+
+impl<L, R> core::text::Renderer for Renderer<L, R>
+where
+    L: core::text::Renderer,
+    R: core::text::Renderer<
+        Font = L::Font,
+        Paragraph = L::Paragraph,
+        Editor = L::Editor,
+    >,
+{
+    type Font = L::Font;
+    type Paragraph = L::Paragraph;
+    type Editor = L::Editor;
+
+    const ICON_FONT: Self::Font = L::ICON_FONT;
+    const CHECKMARK_ICON: char = L::CHECKMARK_ICON;
+    const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON;
+
+    fn default_font(&self) -> Self::Font {
+        delegate!(self, renderer, renderer.default_font())
+    }
+
+    fn default_size(&self) -> core::Pixels {
+        delegate!(self, renderer, renderer.default_size())
+    }
+
+    fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
+        delegate!(self, renderer, renderer.load_font(font));
+    }
+
+    fn fill_paragraph(
+        &mut self,
+        text: &Self::Paragraph,
+        position: Point,
+        color: Color,
+        clip_bounds: Rectangle,
+    ) {
+        delegate!(
+            self,
+            renderer,
+            renderer.fill_paragraph(text, position, color, clip_bounds)
+        );
+    }
+
+    fn fill_editor(
+        &mut self,
+        editor: &Self::Editor,
+        position: Point,
+        color: Color,
+        clip_bounds: Rectangle,
+    ) {
+        delegate!(
+            self,
+            renderer,
+            renderer.fill_editor(editor, position, color, clip_bounds)
+        );
+    }
+
+    fn fill_text(
+        &mut self,
+        text: core::Text<'_, Self::Font>,
+        position: Point,
+        color: Color,
+        clip_bounds: Rectangle,
+    ) {
+        delegate!(
+            self,
+            renderer,
+            renderer.fill_text(text, position, color, clip_bounds)
+        );
+    }
+}
+
+impl<L, R> image::Renderer for Renderer<L, R>
+where
+    L: image::Renderer,
+    R: image::Renderer<Handle = L::Handle>,
+{
+    type Handle = L::Handle;
+
+    fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
+        delegate!(self, renderer, renderer.measure_image(handle))
+    }
+
+    fn draw_image(
+        &mut self,
+        handle: Self::Handle,
+        filter_method: image::FilterMethod,
+        bounds: Rectangle,
+    ) {
+        delegate!(
+            self,
+            renderer,
+            renderer.draw_image(handle, filter_method, bounds)
+        );
+    }
+}
+
+impl<L, R> svg::Renderer for Renderer<L, R>
+where
+    L: svg::Renderer,
+    R: svg::Renderer,
+{
+    fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
+        delegate!(self, renderer, renderer.measure_svg(handle))
+    }
+
+    fn draw_svg(
+        &mut self,
+        handle: svg::Handle,
+        color: Option<Color>,
+        bounds: Rectangle,
+    ) {
+        delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
+    }
+}
+
+impl<L, R> mesh::Renderer for Renderer<L, R>
+where
+    L: mesh::Renderer,
+    R: mesh::Renderer,
+{
+    fn draw_mesh(&mut self, mesh: graphics::Mesh) {
+        delegate!(self, renderer, renderer.draw_mesh(mesh));
+    }
+}
+
+pub enum Compositor<L, R>
+where
+    L: graphics::Compositor,
+    R: graphics::Compositor,
+{
+    Left(L),
+    Right(R),
+}
+
+pub enum Surface<L, R> {
+    Left(L),
+    Right(R),
+}
+
+impl<L, R> graphics::Compositor for Compositor<L, R>
+where
+    L: graphics::Compositor,
+    R: graphics::Compositor,
+    L::Settings: From<crate::Settings>,
+    R::Settings: From<crate::Settings>,
+{
+    type Settings = crate::Settings;
+    type Renderer = Renderer<L::Renderer, R::Renderer>;
+    type Surface = Surface<L::Surface, R::Surface>;
+
+    async fn new<W: compositor::Window + Clone>(
+        settings: Self::Settings,
+        compatible_window: W,
+    ) -> Result<Self, graphics::Error> {
+        if let Ok(left) = L::new(settings.into(), compatible_window.clone())
+            .await
+            .map(Self::Left)
+        {
+            return Ok(left);
+        }
+
+        R::new(settings.into(), compatible_window)
+            .await
+            .map(Self::Right)
+    }
+
+    fn create_renderer(&self) -> Self::Renderer {
+        match self {
+            Self::Left(compositor) => {
+                Renderer::Left(compositor.create_renderer())
+            }
+            Self::Right(compositor) => {
+                Renderer::Right(compositor.create_renderer())
+            }
+        }
+    }
+
+    fn create_surface<W: compositor::Window + Clone>(
+        &mut self,
+        window: W,
+        width: u32,
+        height: u32,
+    ) -> Self::Surface {
+        match self {
+            Self::Left(compositor) => {
+                Surface::Left(compositor.create_surface(window, width, height))
+            }
+            Self::Right(compositor) => {
+                Surface::Right(compositor.create_surface(window, width, height))
+            }
+        }
+    }
+
+    fn configure_surface(
+        &mut self,
+        surface: &mut Self::Surface,
+        width: u32,
+        height: u32,
+    ) {
+        match (self, surface) {
+            (Self::Left(compositor), Surface::Left(surface)) => {
+                compositor.configure_surface(surface, width, height);
+            }
+            (Self::Right(compositor), Surface::Right(surface)) => {
+                compositor.configure_surface(surface, width, height);
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    fn fetch_information(&self) -> compositor::Information {
+        delegate!(self, compositor, compositor.fetch_information())
+    }
+
+    fn present<T: AsRef<str>>(
+        &mut self,
+        renderer: &mut Self::Renderer,
+        surface: &mut Self::Surface,
+        viewport: &graphics::Viewport,
+        background_color: Color,
+        overlay: &[T],
+    ) -> Result<(), compositor::SurfaceError> {
+        match (self, renderer, surface) {
+            (
+                Self::Left(compositor),
+                Renderer::Left(renderer),
+                Surface::Left(surface),
+            ) => compositor.present(
+                renderer,
+                surface,
+                viewport,
+                background_color,
+                overlay,
+            ),
+            (
+                Self::Right(compositor),
+                Renderer::Right(renderer),
+                Surface::Right(surface),
+            ) => compositor.present(
+                renderer,
+                surface,
+                viewport,
+                background_color,
+                overlay,
+            ),
+            _ => unreachable!(),
+        }
+    }
+
+    fn screenshot<T: AsRef<str>>(
+        &mut self,
+        renderer: &mut Self::Renderer,
+        surface: &mut Self::Surface,
+        viewport: &graphics::Viewport,
+        background_color: Color,
+        overlay: &[T],
+    ) -> Vec<u8> {
+        match (self, renderer, surface) {
+            (
+                Self::Left(compositor),
+                Renderer::Left(renderer),
+                Surface::Left(surface),
+            ) => compositor.screenshot(
+                renderer,
+                surface,
+                viewport,
+                background_color,
+                overlay,
+            ),
+            (
+                Self::Right(compositor),
+                Renderer::Right(renderer),
+                Surface::Right(surface),
+            ) => compositor.screenshot(
+                renderer,
+                surface,
+                viewport,
+                background_color,
+                overlay,
+            ),
+            _ => unreachable!(),
+        }
+    }
+}
+
+#[cfg(feature = "wgpu")]
+impl<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R>
+where
+    L: iced_wgpu::primitive::pipeline::Renderer,
+    R: core::Renderer,
+{
+    fn draw_pipeline_primitive(
+        &mut self,
+        bounds: Rectangle,
+        primitive: impl iced_wgpu::primitive::pipeline::Primitive,
+    ) {
+        match self {
+            Self::Left(renderer) => {
+                renderer.draw_pipeline_primitive(bounds, primitive);
+            }
+            Self::Right(_) => {
+                log::warn!(
+                    "Custom shader primitive is not supported with this renderer."
+                );
+            }
+        }
+    }
+}
+
+#[cfg(feature = "geometry")]
+mod geometry {
+    use super::Renderer;
+    use crate::core::{Point, Radians, Size, Vector};
+    use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
+
+    impl<L, R> geometry::Renderer for Renderer<L, R>
+    where
+        L: geometry::Renderer,
+        R: geometry::Renderer,
+    {
+        type Geometry = Geometry<L::Geometry, R::Geometry>;
+        type Frame = Frame<L::Frame, R::Frame>;
+
+        fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame {
+            match self {
+                Self::Left(renderer) => Frame::Left(renderer.new_frame(size)),
+                Self::Right(renderer) => Frame::Right(renderer.new_frame(size)),
+            }
+        }
+
+        fn draw_geometry(&mut self, geometry: Self::Geometry) {
+            match (self, geometry) {
+                (Self::Left(renderer), Geometry::Left(geometry)) => {
+                    renderer.draw_geometry(geometry);
+                }
+                (Self::Right(renderer), Geometry::Right(geometry)) => {
+                    renderer.draw_geometry(geometry);
+                }
+                _ => unreachable!(),
+            }
+        }
+    }
+
+    pub enum Geometry<L, R> {
+        Left(L),
+        Right(R),
+    }
+
+    impl<L, R> geometry::Geometry for Geometry<L, R>
+    where
+        L: geometry::Geometry,
+        R: geometry::Geometry,
+    {
+        type Cache = Geometry<L::Cache, R::Cache>;
+
+        fn load(cache: &Self::Cache) -> Self {
+            match cache {
+                Geometry::Left(cache) => Self::Left(L::load(cache)),
+                Geometry::Right(cache) => Self::Right(R::load(cache)),
+            }
+        }
+
+        fn cache(self) -> Self::Cache {
+            match self {
+                Self::Left(geometry) => Geometry::Left(geometry.cache()),
+                Self::Right(geometry) => Geometry::Right(geometry.cache()),
+            }
+        }
+    }
+
+    pub enum Frame<L, R> {
+        Left(L),
+        Right(R),
+    }
+
+    impl<L, R> geometry::Frame for Frame<L, R>
+    where
+        L: geometry::Frame,
+        R: geometry::Frame,
+    {
+        type Geometry = Geometry<L::Geometry, R::Geometry>;
+
+        fn width(&self) -> f32 {
+            delegate!(self, frame, frame.width())
+        }
+
+        fn height(&self) -> f32 {
+            delegate!(self, frame, frame.height())
+        }
+
+        fn size(&self) -> Size {
+            delegate!(self, frame, frame.size())
+        }
+
+        fn center(&self) -> Point {
+            delegate!(self, frame, frame.center())
+        }
+
+        fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+            delegate!(self, frame, frame.fill(path, fill));
+        }
+
+        fn fill_rectangle(
+            &mut self,
+            top_left: Point,
+            size: Size,
+            fill: impl Into<Fill>,
+        ) {
+            delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
+        }
+
+        fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
+            delegate!(self, frame, frame.stroke(path, stroke));
+        }
+
+        fn fill_text(&mut self, text: impl Into<Text>) {
+            delegate!(self, frame, frame.fill_text(text));
+        }
+
+        fn push_transform(&mut self) {
+            delegate!(self, frame, frame.push_transform());
+        }
+
+        fn pop_transform(&mut self) {
+            delegate!(self, frame, frame.pop_transform());
+        }
+
+        fn draft(&mut self, size: Size) -> Self {
+            match self {
+                Self::Left(frame) => Self::Left(frame.draft(size)),
+                Self::Right(frame) => Self::Right(frame.draft(size)),
+            }
+        }
+
+        fn paste(&mut self, frame: Self, at: Point) {
+            match (self, frame) {
+                (Self::Left(target), Self::Left(source)) => {
+                    target.paste(source, at);
+                }
+                (Self::Right(target), Self::Right(source)) => {
+                    target.paste(source, at);
+                }
+                _ => unreachable!(),
+            }
+        }
+
+        fn translate(&mut self, translation: Vector) {
+            delegate!(self, frame, frame.translate(translation));
+        }
+
+        fn rotate(&mut self, angle: impl Into<Radians>) {
+            delegate!(self, frame, frame.rotate(angle));
+        }
+
+        fn scale(&mut self, scale: impl Into<f32>) {
+            delegate!(self, frame, frame.scale(scale));
+        }
+
+        fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+            delegate!(self, frame, frame.scale_nonuniform(scale));
+        }
+    }
+
+    impl<L, R> From<Frame<L, R>> for Geometry<L::Geometry, R::Geometry>
+    where
+        L: geometry::Frame,
+        R: geometry::Frame,
+    {
+        fn from(frame: Frame<L, R>) -> Self {
+            match frame {
+                Frame::Left(frame) => Self::Left(frame.into()),
+                Frame::Right(frame) => Self::Right(frame.into()),
+            }
+        }
+    }
+}
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index 67096115..f8aa1157 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -4,364 +4,42 @@
 #[cfg(feature = "wgpu")]
 pub use iced_wgpu as wgpu;
 
-pub mod compositor;
-pub mod custom;
-
-#[cfg(feature = "geometry")]
-pub mod geometry;
+pub mod fallback;
 
 mod settings;
 
 pub use iced_graphics as graphics;
 pub use iced_graphics::core;
 
-pub use compositor::Compositor;
-pub use settings::Settings;
-
 #[cfg(feature = "geometry")]
-pub use geometry::Geometry;
-
-use crate::core::renderer;
-use crate::core::text::{self, Text};
-use crate::core::{
-    Background, Color, Font, Pixels, Point, Rectangle, Transformation,
-};
-use crate::graphics::text::Editor;
-use crate::graphics::text::Paragraph;
-use crate::graphics::Mesh;
+pub use iced_graphics::geometry;
 
-use std::borrow::Cow;
+pub use settings::Settings;
 
 /// The default graphics renderer for [`iced`].
 ///
 /// [`iced`]: https://github.com/iced-rs/iced
-pub enum Renderer {
-    TinySkia(iced_tiny_skia::Renderer),
-    #[cfg(feature = "wgpu")]
-    Wgpu(iced_wgpu::Renderer),
-    #[cfg(feature = "custom")]
-    Custom(Box<dyn custom::Renderer>),
-}
-
-macro_rules! delegate {
-    ($renderer:expr, $name:ident, $body:expr) => {
-        match $renderer {
-            Self::TinySkia($name) => $body,
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu($name) => $body,
-            #[cfg(feature = "custom")]
-            Self::Custom($name) => $body,
-        }
-    };
-}
-
-impl Renderer {
-    pub fn draw_mesh(&mut self, mesh: Mesh) {
-        match self {
-            Self::TinySkia(_) => {
-                log::warn!("Unsupported mesh primitive: {mesh:?}");
-            }
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(renderer) => {
-                renderer.draw_primitive(iced_wgpu::Primitive::Custom(
-                    iced_wgpu::primitive::Custom::Mesh(mesh),
-                ));
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(renderer) => {
-                renderer.draw_mesh(mesh);
-            }
-        }
-    }
-}
-
-impl core::Renderer for Renderer {
-    fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
-        match self {
-            Self::TinySkia(renderer) => {
-                let primitives = renderer.start_layer();
-
-                f(self);
-
-                match self {
-                    Self::TinySkia(renderer) => {
-                        renderer.end_layer(primitives, bounds);
-                    }
-                    #[cfg(feature = "wgpu")]
-                    _ => unreachable!(),
-                }
-            }
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(renderer) => {
-                let primitives = renderer.start_layer();
-
-                f(self);
-
-                match self {
-                    #[cfg(feature = "wgpu")]
-                    Self::Wgpu(renderer) => {
-                        renderer.end_layer(primitives, bounds);
-                    }
-                    _ => unreachable!(),
-                }
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(renderer) => {
-                renderer.start_layer();
-
-                f(self);
-
-                match self {
-                    Self::Custom(renderer) => {
-                        renderer.end_layer(bounds);
-                    }
-                    _ => unreachable!(),
-                }
-            }
-        }
-    }
-
-    fn with_transformation(
-        &mut self,
-        transformation: Transformation,
-        f: impl FnOnce(&mut Self),
-    ) {
-        match self {
-            Self::TinySkia(renderer) => {
-                let primitives = renderer.start_transformation();
-
-                f(self);
-
-                match self {
-                    Self::TinySkia(renderer) => {
-                        renderer.end_transformation(primitives, transformation);
-                    }
-                    #[cfg(feature = "wgpu")]
-                    _ => unreachable!(),
-                }
-            }
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(renderer) => {
-                let primitives = renderer.start_transformation();
-
-                f(self);
-
-                match self {
-                    #[cfg(feature = "wgpu")]
-                    Self::Wgpu(renderer) => {
-                        renderer.end_transformation(primitives, transformation);
-                    }
-                    _ => unreachable!(),
-                }
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(renderer) => {
-                renderer.start_transformation();
-
-                f(self);
-
-                match self {
-                    Self::Custom(renderer) => {
-                        renderer.end_transformation(transformation);
-                    }
-                    _ => unreachable!(),
-                }
-            }
-        }
-    }
-
-    fn fill_quad(
-        &mut self,
-        quad: renderer::Quad,
-        background: impl Into<Background>,
-    ) {
-        delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
-    }
-
-    fn clear(&mut self) {
-        delegate!(self, renderer, renderer.clear());
-    }
-}
+#[cfg(not(feature = "wgpu"))]
+pub type Renderer = iced_tiny_skia::Renderer;
 
-impl text::Renderer for Renderer {
-    type Font = Font;
-    type Paragraph = Paragraph;
-    type Editor = Editor;
-
-    const ICON_FONT: Font = iced_tiny_skia::Renderer::ICON_FONT;
-    const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::CHECKMARK_ICON;
-    const ARROW_DOWN_ICON: char = iced_tiny_skia::Renderer::ARROW_DOWN_ICON;
-
-    fn default_font(&self) -> Self::Font {
-        delegate!(self, renderer, renderer.default_font())
-    }
-
-    fn default_size(&self) -> Pixels {
-        delegate!(self, renderer, renderer.default_size())
-    }
-
-    fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
-        delegate!(self, renderer, renderer.load_font(bytes));
-    }
-
-    fn fill_paragraph(
-        &mut self,
-        paragraph: &Self::Paragraph,
-        position: Point,
-        color: Color,
-        clip_bounds: Rectangle,
-    ) {
-        delegate!(
-            self,
-            renderer,
-            renderer.fill_paragraph(paragraph, position, color, clip_bounds)
-        );
-    }
-
-    fn fill_editor(
-        &mut self,
-        editor: &Self::Editor,
-        position: Point,
-        color: Color,
-        clip_bounds: Rectangle,
-    ) {
-        delegate!(
-            self,
-            renderer,
-            renderer.fill_editor(editor, position, color, clip_bounds)
-        );
-    }
-
-    fn fill_text(
-        &mut self,
-        text: Text<'_, Self::Font>,
-        position: Point,
-        color: Color,
-        clip_bounds: Rectangle,
-    ) {
-        delegate!(
-            self,
-            renderer,
-            renderer.fill_text(text, position, color, clip_bounds)
-        );
-    }
-}
-
-#[cfg(feature = "image")]
-impl crate::core::image::Renderer for Renderer {
-    type Handle = crate::core::image::Handle;
-
-    fn measure_image(
-        &self,
-        handle: &crate::core::image::Handle,
-    ) -> core::Size<u32> {
-        delegate!(self, renderer, renderer.measure_image(handle))
-    }
-
-    fn draw_image(
-        &mut self,
-        handle: crate::core::image::Handle,
-        filter_method: crate::core::image::FilterMethod,
-        bounds: Rectangle,
-    ) {
-        delegate!(
-            self,
-            renderer,
-            renderer.draw_image(handle, filter_method, bounds)
-        );
-    }
-}
-
-#[cfg(feature = "svg")]
-impl crate::core::svg::Renderer for Renderer {
-    fn measure_svg(
-        &self,
-        handle: &crate::core::svg::Handle,
-    ) -> core::Size<u32> {
-        delegate!(self, renderer, renderer.measure_svg(handle))
-    }
-
-    fn draw_svg(
-        &mut self,
-        handle: crate::core::svg::Handle,
-        color: Option<crate::core::Color>,
-        bounds: Rectangle,
-    ) {
-        delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
-    }
-}
-
-#[cfg(feature = "geometry")]
-impl crate::graphics::geometry::Renderer for Renderer {
-    type Geometry = crate::Geometry;
+/// The default graphics renderer for [`iced`].
+///
+/// [`iced`]: https://github.com/iced-rs/iced
+#[cfg(feature = "wgpu")]
+pub type Renderer =
+    fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>;
 
-    fn draw(&mut self, layers: Vec<Self::Geometry>) {
-        match self {
-            Self::TinySkia(renderer) => {
-                for layer in layers {
-                    match layer {
-                        crate::Geometry::TinySkia(primitive) => {
-                            renderer.draw_primitive(primitive);
-                        }
-                        #[cfg(feature = "wgpu")]
-                        crate::Geometry::Wgpu(_) => unreachable!(),
-                        #[cfg(feature = "custom")]
-                        crate::Geometry::Custom(_) => unreachable!(),
-                    }
-                }
-            }
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(renderer) => {
-                for layer in layers {
-                    match layer {
-                        crate::Geometry::Wgpu(primitive) => {
-                            renderer.draw_primitive(primitive);
-                        }
-                        crate::Geometry::TinySkia(_) => unreachable!(),
-                        #[cfg(feature = "custom")]
-                        crate::Geometry::Custom(_) => unreachable!(),
-                    }
-                }
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(renderer) => {
-                for layer in layers {
-                    match layer {
-                        crate::Geometry::Custom(geometry) => {
-                            renderer.draw_geometry(geometry);
-                        }
-                        crate::Geometry::TinySkia(_) => unreachable!(),
-                        #[cfg(feature = "wgpu")]
-                        crate::Geometry::Wgpu(_) => unreachable!(),
-                    }
-                }
-            }
-        }
-    }
-}
+/// The default graphics compositor for [`iced`].
+///
+/// [`iced`]: https://github.com/iced-rs/iced
+#[cfg(not(feature = "wgpu"))]
+pub type Compositor = iced_tiny_skia::window::Compositor;
 
+/// The default graphics renderer for [`iced`].
+///
+/// [`iced`]: https://github.com/iced-rs/iced
 #[cfg(feature = "wgpu")]
-impl iced_wgpu::primitive::pipeline::Renderer for Renderer {
-    fn draw_pipeline_primitive(
-        &mut self,
-        bounds: Rectangle,
-        primitive: impl wgpu::primitive::pipeline::Primitive,
-    ) {
-        match self {
-            Self::TinySkia(_renderer) => {
-                log::warn!(
-                    "Custom shader primitive is unavailable with tiny-skia."
-                );
-            }
-            Self::Wgpu(renderer) => {
-                renderer.draw_pipeline_primitive(bounds, primitive);
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(_renderer) => {
-                log::warn!(
-                    "Custom shader primitive is unavailable with custom renderer."
-                );
-            }
-        }
-    }
-}
+pub type Compositor = fallback::Compositor<
+    iced_wgpu::window::Compositor,
+    iced_tiny_skia::window::Compositor,
+>;
diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs
index 432eb8a0..940daa15 100644
--- a/renderer/src/settings.rs
+++ b/renderer/src/settings.rs
@@ -27,3 +27,24 @@ impl Default for Settings {
         }
     }
 }
+
+impl From<Settings> for iced_tiny_skia::Settings {
+    fn from(settings: Settings) -> Self {
+        Self {
+            default_font: settings.default_font,
+            default_text_size: settings.default_text_size,
+        }
+    }
+}
+
+#[cfg(feature = "wgpu")]
+impl From<Settings> for iced_wgpu::Settings {
+    fn from(settings: Settings) -> Self {
+        Self {
+            default_font: settings.default_font,
+            default_text_size: settings.default_text_size,
+            antialiasing: settings.antialiasing,
+            ..iced_wgpu::Settings::default()
+        }
+    }
+}
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index b6487b38..f6bb1c86 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -1018,3 +1018,12 @@ impl backend::Svg for Backend {
         self.vector_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/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index 16787f89..6b1888d0 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -4,7 +4,7 @@ use crate::core::{
 };
 use crate::graphics::geometry::fill::{self, Fill};
 use crate::graphics::geometry::stroke::{self, Stroke};
-use crate::graphics::geometry::{Path, Style, Text};
+use crate::graphics::geometry::{self, Path, Style, Text};
 use crate::graphics::Gradient;
 use crate::primitive::{self, Primitive};
 
@@ -25,23 +25,36 @@ impl Frame {
         }
     }
 
-    pub fn width(&self) -> f32 {
+    pub fn into_primitive(self) -> Primitive {
+        Primitive::Clip {
+            bounds: Rectangle::new(Point::ORIGIN, self.size),
+            content: Box::new(Primitive::Group {
+                primitives: self.primitives,
+            }),
+        }
+    }
+}
+
+impl geometry::Frame for Frame {
+    type Geometry = Primitive;
+
+    fn width(&self) -> f32 {
         self.size.width
     }
 
-    pub fn height(&self) -> f32 {
+    fn height(&self) -> f32 {
         self.size.height
     }
 
-    pub fn size(&self) -> Size {
+    fn size(&self) -> Size {
         self.size
     }
 
-    pub fn center(&self) -> Point {
+    fn center(&self) -> Point {
         Point::new(self.size.width / 2.0, self.size.height / 2.0)
     }
 
-    pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+    fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
         let Some(path) =
             convert_path(path).and_then(|path| path.transform(self.transform))
         else {
@@ -61,7 +74,7 @@ impl Frame {
             }));
     }
 
-    pub fn fill_rectangle(
+    fn fill_rectangle(
         &mut self,
         top_left: Point,
         size: Size,
@@ -89,7 +102,7 @@ impl Frame {
             }));
     }
 
-    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 Some(path) =
             convert_path(path).and_then(|path| path.transform(self.transform))
         else {
@@ -110,7 +123,7 @@ impl Frame {
             }));
     }
 
-    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.transform.get_scale();
@@ -174,51 +187,52 @@ impl Frame {
         }
     }
 
-    pub fn push_transform(&mut self) {
+    fn push_transform(&mut self) {
         self.stack.push(self.transform);
     }
 
-    pub fn pop_transform(&mut self) {
+    fn pop_transform(&mut self) {
         self.transform = self.stack.pop().expect("Pop transform");
     }
 
-    pub fn clip(&mut self, frame: Self, at: Point) {
+    fn draft(&mut self, size: Size) -> Self {
+        Self::new(size)
+    }
+
+    fn paste(&mut self, frame: Self, at: Point) {
         self.primitives.push(Primitive::Transform {
             transformation: Transformation::translate(at.x, at.y),
             content: Box::new(frame.into_primitive()),
         });
     }
 
-    pub fn translate(&mut self, translation: Vector) {
+    fn translate(&mut self, translation: Vector) {
         self.transform =
             self.transform.pre_translate(translation.x, translation.y);
     }
 
-    pub fn rotate(&mut self, angle: impl Into<Radians>) {
+    fn rotate(&mut self, angle: impl Into<Radians>) {
         self.transform = self.transform.pre_concat(
             tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
         );
     }
 
-    pub fn scale(&mut self, scale: impl Into<f32>) {
+    fn scale(&mut self, scale: impl Into<f32>) {
         let scale = scale.into();
 
         self.scale_nonuniform(Vector { x: scale, y: scale });
     }
 
-    pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+    fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
         let scale = scale.into();
 
         self.transform = self.transform.pre_scale(scale.x, scale.y);
     }
+}
 
-    pub fn into_primitive(self) -> Primitive {
-        Primitive::Clip {
-            bounds: Rectangle::new(Point::ORIGIN, self.size),
-            content: Box::new(Primitive::Group {
-                primitives: self.primitives,
-            }),
-        }
+impl From<Frame> for Primitive {
+    fn from(frame: Frame) -> Self {
+        frame.into_primitive()
     }
 }
 
diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs
index 7718d542..b7c428e4 100644
--- a/tiny_skia/src/primitive.rs
+++ b/tiny_skia/src/primitive.rs
@@ -1,5 +1,5 @@
 use crate::core::Rectangle;
-use crate::graphics::Damage;
+use crate::graphics::{Damage, Mesh};
 
 pub type Primitive = crate::graphics::Primitive<Custom>;
 
@@ -42,3 +42,11 @@ impl Damage for Custom {
         }
     }
 }
+
+impl TryFrom<Mesh> for Custom {
+    type Error = &'static str;
+
+    fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
+        Err("unsupported")
+    }
+}
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 09ddbe4d..924aacf1 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -397,3 +397,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..4f6b67b1 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)
-    }
+impl geometry::Frame for Frame {
+    type Geometry = Primitive;
 
-    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 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);
@@ -461,90 +339,153 @@ impl Frame {
             ],
         });
     }
+}
+impl From<Frame> for Primitive {
+    fn from(frame: Frame) -> Self {
+        Self::Group {
+            primitives: frame.into_primitives(),
+        }
+    }
+}
 
-    /// 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,
-                ));
+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 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));
+    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.stack.last_mut().unwrap()
     }
 
-    /// 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_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.scale_nonuniform(Vector { x: scale, y: scale });
+    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!(),
+        }
     }
+}
 
-    /// 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();
+#[derive(Debug)]
+struct Transforms {
+    previous: Vec<Transform>,
+    current: Transform,
+}
 
-        self.transforms.current.0 =
-            self.transforms.current.0.pre_scale(scale.x, scale.y);
+#[derive(Debug, Clone, Copy)]
+struct Transform(lyon::math::Transform);
+
+impl Transform {
+    fn is_identity(&self) -> bool {
+        self.0 == lyon::math::Transform::identity()
     }
 
-    /// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
-    pub fn into_primitive(self) -> Primitive {
-        Primitive::Group {
-            primitives: self.into_primitives(),
+    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/widget/src/canvas.rs b/widget/src/canvas.rs
index 0eda0191..fcd91d17 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -7,7 +7,6 @@ pub use event::Event;
 pub use program::Program;
 
 pub use crate::graphics::geometry::*;
-pub use crate::renderer::geometry::*;
 
 use crate::core;
 use crate::core::layout::{self, Layout};
@@ -21,13 +20,19 @@ use crate::graphics::geometry;
 
 use std::marker::PhantomData;
 
+/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
+///
+/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
+/// change or it is explicitly cleared.
+pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
+
 /// A widget capable of drawing 2D graphics.
 ///
 /// ## Drawing a simple circle
 /// If you want to get a quick overview, here's how we can draw a simple circle:
 ///
 /// ```no_run
-/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program};
+/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program};
 /// # use iced_widget::core::{Color, Rectangle};
 /// # use iced_widget::core::mouse;
 /// # use iced_widget::{Renderer, Theme};
@@ -42,9 +47,9 @@ use std::marker::PhantomData;
 /// impl Program<()> for Circle {
 ///     type State = ();
 ///
-///     fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry>{
+///     fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) {
 ///         // We prepare a new `Frame`
-///         let mut frame = Frame::new(renderer, bounds.size());
+///         let mut frame = frame(renderer, bounds.size());
 ///
 ///         // We create a `Path` representing a simple circle
 ///         let circle = Path::circle(frame.center(), self.radius);
@@ -53,7 +58,7 @@ use std::marker::PhantomData;
 ///         frame.fill(&circle, Color::BLACK);
 ///
 ///         // Finally, we produce the geometry
-///         vec![frame.into_geometry()]
+///         renderer.draw_geometry([frame]);
 ///     }
 /// }
 ///
@@ -210,9 +215,7 @@ where
         renderer.with_transformation(
             Transformation::translate(bounds.x, bounds.y),
             |renderer| {
-                renderer.draw(
-                    self.program.draw(state, renderer, theme, bounds, cursor),
-                );
+                self.program.draw(state, renderer, theme, bounds, cursor);
             },
         );
     }
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index 0bff4bda..307686de 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -37,22 +37,15 @@ where
         (event::Status::Ignored, None)
     }
 
-    /// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
-    ///
-    /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
-    /// [`Cache`].
-    ///
-    /// [`Geometry`]: crate::canvas::Geometry
-    /// [`Frame`]: crate::canvas::Frame
-    /// [`Cache`]: crate::canvas::Cache
+    /// Draws the state of the [`Program`] with the given [`Renderer`].
     fn draw(
         &self,
         state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         theme: &Theme,
         bounds: Rectangle,
         cursor: mouse::Cursor,
-    ) -> Vec<Renderer::Geometry>;
+    );
 
     /// Returns the current mouse interaction of the [`Program`].
     ///
@@ -90,12 +83,12 @@ where
     fn draw(
         &self,
         state: &Self::State,
-        renderer: &Renderer,
+        renderer: &mut Renderer,
         theme: &Theme,
         bounds: Rectangle,
         cursor: mouse::Cursor,
-    ) -> Vec<Renderer::Geometry> {
-        T::draw(self, state, renderer, theme, bounds, cursor)
+    ) {
+        T::draw(self, state, renderer, theme, bounds, cursor);
     }
 
     fn mouse_interaction(
diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs
index 90c0c970..bc46aaaa 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -8,7 +8,6 @@ use crate::core::{
     Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector,
     Widget,
 };
-use crate::graphics::geometry::Renderer as _;
 use crate::Renderer;
 
 use std::cell::RefCell;
@@ -92,6 +91,8 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
         _cursor: mouse::Cursor,
         _viewport: &Rectangle,
     ) {
+        use canvas::Frame;
+
         let state = tree.state.downcast_ref::<State>();
 
         let bounds = layout.bounds();
@@ -142,7 +143,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
         renderer.with_translation(
             bounds.position() - Point::ORIGIN,
             |renderer| {
-                renderer.draw(vec![geometry]);
+                renderer.draw_geometry(vec![geometry]);
             },
         );
     }
@@ -161,11 +162,11 @@ where
 /// The data of a [`QRCode`].
 ///
 /// It stores the contents that will be displayed.
-#[derive(Debug)]
+#[allow(missing_debug_implementations)]
 pub struct Data {
     contents: Vec<qrcode::Color>,
     width: usize,
-    cache: canvas::Cache,
+    cache: canvas::Cache<Renderer>,
 }
 
 impl Data {
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 13d9282d..29786b65 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -130,7 +130,7 @@ pub fn default(theme: &Theme) -> Appearance {
 /// settings.
 pub async fn run<A, E, C>(
     settings: Settings<A::Flags>,
-    compositor_settings: C::Settings,
+    compositor_settings: impl Into<C::Settings>,
 ) -> Result<(), Error>
 where
     A: Application + 'static,
@@ -219,7 +219,7 @@ where
         };
     }
 
-    let compositor = C::new(compositor_settings, window.clone()).await?;
+    let compositor = C::new(compositor_settings.into(), window.clone()).await?;
     let mut renderer = compositor.create_renderer();
 
     for font in settings.fonts {
diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs
index 18db1fb5..c865b0ee 100644
--- a/winit/src/multi_window.rs
+++ b/winit/src/multi_window.rs
@@ -105,7 +105,7 @@ where
 /// settings.
 pub fn run<A, E, C>(
     settings: Settings<A::Flags>,
-    compositor_settings: C::Settings,
+    compositor_settings: impl Into<C::Settings>,
 ) -> Result<(), Error>
 where
     A: Application + 'static,
@@ -186,8 +186,10 @@ where
         };
     }
 
-    let mut compositor =
-        executor::block_on(C::new(compositor_settings, main_window.clone()))?;
+    let mut compositor = executor::block_on(C::new(
+        compositor_settings.into(),
+        main_window.clone(),
+    ))?;
 
     let mut window_manager = WindowManager::new();
     let _ = window_manager.insert(
-- 
cgit 


From 3d17cf8790a18bd0dfe968739c9802833c0bb647 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 21 Mar 2024 22:47:43 +0100
Subject: Remove `custom` module from `iced_renderer`

---
 renderer/src/custom.rs | 162 -------------------------------------------------
 1 file changed, 162 deletions(-)
 delete mode 100644 renderer/src/custom.rs

diff --git a/renderer/src/custom.rs b/renderer/src/custom.rs
deleted file mode 100644
index 4addeb86..00000000
--- a/renderer/src/custom.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-use crate::core::image;
-use crate::core::renderer;
-use crate::core::svg;
-use crate::core::text::Text;
-use crate::core::{
-    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
-};
-use crate::graphics::compositor;
-use crate::graphics::text::{Editor, Paragraph};
-use crate::graphics::{Mesh, Viewport};
-
-#[cfg(feature = "geometry")]
-use crate::graphics::geometry::{self, Fill, Path, Stroke};
-
-use std::borrow::Cow;
-
-pub trait Renderer {
-    fn draw_mesh(&mut self, mesh: Mesh);
-
-    fn start_layer(&mut self);
-
-    fn end_layer(&mut self, bounds: Rectangle);
-
-    fn start_transformation(&mut self);
-
-    fn end_transformation(&mut self, transformation: Transformation);
-
-    fn fill_quad(&mut self, quad: renderer::Quad, background: Background);
-
-    fn clear(&mut self);
-
-    fn default_font(&self) -> Font;
-
-    fn default_size(&self) -> Pixels;
-
-    fn load_font(&mut self, bytes: Cow<'static, [u8]>);
-
-    fn fill_paragraph(
-        &mut self,
-        paragraph: &Paragraph,
-        position: Point,
-        color: Color,
-        clip_bounds: Rectangle,
-    );
-
-    fn fill_editor(
-        &mut self,
-        editor: &Editor,
-        position: Point,
-        color: Color,
-        clip_bounds: Rectangle,
-    );
-
-    fn fill_text(
-        &mut self,
-        text: Text<'_, Font>,
-        position: Point,
-        color: Color,
-        clip_bounds: Rectangle,
-    );
-
-    fn measure_image(&self, handle: &image::Handle) -> Size<u32>;
-
-    fn draw_image(
-        &mut self,
-        handle: image::Handle,
-        filter_method: image::FilterMethod,
-        bounds: Rectangle,
-    );
-
-    fn measure_svg(&self, handle: &svg::Handle) -> Size<u32>;
-
-    fn draw_svg(
-        &mut self,
-        handle: crate::core::svg::Handle,
-        color: Option<crate::core::Color>,
-        bounds: Rectangle,
-    );
-
-    #[cfg(feature = "geometry")]
-    fn new_frame(&self, size: Size) -> Box<dyn Frame>;
-
-    #[cfg(feature = "geometry")]
-    fn draw_geometry(&mut self, geometry: Box<dyn Geometry>);
-
-    fn present(
-        &mut self,
-        surface: &mut dyn Surface,
-        viewport: &Viewport,
-        background_color: Color,
-        compositor: &mut dyn Compositor,
-    ) -> Result<(), compositor::SurfaceError>;
-}
-
-#[cfg(feature = "geometry")]
-pub trait Frame: std::any::Any {
-    fn width(&self) -> f32;
-
-    fn height(&self) -> f32;
-
-    fn size(&self) -> Size;
-
-    fn center(&self) -> Point;
-
-    fn fill(&mut self, path: &Path, fill: Fill);
-
-    fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: Fill);
-
-    fn stroke(&mut self, path: &Path, stroke: Stroke<'_>);
-
-    fn fill_text(&mut self, text: geometry::Text);
-
-    fn translate(&mut self, translation: crate::core::Vector);
-
-    fn rotate(&mut self, angle: crate::core::Radians);
-
-    fn scale(&mut self, scale: f32);
-
-    fn scale_nonuniform(&mut self, scale: crate::core::Vector);
-
-    fn push_transform(&mut self);
-
-    fn pop_transform(&mut self);
-
-    fn clip(&mut self, frame: Box<dyn Frame>, origin: Point);
-
-    fn into_geometry(self: Box<Self>) -> Box<dyn Geometry>;
-}
-
-#[cfg(feature = "geometry")]
-pub trait Geometry: std::any::Any + std::fmt::Debug {
-    fn transform(
-        self: Box<Self>,
-        transformation: Transformation,
-    ) -> Box<dyn Geometry>;
-
-    fn cache(self: Box<Self>) -> std::sync::Arc<dyn Geometry>;
-
-    fn load(self: std::sync::Arc<Self>) -> Box<dyn Geometry>;
-}
-
-pub trait Compositor: std::any::Any {
-    fn create_renderer(&self) -> Box<dyn Renderer>;
-
-    fn create_surface(
-        &mut self,
-        window: Box<dyn compositor::Window>,
-        width: u32,
-        height: u32,
-    ) -> Box<dyn Surface>;
-
-    fn configure_surface(
-        &mut self,
-        surface: &mut dyn Surface,
-        width: u32,
-        height: u32,
-    );
-
-    fn fetch_information(&self) -> compositor::Information;
-}
-
-pub trait Surface: std::any::Any {}
-- 
cgit 


From b972ebca8f8c23d2df1b45bb26038789766a5a65 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Thu, 21 Mar 2024 23:30:36 +0100
Subject: Restore `canvas::Program` API

---
 examples/arc/src/main.rs                 | 10 ++++++----
 examples/bezier_tool/src/main.rs         | 20 +++++++++++---------
 examples/clock/src/main.rs               | 10 ++++++----
 examples/color_palette/src/main.rs       |  8 ++++----
 examples/game_of_life/src/main.rs        | 12 +++++++-----
 examples/layout/src/main.rs              |  6 +++---
 examples/multitouch/src/main.rs          |  8 ++++----
 examples/sierpinski_triangle/src/main.rs |  8 ++++----
 examples/solar_system/src/main.rs        |  8 ++++----
 examples/vectorial_text/src/main.rs      |  6 +++---
 widget/src/canvas.rs                     | 17 +++++++++++++----
 widget/src/canvas/program.rs             | 11 ++++++-----
 12 files changed, 71 insertions(+), 53 deletions(-)

diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
index a7893efa..0aba82a9 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -1,7 +1,9 @@
 use std::{f32::consts::PI, time::Instant};
 
 use iced::mouse;
-use iced::widget::canvas::{self, stroke, Cache, Canvas, Frame, Path, Stroke};
+use iced::widget::canvas::{
+    self, stroke, Cache, Canvas, Frame, Geometry, Path, Stroke,
+};
 use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme};
 
 pub fn main() -> iced::Result {
@@ -55,11 +57,11 @@ impl<Message> canvas::Program<Message> for Arc {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<Geometry> {
         let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
             let palette = theme.palette();
 
@@ -102,6 +104,6 @@ impl<Message> canvas::Program<Message> for Arc {
             );
         });
 
-        renderer.draw_geometry([geometry]);
+        vec![geometry]
     }
 }
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index e51f2a31..31d1e29c 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -52,7 +52,9 @@ impl Example {
 mod bezier {
     use iced::mouse;
     use iced::widget::canvas::event::{self, Event};
-    use iced::widget::canvas::{self, frame, Canvas, Frame, Path, Stroke};
+    use iced::widget::canvas::{
+        self, frame, Canvas, Frame, Geometry, Path, Stroke,
+    };
     use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
 
     #[derive(Default)]
@@ -138,11 +140,11 @@ mod bezier {
         fn draw(
             &self,
             state: &Self::State,
-            renderer: &mut Renderer,
+            renderer: &Renderer,
             _theme: &Theme,
             bounds: Rectangle,
             cursor: mouse::Cursor,
-        ) {
+        ) -> Vec<Geometry> {
             let content =
                 self.state.cache.draw(renderer, bounds.size(), |frame| {
                     Curve::draw_all(self.curves, frame);
@@ -153,10 +155,10 @@ mod bezier {
                     );
                 });
 
-            renderer.draw_geometry([content]);
-
             if let Some(pending) = state {
-                pending.draw(renderer, bounds, cursor);
+                vec![content, pending.draw(renderer, bounds, cursor)]
+            } else {
+                vec![content]
             }
         }
 
@@ -203,10 +205,10 @@ mod bezier {
     impl Pending {
         fn draw(
             &self,
-            renderer: &mut Renderer,
+            renderer: &Renderer,
             bounds: Rectangle,
             cursor: mouse::Cursor,
-        ) {
+        ) -> Geometry {
             let mut frame = frame(renderer, bounds.size());
 
             if let Some(cursor_position) = cursor.position_in(bounds) {
@@ -227,7 +229,7 @@ mod bezier {
                 };
             }
 
-            renderer.draw_geometry([frame]);
+            frame.into()
         }
     }
 }
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index 9f78903c..468443bc 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,6 +1,8 @@
 use iced::alignment;
 use iced::mouse;
-use iced::widget::canvas::{stroke, Cache, Frame, LineCap, Path, Stroke};
+use iced::widget::canvas::{
+    stroke, Cache, Frame, Geometry, LineCap, Path, Stroke,
+};
 use iced::widget::{canvas, container};
 use iced::{
     Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
@@ -82,11 +84,11 @@ impl<Message> canvas::Program<Message> for Clock {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<Geometry> {
         let clock = self.clock.draw(renderer, bounds.size(), |frame| {
             let palette = theme.extended_palette();
 
@@ -163,7 +165,7 @@ impl<Message> canvas::Program<Message> for Clock {
             });
         });
 
-        renderer.draw_geometry([clock]);
+        vec![clock]
     }
 }
 
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index 400766ff..81ad6e41 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -1,6 +1,6 @@
 use iced::alignment::{self, Alignment};
 use iced::mouse;
-use iced::widget::canvas::{self, Canvas, Frame, Path};
+use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
 use iced::widget::{column, row, text, Slider};
 use iced::{
     Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size,
@@ -252,18 +252,18 @@ impl<Message> canvas::Program<Message> for Theme {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         theme: &iced::Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<Geometry> {
         let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
             let palette = theme.extended_palette();
 
             self.draw(frame, palette.background.base.text);
         });
 
-        renderer.draw_geometry([theme]);
+        vec![theme]
     }
 }
 
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index f681b4cc..a3d385f3 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -193,7 +193,9 @@ mod grid {
     use iced::touch;
     use iced::widget::canvas;
     use iced::widget::canvas::event::{self, Event};
-    use iced::widget::canvas::{frame, Cache, Canvas, Frame, Path, Text};
+    use iced::widget::canvas::{
+        frame, Cache, Canvas, Frame, Geometry, Path, Text,
+    };
     use iced::{
         Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
     };
@@ -516,11 +518,11 @@ mod grid {
         fn draw(
             &self,
             _interaction: &Interaction,
-            renderer: &mut Renderer,
+            renderer: &Renderer,
             _theme: &Theme,
             bounds: Rectangle,
             cursor: mouse::Cursor,
-        ) {
+        ) -> Vec<Geometry> {
             let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
 
             let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
@@ -638,9 +640,9 @@ mod grid {
                         }
                     });
 
-                renderer.draw_geometry([life, grid, overlay]);
+                vec![life, grid, overlay]
             } else {
-                renderer.draw_geometry([life, overlay]);
+                vec![life, overlay]
             }
         }
 
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index 198237f5..35d2d3ba 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -292,11 +292,11 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
         fn draw(
             &self,
             _state: &Self::State,
-            renderer: &mut Renderer,
+            renderer: &Renderer,
             theme: &Theme,
             bounds: Rectangle,
             _cursor: mouse::Cursor,
-        ) {
+        ) -> Vec<canvas::Geometry> {
             use canvas::Frame;
 
             let mut frame = canvas::frame(renderer, bounds.size());
@@ -309,7 +309,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
                 palette.background.strong.color,
             );
 
-            renderer.draw_geometry([frame]);
+            vec![frame.into()]
         }
     }
 
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index 41bd0151..6d9039fa 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -5,7 +5,7 @@ use iced::mouse;
 use iced::touch;
 use iced::widget::canvas::event;
 use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::{self, Canvas};
+use iced::widget::canvas::{self, Canvas, Geometry};
 use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme};
 
 use std::collections::HashMap;
@@ -83,11 +83,11 @@ impl canvas::Program<Message> for Multitouch {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         _theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<Geometry> {
         use canvas::Frame;
 
         let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
@@ -156,6 +156,6 @@ impl canvas::Program<Message> for Multitouch {
             }
         });
 
-        renderer.draw_geometry([fingerweb]);
+        vec![fingerweb]
     }
 }
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index b440b8b4..409bc718 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -1,6 +1,6 @@
 use iced::mouse;
 use iced::widget::canvas::event::{self, Event};
-use iced::widget::canvas::{self, Canvas};
+use iced::widget::canvas::{self, Canvas, Geometry};
 use iced::widget::{column, row, slider, text};
 use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme};
 
@@ -107,11 +107,11 @@ impl canvas::Program<Message> for SierpinskiGraph {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         _theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<Geometry> {
         use canvas::Frame;
 
         let geom = self.cache.draw(renderer, bounds.size(), |frame| {
@@ -141,7 +141,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
             });
         });
 
-        renderer.draw_geometry([geom]);
+        vec![geom]
     }
 }
 
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index dd36b711..e8f94ed0 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -10,7 +10,7 @@ use iced::mouse;
 use iced::widget::canvas;
 use iced::widget::canvas::gradient;
 use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::Path;
+use iced::widget::canvas::{Geometry, Path};
 use iced::window;
 use iced::{
     Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription,
@@ -126,11 +126,11 @@ impl<Message> canvas::Program<Message> for State {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         _theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<Geometry> {
         use canvas::Frame;
         use std::f32::consts::PI;
 
@@ -198,7 +198,7 @@ impl<Message> canvas::Program<Message> for State {
             });
         });
 
-        renderer.draw_geometry([background, system]);
+        vec![background, system]
     }
 }
 
diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs
index 9f5baac8..9b605d23 100644
--- a/examples/vectorial_text/src/main.rs
+++ b/examples/vectorial_text/src/main.rs
@@ -124,11 +124,11 @@ impl<Message> canvas::Program<Message> for State {
     fn draw(
         &self,
         _state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         theme: &Theme,
         bounds: Rectangle,
         _cursor: mouse::Cursor,
-    ) {
+    ) -> Vec<canvas::Geometry> {
         use canvas::Frame;
 
         let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
@@ -155,7 +155,7 @@ impl<Message> canvas::Program<Message> for State {
             });
         });
 
-        renderer.draw_geometry([geometry]);
+        vec![geometry]
     }
 }
 
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index fcd91d17..81067491 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -26,13 +26,17 @@ use std::marker::PhantomData;
 /// change or it is explicitly cleared.
 pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
 
+/// The geometry supported by a renderer.
+pub type Geometry<Renderer = crate::Renderer> =
+    <Renderer as geometry::Renderer>::Geometry;
+
 /// A widget capable of drawing 2D graphics.
 ///
 /// ## Drawing a simple circle
 /// If you want to get a quick overview, here's how we can draw a simple circle:
 ///
 /// ```no_run
-/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program};
+/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program};
 /// # use iced_widget::core::{Color, Rectangle};
 /// # use iced_widget::core::mouse;
 /// # use iced_widget::{Renderer, Theme};
@@ -47,7 +51,7 @@ pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
 /// impl Program<()> for Circle {
 ///     type State = ();
 ///
-///     fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) {
+///     fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> {
 ///         // We prepare a new `Frame`
 ///         let mut frame = frame(renderer, bounds.size());
 ///
@@ -58,7 +62,7 @@ pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
 ///         frame.fill(&circle, Color::BLACK);
 ///
 ///         // Finally, we produce the geometry
-///         renderer.draw_geometry([frame]);
+///         vec![frame.into()]
 ///     }
 /// }
 ///
@@ -215,7 +219,12 @@ where
         renderer.with_transformation(
             Transformation::translate(bounds.x, bounds.y),
             |renderer| {
-                self.program.draw(state, renderer, theme, bounds, cursor);
+                let layers =
+                    self.program.draw(state, renderer, theme, bounds, cursor);
+
+                for layer in layers {
+                    renderer.draw_geometry(layer);
+                }
             },
         );
     }
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index 307686de..3ba31474 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -1,5 +1,6 @@
 use crate::canvas::event::{self, Event};
 use crate::canvas::mouse;
+use crate::canvas::Geometry;
 use crate::core::Rectangle;
 use crate::graphics::geometry;
 
@@ -41,11 +42,11 @@ where
     fn draw(
         &self,
         state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         theme: &Theme,
         bounds: Rectangle,
         cursor: mouse::Cursor,
-    );
+    ) -> Vec<Geometry<Renderer>>;
 
     /// Returns the current mouse interaction of the [`Program`].
     ///
@@ -83,12 +84,12 @@ where
     fn draw(
         &self,
         state: &Self::State,
-        renderer: &mut Renderer,
+        renderer: &Renderer,
         theme: &Theme,
         bounds: Rectangle,
         cursor: mouse::Cursor,
-    ) {
-        T::draw(self, state, renderer, theme, bounds, cursor);
+    ) -> Vec<Geometry<Renderer>> {
+        T::draw(self, state, renderer, theme, bounds, cursor)
     }
 
     fn mouse_interaction(
-- 
cgit 


From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 01:35:14 +0100
Subject: Restore `canvas::Frame` API

---
 examples/arc/src/main.rs                  |   2 +-
 examples/bezier_tool/src/main.rs          |  10 +-
 examples/clock/src/main.rs                |   4 +-
 examples/color_palette/src/main.rs        |   2 +-
 examples/game_of_life/src/main.rs         |   8 +-
 examples/layout/src/main.rs               |   6 +-
 examples/loading_spinners/src/circular.rs |   2 +-
 examples/multitouch/src/main.rs           |   2 -
 examples/sierpinski_triangle/src/main.rs  |   2 -
 examples/solar_system/src/main.rs         |   1 -
 examples/vectorial_text/src/main.rs       |   2 -
 graphics/src/geometry.rs                  | 254 +-----------------------------
 graphics/src/geometry/cache.rs            | 123 +++++++++++++++
 graphics/src/geometry/frame.rs            | 208 ++++++++++++++++++++++++
 graphics/src/renderer.rs                  |   3 +-
 renderer/src/fallback.rs                  |  20 +--
 renderer/src/geometry.rs                  | 236 ---------------------------
 renderer/src/geometry/cache.rs            | 137 ----------------
 tiny_skia/src/geometry.rs                 |   8 +-
 wgpu/src/geometry.rs                      |  11 +-
 widget/src/canvas.rs                      |  14 +-
 widget/src/qr_code.rs                     |   2 -
 22 files changed, 378 insertions(+), 679 deletions(-)
 create mode 100644 graphics/src/geometry/cache.rs
 create mode 100644 graphics/src/geometry/frame.rs
 delete mode 100644 renderer/src/geometry.rs
 delete mode 100644 renderer/src/geometry/cache.rs

diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
index 0aba82a9..4576404f 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant};
 
 use iced::mouse;
 use iced::widget::canvas::{
-    self, stroke, Cache, Canvas, Frame, Geometry, Path, Stroke,
+    self, stroke, Cache, Canvas, Geometry, Path, Stroke,
 };
 use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme};
 
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 31d1e29c..289c919b 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -52,9 +52,7 @@ impl Example {
 mod bezier {
     use iced::mouse;
     use iced::widget::canvas::event::{self, Event};
-    use iced::widget::canvas::{
-        self, frame, Canvas, Frame, Geometry, Path, Stroke,
-    };
+    use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
     use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
 
     #[derive(Default)]
@@ -184,7 +182,7 @@ mod bezier {
     }
 
     impl Curve {
-        fn draw_all(curves: &[Curve], frame: &mut impl Frame) {
+        fn draw_all(curves: &[Curve], frame: &mut Frame) {
             let curves = Path::new(|p| {
                 for curve in curves {
                     p.move_to(curve.from);
@@ -209,7 +207,7 @@ mod bezier {
             bounds: Rectangle,
             cursor: mouse::Cursor,
         ) -> Geometry {
-            let mut frame = frame(renderer, bounds.size());
+            let mut frame = Frame::new(renderer, bounds.size());
 
             if let Some(cursor_position) = cursor.position_in(bounds) {
                 match *self {
@@ -229,7 +227,7 @@ mod bezier {
                 };
             }
 
-            frame.into()
+            frame.into_geometry()
         }
     }
 }
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index 468443bc..897f8f1b 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,8 +1,6 @@
 use iced::alignment;
 use iced::mouse;
-use iced::widget::canvas::{
-    stroke, Cache, Frame, Geometry, LineCap, Path, Stroke,
-};
+use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
 use iced::widget::{canvas, container};
 use iced::{
     Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index 81ad6e41..d9325edb 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -156,7 +156,7 @@ impl Theme {
             .into()
     }
 
-    fn draw(&self, frame: &mut impl Frame, text_color: Color) {
+    fn draw(&self, frame: &mut Frame, text_color: Color) {
         let pad = 20.0;
 
         let box_size = Size {
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index a3d385f3..0716b2a4 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -193,9 +193,7 @@ mod grid {
     use iced::touch;
     use iced::widget::canvas;
     use iced::widget::canvas::event::{self, Event};
-    use iced::widget::canvas::{
-        frame, Cache, Canvas, Frame, Geometry, Path, Text,
-    };
+    use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
     use iced::{
         Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
     };
@@ -548,7 +546,7 @@ mod grid {
             });
 
             let overlay = {
-                let mut frame = frame(renderer, bounds.size());
+                let mut frame = Frame::new(renderer, bounds.size());
 
                 let hovered_cell = cursor.position_in(bounds).map(|position| {
                     Cell::at(self.project(position, frame.size()))
@@ -601,7 +599,7 @@ mod grid {
                     ..text
                 });
 
-                frame.into()
+                frame.into_geometry()
             };
 
             if self.scaling >= 0.2 && self.show_lines {
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index 35d2d3ba..713e2b70 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -297,9 +297,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
             bounds: Rectangle,
             _cursor: mouse::Cursor,
         ) -> Vec<canvas::Geometry> {
-            use canvas::Frame;
-
-            let mut frame = canvas::frame(renderer, bounds.size());
+            let mut frame = canvas::Frame::new(renderer, bounds.size());
 
             let palette = theme.extended_palette();
 
@@ -309,7 +307,7 @@ fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
                 palette.background.strong.color,
             );
 
-            vec![frame.into()]
+            vec![frame.into_geometry()]
         }
     }
 
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
index 306988af..cdc6b7ac 100644
--- a/examples/loading_spinners/src/circular.rs
+++ b/examples/loading_spinners/src/circular.rs
@@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
 use iced::event;
 use iced::mouse;
 use iced::time::Instant;
-use iced::widget::canvas::{self, Frame};
+use iced::widget::canvas;
 use iced::window::{self, RedrawRequest};
 use iced::{
     Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index 6d9039fa..2453c7f5 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -88,8 +88,6 @@ impl canvas::Program<Message> for Multitouch {
         bounds: Rectangle,
         _cursor: mouse::Cursor,
     ) -> Vec<Geometry> {
-        use canvas::Frame;
-
         let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
             if self.fingers.len() < 2 {
                 return;
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index 409bc718..9cd6237f 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -112,8 +112,6 @@ impl canvas::Program<Message> for SierpinskiGraph {
         bounds: Rectangle,
         _cursor: mouse::Cursor,
     ) -> Vec<Geometry> {
-        use canvas::Frame;
-
         let geom = self.cache.draw(renderer, bounds.size(), |frame| {
             frame.stroke(
                 &canvas::Path::rectangle(Point::ORIGIN, frame.size()),
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index e8f94ed0..deb211d8 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -131,7 +131,6 @@ impl<Message> canvas::Program<Message> for State {
         bounds: Rectangle,
         _cursor: mouse::Cursor,
     ) -> Vec<Geometry> {
-        use canvas::Frame;
         use std::f32::consts::PI;
 
         let background =
diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs
index 9b605d23..a7391e23 100644
--- a/examples/vectorial_text/src/main.rs
+++ b/examples/vectorial_text/src/main.rs
@@ -129,8 +129,6 @@ impl<Message> canvas::Program<Message> for State {
         bounds: Rectangle,
         _cursor: mouse::Cursor,
     ) -> Vec<canvas::Geometry> {
-        use canvas::Frame;
-
         let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
             let palette = theme.palette();
             let center = bounds.center();
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index cd4c9267..2b18243e 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -1,12 +1,16 @@
 //! Build and draw geometry.
 pub mod fill;
+pub mod frame;
 pub mod path;
 pub mod stroke;
 
+mod cache;
 mod style;
 mod text;
 
+pub use cache::Cache;
 pub use fill::Fill;
+pub use frame::Frame;
 pub use path::Path;
 pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
 pub use style::Style;
@@ -14,18 +18,7 @@ pub use text::Text;
 
 pub use crate::gradient::{self, Gradient};
 
-use crate::core::{Point, Radians, Rectangle, Size, Vector};
-use crate::Primitive;
-
-use std::cell::RefCell;
-use std::sync::Arc;
-
-pub fn frame<Renderer>(renderer: &Renderer, size: Size) -> Renderer::Frame
-where
-    Renderer: self::Renderer,
-{
-    renderer.new_frame(size)
-}
+use crate::core::Size;
 
 /// A renderer capable of drawing some [`Self::Geometry`].
 pub trait Renderer: crate::core::Renderer {
@@ -33,7 +26,7 @@ pub trait Renderer: crate::core::Renderer {
     type Geometry: Geometry;
 
     /// The kind of [`Frame`] this renderer supports.
-    type Frame: Frame<Geometry = Self::Geometry>;
+    type Frame: frame::Backend<Geometry = Self::Geometry>;
 
     fn new_frame(&self, size: Size) -> Self::Frame;
 
@@ -43,127 +36,11 @@ pub trait Renderer: crate::core::Renderer {
 
 pub trait Backend {
     /// The kind of [`Frame`] this backend supports.
-    type Frame: Frame;
+    type Frame: frame::Backend;
 
     fn new_frame(&self, size: Size) -> Self::Frame;
 }
 
-pub trait Frame: Sized + Into<Self::Geometry> {
-    /// The kind of geometry this frame can draw.
-    type Geometry: Geometry;
-
-    /// Returns the width of the [`Frame`].
-    fn width(&self) -> f32;
-
-    /// Returns the height of the [`Frame`].
-    fn height(&self) -> f32;
-
-    /// Returns the dimensions of the [`Frame`].
-    fn size(&self) -> Size;
-
-    /// Returns the coordinate of the center of the [`Frame`].
-    fn center(&self) -> Point;
-
-    /// Draws the given [`Path`] on the [`Frame`] by filling it with the
-    /// provided style.
-    fn fill(&mut self, path: &Path, fill: impl Into<Fill>);
-
-    /// Draws an axis-aligned rectangle given its top-left corner coordinate and
-    /// its `Size` on the [`Frame`] by filling it with the provided style.
-    fn fill_rectangle(
-        &mut self,
-        top_left: Point,
-        size: Size,
-        fill: impl Into<Fill>,
-    );
-
-    /// Draws the stroke of the given [`Path`] on the [`Frame`] with the
-    /// provided style.
-    fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
-
-    /// 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.
-    fn fill_text(&mut self, text: impl Into<Text>);
-
-    /// 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]
-    fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.push_transform();
-
-        let result = f(self);
-
-        self.pop_transform();
-
-        result
-    }
-
-    /// Pushes the current transform in the transform stack.
-    fn push_transform(&mut self);
-
-    /// Pops a transform from the transform stack and sets it as the current transform.
-    fn pop_transform(&mut self);
-
-    /// 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]
-    fn with_clip<R>(
-        &mut self,
-        region: Rectangle,
-        f: impl FnOnce(&mut Self) -> R,
-    ) -> R {
-        let mut frame = self.draft(region.size());
-
-        let result = f(&mut frame);
-
-        let origin = Point::new(region.x, region.y);
-
-        self.paste(frame, origin);
-
-        result
-    }
-
-    /// Creates a new [`Frame`] with the given [`Size`].
-    ///
-    /// Draw its contents back to this [`Frame`] with [`paste`].
-    ///
-    /// [`paste`]: Self::paste
-    fn draft(&mut self, size: Size) -> Self;
-
-    /// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
-    fn paste(&mut self, frame: Self, at: Point);
-
-    /// Applies a translation to the current transform of the [`Frame`].
-    fn translate(&mut self, translation: Vector);
-
-    /// Applies a rotation in radians to the current transform of the [`Frame`].
-    fn rotate(&mut self, angle: impl Into<Radians>);
-
-    /// Applies a uniform scaling to the current transform of the [`Frame`].
-    fn scale(&mut self, scale: impl Into<f32>);
-
-    /// Applies a non-uniform scaling to the current transform of the [`Frame`].
-    fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
-}
-
 pub trait Geometry: Sized {
     type Cache;
 
@@ -171,120 +48,3 @@ pub trait Geometry: Sized {
 
     fn cache(self) -> Self::Cache;
 }
-
-/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
-///
-/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
-/// change or it is explicitly cleared.
-pub struct Cache<Renderer>
-where
-    Renderer: self::Renderer,
-{
-    state: RefCell<State<Renderer::Geometry>>,
-}
-
-impl<Renderer> Cache<Renderer>
-where
-    Renderer: self::Renderer,
-{
-    /// Creates a new empty [`Cache`].
-    pub fn new() -> Self {
-        Cache {
-            state: RefCell::new(State::Empty),
-        }
-    }
-
-    /// Clears the [`Cache`], forcing a redraw the next time it is used.
-    pub fn clear(&self) {
-        *self.state.borrow_mut() = State::Empty;
-    }
-
-    /// Draws [`Geometry`] using the provided closure and stores it in the
-    /// [`Cache`].
-    ///
-    /// The closure will only be called when
-    /// - the bounds have changed since the previous draw call.
-    /// - the [`Cache`] is empty or has been explicitly cleared.
-    ///
-    /// Otherwise, the previously stored [`Geometry`] will be returned. The
-    /// [`Cache`] is not cleared in this case. In other words, it will keep
-    /// returning the stored [`Geometry`] if needed.
-    pub fn draw(
-        &self,
-        renderer: &Renderer,
-        bounds: Size,
-        draw_fn: impl FnOnce(&mut Renderer::Frame),
-    ) -> Renderer::Geometry {
-        use std::ops::Deref;
-
-        if let State::Filled {
-            bounds: cached_bounds,
-            geometry,
-        } = self.state.borrow().deref()
-        {
-            if *cached_bounds == bounds {
-                return Geometry::load(geometry);
-            }
-        }
-
-        let mut frame = frame(renderer, bounds);
-        draw_fn(&mut frame);
-
-        let geometry = frame.into().cache();
-        let result = Geometry::load(&geometry);
-
-        *self.state.borrow_mut() = State::Filled { bounds, geometry };
-
-        result
-    }
-}
-
-impl<Renderer> std::fmt::Debug for Cache<Renderer>
-where
-    Renderer: self::Renderer,
-{
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let state = self.state.borrow();
-
-        match *state {
-            State::Empty => write!(f, "Cache::Empty"),
-            State::Filled { bounds, .. } => {
-                write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
-            }
-        }
-    }
-}
-
-impl<Renderer> Default for Cache<Renderer>
-where
-    Renderer: self::Renderer,
-{
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-enum State<Geometry>
-where
-    Geometry: self::Geometry,
-{
-    Empty,
-    Filled {
-        bounds: Size,
-        geometry: Geometry::Cache,
-    },
-}
-
-impl<T> Geometry for Primitive<T> {
-    type Cache = Arc<Self>;
-
-    fn load(cache: &Arc<Self>) -> Self {
-        Self::Cache {
-            content: cache.clone(),
-        }
-    }
-
-    fn cache(self) -> Arc<Self> {
-        Arc::new(self)
-    }
-}
diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs
new file mode 100644
index 00000000..490e69e2
--- /dev/null
+++ b/graphics/src/geometry/cache.rs
@@ -0,0 +1,123 @@
+use crate::core::Size;
+use crate::geometry::{self, Frame, Geometry};
+use crate::Primitive;
+
+use std::cell::RefCell;
+use std::sync::Arc;
+
+/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
+///
+/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
+/// change or it is explicitly cleared.
+pub struct Cache<Renderer>
+where
+    Renderer: geometry::Renderer,
+{
+    state: RefCell<State<Renderer::Geometry>>,
+}
+
+impl<Renderer> Cache<Renderer>
+where
+    Renderer: geometry::Renderer,
+{
+    /// Creates a new empty [`Cache`].
+    pub fn new() -> Self {
+        Cache {
+            state: RefCell::new(State::Empty),
+        }
+    }
+
+    /// Clears the [`Cache`], forcing a redraw the next time it is used.
+    pub fn clear(&self) {
+        *self.state.borrow_mut() = State::Empty;
+    }
+
+    /// Draws [`Geometry`] using the provided closure and stores it in the
+    /// [`Cache`].
+    ///
+    /// The closure will only be called when
+    /// - the bounds have changed since the previous draw call.
+    /// - the [`Cache`] is empty or has been explicitly cleared.
+    ///
+    /// Otherwise, the previously stored [`Geometry`] will be returned. The
+    /// [`Cache`] is not cleared in this case. In other words, it will keep
+    /// returning the stored [`Geometry`] if needed.
+    pub fn draw(
+        &self,
+        renderer: &Renderer,
+        bounds: Size,
+        draw_fn: impl FnOnce(&mut Frame<Renderer>),
+    ) -> Renderer::Geometry {
+        use std::ops::Deref;
+
+        if let State::Filled {
+            bounds: cached_bounds,
+            geometry,
+        } = self.state.borrow().deref()
+        {
+            if *cached_bounds == bounds {
+                return Geometry::load(geometry);
+            }
+        }
+
+        let mut frame = Frame::new(renderer, bounds);
+        draw_fn(&mut frame);
+
+        let geometry = frame.into_geometry().cache();
+        let result = Geometry::load(&geometry);
+
+        *self.state.borrow_mut() = State::Filled { bounds, geometry };
+
+        result
+    }
+}
+
+impl<Renderer> std::fmt::Debug for Cache<Renderer>
+where
+    Renderer: geometry::Renderer,
+{
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let state = self.state.borrow();
+
+        match *state {
+            State::Empty => write!(f, "Cache::Empty"),
+            State::Filled { bounds, .. } => {
+                write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
+            }
+        }
+    }
+}
+
+impl<Renderer> Default for Cache<Renderer>
+where
+    Renderer: geometry::Renderer,
+{
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+enum State<Geometry>
+where
+    Geometry: self::Geometry,
+{
+    Empty,
+    Filled {
+        bounds: Size,
+        geometry: Geometry::Cache,
+    },
+}
+
+impl<T> Geometry for Primitive<T> {
+    type Cache = Arc<Self>;
+
+    fn load(cache: &Arc<Self>) -> Self {
+        Self::Cache {
+            content: cache.clone(),
+        }
+    }
+
+    fn cache(self) -> Arc<Self> {
+        Arc::new(self)
+    }
+}
diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
new file mode 100644
index 00000000..e88c43b0
--- /dev/null
+++ b/graphics/src/geometry/frame.rs
@@ -0,0 +1,208 @@
+use crate::core::{Point, Radians, Rectangle, Size, Vector};
+use crate::geometry::{self, Geometry};
+use crate::geometry::{Fill, Path, Stroke, Text};
+
+pub struct Frame<Renderer>
+where
+    Renderer: geometry::Renderer,
+{
+    raw: Renderer::Frame,
+}
+
+impl<Renderer> Frame<Renderer>
+where
+    Renderer: geometry::Renderer,
+{
+    pub fn new(renderer: &Renderer, size: Size) -> Self {
+        Self {
+            raw: renderer.new_frame(size),
+        }
+    }
+
+    /// Returns the width of the [`Frame`].
+    pub fn width(&self) -> f32 {
+        self.raw.width()
+    }
+
+    /// Returns the height of the [`Frame`].
+    pub fn height(&self) -> f32 {
+        self.raw.height()
+    }
+
+    /// Returns the dimensions of the [`Frame`].
+    pub fn size(&self) -> Size {
+        self.raw.size()
+    }
+
+    /// Returns the coordinate of the center of the [`Frame`].
+    pub fn center(&self) -> Point {
+        self.raw.center()
+    }
+
+    /// 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>) {
+        self.raw.fill(path, fill);
+    }
+
+    /// 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(
+        &mut self,
+        top_left: Point,
+        size: Size,
+        fill: impl Into<Fill>,
+    ) {
+        self.raw.fill_rectangle(top_left, size, fill);
+    }
+
+    /// 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>>) {
+        self.raw.stroke(path, stroke);
+    }
+
+    /// 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>) {
+        self.raw.fill_text(text);
+    }
+
+    /// 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 Self) -> R) -> R {
+        self.push_transform();
+
+        let result = f(self);
+
+        self.pop_transform();
+
+        result
+    }
+
+    /// Pushes the current transform in the transform stack.
+    pub fn push_transform(&mut self) {
+        self.raw.push_transform();
+    }
+
+    /// Pops a transform from the transform stack and sets it as the current transform.
+    pub fn pop_transform(&mut self) {
+        self.raw.pop_transform();
+    }
+
+    /// 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 Self) -> R,
+    ) -> R {
+        let mut frame = self.draft(region.size());
+
+        let result = f(&mut frame);
+
+        let origin = Point::new(region.x, region.y);
+
+        self.paste(frame, origin);
+
+        result
+    }
+
+    /// Creates a new [`Frame`] with the given [`Size`].
+    ///
+    /// Draw its contents back to this [`Frame`] with [`paste`].
+    ///
+    /// [`paste`]: Self::paste
+    pub fn draft(&mut self, size: Size) -> Self {
+        Self {
+            raw: self.raw.draft(size),
+        }
+    }
+
+    /// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
+    pub fn paste(&mut self, frame: Self, at: Point) {
+        self.raw.paste(frame.raw, at);
+    }
+
+    /// Applies a translation to the current transform of the [`Frame`].
+    pub fn translate(&mut self, translation: Vector) {
+        self.raw.translate(translation);
+    }
+
+    /// Applies a rotation in radians to the current transform of the [`Frame`].
+    pub fn rotate(&mut self, angle: impl Into<Radians>) {
+        self.raw.rotate(angle);
+    }
+
+    /// Applies a uniform scaling to the current transform of the [`Frame`].
+    pub fn scale(&mut self, scale: impl Into<f32>) {
+        self.raw.scale(scale);
+    }
+
+    /// Applies a non-uniform scaling to the current transform of the [`Frame`].
+    pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+        self.raw.scale_nonuniform(scale);
+    }
+
+    pub fn into_geometry(self) -> Renderer::Geometry {
+        self.raw.into_geometry()
+    }
+}
+
+/// The internal implementation of a [`Frame`].
+///
+/// Analogous to [`Frame`]. See [`Frame`] for the documentation
+/// of each method.
+#[allow(missing_docs)]
+pub trait Backend: Sized {
+    type Geometry: Geometry;
+
+    fn width(&self) -> f32;
+    fn height(&self) -> f32;
+    fn size(&self) -> Size;
+    fn center(&self) -> Point;
+
+    fn push_transform(&mut self);
+    fn pop_transform(&mut self);
+
+    fn translate(&mut self, translation: Vector);
+    fn rotate(&mut self, angle: impl Into<Radians>);
+    fn scale(&mut self, scale: impl Into<f32>);
+    fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
+
+    fn draft(&mut self, size: Size) -> Self;
+    fn paste(&mut self, frame: Self, at: Point);
+
+    fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
+
+    fn fill(&mut self, path: &Path, fill: impl Into<Fill>);
+    fn fill_text(&mut self, text: impl Into<Text>);
+    fn fill_rectangle(
+        &mut self,
+        top_left: Point,
+        size: Size,
+        fill: impl Into<Fill>,
+    );
+
+    fn into_geometry(self) -> Self::Geometry;
+}
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 3b21aa11..2fcb55aa 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -257,7 +257,8 @@ impl<B: Backend> mesh::Renderer for Renderer<B> {
 impl<B> crate::geometry::Renderer for Renderer<B>
 where
     B: Backend + crate::geometry::Backend,
-    B::Frame: crate::geometry::Frame<Geometry = Primitive<B::Primitive>>,
+    B::Frame:
+        crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>,
 {
     type Frame = B::Frame;
     type Geometry = Primitive<B::Primitive>;
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index a4c725c0..659f253d 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -459,10 +459,10 @@ mod geometry {
         Right(R),
     }
 
-    impl<L, R> geometry::Frame for Frame<L, R>
+    impl<L, R> geometry::frame::Backend for Frame<L, R>
     where
-        L: geometry::Frame,
-        R: geometry::Frame,
+        L: geometry::frame::Backend,
+        R: geometry::frame::Backend,
     {
         type Geometry = Geometry<L::Geometry, R::Geometry>;
 
@@ -545,17 +545,11 @@ mod geometry {
         fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
             delegate!(self, frame, frame.scale_nonuniform(scale));
         }
-    }
 
-    impl<L, R> From<Frame<L, R>> for Geometry<L::Geometry, R::Geometry>
-    where
-        L: geometry::Frame,
-        R: geometry::Frame,
-    {
-        fn from(frame: Frame<L, R>) -> Self {
-            match frame {
-                Frame::Left(frame) => Self::Left(frame.into()),
-                Frame::Right(frame) => Self::Right(frame.into()),
+        fn into_geometry(self) -> Self::Geometry {
+            match self {
+                Frame::Left(frame) => Geometry::Left(frame.into_geometry()),
+                Frame::Right(frame) => Geometry::Right(frame.into_geometry()),
             }
         }
     }
diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs
deleted file mode 100644
index a16cecd5..00000000
--- a/renderer/src/geometry.rs
+++ /dev/null
@@ -1,236 +0,0 @@
-mod cache;
-
-pub use cache::Cache;
-
-use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector};
-use crate::graphics::geometry::{Fill, Path, Stroke, Text};
-use crate::Renderer;
-
-macro_rules! delegate {
-    ($frame:expr, $name:ident, $body:expr) => {
-        match $frame {
-            Self::TinySkia($name) => $body,
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu($name) => $body,
-            #[cfg(feature = "custom")]
-            Self::Custom($name) => $body,
-        }
-    };
-}
-
-pub enum Geometry {
-    TinySkia(iced_tiny_skia::Primitive),
-    #[cfg(feature = "wgpu")]
-    Wgpu(iced_wgpu::Primitive),
-    #[cfg(feature = "custom")]
-    Custom(Box<dyn crate::custom::Geometry>),
-}
-
-impl Geometry {
-    pub fn transform(self, transformation: Transformation) -> Self {
-        match self {
-            Self::TinySkia(primitive) => {
-                Self::TinySkia(primitive.transform(transformation))
-            }
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(primitive) => {
-                Self::Wgpu(primitive.transform(transformation))
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(geometry) => {
-                Self::Custom(geometry.transform(transformation))
-            }
-        }
-    }
-}
-
-pub enum Frame {
-    TinySkia(iced_tiny_skia::geometry::Frame),
-    #[cfg(feature = "wgpu")]
-    Wgpu(iced_wgpu::geometry::Frame),
-    #[cfg(feature = "custom")]
-    Custom(Box<dyn crate::custom::Frame>),
-}
-
-impl Frame {
-    pub fn new(renderer: &Renderer, size: Size) -> Self {
-        match renderer {
-            Renderer::TinySkia(_) => {
-                Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size))
-            }
-            #[cfg(feature = "wgpu")]
-            Renderer::Wgpu(_) => {
-                Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
-            }
-            #[cfg(feature = "custom")]
-            Renderer::Custom(renderer) => {
-                Frame::Custom(renderer.new_frame(size))
-            }
-        }
-    }
-
-    /// Returns the width of the [`Frame`].
-    #[inline]
-    pub fn width(&self) -> f32 {
-        delegate!(self, frame, frame.width())
-    }
-
-    /// Returns the height of the [`Frame`].
-    #[inline]
-    pub fn height(&self) -> f32 {
-        delegate!(self, frame, frame.height())
-    }
-
-    /// Returns the dimensions of the [`Frame`].
-    #[inline]
-    pub fn size(&self) -> Size {
-        delegate!(self, frame, frame.size())
-    }
-
-    /// Returns the coordinate of the center of the [`Frame`].
-    #[inline]
-    pub fn center(&self) -> Point {
-        delegate!(self, frame, frame.center())
-    }
-
-    /// 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>) {
-        delegate!(self, frame, frame.fill(path, fill.into()));
-    }
-
-    /// 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(
-        &mut self,
-        top_left: Point,
-        size: Size,
-        fill: impl Into<Fill>,
-    ) {
-        delegate!(
-            self,
-            frame,
-            frame.fill_rectangle(top_left, size, fill.into())
-        );
-    }
-
-    /// 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>>) {
-        delegate!(self, frame, frame.stroke(path, stroke.into()));
-    }
-
-    /// 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>) {
-        delegate!(self, frame, frame.fill_text(text.into()));
-    }
-
-    /// 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 {
-        delegate!(self, frame, frame.push_transform());
-
-        let result = f(self);
-
-        delegate!(self, frame, frame.pop_transform());
-
-        result
-    }
-
-    /// 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 = match self {
-            Self::TinySkia(_) => Self::TinySkia(
-                iced_tiny_skia::geometry::Frame::new(region.size()),
-            ),
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(_) => {
-                Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size()))
-            }
-            #[cfg(feature = "custom")]
-            Self::Custom(frame) => Self::Custom(frame.new(region.size())),
-        };
-
-        let result = f(&mut frame);
-
-        let origin = Point::new(region.x, region.y);
-
-        match (self, frame) {
-            (Self::TinySkia(target), Self::TinySkia(frame)) => {
-                target.clip(frame, origin);
-            }
-            #[cfg(feature = "wgpu")]
-            (Self::Wgpu(target), Self::Wgpu(frame)) => {
-                target.clip(frame, origin);
-            }
-            #[cfg(feature = "custom")]
-            (Self::Custom(target), Self::Custom(frame)) => {
-                target.clip(frame, origin);
-            }
-            #[allow(unreachable_patterns)]
-            _ => unreachable!(),
-        };
-
-        result
-    }
-
-    /// Applies a translation to the current transform of the [`Frame`].
-    #[inline]
-    pub fn translate(&mut self, translation: Vector) {
-        delegate!(self, frame, frame.translate(translation));
-    }
-
-    /// Applies a rotation in radians to the current transform of the [`Frame`].
-    #[inline]
-    pub fn rotate(&mut self, angle: impl Into<Radians>) {
-        delegate!(self, frame, frame.rotate(angle.into()));
-    }
-
-    /// Applies a uniform scaling to the current transform of the [`Frame`].
-    #[inline]
-    pub fn scale(&mut self, scale: impl Into<f32>) {
-        delegate!(self, frame, frame.scale(scale.into()));
-    }
-
-    /// Applies a non-uniform scaling to the current transform of the [`Frame`].
-    #[inline]
-    pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
-        delegate!(self, frame, frame.scale_nonuniform(scale.into()));
-    }
-
-    pub fn into_geometry(self) -> Geometry {
-        match self {
-            Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()),
-            #[cfg(feature = "wgpu")]
-            Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()),
-            #[cfg(feature = "custom")]
-            Self::Custom(frame) => Geometry::Custom(frame.into_geometry()),
-        }
-    }
-}
diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs
deleted file mode 100644
index 20f73f22..00000000
--- a/renderer/src/geometry/cache.rs
+++ /dev/null
@@ -1,137 +0,0 @@
-use crate::core::Size;
-use crate::geometry::{Frame, Geometry};
-use crate::Renderer;
-
-use std::cell::RefCell;
-use std::sync::Arc;
-
-/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
-///
-/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
-/// change or it is explicitly cleared.
-#[derive(Debug, Default)]
-pub struct Cache {
-    state: RefCell<State>,
-}
-
-#[derive(Debug, Default)]
-enum State {
-    #[default]
-    Empty,
-    Filled {
-        bounds: Size,
-        primitive: Internal,
-    },
-}
-
-#[derive(Debug, Clone)]
-enum Internal {
-    TinySkia(Arc<iced_tiny_skia::Primitive>),
-    #[cfg(feature = "wgpu")]
-    Wgpu(Arc<iced_wgpu::Primitive>),
-    #[cfg(feature = "custom")]
-    Custom(Arc<dyn crate::custom::Geometry>),
-}
-
-impl Cache {
-    /// Creates a new empty [`Cache`].
-    pub fn new() -> Self {
-        Cache {
-            state: RefCell::default(),
-        }
-    }
-
-    /// Clears the [`Cache`], forcing a redraw the next time it is used.
-    pub fn clear(&self) {
-        *self.state.borrow_mut() = State::Empty;
-    }
-
-    /// Draws [`Geometry`] using the provided closure and stores it in the
-    /// [`Cache`].
-    ///
-    /// The closure will only be called when
-    /// - the bounds have changed since the previous draw call.
-    /// - the [`Cache`] is empty or has been explicitly cleared.
-    ///
-    /// Otherwise, the previously stored [`Geometry`] will be returned. The
-    /// [`Cache`] is not cleared in this case. In other words, it will keep
-    /// returning the stored [`Geometry`] if needed.
-    pub fn draw(
-        &self,
-        renderer: &Renderer,
-        bounds: Size,
-        draw_fn: impl FnOnce(&mut Frame),
-    ) -> Geometry {
-        use std::ops::Deref;
-
-        if let State::Filled {
-            bounds: cached_bounds,
-            primitive,
-        } = self.state.borrow().deref()
-        {
-            if *cached_bounds == bounds {
-                match primitive {
-                    Internal::TinySkia(primitive) => {
-                        return Geometry::TinySkia(
-                            iced_tiny_skia::Primitive::Cache {
-                                content: primitive.clone(),
-                            },
-                        );
-                    }
-                    #[cfg(feature = "wgpu")]
-                    Internal::Wgpu(primitive) => {
-                        return Geometry::Wgpu(iced_wgpu::Primitive::Cache {
-                            content: primitive.clone(),
-                        });
-                    }
-                    #[cfg(feature = "custom")]
-                    Internal::Custom(geometry) => {
-                        return Geometry::Custom(geometry.clone().load())
-                    }
-                }
-            }
-        }
-
-        let mut frame = Frame::new(renderer, bounds);
-        draw_fn(&mut frame);
-
-        let primitive = {
-            let geometry = frame.into_geometry();
-
-            match geometry {
-                Geometry::TinySkia(primitive) => {
-                    Internal::TinySkia(Arc::new(primitive))
-                }
-                #[cfg(feature = "wgpu")]
-                Geometry::Wgpu(primitive) => {
-                    Internal::Wgpu(Arc::new(primitive))
-                }
-                #[cfg(feature = "custom")]
-                Geometry::Custom(geometry) => {
-                    Internal::Custom(geometry.cache())
-                }
-            }
-        };
-
-        *self.state.borrow_mut() = State::Filled {
-            bounds,
-            primitive: primitive.clone(),
-        };
-
-        match primitive {
-            Internal::TinySkia(primitive) => {
-                Geometry::TinySkia(iced_tiny_skia::Primitive::Cache {
-                    content: primitive,
-                })
-            }
-            #[cfg(feature = "wgpu")]
-            Internal::Wgpu(primitive) => {
-                Geometry::Wgpu(iced_wgpu::Primitive::Cache {
-                    content: primitive,
-                })
-            }
-            #[cfg(feature = "custom")]
-            Internal::Custom(geometry) => Geometry::Custom(geometry.load()),
-        }
-    }
-}
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index 6b1888d0..76482e12 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -35,7 +35,7 @@ impl Frame {
     }
 }
 
-impl geometry::Frame for Frame {
+impl geometry::frame::Backend for Frame {
     type Geometry = Primitive;
 
     fn width(&self) -> f32 {
@@ -228,11 +228,9 @@ impl geometry::Frame for Frame {
 
         self.transform = self.transform.pre_scale(scale.x, scale.y);
     }
-}
 
-impl From<Frame> for Primitive {
-    fn from(frame: Frame) -> Self {
-        frame.into_primitive()
+    fn into_geometry(self) -> Self::Geometry {
+        self.into_primitive()
     }
 }
 
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index 4f6b67b1..ba56c59d 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -80,7 +80,7 @@ impl Frame {
     }
 }
 
-impl geometry::Frame for Frame {
+impl geometry::frame::Backend for Frame {
     type Geometry = Primitive;
 
     /// Creates a new empty [`Frame`] with the given dimensions.
@@ -339,11 +339,10 @@ impl geometry::Frame for Frame {
             ],
         });
     }
-}
-impl From<Frame> for Primitive {
-    fn from(frame: Frame) -> Self {
-        Self::Group {
-            primitives: frame.into_primitives(),
+
+    fn into_geometry(self) -> Self::Geometry {
+        Primitive::Group {
+            primitives: self.into_primitives(),
         }
     }
 }
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 81067491..7a21895a 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -6,7 +6,10 @@ mod program;
 pub use event::Event;
 pub use program::Program;
 
-pub use crate::graphics::geometry::*;
+pub use crate::graphics::geometry::{
+    fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,
+    Path, Stroke, Style, Text,
+};
 
 use crate::core;
 use crate::core::layout::{self, Layout};
@@ -30,13 +33,16 @@ pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
 pub type Geometry<Renderer = crate::Renderer> =
     <Renderer as geometry::Renderer>::Geometry;
 
+/// The frame supported by a renderer.
+pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
+
 /// A widget capable of drawing 2D graphics.
 ///
 /// ## Drawing a simple circle
 /// If you want to get a quick overview, here's how we can draw a simple circle:
 ///
 /// ```no_run
-/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program};
+/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program};
 /// # use iced_widget::core::{Color, Rectangle};
 /// # use iced_widget::core::mouse;
 /// # use iced_widget::{Renderer, Theme};
@@ -53,7 +59,7 @@ pub type Geometry<Renderer = crate::Renderer> =
 ///
 ///     fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> {
 ///         // We prepare a new `Frame`
-///         let mut frame = frame(renderer, bounds.size());
+///         let mut frame = Frame::new(renderer, bounds.size());
 ///
 ///         // We create a `Path` representing a simple circle
 ///         let circle = Path::circle(frame.center(), self.radius);
@@ -62,7 +68,7 @@ pub type Geometry<Renderer = crate::Renderer> =
 ///         frame.fill(&circle, Color::BLACK);
 ///
 ///         // Finally, we produce the geometry
-///         vec![frame.into()]
+///         vec![frame.into_geometry()]
 ///     }
 /// }
 ///
diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs
index bc46aaaa..84898dc0 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -91,8 +91,6 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
         _cursor: mouse::Cursor,
         _viewport: &Rectangle,
     ) {
-        use canvas::Frame;
-
         let state = tree.state.downcast_ref::<State>();
 
         let bounds = layout.bounds();
-- 
cgit 


From 85800c99ab285efd244c0addfdcf3c732a98de1d Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 01:53:48 +0100
Subject: Fix broken links in documentation

---
 graphics/src/cached.rs         | 33 +++++++++++++++++++++++++++++++++
 graphics/src/geometry.rs       | 14 +++++---------
 graphics/src/geometry/cache.rs | 33 +++++++++------------------------
 graphics/src/geometry/frame.rs | 11 ++++++++---
 graphics/src/lib.rs            |  6 ++++--
 graphics/src/mesh.rs           |  2 ++
 graphics/src/renderer.rs       | 12 ------------
 renderer/src/fallback.rs       |  7 ++++---
 widget/src/canvas/program.rs   |  9 ++++++++-
 9 files changed, 73 insertions(+), 54 deletions(-)
 create mode 100644 graphics/src/cached.rs

diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs
new file mode 100644
index 00000000..f7968c9f
--- /dev/null
+++ b/graphics/src/cached.rs
@@ -0,0 +1,33 @@
+use crate::Primitive;
+
+use std::sync::Arc;
+
+/// A piece of data that can be cached.
+pub trait Cached: Sized {
+    /// The type of cache produced.
+    type Cache;
+
+    /// Loads the [`Cache`] into a proper instance.
+    ///
+    /// [`Cache`]: Self::Cache
+    fn load(cache: &Self::Cache) -> Self;
+
+    /// Caches this value, producing its corresponding [`Cache`].
+    ///
+    /// [`Cache`]: Self::Cache
+    fn cache(self) -> Self::Cache;
+}
+
+impl<T> Cached for Primitive<T> {
+    type Cache = Arc<Self>;
+
+    fn load(cache: &Arc<Self>) -> Self {
+        Self::Cache {
+            content: cache.clone(),
+        }
+    }
+
+    fn cache(self) -> Arc<Self> {
+        Arc::new(self)
+    }
+}
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index 2b18243e..cc2359b6 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -19,32 +19,28 @@ pub use text::Text;
 pub use crate::gradient::{self, Gradient};
 
 use crate::core::Size;
+use crate::Cached;
 
 /// A renderer capable of drawing some [`Self::Geometry`].
 pub trait Renderer: crate::core::Renderer {
     /// The kind of geometry this renderer can draw.
-    type Geometry: Geometry;
+    type Geometry: Cached;
 
     /// The kind of [`Frame`] this renderer supports.
     type Frame: frame::Backend<Geometry = Self::Geometry>;
 
+    /// Creates a new [`Self::Frame`].
     fn new_frame(&self, size: Size) -> Self::Frame;
 
     /// Draws the given [`Self::Geometry`].
     fn draw_geometry(&mut self, geometry: Self::Geometry);
 }
 
+/// The graphics backend of a geometry renderer.
 pub trait Backend {
     /// The kind of [`Frame`] this backend supports.
     type Frame: frame::Backend;
 
+    /// Creates a new [`Self::Frame`].
     fn new_frame(&self, size: Size) -> Self::Frame;
 }
-
-pub trait Geometry: Sized {
-    type Cache;
-
-    fn load(cache: &Self::Cache) -> Self;
-
-    fn cache(self) -> Self::Cache;
-}
diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs
index 490e69e2..37d433c2 100644
--- a/graphics/src/geometry/cache.rs
+++ b/graphics/src/geometry/cache.rs
@@ -1,11 +1,10 @@
 use crate::core::Size;
-use crate::geometry::{self, Frame, Geometry};
-use crate::Primitive;
+use crate::geometry::{self, Frame};
+use crate::Cached;
 
 use std::cell::RefCell;
-use std::sync::Arc;
 
-/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
+/// A simple cache that stores generated geometry to avoid recomputation.
 ///
 /// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
 /// change or it is explicitly cleared.
@@ -32,16 +31,16 @@ where
         *self.state.borrow_mut() = State::Empty;
     }
 
-    /// Draws [`Geometry`] using the provided closure and stores it in the
+    /// Draws geometry using the provided closure and stores it in the
     /// [`Cache`].
     ///
     /// The closure will only be called when
     /// - the bounds have changed since the previous draw call.
     /// - the [`Cache`] is empty or has been explicitly cleared.
     ///
-    /// Otherwise, the previously stored [`Geometry`] will be returned. The
+    /// Otherwise, the previously stored geometry will be returned. The
     /// [`Cache`] is not cleared in this case. In other words, it will keep
-    /// returning the stored [`Geometry`] if needed.
+    /// returning the stored geometry if needed.
     pub fn draw(
         &self,
         renderer: &Renderer,
@@ -56,7 +55,7 @@ where
         } = self.state.borrow().deref()
         {
             if *cached_bounds == bounds {
-                return Geometry::load(geometry);
+                return Cached::load(geometry);
             }
         }
 
@@ -64,7 +63,7 @@ where
         draw_fn(&mut frame);
 
         let geometry = frame.into_geometry().cache();
-        let result = Geometry::load(&geometry);
+        let result = Cached::load(&geometry);
 
         *self.state.borrow_mut() = State::Filled { bounds, geometry };
 
@@ -99,7 +98,7 @@ where
 
 enum State<Geometry>
 where
-    Geometry: self::Geometry,
+    Geometry: Cached,
 {
     Empty,
     Filled {
@@ -107,17 +106,3 @@ where
         geometry: Geometry::Cache,
     },
 }
-
-impl<T> Geometry for Primitive<T> {
-    type Cache = Arc<Self>;
-
-    fn load(cache: &Arc<Self>) -> Self {
-        Self::Cache {
-            content: cache.clone(),
-        }
-    }
-
-    fn cache(self) -> Arc<Self> {
-        Arc::new(self)
-    }
-}
diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
index e88c43b0..37e0df38 100644
--- a/graphics/src/geometry/frame.rs
+++ b/graphics/src/geometry/frame.rs
@@ -1,7 +1,10 @@
+//! Draw and generate geometry.
 use crate::core::{Point, Radians, Rectangle, Size, Vector};
-use crate::geometry::{self, Geometry};
-use crate::geometry::{Fill, Path, Stroke, Text};
+use crate::geometry::{self, Fill, Path, Stroke, Text};
+use crate::Cached;
 
+/// The region of a surface that can be used to draw geometry.
+#[allow(missing_debug_implementations)]
 pub struct Frame<Renderer>
 where
     Renderer: geometry::Renderer,
@@ -13,6 +16,7 @@ impl<Renderer> Frame<Renderer>
 where
     Renderer: geometry::Renderer,
 {
+    /// Creates a new [`Frame`] with the given dimensions.
     pub fn new(renderer: &Renderer, size: Size) -> Self {
         Self {
             raw: renderer.new_frame(size),
@@ -164,6 +168,7 @@ where
         self.raw.scale_nonuniform(scale);
     }
 
+    /// Turns the [`Frame`] into its underlying geometry.
     pub fn into_geometry(self) -> Renderer::Geometry {
         self.raw.into_geometry()
     }
@@ -175,7 +180,7 @@ where
 /// of each method.
 #[allow(missing_docs)]
 pub trait Backend: Sized {
-    type Geometry: Geometry;
+    type Geometry: Cached;
 
     fn width(&self) -> f32;
     fn height(&self) -> f32;
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 6d0862ad..a682b89b 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -9,14 +9,15 @@
 )]
 #![forbid(rust_2018_idioms)]
 #![deny(
-    // missing_debug_implementations,
-    // missing_docs,
+    missing_debug_implementations,
+    missing_docs,
     unsafe_code,
     unused_results,
     rustdoc::broken_intra_doc_links
 )]
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 mod antialiasing;
+mod cached;
 mod error;
 mod primitive;
 mod viewport;
@@ -38,6 +39,7 @@ pub mod image;
 
 pub use antialiasing::Antialiasing;
 pub use backend::Backend;
+pub use cached::Cached;
 pub use compositor::Compositor;
 pub use damage::Damage;
 pub use error::Error;
diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs
index 5be3ee5b..d671f494 100644
--- a/graphics/src/mesh.rs
+++ b/graphics/src/mesh.rs
@@ -75,6 +75,8 @@ pub struct GradientVertex2D {
     pub gradient: gradient::Packed,
 }
 
+/// A renderer capable of drawing a [`Mesh`].
 pub trait Renderer: core::Renderer {
+    /// Draws the given [`Mesh`].
     fn draw_mesh(&mut self, mesh: Mesh);
 }
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 2fcb55aa..eb720495 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -58,18 +58,6 @@ impl<B: Backend> Renderer<B> {
     ) -> O {
         f(&mut self.backend, &self.primitives)
     }
-
-    #[cfg(feature = "geometry")]
-    pub fn draw_geometry<Geometry>(
-        &mut self,
-        layers: impl IntoIterator<Item = Geometry>,
-    ) where
-        Geometry: Into<Primitive<B::Primitive>>,
-    {
-        for layer in layers {
-            self.draw_primitive(layer.into());
-        }
-    }
 }
 
 impl<B: Backend> iced_core::Renderer for Renderer<B> {
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 659f253d..249da9e9 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -398,6 +398,7 @@ mod geometry {
     use super::Renderer;
     use crate::core::{Point, Radians, Size, Vector};
     use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
+    use crate::graphics::Cached;
 
     impl<L, R> geometry::Renderer for Renderer<L, R>
     where
@@ -432,10 +433,10 @@ mod geometry {
         Right(R),
     }
 
-    impl<L, R> geometry::Geometry for Geometry<L, R>
+    impl<L, R> Cached for Geometry<L, R>
     where
-        L: geometry::Geometry,
-        R: geometry::Geometry,
+        L: Cached,
+        R: Cached,
     {
         type Cache = Geometry<L::Cache, R::Cache>;
 
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index 3ba31474..a7ded0f4 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -38,7 +38,14 @@ where
         (event::Status::Ignored, None)
     }
 
-    /// Draws the state of the [`Program`] with the given [`Renderer`].
+    /// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
+    ///
+    /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
+    /// [`Cache`].
+    ///
+    /// [`Geometry`]: crate::canvas::Geometry
+    /// [`Frame`]: crate::canvas::Frame
+    /// [`Cache`]: crate::canvas::Cache
     fn draw(
         &self,
         state: &Self::State,
-- 
cgit 


From bbafeed13d20f2cbd6fc18b949b34596aa0c6c2e Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 01:55:28 +0100
Subject: Fix outdated warning in docs of `Frame::fill_text`

---
 graphics/src/geometry/frame.rs | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
index 37e0df38..b54fca5d 100644
--- a/graphics/src/geometry/frame.rs
+++ b/graphics/src/geometry/frame.rs
@@ -69,16 +69,9 @@ where
     /// 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
+    /// __Warning:__ 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>) {
         self.raw.fill_text(text);
     }
-- 
cgit 


From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 05:27:31 +0100
Subject: Make `iced_tiny_skia` optional with a `tiny-skia` feature

---
 Cargo.toml                                |  4 +-
 core/src/element.rs                       |  4 +-
 core/src/renderer.rs                      |  4 --
 core/src/renderer/null.rs                 | 34 +++++++++--------
 core/src/size.rs                          |  2 +-
 examples/loading_spinners/src/circular.rs |  4 +-
 graphics/src/backend.rs                   |  3 ++
 graphics/src/compositor.rs                | 62 ++++++++++++++++++++++++++++++-
 graphics/src/geometry.rs                  |  4 +-
 graphics/src/geometry/frame.rs            |  3 +-
 graphics/src/mesh.rs                      |  4 +-
 renderer/Cargo.toml                       | 10 +++--
 renderer/src/fallback.rs                  | 29 +--------------
 renderer/src/lib.rs                       | 53 ++++++++++++++++----------
 renderer/src/settings.rs                  |  5 +++
 runtime/src/user_interface.rs             | 12 +++---
 tiny_skia/src/backend.rs                  |  3 ++
 wgpu/src/backend.rs                       |  2 +
 wgpu/src/primitive/pipeline.rs            |  4 +-
 widget/src/qr_code.rs                     |  4 +-
 20 files changed, 158 insertions(+), 92 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index b82c0f67..56b5a911 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,9 +18,11 @@ all-features = true
 maintenance = { status = "actively-developed" }
 
 [features]
-default = ["wgpu", "fira-sans", "auto-detect-theme"]
+default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
 # Enable the `wgpu` GPU-accelerated renderer backend
 wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
+# Enable the `tiny-skia` software renderer backend
+tiny-skia = ["iced_renderer/tiny-skia"]
 # Enables the `Image` widget
 image = ["iced_widget/image", "dep:image"]
 # Enables the `Svg` widget
diff --git a/core/src/element.rs b/core/src/element.rs
index 989eaa3b..7d918a2e 100644
--- a/core/src/element.rs
+++ b/core/src/element.rs
@@ -95,7 +95,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
     ///
     /// ```no_run
     /// # mod iced {
-    /// #     pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
+    /// #     pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
     /// #
     /// #     pub mod widget {
     /// #         pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> {
@@ -109,7 +109,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
     /// #     pub enum Message {}
     /// #     pub struct Counter;
     /// #
-    /// #     pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
+    /// #     pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
     /// #
     /// #     impl Counter {
     /// #         pub fn view(&self) -> Element<Message> {
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 406b33f3..dfedcd45 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -1,10 +1,6 @@
 //! Write your own renderer.
-#[cfg(debug_assertions)]
 mod null;
 
-#[cfg(debug_assertions)]
-pub use null::Null;
-
 use crate::{
     Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector,
 };
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 0d7b7c14..af7dc15f 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,4 +1,5 @@
 use crate::alignment;
+use crate::image;
 use crate::renderer::{self, Renderer};
 use crate::text::{self, Text};
 use crate::{
@@ -7,20 +8,7 @@ use crate::{
 
 use std::borrow::Cow;
 
-/// A renderer that does nothing.
-///
-/// It can be useful if you are writing tests!
-#[derive(Debug, Clone, Copy, Default)]
-pub struct Null;
-
-impl Null {
-    /// Creates a new [`Null`] renderer.
-    pub fn new() -> Null {
-        Null
-    }
-}
-
-impl Renderer for Null {
+impl Renderer for () {
     fn start_layer(&mut self) {}
 
     fn end_layer(&mut self, _bounds: Rectangle) {}
@@ -39,7 +27,7 @@ impl Renderer for Null {
     }
 }
 
-impl text::Renderer for Null {
+impl text::Renderer for () {
     type Font = Font;
     type Paragraph = ();
     type Editor = ();
@@ -173,3 +161,19 @@ impl text::Editor for () {
     ) {
     }
 }
+
+impl image::Renderer for () {
+    type Handle = ();
+
+    fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
+        Size::default()
+    }
+
+    fn draw_image(
+        &mut self,
+        _handle: Self::Handle,
+        _filter_method: image::FilterMethod,
+        _bounds: Rectangle,
+    ) {
+    }
+}
diff --git a/core/src/size.rs b/core/src/size.rs
index 267fc90e..55db759d 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -1,7 +1,7 @@
 use crate::Vector;
 
 /// An amount of space in 2 dimensions.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
 pub struct Size<T = f32> {
     /// The width.
     pub width: T,
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
index cdc6b7ac..de728af2 100644
--- a/examples/loading_spinners/src/circular.rs
+++ b/examples/loading_spinners/src/circular.rs
@@ -356,7 +356,9 @@ where
         renderer.with_translation(
             Vector::new(bounds.x, bounds.y),
             |renderer| {
-                renderer.draw_geometry([geometry]);
+                use iced::advanced::graphics::geometry::Renderer as _;
+
+                renderer.draw_geometry(geometry);
             },
         );
     }
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index e394c956..e982b54a 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -10,6 +10,9 @@ use std::borrow::Cow;
 ///
 /// [`Renderer`]: crate::Renderer
 pub trait Backend {
+    /// The compositor of this [`Backend`].
+    type Compositor;
+
     /// The custom kind of primitives this [`Backend`] supports.
     type Primitive: TryFrom<Mesh, Error = &'static str>;
 }
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs
index 91951a8e..32cea46a 100644
--- a/graphics/src/compositor.rs
+++ b/graphics/src/compositor.rs
@@ -15,7 +15,7 @@ pub trait Compositor: Sized {
     type Settings: Default;
 
     /// The iced renderer of the backend.
-    type Renderer: iced_core::Renderer;
+    type Renderer;
 
     /// The surface of the backend.
     type Surface;
@@ -122,3 +122,63 @@ pub struct Information {
     /// Contains the graphics backend.
     pub backend: String,
 }
+
+impl Compositor for () {
+    type Settings = ();
+    type Renderer = ();
+    type Surface = ();
+
+    async fn new<W: Window + Clone>(
+        _settings: Self::Settings,
+        _compatible_window: W,
+    ) -> Result<Self, Error> {
+        Ok(())
+    }
+
+    fn create_renderer(&self) -> Self::Renderer {}
+
+    fn create_surface<W: Window + Clone>(
+        &mut self,
+        _window: W,
+        _width: u32,
+        _height: u32,
+    ) -> Self::Surface {
+    }
+
+    fn configure_surface(
+        &mut self,
+        _surface: &mut Self::Surface,
+        _width: u32,
+        _height: u32,
+    ) {
+    }
+
+    fn fetch_information(&self) -> Information {
+        Information {
+            adapter: String::from("Null Renderer"),
+            backend: String::from("Null"),
+        }
+    }
+
+    fn present<T: AsRef<str>>(
+        &mut self,
+        _renderer: &mut Self::Renderer,
+        _surface: &mut Self::Surface,
+        _viewport: &Viewport,
+        _background_color: Color,
+        _overlay: &[T],
+    ) -> Result<(), SurfaceError> {
+        Ok(())
+    }
+
+    fn screenshot<T: AsRef<str>>(
+        &mut self,
+        _renderer: &mut Self::Renderer,
+        _surface: &mut Self::Surface,
+        _viewport: &Viewport,
+        _background_color: Color,
+        _overlay: &[T],
+    ) -> Vec<u8> {
+        vec![]
+    }
+}
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index cc2359b6..194f37b2 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -18,11 +18,11 @@ pub use text::Text;
 
 pub use crate::gradient::{self, Gradient};
 
-use crate::core::Size;
+use crate::core::{self, Size};
 use crate::Cached;
 
 /// A renderer capable of drawing some [`Self::Geometry`].
-pub trait Renderer: crate::core::Renderer {
+pub trait Renderer: core::Renderer {
     /// The kind of geometry this renderer can draw.
     type Geometry: Cached;
 
diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
index b54fca5d..635012d0 100644
--- a/graphics/src/geometry/frame.rs
+++ b/graphics/src/geometry/frame.rs
@@ -1,7 +1,6 @@
 //! Draw and generate geometry.
 use crate::core::{Point, Radians, Rectangle, Size, Vector};
 use crate::geometry::{self, Fill, Path, Stroke, Text};
-use crate::Cached;
 
 /// The region of a surface that can be used to draw geometry.
 #[allow(missing_debug_implementations)]
@@ -173,7 +172,7 @@ where
 /// of each method.
 #[allow(missing_docs)]
 pub trait Backend: Sized {
-    type Geometry: Cached;
+    type Geometry;
 
     fn width(&self) -> f32;
     fn height(&self) -> f32;
diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs
index d671f494..20692b07 100644
--- a/graphics/src/mesh.rs
+++ b/graphics/src/mesh.rs
@@ -1,6 +1,6 @@
 //! Draw triangles!
 use crate::color;
-use crate::core::{self, Rectangle, Size};
+use crate::core::{Rectangle, Size};
 use crate::gradient;
 use crate::Damage;
 
@@ -76,7 +76,7 @@ pub struct GradientVertex2D {
 }
 
 /// A renderer capable of drawing a [`Mesh`].
-pub trait Renderer: core::Renderer {
+pub trait Renderer {
     /// Draws the given [`Mesh`].
     fn draw_mesh(&mut self, mesh: Mesh);
 }
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
index 18e9e8f8..39c19fa3 100644
--- a/renderer/Cargo.toml
+++ b/renderer/Cargo.toml
@@ -12,18 +12,20 @@ keywords.workspace = true
 
 [features]
 wgpu = ["iced_wgpu"]
-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"]
+tiny-skia = ["iced_tiny_skia"]
+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"]
 tracing = ["iced_wgpu?/tracing"]
 web-colors = ["iced_wgpu?/web-colors"]
 webgl = ["iced_wgpu?/webgl"]
 fira-sans = ["iced_graphics/fira-sans"]
-custom = []
 
 [dependencies]
 iced_graphics.workspace = true
+
 iced_tiny_skia.workspace = true
+iced_tiny_skia.optional = true
 
 iced_wgpu.workspace = true
 iced_wgpu.optional = true
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 249da9e9..4431606a 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -8,11 +8,7 @@ use crate::graphics;
 use crate::graphics::compositor;
 use crate::graphics::mesh;
 
-pub enum Renderer<L, R>
-where
-    L: core::Renderer,
-    R: core::Renderer,
-{
+pub enum Renderer<L, R> {
     Left(L),
     Right(R),
 }
@@ -26,29 +22,6 @@ macro_rules! delegate {
     };
 }
 
-impl<L, R> Renderer<L, R>
-where
-    L: core::Renderer,
-    R: core::Renderer,
-{
-    #[cfg(feature = "geometry")]
-    pub fn draw_geometry<Geometry>(
-        &mut self,
-        layers: impl IntoIterator<Item = Geometry>,
-    ) where
-        L: graphics::geometry::Renderer,
-        R: graphics::geometry::Renderer,
-
-        Geometry: Into<geometry::Geometry<L::Geometry, R::Geometry>>,
-    {
-        use graphics::geometry::Renderer;
-
-        for layer in layers {
-            <Self as Renderer>::draw_geometry(self, layer.into());
-        }
-    }
-}
-
 impl<L, R> core::Renderer for Renderer<L, R>
 where
     L: core::Renderer,
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index f8aa1157..199b431e 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -19,27 +19,40 @@ pub use settings::Settings;
 /// The default graphics renderer for [`iced`].
 ///
 /// [`iced`]: https://github.com/iced-rs/iced
-#[cfg(not(feature = "wgpu"))]
-pub type Renderer = iced_tiny_skia::Renderer;
-
-/// The default graphics renderer for [`iced`].
-///
-/// [`iced`]: https://github.com/iced-rs/iced
-#[cfg(feature = "wgpu")]
-pub type Renderer =
-    fallback::Renderer<iced_wgpu::Renderer, iced_tiny_skia::Renderer>;
+pub type Renderer = renderer::Renderer;
 
 /// The default graphics compositor for [`iced`].
 ///
 /// [`iced`]: https://github.com/iced-rs/iced
-#[cfg(not(feature = "wgpu"))]
-pub type Compositor = iced_tiny_skia::window::Compositor;
-
-/// The default graphics renderer for [`iced`].
-///
-/// [`iced`]: https://github.com/iced-rs/iced
-#[cfg(feature = "wgpu")]
-pub type Compositor = fallback::Compositor<
-    iced_wgpu::window::Compositor,
-    iced_tiny_skia::window::Compositor,
->;
+pub type Compositor = renderer::Compositor;
+
+#[cfg(all(feature = "wgpu", feature = "tiny-skia"))]
+mod renderer {
+    pub type Renderer = crate::fallback::Renderer<
+        iced_wgpu::Renderer,
+        iced_tiny_skia::Renderer,
+    >;
+
+    pub type Compositor = crate::fallback::Compositor<
+        iced_wgpu::window::Compositor,
+        iced_tiny_skia::window::Compositor,
+    >;
+}
+
+#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))]
+mod renderer {
+    pub type Renderer = iced_wgpu::Renderer;
+    pub type Compositor = iced_wgpu::window::Compositor;
+}
+
+#[cfg(all(not(feature = "wgpu"), feature = "tiny-skia"))]
+mod renderer {
+    pub type Renderer = iced_tiny_skia::Renderer;
+    pub type Compositor = iced_tiny_skia::window::Compositor;
+}
+
+#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))]
+mod renderer {
+    pub type Renderer = ();
+    pub type Compositor = ();
+}
diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs
index 940daa15..27788db9 100644
--- a/renderer/src/settings.rs
+++ b/renderer/src/settings.rs
@@ -28,6 +28,7 @@ impl Default for Settings {
     }
 }
 
+#[cfg(feature = "tiny-skia")]
 impl From<Settings> for iced_tiny_skia::Settings {
     fn from(settings: Settings) -> Self {
         Self {
@@ -48,3 +49,7 @@ impl From<Settings> for iced_wgpu::Settings {
         }
     }
 }
+
+impl From<Settings> for () {
+    fn from(_settings: Settings) -> Self {}
+}
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index 748fb651..006225ed 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -45,7 +45,7 @@ where
     ///
     /// ```no_run
     /// # mod iced_wgpu {
-    /// #     pub use iced_runtime::core::renderer::Null as Renderer;
+    /// #     pub type Renderer = ();
     /// # }
     /// #
     /// # pub struct Counter;
@@ -62,7 +62,7 @@ where
     /// // Initialization
     /// let mut counter = Counter::new();
     /// let mut cache = user_interface::Cache::new();
-    /// let mut renderer = Renderer::new();
+    /// let mut renderer = Renderer::default();
     /// let mut window_size = Size::new(1024.0, 768.0);
     ///
     /// // Application loop
@@ -121,7 +121,7 @@ where
     ///
     /// ```no_run
     /// # mod iced_wgpu {
-    /// #     pub use iced_runtime::core::renderer::Null as Renderer;
+    /// #     pub type Renderer = ();
     /// # }
     /// #
     /// # pub struct Counter;
@@ -139,7 +139,7 @@ where
     ///
     /// let mut counter = Counter::new();
     /// let mut cache = user_interface::Cache::new();
-    /// let mut renderer = Renderer::new();
+    /// let mut renderer = Renderer::default();
     /// let mut window_size = Size::new(1024.0, 768.0);
     /// let mut cursor = mouse::Cursor::default();
     /// let mut clipboard = clipboard::Null;
@@ -374,7 +374,7 @@ where
     ///
     /// ```no_run
     /// # mod iced_wgpu {
-    /// #     pub use iced_runtime::core::renderer::Null as Renderer;
+    /// #     pub type Renderer = ();
     /// #     pub type Theme = ();
     /// # }
     /// #
@@ -394,7 +394,7 @@ where
     ///
     /// let mut counter = Counter::new();
     /// let mut cache = user_interface::Cache::new();
-    /// let mut renderer = Renderer::new();
+    /// let mut renderer = Renderer::default();
     /// let mut window_size = Size::new(1024.0, 768.0);
     /// let mut cursor = mouse::Cursor::default();
     /// let mut clipboard = clipboard::Null;
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index f6bb1c86..b0dd4759 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -5,6 +5,7 @@ use crate::graphics::backend;
 use crate::graphics::text;
 use crate::graphics::{Damage, Viewport};
 use crate::primitive::{self, Primitive};
+use crate::window;
 
 use std::borrow::Cow;
 
@@ -990,6 +991,8 @@ fn rounded_box_sdf(
 }
 
 impl iced_graphics::Backend for Backend {
+    type Compositor = window::Compositor;
+
     type Primitive = primitive::Custom;
 }
 
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 924aacf1..3675d50b 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")]
@@ -372,6 +373,7 @@ impl Backend {
 }
 
 impl crate::graphics::Backend for Backend {
+    type Compositor = window::Compositor;
     type Primitive = primitive::Custom;
 }
 
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/widget/src/qr_code.rs b/widget/src/qr_code.rs
index 84898dc0..601e5808 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -141,7 +141,9 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
         renderer.with_translation(
             bounds.position() - Point::ORIGIN,
             |renderer| {
-                renderer.draw_geometry(vec![geometry]);
+                use crate::graphics::geometry::Renderer as _;
+
+                renderer.draw_geometry(geometry);
             },
         );
     }
-- 
cgit 


From 4f2f40c68b4647f281d34034beb159a41422aa06 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 05:41:15 +0100
Subject: Fix standalone compilation of `iced_widget` crate

---
 core/src/renderer.rs           |  1 +
 core/src/renderer/null.rs      | 15 ++++++++++++++
 graphics/src/cached.rs         |  9 +++++++++
 graphics/src/geometry.rs       | 10 +++++++++
 graphics/src/geometry/frame.rs | 46 ++++++++++++++++++++++++++++++++++++++++++
 tiny_skia/src/backend.rs       |  1 -
 6 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index dfedcd45..6712314e 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -1,4 +1,5 @@
 //! Write your own renderer.
+#[cfg(debug_assertions)]
 mod null;
 
 use crate::{
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index af7dc15f..c26ce1a5 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,6 +1,7 @@
 use crate::alignment;
 use crate::image;
 use crate::renderer::{self, Renderer};
+use crate::svg;
 use crate::text::{self, Text};
 use crate::{
     Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
@@ -177,3 +178,17 @@ impl image::Renderer for () {
     ) {
     }
 }
+
+impl svg::Renderer for () {
+    fn measure_svg(&self, _handle: &svg::Handle) -> Size<u32> {
+        Size::default()
+    }
+
+    fn draw_svg(
+        &mut self,
+        _handle: svg::Handle,
+        _color: Option<Color>,
+        _bounds: Rectangle,
+    ) {
+    }
+}
diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs
index f7968c9f..b52f9d9d 100644
--- a/graphics/src/cached.rs
+++ b/graphics/src/cached.rs
@@ -31,3 +31,12 @@ impl<T> Cached for Primitive<T> {
         Arc::new(self)
     }
 }
+
+#[cfg(debug_assertions)]
+impl Cached for () {
+    type Cache = ();
+
+    fn load(_cache: &Self::Cache) -> Self {}
+
+    fn cache(self) -> Self::Cache {}
+}
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index 194f37b2..d251efb8 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -44,3 +44,13 @@ pub trait Backend {
     /// Creates a new [`Self::Frame`].
     fn new_frame(&self, size: Size) -> Self::Frame;
 }
+
+#[cfg(debug_assertions)]
+impl Renderer for () {
+    type Geometry = ();
+    type Frame = ();
+
+    fn new_frame(&self, _size: Size) -> Self::Frame {}
+
+    fn draw_geometry(&mut self, _geometry: Self::Geometry) {}
+}
diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
index 635012d0..ad35e8ec 100644
--- a/graphics/src/geometry/frame.rs
+++ b/graphics/src/geometry/frame.rs
@@ -203,3 +203,49 @@ pub trait Backend: Sized {
 
     fn into_geometry(self) -> Self::Geometry;
 }
+
+#[cfg(debug_assertions)]
+impl Backend for () {
+    type Geometry = ();
+
+    fn width(&self) -> f32 {
+        0.0
+    }
+
+    fn height(&self) -> f32 {
+        0.0
+    }
+
+    fn size(&self) -> Size {
+        Size::ZERO
+    }
+
+    fn center(&self) -> Point {
+        Point::ORIGIN
+    }
+
+    fn push_transform(&mut self) {}
+    fn pop_transform(&mut self) {}
+
+    fn translate(&mut self, _translation: Vector) {}
+    fn rotate(&mut self, _angle: impl Into<Radians>) {}
+    fn scale(&mut self, _scale: impl Into<f32>) {}
+    fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
+
+    fn draft(&mut self, _size: Size) -> Self {}
+    fn paste(&mut self, _frame: Self, _at: Point) {}
+
+    fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
+
+    fn fill(&mut self, _path: &Path, _fill: impl Into<Fill>) {}
+    fn fill_text(&mut self, _text: impl Into<Text>) {}
+    fn fill_rectangle(
+        &mut self,
+        _top_left: Point,
+        _size: Size,
+        _fill: impl Into<Fill>,
+    ) {
+    }
+
+    fn into_geometry(self) -> Self::Geometry {}
+}
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index b0dd4759..6d4e6cda 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -992,7 +992,6 @@ fn rounded_box_sdf(
 
 impl iced_graphics::Backend for Backend {
     type Compositor = window::Compositor;
-
     type Primitive = primitive::Custom;
 }
 
-- 
cgit 


From 5137d655e6bbd29581fc1469d0385515113f2999 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 07:09:51 +0100
Subject: Allow custom renderers in `Program` and `Application`

---
 graphics/src/backend.rs            |  6 ++--
 graphics/src/compositor.rs         | 24 ++++++++++-----
 graphics/src/lib.rs                |  2 ++
 graphics/src/renderer.rs           |  8 +++++
 graphics/src/settings.rs           | 29 ++++++++++++++++++
 renderer/src/fallback.rs           | 13 ++++++---
 renderer/src/lib.rs                |  4 ---
 renderer/src/settings.rs           | 55 ----------------------------------
 runtime/src/program.rs             |  4 +--
 src/application.rs                 | 18 ++++++++----
 src/lib.rs                         |  5 ++--
 src/multi_window.rs                |  4 +--
 src/program.rs                     | 60 ++++++++++++++++++++++++++------------
 tiny_skia/src/settings.rs          | 10 +++++++
 tiny_skia/src/window/compositor.rs |  7 ++---
 wgpu/src/settings.rs               | 13 ++++++++-
 wgpu/src/window/compositor.rs      |  8 ++---
 winit/src/application.rs           |  5 ++--
 winit/src/multi_window.rs          |  9 +++---
 19 files changed, 162 insertions(+), 122 deletions(-)
 create mode 100644 graphics/src/settings.rs
 delete mode 100644 renderer/src/settings.rs

diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index e982b54a..aa7bf4e8 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -2,16 +2,16 @@
 use crate::core::image;
 use crate::core::svg;
 use crate::core::Size;
-use crate::Mesh;
+use crate::{Compositor, Mesh, Renderer};
 
 use std::borrow::Cow;
 
 /// The graphics backend of a [`Renderer`].
 ///
 /// [`Renderer`]: crate::Renderer
-pub trait Backend {
+pub trait Backend: Sized {
     /// The compositor of this [`Backend`].
-    type Compositor;
+    type Compositor: Compositor<Renderer = Renderer<Self>>;
 
     /// The custom kind of primitives this [`Backend`] supports.
     type Primitive: TryFrom<Mesh, Error = &'static str>;
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs
index 32cea46a..4d548f30 100644
--- a/graphics/src/compositor.rs
+++ b/graphics/src/compositor.rs
@@ -1,9 +1,9 @@
 //! A compositor is responsible for initializing a renderer and managing window
 //! surfaces.
-use crate::{Error, Viewport};
-
+use crate::core;
 use crate::core::Color;
 use crate::futures::{MaybeSend, MaybeSync};
+use crate::{Error, Settings, Viewport};
 
 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
 use std::future::Future;
@@ -11,9 +11,6 @@ use thiserror::Error;
 
 /// A graphics compositor that can draw to windows.
 pub trait Compositor: Sized {
-    /// The settings of the backend.
-    type Settings: Default;
-
     /// The iced renderer of the backend.
     type Renderer;
 
@@ -22,7 +19,7 @@ pub trait Compositor: Sized {
 
     /// Creates a new [`Compositor`].
     fn new<W: Window + Clone>(
-        settings: Self::Settings,
+        settings: Settings,
         compatible_window: W,
     ) -> impl Future<Output = Result<Self, Error>>;
 
@@ -93,6 +90,12 @@ impl<T> Window for T where
 {
 }
 
+/// A renderer that supports composition.
+pub trait Renderer: core::Renderer {
+    /// The compositor of the renderer.
+    type Compositor: Compositor<Renderer = Self>;
+}
+
 /// Result of an unsuccessful call to [`Compositor::present`].
 #[derive(Clone, PartialEq, Eq, Debug, Error)]
 pub enum SurfaceError {
@@ -123,13 +126,13 @@ pub struct Information {
     pub backend: String,
 }
 
+#[cfg(debug_assertions)]
 impl Compositor for () {
-    type Settings = ();
     type Renderer = ();
     type Surface = ();
 
     async fn new<W: Window + Clone>(
-        _settings: Self::Settings,
+        _settings: Settings,
         _compatible_window: W,
     ) -> Result<Self, Error> {
         Ok(())
@@ -182,3 +185,8 @@ impl Compositor for () {
         vec![]
     }
 }
+
+#[cfg(debug_assertions)]
+impl Renderer for () {
+    type Compositor = ();
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index a682b89b..2e476f8c 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -20,6 +20,7 @@ mod antialiasing;
 mod cached;
 mod error;
 mod primitive;
+mod settings;
 mod viewport;
 
 pub mod backend;
@@ -47,6 +48,7 @@ pub use gradient::Gradient;
 pub use mesh::Mesh;
 pub use primitive::Primitive;
 pub use renderer::Renderer;
+pub use settings::Settings;
 pub use viewport::Viewport;
 
 pub use iced_core as core;
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index eb720495..5de7f97f 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -1,5 +1,6 @@
 //! Create a renderer from a [`Backend`].
 use crate::backend::{self, Backend};
+use crate::compositor;
 use crate::core;
 use crate::core::image;
 use crate::core::renderer;
@@ -259,3 +260,10 @@ where
         self.draw_primitive(geometry);
     }
 }
+
+impl<B> compositor::Renderer for Renderer<B>
+where
+    B: Backend,
+{
+    type Compositor = B::Compositor;
+}
diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs
new file mode 100644
index 00000000..68673536
--- /dev/null
+++ b/graphics/src/settings.rs
@@ -0,0 +1,29 @@
+use crate::core::{Font, Pixels};
+use crate::Antialiasing;
+
+/// The settings of a Backend.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Settings {
+    /// The default [`Font`] to use.
+    pub default_font: Font,
+
+    /// The default size of text.
+    ///
+    /// By default, it will be set to `16.0`.
+    pub default_text_size: Pixels,
+
+    /// The antialiasing strategy that will be used for triangle primitives.
+    ///
+    /// By default, it is `None`.
+    pub antialiasing: Option<Antialiasing>,
+}
+
+impl Default for Settings {
+    fn default() -> Settings {
+        Settings {
+            default_font: Font::default(),
+            default_text_size: Pixels(16.0),
+            antialiasing: None,
+        }
+    }
+}
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 4431606a..28e73dd8 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -200,15 +200,12 @@ impl<L, R> graphics::Compositor for Compositor<L, R>
 where
     L: graphics::Compositor,
     R: graphics::Compositor,
-    L::Settings: From<crate::Settings>,
-    R::Settings: From<crate::Settings>,
 {
-    type Settings = crate::Settings;
     type Renderer = Renderer<L::Renderer, R::Renderer>;
     type Surface = Surface<L::Surface, R::Surface>;
 
     async fn new<W: compositor::Window + Clone>(
-        settings: Self::Settings,
+        settings: graphics::Settings,
         compatible_window: W,
     ) -> Result<Self, graphics::Error> {
         if let Ok(left) = L::new(settings.into(), compatible_window.clone())
@@ -528,3 +525,11 @@ mod geometry {
         }
     }
 }
+
+impl<L, R> compositor::Renderer for Renderer<L, R>
+where
+    L: compositor::Renderer,
+    R: compositor::Renderer,
+{
+    type Compositor = Compositor<L::Compositor, R::Compositor>;
+}
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index 199b431e..7c48995d 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -6,16 +6,12 @@ pub use iced_wgpu as wgpu;
 
 pub mod fallback;
 
-mod settings;
-
 pub use iced_graphics as graphics;
 pub use iced_graphics::core;
 
 #[cfg(feature = "geometry")]
 pub use iced_graphics::geometry;
 
-pub use settings::Settings;
-
 /// The default graphics renderer for [`iced`].
 ///
 /// [`iced`]: https://github.com/iced-rs/iced
diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs
deleted file mode 100644
index 27788db9..00000000
--- a/renderer/src/settings.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use crate::core::{Font, Pixels};
-use crate::graphics::Antialiasing;
-
-/// The settings of a Backend.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Settings {
-    /// The default [`Font`] to use.
-    pub default_font: Font,
-
-    /// The default size of text.
-    ///
-    /// By default, it will be set to `16.0`.
-    pub default_text_size: Pixels,
-
-    /// The antialiasing strategy that will be used for triangle primitives.
-    ///
-    /// By default, it is `None`.
-    pub antialiasing: Option<Antialiasing>,
-}
-
-impl Default for Settings {
-    fn default() -> Settings {
-        Settings {
-            default_font: Font::default(),
-            default_text_size: Pixels(16.0),
-            antialiasing: None,
-        }
-    }
-}
-
-#[cfg(feature = "tiny-skia")]
-impl From<Settings> for iced_tiny_skia::Settings {
-    fn from(settings: Settings) -> Self {
-        Self {
-            default_font: settings.default_font,
-            default_text_size: settings.default_text_size,
-        }
-    }
-}
-
-#[cfg(feature = "wgpu")]
-impl From<Settings> for iced_wgpu::Settings {
-    fn from(settings: Settings) -> Self {
-        Self {
-            default_font: settings.default_font,
-            default_text_size: settings.default_text_size,
-            antialiasing: settings.antialiasing,
-            ..iced_wgpu::Settings::default()
-        }
-    }
-}
-
-impl From<Settings> for () {
-    fn from(_settings: Settings) -> Self {}
-}
diff --git a/runtime/src/program.rs b/runtime/src/program.rs
index 6c1b8f07..0ea94d3b 100644
--- a/runtime/src/program.rs
+++ b/runtime/src/program.rs
@@ -2,7 +2,7 @@
 use crate::Command;
 
 use iced_core::text;
-use iced_core::{Element, Renderer};
+use iced_core::Element;
 
 mod state;
 
@@ -11,7 +11,7 @@ pub use state::State;
 /// The core of a user interface application following The Elm Architecture.
 pub trait Program: Sized {
     /// The graphics backend to use to draw the [`Program`].
-    type Renderer: Renderer + text::Renderer;
+    type Renderer: text::Renderer;
 
     /// The theme used to draw the [`Program`].
     type Theme;
diff --git a/src/application.rs b/src/application.rs
index 8317abcb..62fe77da 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -1,4 +1,6 @@
 //! Build interactive cross-platform applications.
+use crate::core::text;
+use crate::graphics::compositor;
 use crate::shell::application;
 use crate::{Command, Element, Executor, Settings, Subscription};
 
@@ -60,7 +62,7 @@ pub use application::{Appearance, DefaultStyle};
 /// ```no_run
 /// use iced::advanced::Application;
 /// use iced::executor;
-/// use iced::{Command, Element, Settings, Theme};
+/// use iced::{Command, Element, Settings, Theme, Renderer};
 ///
 /// pub fn main() -> iced::Result {
 ///     Hello::run(Settings::default())
@@ -73,6 +75,7 @@ pub use application::{Appearance, DefaultStyle};
 ///     type Flags = ();
 ///     type Message = ();
 ///     type Theme = Theme;
+///     type Renderer = Renderer;
 ///
 ///     fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
 ///         (Hello, Command::none())
@@ -109,6 +112,9 @@ where
     /// The theme of your [`Application`].
     type Theme: Default;
 
+    /// The renderer of your [`Application`].
+    type Renderer: text::Renderer + compositor::Renderer;
+
     /// The data needed to initialize your [`Application`].
     type Flags;
 
@@ -142,7 +148,7 @@ where
     /// Returns the widgets to display in the [`Application`].
     ///
     /// These widgets can produce __messages__ based on user interaction.
-    fn view(&self) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>;
+    fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>;
 
     /// Returns the current [`Theme`] of the [`Application`].
     ///
@@ -195,7 +201,7 @@ where
         Self: 'static,
     {
         #[allow(clippy::needless_update)]
-        let renderer_settings = crate::renderer::Settings {
+        let renderer_settings = crate::graphics::Settings {
             default_font: settings.default_font,
             default_text_size: settings.default_text_size,
             antialiasing: if settings.antialiasing {
@@ -203,13 +209,13 @@ where
             } else {
                 None
             },
-            ..crate::renderer::Settings::default()
+            ..crate::graphics::Settings::default()
         };
 
         let run = crate::shell::application::run::<
             Instance<Self>,
             Self::Executor,
-            crate::renderer::Compositor,
+            <Self::Renderer as compositor::Renderer>::Compositor,
         >(settings.into(), renderer_settings);
 
         #[cfg(target_arch = "wasm32")]
@@ -241,7 +247,7 @@ where
 {
     type Message = A::Message;
     type Theme = A::Theme;
-    type Renderer = crate::Renderer;
+    type Renderer = A::Renderer;
 
     fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
         self.0.update(message)
diff --git a/src/lib.rs b/src/lib.rs
index 0e9566e2..171109e7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -372,15 +372,16 @@ pub type Result = std::result::Result<(), Error>;
 ///     ]
 /// }
 /// ```
-pub fn run<State, Message, Theme>(
+pub fn run<State, Message, Theme, Renderer>(
     title: impl program::Title<State> + 'static,
     update: impl program::Update<State, Message> + 'static,
-    view: impl for<'a> program::View<'a, State, Message, Theme> + 'static,
+    view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static,
 ) -> Result
 where
     State: Default + 'static,
     Message: std::fmt::Debug + Send + 'static,
     Theme: Default + program::DefaultStyle + 'static,
+    Renderer: graphics::compositor::Renderer + core::text::Renderer + 'static,
 {
     program(title, update, view).run()
 }
diff --git a/src/multi_window.rs b/src/multi_window.rs
index fca0be46..b81297dc 100644
--- a/src/multi_window.rs
+++ b/src/multi_window.rs
@@ -174,7 +174,7 @@ where
         Self: 'static,
     {
         #[allow(clippy::needless_update)]
-        let renderer_settings = crate::renderer::Settings {
+        let renderer_settings = crate::graphics::Settings {
             default_font: settings.default_font,
             default_text_size: settings.default_text_size,
             antialiasing: if settings.antialiasing {
@@ -182,7 +182,7 @@ where
             } else {
                 None
             },
-            ..crate::renderer::Settings::default()
+            ..crate::graphics::Settings::default()
         };
 
         Ok(crate::shell::multi_window::run::<
diff --git a/src/program.rs b/src/program.rs
index 7a366585..f5a2bc53 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -32,6 +32,7 @@
 //! ```
 use crate::application::Application;
 use crate::executor::{self, Executor};
+use crate::graphics::compositor;
 use crate::window;
 use crate::{Command, Element, Font, Result, Settings, Size, Subscription};
 
@@ -67,37 +68,41 @@ use std::borrow::Cow;
 ///     ]
 /// }
 /// ```
-pub fn program<State, Message, Theme>(
+pub fn program<State, Message, Theme, Renderer>(
     title: impl Title<State>,
     update: impl Update<State, Message>,
-    view: impl for<'a> self::View<'a, State, Message, Theme>,
+    view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
 ) -> Program<impl Definition<State = State, Message = Message, Theme = Theme>>
 where
     State: 'static,
     Message: Send + std::fmt::Debug,
     Theme: Default + DefaultStyle,
+    Renderer: compositor::Renderer + crate::core::text::Renderer,
 {
     use std::marker::PhantomData;
 
-    struct Application<State, Message, Theme, Update, View> {
+    struct Application<State, Message, Theme, Renderer, Update, View> {
         update: Update,
         view: View,
         _state: PhantomData<State>,
         _message: PhantomData<Message>,
         _theme: PhantomData<Theme>,
+        _renderer: PhantomData<Renderer>,
     }
 
-    impl<State, Message, Theme, Update, View> Definition
-        for Application<State, Message, Theme, Update, View>
+    impl<State, Message, Theme, Renderer, Update, View> Definition
+        for Application<State, Message, Theme, Renderer, Update, View>
     where
         Message: Send + std::fmt::Debug,
         Theme: Default + DefaultStyle,
+        Renderer: compositor::Renderer + crate::core::text::Renderer,
         Update: self::Update<State, Message>,
-        View: for<'a> self::View<'a, State, Message, Theme>,
+        View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
     {
         type State = State;
         type Message = Message;
         type Theme = Theme;
+        type Renderer = Renderer;
         type Executor = executor::Default;
 
         fn load(&self) -> Command<Self::Message> {
@@ -115,7 +120,7 @@ where
         fn view<'a>(
             &self,
             state: &'a Self::State,
-        ) -> Element<'a, Self::Message, Self::Theme> {
+        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
             self.view.view(state).into()
         }
     }
@@ -127,6 +132,7 @@ where
             _state: PhantomData,
             _message: PhantomData,
             _theme: PhantomData,
+            _renderer: PhantomData,
         },
         settings: Settings::default(),
     }
@@ -184,6 +190,7 @@ impl<P: Definition> Program<P> {
         impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> {
             type Message = P::Message;
             type Theme = P::Theme;
+            type Renderer = P::Renderer;
             type Flags = (P, I);
             type Executor = P::Executor;
 
@@ -216,7 +223,7 @@ impl<P: Definition> Program<P> {
 
             fn view(
                 &self,
-            ) -> crate::Element<'_, Self::Message, Self::Theme, crate::Renderer>
+            ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer>
             {
                 self.program.view(&self.state)
             }
@@ -417,6 +424,9 @@ pub trait Definition: Sized {
     /// The theme of the program.
     type Theme: Default + DefaultStyle;
 
+    /// The renderer of the program.
+    type Renderer: compositor::Renderer + crate::core::text::Renderer;
+
     /// The executor of the program.
     type Executor: Executor;
 
@@ -431,7 +441,7 @@ pub trait Definition: Sized {
     fn view<'a>(
         &self,
         state: &'a Self::State,
-    ) -> Element<'a, Self::Message, Self::Theme>;
+    ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;
 
     fn title(&self, _state: &Self::State) -> String {
         String::from("A cool iced application!")
@@ -470,6 +480,7 @@ fn with_title<P: Definition>(
         type State = P::State;
         type Message = P::Message;
         type Theme = P::Theme;
+        type Renderer = P::Renderer;
         type Executor = P::Executor;
 
         fn load(&self) -> Command<Self::Message> {
@@ -491,7 +502,7 @@ fn with_title<P: Definition>(
         fn view<'a>(
             &self,
             state: &'a Self::State,
-        ) -> Element<'a, Self::Message, Self::Theme> {
+        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
             self.program.view(state)
         }
 
@@ -534,6 +545,7 @@ fn with_load<P: Definition>(
         type State = P::State;
         type Message = P::Message;
         type Theme = P::Theme;
+        type Renderer = P::Renderer;
         type Executor = executor::Default;
 
         fn load(&self) -> Command<Self::Message> {
@@ -551,7 +563,7 @@ fn with_load<P: Definition>(
         fn view<'a>(
             &self,
             state: &'a Self::State,
-        ) -> Element<'a, Self::Message, Self::Theme> {
+        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
             self.program.view(state)
         }
 
@@ -598,6 +610,7 @@ fn with_subscription<P: Definition>(
         type State = P::State;
         type Message = P::Message;
         type Theme = P::Theme;
+        type Renderer = P::Renderer;
         type Executor = executor::Default;
 
         fn subscription(
@@ -622,7 +635,7 @@ fn with_subscription<P: Definition>(
         fn view<'a>(
             &self,
             state: &'a Self::State,
-        ) -> Element<'a, Self::Message, Self::Theme> {
+        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
             self.program.view(state)
         }
 
@@ -665,6 +678,7 @@ fn with_theme<P: Definition>(
         type State = P::State;
         type Message = P::Message;
         type Theme = P::Theme;
+        type Renderer = P::Renderer;
         type Executor = P::Executor;
 
         fn theme(&self, state: &Self::State) -> Self::Theme {
@@ -690,7 +704,7 @@ fn with_theme<P: Definition>(
         fn view<'a>(
             &self,
             state: &'a Self::State,
-        ) -> Element<'a, Self::Message, Self::Theme> {
+        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
             self.program.view(state)
         }
 
@@ -729,6 +743,7 @@ fn with_style<P: Definition>(
         type State = P::State;
         type Message = P::Message;
         type Theme = P::Theme;
+        type Renderer = P::Renderer;
         type Executor = P::Executor;
 
         fn style(
@@ -758,7 +773,7 @@ fn with_style<P: Definition>(
         fn view<'a>(
             &self,
             state: &'a Self::State,
-        ) -> Element<'a, Self::Message, Self::Theme> {
+        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
             self.program.view(state)
         }
 
@@ -834,18 +849,25 @@ where
 ///
 /// This trait allows the [`program`] builder to take any closure that
 /// returns any `Into<Element<'_, Message>>`.
-pub trait View<'a, State, Message, Theme> {
+pub trait View<'a, State, Message, Theme, Renderer> {
     /// Produces the widget of the [`Program`].
-    fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>>;
+    fn view(
+        &self,
+        state: &'a State,
+    ) -> impl Into<Element<'a, Message, Theme, Renderer>>;
 }
 
-impl<'a, T, State, Message, Theme, Widget> View<'a, State, Message, Theme> for T
+impl<'a, T, State, Message, Theme, Renderer, Widget>
+    View<'a, State, Message, Theme, Renderer> for T
 where
     T: Fn(&'a State) -> Widget,
     State: 'static,
-    Widget: Into<Element<'a, Message, Theme>>,
+    Widget: Into<Element<'a, Message, Theme, Renderer>>,
 {
-    fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>> {
+    fn view(
+        &self,
+        state: &'a State,
+    ) -> impl Into<Element<'a, Message, Theme, Renderer>> {
         self(state)
     }
 }
diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs
index ec27b218..01d015b4 100644
--- a/tiny_skia/src/settings.rs
+++ b/tiny_skia/src/settings.rs
@@ -1,4 +1,5 @@
 use crate::core::{Font, Pixels};
+use crate::graphics;
 
 /// The settings of a [`Backend`].
 ///
@@ -22,3 +23,12 @@ 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,
+        }
+    }
+}
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index a98825f1..0c08097b 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -1,7 +1,7 @@
 use crate::core::{Color, Rectangle, Size};
 use crate::graphics::compositor::{self, Information};
 use crate::graphics::damage;
-use crate::graphics::{Error, Viewport};
+use crate::graphics::{self, Error, Viewport};
 use crate::{Backend, Primitive, Renderer, Settings};
 
 use std::collections::VecDeque;
@@ -25,15 +25,14 @@ pub struct Surface {
 }
 
 impl crate::graphics::Compositor for Compositor {
-    type Settings = Settings;
     type Renderer = Renderer;
     type Surface = Surface;
 
     fn new<W: compositor::Window>(
-        settings: Self::Settings,
+        settings: graphics::Settings,
         compatible_window: W,
     ) -> impl Future<Output = Result<Self, Error>> {
-        future::ready(Ok(new(settings, compatible_window)))
+        future::ready(Ok(new(settings.into(), compatible_window)))
     }
 
     fn create_renderer(&self) -> Self::Renderer {
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index c9338fec..9943aa3e 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`].
 ///
@@ -64,3 +64,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 328ad781..74dfadba 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -1,9 +1,8 @@
 //! 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::{self, Error, Viewport};
 use crate::{Backend, Primitive, Renderer, Settings};
 
 use std::future::Future;
@@ -225,15 +224,14 @@ 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,
+        settings: graphics::Settings,
         compatible_window: W,
     ) -> impl Future<Output = Result<Self, Error>> {
-        new(settings, compatible_window)
+        new(settings.into(), compatible_window)
     }
 
     fn create_renderer(&self) -> Self::Renderer {
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 29786b65..d68523fa 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -13,6 +13,7 @@ use crate::core::window;
 use crate::core::{Color, Event, Point, Size, Theme};
 use crate::futures::futures;
 use crate::futures::{Executor, Runtime, Subscription};
+use crate::graphics;
 use crate::graphics::compositor::{self, Compositor};
 use crate::runtime::clipboard;
 use crate::runtime::program::Program;
@@ -130,7 +131,7 @@ pub fn default(theme: &Theme) -> Appearance {
 /// settings.
 pub async fn run<A, E, C>(
     settings: Settings<A::Flags>,
-    compositor_settings: impl Into<C::Settings>,
+    graphics_settings: graphics::Settings,
 ) -> Result<(), Error>
 where
     A: Application + 'static,
@@ -219,7 +220,7 @@ where
         };
     }
 
-    let compositor = C::new(compositor_settings.into(), window.clone()).await?;
+    let compositor = C::new(graphics_settings, window.clone()).await?;
     let mut renderer = compositor.create_renderer();
 
     for font in settings.fonts {
diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs
index c865b0ee..b4c25411 100644
--- a/winit/src/multi_window.rs
+++ b/winit/src/multi_window.rs
@@ -16,6 +16,7 @@ use crate::futures::futures::executor;
 use crate::futures::futures::task;
 use crate::futures::futures::{Future, StreamExt};
 use crate::futures::{Executor, Runtime, Subscription};
+use crate::graphics;
 use crate::graphics::{compositor, Compositor};
 use crate::multi_window::window_manager::WindowManager;
 use crate::runtime::command::{self, Command};
@@ -105,7 +106,7 @@ where
 /// settings.
 pub fn run<A, E, C>(
     settings: Settings<A::Flags>,
-    compositor_settings: impl Into<C::Settings>,
+    graphics_settings: graphics::Settings,
 ) -> Result<(), Error>
 where
     A: Application + 'static,
@@ -186,10 +187,8 @@ where
         };
     }
 
-    let mut compositor = executor::block_on(C::new(
-        compositor_settings.into(),
-        main_window.clone(),
-    ))?;
+    let mut compositor =
+        executor::block_on(C::new(graphics_settings, main_window.clone()))?;
 
     let mut window_manager = WindowManager::new();
     let _ = window_manager.insert(
-- 
cgit 


From a2c897792ccb8f91a8479c1eca9146c439e9173b Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 07:12:46 +0100
Subject: Fix unnecessary `into` calls in `iced_renderer::fallback`

---
 renderer/src/fallback.rs | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 28e73dd8..8daab74f 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -208,16 +208,14 @@ where
         settings: graphics::Settings,
         compatible_window: W,
     ) -> Result<Self, graphics::Error> {
-        if let Ok(left) = L::new(settings.into(), compatible_window.clone())
+        if let Ok(left) = L::new(settings, compatible_window.clone())
             .await
             .map(Self::Left)
         {
             return Ok(left);
         }
 
-        R::new(settings.into(), compatible_window)
-            .await
-            .map(Self::Right)
+        R::new(settings, compatible_window).await.map(Self::Right)
     }
 
     fn create_renderer(&self) -> Self::Renderer {
-- 
cgit 


From 441e9237cd1c9c9b61d9b144b5b4dafa236ace28 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Fri, 22 Mar 2024 19:35:19 +0100
Subject: Rename `compositor::Renderer` to `Default`

---
 graphics/src/backend.rs    |  6 +++---
 graphics/src/compositor.rs |  7 +++----
 graphics/src/renderer.rs   |  2 +-
 renderer/src/fallback.rs   |  6 +++---
 src/application.rs         |  4 ++--
 src/lib.rs                 |  2 +-
 src/program.rs             | 12 +++++++++---
 tiny_skia/src/backend.rs   |  4 ++--
 wgpu/src/backend.rs        |  4 ++--
 9 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index aa7bf4e8..7abc42c5 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -10,11 +10,11 @@ use std::borrow::Cow;
 ///
 /// [`Renderer`]: crate::Renderer
 pub trait Backend: Sized {
-    /// The compositor of this [`Backend`].
-    type Compositor: Compositor<Renderer = Renderer<Self>>;
-
     /// The custom kind of primitives this [`Backend`] supports.
     type Primitive: TryFrom<Mesh, Error = &'static str>;
+
+    /// The default compositor of this [`Backend`].
+    type Compositor: Compositor<Renderer = Renderer<Self>>;
 }
 
 /// A graphics backend that supports text rendering.
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs
index 4d548f30..8c67cd16 100644
--- a/graphics/src/compositor.rs
+++ b/graphics/src/compositor.rs
@@ -1,6 +1,5 @@
 //! A compositor is responsible for initializing a renderer and managing window
 //! surfaces.
-use crate::core;
 use crate::core::Color;
 use crate::futures::{MaybeSend, MaybeSync};
 use crate::{Error, Settings, Viewport};
@@ -90,8 +89,8 @@ impl<T> Window for T where
 {
 }
 
-/// A renderer that supports composition.
-pub trait Renderer: core::Renderer {
+/// Defines the default compositor of a renderer.
+pub trait Default {
     /// The compositor of the renderer.
     type Compositor: Compositor<Renderer = Self>;
 }
@@ -187,6 +186,6 @@ impl Compositor for () {
 }
 
 #[cfg(debug_assertions)]
-impl Renderer for () {
+impl Default for () {
     type Compositor = ();
 }
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 5de7f97f..f517ff3e 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -261,7 +261,7 @@ where
     }
 }
 
-impl<B> compositor::Renderer for Renderer<B>
+impl<B> compositor::Default for Renderer<B>
 where
     B: Backend,
 {
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 8daab74f..ca445746 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -524,10 +524,10 @@ mod geometry {
     }
 }
 
-impl<L, R> compositor::Renderer for Renderer<L, R>
+impl<L, R> compositor::Default for Renderer<L, R>
 where
-    L: compositor::Renderer,
-    R: compositor::Renderer,
+    L: compositor::Default,
+    R: compositor::Default,
 {
     type Compositor = Compositor<L::Compositor, R::Compositor>;
 }
diff --git a/src/application.rs b/src/application.rs
index 62fe77da..9197834b 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -113,7 +113,7 @@ where
     type Theme: Default;
 
     /// The renderer of your [`Application`].
-    type Renderer: text::Renderer + compositor::Renderer;
+    type Renderer: text::Renderer + compositor::Default;
 
     /// The data needed to initialize your [`Application`].
     type Flags;
@@ -215,7 +215,7 @@ where
         let run = crate::shell::application::run::<
             Instance<Self>,
             Self::Executor,
-            <Self::Renderer as compositor::Renderer>::Compositor,
+            <Self::Renderer as compositor::Default>::Compositor,
         >(settings.into(), renderer_settings);
 
         #[cfg(target_arch = "wasm32")]
diff --git a/src/lib.rs b/src/lib.rs
index 171109e7..e67b46e3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -381,7 +381,7 @@ where
     State: Default + 'static,
     Message: std::fmt::Debug + Send + 'static,
     Theme: Default + program::DefaultStyle + 'static,
-    Renderer: graphics::compositor::Renderer + core::text::Renderer + 'static,
+    Renderer: program::Renderer + 'static,
 {
     program(title, update, view).run()
 }
diff --git a/src/program.rs b/src/program.rs
index f5a2bc53..705e140d 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -31,6 +31,7 @@
 //! }
 //! ```
 use crate::application::Application;
+use crate::core::text;
 use crate::executor::{self, Executor};
 use crate::graphics::compositor;
 use crate::window;
@@ -77,7 +78,7 @@ where
     State: 'static,
     Message: Send + std::fmt::Debug,
     Theme: Default + DefaultStyle,
-    Renderer: compositor::Renderer + crate::core::text::Renderer,
+    Renderer: self::Renderer,
 {
     use std::marker::PhantomData;
 
@@ -95,7 +96,7 @@ where
     where
         Message: Send + std::fmt::Debug,
         Theme: Default + DefaultStyle,
-        Renderer: compositor::Renderer + crate::core::text::Renderer,
+        Renderer: self::Renderer,
         Update: self::Update<State, Message>,
         View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
     {
@@ -425,7 +426,7 @@ pub trait Definition: Sized {
     type Theme: Default + DefaultStyle;
 
     /// The renderer of the program.
-    type Renderer: compositor::Renderer + crate::core::text::Renderer;
+    type Renderer: Renderer + crate::core::text::Renderer;
 
     /// The executor of the program.
     type Executor: Executor;
@@ -871,3 +872,8 @@ where
         self(state)
     }
 }
+
+/// The renderer of some [`Program`].
+pub trait Renderer: text::Renderer + compositor::Default {}
+
+impl<T> Renderer for T where T: text::Renderer + compositor::Default {}
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index 6d4e6cda..8c8781e3 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -990,9 +990,9 @@ fn rounded_box_sdf(
     (x.powf(2.0) + y.powf(2.0)).sqrt() - radius
 }
 
-impl iced_graphics::Backend for Backend {
-    type Compositor = window::Compositor;
+impl backend::Backend for Backend {
     type Primitive = primitive::Custom;
+    type Compositor = window::Compositor;
 }
 
 impl backend::Text for Backend {
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 3675d50b..5019191c 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -372,9 +372,9 @@ impl Backend {
     }
 }
 
-impl crate::graphics::Backend for Backend {
-    type Compositor = window::Compositor;
+impl backend::Backend for Backend {
     type Primitive = primitive::Custom;
+    type Compositor = window::Compositor;
 }
 
 impl backend::Text for Backend {
-- 
cgit 


From 4f5b63f1f4cd7d3ab72289c697f4abc767114eca Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Sun, 24 Mar 2024 08:04:28 +0100
Subject: Reintroduce backend selection through `ICED_BACKEND` env var

---
 graphics/src/compositor.rs         |  15 +++-
 graphics/src/error.rs              |  27 ++++++-
 graphics/src/lib.rs                |   2 +-
 renderer/src/fallback.rs           |  51 +++++++++++--
 tiny_skia/src/window/compositor.rs |  21 ++++--
 wgpu/Cargo.toml                    |   1 +
 wgpu/src/settings.rs               |  24 -------
 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 {
-- 
cgit 


From 6a4f5ac2081699f7cf20c917b367366ab49eeef1 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Sun, 24 Mar 2024 08:45:11 +0100
Subject: Remove redundant `text::Renderer` bound in `program`

---
 src/program.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/program.rs b/src/program.rs
index 705e140d..d4c2a266 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -426,7 +426,7 @@ pub trait Definition: Sized {
     type Theme: Default + DefaultStyle;
 
     /// The renderer of the program.
-    type Renderer: Renderer + crate::core::text::Renderer;
+    type Renderer: Renderer;
 
     /// The executor of the program.
     type Executor: Executor;
-- 
cgit