[frame_stats] Draw a live plot of frame time samples

Also abandoned the FrameScope idea and revised the `Stats::add_sample`
to accept a struct to accept a variety of future measurements.
This commit is contained in:
Arman Uguray 2023-03-16 00:15:47 -07:00
parent bacaeebcb6
commit 1ac4a4f1a8
2 changed files with 56 additions and 29 deletions

View file

@ -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")))]

View file

@ -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<Item = &'a u64>,
{
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<u64>,
}
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<Item = &u64> {
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 {