From 3b146c82c3809c9b507481d570daf455c6aa6a08 Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Mon, 29 Dec 2025 16:32:05 +0100 Subject: [PATCH 1/6] =?UTF-8?q?perf(vello=5Fcommon):=20Cull=20B=C3=A9zier?= =?UTF-8?q?=20path=20elements=20during=20flattening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This conservatively checks whether Bézier path elements we're about to flatten are outside the viewport. If they are fully to the right, top, or bottom of the viewport, the Bézier does not impact pixel coverage or coarse winding at all, and can be ignored. If it is fully to the left, it does impact pixel coverage and coarse winding, but only the element's start and endpoint y-values matter, not the exact shape, meaning we can just yield a line rather than finely flattening. If more or less everything ends up to be in the viewport, the additional calculation is wasted and increases flattening time by ~3%. If geometry ends up culled, flattening and tiling times can be reduced significantly, but this is of course workload-dependent. The following two Ghostscript Tiger's have their viewboxes reduced to `50 50 100 100` and `90 90 20 20`, down from `0 0 200 200`. Their flattening time is reduce by 52% and 90% respectively, and their tiling time by 22% and 60%. Flattening timings: ``` flatten/Ghostscript_Tiger time: [209.94 µs 210.21 µs 210.51 µs] change: [+2.6850% +3.1753% +3.6309%] (p = 0.00 < 0.05) Performance has regressed. Found 5 outliers among 100 measurements (5.00%) 4 (4.00%) high mild 1 (1.00%) high severe flatten/Ghostscript_Tiger-viewboxed time: [97.189 µs 97.287 µs 97.399 µs] change: [-52.787% -52.650% -52.514%] (p = 0.00 < 0.05) Performance has improved. Found 8 outliers among 100 measurements (8.00%) 6 (6.00%) high mild 2 (2.00%) high severe flatten/Ghostscript_Tiger-viewboxed-extreme time: [19.722 µs 19.741 µs 19.761 µs] change: [-90.311% -90.280% -90.255%] (p = 0.00 < 0.05) Performance has improved. Found 8 outliers among 100 measurements (8.00%) 7 (7.00%) high mild 1 (1.00%) high severe flatten/paris-30k time: [12.740 ms 12.764 ms 12.788 ms] change: [+2.6014% +3.3631% +4.0837%] (p = 0.00 < 0.05) Performance has regressed. Found 3 outliers among 100 measurements (3.00%) 3 (3.00%) high mild ``` Tiling timings: ``` tile/Ghostscript_Tiger time: [175.39 µs 175.79 µs 176.28 µs] change: [-0.4403% -0.0016% +0.4400%] (p = 1.00 > 0.05) No change in performance detected. Found 1 outliers among 50 measurements (2.00%) 1 (2.00%) high mild tile/Ghostscript_Tiger-viewboxed time: [78.932 µs 79.147 µs 79.409 µs] change: [-23.209% -22.803% -22.369%] (p = 0.00 < 0.05) Performance has improved. Found 5 outliers among 50 measurements (10.00%) 1 (2.00%) high mild 4 (8.00%) high severe tile/Ghostscript_Tiger-viewboxed-extreme time: [13.378 µs 13.390 µs 13.405 µs] change: [-60.417% -60.306% -60.199%] (p = 0.00 < 0.05) Performance has improved. Found 6 outliers among 50 measurements (12.00%) 2 (4.00%) high mild 4 (8.00%) high severe tile/paris-30k time: [20.970 ms 21.001 ms 21.034 ms] change: [-0.4881% -0.2397% +0.0108%] (p = 0.07 > 0.05) No change in performance detected. Found 1 outliers among 50 measurements (2.00%) 1 (2.00%) high mild ``` --- sparse_strips/vello_bench/src/data.rs | 4 ++ sparse_strips/vello_bench/src/flatten.rs | 4 ++ sparse_strips/vello_common/src/flatten.rs | 41 +++++++++--- .../vello_common/src/flatten_simd.rs | 63 +++++++++++++++---- .../vello_common/src/strip_generator.rs | 4 ++ sparse_strips/vello_common/src/tile.rs | 7 ++- sparse_strips/vello_toy/src/debug.rs | 4 ++ 7 files changed, 104 insertions(+), 23 deletions(-) diff --git a/sparse_strips/vello_bench/src/data.rs b/sparse_strips/vello_bench/src/data.rs index 237a4168f..5a3b1c83b 100644 --- a/sparse_strips/vello_bench/src/data.rs +++ b/sparse_strips/vello_bench/src/data.rs @@ -86,6 +86,8 @@ impl DataItem { path.transform, &mut temp_buf, &mut FlattenCtx::default(), + self.width, + self.height, ); line_buf.extend(&temp_buf); } @@ -103,6 +105,8 @@ impl DataItem { &mut temp_buf, &mut FlattenCtx::default(), &mut StrokeCtx::default(), + self.width, + self.height, ); line_buf.extend(&temp_buf); } diff --git a/sparse_strips/vello_bench/src/flatten.rs b/sparse_strips/vello_bench/src/flatten.rs index 2209a9080..4a67f8185 100644 --- a/sparse_strips/vello_bench/src/flatten.rs +++ b/sparse_strips/vello_bench/src/flatten.rs @@ -33,6 +33,8 @@ pub fn flatten(c: &mut Criterion) { path.transform, &mut temp_buf, &mut flatten_ctx, + $item.width, + $item.height, ); line_buf.extend(&temp_buf); } @@ -44,6 +46,8 @@ pub fn flatten(c: &mut Criterion) { Affine::IDENTITY, &mut temp_buf, &mut flatten_ctx, + $item.width, + $item.height, ); line_buf.extend(&temp_buf); } diff --git a/sparse_strips/vello_common/src/flatten.rs b/sparse_strips/vello_common/src/flatten.rs index 9451a48a4..dd161cfa1 100644 --- a/sparse_strips/vello_common/src/flatten.rs +++ b/sparse_strips/vello_common/src/flatten.rs @@ -36,6 +36,16 @@ impl Point { } } +impl From for Point { + #[inline(always)] + fn from(value: crate::kurbo::Point) -> Self { + Self { + x: value.x as f32, + y: value.y as f32, + } + } +} + impl core::ops::Add for Point { type Output = Self; @@ -83,8 +93,10 @@ pub fn fill( affine: Affine, line_buf: &mut Vec, ctx: &mut FlattenCtx, + width: u16, + height: u16, ) { - dispatch!(level, simd => fill_impl(simd, path, affine, line_buf, ctx)); + dispatch!(level, simd => fill_impl(simd, path, affine, line_buf, ctx, width, height)); } /// Flatten a filled bezier path into line segments. @@ -95,12 +107,10 @@ pub fn fill_impl( affine: Affine, line_buf: &mut Vec, flatten_ctx: &mut FlattenCtx, + width: u16, + height: u16, ) { line_buf.clear(); - let iter = path.into_iter().map( - #[inline(always)] - |el| affine * el, - ); let mut lb = FlattenerCallback { line_buf, @@ -109,7 +119,7 @@ pub fn fill_impl( is_nan: false, }; - crate::flatten_simd::flatten(simd, iter, &mut lb, flatten_ctx); + crate::flatten_simd::flatten(simd, affine, path, &mut lb, flatten_ctx, width, height); // A path that contains NaN is ill-defined, so ignore it. if lb.is_nan { @@ -127,6 +137,8 @@ pub fn stroke( line_buf: &mut Vec, flatten_ctx: &mut FlattenCtx, stroke_ctx: &mut StrokeCtx, + width: u16, + height: u16, ) { // TODO: Temporary hack to ensure that strokes are scaled properly by the transform. let tolerance = TOL @@ -136,7 +148,15 @@ pub fn stroke( .max(1.); expand_stroke(path, style, tolerance, stroke_ctx); - fill(level, stroke_ctx.output(), affine, line_buf, flatten_ctx); + fill( + level, + stroke_ctx.output(), + affine, + line_buf, + flatten_ctx, + width, + height, + ); } /// Expand a stroked path to a filled path. @@ -163,13 +183,14 @@ impl Callback for FlattenerCallback<'_> { LinePathEl::MoveTo(p) => { self.is_nan |= p.is_nan(); - self.start = Point::new(p.x as f32, p.y as f32); - self.p0 = self.start; + let p = p.into(); + self.start = p; + self.p0 = p; } LinePathEl::LineTo(p) => { self.is_nan |= p.is_nan(); - let p = Point::new(p.x as f32, p.y as f32); + let p = p.into(); self.line_buf.push(Line::new(self.p0, p)); self.p0 = p; } diff --git a/sparse_strips/vello_common/src/flatten_simd.rs b/sparse_strips/vello_common/src/flatten_simd.rs index 10f447d05..8d5c779e2 100644 --- a/sparse_strips/vello_common/src/flatten_simd.rs +++ b/sparse_strips/vello_common/src/flatten_simd.rs @@ -8,7 +8,7 @@ use crate::flatten::{SQRT_TOL, TOL, TOL_2}; #[cfg(not(feature = "std"))] use crate::kurbo::common::FloatFuncs as _; -use crate::kurbo::{CubicBez, Line, ParamCurve, ParamCurveNearest, PathEl, Point, QuadBez}; +use crate::kurbo::{Affine, CubicBez, Line, ParamCurve, ParamCurveNearest, PathEl, Point, QuadBez}; use alloc::vec::Vec; use bytemuck::{Pod, Zeroable}; use fearless_simd::*; @@ -43,17 +43,25 @@ pub(crate) trait Callback { #[inline(always)] pub(crate) fn flatten( simd: S, + affine: Affine, path: impl IntoIterator, callback: &mut impl Callback, flatten_ctx: &mut FlattenCtx, + width: u16, + height: u16, ) { flatten_ctx.flattened_cubics.clear(); + let width = width as f64; + let height = height as f64; + let mut closed = true; let mut start_pt = Point::ZERO; let mut last_pt = Point::ZERO; for el in path { + let el = affine * el; + match el { PathEl::MoveTo(p) => { if !closed && last_pt != start_pt { @@ -72,11 +80,26 @@ pub(crate) fn flatten( PathEl::QuadTo(p1, p2) => { debug_assert!(!closed, "Expected a `MoveTo` before a `QuadTo`"); let p0 = last_pt; - // An upper bound on the shortest distance of any point on the quadratic Bezier - // curve to the line segment [p0, p2] is 1/2 of the control-point-to-line-segment + let line = Line::new(p0, p2); + // If the quadratic Bézier is fully to the right, top, or bottom of the viewport, + // it does not impact pixel coverage or winding. We can ignore it. The following + // checks that conservatively by checking whether the bounding box of the Bézier's + // control points is fully to the right, top, or bottom of the viewport. + if [p0, p1, p2].into_iter().all(|p| p.x > width) + || [p0, p1, p2].into_iter().all(|p| p.y < 0.) + || [p0, p1, p2].into_iter().all(|p| p.y > height) + { + callback.callback(LinePathEl::MoveTo(p2)); + } + // The following checks two things. First, if the quadratic Bézier is fully to the + // left of the viewport, it may affect pixel coverage and winding, but its exact + // shape does not matter. It can be emitted as a line segment [p0, p2]. + // + // Second, an upper bound on the shortest distance of any point on the quadratic + // Bézier curve to the line segment [p0, p2] is 1/2 of the control-point-to-line-segment // distance. // - // The derivation is similar to that for the cubic Bezier (see below). In + // The derivation is similar to that for the cubic Bézier (see below). In // short: // // q(t) = B0(t) p0 + B1(t) p1 + B2(t) p2 @@ -88,8 +111,9 @@ pub(crate) fn flatten( // // The following takes the square to elide the square root of the Euclidean // distance. - let line = Line::new(p0, p2); - if line.nearest(p1, 0.).distance_sq <= 4. * TOL_2 { + else if [p0, p1, p2].into_iter().all(|p| p.x < 0.) + || line.nearest(p1, 0.).distance_sq <= 4. * TOL_2 + { callback.callback(LinePathEl::LineTo(p2)); } else { let q = QuadBez::new(p0, p1, p2); @@ -109,7 +133,22 @@ pub(crate) fn flatten( PathEl::CurveTo(p1, p2, p3) => { debug_assert!(!closed, "Expected a `MoveTo` before a `CurveTo`"); let p0 = last_pt; - // An upper bound on the shortest distance of any point on the cubic Bezier + let line = Line::new(p0, p3); + // If the cubic Bézier is fully to the right, top, or bottom of the viewport, it + // does not impact pixel coverage or winding. We can ignore it. The following + // checks that conservatively by checking whether the bounding box of the Bézier's + // control points is fully to the right, top, or bottom of the viewport. + if [p0, p1, p2, p3].into_iter().all(|p| p.x > width) + || [p0, p1, p2, p3].into_iter().all(|p| p.y < 0.) + || [p0, p1, p2, p3].into_iter().all(|p| p.y > height) + { + callback.callback(LinePathEl::MoveTo(p3)); + } + // The following checks two things. First, if the cubic Bézier is fully to the + // left of the viewport, it may affect pixel coverage and winding, but its exact + // shape does not matter. It can be emitted as a line segment [p0, p3]. + // + // Second, an upper bound on the shortest distance of any point on the cubic Bézier // curve to the line segment [p0, p3] is 3/4 of the maximum of the // control-point-to-line-segment distances. // @@ -128,11 +167,11 @@ pub(crate) fn flatten( // // The following takes the square to elide the square root of the Euclidean // distance. - let line = Line::new(p0, p3); - if f64::max( - line.nearest(p1, 0.).distance_sq, - line.nearest(p2, 0.).distance_sq, - ) <= 16. / 9. * TOL_2 + else if [p0, p1, p2].into_iter().all(|p| p.x < 0.) + || f64::max( + line.nearest(p1, 0.).distance_sq, + line.nearest(p2, 0.).distance_sq, + ) <= 16. / 9. * TOL_2 { callback.callback(LinePathEl::LineTo(p3)); } else { diff --git a/sparse_strips/vello_common/src/strip_generator.rs b/sparse_strips/vello_common/src/strip_generator.rs index 24414310b..3c519dfcb 100644 --- a/sparse_strips/vello_common/src/strip_generator.rs +++ b/sparse_strips/vello_common/src/strip_generator.rs @@ -102,6 +102,8 @@ impl StripGenerator { transform, &mut self.line_buf, &mut self.flatten_ctx, + self.width, + self.height, ); self.generate_with_clip(aliasing_threshold, strip_storage, fill_rule, clip_path); @@ -125,6 +127,8 @@ impl StripGenerator { &mut self.line_buf, &mut self.flatten_ctx, &mut self.stroke_ctx, + self.width, + self.height, ); self.generate_with_clip(aliasing_threshold, strip_storage, Fill::NonZero, clip_path); } diff --git a/sparse_strips/vello_common/src/tile.rs b/sparse_strips/vello_common/src/tile.rs index b93a45bb2..a58491107 100644 --- a/sparse_strips/vello_common/src/tile.rs +++ b/sparse_strips/vello_common/src/tile.rs @@ -1251,6 +1251,9 @@ mod tests { #[test] fn vertical_path_on_the_right_of_viewport() { + const VIEWPORT_WIDTH: u16 = 10; + const VIEWPORT_HEIGHT: u16 = 10; + let path = BezPath::from_svg("M261,0 L78848,0 L78848,4 L261,4 Z").unwrap(); let mut line_buf = vec![]; fill( @@ -1259,10 +1262,12 @@ mod tests { Affine::IDENTITY, &mut line_buf, &mut FlattenCtx::default(), + VIEWPORT_WIDTH, + VIEWPORT_HEIGHT, ); let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback())); - tiles.assert_tiles_match(&line_buf, 10, 10, &[]); + tiles.assert_tiles_match(&line_buf, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, &[]); } #[test] diff --git a/sparse_strips/vello_toy/src/debug.rs b/sparse_strips/vello_toy/src/debug.rs index ac1df2643..a373b3093 100644 --- a/sparse_strips/vello_toy/src/debug.rs +++ b/sparse_strips/vello_toy/src/debug.rs @@ -49,6 +49,8 @@ fn main() { Affine::IDENTITY, &mut line_buf, &mut FlattenCtx::default(), + args.width, + args.height, ); } else { let stroke = Stroke { @@ -66,6 +68,8 @@ fn main() { &mut line_buf, &mut FlattenCtx::default(), &mut StrokeCtx::default(), + args.width, + args.height, ); } } From 12608f513d0947e7ddc582a960029d5fa7afb194 Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Mon, 5 Jan 2026 15:21:16 +0100 Subject: [PATCH 2/6] Add docs about culling --- sparse_strips/vello_common/src/flatten.rs | 95 ++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/sparse_strips/vello_common/src/flatten.rs b/sparse_strips/vello_common/src/flatten.rs index dd161cfa1..3639be9e3 100644 --- a/sparse_strips/vello_common/src/flatten.rs +++ b/sparse_strips/vello_common/src/flatten.rs @@ -86,7 +86,94 @@ impl Line { } } -/// Flatten a filled bezier path into line segments. +/// Flatten a filled Bézier path into line segments. +/// +/// # Open subpaths and culling +/// +/// Open subpaths in the input path get closed by connecting the last endpoint in the subpath to +/// the starting point. The output lines in `line_buf` describe the flattened path, but these lines +/// may describe open subpaths, as some path elements may have been culled. +/// +/// For example, consider the following, where the box describes the viewport, a path is marked by +/// `*`, and the region to be filled in the viewport is shaded. For ease of drawing the ASCII art, +/// the path elements are all lines ([`PathEl::LineTo`]), but the same also holds for Bézier path +/// elements. +/// +/// ```text +/// ---> winding scan direction +/// +/// * * * * * +/// * * +/// ---------- * ----- * +/// | *░░░░░░░| * +/// | *░░░░░░░░░| * +/// | *░░░░░░░░░░░| * +/// | *░░░░░░░░░░░░░| * +/// |*░░░░░░░░░░░░░░░| * +/// *|░░░░░░░░░░░░░░░░| * +/// * |░░░░░░░░░░░░░░░░| * +/// * |░░░░░░░░░░░░░░░░| * +/// * ------------------ * +/// * * +/// * * +/// * * +/// * * +/// * * +/// * * +/// * * +/// * * * * * * * * * * * * * * +/// ``` +/// +/// Because the winding scan direction is from left to right, only the left-of-viewport and +/// diagonal lines matter in later stages of rendering for the winding number and pixel coverage. +/// The other three lines can be culled. +/// +/// ```text +/// * +/// * +/// ---------- * ----- +/// | *░░░░░░░| +/// | *░░░░░░░░░| +/// | *░░░░░░░░░░░| +/// | *░░░░░░░░░░░░░| +/// |*░░░░░░░░░░░░░░░| +/// *|░░░░░░░░░░░░░░░░| +/// * |░░░░░░░░░░░░░░░░| +/// * |░░░░░░░░░░░░░░░░| +/// * ------------------ +/// * +/// * +/// * +/// * +/// * +/// * +/// * +/// ``` +/// +/// It is important to keep these flattened subpaths open after culling, as closing the subpaths +/// might yield different geometry like the following. +/// +/// ```text +/// * +/// ** +/// ---------- * *---- +/// | *░░* | +/// | *░░░* | +/// | *░░░░* | +/// | *░░░░░* | +/// |*░░░░░░* | +/// *|░░░░░░* | +/// * |░░░░░* | +/// * |░░░░* | +/// * --- * ------------ +/// * * +/// * * +/// * * +/// * * +/// * * +/// ** +/// * +/// ``` pub fn fill( level: Level, path: impl IntoIterator, @@ -100,6 +187,8 @@ pub fn fill( } /// Flatten a filled bezier path into line segments. +/// +/// See the note about open subpaths and culling on [`fill`]. #[inline(always)] pub fn fill_impl( simd: S, @@ -128,7 +217,9 @@ pub fn fill_impl( line_buf.clear(); } } -/// Flatten a stroked bezier path into line segments. +/// Flatten a stroked Bézier path into line segments. +/// +/// See the note about open subpaths and culling on [`fill`]. pub fn stroke( level: Level, path: impl IntoIterator, From 9d47ff06be0e3b326ef4c08823d807af493606ae Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Thu, 15 Jan 2026 15:24:52 +0100 Subject: [PATCH 3/6] Changelog --- sparse_strips/vello_common/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sparse_strips/vello_common/CHANGELOG.md b/sparse_strips/vello_common/CHANGELOG.md index 6de85132f..a8ddae1d2 100644 --- a/sparse_strips/vello_common/CHANGELOG.md +++ b/sparse_strips/vello_common/CHANGELOG.md @@ -19,6 +19,9 @@ This release has an [MSRV][] of 1.88. - Improved Bézier flattening performance by catching more Béziers whose chords are immediately within rendering tolerance. ([#1216][] by [@tomcur][]) - Significantly improved rendering performance of scenes including blend layers by ensuring no commands are generated for wide tiles without layer content. ([#1399][] by [@tomcur][]) +- Improved flattening and tiling performance by culling out-of-viewport Béziers before flattening. ([#1341][] by [@tomcur][]) + This drops all out-of-viewport geometry that does not impact winding or pixel coverage (i.e., geometry above, to the right, or below the viewport). + For geometry that is to the left of the viewport (impacting winding), this skips flattening by directly yielding the Bézier's chord. ### Fixed @@ -121,6 +124,7 @@ See also the [vello_cpu 0.0.1](../vello_cpu/CHANGELOG.md#001---2025-05-10) relea [#1329]: https://github.com/linebender/vello/pull/1329 [#1336]: https://github.com/linebender/vello/pull/1327 [#1338]: https://github.com/linebender/vello/pull/1327 +[#1341]: https://github.com/linebender/vello/pull/1341 [#1349]: https://github.com/linebender/vello/pull/1349 [#1353]: https://github.com/linebender/vello/pull/1353 [#1354]: https://github.com/linebender/vello/pull/1354 From 2d1c6722dc841266980702e1ff24b69d328a2832 Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Fri, 20 Feb 2026 01:24:50 +0100 Subject: [PATCH 4/6] Clippy --- sparse_strips/vello_common/src/flatten.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sparse_strips/vello_common/src/flatten.rs b/sparse_strips/vello_common/src/flatten.rs index 3639be9e3..1ae41c80b 100644 --- a/sparse_strips/vello_common/src/flatten.rs +++ b/sparse_strips/vello_common/src/flatten.rs @@ -36,9 +36,9 @@ impl Point { } } -impl From for Point { +impl From for Point { #[inline(always)] - fn from(value: crate::kurbo::Point) -> Self { + fn from(value: kurbo::Point) -> Self { Self { x: value.x as f32, y: value.y as f32, From 8e16391973a7430fa11545257c5573f5c829b2cb Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Fri, 20 Feb 2026 01:46:16 +0100 Subject: [PATCH 5/6] Fix missing cubic endpoint --- sparse_strips/vello_common/src/flatten_simd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sparse_strips/vello_common/src/flatten_simd.rs b/sparse_strips/vello_common/src/flatten_simd.rs index 8d5c779e2..53d1aefc3 100644 --- a/sparse_strips/vello_common/src/flatten_simd.rs +++ b/sparse_strips/vello_common/src/flatten_simd.rs @@ -167,7 +167,7 @@ pub(crate) fn flatten( // // The following takes the square to elide the square root of the Euclidean // distance. - else if [p0, p1, p2].into_iter().all(|p| p.x < 0.) + else if [p0, p1, p2, p3].into_iter().all(|p| p.x < 0.) || f64::max( line.nearest(p1, 0.).distance_sq, line.nearest(p2, 0.).distance_sq, From ee66818e8d2a06a6c18cb370dd4549c253d29d2b Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Fri, 20 Feb 2026 13:59:07 +0100 Subject: [PATCH 6/6] Undo iter change --- sparse_strips/vello_common/src/flatten.rs | 6 +++++- sparse_strips/vello_common/src/flatten_simd.rs | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sparse_strips/vello_common/src/flatten.rs b/sparse_strips/vello_common/src/flatten.rs index 1ae41c80b..f9707635d 100644 --- a/sparse_strips/vello_common/src/flatten.rs +++ b/sparse_strips/vello_common/src/flatten.rs @@ -200,6 +200,10 @@ pub fn fill_impl( height: u16, ) { line_buf.clear(); + let iter = path.into_iter().map( + #[inline(always)] + |el| affine * el, + ); let mut lb = FlattenerCallback { line_buf, @@ -208,7 +212,7 @@ pub fn fill_impl( is_nan: false, }; - crate::flatten_simd::flatten(simd, affine, path, &mut lb, flatten_ctx, width, height); + crate::flatten_simd::flatten(simd, iter, &mut lb, flatten_ctx, width, height); // A path that contains NaN is ill-defined, so ignore it. if lb.is_nan { diff --git a/sparse_strips/vello_common/src/flatten_simd.rs b/sparse_strips/vello_common/src/flatten_simd.rs index 53d1aefc3..32e2e64b1 100644 --- a/sparse_strips/vello_common/src/flatten_simd.rs +++ b/sparse_strips/vello_common/src/flatten_simd.rs @@ -8,7 +8,7 @@ use crate::flatten::{SQRT_TOL, TOL, TOL_2}; #[cfg(not(feature = "std"))] use crate::kurbo::common::FloatFuncs as _; -use crate::kurbo::{Affine, CubicBez, Line, ParamCurve, ParamCurveNearest, PathEl, Point, QuadBez}; +use crate::kurbo::{CubicBez, Line, ParamCurve, ParamCurveNearest, PathEl, Point, QuadBez}; use alloc::vec::Vec; use bytemuck::{Pod, Zeroable}; use fearless_simd::*; @@ -43,7 +43,6 @@ pub(crate) trait Callback { #[inline(always)] pub(crate) fn flatten( simd: S, - affine: Affine, path: impl IntoIterator, callback: &mut impl Callback, flatten_ctx: &mut FlattenCtx, @@ -60,8 +59,6 @@ pub(crate) fn flatten( let mut last_pt = Point::ZERO; for el in path { - let el = affine * el; - match el { PathEl::MoveTo(p) => { if !closed && last_pt != start_pt {