mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-09 12:21:31 +11:00
[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:
parent
bacaeebcb6
commit
1ac4a4f1a8
|
@ -260,7 +260,7 @@ fn run(
|
||||||
let height = render_state.surface.config.height;
|
let height = render_state.surface.config.height;
|
||||||
let device_handle = &render_cx.devices[render_state.surface.dev_id];
|
let device_handle = &render_cx.devices[render_state.surface.dev_id];
|
||||||
let snapshot = stats.snapshot();
|
let snapshot = stats.snapshot();
|
||||||
let _stats_frame = stats.frame_scope();
|
let frame_start_time = Instant::now();
|
||||||
|
|
||||||
// Allow looping forever
|
// Allow looping forever
|
||||||
scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32);
|
scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32);
|
||||||
|
@ -310,6 +310,7 @@ fn run(
|
||||||
&mut scene_params.text,
|
&mut scene_params.text,
|
||||||
width as f64,
|
width as f64,
|
||||||
height as f64,
|
height as f64,
|
||||||
|
stats.samples(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let surface_texture = render_state
|
let surface_texture = render_state
|
||||||
|
@ -350,6 +351,10 @@ fn run(
|
||||||
.expect("failed to render to surface");
|
.expect("failed to render to surface");
|
||||||
surface_texture.present();
|
surface_texture.present();
|
||||||
device_handle.device.poll(wgpu::Maintain::Poll);
|
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 {
|
Event::UserEvent(event) => match event {
|
||||||
#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
|
#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
use scenes::SimpleText;
|
use scenes::SimpleText;
|
||||||
use std::{collections::VecDeque, time::Instant};
|
use std::collections::VecDeque;
|
||||||
use vello::{
|
use vello::{
|
||||||
kurbo::{Affine, Rect},
|
kurbo::{Affine, PathEl, Rect},
|
||||||
peniko::{Brush, Color, Fill},
|
peniko::{Brush, Color, Fill},
|
||||||
SceneBuilder,
|
SceneBuilder,
|
||||||
};
|
};
|
||||||
|
@ -33,20 +33,23 @@ pub struct Snapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snapshot {
|
impl Snapshot {
|
||||||
pub fn draw_layer(
|
pub fn draw_layer<'a, T>(
|
||||||
&self,
|
&self,
|
||||||
sb: &mut SceneBuilder,
|
sb: &mut SceneBuilder,
|
||||||
text: &mut SimpleText,
|
text: &mut SimpleText,
|
||||||
viewport_width: f64,
|
viewport_width: f64,
|
||||||
viewport_height: f64,
|
viewport_height: f64,
|
||||||
) {
|
samples: T,
|
||||||
let width = (viewport_width * 0.4).max(200.).min(400.);
|
) where
|
||||||
let height = width * 0.3;
|
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 x_offset = viewport_width - width;
|
||||||
let y_offset = viewport_height - height;
|
let y_offset = viewport_height - height;
|
||||||
let offset = Affine::translate((x_offset, y_offset));
|
let offset = Affine::translate((x_offset, y_offset));
|
||||||
let text_height = height * 0.2;
|
let text_height = height * 0.1;
|
||||||
let left_margin = width * 0.03;
|
let left_margin = width * 0.01;
|
||||||
let text_size = (text_height * 0.9) as f32;
|
let text_size = (text_height * 0.9) as f32;
|
||||||
sb.fill(
|
sb.fill(
|
||||||
Fill::NonZero,
|
Fill::NonZero,
|
||||||
|
@ -95,9 +98,42 @@ impl Snapshot {
|
||||||
offset * Affine::translate((width * 0.67, text_height)),
|
offset * Affine::translate((width * 0.67, text_height)),
|
||||||
&format!("FPS: {:.2}", self.fps),
|
&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 {
|
pub struct Stats {
|
||||||
count: usize,
|
count: usize,
|
||||||
sum: u64,
|
sum: u64,
|
||||||
|
@ -106,18 +142,6 @@ pub struct Stats {
|
||||||
samples: VecDeque<u64>,
|
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 {
|
impl Stats {
|
||||||
pub fn new() -> Stats {
|
pub fn new() -> Stats {
|
||||||
Stats {
|
Stats {
|
||||||
|
@ -129,6 +153,10 @@ impl Stats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn samples(&self) -> impl Iterator<Item = &u64> {
|
||||||
|
self.samples.iter()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> Snapshot {
|
pub fn snapshot(&self) -> Snapshot {
|
||||||
let frame_time_ms = (self.sum as f64 / self.count as f64) * 0.001;
|
let frame_time_ms = (self.sum as f64 / self.count as f64) * 0.001;
|
||||||
let fps = 1000. / frame_time_ms;
|
let fps = 1000. / frame_time_ms;
|
||||||
|
@ -140,20 +168,14 @@ impl Stats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame_scope<'a>(&'a mut self) -> FrameScope<'a> {
|
pub fn add_sample(&mut self, sample: Sample) {
|
||||||
FrameScope {
|
|
||||||
stats: self,
|
|
||||||
start: Instant::now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_sample(&mut self, micros: u64) {
|
|
||||||
let oldest = if self.count < SLIDING_WINDOW_SIZE {
|
let oldest = if self.count < SLIDING_WINDOW_SIZE {
|
||||||
self.count += 1;
|
self.count += 1;
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
self.samples.pop_front()
|
self.samples.pop_front()
|
||||||
};
|
};
|
||||||
|
let micros = sample.frame_time_us;
|
||||||
self.sum += micros;
|
self.sum += micros;
|
||||||
self.samples.push_back(micros);
|
self.samples.push_back(micros);
|
||||||
if let Some(oldest) = oldest {
|
if let Some(oldest) = oldest {
|
||||||
|
|
Loading…
Reference in a new issue