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, RefCell};
6use std::rc::Rc;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::thread::{self, JoinHandle};
10use std::time::Duration;
11
12use constellation_traits::EmbedderToConstellationMessage;
13use crossbeam_channel::{Sender, select};
14use embedder_traits::{EventLoopWaker, RefreshDriver};
15use log::warn;
16use timers::{BoxedTimerCallback, TimerEventRequest, TimerScheduler};
17
18use crate::painter::Painter;
19use crate::webview_renderer::WebViewRenderer;
20
21/// The [`BaseRefreshDriver`] is a "base class" for [`RefreshDriver`] trait
22/// implementations. It encapsulates shared behavior so that it does not have to be
23/// implemented by all trait implementations. It is responsible for providing
24/// [`RefreshDriver`] implementations with a callback that is used to wake up the event
25/// loop and trigger frame readiness.
26pub(crate) struct BaseRefreshDriver {
27    /// Whether or not the [`BaseRefreshDriver`] is waiting for a frame. Once the [`RefreshDriver`]
28    /// informs the base that a frame start happened, this becomes false.
29    waiting_for_frame: Arc<AtomicBool>,
30    /// An [`EventLooperWaker`] which alerts the main UI event loop when a frame start occurs.
31    event_loop_waker: Box<dyn EventLoopWaker>,
32    /// A list of internal observers that watch for frame starts.
33    observers: RefCell<Vec<Rc<dyn RefreshDriverObserver>>>,
34    /// The implementation of the [`RefreshDriver`]. By default this is a simple timer, but the
35    /// embedder can install a custom driver, such as one that is run via the hardware vsync signal.
36    refresh_driver: Rc<dyn RefreshDriver>,
37}
38
39impl BaseRefreshDriver {
40    pub(crate) fn new(
41        event_loop_waker: Box<dyn EventLoopWaker>,
42        refresh_driver: Option<Rc<dyn RefreshDriver>>,
43    ) -> Self {
44        let refresh_driver =
45            refresh_driver.unwrap_or_else(|| Rc::new(TimerRefreshDriver::default()));
46        Self {
47            waiting_for_frame: Arc::new(AtomicBool::new(false)),
48            event_loop_waker,
49            observers: Default::default(),
50            refresh_driver,
51        }
52    }
53
54    pub(crate) fn add_observer(&self, observer: Rc<dyn RefreshDriverObserver>) {
55        let mut observers = self.observers.borrow_mut();
56        observers.push(observer);
57
58        // If this is the first observer, make sure to observe the next frame.
59        if observers.len() == 1 {
60            self.observe_next_frame();
61        }
62    }
63
64    pub(crate) fn notify_will_paint(&self, painter: &mut Painter) {
65        // Limit the borrow of `self.observers` to the minimum here.
66        let still_has_observers = {
67            let mut observers = self.observers.borrow_mut();
68            observers.retain(|observer| observer.frame_started(painter));
69            !observers.is_empty()
70        };
71
72        if still_has_observers {
73            self.observe_next_frame();
74        }
75    }
76
77    fn observe_next_frame(&self) {
78        self.waiting_for_frame.store(true, Ordering::Relaxed);
79
80        let waiting_for_frame = self.waiting_for_frame.clone();
81        let event_loop_waker = self.event_loop_waker.clone_box();
82        self.refresh_driver.observe_next_frame(Box::new(move || {
83            waiting_for_frame.store(false, Ordering::Relaxed);
84            event_loop_waker.wake();
85        }));
86    }
87
88    /// Whether or not the renderer should trigger a message to the embedder to request a
89    /// repaint. This might be true if we are animating and we are still waiting for a new
90    /// frame from the `RefreshDriver`.
91    pub(crate) fn wait_to_paint(&self) -> bool {
92        !self.observers.borrow().is_empty() && self.waiting_for_frame.load(Ordering::Relaxed)
93    }
94}
95
96/// A [`RefreshDriverObserver`] is an internal subscriber to frame start signals from the
97/// [`RefreshDriver`]. Examples of these kind of observers would be one that triggers new
98/// animation frames right after vsync signals or one that handles touch interactions once
99/// per frame.
100pub(crate) trait RefreshDriverObserver {
101    /// Informs the observer that a new frame has started. The observer should return
102    /// `true` to keep observing or `false` if wants to stop observing and should be
103    /// removed by the [`BaseRefreshDriver`].
104    fn frame_started(&self, painter: &mut Painter) -> bool;
105}
106
107/// The [`AnimationRefreshDriverObserver`] is the default implementation of a
108/// [`RefreshDriver`] on systems without vsync hardware integration. It has a very basic
109/// way of triggering frames using a timer. It prevents new animation frames until the
110/// timer has fired.
111pub(crate) struct AnimationRefreshDriverObserver {
112    /// The channel on which messages can be sent to the Constellation.
113    pub(crate) constellation_sender: Sender<EmbedderToConstellationMessage>,
114
115    /// Whether or not we are currently animating via a timer.
116    pub(crate) animating: Cell<bool>,
117}
118
119impl AnimationRefreshDriverObserver {
120    pub(crate) fn new(constellation_sender: Sender<EmbedderToConstellationMessage>) -> Self {
121        Self {
122            constellation_sender,
123            animating: Default::default(),
124        }
125    }
126
127    pub(crate) fn notify_animation_state_changed(
128        &self,
129        webview_renderer: &WebViewRenderer,
130    ) -> bool {
131        if !webview_renderer.animating() {
132            // If no other WebView is animating we will officially stop animating once the
133            // next frame has been painted.
134            return false;
135        }
136
137        if let Err(error) =
138            self.constellation_sender
139                .send(EmbedderToConstellationMessage::TickAnimation(vec![
140                    webview_renderer.id,
141                ]))
142        {
143            warn!("Sending tick to constellation failed ({error:?}).");
144        }
145
146        if self.animating.get() {
147            return false;
148        }
149
150        self.animating.set(true);
151        true
152    }
153}
154
155impl RefreshDriverObserver for AnimationRefreshDriverObserver {
156    fn frame_started(&self, painter: &mut Painter) -> bool {
157        // If any WebViews are animating ask them to paint again for another animation tick.
158        let animating_webviews = painter.animating_webviews();
159
160        // If nothing is animating any longer, update our state and exit early without requesting
161        // any new frames.
162        if animating_webviews.is_empty() {
163            self.animating.set(false);
164            return false;
165        }
166
167        // Request new animation frames from all animating WebViews.
168        if let Err(error) =
169            self.constellation_sender
170                .send(EmbedderToConstellationMessage::TickAnimation(
171                    animating_webviews,
172                ))
173        {
174            warn!("Sending tick to constellation failed ({error:?}).");
175            return false;
176        }
177
178        self.animating.set(true);
179        true
180    }
181}
182
183enum TimerThreadMessage {
184    Request(TimerEventRequest),
185    Quit,
186}
187
188/// A thread that manages a [`TimerScheduler`] running in the background of the
189/// [`RefreshDriver`]. This is necessary because we need a reliable way of waking up the
190/// embedder's main thread, which may just be sleeping until the `EventLoopWaker` asks it
191/// to wake up.
192///
193/// It would be nice to integrate this somehow into the embedder thread, but it would
194/// require both some communication with the embedder and for all embedders to be well
195/// behave respecting wakeup timeouts -- a bit too much to ask at the moment.
196struct TimerRefreshDriver {
197    sender: Sender<TimerThreadMessage>,
198    join_handle: Option<JoinHandle<()>>,
199}
200
201impl Default for TimerRefreshDriver {
202    fn default() -> Self {
203        let (sender, receiver) = crossbeam_channel::unbounded::<TimerThreadMessage>();
204        let join_handle = thread::Builder::new()
205            .name(String::from("CompositorTimerThread"))
206            .spawn(move || {
207                let mut scheduler = TimerScheduler::default();
208
209                loop {
210                    select! {
211                        recv(receiver) -> message => {
212                            match message {
213                                Ok(TimerThreadMessage::Request(request)) => {
214                                    scheduler.schedule_timer(request);
215                                },
216                                _ => return,
217                            }
218                        },
219                        recv(scheduler.wait_channel()) -> _message => {
220                            scheduler.dispatch_completed_timers();
221                        },
222                    };
223                }
224            })
225            .expect("Could not create RefreshDriver timer thread.");
226
227        Self {
228            sender,
229            join_handle: Some(join_handle),
230        }
231    }
232}
233
234impl TimerRefreshDriver {
235    fn queue_timer(&self, duration: Duration, callback: BoxedTimerCallback) {
236        let _ = self
237            .sender
238            .send(TimerThreadMessage::Request(TimerEventRequest {
239                callback,
240                duration,
241            }));
242    }
243}
244
245impl RefreshDriver for TimerRefreshDriver {
246    fn observe_next_frame(&self, new_start_frame_callback: Box<dyn Fn() + Send + 'static>) {
247        const FRAME_DURATION: Duration = Duration::from_millis(1000 / 120);
248        self.queue_timer(FRAME_DURATION, new_start_frame_callback);
249    }
250}
251
252impl Drop for TimerRefreshDriver {
253    fn drop(&mut self) {
254        let _ = self.sender.send(TimerThreadMessage::Quit);
255        if let Some(join_handle) = self.join_handle.take() {
256            let _ = join_handle.join();
257        }
258    }
259}