From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:27:17 +0100 Subject: Implement composable, type-safe renderer fallback --- wgpu/src/geometry.rs | 493 +++++++++++++++++++++++---------------------------- 1 file changed, 217 insertions(+), 276 deletions(-) (limited to 'wgpu/src/geometry.rs') 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), - Gradient(tessellation::VertexBuffers), -} - -struct BufferStack { - stack: Vec, -} - -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 { - 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 { + 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 { - 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, - 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) { + fn fill(&mut self, path: &Path, fill: impl Into) { 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>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { 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) { + fn fill_text(&mut self, text: impl Into) { 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(&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) { + 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) { + 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( - &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) { + 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 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), + Gradient(tessellation::VertexBuffers), +} + +struct BufferStack { + stack: Vec, +} + +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) { - 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) { - let scale = scale.into(); + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box { + 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 { + 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) { - let scale = scale.into(); +#[derive(Debug)] +struct Transforms { + previous: Vec, + 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 { - 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, } -- cgit From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:35:14 +0100 Subject: Restore `canvas::Frame` API --- wgpu/src/geometry.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'wgpu/src/geometry.rs') 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 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(), } } } -- cgit From b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 21:07:54 +0200 Subject: Redesign `iced_wgpu` layering architecture --- wgpu/src/geometry.rs | 175 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 70 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index ba56c59d..7c8c0a35 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -10,31 +10,82 @@ use crate::graphics::geometry::{ }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::primitive::{self, Primitive}; +use crate::graphics::{self, Cached}; +use crate::layer; +use crate::text; use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; /// A frame for drawing some geometry. #[allow(missing_debug_implementations)] pub struct Frame { size: Size, buffers: BufferStack, - primitives: Vec, + layers: Vec, + text: text::Batch, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, stroke_tessellator: tessellation::StrokeTessellator, } +pub enum Geometry { + Live(Vec), + Cached(Rc<[Rc>]>), +} + +impl Cached for Geometry { + type Cache = Rc<[Rc>]>; + + fn load(cache: &Self::Cache) -> Self { + Geometry::Cached(cache.clone()) + } + + fn cache(self, previous: Option) -> Self::Cache { + match self { + Self::Live(live) => { + let mut layers = live.into_iter(); + + let mut new: Vec<_> = previous + .map(|previous| { + previous + .iter() + .cloned() + .zip(layers.by_ref()) + .map(|(cached, live)| { + cached.borrow_mut().update(live); + cached + }) + .collect() + }) + .unwrap_or_default(); + + new.extend( + layers + .map(layer::Live::into_cached) + .map(RefCell::new) + .map(Rc::new), + ); + + Rc::from(new) + } + Self::Cached(cache) => cache, + } + } +} + impl Frame { /// Creates a new [`Frame`] with the given [`Size`]. pub fn new(size: Size) -> Frame { Frame { size, buffers: BufferStack::new(), - primitives: Vec::new(), + layers: Vec::new(), + text: text::Batch::new(), transforms: Transforms { previous: Vec::new(), current: Transform(lyon::math::Transform::identity()), @@ -44,49 +95,54 @@ impl Frame { } } - fn into_primitives(mut self) -> Vec { - 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 into_layers(mut self) -> Vec { + if !self.text.is_empty() || !self.buffers.stack.is_empty() { + let clip_bounds = Rectangle::with_size(self.size); + let transformation = Transformation::IDENTITY; + + // TODO: Generate different meshes for different transformations (?) + // Instead of transforming each path + let meshes = self + .buffers + .stack + .into_iter() + .map(|buffer| match buffer { + Buffer::Solid(buffer) => Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + transformation: Transformation::IDENTITY, + size: self.size, + }, + Buffer::Gradient(buffer) => Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + transformation: Transformation::IDENTITY, + size: self.size, + }, + }) + .collect(); + + let layer = layer::Live { + bounds: Some(clip_bounds), + transformation, + meshes, + text: self.text, + ..layer::Live::default() + }; + + self.layers.push(layer); } - self.primitives + self.layers } } impl geometry::frame::Backend for Frame { - type Geometry = Primitive; - - /// 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. + type Geometry = Geometry; #[inline] fn width(&self) -> f32 { @@ -246,8 +302,7 @@ impl geometry::frame::Backend for Frame { height: f32::INFINITY, }; - // TODO: Honor layering! - self.primitives.push(Primitive::Text { + self.text.push(graphics::Text::Cached { content: text.content, bounds, color: text.color, @@ -313,37 +368,17 @@ impl geometry::frame::Backend for Frame { } 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); - - let (text, meshes) = primitives - .into_iter() - .partition(|primitive| matches!(primitive, Primitive::Text { .. })); - - self.primitives.push(Primitive::Group { - primitives: vec![ - Primitive::Transform { - transformation, - content: Box::new(Primitive::Group { primitives: meshes }), - }, - Primitive::Transform { - transformation, - content: Box::new(Primitive::Clip { - bounds: Rectangle::with_size(size), - content: Box::new(Primitive::Group { - primitives: text, - }), - }), - }, - ], - }); + let translation = Transformation::translate(at.x, at.y); + + self.layers + .extend(frame.into_layers().into_iter().map(|mut layer| { + layer.transformation = layer.transformation * translation; + layer + })); } fn into_geometry(self) -> Self::Geometry { - Primitive::Group { - primitives: self.into_primitives(), - } + Geometry::Live(self.into_layers()) } } -- cgit From d461f23e8dd34c5de73e0aa176a3301b01564652 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:31:13 +0200 Subject: Use default tolerance for dashed paths in `iced_wgpu` --- wgpu/src/geometry.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 7c8c0a35..d153c764 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -626,7 +626,9 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { let mut draw_line = false; walk_along_path( - path.raw().iter().flattened(0.01), + path.raw().iter().flattened( + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + ), 0.0, lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, &mut RepeatedPattern { -- cgit From 394e599c3a096b036aabdd6f850c4a7c518d44fa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 00:40:39 +0200 Subject: Fix layer transformations --- wgpu/src/geometry.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index d153c764..611e81f1 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -106,23 +106,28 @@ impl Frame { .buffers .stack .into_iter() - .map(|buffer| match buffer { - Buffer::Solid(buffer) => Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - transformation: Transformation::IDENTITY, - size: self.size, - }, - Buffer::Gradient(buffer) => Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - transformation: Transformation::IDENTITY, - size: self.size, - }, + .filter_map(|buffer| match buffer { + Buffer::Solid(buffer) if !buffer.indices.is_empty() => { + Some(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + transformation: Transformation::IDENTITY, + size: self.size, + }) + } + Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { + Some(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + transformation: Transformation::IDENTITY, + size: self.size, + }) + } + _ => None, }) .collect(); -- cgit From 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 23:59:21 +0200 Subject: Decouple caching from layering and simplify everything --- wgpu/src/geometry.rs | 188 ++++++++++++++++++++++----------------------------- 1 file changed, 80 insertions(+), 108 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 611e81f1..c8c350c5 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,40 +6,44 @@ use crate::core::{ use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ - self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, + self, LineCap, LineDash, LineJoin, Path, Stroke, Style, }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Cached}; -use crate::layer; +use crate::graphics::{self, Cached, Text}; use crate::text; +use crate::triangle; use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; -use std::cell::RefCell; -use std::rc::Rc; /// A frame for drawing some geometry. #[allow(missing_debug_implementations)] pub struct Frame { - size: Size, + clip_bounds: Rectangle, buffers: BufferStack, - layers: Vec, - text: text::Batch, + meshes: Vec, + text: Vec, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, stroke_tessellator: tessellation::StrokeTessellator, } pub enum Geometry { - Live(Vec), - Cached(Rc<[Rc>]>), + Live { meshes: Vec, text: Vec }, + Cached(Cache), +} + +#[derive(Clone)] +pub struct Cache { + pub meshes: triangle::Cache, + pub text: text::Cache, } impl Cached for Geometry { - type Cache = Rc<[Rc>]>; + type Cache = Cache; fn load(cache: &Self::Cache) -> Self { Geometry::Cached(cache.clone()) @@ -47,31 +51,18 @@ impl Cached for Geometry { fn cache(self, previous: Option) -> Self::Cache { match self { - Self::Live(live) => { - let mut layers = live.into_iter(); - - let mut new: Vec<_> = previous - .map(|previous| { - previous - .iter() - .cloned() - .zip(layers.by_ref()) - .map(|(cached, live)| { - cached.borrow_mut().update(live); - cached - }) - .collect() - }) - .unwrap_or_default(); - - new.extend( - layers - .map(layer::Live::into_cached) - .map(RefCell::new) - .map(Rc::new), - ); - - Rc::from(new) + Self::Live { meshes, text } => { + if let Some(mut previous) = previous { + previous.meshes.update(meshes); + previous.text.update(text); + + previous + } else { + Cache { + meshes: triangle::Cache::new(meshes), + text: text::Cache::new(text), + } + } } Self::Cached(cache) => cache, } @@ -81,69 +72,26 @@ impl Cached for Geometry { impl Frame { /// Creates a new [`Frame`] with the given [`Size`]. pub fn new(size: Size) -> Frame { + Self::with_clip(Rectangle::with_size(size)) + } + + /// Creates a new [`Frame`] with the given clip bounds. + pub fn with_clip(bounds: Rectangle) -> Frame { Frame { - size, + clip_bounds: bounds, buffers: BufferStack::new(), - layers: Vec::new(), - text: text::Batch::new(), + meshes: Vec::new(), + text: Vec::new(), transforms: Transforms { previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), + current: Transform(lyon::math::Transform::translation( + bounds.x, bounds.y, + )), }, fill_tessellator: tessellation::FillTessellator::new(), stroke_tessellator: tessellation::StrokeTessellator::new(), } } - - fn into_layers(mut self) -> Vec { - if !self.text.is_empty() || !self.buffers.stack.is_empty() { - let clip_bounds = Rectangle::with_size(self.size); - let transformation = Transformation::IDENTITY; - - // TODO: Generate different meshes for different transformations (?) - // Instead of transforming each path - let meshes = self - .buffers - .stack - .into_iter() - .filter_map(|buffer| match buffer { - Buffer::Solid(buffer) if !buffer.indices.is_empty() => { - Some(Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - transformation: Transformation::IDENTITY, - size: self.size, - }) - } - Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { - Some(Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - transformation: Transformation::IDENTITY, - size: self.size, - }) - } - _ => None, - }) - .collect(); - - let layer = layer::Live { - bounds: Some(clip_bounds), - transformation, - meshes, - text: self.text, - ..layer::Live::default() - }; - - self.layers.push(layer); - } - - self.layers - } } impl geometry::frame::Backend for Frame { @@ -151,22 +99,22 @@ impl geometry::frame::Backend for Frame { #[inline] fn width(&self) -> f32 { - self.size.width + self.clip_bounds.width } #[inline] fn height(&self) -> f32 { - self.size.height + self.clip_bounds.height } #[inline] fn size(&self) -> Size { - self.size + self.clip_bounds.size() } #[inline] fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) + Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0) } fn fill(&mut self, path: &Path, fill: impl Into) { @@ -269,7 +217,7 @@ impl geometry::frame::Backend for Frame { .expect("Stroke path"); } - fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -312,12 +260,12 @@ impl geometry::frame::Backend for Frame { bounds, color: text.color, size, - line_height, + line_height: line_height.to_absolute(size), font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, shaping: text.shaping, - clip_bounds: Rectangle::with_size(Size::INFINITY), + clip_bounds: self.clip_bounds, }); } else { text.draw_with(|path, color| self.fill(&path, color)); @@ -368,22 +316,25 @@ impl geometry::frame::Backend for Frame { self.transforms.current = self.transforms.previous.pop().unwrap(); } - fn draft(&mut self, size: Size) -> Frame { - Frame::new(size) + fn draft(&mut self, clip_bounds: Rectangle) -> Frame { + Frame::with_clip(clip_bounds) } - fn paste(&mut self, frame: Frame, at: Point) { - let translation = Transformation::translate(at.x, at.y); + fn paste(&mut self, frame: Frame, _at: Point) { + self.meshes + .extend(frame.buffers.into_meshes(frame.clip_bounds)); - self.layers - .extend(frame.into_layers().into_iter().map(|mut layer| { - layer.transformation = layer.transformation * translation; - layer - })); + self.text.extend(frame.text); } - fn into_geometry(self) -> Self::Geometry { - Geometry::Live(self.into_layers()) + fn into_geometry(mut self) -> Self::Geometry { + self.meshes + .extend(self.buffers.into_meshes(self.clip_bounds)); + + Geometry::Live { + meshes: self.meshes, + text: self.text, + } } } @@ -469,6 +420,27 @@ impl BufferStack { _ => unreachable!(), } } + + fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator { + self.stack.into_iter().map(move |buffer| match buffer { + Buffer::Solid(buffer) => Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }, + Buffer::Gradient(buffer) => Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }, + }) + } } #[derive(Debug)] -- cgit From 7eb16452f340fe228e6928b496f8df6e9e86e554 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Apr 2024 00:57:59 +0200 Subject: Avoid generating empty `Frame` geometry in `iced_wgpu` --- wgpu/src/geometry.rs | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index c8c350c5..b689d2a7 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -422,24 +422,31 @@ impl BufferStack { } fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator { - self.stack.into_iter().map(move |buffer| match buffer { - Buffer::Solid(buffer) => Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - clip_bounds, - transformation: Transformation::IDENTITY, - }, - Buffer::Gradient(buffer) => Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - clip_bounds, - transformation: Transformation::IDENTITY, - }, - }) + self.stack + .into_iter() + .filter_map(move |buffer| match buffer { + Buffer::Solid(buffer) if !buffer.indices.is_empty() => { + Some(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }) + } + Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { + Some(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }) + } + _ => None, + }) } } -- cgit From 441aac25995290a83162a4728f22492ff69a5f4d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Apr 2024 03:06:40 +0200 Subject: Avoid generating empty caches in `iced_wgpu` --- wgpu/src/geometry.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index b689d2a7..c36ff38e 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -38,8 +38,8 @@ pub enum Geometry { #[derive(Clone)] pub struct Cache { - pub meshes: triangle::Cache, - pub text: text::Cache, + pub meshes: Option, + pub text: Option, } impl Cached for Geometry { @@ -53,8 +53,17 @@ impl Cached for Geometry { match self { Self::Live { meshes, text } => { if let Some(mut previous) = previous { - previous.meshes.update(meshes); - previous.text.update(text); + if let Some(cache) = &mut previous.meshes { + cache.update(meshes); + } else { + previous.meshes = triangle::Cache::new(meshes); + } + + if let Some(cache) = &mut previous.text { + cache.update(text); + } else { + previous.text = text::Cache::new(text); + } previous } else { -- cgit From 6ad5bb3597f640ac329801adf735d633bf0a512f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Apr 2024 22:25:16 +0200 Subject: Port `iced_tiny_skia` to new layering architecture --- wgpu/src/geometry.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 985650e2..60967082 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -19,18 +19,6 @@ use lyon::tessellation; use std::borrow::Cow; -/// A frame for drawing some geometry. -#[allow(missing_debug_implementations)] -pub struct Frame { - clip_bounds: Rectangle, - buffers: BufferStack, - meshes: Vec, - text: Vec, - transforms: Transforms, - fill_tessellator: tessellation::FillTessellator, - stroke_tessellator: tessellation::StrokeTessellator, -} - #[derive(Debug)] pub enum Geometry { Live { meshes: Vec, text: Vec }, @@ -79,6 +67,18 @@ impl Cached for Geometry { } } +/// A frame for drawing some geometry. +#[allow(missing_debug_implementations)] +pub struct Frame { + clip_bounds: Rectangle, + buffers: BufferStack, + meshes: Vec, + text: Vec, + transforms: Transforms, + fill_tessellator: tessellation::FillTessellator, + stroke_tessellator: tessellation::StrokeTessellator, +} + impl Frame { /// Creates a new [`Frame`] with the given [`Size`]. pub fn new(size: Size) -> Frame { -- cgit From b5b78d505e22cafccb4ecbf57dc61f536ca558ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 07:57:54 +0200 Subject: Introduce `canvas::Cache` grouping Caches with the same `Group` will share their text atlas! --- wgpu/src/geometry.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'wgpu/src/geometry.rs') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 60967082..f6213e1d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -3,6 +3,7 @@ use crate::core::text::LineHeight; use crate::core::{ Pixels, Point, Radians, Rectangle, Size, Transformation, Vector, }; +use crate::graphics::cache::{self, Cached}; use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ @@ -10,7 +11,7 @@ use crate::graphics::geometry::{ }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Cached, Text}; +use crate::graphics::{self, Text}; use crate::text; use crate::triangle; @@ -38,7 +39,11 @@ impl Cached for Geometry { Geometry::Cached(cache.clone()) } - fn cache(self, previous: Option) -> Self::Cache { + fn cache( + self, + group: cache::Group, + previous: Option, + ) -> Self::Cache { match self { Self::Live { meshes, text } => { if let Some(mut previous) = previous { @@ -51,14 +56,14 @@ impl Cached for Geometry { if let Some(cache) = &mut previous.text { cache.update(text); } else { - previous.text = text::Cache::new(text); + previous.text = text::Cache::new(group, text); } previous } else { Cache { meshes: triangle::Cache::new(meshes), - text: text::Cache::new(text), + text: text::Cache::new(group, text), } } } -- cgit