Skip to content

Commit

Permalink
Match CPU/GPU logic for start tangents
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
raphlinus committed Sep 26, 2024
1 parent 28cddb9 commit a05c730
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 16 deletions.
52 changes: 40 additions & 12 deletions vello_encoding/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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 {
Expand All @@ -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")]
Expand Down
8 changes: 4 additions & 4 deletions vello_shaders/shader/flatten.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit a05c730

Please sign in to comment.