Skip to main content

ursus_core/render/
frame_stats.rs

1use 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}