From 0a58f3c9598d5cf6d7a5b2ea160b1080671f5a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Sat, 14 Oct 2023 14:33:18 +0200 Subject: [PATCH 1/3] Fix shader compilation on Chrome --- shader/fine.wgsl | 12 ++++++------ shader/path_count.wgsl | 18 +++++++++--------- shader/path_count_setup.wgsl | 2 +- shader/path_tiling.wgsl | 12 ++++++------ shader/path_tiling_setup.wgsl | 2 +- src/shaders.rs | 6 +++--- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 5d500ad04..1cd0784ac 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -182,9 +182,9 @@ fn fill_path_ms(fill: CmdFill, wg_id: vec2, local_id: vec2) -> array= xy0.x; - let sign = select(-1.0, 1.0, is_positive_slope); - let xt0 = floor(xy0.x * sign); - let c = xy0.x * sign - xt0; + let x_sign = select(-1.0, 1.0, is_positive_slope); + let xt0 = floor(xy0.x * x_sign); + let c = xy0.x * x_sign - xt0; let y0i = floor(xy0.y); let ytop = y0i + 1.0; let b = min((dy * c + dx * (ytop - xy0.y)) * idxdy, ONE_MINUS_ULP); @@ -194,12 +194,12 @@ fn fill_path_ms(fill: CmdFill, wg_id: vec2, local_id: vec2) -> array, local_id: vec2) -> array> ((local_id.y & 7u) << 2u)) - 8u; diff --git a/shader/path_count.wgsl b/shader/path_count.wgsl index dabb6d193..fd92e833c 100644 --- a/shader/path_count.wgsl +++ b/shader/path_count.wgsl @@ -79,9 +79,9 @@ fn main( let idxdy = 1.0 / (dx + dy); var a = dx * idxdy; let is_positive_slope = s1.x >= s0.x; - let sign = select(-1.0, 1.0, is_positive_slope); - let xt0 = floor(s0.x * sign); - let c = s0.x * sign - xt0; + let x_sign = select(-1.0, 1.0, is_positive_slope); + let xt0 = floor(s0.x * x_sign); + let c = s0.x * x_sign - xt0; let y0 = floor(s0.y); let ytop = select(y0 + 1.0, ceil(s0.y), s0.y == s1.y); let b = min((dy * c + dx * (ytop - s0.y)) * idxdy, ONE_MINUS_ULP); @@ -89,7 +89,7 @@ fn main( if robust_err != 0.0 { a -= ROBUST_EPSILON * sign(robust_err); } - let x0 = xt0 * sign + select(-1.0, 0.0, is_positive_slope); + let x0 = xt0 * x_sign + select(-1.0, 0.0, is_positive_slope); let path = paths[line.path_ix]; let bbox = vec4(path.bbox); @@ -129,8 +129,8 @@ fn main( } else { let fudge = select(1.0, 0.0, is_positive_slope); if xmin < f32(bbox.x) { - var f = round((sign * (f32(bbox.x) - x0) - b + fudge) / a); - if (x0 + sign * floor(a * f + b) < f32(bbox.x)) == is_positive_slope { + var f = round((x_sign * (f32(bbox.x) - x0) - b + fudge) / a); + if (x0 + x_sign * floor(a * f + b) < f32(bbox.x)) == is_positive_slope { f += 1.0; } let ynext = i32(y0 + f - floor(a * f + b) + 1.0); @@ -149,8 +149,8 @@ fn main( } } if max(s0.x, s1.x) > f32(bbox.z) { - var f = round((sign * (f32(bbox.z) - x0) - b + fudge) / a); - if (x0 + sign * floor(a * f + b) < f32(bbox.z)) == is_positive_slope { + var f = round((x_sign * (f32(bbox.z) - x0) - b + fudge) / a); + if (x0 + x_sign * floor(a * f + b) < f32(bbox.z)) == is_positive_slope { f += 1.0; } if is_positive_slope { @@ -178,7 +178,7 @@ fn main( let z = floor(zf); // x, y are tile coordinates relative to render target let y = i32(y0 + f32(subix) - z); - let x = i32(x0 + sign * z); + let x = i32(x0 + x_sign * z); let base = i32(path.tiles) + (y - bbox.y) * stride - bbox.x; let top_edge = select(last_z == z, y0 == s0.y, subix == 0u); if top_edge && x + 1 < bbox.z { diff --git a/shader/path_count_setup.wgsl b/shader/path_count_setup.wgsl index 95af29c38..3715aa59b 100644 --- a/shader/path_count_setup.wgsl +++ b/shader/path_count_setup.wgsl @@ -5,7 +5,7 @@ #import bump @group(0) @binding(0) -var bump: BumpAllocators; +var bump: BumpAllocators; @group(0) @binding(1) var indirect: IndirectCount; diff --git a/shader/path_tiling.wgsl b/shader/path_tiling.wgsl index fad5d725e..1e5ea17a4 100644 --- a/shader/path_tiling.wgsl +++ b/shader/path_tiling.wgsl @@ -8,7 +8,7 @@ #import tile @group(0) @binding(0) -var bump: BumpAllocators; +var bump: BumpAllocators; @group(0) @binding(1) var seg_counts: array; @@ -62,9 +62,9 @@ fn main( let idxdy = 1.0 / (dx + dy); var a = dx * idxdy; let is_positive_slope = s1.x >= s0.x; - let sign = select(-1.0, 1.0, is_positive_slope); - let xt0 = floor(s0.x * sign); - let c = s0.x * sign - xt0; + let x_sign = select(-1.0, 1.0, is_positive_slope); + let xt0 = floor(s0.x * x_sign); + let c = s0.x * x_sign - xt0; let y0i = floor(s0.y); let ytop = select(y0i + 1.0, ceil(s0.y), s0.y == s1.y); let b = min((dy * c + dx * (ytop - s0.y)) * idxdy, ONE_MINUS_ULP); @@ -72,9 +72,9 @@ fn main( if robust_err != 0.0 { a -= ROBUST_EPSILON * sign(robust_err); } - let x0i = i32(xt0 * sign + 0.5 * (sign - 1.0)); + let x0i = i32(xt0 * x_sign + 0.5 * (x_sign - 1.0)); let z = floor(a * f32(seg_within_line) + b); - let x = x0i + i32(sign * z); + let x = x0i + i32(x_sign * z); let y = i32(y0i + f32(seg_within_line) - z); let path = paths[line.path_ix]; diff --git a/shader/path_tiling_setup.wgsl b/shader/path_tiling_setup.wgsl index 722be1914..466152a2b 100644 --- a/shader/path_tiling_setup.wgsl +++ b/shader/path_tiling_setup.wgsl @@ -5,7 +5,7 @@ #import bump @group(0) @binding(0) -var bump: BumpAllocators; +var bump: BumpAllocators; @group(0) @binding(1) var indirect: IndirectCount; diff --git a/src/shaders.rs b/src/shaders.rs index 668dafac4..91d56db97 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -249,7 +249,7 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result Result Date: Tue, 17 Oct 2023 00:06:29 -0400 Subject: [PATCH 2/3] [test_scenes] Tests scenes for strokes Added two new test scenes that exercise stroke styles and some edge conditions: 1. stroke_styles: Renders a combination of cap and join styles 2. tricky_strokes: Renders cubic strokes that demonstrate certain edge conditions, such as cusps and co-linear control points. Several of these tests are commented out as they trigger an infinite recursion in the kurbo curve fitting logic and crash the example. These should probably get copied as unit tests in kurbo as part of a fix for the infinite recursions. --- examples/scenes/src/test_scenes.rs | 170 ++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index a3e7707c8..b2e0b8cc6 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -1,5 +1,8 @@ +// Copyright 2022 The Vello authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet}; -use vello::kurbo::{Affine, BezPath, Cap, Ellipse, PathEl, Point, Rect, Stroke}; +use vello::kurbo::{Affine, BezPath, Cap, Ellipse, Join, PathEl, Point, Rect, Stroke}; use vello::peniko::*; use vello::*; @@ -31,6 +34,8 @@ pub fn test_scenes() -> SceneSet { scene!(splash_with_tiger(), "splash_with_tiger", false), scene!(crate::mmark::MMark::new(80_000), "mmark", false), scene!(funky_paths), + scene!(stroke_styles), + scene!(tricky_strokes), scene!(cardioid_and_friends), scene!(animated_text: animated), scene!(gradient_extend), @@ -91,6 +96,169 @@ fn funky_paths(sb: &mut SceneBuilder, _: &mut SceneParams) { ); } +fn stroke_styles(sb: &mut SceneBuilder, params: &mut SceneParams) { + use PathEl::*; + let colors = [ + Color::rgb8(140, 181, 236), + Color::rgb8(246, 236, 202), + Color::rgb8(201, 147, 206), + Color::rgb8(150, 195, 160), + ]; + let simple_stroke = [LineTo((100., 0.).into())]; + let join_stroke = [ + CurveTo((20., 0.).into(), (42.5, 5.).into(), (50., 25.).into()), + CurveTo((57.5, 5.).into(), (80., 0.).into(), (100., 0.).into()), + ]; + let cap_styles = [Cap::Butt, Cap::Square, Cap::Round]; + let join_styles = [Join::Bevel, Join::Miter, Join::Round]; + + // Simple strokes with cap combinations + let t = Affine::translate((60., 40.)) * Affine::scale(2.); + let mut y = 0.; + let mut color_idx = 0; + for start in cap_styles { + for end in cap_styles { + params.text.add( + sb, + None, + 12., + None, + Affine::translate((0., y)) * t, + &format!("Start cap: {:?}, End cap: {:?}", start, end), + ); + sb.stroke( + &Stroke::new(20.).with_start_cap(start).with_end_cap(end), + Affine::translate((0., y + 30.)) * t, + colors[color_idx], + None, + &simple_stroke, + ); + y += 180.; + color_idx = (color_idx + 1) % colors.len(); + } + } + + // Cap and join combinations + let t = Affine::translate((500., 0.)) * t; + y = 0.; + for cap in cap_styles { + for join in join_styles { + params.text.add( + sb, + None, + 12., + None, + Affine::translate((0., y)) * t, + &format!("Caps: {:?}, Joins: {:?}", cap, join), + ); + sb.stroke( + &Stroke::new(20.).with_caps(cap).with_join(join), + Affine::translate((0., y + 30.)) * t, + colors[color_idx], + None, + &join_stroke, + ); + y += 185.; + color_idx = (color_idx + 1) % colors.len(); + } + } +} + +// This test has been adapted from Skia's "trickycubicstrokes" GM slide which can be found at +// `github.com/google/skia/blob/0d4d11451c4f4e184305cbdbd67f6b3edfa4b0e3/gm/trickycubicstrokes.cpp` +fn tricky_strokes(sb: &mut SceneBuilder, _: &mut SceneParams) { + use PathEl::*; + let colors = [ + Color::rgb8(140, 181, 236), + Color::rgb8(246, 236, 202), + Color::rgb8(201, 147, 206), + Color::rgb8(150, 195, 160), + ]; + + const CELL_SIZE: f64 = 200.; + const STROKE_WIDTH: f64 = 30.; + const NUM_COLS: usize = 5; + + fn stroke_bounds(pts: &[(f64, f64); 4]) -> Rect { + use kurbo::{CubicBez, Shape}; + CubicBez::new(pts[0], pts[1], pts[2], pts[3]) + .bounding_box() + .inflate(STROKE_WIDTH, STROKE_WIDTH) + } + + fn map_rect_to_rect(src: &Rect, dst: &Rect) -> (Affine, f64) { + let (scale, x_larger) = { + let sx = dst.width() / src.width(); + let sy = dst.height() / src.height(); + (sx.min(sy), sx > sy) + }; + let tx = dst.x0 - src.x0 * scale; + let ty = dst.y0 - src.y0 * scale; + let (tx, ty) = if x_larger { + (tx + 0.5 * (dst.width() - src.width() * scale), ty) + } else { + (tx, ty + 0.5 * (dst.height() - src.height() * scale)) + }; + (Affine::new([scale, 0.0, 0.0, scale, tx, ty]), scale) + } + + let tricky_cubics = [ + [(122., 737.), (348., 553.), (403., 761.), (400., 760.)], + [(244., 520.), (244., 518.), (1141., 634.), (394., 688.)], + [(550., 194.), (138., 130.), (1035., 246.), (288., 300.)], + [(226., 733.), (556., 779.), (-43., 471.), (348., 683.)], + [(268., 204.), (492., 304.), (352., 23.), (433., 412.)], + [(172., 480.), (396., 580.), (256., 299.), (338., 677.)], + [(731., 340.), (318., 252.), (1026., -64.), (367., 265.)], + [(475., 708.), (62., 620.), (770., 304.), (220., 659.)], + [(0., 0.), (128., 128.), (128., 0.), (0., 128.)], // Perfect cusp + [(0., 0.01), (128., 127.999), (128., 0.01), (0., 127.99)], // Near-cusp + ]; + + // FIXME: The following curves all cause a crash due to a stack overflow following an + // infinite recursion in `kurbo::fit::fit_to_bezpath_rec` which gets called by + // `SceneBuilder::stroke` below. Disabling these tests until kurbo handles these + // gracefully. Move these into `tricky_cubics` above once they are fixed. + let _broken_cubics = [ + [(0., -0.01), (128., 128.001), (128., -0.01), (0., 128.001)], // Near-cusp + [(0., 0.), (0., -10.), (0., -10.), (0., 10.)], // Flat line with 180 + [(10., 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s + [(39., -39.), (40., -40.), (40., -40.), (0., 0.)], // Flat diagonal with 180 + [(40., 40.), (0., 0.), (200., 200.), (0., 0.)], // Diag w/ an internal 180 + [(0., 0.), (1e-2, 0.), (-1e-2, 0.), (0., 0.)], // Circle + // Flat line with no turns: + [ + (400.75, 100.05), + (400.75, 100.05), + (100.05, 300.95), + (100.05, 300.95), + ], + [(0.5, 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s + [(10., 0.), (0., 0.), (10., 0.), (10., 0.)], // Flat line with a 180 + ]; + let mut color_idx = 0; + for (i, cubic) in tricky_cubics.into_iter().enumerate() { + let x = (i % NUM_COLS) as f64 * CELL_SIZE; + let y = (i / NUM_COLS) as f64 * CELL_SIZE; + let cell = Rect::new(x, y, x + CELL_SIZE, y + CELL_SIZE); + let bounds = stroke_bounds(&cubic); + let (t, s) = map_rect_to_rect(&bounds, &cell); + sb.stroke( + &Stroke::new(STROKE_WIDTH / s) + .with_caps(Cap::Butt) + .with_join(Join::Miter), + t, + colors[color_idx], + None, + &[ + MoveTo(cubic[0].into()), + CurveTo(cubic[1].into(), cubic[2].into(), cubic[3].into()), + ], + ); + color_idx = (color_idx + 1) % colors.len(); + } +} + fn cardioid_and_friends(sb: &mut SceneBuilder, _: &mut SceneParams) { render_cardioid(sb); render_clip_test(sb); From d538f6511e2561bf9809bdef3c61b2629635a43d Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Tue, 17 Oct 2023 23:04:12 -0400 Subject: [PATCH 3/3] [test_scenes] Add stroke test cases for miter limit --- examples/scenes/src/test_scenes.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index b2e0b8cc6..982076f29 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -109,8 +109,10 @@ fn stroke_styles(sb: &mut SceneBuilder, params: &mut SceneParams) { CurveTo((20., 0.).into(), (42.5, 5.).into(), (50., 25.).into()), CurveTo((57.5, 5.).into(), (80., 0.).into(), (100., 0.).into()), ]; + let miter_stroke = [LineTo((90., 21.).into()), LineTo((0., 42.).into())]; let cap_styles = [Cap::Butt, Cap::Square, Cap::Round]; let join_styles = [Join::Bevel, Join::Miter, Join::Round]; + let miter_limits = [4., 5., 0.1, 10.]; // Simple strokes with cap combinations let t = Affine::translate((60., 40.)) * Affine::scale(2.); @@ -162,6 +164,32 @@ fn stroke_styles(sb: &mut SceneBuilder, params: &mut SceneParams) { color_idx = (color_idx + 1) % colors.len(); } } + + // Miter limit + let t = Affine::translate((500., 0.)) * t; + y = 0.; + for ml in miter_limits { + params.text.add( + sb, + None, + 12., + None, + Affine::translate((0., y)) * t, + &format!("Miter limit: {}", ml), + ); + sb.stroke( + &Stroke::new(10.) + .with_caps(Cap::Butt) + .with_join(Join::Miter) + .with_miter_limit(ml), + Affine::translate((0., y + 30.)) * t, + colors[color_idx], + None, + &miter_stroke, + ); + y += 180.; + color_idx = (color_idx + 1) % colors.len(); + } } // This test has been adapted from Skia's "trickycubicstrokes" GM slide which can be found at