compositing/
refresh_driver.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::collections::hash_map::Values;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::thread::{self, JoinHandle};
10use std::time::Duration;
11
12use base::id::WebViewId;
13use constellation_traits::EmbedderToConstellationMessage;
14use crossbeam_channel::{Sender, select};
15use embedder_traits::EventLoopWaker;
16use log::warn;
17use timers::{BoxedTimerCallback, TimerEventRequest, TimerScheduler};
18
19use crate::compositor::RepaintReason;
20use crate::webview_renderer::WebViewRenderer;
21
22const FRAME_DURATION: Duration = Duration::from_millis(1000 / 120);
23
24/// The [`RefreshDriver`] is responsible for controlling updates to aall `WebView`s
25/// onscreen presentation. Currently, it only manages controlling animation update
26/// requests.
27///
28/// The implementation is very basic at the moment, only requesting new animation
29/// frames at a constant time after a repaint.
30pub(crate) struct RefreshDriver {
31    /// The channel on which messages can be sent to the Constellation.
32    pub(crate) constellation_sender: Sender<EmbedderToConstellationMessage>,
33
34    /// Whether or not we are currently animating via a timer.
35    pub(crate) animating: Cell<bool>,
36
37    /// Whether or not we are waiting for our frame timeout to trigger
38    pub(crate) waiting_for_frame_timeout: Arc<AtomicBool>,
39
40    /// A [`TimerThread`] which is used to schedule frame timeouts in the future.
41    timer_thread: TimerThread,
42
43    /// An [`EventLoopWaker`] to be used to wake up the embedder when it is
44    /// time to paint a frame.
45    event_loop_waker: Box<dyn EventLoopWaker>,
46}
47
48impl RefreshDriver {
49    pub(crate) fn new(
50        constellation_sender: Sender<EmbedderToConstellationMessage>,
51        event_loop_waker: Box<dyn EventLoopWaker>,
52    ) -> Self {
53        Self {
54            constellation_sender,
55            animating: Default::default(),
56            waiting_for_frame_timeout: Default::default(),
57            timer_thread: Default::default(),
58            event_loop_waker,
59        }
60    }
61
62    fn timer_callback(&self) -> BoxedTimerCallback {
63        let waiting_for_frame_timeout = self.waiting_for_frame_timeout.clone();
64        let event_loop_waker = self.event_loop_waker.clone_box();
65        Box::new(move || {
66            waiting_for_frame_timeout.store(false, Ordering::Relaxed);
67            event_loop_waker.wake();
68        })
69    }
70
71    /// Notify the [`RefreshDriver`] that a paint is about to happen. This will trigger
72    /// new animation frames for all active `WebView`s and schedule a new frame deadline.
73    pub(crate) fn notify_will_paint(
74        &self,
75        webview_renderers: Values<'_, WebViewId, WebViewRenderer>,
76    ) {
77        // If we are still waiting for the frame to timeout this paint was caused for some
78        // non-animation related reason and we should wait until the frame timeout to trigger
79        // the next one.
80        if self.waiting_for_frame_timeout.load(Ordering::Relaxed) {
81            return;
82        }
83
84        // If any WebViews are animating ask them to paint again for another animation tick.
85        let animating_webviews: Vec<_> = webview_renderers
86            .filter_map(|webview_renderer| {
87                if webview_renderer.animating() {
88                    Some(webview_renderer.id)
89                } else {
90                    None
91                }
92            })
93            .collect();
94
95        // If nothing is animating any longer, update our state and exit early without requesting
96        // any noew frames nor triggering a new animation deadline.
97        if animating_webviews.is_empty() {
98            self.animating.set(false);
99            return;
100        }
101
102        if let Err(error) =
103            self.constellation_sender
104                .send(EmbedderToConstellationMessage::TickAnimation(
105                    animating_webviews,
106                ))
107        {
108            warn!("Sending tick to constellation failed ({error:?}).");
109        }
110
111        // Queue the next frame deadline.
112        self.animating.set(true);
113        self.waiting_for_frame_timeout
114            .store(true, Ordering::Relaxed);
115        self.timer_thread
116            .queue_timer(FRAME_DURATION, self.timer_callback());
117    }
118
119    /// Notify the [`RefreshDriver`] that the animation state of a particular `WebView`
120    /// via its associated [`WebViewRenderer`] has changed. In the case that a `WebView`
121    /// has started animating, the [`RefreshDriver`] will request a new frame from it
122    /// immediately, but only render that frame at the next frame deadline.
123    pub(crate) fn notify_animation_state_changed(&self, webview_renderer: &WebViewRenderer) {
124        if !webview_renderer.animating() {
125            // If no other WebView is animating we will officially stop animated once the
126            // next frame has been painted.
127            return;
128        }
129
130        if let Err(error) =
131            self.constellation_sender
132                .send(EmbedderToConstellationMessage::TickAnimation(vec![
133                    webview_renderer.id,
134                ]))
135        {
136            warn!("Sending tick to constellation failed ({error:?}).");
137        }
138
139        if self.animating.get() {
140            return;
141        }
142
143        self.animating.set(true);
144        self.waiting_for_frame_timeout
145            .store(true, Ordering::Relaxed);
146        self.timer_thread
147            .queue_timer(FRAME_DURATION, self.timer_callback());
148    }
149
150    /// Whether or not the renderer should trigger a message to the embedder to request a
151    /// repaint. This might be false if: we are animating and the repaint reason is just
152    /// for a new frame. In that case, the renderer should wait until the frame timeout to
153    /// ask the embedder to repaint.
154    pub(crate) fn wait_to_paint(&self, repaint_reason: RepaintReason) -> bool {
155        if !self.animating.get() || repaint_reason != RepaintReason::NewWebRenderFrame {
156            return false;
157        }
158
159        self.waiting_for_frame_timeout.load(Ordering::Relaxed)
160    }
161}
162
163enum TimerThreadMessage {
164    Request(TimerEventRequest),
165    Quit,
166}
167
168/// A thread that manages a [`TimerScheduler`] running in the background of the
169/// [`RefreshDriver`]. This is necessary because we need a reliable way of waking up the
170/// embedder's main thread, which may just be sleeping until the `EventLoopWaker` asks it
171/// to wake up.
172///
173/// It would be nice to integrate this somehow into the embedder thread, but it would
174/// require both some communication with the embedder and for all embedders to be well
175/// behave respecting wakeup timeouts -- a bit too much to ask at the moment.
176struct TimerThread {
177    sender: Sender<TimerThreadMessage>,
178    join_handle: Option<JoinHandle<()>>,
179}
180
181impl Drop for TimerThread {
182    fn drop(&mut self) {
183        let _ = self.sender.send(TimerThreadMessage::Quit);
184        if let Some(join_handle) = self.join_handle.take() {
185            let _ = join_handle.join();
186        }
187    }
188}
189
190impl Default for TimerThread {
191    fn default() -> Self {
192        let (sender, receiver) = crossbeam_channel::unbounded::<TimerThreadMessage>();
193        let join_handle = thread::Builder::new()
194            .name(String::from("CompositorTimerThread"))
195            .spawn(move || {
196                let mut scheduler = TimerScheduler::default();
197
198                loop {
199                    select! {
200                        recv(receiver) -> message => {
201                            match message {
202                                Ok(TimerThreadMessage::Request(request)) => {
203                                    scheduler.schedule_timer(request);
204                                },
205                                _ => return,
206                            }
207                        },
208                        recv(scheduler.wait_channel()) -> _message => {
209                            scheduler.dispatch_completed_timers();
210                        },
211                    };
212                }
213            })
214            .expect("Could not create RefreshDriver timer thread.");
215
216        Self {
217            sender,
218            join_handle: Some(join_handle),
219        }
220    }
221}
222
223impl TimerThread {
224    fn queue_timer(&self, duration: Duration, callback: BoxedTimerCallback) {
225        let _ = self
226            .sender
227            .send(TimerThreadMessage::Request(TimerEventRequest {
228                callback,
229                duration,
230            }));
231    }
232}