feat: Add FPS counter

This commit is contained in:
2026-09-15 21:22:39 +02:00
parent fbe7f4da13
commit f039d38bfa
+46
View File
@@ -207,6 +207,14 @@ pub struct FractalApp {
/// Time-based animation of colours / Julia c / Phoenix p / zoom. /// Time-based animation of colours / Julia c / Phoenix p / zoom.
anim: AnimState, anim: AnimState,
/// Smoothed frames-per-second, recomputed each ~0.5 s window. Only advances
/// while the app is actually repainting (interaction / animation / export);
/// idle frames aren't forced, so a frozen value means "nothing to render".
fps: f32,
/// Frames counted in the current FPS window, and its start time (`i.time`).
fps_frames: u32,
fps_window_start: f64,
/// Reference orbit (`Z_n` as f32 pairs) for the current view. /// Reference orbit (`Z_n` as f32 pairs) for the current view.
reference: Arc<Vec<[f32; 2]>>, reference: Arc<Vec<[f32; 2]>>,
/// Bumped whenever `reference` is replaced, so the GPU re-uploads it. /// Bumped whenever `reference` is replaced, so the GPU re-uploads it.
@@ -309,6 +317,9 @@ impl FractalApp {
controls_open: true, controls_open: true,
fullscreen: false, fullscreen: false,
anim: AnimState::default(), anim: AnimState::default(),
fps: 0.0,
fps_frames: 0,
fps_window_start: 0.0,
reference: Arc::new(Vec::new()), reference: Arc::new(Vec::new()),
generation: 0, generation: 0,
ref_center_re, ref_center_re,
@@ -904,6 +915,19 @@ impl FractalApp {
self.fullscreen = !self.fullscreen; self.fullscreen = !self.fullscreen;
self.apply_fullscreen(ui.ctx()); self.apply_fullscreen(ui.ctx());
} }
// FPS readout. Monospace + fixed width so the number
// changing doesn't jitter the button row.
ui.add(
egui::Label::new(
egui::RichText::new(format!("{:>3.0} FPS", self.fps))
.monospace(),
)
.selectable(false),
)
.on_hover_text(
"Frames per second while rendering (interaction, \
animation, export). Frozen when idle.",
);
}); });
}); });
}); });
@@ -948,6 +972,27 @@ impl FractalApp {
} }
} }
/// Recompute the smoothed FPS. Counts frames over a ~0.5 s wall-clock window
/// (using egui's monotonic `i.time`, which works on native and web) and
/// divides once the window closes, so the readout is steady rather than
/// jittering every frame. Only advances when egui repaints — i.e. while the
/// app is doing work — so an idle app shows its last measured rate.
fn update_fps(&mut self, ui: &egui::Ui) {
let now = ui.input(|i| i.time);
// Reset the window if time went backwards or hasn't started yet.
if self.fps_window_start <= 0.0 || now < self.fps_window_start {
self.fps_window_start = now;
self.fps_frames = 0;
}
self.fps_frames += 1;
let elapsed = now - self.fps_window_start;
if elapsed >= 0.5 {
self.fps = (self.fps_frames as f64 / elapsed) as f32;
self.fps_frames = 0;
self.fps_window_start = now;
}
}
/// Advance any enabled animations by the frame's elapsed time, and request a /// Advance any enabled animations by the frame's elapsed time, and request a
/// repaint while active. Animations render at full resolution/AA (they do not /// repaint while active. Animations render at full resolution/AA (they do not
/// trigger the interaction low-res pass). /// trigger the interaction low-res pass).
@@ -1418,6 +1463,7 @@ impl FractalApp {
impl eframe::App for FractalApp { impl eframe::App for FractalApp {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
self.poll_export(ui.ctx()); self.poll_export(ui.ctx());
self.update_fps(ui);
// Track the real fullscreen state (e.g. the user pressing Esc/F11 or the // Track the real fullscreen state (e.g. the user pressing Esc/F11 or the
// browser leaving fullscreen) so the toggle button label stays correct. // browser leaving fullscreen) so the toggle button label stays correct.
self.sync_fullscreen(ui.ctx()); self.sync_fullscreen(ui.ctx());