Merge pull request #387 from Zoxc/wasm-fix

Fix shader compilation on Chrome
diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs
index 48d70d3..982076f 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, Ellipse, PathEl, Point, Rect, Stroke};
+use vello::kurbo::{Affine, BezPath, Cap, Ellipse, Join, PathEl, Point, Rect, Stroke};
 use vello::peniko::*;
 use vello::*;
 
@@ -13,35 +16,26 @@
         scene!($name: true)
     };
     ($name: ident: $animated: literal) => {
+        scene!($name, stringify!($name), $animated)
+    };
+    ($func:expr, $name: expr, $animated: literal) => {
         ExampleScene {
             config: SceneConfig {
                 animated: $animated,
-                name: stringify!($name).to_owned(),
+                name: $name.to_owned(),
             },
-            function: Box::new($name),
+            function: Box::new($func),
         }
     };
 }
 
 pub fn test_scenes() -> SceneSet {
-    let splash_scene = ExampleScene {
-        config: SceneConfig {
-            animated: false,
-            name: "splash_with_tiger".to_owned(),
-        },
-        function: Box::new(splash_with_tiger()),
-    };
-    let mmark_scene = ExampleScene {
-        config: SceneConfig {
-            animated: false,
-            name: "mmark".to_owned(),
-        },
-        function: Box::new(crate::mmark::MMark::new(80_000)),
-    };
     let scenes = vec![
-        splash_scene,
-        mmark_scene,
+        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),
@@ -52,6 +46,8 @@
         scene!(labyrinth),
         scene!(base_color_test: animated),
         scene!(clip_test: animated),
+        scene!(longpathdash(Cap::Butt), "longpathdash (butt caps)", false),
+        scene!(longpathdash(Cap::Round), "longpathdash (round caps)", false),
     ];
 
     SceneSet { scenes }
@@ -100,6 +96,197 @@
     );
 }
 
+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 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.);
+    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();
+        }
+    }
+
+    // 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
+// `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);
@@ -107,6 +294,48 @@
     //render_tiger(sb, false);
 }
 
+fn longpathdash(cap: Cap) -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
+    use std::f64::consts::PI;
+    use PathEl::*;
+    move |sb, _| {
+        let mut path = BezPath::new();
+        let mut x = 32;
+        while x < 256 {
+            let mut a: f64 = 0.0;
+            while a < PI * 2.0 {
+                let pts = [
+                    (256.0 + a.sin() * x as f64, 256.0 + a.cos() * x as f64),
+                    (
+                        256.0 + (a + PI / 3.0).sin() * (x + 64) as f64,
+                        256.0 + (a + PI / 3.0).cos() * (x + 64) as f64,
+                    ),
+                ];
+                path.push(MoveTo(pts[0].into()));
+                let mut i: f64 = 0.0;
+                while i < 1.0 {
+                    path.push(LineTo(
+                        (
+                            pts[0].0 * (1.0 - i) + pts[1].0 * i,
+                            pts[0].1 * (1.0 - i) + pts[1].1 * i,
+                        )
+                            .into(),
+                    ));
+                    i += 0.05;
+                }
+                a += PI * 0.01;
+            }
+            x += 16;
+        }
+        sb.stroke(
+            &Stroke::new(1.0).with_caps(cap).with_dashes(0.0, [1.0, 1.0]),
+            Affine::translate((50.0, 50.0)),
+            Color::rgb8(255, 255, 0),
+            None,
+            &path,
+        );
+    }
+}
+
 fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) {
     // Uses the static array address as a cache key for expedience. Real code
     // should use a better strategy.