ursus_core/render/
frame_stats.rs1use crate::vulkan::timestamps::GpuFrameTimes;
2use std::collections::VecDeque;
3use std::sync::{Arc, Mutex};
4
5pub const FRAME_HISTORY_LEN: usize = 120;
6
7#[derive(Debug, Clone, Default)]
8pub struct FrameSample {
9 pub cpu_ms: f32,
10 pub fps: f32,
11}
12
13#[derive(Debug, Default)]
14pub struct FrameStatsInner {
15 history: VecDeque<FrameSample>,
16 last_gpu_times: Option<GpuFrameTimes>,
17 smoothed_ms: f32,
18}
19
20impl FrameStatsInner {
21 fn push(&mut self, sample: FrameSample) {
22 if self.history.len() >= FRAME_HISTORY_LEN {
23 self.history.pop_front();
24 }
25 self.history.push_back(sample);
26 }
27}
28
29#[derive(Clone)]
30pub struct FrameStats {
31 inner: Arc<Mutex<FrameStatsInner>>,
32}
33
34impl FrameStats {
35 pub fn new() -> Self {
36 Self { inner: Arc::new(Mutex::new(FrameStatsInner::default())) }
37 }
38
39 pub fn record_cpu_frame(&self, cpu_ms: f32) {
40 let fps = if cpu_ms > 0.0 { 1000.0 / cpu_ms } else { 0.0 };
41
42 let mut inner = self.inner.lock().unwrap();
43
44 const SMOOTHING: f32 = 0.9;
45 if inner.smoothed_ms <= 0.0 {
46 inner.smoothed_ms = cpu_ms;
47 } else {
48 inner.smoothed_ms = inner.smoothed_ms * SMOOTHING + cpu_ms * (1.0 - SMOOTHING);
49 }
50
51 inner.push(FrameSample { cpu_ms, fps });
52 }
53
54 pub fn record_gpu_times(&self, times: GpuFrameTimes) {
55 let mut inner = self.inner.lock().unwrap();
56 inner.last_gpu_times = Some(times);
57 }
58
59 pub fn current_fps(&self) -> f32 {
60 let inner = self.inner.lock().unwrap();
61 if inner.smoothed_ms > 0.0 {
62 1000.0 / inner.smoothed_ms
63 } else {
64 0.0
65 }
66 }
67
68 pub fn history_snapshot(&self) -> Vec<FrameSample> {
69 self.inner.lock().unwrap().history.iter().cloned().collect()
70 }
71
72 pub fn last_gpu_times(&self) -> Option<GpuFrameTimes> {
73 self.inner.lock().unwrap().last_gpu_times.clone()
74 }
75}
76
77impl Default for FrameStats {
78 fn default() -> Self {
79 Self::new()
80 }
81}