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