ferroui_browser/rendering/
render_statistics.rs1use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU64, AtomicUsize, Ordering};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
16pub struct RenderStatistics {
17 pub frames: u64,
19 pub frame_thread: usize,
24 pub frame_kind: i32,
30 pub frame_gl_major_version: i32,
33 pub frame_width: i32,
35 pub frame_height: i32,
37 pub ticks: u64,
41 pub tick_proxied_calls: Option<u64>,
47 pub last_proxied_function: i64,
50 pub canvases_released: u64,
54 pub render_thread_panics: u64,
59}
60
61impl RenderStatistics {
62 pub fn current() -> Self {
64 COUNTERS.read()
65 }
66
67 pub(crate) fn frame_presented(thread: usize, kind: i32, gl_major_version: i32, width: i32, height: i32) {
71 COUNTERS.frame_presented(thread, kind, gl_major_version, width, height);
72 }
73
74 pub(crate) fn tick_ended(proxied_calls: Option<u64>, last_proxied_function: Option<i64>) {
79 COUNTERS.tick_ended(proxied_calls, last_proxied_function);
80 }
81
82 pub(crate) fn canvas_released() {
85 COUNTERS.canvases_released.fetch_add(1, Ordering::SeqCst);
86 }
87
88 pub(crate) fn render_thread_panicked() {
90 COUNTERS.render_thread_panics.fetch_add(1, Ordering::SeqCst);
91 }
92}
93
94struct Counters {
95 frames: AtomicU64,
96 frame_thread: AtomicUsize,
97 frame_kind: AtomicI32,
98 frame_gl_major_version: AtomicI32,
99 frame_width: AtomicI32,
100 frame_height: AtomicI32,
101 ticks: AtomicU64,
102 tick_proxied_calls: AtomicU64,
103 proxied_calls_counted: AtomicBool,
105 last_proxied_function: AtomicI64,
106 canvases_released: AtomicU64,
107 render_thread_panics: AtomicU64,
108}
109
110static COUNTERS: Counters = Counters::new();
111
112impl Counters {
113 const fn new() -> Self {
114 Self {
115 frames: AtomicU64::new(0),
116 frame_thread: AtomicUsize::new(0),
117 frame_kind: AtomicI32::new(0),
118 frame_gl_major_version: AtomicI32::new(0),
119 frame_width: AtomicI32::new(0),
120 frame_height: AtomicI32::new(0),
121 ticks: AtomicU64::new(0),
122 tick_proxied_calls: AtomicU64::new(0),
123 proxied_calls_counted: AtomicBool::new(true),
124 last_proxied_function: AtomicI64::new(-1),
125 canvases_released: AtomicU64::new(0),
126 render_thread_panics: AtomicU64::new(0),
127 }
128 }
129
130 fn frame_presented(&self, thread: usize, kind: i32, gl_major_version: i32, width: i32, height: i32) {
131 self.frame_thread.store(thread, Ordering::SeqCst);
132 self.frame_kind.store(kind, Ordering::SeqCst);
133 self.frame_gl_major_version.store(gl_major_version, Ordering::SeqCst);
134 self.frame_width.store(width, Ordering::SeqCst);
135 self.frame_height.store(height, Ordering::SeqCst);
136 self.frames.fetch_add(1, Ordering::SeqCst);
138 }
139
140 fn tick_ended(&self, proxied_calls: Option<u64>, last_proxied_function: Option<i64>) {
141 match proxied_calls {
142 Some(proxied_calls) => {
143 self.tick_proxied_calls.fetch_add(proxied_calls, Ordering::SeqCst);
144 }
145 None => self.proxied_calls_counted.store(false, Ordering::SeqCst),
146 }
147 if let Some(function) = last_proxied_function {
148 self.last_proxied_function.store(function, Ordering::SeqCst);
149 }
150 self.ticks.fetch_add(1, Ordering::SeqCst);
151 }
152
153 fn read(&self) -> RenderStatistics {
154 let ticks = self.ticks.load(Ordering::SeqCst);
155 let counted = ticks > 0 && self.proxied_calls_counted.load(Ordering::SeqCst);
156 RenderStatistics {
157 frames: self.frames.load(Ordering::SeqCst),
158 frame_thread: self.frame_thread.load(Ordering::SeqCst),
159 frame_kind: self.frame_kind.load(Ordering::SeqCst),
160 frame_gl_major_version: self.frame_gl_major_version.load(Ordering::SeqCst),
161 frame_width: self.frame_width.load(Ordering::SeqCst),
162 frame_height: self.frame_height.load(Ordering::SeqCst),
163 ticks,
164 tick_proxied_calls: counted.then(|| self.tick_proxied_calls.load(Ordering::SeqCst)),
165 last_proxied_function: self.last_proxied_function.load(Ordering::SeqCst),
166 canvases_released: self.canvases_released.load(Ordering::SeqCst),
167 render_thread_panics: self.render_thread_panics.load(Ordering::SeqCst),
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::interop::canvas_helper::{RENDER_TARGET_KIND_SOFTWARE, RENDER_TARGET_KIND_WEB_GL};
176
177 #[test]
178 fn nothing_is_known_before_the_first_frame() {
179 let counters = Counters::new();
180
181 assert_eq!(RenderStatistics { last_proxied_function: -1, ..Default::default() }, counters.read());
182 }
183
184 #[test]
185 fn the_last_frame_is_described_and_every_frame_is_counted() {
186 let counters = Counters::new();
187 counters.frame_presented(4096, RENDER_TARGET_KIND_WEB_GL, 3, 460, 520);
188 std::thread::scope(|scope| {
190 scope.spawn(|| counters.frame_presented(8192, RENDER_TARGET_KIND_SOFTWARE, 0, 300, 180));
191 });
192
193 let statistics = counters.read();
194 assert_eq!(2, statistics.frames);
195 assert_eq!(8192, statistics.frame_thread);
196 assert_eq!(RENDER_TARGET_KIND_SOFTWARE, statistics.frame_kind);
197 assert_eq!(0, statistics.frame_gl_major_version);
198 assert_eq!((300, 180), (statistics.frame_width, statistics.frame_height));
199 assert_eq!(0, statistics.ticks);
200 assert_eq!(None, statistics.tick_proxied_calls);
201 }
202
203 #[test]
204 fn the_proxied_calls_of_the_ticks_are_added_up() {
205 let counters = Counters::new();
206 counters.tick_ended(Some(0), None);
207 counters.tick_ended(Some(2), Some(17));
208 counters.tick_ended(Some(0), None);
209
210 let statistics = counters.read();
211 assert_eq!(3, statistics.ticks);
212 assert_eq!(Some(2), statistics.tick_proxied_calls);
213 assert_eq!(17, statistics.last_proxied_function);
214 }
215
216 #[test]
217 fn a_tick_that_was_not_counted_makes_the_sum_unknown() {
218 let counters = Counters::new();
219 counters.tick_ended(Some(1), Some(3));
220 counters.tick_ended(None, None);
221 counters.tick_ended(Some(1), None);
222
223 let statistics = counters.read();
224 assert_eq!(3, statistics.ticks);
225 assert_eq!(None, statistics.tick_proxied_calls);
226 assert_eq!(3, statistics.last_proxied_function);
227 }
228}