diff --git a/examples/scenes/src/mmark.rs b/examples/scenes/src/mmark.rs new file mode 100644 index 0000000..dc5ce32 --- /dev/null +++ b/examples/scenes/src/mmark.rs @@ -0,0 +1,191 @@ +//! A benchmark based on MotionMark 1.2's path benchmark. + +use rand::{seq::SliceRandom, Rng}; +use vello::peniko::Color; +use vello::{ + kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez}, + peniko::Stroke, + SceneBuilder, +}; + +use crate::{SceneParams, TestScene}; + +const WIDTH: usize = 1600; +const HEIGHT: usize = 900; + +const GRID_WIDTH: i64 = 80; +const GRID_HEIGHT: i64 = 40; + +pub struct MMark { + elements: Vec, +} + +struct Element { + seg: PathSeg, + color: Color, + width: f64, + is_split: bool, + grid_point: GridPoint, +} + +#[derive(Clone, Copy)] +struct GridPoint((i64, i64)); + +impl MMark { + pub fn new(n: usize) -> MMark { + let mut result = MMark { elements: vec![] }; + result.resize(n); + result + } + + fn resize(&mut self, n: usize) { + let old_n = self.elements.len(); + if n < old_n { + self.elements.truncate(n) + } else if n > old_n { + let mut last = self + .elements + .last() + .map(|e| e.grid_point) + .unwrap_or(GridPoint((GRID_WIDTH / 2, GRID_HEIGHT / 2))); + self.elements.extend((old_n..n).map(|_| { + let element = Element::new_rand(last); + last = element.grid_point; + element + })); + } + } +} + +impl TestScene for MMark { + fn render(&mut self, sb: &mut SceneBuilder, params: &mut SceneParams) { + let c = params.complexity; + let n = if c < 10 { + (c + 1) * 1000 + } else { + ((c - 8) * 10000).min(120_000) + }; + self.resize(n); + let mut rng = rand::thread_rng(); + let mut path = BezPath::new(); + let len = self.elements.len(); + for (i, element) in self.elements.iter_mut().enumerate() { + if path.is_empty() { + path.move_to(element.seg.start()); + } + match element.seg { + PathSeg::Line(l) => path.line_to(l.p1), + PathSeg::Quad(q) => path.quad_to(q.p1, q.p2), + PathSeg::Cubic(c) => path.curve_to(c.p1, c.p2, c.p3), + } + if element.is_split || i == len { + // This gets color and width from the last element, original + // gets it from the first, but this should not matter. + sb.stroke( + &Stroke::new(element.width as f32), + Affine::IDENTITY, + element.color, + None, + &path, + ); + path.truncate(0); // Should have clear method, to avoid allocations. + } + if rng.gen::() > 0.995 { + element.is_split ^= true; + } + } + let label = format!("mmark test: {} path elements (up/down to adjust)", n); + params.text.add( + sb, + None, + 40.0, + None, + Affine::translate((100.0, 1100.0)), + &label, + ); + } +} + +const COLORS: &[Color] = &[ + Color::rgb8(0x10, 0x10, 0x10), + Color::rgb8(0x80, 0x80, 0x80), + Color::rgb8(0xc0, 0xc0, 0xc0), + Color::rgb8(0x10, 0x10, 0x10), + Color::rgb8(0x80, 0x80, 0x80), + Color::rgb8(0xc0, 0xc0, 0xc0), + Color::rgb8(0xe0, 0x10, 0x40), +]; + +impl Element { + fn new_rand(last: GridPoint) -> Element { + let mut rng = rand::thread_rng(); + let seg_type = rng.gen_range(0..4); + let next = GridPoint::random_point(last); + let (grid_point, seg) = if seg_type < 2 { + ( + next, + PathSeg::Line(Line::new(last.coordinate(), next.coordinate())), + ) + } else if seg_type < 3 { + let p2 = GridPoint::random_point(next); + ( + p2, + PathSeg::Quad(QuadBez::new( + last.coordinate(), + next.coordinate(), + p2.coordinate(), + )), + ) + } else { + let p2 = GridPoint::random_point(next); + let p3 = GridPoint::random_point(next); + ( + p3, + PathSeg::Cubic(CubicBez::new( + last.coordinate(), + next.coordinate(), + p2.coordinate(), + p3.coordinate(), + )), + ) + }; + let color = COLORS.choose(&mut rng).unwrap().clone(); + let width = rng.gen::().powi(5) * 20.0 + 1.0; + let is_split = rng.gen(); + Element { + seg, + color, + width, + is_split, + grid_point, + } + } +} + +const OFFSETS: &[(i64, i64)] = &[(-4, 0), (2, 0), (1, -2), (1, 2)]; + +impl GridPoint { + fn random_point(last: GridPoint) -> GridPoint { + let mut rng = rand::thread_rng(); + + let offset = OFFSETS.choose(&mut rng).unwrap(); + let mut x = last.0 .0 + offset.0; + if x < 0 || x > GRID_WIDTH { + x -= offset.0 * 2; + } + let mut y = last.0 .1 + offset.1; + if y < 0 || y > GRID_HEIGHT { + y -= offset.1 * 2; + } + GridPoint((x, y)) + } + + fn coordinate(&self) -> Point { + let scale_x = WIDTH as f64 / ((GRID_WIDTH + 1) as f64); + let scale_y = HEIGHT as f64 / ((GRID_HEIGHT + 1) as f64); + Point::new( + (self.0 .0 as f64 + 0.5) * scale_x, + 100.0 + (self.0 .1 as f64 + 0.5) * scale_y, + ) + } +}