summaryrefslogtreecommitdiffstats
path: root/wgpu/src/geometry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'wgpu/src/geometry.rs')
-rw-r--r--wgpu/src/geometry.rs578
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 {