Adaptation of MotionMark 1.2 path test
This is a quick and dirty adapation of the MotionMark 1.2 path test into
Rust. It is not at all a fair comparison, as the original is using miter
joins and butt caps, while this one is round/round (to use the SDF-based
stroke renderer). Nonetheless, it might be fun to play with.
diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs
index 26e095f..e3f52d6 100644
--- a/piet-gpu-hal/src/vulkan.rs
+++ b/piet-gpu-hal/src/vulkan.rs
@@ -409,7 +409,7 @@
.get_physical_device_surface_present_modes(device.physical_device, surface.surface)?;
// Can change to MAILBOX to force high frame rates.
- const PREFERRED_MODE: vk::PresentModeKHR = vk::PresentModeKHR::FIFO;
+ const PREFERRED_MODE: vk::PresentModeKHR = vk::PresentModeKHR::IMMEDIATE;
let present_mode = present_modes
.into_iter()
.find(|mode| *mode == PREFERRED_MODE)
diff --git a/piet-gpu/bin/winit.rs b/piet-gpu/bin/winit.rs
index ef41b31..7e3d4c6 100644
--- a/piet-gpu/bin/winit.rs
+++ b/piet-gpu/bin/winit.rs
@@ -17,6 +17,8 @@
const WIDTH: usize = 2048;
const HEIGHT: usize = 1536;
+const MMARK_SIZE: usize = 60_000;
+
fn main() -> Result<(), Error> {
let matches = App::new("piet-gpu test")
.arg(Arg::with_name("INPUT").index(1))
@@ -57,6 +59,8 @@
let mut submitted: [Option<SubmittedCmdBuf>; NUM_FRAMES] = Default::default();
let mut renderer = Renderer::new(&session, WIDTH, HEIGHT, NUM_FRAMES)?;
+ let mut mmark = piet_gpu::mmark::MMark::new(MMARK_SIZE);
+ let mut last_time = std::time::Instant::now();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; // `ControlFlow::Wait` if only re-render on event
@@ -75,12 +79,17 @@
}
Event::RedrawRequested(window_id) if window_id == window.id() => {
let frame_idx = current_frame % NUM_FRAMES;
+ let now = std::time::Instant::now();
+ let elapsed = now - last_time;
+ last_time = now;
+ let frame_s = format!("{:.1}ms", elapsed.as_micros() as f64 * 1e-3);
if let Some(submitted) = submitted[frame_idx].take() {
cmd_bufs[frame_idx] = submitted.wait().unwrap();
let ts = session.fetch_query_pool(&query_pools[frame_idx]).unwrap();
info_string = format!(
- "{:.3}ms :: e:{:.3}ms|alloc:{:.3}ms|cp:{:.3}ms|bd:{:.3}ms|bin:{:.3}ms|cr:{:.3}ms|r:{:.3}ms",
+ "{} {:.3}ms :: e:{:.3}ms|alloc:{:.3}ms|cp:{:.3}ms|bd:{:.3}ms|bin:{:.3}ms|cr:{:.3}ms|r:{:.3}ms",
+ frame_s,
ts[6] * 1e3,
ts[0] * 1e3,
(ts[1] - ts[0]) * 1e3,
@@ -103,7 +112,7 @@
}
test_scenes::render_svg(&mut ctx, input, scale);
} else {
- test_scenes::render_anim_frame(&mut ctx, current_frame);
+ mmark.draw(&mut ctx);
}
render_info_string(&mut ctx, &info_string);
if let Err(e) = renderer.upload_render_ctx(&mut ctx, frame_idx) {
diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs
index 30fcf8f..b445331 100644
--- a/piet-gpu/src/lib.rs
+++ b/piet-gpu/src/lib.rs
@@ -1,4 +1,5 @@
mod gradient;
+pub mod mmark;
mod pico_svg;
mod render_ctx;
pub mod test_scenes;
diff --git a/piet-gpu/src/mmark.rs b/piet-gpu/src/mmark.rs
new file mode 100644
index 0000000..8deda1b
--- /dev/null
+++ b/piet-gpu/src/mmark.rs
@@ -0,0 +1,121 @@
+//! A benchmark based on MotionMark 1.2's path benchmark.
+
+use rand::{Rng, seq::SliceRandom};
+use piet::{Color, RenderContext, kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez}};
+
+use crate::PietGpuRenderContext;
+
+const WIDTH: usize = 1600;
+const HEIGHT: usize = 900;
+
+const GRID_WIDTH: i64 = 80;
+const GRID_HEIGHT: i64 = 40;
+
+pub struct MMark {
+ elements: Vec<Element>,
+}
+
+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 last = GridPoint((GRID_WIDTH / 2, GRID_HEIGHT / 2));
+ let elements = (0..n).map(|_| {
+ let element = Element::new_rand(last);
+ last = element.grid_point;
+ element
+ }).collect();
+ MMark { elements }
+ }
+
+ pub fn draw(&mut self, ctx: &mut PietGpuRenderContext) {
+ 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.
+ ctx.stroke(&path, &element.color, element.width);
+ path = BezPath::new(); // Should have clear method, to avoid allocations.
+ }
+ if rng.gen::<f32>() > 0.995 {
+ element.is_split ^= true;
+ }
+ }
+ }
+}
+
+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::<f64>().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)
+ }
+}