Skip to main content

ferroui_browser/rendering/
render_statistics.rs

1use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU64, AtomicUsize, Ordering};
2
3/// What the page can read about the frames that reached its canvases: how
4/// many, which thread drew the last one and to what, and what the ticks of
5/// a render thread cost the thread of the page.
6///
7/// Not from upstream. With a render worker nothing on the thread of the
8/// page sees a frame, so a page (a test, a diagnostic overlay of the host)
9/// that wants to know where rendering happens reads it here. The numbers
10/// are counted for all canvases of the module.
11///
12/// The fields are read one after the other from counters that the thread
13/// that renders goes on writing: two of them may belong to different
14/// frames.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
16pub struct RenderStatistics {
17    /// The frames that were drawn to a canvas since the module started.
18    pub frames: u64,
19    /// The thread that drew the last frame, as
20    /// [`current_thread`](crate::interop::thread_proxy::current_thread)
21    /// names it there; 0 before the first frame, and always in a build
22    /// without threads.
23    pub frame_thread: usize,
24    /// The kind of the render target of the last frame:
25    /// [`RENDER_TARGET_KIND_WEB_GL`](crate::interop::canvas_helper::RENDER_TARGET_KIND_WEB_GL)
26    /// or
27    /// [`RENDER_TARGET_KIND_SOFTWARE`](crate::interop::canvas_helper::RENDER_TARGET_KIND_SOFTWARE);
28    /// 0 before the first frame.
29    pub frame_kind: i32,
30    /// The major version of OpenGL ES the last frame was drawn with (2 for
31    /// WebGL 1, 3 for WebGL 2); 0 for a frame drawn in memory.
32    pub frame_gl_major_version: i32,
33    /// The width of the last frame in device pixels.
34    pub frame_width: i32,
35    /// The height of the last frame in device pixels.
36    pub frame_height: i32,
37    /// The ticks of the frame loop of a render thread since the module
38    /// started. A tick renders a frame only when something changed. Ticks
39    /// of the frame loop of the page are not counted.
40    pub ticks: u64,
41    /// The calls the runtime carried from the render thread to the main
42    /// thread of the page during those ticks (a call of the C library that
43    /// only the main thread can serve: a line on the console, a file);
44    /// `None` when the script of the thread does not count them. While such
45    /// a call is served the render thread waits for the thread of the page.
46    pub tick_proxied_calls: Option<u64>,
47    /// The index, in the table of the script of the module, of the function
48    /// of the last such call; -1 when there was none.
49    pub last_proxied_function: i64,
50    /// The canvases of closed views whose render target the thread that
51    /// drew to them has released
52    /// ([`RenderTargetBrowserSurface::dispose`](super::RenderTargetBrowserSurface::dispose)).
53    pub canvases_released: u64,
54    /// The panics of the render thread since the module started. A frame
55    /// that panics is caught by the render loop, which goes on with the next
56    /// tick; each is reported to the thread of the page
57    /// ([`RenderWorker`](super::RenderWorker)).
58    pub render_thread_panics: u64,
59}
60
61impl RenderStatistics {
62    /// The statistics as they are now. Any thread may ask.
63    pub fn current() -> Self {
64        COUNTERS.read()
65    }
66
67    /// A frame was drawn to a canvas by the calling thread, `thread` as
68    /// [`current_thread`](crate::interop::thread_proxy::current_thread)
69    /// names it.
70    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    /// A tick of the frame loop of a render thread ended; see
75    /// [`tick_proxied_calls`](Self::tick_proxied_calls) and
76    /// [`last_proxied_function`](Self::last_proxied_function) for the two
77    /// arguments.
78    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    /// The thread that renders released the render target of the canvas of
83    /// a closed view.
84    pub(crate) fn canvas_released() {
85        COUNTERS.canvases_released.fetch_add(1, Ordering::SeqCst);
86    }
87
88    /// The render thread panicked.
89    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    /// Whether every tick so far came with a count of its proxied calls.
104    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        // Last: a reader that sees the new count sees the frame it counts.
137        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        // Drawn by another thread, as the render thread does.
189        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}