[frame_stats] Add frame statistics UI to with_winit example

Added a module for frame time statistics and UI layer that displays
the average, minimum, and maximum frame time alongside FPS. The UI
can be toggled by pressing the `S` key.
This commit is contained in:
Arman Uguray 2023-03-04 01:49:09 -08:00
parent 17096ad878
commit 89fb1b89da
3 changed files with 160 additions and 0 deletions

View file

@ -18,4 +18,5 @@ $ cargo run -p with_winit --release -- [SVG FILES]
- Mouse scroll wheel will zoom. - Mouse scroll wheel will zoom.
- Arrow keys switch between SVG images in the current set. - Arrow keys switch between SVG images in the current set.
- Space resets the position and zoom of the image. - Space resets the position and zoom of the image.
- S toggles the frame statistics layer
- Escape exits the program. - Escape exits the program.

View file

@ -37,6 +37,7 @@ use winit::{
#[cfg(not(any(target_arch = "wasm32", target_os = "android")))] #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
mod hot_reload; mod hot_reload;
mod multi_touch; mod multi_touch;
mod stats;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(about, long_about = None, bin_name="cargo run -p with_winit --")] #[command(about, long_about = None, bin_name="cargo run -p with_winit --")]
@ -97,6 +98,8 @@ fn run(
let mut fragment = SceneFragment::new(); let mut fragment = SceneFragment::new();
let mut simple_text = SimpleText::new(); let mut simple_text = SimpleText::new();
let mut images = ImageCache::new(); let mut images = ImageCache::new();
let mut stats = stats::Stats::new();
let mut stats_toggle = false;
let start = Instant::now(); let start = Instant::now();
let mut touch_state = multi_touch::TouchState::new(); let mut touch_state = multi_touch::TouchState::new();
@ -142,6 +145,9 @@ fn run(
Some(VirtualKeyCode::Space) => { Some(VirtualKeyCode::Space) => {
transform = Affine::IDENTITY; transform = Affine::IDENTITY;
} }
Some(VirtualKeyCode::S) => {
stats_toggle = !stats_toggle;
}
Some(VirtualKeyCode::Escape) => { Some(VirtualKeyCode::Escape) => {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
} }
@ -253,6 +259,8 @@ fn run(
let width = render_state.surface.config.width; let width = render_state.surface.config.width;
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 _stats_frame = stats.frame_scope();
// 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);
@ -296,6 +304,9 @@ fn run(
transform = transform * Affine::scale(scale_factor); transform = transform * Affine::scale(scale_factor);
} }
builder.append(&fragment, Some(transform)); builder.append(&fragment, Some(transform));
if stats_toggle {
snapshot.draw_layer(&mut builder, &mut scene_params.text, width as f64);
}
let surface_texture = render_state let surface_texture = render_state
.surface .surface
.surface .surface

View file

@ -0,0 +1,148 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
use scenes::SimpleText;
use std::{collections::VecDeque, time::Instant};
use vello::{
kurbo::{Affine, Rect},
peniko::{Brush, Color, Fill},
SceneBuilder,
};
const SLIDING_WINDOW_SIZE: usize = 100;
#[derive(Debug)]
pub struct Snapshot {
pub fps: f64,
pub frame_time_ms: f64,
pub frame_time_min_ms: f64,
pub frame_time_max_ms: f64,
}
impl Snapshot {
pub fn draw_layer(&self, sb: &mut SceneBuilder, text: &mut SimpleText, width: f64) {
let x_offset = width - 450.;
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&Brush::Solid(Color::rgba8(0, 0, 0, 200)),
None,
&Rect::new(x_offset, 0., width, 115.),
);
text.add(
sb,
None,
25.,
Some(&Brush::Solid(Color::WHITE)),
Affine::translate((x_offset + 15., 30.)),
&format!("Frame Time: {:.2} ms", self.frame_time_ms),
);
text.add(
sb,
None,
25.,
Some(&Brush::Solid(Color::WHITE)),
Affine::translate((x_offset + 15., 60.)),
&format!("Frame Time (min): {:.2} ms", self.frame_time_min_ms),
);
text.add(
sb,
None,
25.,
Some(&Brush::Solid(Color::WHITE)),
Affine::translate((x_offset + 15., 90.)),
&format!("Frame Time (max): {:.2} ms", self.frame_time_max_ms),
);
text.add(
sb,
None,
25.,
Some(&Brush::Solid(Color::WHITE)),
Affine::translate((x_offset + 300., 30.)),
&format!("FPS: {:.2}", self.fps),
);
}
}
pub struct Stats {
count: usize,
sum: u64,
min: u64,
max: 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 {
pub fn new() -> Stats {
Stats {
count: 0,
sum: 0,
min: u64::MAX,
max: u64::MIN,
samples: VecDeque::with_capacity(SLIDING_WINDOW_SIZE),
}
}
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;
Snapshot {
fps,
frame_time_ms,
frame_time_min_ms: self.min as f64 * 0.001,
frame_time_max_ms: self.max as f64 * 0.001,
}
}
pub fn frame_scope<'a>(&'a mut self) -> FrameScope<'a> {
FrameScope {
stats: self,
start: Instant::now(),
}
}
fn add_sample(&mut self, micros: u64) {
let oldest = if self.count < SLIDING_WINDOW_SIZE {
self.count += 1;
None
} else {
self.samples.pop_front()
};
self.sum += micros;
self.samples.push_back(micros);
if let Some(oldest) = oldest {
self.sum -= oldest;
}
if micros < self.min {
self.min = micros;
}
if micros > self.max {
self.max = micros;
}
}
}