From a05c7302f0122aea68b9509ef1564a02b6ef3e7f Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Sep 2024 16:32:01 -0400 Subject: [PATCH] Match CPU/GPU logic for start tangents When encoding a start tangent for an end cap or closed segment, use logic designed to match the GPU computed tangent for the first segment. Note: this changes the GPU calculation to write out the lerp calculation explicitly rather than use the mix intrinsic, so we can rely on the rounding behavior. In the presence of fastmath, the rounding behavior is not guaranteed, but it is verified to work on M1. Fixes #704, unless we get bitten by fastmath. --- vello_encoding/src/path.rs | 52 ++++++++++++++++++++++++------- vello_shaders/shader/flatten.wgsl | 8 ++--- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/vello_encoding/src/path.rs b/vello_encoding/src/path.rs index f5518d03..be1baae1 100644 --- a/vello_encoding/src/path.rs +++ b/vello_encoding/src/path.rs @@ -524,7 +524,7 @@ impl<'a> PathEncoder<'a> { } if self.state == PathState::MoveTo { // Ensure that we don't end up with a zero-length start tangent. - let Some((x, y)) = self.start_tangent_for_curve((x, y), None, None) else { + let Some((x, y)) = self.start_tangent_for_line((x, y)) else { return; }; self.first_start_tangent_end = [x, y]; @@ -552,7 +552,7 @@ impl<'a> PathEncoder<'a> { } if self.state == PathState::MoveTo { // Ensure that we don't end up with a zero-length start tangent. - let Some((x, y)) = self.start_tangent_for_curve((x1, y1), Some((x2, y2)), None) else { + let Some((x, y)) = self.start_tangent_for_quad((x1, y1), (x2, y2)) else { return; }; self.first_start_tangent_end = [x, y]; @@ -580,9 +580,7 @@ impl<'a> PathEncoder<'a> { } if self.state == PathState::MoveTo { // Ensure that we don't end up with a zero-length start tangent. - let Some((x, y)) = - self.start_tangent_for_curve((x1, y1), Some((x2, y2)), Some((x3, y3))) - else { + let Some((x, y)) = self.start_tangent_for_curve((x1, y1), (x2, y2), (x3, y3)) else { return; }; self.first_start_tangent_end = [x, y]; @@ -748,20 +746,17 @@ impl<'a> PathEncoder<'a> { } // Returns the end point of the start tangent of a curve starting at `(x0, y0)`, or `None` if the - // curve is degenerate / has zero-length. The inputs are a sequence of control points that can - // represent a line, a quadratic Bezier, or a cubic Bezier. Lines and quadratic Beziers can be - // passed to this function by simply setting the invalid control point degrees equal to `None`. + // curve is degenerate / has zero-length. The inputs are a sequence of control points that + // represent a cubic Bezier. // // `self.first_point` is always treated as the first control point of the curve. fn start_tangent_for_curve( &self, p1: (f32, f32), - p2: Option<(f32, f32)>, - p3: Option<(f32, f32)>, + p2: (f32, f32), + p3: (f32, f32), ) -> Option<(f32, f32)> { let p0 = (self.first_point[0], self.first_point[1]); - let p2 = p2.unwrap_or(p0); - let p3 = p3.unwrap_or(p0); let pt = if (p1.0 - p0.0).abs() > EPSILON || (p1.1 - p0.1).abs() > EPSILON { p1 } else if (p2.0 - p0.0).abs() > EPSILON || (p2.1 - p0.1).abs() > EPSILON { @@ -773,6 +768,39 @@ impl<'a> PathEncoder<'a> { }; Some(pt) } + + // Similar to `start_tangent_for_curve` but for a line. + fn start_tangent_for_line(&self, p1: (f32, f32)) -> Option<(f32, f32)> { + let p0 = (self.first_point[0], self.first_point[1]); + let pt = if (p1.0 - p0.0).abs() > EPSILON || (p1.1 - p0.1).abs() > EPSILON { + ( + p0.0 + 1. / 3. * (p1.0 - p0.0), + p0.1 + 1. / 3. * (p1.1 - p0.1), + ) + } else { + return None; + }; + Some(pt) + } + + // Similar to `start_tangent_for_curve` but for a quadratic Bézier. + fn start_tangent_for_quad(&self, p1: (f32, f32), p2: (f32, f32)) -> Option<(f32, f32)> { + let p0 = (self.first_point[0], self.first_point[1]); + let pt = if (p1.0 - p0.0).abs() > EPSILON || (p1.1 - p0.1).abs() > EPSILON { + ( + p1.0 + 1. / 3. * (p0.0 - p1.0), + p1.1 + 1. / 3. * (p0.1 - p1.1), + ) + } else if (p2.0 - p0.0).abs() > EPSILON || (p2.1 - p0.1).abs() > EPSILON { + ( + p1.0 + 1. / 3. * (p2.0 - p1.0), + p1.1 + 1. / 3. * (p2.1 - p1.1), + ) + } else { + return None; + }; + Some(pt) + } } #[cfg(feature = "full")] diff --git a/vello_shaders/shader/flatten.wgsl b/vello_shaders/shader/flatten.wgsl index cd42fdeb..f6266ab7 100644 --- a/vello_shaders/shader/flatten.wgsl +++ b/vello_shaders/shader/flatten.wgsl @@ -735,12 +735,12 @@ fn read_path_segment(tag: PathTagData, is_stroke: bool) -> CubicPoints { // Degree-raise if seg_type == PATH_TAG_LINETO { p3 = p1; - p2 = mix(p3, p0, 1.0 / 3.0); - p1 = mix(p0, p3, 1.0 / 3.0); + p2 = p3 + (1.0 / 3.0) * (p0 - p3); + p1 = p0 + (1.0 / 3.0) * (p3 - p0); } else if seg_type == PATH_TAG_QUADTO { p3 = p2; - p2 = mix(p1, p2, 1.0 / 3.0); - p1 = mix(p1, p0, 1.0 / 3.0); + p2 = p1 + (1.0 / 3.0) * (p2 - p1); + p1 = p1 + (1.0 / 3.0) * (p0 - p1); } return CubicPoints(p0, p1, p2, p3);