Files
servo/components/shared/compositing/lib.rs
Narfinger e64f021550 Allow WebViews and fonts to have a RenderingGroupId. (#39140)
Motivation: The font cache currently has to store a cache of Keys which
need to be given by the webrender instance.
Having a cache for every WebViewId in the future when we have every
webview have the different webrender::DocumentId might be too wasteful
to store this key cache per DocumentId. This proposes to include in the
WebViewId another id, the RenderingGroupId. This id can be easily
changed
to be equivalent to the DocumentId when we support multiple DocumentIds
for a unique Webrender instance.
Additionally this will keep it easier to integrate the currently out of
tree patches for multiple rendering contexts with different webrenders.


Change:
We introduce the RenderingGroupId in the WebViewId and allow a method to
extract it. The font key cache uses this cache
and forwards it to the Compositor when requesting new changes. The
compositor currently ignores this id.
Additionally, the WebView can return the RenderingGroupId. The WebViewId
also has an appropiate constructor for specifying a RenderingGroupId.
Because there currently will be only one RenderingGroupId the
performance will be minimal.


Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>

Testing: This should be covered by WPT tests and normal browsing
behavior works fine.

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2025-09-29 10:01:56 +00:00

630 lines
23 KiB
Rust

/* 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, RenderingGroupId, WebViewId};
use crossbeam_channel::Sender;
use embedder_traits::{AnimationState, EventLoopWaker, TouchEventResult};
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use rustc_hash::FxHashMap;
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::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(GenericSender<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>)>,
RenderingGroupId,
),
/// 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) = generic_channel::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,
rendering_group_id: RenderingGroupId,
) -> (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,
rendering_group_id,
));
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: FxHashMap<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);
ExternalImage {
uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0),
source,
}
},
}
}
/// 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 rendering_group_id(&self) -> Option<RenderingGroupId>;
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;
}
}