diff options
| author | 2023-03-09 19:05:38 +0100 | |
|---|---|---|
| committer | 2023-03-09 19:05:38 +0100 | |
| commit | caf2836b1b15bff6e8a2ea72441d67f297eb8707 (patch) | |
| tree | 0ffa0d1d604780999892b88de85ee93e3ed7d539 /renderer | |
| parent | 11b2c3bbe31a43e73a61b9bd9f022233f302ae27 (diff) | |
| parent | 424ac8177309440bbd8efe0dd9f7622cb10807ce (diff) | |
| download | iced-caf2836b1b15bff6e8a2ea72441d67f297eb8707.tar.gz iced-caf2836b1b15bff6e8a2ea72441d67f297eb8707.tar.bz2 iced-caf2836b1b15bff6e8a2ea72441d67f297eb8707.zip | |
Merge pull request #1748 from iced-rs/feature/software-renderer
Software renderer, runtime renderer fallback, and core consolidation
Diffstat (limited to 'renderer')
| -rw-r--r-- | renderer/Cargo.toml | 30 | ||||
| -rw-r--r-- | renderer/src/backend.rs | 98 | ||||
| -rw-r--r-- | renderer/src/compositor.rs | 185 | ||||
| -rw-r--r-- | renderer/src/geometry.rs | 179 | ||||
| -rw-r--r-- | renderer/src/geometry/cache.rs | 87 | ||||
| -rw-r--r-- | renderer/src/lib.rs | 22 | ||||
| -rw-r--r-- | renderer/src/settings.rs | 31 | ||||
| -rw-r--r-- | renderer/src/widget.rs | 11 | 
8 files changed, 643 insertions, 0 deletions
| diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml new file mode 100644 index 00000000..629c11ba --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "iced_renderer" +version = "0.1.0" +edition = "2021" + +[features] +wgpu = ["iced_wgpu"] +tiny-skia = ["iced_tiny_skia"] +image = ["iced_wgpu/image", "iced_tiny_skia/image"] +svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] +geometry = ["iced_graphics/geometry", "iced_wgpu?/geometry", "iced_tiny_skia?/geometry"] +tracing = ["iced_wgpu/tracing"] + +[dependencies] +raw-window-handle = "0.5" +thiserror = "1" + +[dependencies.iced_graphics] +version = "0.7" +path = "../graphics" + +[dependencies.iced_wgpu] +version = "0.9" +path = "../wgpu" +optional = true + +[dependencies.iced_tiny_skia] +version = "0.1" +path = "../tiny_skia" +optional = true diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs new file mode 100644 index 00000000..e77b708b --- /dev/null +++ b/renderer/src/backend.rs @@ -0,0 +1,98 @@ +use crate::core::text; +use crate::core::{Font, Point, Size}; +use crate::graphics::backend; + +use std::borrow::Cow; + +#[allow(clippy::large_enum_variant)] +pub enum Backend { +    #[cfg(feature = "wgpu")] +    Wgpu(iced_wgpu::Backend), +    #[cfg(feature = "tiny-skia")] +    TinySkia(iced_tiny_skia::Backend), +} + +macro_rules! delegate { +    ($backend:expr, $name:ident, $body:expr) => { +        match $backend { +            #[cfg(feature = "wgpu")] +            Self::Wgpu($name) => $body, +            #[cfg(feature = "tiny-skia")] +            Self::TinySkia($name) => $body, +        } +    }; +} + +impl iced_graphics::Backend for Backend { +    fn trim_measurements(&mut self) { +        delegate!(self, backend, backend.trim_measurements()); +    } +} + +impl backend::Text for Backend { +    const ICON_FONT: Font = Font::Name("Iced-Icons"); +    const CHECKMARK_ICON: char = '\u{f00c}'; +    const ARROW_DOWN_ICON: char = '\u{e800}'; + +    fn default_font(&self) -> Font { +        delegate!(self, backend, backend.default_font()) +    } + +    fn default_size(&self) -> f32 { +        delegate!(self, backend, backend.default_size()) +    } + +    fn measure( +        &self, +        contents: &str, +        size: f32, +        font: Font, +        bounds: Size, +    ) -> (f32, f32) { +        delegate!(self, backend, backend.measure(contents, size, font, bounds)) +    } + +    fn hit_test( +        &self, +        contents: &str, +        size: f32, +        font: Font, +        bounds: Size, +        position: Point, +        nearest_only: bool, +    ) -> Option<text::Hit> { +        delegate!( +            self, +            backend, +            backend.hit_test( +                contents, +                size, +                font, +                bounds, +                position, +                nearest_only +            ) +        ) +    } + +    fn load_font(&mut self, font: Cow<'static, [u8]>) { +        delegate!(self, backend, backend.load_font(font)); +    } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { +    fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { +        delegate!(self, backend, backend.dimensions(handle)) +    } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { +    fn viewport_dimensions( +        &self, +        handle: &crate::core::svg::Handle, +    ) -> Size<u32> { +        delegate!(self, backend, backend.viewport_dimensions(handle)) +    } +} diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs new file mode 100644 index 00000000..218e7e33 --- /dev/null +++ b/renderer/src/compositor.rs @@ -0,0 +1,185 @@ +use crate::core::Color; +use crate::graphics::compositor::{Information, SurfaceError}; +use crate::graphics::{Error, Viewport}; +use crate::{Renderer, Settings}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + +pub enum Compositor<Theme> { +    #[cfg(feature = "wgpu")] +    Wgpu(iced_wgpu::window::Compositor<Theme>), +    #[cfg(feature = "tiny-skia")] +    TinySkia(iced_tiny_skia::window::Compositor<Theme>), +} + +pub enum Surface { +    #[cfg(feature = "wgpu")] +    Wgpu(iced_wgpu::window::Surface), +    #[cfg(feature = "tiny-skia")] +    TinySkia(iced_tiny_skia::window::Surface), +} + +impl<Theme> crate::graphics::Compositor for Compositor<Theme> { +    type Settings = Settings; +    type Renderer = Renderer<Theme>; +    type Surface = Surface; + +    fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( +        settings: Self::Settings, +        compatible_window: Option<&W>, +    ) -> Result<(Self, Self::Renderer), Error> { +        #[cfg(feature = "wgpu")] +        let new_wgpu = |settings: Self::Settings, compatible_window| { +            let (compositor, backend) = 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, +            )?; + +            Ok(( +                Self::Wgpu(compositor), +                Renderer::new(crate::Backend::Wgpu(backend)), +            )) +        }; + +        #[cfg(feature = "tiny-skia")] +        let new_tiny_skia = |settings: Self::Settings, _compatible_window| { +            let (compositor, backend) = iced_tiny_skia::window::compositor::new( +                iced_tiny_skia::Settings { +                    default_font: settings.default_font, +                    default_text_size: settings.default_text_size, +                }, +            ); + +            Ok(( +                Self::TinySkia(compositor), +                Renderer::new(crate::Backend::TinySkia(backend)), +            )) +        }; + +        let fail = |_, _| Err(Error::GraphicsAdapterNotFound); + +        let candidates = &[ +            #[cfg(feature = "wgpu")] +            new_wgpu, +            #[cfg(feature = "tiny-skia")] +            new_tiny_skia, +            fail, +        ]; + +        let mut error = Error::GraphicsAdapterNotFound; + +        for candidate in candidates { +            match candidate(settings, compatible_window) { +                Ok((compositor, renderer)) => { +                    return Ok((compositor, renderer)) +                } +                Err(new_error) => { +                    error = new_error; +                } +            } +        } + +        Err(error) +    } + +    fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( +        &mut self, +        window: &W, +        width: u32, +        height: u32, +    ) -> Surface { +        match self { +            #[cfg(feature = "wgpu")] +            Self::Wgpu(compositor) => { +                Surface::Wgpu(compositor.create_surface(window, width, height)) +            } +            #[cfg(feature = "tiny-skia")] +            Self::TinySkia(compositor) => Surface::TinySkia( +                compositor.create_surface(window, width, height), +            ), +        } +    } + +    fn configure_surface( +        &mut self, +        surface: &mut Surface, +        width: u32, +        height: u32, +    ) { +        match (self, surface) { +            #[cfg(feature = "wgpu")] +            (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { +                compositor.configure_surface(surface, width, height); +            } +            #[cfg(feature = "tiny-skia")] +            (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { +                compositor.configure_surface(surface, width, height); +            } +            #[allow(unreachable_patterns)] +            _ => panic!( +                "The provided surface is not compatible with the compositor." +            ), +        } +    } + +    fn fetch_information(&self) -> Information { +        match self { +            #[cfg(feature = "wgpu")] +            Self::Wgpu(compositor) => compositor.fetch_information(), +            #[cfg(feature = "tiny-skia")] +            Self::TinySkia(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> { +        renderer.with_primitives(|backend, primitives| { +            match (self, backend, surface) { +                #[cfg(feature = "wgpu")] +                ( +                    Self::Wgpu(compositor), +                    crate::Backend::Wgpu(backend), +                    Surface::Wgpu(surface), +                ) => iced_wgpu::window::compositor::present( +                    compositor, +                    backend, +                    surface, +                    primitives, +                    viewport, +                    background_color, +                    overlay, +                ), +                #[cfg(feature = "tiny-skia")] +                ( +                    Self::TinySkia(compositor), +                    crate::Backend::TinySkia(backend), +                    Surface::TinySkia(surface), +                ) => iced_tiny_skia::window::compositor::present( +                    compositor, +                    backend, +                    surface, +                    primitives, +                    viewport, +                    background_color, +                    overlay, +                ), +                #[allow(unreachable_patterns)] +                _ => panic!( +                    "The provided renderer or surface are not compatible \ +                    with the compositor." +                ), +            } +        }) +    } +} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs new file mode 100644 index 00000000..21ef2c06 --- /dev/null +++ b/renderer/src/geometry.rs @@ -0,0 +1,179 @@ +mod cache; + +pub use cache::Cache; + +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; +use crate::Backend; + +pub enum Frame { +    #[cfg(feature = "wgpu")] +    Wgpu(iced_wgpu::geometry::Frame), +    #[cfg(feature = "tiny-skia")] +    TinySkia(iced_tiny_skia::geometry::Frame), +} + +macro_rules! delegate { +    ($frame:expr, $name:ident, $body:expr) => { +        match $frame { +            #[cfg(feature = "wgpu")] +            Self::Wgpu($name) => $body, +            #[cfg(feature = "tiny-skia")] +            Self::TinySkia($name) => $body, +        } +    }; +} + +impl Frame { +    pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self { +        match renderer.backend() { +            #[cfg(feature = "wgpu")] +            Backend::Wgpu(_) => { +                Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) +            } +            #[cfg(feature = "tiny-skia")] +            Backend::TinySkia(_) => { +                Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(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)); +    } + +    /// 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)); +    } + +    /// 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)); +    } + +    /// 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. +    /// +    /// [`Canvas`]: crate::widget::Canvas +    pub fn fill_text(&mut self, text: impl Into<Text>) { +        delegate!(self, frame, frame.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(&mut self, f: impl FnOnce(&mut Frame)) { +        delegate!(self, frame, frame.push_transform()); + +        f(self); + +        delegate!(self, frame, frame.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(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { +        let mut frame = match self { +            #[cfg(feature = "wgpu")] +            Self::Wgpu(_) => { +                Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) +            } +            #[cfg(feature = "tiny-skia")] +            Self::TinySkia(_) => Self::TinySkia( +                iced_tiny_skia::geometry::Frame::new(region.size()), +            ), +        }; + +        f(&mut frame); + +        let translation = Vector::new(region.x, region.y); + +        match (self, frame) { +            #[cfg(feature = "wgpu")] +            (Self::Wgpu(target), Self::Wgpu(frame)) => { +                target.clip(frame, translation); +            } +            #[cfg(feature = "tiny-skia")] +            (Self::TinySkia(target), Self::TinySkia(frame)) => { +                target.clip(frame, translation); +            } +            #[allow(unreachable_patterns)] +            _ => unreachable!(), +        }; +    } + +    /// 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: f32) { +        delegate!(self, frame, frame.rotate(angle)); +    } + +    /// Applies a scaling to the current transform of the [`Frame`]. +    #[inline] +    pub fn scale(&mut self, scale: f32) { +        delegate!(self, frame, frame.scale(scale)); +    } + +    pub fn into_geometry(self) -> Geometry { +        Geometry(delegate!(self, frame, frame.into_primitive())) +    } +} diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs new file mode 100644 index 00000000..2a3534d0 --- /dev/null +++ b/renderer/src/geometry/cache.rs @@ -0,0 +1,87 @@ +use crate::core::Size; +use crate::geometry::{Frame, Geometry}; +use crate::graphics::Primitive; +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: Arc<Primitive>, +    }, +} + +impl Cache { +    /// Creates a new empty [`Cache`]. +    pub fn new() -> Self { +        Cache { +            state: Default::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<Theme>( +        &self, +        renderer: &Renderer<Theme>, +        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 { +                return Geometry(Primitive::Cache { +                    content: primitive.clone(), +                }); +            } +        } + +        let mut frame = Frame::new(renderer, bounds); +        draw_fn(&mut frame); + +        let primitive = { +            let geometry = frame.into_geometry(); + +            Arc::new(geometry.0) +        }; + +        *self.state.borrow_mut() = State::Filled { +            bounds, +            primitive: primitive.clone(), +        }; + +        Geometry(Primitive::Cache { content: primitive }) +    } +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs new file mode 100644 index 00000000..ba737b11 --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,22 @@ +#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] +compile_error!("No backend selected. Enable at least one backend feature: `wgpu` or `tiny-skia`."); + +pub mod compositor; + +#[cfg(feature = "geometry")] +pub mod geometry; + +mod backend; +mod settings; + +pub use iced_graphics as graphics; +pub use iced_graphics::core; + +pub use backend::Backend; +pub use compositor::Compositor; +pub use settings::Settings; + +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs new file mode 100644 index 00000000..d32c87d3 --- /dev/null +++ b/renderer/src/settings.rs @@ -0,0 +1,31 @@ +use crate::core::Font; +use crate::graphics::Antialiasing; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::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: f32, + +    /// 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::SansSerif, +            default_text_size: 16.0, +            antialiasing: None, +        } +    } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs new file mode 100644 index 00000000..6c0c2a83 --- /dev/null +++ b/renderer/src/widget.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "canvas")] +pub mod canvas; + +#[cfg(feature = "canvas")] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +pub use qr_code::QRCode; | 
