diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index 76a1100..7943d6f 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -260,7 +260,7 @@ fn run( let height = render_state.surface.config.height; let device_handle = &render_cx.devices[render_state.surface.dev_id]; let snapshot = stats.snapshot(); - let _stats_frame = stats.frame_scope(); + let frame_start_time = Instant::now(); // Allow looping forever scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32); @@ -310,6 +310,7 @@ fn run( &mut scene_params.text, width as f64, height as f64, + stats.samples(), ); } let surface_texture = render_state @@ -350,6 +351,10 @@ fn run( .expect("failed to render to surface"); surface_texture.present(); device_handle.device.poll(wgpu::Maintain::Poll); + + stats.add_sample(stats::Sample { + frame_time_us: frame_start_time.elapsed().as_micros() as u64, + }); } Event::UserEvent(event) => match event { #[cfg(not(any(target_arch = "wasm32", target_os = "android")))] diff --git a/examples/with_winit/src/stats.rs b/examples/with_winit/src/stats.rs index 344fc9e..e0b0b28 100644 --- a/examples/with_winit/src/stats.rs +++ b/examples/with_winit/src/stats.rs @@ -15,9 +15,9 @@ // Also licensed under MIT license, at your choice. use scenes::SimpleText; -use std::{collections::VecDeque, time::Instant}; +use std::collections::VecDeque; use vello::{ - kurbo::{Affine, Rect}, + kurbo::{Affine, PathEl, Rect}, peniko::{Brush, Color, Fill}, SceneBuilder, }; @@ -33,20 +33,23 @@ pub struct Snapshot { } impl Snapshot { - pub fn draw_layer( + pub fn draw_layer<'a, T>( &self, sb: &mut SceneBuilder, text: &mut SimpleText, viewport_width: f64, viewport_height: f64, - ) { - let width = (viewport_width * 0.4).max(200.).min(400.); - let height = width * 0.3; + samples: T, + ) where + T: Iterator, + { + let width = (viewport_width * 0.4).max(200.).min(600.); + let height = width * 0.6; let x_offset = viewport_width - width; let y_offset = viewport_height - height; let offset = Affine::translate((x_offset, y_offset)); - let text_height = height * 0.2; - let left_margin = width * 0.03; + let text_height = height * 0.1; + let left_margin = width * 0.01; let text_size = (text_height * 0.9) as f32; sb.fill( Fill::NonZero, @@ -95,9 +98,42 @@ impl Snapshot { offset * Affine::translate((width * 0.67, text_height)), &format!("FPS: {:.2}", self.fps), ); + + // Plot the samples with a bar graph + use PathEl::*; + let graph_max_height = height * 0.5; + let graph_max_width = width - 2. * left_margin; + let bar_extent = graph_max_width / (SLIDING_WINDOW_SIZE as f64); + let bar_width = bar_extent * 0.3; + let bar = [ + MoveTo((0., graph_max_height).into()), + LineTo((0., 0.).into()), + LineTo((bar_width, 0.).into()), + LineTo((bar_width, graph_max_height).into()), + ]; + for (i, sample) in samples.enumerate() { + let t = offset * Affine::translate(((i as f64) * bar_extent, graph_max_height)); + // The height of each sample is based on its ratio to the maximum observed frame time. + // Currently this maximum scale is sticky and a high temporary spike will permanently + // shrink the draw size of the overall average sample, so scale the size non-linearly to + // emphasize smaller samples. + let h = (*sample as f64) * 0.001 / self.frame_time_max_ms; + let s = Affine::scale_non_uniform(1., -h.sqrt()); + sb.fill( + Fill::NonZero, + t * Affine::translate((left_margin, 5. * text_height)) * s, + Color::rgb8(0, 240, 0), + None, + &bar, + ); + } } } +pub struct Sample { + pub frame_time_us: u64, +} + pub struct Stats { count: usize, sum: u64, @@ -106,18 +142,6 @@ pub struct Stats { samples: VecDeque, } -pub struct FrameScope<'a> { - stats: &'a mut Stats, - start: Instant, -} - -impl<'a> Drop for FrameScope<'a> { - fn drop(&mut self) { - self.stats - .add_sample(self.start.elapsed().as_micros() as u64); - } -} - impl Stats { pub fn new() -> Stats { Stats { @@ -129,6 +153,10 @@ impl Stats { } } + pub fn samples(&self) -> impl Iterator { + self.samples.iter() + } + pub fn snapshot(&self) -> Snapshot { let frame_time_ms = (self.sum as f64 / self.count as f64) * 0.001; let fps = 1000. / frame_time_ms; @@ -140,20 +168,14 @@ impl Stats { } } - pub fn frame_scope<'a>(&'a mut self) -> FrameScope<'a> { - FrameScope { - stats: self, - start: Instant::now(), - } - } - - fn add_sample(&mut self, micros: u64) { + pub fn add_sample(&mut self, sample: Sample) { let oldest = if self.count < SLIDING_WINDOW_SIZE { self.count += 1; None } else { self.samples.pop_front() }; + let micros = sample.frame_time_us; self.sum += micros; self.samples.push_back(micros); if let Some(oldest) = oldest {