diff options
Diffstat (limited to 'wgpu/src/geometry.rs')
-rw-r--r-- | wgpu/src/geometry.rs | 578 |
1 files changed, 277 insertions, 301 deletions
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f4e0fbda..f6213e1d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -3,215 +3,136 @@ 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::{ - 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::primitive::{self, Primitive}; +use crate::graphics::{self, Text}; +use crate::text; +use crate::triangle; use lyon::geom::euclid; use lyon::tessellation; + use std::borrow::Cow; -/// A frame for drawing some geometry. -#[allow(missing_debug_implementations)] -pub struct Frame { - size: Size, - buffers: BufferStack, - primitives: Vec<Primitive>, - transforms: Transforms, - fill_tessellator: tessellation::FillTessellator, - stroke_tessellator: tessellation::StrokeTessellator, +#[derive(Debug)] +pub enum Geometry { + Live { meshes: Vec<Mesh>, text: Vec<Text> }, + Cached(Cache), } -enum Buffer { - Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), - Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), +#[derive(Debug, Clone)] +pub struct Cache { + pub meshes: Option<triangle::Cache>, + pub text: Option<text::Cache>, } -struct BufferStack { - stack: Vec<Buffer>, -} +impl Cached for Geometry { + type Cache = Cache; -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } + fn load(cache: &Self::Cache) -> Self { + Geometry::Cached(cache.clone()) } - 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() - } + fn cache( + self, + group: cache::Group, + previous: Option<Self::Cache>, + ) -> Self::Cache { + match self { + Self::Live { meshes, text } => { + if let Some(mut previous) = previous { + if let Some(cache) = &mut previous.meshes { + cache.update(meshes); + } else { + previous.meshes = triangle::Cache::new(meshes); + } - 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!(), - } - } + if let Some(cache) = &mut previous.text { + cache.update(text); + } else { + previous.text = text::Cache::new(group, text); + } - 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(), - }, - )) + previous + } else { + Cache { + meshes: triangle::Cache::new(meshes), + text: text::Cache::new(group, text), + } + } } - _ => unreachable!(), + Self::Cached(cache) => cache, } } } -#[derive(Debug)] -struct Transforms { - previous: Vec<Transform>, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { - fn is_identity(&self) -> bool { - self.0 == lyon::math::Transform::identity() - } - - fn is_scale_translation(&self) -> bool { - self.0.m12.abs() < 2.0 * f32::EPSILON - && self.0.m21.abs() < 2.0 * f32::EPSILON - } - - fn scale(&self) -> (f32, f32) { - (self.0.m11, self.0.m22) - } - - fn transform_point(&self, point: Point) -> Point { - let transformed = self - .0 - .transform_point(euclid::Point2D::new(point.x, point.y)); - - Point { - x: transformed.x, - y: transformed.y, - } - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - match &mut gradient { - Gradient::Linear(linear) => { - linear.start = self.transform_point(linear.start); - linear.end = self.transform_point(linear.end); - } - } - - gradient - } +/// A frame for drawing some geometry. +#[allow(missing_debug_implementations)] +pub struct Frame { + clip_bounds: Rectangle, + buffers: BufferStack, + meshes: Vec<Mesh>, + text: Vec<Text>, + transforms: Transforms, + fill_tessellator: tessellation::FillTessellator, + stroke_tessellator: tessellation::StrokeTessellator, } 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. + /// 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(), - primitives: Vec::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(), } } +} + +impl geometry::frame::Backend for Frame { + type Geometry = Geometry; - /// Returns the width of the [`Frame`]. #[inline] - pub fn width(&self) -> f32 { - self.size.width + fn width(&self) -> f32 { + self.clip_bounds.width } - /// Returns the height of the [`Frame`]. #[inline] - pub fn height(&self) -> f32 { - self.size.height + fn height(&self) -> f32 { + self.clip_bounds.height } - /// Returns the dimensions of the [`Frame`]. #[inline] - pub fn size(&self) -> Size { - self.size + fn size(&self) -> Size { + self.clip_bounds.size() } - /// Returns the coordinate of the center of the [`Frame`]. #[inline] - pub fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) + fn center(&self) -> Point { + Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.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 +160,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 +195,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 +232,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<geometry::Text>) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -366,105 +270,25 @@ impl 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, 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)); } } - /// 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 - } - - /// Pushes the current transform in the transform stack. - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); - } - - /// 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(); - } - - /// 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()); - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - self.clip(frame, origin); - - result - } - - /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn clip(&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, - }), - }), - }, - ], - }); - } - - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { + fn translate(&mut self, translation: Vector) { self.transforms.current.0 = self.transforms .current @@ -475,9 +299,8 @@ impl Frame { )); } - /// Applies a rotation in radians to the current transform of the [`Frame`]. #[inline] - pub fn rotate(&mut self, angle: impl Into<Radians>) { + fn rotate(&mut self, angle: impl Into<Radians>) { self.transforms.current.0 = self .transforms .current @@ -485,66 +308,217 @@ impl Frame { .pre_rotate(lyon::math::Angle::radians(angle.into().0)); } - /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] - 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 }); } - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { let scale = scale.into(); self.transforms.current.0 = self.transforms.current.0.pre_scale(scale.x, scale.y); } - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), + fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } + + fn draft(&mut self, clip_bounds: Rectangle) -> Frame { + Frame::with_clip(clip_bounds) + } + + fn paste(&mut self, frame: Frame, _at: Point) { + self.meshes + .extend(frame.buffers.into_meshes(frame.clip_bounds)); + + self.text.extend(frame.text); + } + + 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, } } +} - 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, - }), - )); - } +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(), + )); } - 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, - }), - )); - } + }, + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::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(), + }, + )) } + _ => unreachable!(), } + } - self.primitives + 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!(), + } + } + + fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> { + 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, + }) } } +#[derive(Debug)] +struct Transforms { + previous: Vec<Transform>, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform(lyon::math::Transform); + +impl Transform { + fn is_identity(&self) -> bool { + self.0 == lyon::math::Transform::identity() + } + + fn is_scale_translation(&self) -> bool { + self.0.m12.abs() < 2.0 * f32::EPSILON + && self.0.m21.abs() < 2.0 * f32::EPSILON + } + + fn scale(&self) -> (f32, f32) { + (self.0.m11, self.0.m22) + } + + fn transform_point(&self, point: Point) -> Point { + let transformed = self + .0 + .transform_point(euclid::Point2D::new(point.x, point.y)); + + Point { + x: transformed.x, + y: transformed.y, + } + } + + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) + } + } + } + + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + match &mut gradient { + Gradient::Linear(linear) => { + linear.start = self.transform_point(linear.start); + linear.end = self.transform_point(linear.end); + } + } + + gradient + } +} struct GradientVertex2DBuilder { gradient: gradient::Packed, } @@ -651,7 +625,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 { |