| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| |
| //! The interface to the `compositing` crate. |
| |
| use std::fmt::{Debug, Error, Formatter}; |
| |
| use base::Epoch; |
| use base::id::{PipelineId, WebViewId}; |
| use crossbeam_channel::Sender; |
| use embedder_traits::{AnimationState, EventLoopWaker, TouchEventResult}; |
| use ipc_channel::ipc::IpcSender; |
| use log::warn; |
| use malloc_size_of_derive::MallocSizeOf; |
| use smallvec::SmallVec; |
| use strum_macros::IntoStaticStr; |
| use webrender_api::{DocumentId, FontVariation}; |
| |
| pub mod display_list; |
| pub mod rendering_context; |
| pub mod viewport_description; |
| |
| use std::collections::HashMap; |
| use std::sync::{Arc, Mutex}; |
| |
| use base::generic_channel::{self, GenericCallback, GenericSender}; |
| use bitflags::bitflags; |
| use display_list::CompositorDisplayListInfo; |
| use embedder_traits::ScreenGeometry; |
| use euclid::default::Size2D as UntypedSize2D; |
| use ipc_channel::ipc::{self, IpcSharedMemory}; |
| use profile_traits::mem::{OpaqueSender, ReportsChan}; |
| use serde::{Deserialize, Serialize}; |
| pub use webrender_api::ExternalImageSource; |
| use webrender_api::units::{LayoutVector2D, TexelRect}; |
| use webrender_api::{ |
| BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData, |
| ExternalImageHandler, ExternalImageId, ExternalScrollId, FontInstanceFlags, FontInstanceKey, |
| FontKey, ImageData, ImageDescriptor, ImageKey, NativeFontHandle, |
| PipelineId as WebRenderPipelineId, |
| }; |
| |
| use crate::viewport_description::ViewportDescription; |
| |
| /// Sends messages to the compositor. |
| #[derive(Clone)] |
| pub struct CompositorProxy { |
| pub sender: Sender<Result<CompositorMsg, ipc_channel::Error>>, |
| /// Access to [`Self::sender`] that is possible to send across an IPC |
| /// channel. These messages are routed via the router thread to |
| /// [`Self::sender`]. |
| pub cross_process_compositor_api: CrossProcessCompositorApi, |
| pub event_loop_waker: Box<dyn EventLoopWaker>, |
| } |
| |
| impl OpaqueSender<CompositorMsg> for CompositorProxy { |
| fn send(&self, message: CompositorMsg) { |
| CompositorProxy::send(self, message) |
| } |
| } |
| |
| impl CompositorProxy { |
| pub fn send(&self, msg: CompositorMsg) { |
| self.route_msg(Ok(msg)) |
| } |
| |
| /// Helper method to route a deserialized IPC message to the receiver. |
| /// |
| /// This method is a temporary solution, and will be removed when migrating |
| /// to `GenericChannel`. |
| pub fn route_msg(&self, msg: Result<CompositorMsg, ipc_channel::Error>) { |
| if let Err(err) = self.sender.send(msg) { |
| warn!("Failed to send response ({:?}).", err); |
| } |
| self.event_loop_waker.wake(); |
| } |
| } |
| |
| /// Messages from (or via) the constellation thread to the compositor. |
| #[derive(Deserialize, IntoStaticStr, Serialize)] |
| pub enum CompositorMsg { |
| /// Alerts the compositor that the given pipeline has changed whether it is running animations. |
| ChangeRunningAnimationsState(WebViewId, PipelineId, AnimationState), |
| /// Create or update a webview, given its frame tree. |
| CreateOrUpdateWebView(SendableFrameTree), |
| /// Remove a webview. |
| RemoveWebView(WebViewId), |
| /// Script has handled a touch event, and either prevented or allowed default actions. |
| TouchEventProcessed(WebViewId, TouchEventResult), |
| /// A reply to the compositor asking if the output image is stable. |
| IsReadyToSaveImageReply(bool), |
| /// Set whether to use less resources by stopping animations. |
| SetThrottled(WebViewId, PipelineId, bool), |
| /// WebRender has produced a new frame. This message informs the compositor that |
| /// the frame is ready. It contains a bool to indicate if it needs to composite and the |
| /// `DocumentId` of the new frame. |
| NewWebRenderFrameReady(DocumentId, bool), |
| /// Script or the Constellation is notifying the renderer that a Pipeline has finished |
| /// shutting down. The renderer will not discard the Pipeline until both report that |
| /// they have fully shut it down, to avoid recreating it due to any subsequent |
| /// messages. |
| PipelineExited(WebViewId, PipelineId, PipelineExitSource), |
| /// The load of a page has completed |
| LoadComplete(WebViewId), |
| /// Inform WebRender of the existence of this pipeline. |
| SendInitialTransaction(WebRenderPipelineId), |
| /// Perform a scroll operation. |
| SendScrollNode( |
| WebViewId, |
| WebRenderPipelineId, |
| LayoutVector2D, |
| ExternalScrollId, |
| ), |
| /// Inform WebRender of a new display list for the given pipeline. |
| SendDisplayList { |
| /// The [`WebViewId`] that this display list belongs to. |
| webview_id: WebViewId, |
| /// A descriptor of this display list used to construct this display list from raw data. |
| display_list_descriptor: BuiltDisplayListDescriptor, |
| /// An [ipc::IpcBytesReceiver] used to send the raw data of the display list. |
| display_list_receiver: ipc::IpcBytesReceiver, |
| }, |
| /// Ask the renderer to generate a frame for the current set of display lists that |
| /// have been sent to the renderer. |
| GenerateFrame, |
| /// Create a new image key. The result will be returned via the |
| /// provided channel sender. |
| GenerateImageKey(IpcSender<ImageKey>), |
| /// The same as the above but it will be forwarded to the pipeline instead |
| /// of send via a channel. |
| GenerateImageKeysForPipeline(PipelineId), |
| /// Perform a resource update operation. |
| UpdateImages(SmallVec<[ImageUpdate; 1]>), |
| /// Pause all pipeline display list processing for the given pipeline until the |
| /// following image updates have been received. This is used to ensure that canvas |
| /// elements have had a chance to update their rendering and send the image update to |
| /// the renderer before their associated display list is actually displayed. |
| DelayNewFrameForCanvas(PipelineId, Epoch, Vec<ImageKey>), |
| |
| /// Generate a new batch of font keys which can be used to allocate |
| /// keys asynchronously. |
| GenerateFontKeys( |
| usize, |
| usize, |
| GenericSender<(Vec<FontKey>, Vec<FontInstanceKey>)>, |
| ), |
| /// Add a font with the given data and font key. |
| AddFont(FontKey, Arc<IpcSharedMemory>, u32), |
| /// Add a system font with the given font key and handle. |
| AddSystemFont(FontKey, NativeFontHandle), |
| /// Add an instance of a font with the given instance key. |
| AddFontInstance( |
| FontInstanceKey, |
| FontKey, |
| f32, |
| FontInstanceFlags, |
| Vec<FontVariation>, |
| ), |
| /// Remove the given font resources from our WebRender instance. |
| RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>), |
| /// Measure the current memory usage associated with the compositor. |
| /// The report must be sent on the provided channel once it's complete. |
| CollectMemoryReport(ReportsChan), |
| /// A top-level frame has parsed a viewport metatag and is sending the new constraints. |
| Viewport(WebViewId, ViewportDescription), |
| } |
| |
| impl Debug for CompositorMsg { |
| fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { |
| let string: &'static str = self.into(); |
| write!(formatter, "{string}") |
| } |
| } |
| |
| #[derive(Deserialize, Serialize)] |
| pub struct SendableFrameTree { |
| pub pipeline: CompositionPipeline, |
| pub children: Vec<SendableFrameTree>, |
| } |
| |
| /// The subset of the pipeline that is needed for layer composition. |
| #[derive(Clone, Deserialize, Serialize)] |
| pub struct CompositionPipeline { |
| pub id: PipelineId, |
| pub webview_id: WebViewId, |
| } |
| |
| /// A mechanism to send messages from ScriptThread to the parent process' WebRender instance. |
| #[derive(Clone, Deserialize, MallocSizeOf, Serialize)] |
| pub struct CrossProcessCompositorApi(GenericCallback<CompositorMsg>); |
| |
| impl CrossProcessCompositorApi { |
| /// Create a new [`CrossProcessCompositorApi`] struct. |
| pub fn new(callback: GenericCallback<CompositorMsg>) -> Self { |
| CrossProcessCompositorApi(callback) |
| } |
| |
| /// Create a new [`CrossProcessCompositorApi`] struct that does not have a listener on the other |
| /// end to use for unit testing. |
| pub fn dummy() -> Self { |
| let callback = GenericCallback::new(|_msg| ()).unwrap(); |
| Self(callback) |
| } |
| |
| /// Inform WebRender of the existence of this pipeline. |
| pub fn send_initial_transaction(&self, pipeline: WebRenderPipelineId) { |
| if let Err(e) = self.0.send(CompositorMsg::SendInitialTransaction(pipeline)) { |
| warn!("Error sending initial transaction: {}", e); |
| } |
| } |
| |
| /// Perform a scroll operation. |
| pub fn send_scroll_node( |
| &self, |
| webview_id: WebViewId, |
| pipeline_id: WebRenderPipelineId, |
| point: LayoutVector2D, |
| scroll_id: ExternalScrollId, |
| ) { |
| if let Err(e) = self.0.send(CompositorMsg::SendScrollNode( |
| webview_id, |
| pipeline_id, |
| point, |
| scroll_id, |
| )) { |
| warn!("Error sending scroll node: {}", e); |
| } |
| } |
| |
| pub fn delay_new_frame_for_canvas( |
| &self, |
| pipeline_id: PipelineId, |
| canvas_epoch: Epoch, |
| image_keys: Vec<ImageKey>, |
| ) { |
| if let Err(error) = self.0.send(CompositorMsg::DelayNewFrameForCanvas( |
| pipeline_id, |
| canvas_epoch, |
| image_keys, |
| )) { |
| warn!("Error delaying frames for canvas image updates {error:?}"); |
| } |
| } |
| |
| /// Inform WebRender of a new display list for the given pipeline. |
| pub fn send_display_list( |
| &self, |
| webview_id: WebViewId, |
| display_list_info: &CompositorDisplayListInfo, |
| list: BuiltDisplayList, |
| ) { |
| let (display_list_data, display_list_descriptor) = list.into_data(); |
| let (display_list_sender, display_list_receiver) = ipc::bytes_channel().unwrap(); |
| if let Err(e) = self.0.send(CompositorMsg::SendDisplayList { |
| webview_id, |
| display_list_descriptor, |
| display_list_receiver, |
| }) { |
| warn!("Error sending display list: {}", e); |
| } |
| |
| let display_list_info_serialized = |
| bincode::serialize(&display_list_info).unwrap_or_default(); |
| if let Err(error) = display_list_sender.send(&display_list_info_serialized) { |
| warn!("Error sending display list info: {error}"); |
| } |
| |
| if let Err(error) = display_list_sender.send(&display_list_data.items_data) { |
| warn!("Error sending display list items: {error}"); |
| } |
| if let Err(error) = display_list_sender.send(&display_list_data.cache_data) { |
| warn!("Error sending display list cache data: {error}"); |
| } |
| if let Err(error) = display_list_sender.send(&display_list_data.spatial_tree) { |
| warn!("Error sending display spatial tree: {error}"); |
| } |
| } |
| |
| /// Ask the Servo renderer to generate a new frame after having new display lists. |
| pub fn generate_frame(&self) { |
| if let Err(error) = self.0.send(CompositorMsg::GenerateFrame) { |
| warn!("Error generating frame: {error}"); |
| } |
| } |
| |
| /// Create a new image key. Blocks until the key is available. |
| pub fn generate_image_key_blocking(&self) -> Option<ImageKey> { |
| let (sender, receiver) = ipc::channel().unwrap(); |
| self.0.send(CompositorMsg::GenerateImageKey(sender)).ok()?; |
| receiver.recv().ok() |
| } |
| |
| /// Sends a message to the compositor for creating new image keys. |
| /// The compositor will then send a batch of keys over the constellation to the script_thread |
| /// and the appropriate pipeline. |
| pub fn generate_image_key_async(&self, pipeline_id: PipelineId) { |
| if let Err(e) = self |
| .0 |
| .send(CompositorMsg::GenerateImageKeysForPipeline(pipeline_id)) |
| { |
| warn!("Could not send image keys to Compositor {}", e); |
| } |
| } |
| |
| pub fn add_image( |
| &self, |
| key: ImageKey, |
| descriptor: ImageDescriptor, |
| data: SerializableImageData, |
| ) { |
| self.update_images([ImageUpdate::AddImage(key, descriptor, data)].into()); |
| } |
| |
| pub fn update_image( |
| &self, |
| key: ImageKey, |
| descriptor: ImageDescriptor, |
| data: SerializableImageData, |
| epoch: Option<Epoch>, |
| ) { |
| self.update_images([ImageUpdate::UpdateImage(key, descriptor, data, epoch)].into()); |
| } |
| |
| pub fn delete_image(&self, key: ImageKey) { |
| self.update_images([ImageUpdate::DeleteImage(key)].into()); |
| } |
| |
| /// Perform an image resource update operation. |
| pub fn update_images(&self, updates: SmallVec<[ImageUpdate; 1]>) { |
| if let Err(e) = self.0.send(CompositorMsg::UpdateImages(updates)) { |
| warn!("error sending image updates: {}", e); |
| } |
| } |
| |
| pub fn remove_unused_font_resources( |
| &self, |
| keys: Vec<FontKey>, |
| instance_keys: Vec<FontInstanceKey>, |
| ) { |
| if keys.is_empty() && instance_keys.is_empty() { |
| return; |
| } |
| let _ = self.0.send(CompositorMsg::RemoveFonts(keys, instance_keys)); |
| } |
| |
| pub fn add_font_instance( |
| &self, |
| font_instance_key: FontInstanceKey, |
| font_key: FontKey, |
| size: f32, |
| flags: FontInstanceFlags, |
| variations: Vec<FontVariation>, |
| ) { |
| let _x = self.0.send(CompositorMsg::AddFontInstance( |
| font_instance_key, |
| font_key, |
| size, |
| flags, |
| variations, |
| )); |
| } |
| |
| pub fn add_font(&self, font_key: FontKey, data: Arc<IpcSharedMemory>, index: u32) { |
| let _ = self.0.send(CompositorMsg::AddFont(font_key, data, index)); |
| } |
| |
| pub fn add_system_font(&self, font_key: FontKey, handle: NativeFontHandle) { |
| let _ = self.0.send(CompositorMsg::AddSystemFont(font_key, handle)); |
| } |
| |
| pub fn fetch_font_keys( |
| &self, |
| number_of_font_keys: usize, |
| number_of_font_instance_keys: usize, |
| ) -> (Vec<FontKey>, Vec<FontInstanceKey>) { |
| let (sender, receiver) = generic_channel::channel().expect("Could not create IPC channel"); |
| let _ = self.0.send(CompositorMsg::GenerateFontKeys( |
| number_of_font_keys, |
| number_of_font_instance_keys, |
| sender, |
| )); |
| receiver.recv().unwrap() |
| } |
| |
| pub fn viewport(&self, webview_id: WebViewId, description: ViewportDescription) { |
| let _ = self |
| .0 |
| .send(CompositorMsg::Viewport(webview_id, description)); |
| } |
| |
| pub fn pipeline_exited( |
| &self, |
| webview_id: WebViewId, |
| pipeline_id: PipelineId, |
| source: PipelineExitSource, |
| ) { |
| let _ = self.0.send(CompositorMsg::PipelineExited( |
| webview_id, |
| pipeline_id, |
| source, |
| )); |
| } |
| } |
| |
| /// This trait is used as a bridge between the different GL clients |
| /// in Servo that handles WebRender ExternalImages and the WebRender |
| /// ExternalImageHandler API. |
| // |
| /// This trait is used to notify lock/unlock messages and get the |
| /// required info that WR needs. |
| pub trait WebrenderExternalImageApi { |
| fn lock(&mut self, id: u64) -> (ExternalImageSource<'_>, UntypedSize2D<i32>); |
| fn unlock(&mut self, id: u64); |
| } |
| |
| /// Type of Webrender External Image Handler. |
| pub enum WebrenderImageHandlerType { |
| WebGL, |
| Media, |
| WebGPU, |
| } |
| |
| /// List of Webrender external images to be shared among all external image |
| /// consumers (WebGL, Media, WebGPU). |
| /// It ensures that external image identifiers are unique. |
| #[derive(Default)] |
| pub struct WebrenderExternalImageRegistry { |
| /// Map of all generated external images. |
| external_images: HashMap<ExternalImageId, WebrenderImageHandlerType>, |
| /// Id generator for the next external image identifier. |
| next_image_id: u64, |
| } |
| |
| impl WebrenderExternalImageRegistry { |
| pub fn next_id(&mut self, handler_type: WebrenderImageHandlerType) -> ExternalImageId { |
| self.next_image_id += 1; |
| let key = ExternalImageId(self.next_image_id); |
| self.external_images.insert(key, handler_type); |
| key |
| } |
| |
| pub fn remove(&mut self, key: &ExternalImageId) { |
| self.external_images.remove(key); |
| } |
| |
| pub fn get(&self, key: &ExternalImageId) -> Option<&WebrenderImageHandlerType> { |
| self.external_images.get(key) |
| } |
| } |
| |
| /// WebRender External Image Handler implementation. |
| pub struct WebrenderExternalImageHandlers { |
| /// WebGL handler. |
| webgl_handler: Option<Box<dyn WebrenderExternalImageApi>>, |
| /// Media player handler. |
| media_handler: Option<Box<dyn WebrenderExternalImageApi>>, |
| /// WebGPU handler. |
| webgpu_handler: Option<Box<dyn WebrenderExternalImageApi>>, |
| /// Webrender external images. |
| external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, |
| } |
| |
| impl WebrenderExternalImageHandlers { |
| pub fn new() -> (Self, Arc<Mutex<WebrenderExternalImageRegistry>>) { |
| let external_images = Arc::new(Mutex::new(WebrenderExternalImageRegistry::default())); |
| ( |
| Self { |
| webgl_handler: None, |
| media_handler: None, |
| webgpu_handler: None, |
| external_images: external_images.clone(), |
| }, |
| external_images, |
| ) |
| } |
| |
| pub fn set_handler( |
| &mut self, |
| handler: Box<dyn WebrenderExternalImageApi>, |
| handler_type: WebrenderImageHandlerType, |
| ) { |
| match handler_type { |
| WebrenderImageHandlerType::WebGL => self.webgl_handler = Some(handler), |
| WebrenderImageHandlerType::Media => self.media_handler = Some(handler), |
| WebrenderImageHandlerType::WebGPU => self.webgpu_handler = Some(handler), |
| } |
| } |
| } |
| |
| impl ExternalImageHandler for WebrenderExternalImageHandlers { |
| /// Lock the external image. Then, WR could start to read the |
| /// image content. |
| /// The WR client should not change the image content until the |
| /// unlock() call. |
| fn lock(&mut self, key: ExternalImageId, _channel_index: u8) -> ExternalImage<'_> { |
| let external_images = self.external_images.lock().unwrap(); |
| let handler_type = external_images |
| .get(&key) |
| .expect("Tried to get unknown external image"); |
| match handler_type { |
| WebrenderImageHandlerType::WebGL => { |
| let (source, size) = self.webgl_handler.as_mut().unwrap().lock(key.0); |
| let texture_id = match source { |
| ExternalImageSource::NativeTexture(b) => b, |
| _ => panic!("Wrong type"), |
| }; |
| ExternalImage { |
| uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), |
| source: ExternalImageSource::NativeTexture(texture_id), |
| } |
| }, |
| WebrenderImageHandlerType::Media => { |
| let (source, size) = self.media_handler.as_mut().unwrap().lock(key.0); |
| let texture_id = match source { |
| ExternalImageSource::NativeTexture(b) => b, |
| _ => panic!("Wrong type"), |
| }; |
| ExternalImage { |
| uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), |
| source: ExternalImageSource::NativeTexture(texture_id), |
| } |
| }, |
| WebrenderImageHandlerType::WebGPU => { |
| let (source, size) = self.webgpu_handler.as_mut().unwrap().lock(key.0); |
| let buffer = match source { |
| ExternalImageSource::RawData(b) => b, |
| _ => panic!("Wrong type"), |
| }; |
| ExternalImage { |
| uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), |
| source: ExternalImageSource::RawData(buffer), |
| } |
| }, |
| } |
| } |
| |
| /// Unlock the external image. The WR should not read the image |
| /// content after this call. |
| fn unlock(&mut self, key: ExternalImageId, _channel_index: u8) { |
| let external_images = self.external_images.lock().unwrap(); |
| let handler_type = external_images |
| .get(&key) |
| .expect("Tried to get unknown external image"); |
| match handler_type { |
| WebrenderImageHandlerType::WebGL => self.webgl_handler.as_mut().unwrap().unlock(key.0), |
| WebrenderImageHandlerType::Media => self.media_handler.as_mut().unwrap().unlock(key.0), |
| WebrenderImageHandlerType::WebGPU => { |
| self.webgpu_handler.as_mut().unwrap().unlock(key.0) |
| }, |
| }; |
| } |
| } |
| |
| #[derive(Deserialize, Serialize)] |
| /// Serializable image updates that must be performed by WebRender. |
| pub enum ImageUpdate { |
| /// Register a new image. |
| AddImage(ImageKey, ImageDescriptor, SerializableImageData), |
| /// Delete a previously registered image registration. |
| DeleteImage(ImageKey), |
| /// Update an existing image registration. |
| UpdateImage( |
| ImageKey, |
| ImageDescriptor, |
| SerializableImageData, |
| Option<Epoch>, |
| ), |
| } |
| |
| impl Debug for ImageUpdate { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| match self { |
| Self::AddImage(image_key, image_desc, _) => f |
| .debug_tuple("AddImage") |
| .field(image_key) |
| .field(image_desc) |
| .finish(), |
| Self::DeleteImage(image_key) => f.debug_tuple("DeleteImage").field(image_key).finish(), |
| Self::UpdateImage(image_key, image_desc, _, epoch) => f |
| .debug_tuple("UpdateImage") |
| .field(image_key) |
| .field(image_desc) |
| .field(epoch) |
| .finish(), |
| } |
| } |
| } |
| |
| #[derive(Debug, Deserialize, Serialize)] |
| /// Serialized `ImageData`. It contains IPC byte channel receiver to prevent from loading bytes too |
| /// slow. |
| pub enum SerializableImageData { |
| /// A simple series of bytes, provided by the embedding and owned by WebRender. |
| /// The format is stored out-of-band, currently in ImageDescriptor. |
| Raw(IpcSharedMemory), |
| /// An image owned by the embedding, and referenced by WebRender. This may |
| /// take the form of a texture or a heap-allocated buffer. |
| External(ExternalImageData), |
| } |
| |
| impl From<SerializableImageData> for ImageData { |
| fn from(value: SerializableImageData) -> Self { |
| match value { |
| SerializableImageData::Raw(shared_memory) => ImageData::new(shared_memory.to_vec()), |
| SerializableImageData::External(image) => ImageData::External(image), |
| } |
| } |
| } |
| |
| /// A trait that exposes the embedding layer's `WebView` to the Servo renderer. |
| /// This is to prevent a dependency cycle between the renderer and the embedding |
| /// layer. |
| pub trait WebViewTrait { |
| fn id(&self) -> WebViewId; |
| fn screen_geometry(&self) -> Option<ScreenGeometry>; |
| fn set_animating(&self, new_value: bool); |
| } |
| |
| /// What entity is reporting that a `Pipeline` has exited. Only when all have |
| /// done this will the renderer discard its details. |
| #[derive(Clone, Copy, Default, Deserialize, PartialEq, Serialize)] |
| pub struct PipelineExitSource(u8); |
| |
| bitflags! { |
| impl PipelineExitSource: u8 { |
| const Script = 1 << 0; |
| const Constellation = 1 << 1; |
| } |
| } |