compositing/
compositor.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, Ref, RefCell, RefMut};
6use std::collections::HashMap;
7use std::env;
8use std::fs::create_dir_all;
9use std::rc::Rc;
10use std::sync::{Arc, Mutex};
11use std::time::{SystemTime, UNIX_EPOCH};
12
13use base::generic_channel::RoutedReceiver;
14use base::id::{PainterId, WebViewId};
15use bitflags::bitflags;
16use canvas_traits::webgl::WebGLThreads;
17use compositing_traits::{CompositorMsg, WebRenderExternalImageRegistry, WebViewTrait};
18use constellation_traits::EmbedderToConstellationMessage;
19use crossbeam_channel::Sender;
20use dpi::PhysicalSize;
21use embedder_traits::{
22    InputEventAndId, InputEventId, InputEventResult, ScreenshotCaptureError, Scroll, ShutdownState,
23    ViewportDetails, WebViewPoint, WebViewRect,
24};
25use euclid::{Scale, Size2D};
26use image::RgbaImage;
27use ipc_channel::ipc::{self};
28use log::debug;
29use profile_traits::mem::{
30    ProcessReports, ProfilerRegistration, Report, ReportKind, perform_memory_report,
31};
32use profile_traits::path;
33use profile_traits::time::{self as profile_time};
34use servo_geometry::DeviceIndependentPixel;
35use style_traits::CSSPixel;
36use webrender::{CaptureBits, MemoryReport};
37use webrender_api::units::{DevicePixel, DevicePoint, DeviceRect};
38
39use crate::InitialCompositorState;
40use crate::painter::Painter;
41use crate::webview_renderer::UnknownWebView;
42
43/// An option to control what kind of WebRender debugging is enabled while Servo is running.
44#[derive(Copy, Clone)]
45pub enum WebRenderDebugOption {
46    Profiler,
47    TextureCacheDebug,
48    RenderTargetDebug,
49}
50
51/// NB: Never block on the constellation, because sometimes the constellation blocks on us.
52pub struct IOCompositor {
53    /// All of the [`Painters`] for this [`IOCompositor`]. Each [`Painter`] handles painting to
54    /// a single [`RenderingContext`].
55    painters: Vec<Rc<RefCell<Painter>>>,
56
57    /// Tracks whether we are in the process of shutting down, or have shut down and should close
58    /// the compositor. This is shared with the `Servo` instance.
59    shutdown_state: Rc<Cell<ShutdownState>>,
60
61    /// The port on which we receive messages.
62    compositor_receiver: RoutedReceiver<CompositorMsg>,
63
64    /// The channel on which messages can be sent to the constellation.
65    pub(crate) embedder_to_constellation_sender: Sender<EmbedderToConstellationMessage>,
66
67    /// The channel on which messages can be sent to the time profiler.
68    time_profiler_chan: profile_time::ProfilerChan,
69
70    /// A handle to the memory profiler which will automatically unregister
71    /// when it's dropped.
72    _mem_profiler_registration: ProfilerRegistration,
73}
74
75/// Why we need to be repainted. This is used for debugging.
76#[derive(Clone, Copy, Default, PartialEq)]
77pub(crate) struct RepaintReason(u8);
78
79bitflags! {
80    impl RepaintReason: u8 {
81        /// We're performing the single repaint in headless mode.
82        const ReadyForScreenshot = 1 << 0;
83        /// We're performing a repaint to run an animation.
84        const ChangedAnimationState = 1 << 1;
85        /// A new WebRender frame has arrived.
86        const NewWebRenderFrame = 1 << 2;
87        /// The window has been resized and will need to be synchronously repainted.
88        const Resize = 1 << 3;
89    }
90}
91
92impl IOCompositor {
93    pub fn new(state: InitialCompositorState) -> Rc<RefCell<Self>> {
94        let registration = state.mem_profiler_chan.prepare_memory_reporting(
95            "compositor".into(),
96            state.compositor_proxy.clone(),
97            CompositorMsg::CollectMemoryReport,
98        );
99
100        let compositor = Rc::new(RefCell::new(IOCompositor {
101            painters: Default::default(),
102            shutdown_state: state.shutdown_state,
103            compositor_receiver: state.receiver,
104            embedder_to_constellation_sender: state.embedder_to_constellation_sender.clone(),
105            time_profiler_chan: state.time_profiler_chan,
106            _mem_profiler_registration: registration,
107        }));
108
109        let painter = Painter::new(
110            state.rendering_context,
111            state.compositor_proxy,
112            state.event_loop_waker,
113            state.refresh_driver,
114            state.shaders_path,
115            compositor.borrow().embedder_to_constellation_sender.clone(),
116            #[cfg(feature = "webxr")]
117            state.webxr_registry,
118        );
119
120        compositor
121            .borrow_mut()
122            .painters
123            .push(Rc::new(RefCell::new(painter)));
124
125        compositor
126    }
127
128    pub(crate) fn painter<'a>(&'a self) -> Ref<'a, Painter> {
129        self.painters[0].borrow()
130    }
131
132    pub(crate) fn painter_mut<'a>(&'a self) -> RefMut<'a, Painter> {
133        self.painters[0].borrow_mut()
134    }
135
136    pub fn painter_id(&self) -> PainterId {
137        self.painters[0].borrow().painter_id
138    }
139
140    pub fn deinit(&mut self) {
141        for painter in &self.painters {
142            painter.borrow_mut().deinit();
143        }
144    }
145
146    pub fn rendering_context_size(&self) -> Size2D<u32, DevicePixel> {
147        self.painter().rendering_context.size2d()
148    }
149
150    pub fn webgl_threads(&self) -> WebGLThreads {
151        self.painter().webgl_threads.clone()
152    }
153
154    pub fn webrender_external_images(&self) -> Arc<Mutex<WebRenderExternalImageRegistry>> {
155        self.painter().webrender_external_images.clone()
156    }
157
158    pub fn webxr_running(&self) -> bool {
159        #[cfg(feature = "webxr")]
160        {
161            self.painter().webxr_main_thread.running()
162        }
163        #[cfg(not(feature = "webxr"))]
164        {
165            false
166        }
167    }
168
169    #[cfg(feature = "webxr")]
170    pub fn webxr_main_thread_registry(&self) -> webxr_api::Registry {
171        self.painter().webxr_main_thread.registry()
172    }
173
174    #[cfg(feature = "webgpu")]
175    pub fn webgpu_image_map(&self) -> webgpu::canvas_context::WGPUImageMap {
176        self.painter().webgpu_image_map.clone()
177    }
178
179    pub fn webviews_needing_repaint(&self) -> Vec<WebViewId> {
180        self.painters
181            .iter()
182            .flat_map(|painter| painter.borrow().webviews_needing_repaint())
183            .collect()
184    }
185
186    pub fn finish_shutting_down(&self) {
187        // Drain compositor port, sometimes messages contain channels that are blocking
188        // another thread from finishing (i.e. SetFrameTree).
189        while self.compositor_receiver.try_recv().is_ok() {}
190
191        // Tell the profiler, memory profiler, and scrolling timer to shut down.
192        if let Ok((sender, receiver)) = ipc::channel() {
193            self.time_profiler_chan
194                .send(profile_time::ProfilerMsg::Exit(sender));
195            let _ = receiver.recv();
196        }
197    }
198
199    fn handle_browser_message(&self, msg: CompositorMsg) {
200        trace_msg_from_constellation!(msg, "{msg:?}");
201
202        match self.shutdown_state() {
203            ShutdownState::NotShuttingDown => {},
204            ShutdownState::ShuttingDown => {
205                self.handle_browser_message_while_shutting_down(msg);
206                return;
207            },
208            ShutdownState::FinishedShuttingDown => {
209                // Messages to the compositor are ignored after shutdown is complete.
210                return;
211            },
212        }
213
214        match msg {
215            CompositorMsg::CollectMemoryReport(sender) => {
216                self.collect_memory_report(sender);
217            },
218            CompositorMsg::ChangeRunningAnimationsState(
219                webview_id,
220                pipeline_id,
221                animation_state,
222            ) => {
223                self.painter_mut().change_running_animations_state(
224                    webview_id,
225                    pipeline_id,
226                    animation_state,
227                );
228            },
229            CompositorMsg::CreateOrUpdateWebView(frame_tree) => {
230                self.painter_mut().set_frame_tree_for_webview(&frame_tree);
231            },
232            CompositorMsg::RemoveWebView(webview_id) => {
233                self.painter_mut().remove_webview(webview_id);
234            },
235            CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => {
236                self.painter_mut()
237                    .set_throttled(webview_id, pipeline_id, throttled);
238            },
239            CompositorMsg::PipelineExited(webview_id, pipeline_id, pipeline_exit_source) => {
240                self.painter_mut().notify_pipeline_exited(
241                    webview_id,
242                    pipeline_id,
243                    pipeline_exit_source,
244                );
245            },
246            CompositorMsg::NewWebRenderFrameReady(..) => {
247                unreachable!("New WebRender frames should be handled in the caller.");
248            },
249            CompositorMsg::SendInitialTransaction(webview_id, pipeline_id) => {
250                self.painter_mut()
251                    .send_initial_pipeline_transaction(webview_id, pipeline_id);
252            },
253            CompositorMsg::ScrollNodeByDelta(
254                webview_id,
255                pipeline_id,
256                offset,
257                external_scroll_id,
258            ) => {
259                self.painter_mut().scroll_node_by_delta(
260                    webview_id,
261                    pipeline_id,
262                    offset,
263                    external_scroll_id,
264                );
265            },
266            CompositorMsg::ScrollViewportByDelta(webview_id, delta) => {
267                self.painter_mut()
268                    .scroll_viewport_by_delta(webview_id, delta);
269            },
270            CompositorMsg::UpdateEpoch {
271                webview_id,
272                pipeline_id,
273                epoch,
274            } => {
275                self.painter_mut()
276                    .update_epoch(webview_id, pipeline_id, epoch);
277            },
278            CompositorMsg::SendDisplayList {
279                webview_id,
280                display_list_descriptor,
281                display_list_receiver,
282            } => {
283                self.painter_mut().handle_new_display_list(
284                    webview_id,
285                    display_list_descriptor,
286                    display_list_receiver,
287                );
288            },
289            CompositorMsg::GenerateFrame => {
290                self.painter_mut().generate_frame_for_script();
291            },
292            CompositorMsg::GenerateImageKey(sender) => {
293                let _ = sender.send(self.painter().generate_image_key());
294            },
295            CompositorMsg::GenerateImageKeysForPipeline(pipeline_id) => {
296                let _ = self.embedder_to_constellation_sender.send(
297                    EmbedderToConstellationMessage::SendImageKeysForPipeline(
298                        pipeline_id,
299                        self.painter().generate_image_keys(),
300                    ),
301                );
302            },
303            CompositorMsg::UpdateImages(updates) => {
304                self.painter_mut().update_images(updates);
305            },
306            CompositorMsg::DelayNewFrameForCanvas(pipeline_id, canvas_epoch, image_keys) => self
307                .painter_mut()
308                .delay_new_frames_for_canvas(pipeline_id, canvas_epoch, image_keys),
309            CompositorMsg::AddFont(font_key, data, index) => {
310                self.painter_mut().add_font(font_key, data, index);
311            },
312            CompositorMsg::AddSystemFont(font_key, native_handle) => {
313                self.painter_mut().add_system_font(font_key, native_handle);
314            },
315            CompositorMsg::AddFontInstance(
316                font_instance_key,
317                font_key,
318                size,
319                flags,
320                variations,
321            ) => {
322                self.painter_mut().add_font_instance(
323                    font_instance_key,
324                    font_key,
325                    size,
326                    flags,
327                    variations,
328                );
329            },
330            CompositorMsg::RemoveFonts(keys, instance_keys) => {
331                self.painter_mut().remove_fonts(keys, instance_keys);
332            },
333            CompositorMsg::GenerateFontKeys(
334                number_of_font_keys,
335                number_of_font_instance_keys,
336                result_sender,
337                _painter_id,
338            ) => {
339                let _ = result_sender.send(
340                    self.painter_mut()
341                        .generate_font_keys(number_of_font_keys, number_of_font_instance_keys),
342                );
343            },
344            CompositorMsg::Viewport(webview_id, viewport_description) => {
345                self.painter_mut()
346                    .set_viewport_description(webview_id, viewport_description);
347            },
348            CompositorMsg::ScreenshotReadinessReponse(webview_id, pipelines_and_epochs) => {
349                self.painter()
350                    .handle_screenshot_readiness_reply(webview_id, pipelines_and_epochs);
351            },
352            CompositorMsg::SendLCPCandidate(lcp_candidate, webview_id, pipeline_id, epoch) => {
353                self.painter_mut().append_lcp_candidate(
354                    lcp_candidate,
355                    webview_id,
356                    pipeline_id,
357                    epoch,
358                );
359            },
360        }
361    }
362
363    fn collect_memory_report(&self, sender: profile_traits::mem::ReportsChan) {
364        let mut memory_report = MemoryReport::default();
365        for painter in &self.painters {
366            memory_report += painter.borrow().report_memory();
367        }
368
369        let mut reports = vec![
370            Report {
371                path: path!["webrender", "fonts"],
372                kind: ReportKind::ExplicitJemallocHeapSize,
373                size: memory_report.fonts,
374            },
375            Report {
376                path: path!["webrender", "images"],
377                kind: ReportKind::ExplicitJemallocHeapSize,
378                size: memory_report.images,
379            },
380            Report {
381                path: path!["webrender", "display-list"],
382                kind: ReportKind::ExplicitJemallocHeapSize,
383                size: memory_report.display_list,
384            },
385        ];
386
387        perform_memory_report(|ops| {
388            reports.push(Report {
389                path: path!["compositor", "scroll-tree"],
390                kind: ReportKind::ExplicitJemallocHeapSize,
391                size: self
392                    .painter()
393                    .webview_renderers
394                    .scroll_trees_memory_usage(ops),
395            });
396        });
397
398        sender.send(ProcessReports::new(reports));
399    }
400
401    /// Handle messages sent to the compositor during the shutdown process. In general,
402    /// the things the compositor can do in this state are limited. It's very important to
403    /// answer any synchronous messages though as other threads might be waiting on the
404    /// results to finish their own shut down process. We try to do as little as possible
405    /// during this time.
406    ///
407    /// When that involves generating WebRender ids, our approach here is to simply
408    /// generate them, but assume they will never be used, since once shutting down the
409    /// compositor no longer does any WebRender frame generation.
410    fn handle_browser_message_while_shutting_down(&self, msg: CompositorMsg) {
411        match msg {
412            CompositorMsg::PipelineExited(webview_id, pipeline_id, pipeline_exit_source) => {
413                self.painter_mut().notify_pipeline_exited(
414                    webview_id,
415                    pipeline_id,
416                    pipeline_exit_source,
417                );
418            },
419            CompositorMsg::GenerateImageKey(sender) => {
420                let _ = sender.send(self.painter().webrender_api.generate_image_key());
421            },
422            CompositorMsg::GenerateFontKeys(
423                number_of_font_keys,
424                number_of_font_instance_keys,
425                result_sender,
426                _painter_id,
427            ) => {
428                let _ = result_sender.send(
429                    self.painter_mut()
430                        .generate_font_keys(number_of_font_keys, number_of_font_instance_keys),
431                );
432            },
433            _ => {
434                debug!("Ignoring message ({:?} while shutting down", msg);
435            },
436        }
437    }
438
439    pub fn add_webview(&self, webview: Box<dyn WebViewTrait>, viewport_details: ViewportDetails) {
440        self.painter_mut().add_webview(webview, viewport_details);
441    }
442
443    pub fn show_webview(
444        &self,
445        webview_id: WebViewId,
446        hide_others: bool,
447    ) -> Result<(), UnknownWebView> {
448        self.painter_mut().show_webview(webview_id, hide_others)
449    }
450
451    pub fn hide_webview(&self, webview_id: WebViewId) -> Result<(), UnknownWebView> {
452        self.painter_mut().hide_webview(webview_id)
453    }
454
455    pub fn raise_webview_to_top(
456        &self,
457        webview_id: WebViewId,
458        hide_others: bool,
459    ) -> Result<(), UnknownWebView> {
460        self.painter_mut()
461            .raise_webview_to_top(webview_id, hide_others)
462    }
463
464    pub fn move_resize_webview(&self, webview_id: WebViewId, rect: DeviceRect) {
465        if self.shutdown_state() != ShutdownState::NotShuttingDown {
466            return;
467        }
468        self.painter_mut().move_resize_webview(webview_id, rect);
469    }
470
471    pub fn set_hidpi_scale_factor(
472        &self,
473        webview_id: WebViewId,
474        new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
475    ) {
476        if self.shutdown_state() != ShutdownState::NotShuttingDown {
477            return;
478        }
479        self.painter_mut()
480            .set_hidpi_scale_factor(webview_id, new_scale_factor);
481    }
482
483    pub fn resize_rendering_context(&self, new_size: PhysicalSize<u32>) {
484        if self.shutdown_state() != ShutdownState::NotShuttingDown {
485            return;
486        }
487        self.painter_mut().resize_rendering_context(new_size);
488    }
489
490    pub fn set_page_zoom(&self, webview_id: WebViewId, new_zoom: f32) {
491        if self.shutdown_state() != ShutdownState::NotShuttingDown {
492            return;
493        }
494        self.painter_mut().set_page_zoom(webview_id, new_zoom);
495    }
496
497    pub fn page_zoom(&self, webview_id: WebViewId) -> f32 {
498        self.painter().page_zoom(webview_id)
499    }
500
501    /// Render the WebRender scene to the active `RenderingContext`.
502    pub fn render(&self) {
503        self.painter_mut().render(&self.time_profiler_chan);
504    }
505
506    /// Get the message receiver for this [`IOCompositor`].
507    pub fn receiver(&self) -> &RoutedReceiver<CompositorMsg> {
508        &self.compositor_receiver
509    }
510
511    #[servo_tracing::instrument(skip_all)]
512    pub fn handle_messages(&self, mut messages: Vec<CompositorMsg>) {
513        // Pull out the `NewWebRenderFrameReady` messages from the list of messages and handle them
514        // at the end of this function. This prevents overdraw when more than a single message of
515        // this type of received. In addition, if any of these frames need a repaint, that reflected
516        // when calling `handle_new_webrender_frame_ready`.
517        let mut saw_webrender_frame_ready_for_painter = HashMap::new();
518        messages.retain(|message| match message {
519            CompositorMsg::NewWebRenderFrameReady(painter_id, _document_id, need_repaint) => {
520                self.painter().decrement_pending_frames();
521                *saw_webrender_frame_ready_for_painter
522                    .entry(*painter_id)
523                    .or_insert(*need_repaint) |= *need_repaint;
524
525                false
526            },
527            _ => true,
528        });
529
530        for message in messages {
531            self.handle_browser_message(message);
532            if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
533                return;
534            }
535        }
536
537        for (_, repaint_needed) in saw_webrender_frame_ready_for_painter.iter() {
538            self.painter()
539                .handle_new_webrender_frame_ready(*repaint_needed);
540        }
541    }
542
543    #[servo_tracing::instrument(skip_all)]
544    pub fn perform_updates(&self) -> bool {
545        if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
546            return false;
547        }
548
549        for painter in &self.painters {
550            painter.borrow_mut().perform_updates();
551        }
552
553        self.shutdown_state() != ShutdownState::FinishedShuttingDown
554    }
555
556    pub fn toggle_webrender_debug(&self, option: WebRenderDebugOption) {
557        for painter in &self.painters {
558            painter.borrow_mut().toggle_webrender_debug(option);
559        }
560    }
561
562    pub fn capture_webrender(&self) {
563        let capture_id = SystemTime::now()
564            .duration_since(UNIX_EPOCH)
565            .unwrap_or_default()
566            .as_secs()
567            .to_string();
568        let available_path = [env::current_dir(), Ok(env::temp_dir())]
569            .iter()
570            .filter_map(|val| {
571                val.as_ref()
572                    .map(|dir| dir.join("webrender-captures").join(&capture_id))
573                    .ok()
574            })
575            .find(|val| create_dir_all(val).is_ok());
576
577        let Some(capture_path) = available_path else {
578            eprintln!("Couldn't create a path for WebRender captures.");
579            return;
580        };
581
582        println!("Saving WebRender capture to {capture_path:?}");
583        self.painter()
584            .webrender_api
585            .save_capture(capture_path.clone(), CaptureBits::all());
586    }
587
588    pub fn notify_input_event(&self, webview_id: WebViewId, event: InputEventAndId) {
589        if self.shutdown_state() != ShutdownState::NotShuttingDown {
590            return;
591        }
592        self.painter_mut().notify_input_event(webview_id, event);
593    }
594
595    pub fn notify_scroll_event(&self, webview_id: WebViewId, scroll: Scroll, point: WebViewPoint) {
596        if self.shutdown_state() != ShutdownState::NotShuttingDown {
597            return;
598        }
599        self.painter_mut()
600            .notify_scroll_event(webview_id, scroll, point);
601    }
602
603    pub fn pinch_zoom(&self, webview_id: WebViewId, pinch_zoom_delta: f32, center: DevicePoint) {
604        if self.shutdown_state() != ShutdownState::NotShuttingDown {
605            return;
606        }
607        self.painter_mut()
608            .pinch_zoom(webview_id, pinch_zoom_delta, center);
609    }
610
611    pub fn device_pixels_per_page_pixel(
612        &self,
613        webview_id: WebViewId,
614    ) -> Scale<f32, CSSPixel, DevicePixel> {
615        self.painter_mut().device_pixels_per_page_pixel(webview_id)
616    }
617
618    pub(crate) fn shutdown_state(&self) -> ShutdownState {
619        self.shutdown_state.get()
620    }
621
622    pub fn request_screenshot(
623        &self,
624        webview_id: WebViewId,
625        rect: Option<WebViewRect>,
626        callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
627    ) {
628        self.painter()
629            .request_screenshot(webview_id, rect, callback);
630    }
631
632    pub fn notify_input_event_handled(
633        &self,
634        webview_id: WebViewId,
635        input_event_id: InputEventId,
636        result: InputEventResult,
637    ) {
638        self.painter_mut()
639            .notify_input_event_handled(webview_id, input_event_id, result);
640    }
641}