/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{BorderRadius, ColorF, ExternalImageId, ImageBufferKind, ImageKey, ImageRendering, YuvFormat, YuvRangedColorSpace}; use api::units::*; use api::ColorDepth; usecrate::image_source::resolve_image; usecrate::picture::ResolvedSurfaceTexture; usecrate::renderer::GpuBufferBuilderF; use euclid::Box2D; usecrate::gpu_types::{ZBufferId, ZBufferIdGenerator}; usecrate::internal_types::{FrameAllocator, FrameMemory, FrameVec, TextureSource}; usecrate::invalidation::compare::ImageDependency; usecrate::tile_cache::{TileCacheInstance, TileSurface}; usecrate::tile_cache::TileId; usecrate::prim_store::{DeferredResolve, PrimitiveInstanceIndex}; usecrate::resource_cache::{ImageRequest, ResourceCache}; usecrate::segment::EdgeMask; usecrate::util::{extract_inner_rect_safe, Preallocator, ScaleOffset}; usecrate::tile_cache::PictureCacheDebugInfo; usecrate::device::Device; usecrate::space::SpaceMapper; use std::{ops, u64, os::raw::c_void, hash}; use std::num::NonZeroUsize;
/// Which method is being used to draw a requested compositor surface #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)] pubenum CompositorSurfaceKind { /// Don't create a native compositor surface, blit it as a regular primitive
Blit, /// Create a native surface, draw it under content (must be opaque)
Underlay, /// Create a native surface, draw it between sub-slices (supports transparent)
Overlay,
}
/// Describes an operation to apply to a native surface #[derive(Debug, Clone)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct NativeSurfaceOperation { pub details: NativeSurfaceOperationDetails,
}
/// Describes the source surface information for a tile to be composited. This /// is the analog of the TileSurface type, with target surface information /// resolved such that it can be used by the renderer. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Clone)] pubenum CompositeTileSurface {
Texture {
surface: ResolvedSurfaceTexture,
},
Color {
color: ColorF,
},
ExternalSurface {
external_surface_index: ResolvedExternalSurfaceIndex,
},
}
/// The surface format for a tile being composited. #[derive(Debug, Copy, Clone, PartialEq)] pubenum CompositeSurfaceFormat {
Rgba,
Yuv,
}
bitflags! { /// Optional features that can be opted-out of when compositing, /// possibly allowing a fast path to be selected. #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] pubstruct CompositeFeatures: u8 { // UV coordinates do not require clamping, for example because the // entire texture is being composited. const NO_UV_CLAMP = 1 << 0; // The texture sample should not be modulated by a specified color. const NO_COLOR_MODULATION = 1 << 1; // Can skip applying clip mask. const NO_CLIP_MASK = 1 << 2;
}
}
// Index in to the compositor transforms stored in `CompositeState` #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone)] pubstruct CompositorTransformIndex(usize);
// Index in to the compositor clips stored in `CompositeState` #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone)] pubstruct CompositorClipIndex(NonZeroUsize);
/// Describes the geometry and surface of a tile to be composited #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Clone)] pubstruct CompositeTile { pub surface: CompositeTileSurface, pub local_rect: PictureRect, pub local_valid_rect: PictureRect, pub local_dirty_rect: PictureRect, pub device_clip_rect: DeviceRect, pub z_id: ZBufferId, pub kind: TileKind, pub transform_index: CompositorTransformIndex, pub clip_index: Option<CompositorClipIndex>, pub tile_id: Option<TileId>,
}
pubfn tile_kind(surface: &CompositeTileSurface, is_opaque: bool) -> TileKind { match surface { // Color tiles are, by definition, opaque. We might support non-opaque color // tiles if we ever find pages that have a lot of these.
CompositeTileSurface::Color { .. } => TileKind::Opaque,
CompositeTileSurface::Texture { .. }
| CompositeTileSurface::ExternalSurface { .. } => { // Texture surfaces get bucketed by opaque/alpha, for z-rejection // on the Draw compositor mode. if is_opaque {
TileKind::Opaque
} else {
TileKind::Alpha
}
}
}
}
/// Describes information about drawing a primitive as a compositor surface. /// For now, we support only YUV images as compositor surfaces, but in future /// this will also support RGBA images. #[derive(Clone)] pubstruct ExternalSurfaceDescriptor { // Normalized rectangle of this surface in local coordinate space // TODO(gw): Fix up local_rect unit kinds in ExternalSurfaceDescriptor (many flow on effects) pub local_surface_size: LayoutSize, pub local_rect: PictureRect, pub local_clip_rect: PictureRect, pub clip_rect: DeviceRect, pub transform_index: CompositorTransformIndex, pub compositor_clip_index: Option<CompositorClipIndex>, pub image_rendering: ImageRendering, pub z_id: ZBufferId, pub dependency: ExternalSurfaceDependency, /// If native compositing is enabled, the native compositor surface handle. /// Otherwise, this will be None pub native_surface_id: Option<NativeSurfaceId>, /// If the native surface needs to be updated, this will contain the size /// of the native surface as Some(size). If not dirty, this is None. pub update_params: Option<DeviceIntSize>, /// If using external compositing, a user key for the client pub external_image_id: Option<ExternalImageId>, pub prim_instance_index: PrimitiveInstanceIndex,
}
impl ExternalSurfaceDescriptor { /// Calculate an optional occlusion rect for a given compositor surface pubfn get_occluder_rect(
&self,
local_clip_rect: &PictureRect,
map_pic_to_world: &SpaceMapper<PicturePixel, WorldPixel>,
) -> Option<WorldRect> { let local_surface_rect = self
.local_rect
.intersection(&self.local_clip_rect)
.and_then(|r| {
r.intersection(local_clip_rect)
});
local_surface_rect.map(|local_surface_rect| {
map_pic_to_world
.map(&local_surface_rect)
.expect("bug: unable to map external surface to world space")
})
}
}
/// Information about a plane in a YUV or RGB surface. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone)] pubstruct ExternalPlaneDescriptor { pub texture: TextureSource, pub uv_rect: TexelRect,
}
/// An ExternalSurfaceDescriptor that has had image keys /// resolved to texture handles. This contains all the /// information that the compositor step in renderer /// needs to know. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ResolvedExternalSurface { pub color_data: ResolvedExternalSurfaceColorData, pub image_buffer_kind: ImageBufferKind, // Update information for a native surface if it's dirty pub update_params: Option<(NativeSurfaceId, DeviceIntSize)>, /// If using external compositing, a user key for the client pub external_image_id: Option<ExternalImageId>,
}
/// Public interface specified in `WebRenderOptions` that configures /// how WR compositing will operate. pubenum CompositorConfig { /// Let WR draw tiles via normal batching. This requires no special OS support.
Draw { /// If this is zero, a full screen present occurs at the end of the /// frame. This is the simplest and default mode. If this is non-zero, /// then the operating system supports a form of 'partial present' where /// only dirty regions of the framebuffer need to be updated.
max_partial_present_rects: usize, /// If this is true, WR must draw the previous frames' dirty regions when /// doing a partial present. This is used for EGL which requires the front /// buffer to always be fully consistent.
draw_previous_partial_present_regions: bool, /// A client provided interface to a compositor handling partial present. /// Required if webrender must query the backbuffer's age.
partial_present: Option<Box<dyn PartialPresentCompositor>>,
},
Layer { /// If supplied, composite the frame using the new experimental compositing /// interface. If this is set, it overrides `compositor_config`. These will /// be unified as the interface stabilises.
compositor: Box<dyn LayerCompositor>,
}, /// Use a native OS compositor to draw tiles. This requires clients to implement /// the Compositor trait, but can be significantly more power efficient on operating /// systems that support it.
Native { /// A client provided interface to a native / OS compositor.
compositor: Box<dyn Compositor>,
}
}
impl Default for CompositorConfig { /// Default compositor config is full present without partial present. fn default() -> Self {
CompositorConfig::Draw {
max_partial_present_rects: 0,
draw_previous_partial_present_regions: false,
partial_present: None,
}
}
}
/// This is a representation of `CompositorConfig` without the `Compositor` trait /// present. This allows it to be freely copied to other threads, such as the render /// backend where the frame builder can access it. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone, PartialEq)] pubenum CompositorKind { /// WR handles compositing via drawing.
Draw { /// Partial present support.
max_partial_present_rects: usize, /// Draw previous regions when doing partial present.
draw_previous_partial_present_regions: bool,
},
Layer {
}, /// Native OS compositor.
Native { /// The capabilities of the underlying platform.
capabilities: CompositorCapabilities,
},
}
impl Default for CompositorKind { /// Default compositor config is full present without partial present. fn default() -> Self {
CompositorKind::Draw {
max_partial_present_rects: 0,
draw_previous_partial_present_regions: false,
}
}
}
pubfn should_redraw_on_invalidation(&self) -> bool { matchself {
CompositorKind::Draw { max_partial_present_rects, .. } => { // When partial present is enabled, we need to force redraw.
*max_partial_present_rects > 0
}
CompositorKind::Layer { } => false, // TODO(gwc): Is this correct?
CompositorKind::Native { capabilities, .. } => capabilities.redraw_on_invalidation,
}
}
}
/// The backing surface kind for a tile. Same as `TileSurface`, minus /// the texture cache handles, visibility masks etc. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(PartialEq, Clone)] pubenum TileSurfaceKind {
Texture,
Color {
color: ColorF,
},
}
/// Describes properties that identify a tile composition uniquely. /// The backing surface for this tile. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(PartialEq, Clone)] pubstruct CompositeTileDescriptor { pub tile_id: TileId, pub surface_kind: TileSurfaceKind,
}
// Whether a compositor surface / swapchain is being used // by WR to render content, or is an external swapchain for video #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone)] pubenum CompositorSurfaceUsage {
Content,
External {
image_key: ImageKey,
external_image_id: ExternalImageId,
transform_index: CompositorTransformIndex,
},
DebugOverlay,
}
impl CompositorSurfaceUsage { // Returns true if usage is compatible pubfn matches(&self, other: &CompositorSurfaceUsage) -> bool { match (self, other) { // Surfaces used for content are always compatible
(CompositorSurfaceUsage::Content, CompositorSurfaceUsage::Content) => true,
/// Describes the properties that identify a surface composition uniquely. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(PartialEq, Clone)] pubstruct CompositeSurfaceDescriptor { pub surface_id: Option<NativeSurfaceId>, pub clip_rect: DeviceRect, pub transform: CompositorSurfaceTransform, // A list of image keys and generations that this compositor surface // depends on. This avoids composites being skipped when the only // thing that has changed is the generation of an compositor surface // image dependency. pub image_dependencies: [ImageDependency; 3], pub image_rendering: ImageRendering, // List of the surface information for each tile added to this virtual surface pub tile_descriptors: Vec<CompositeTileDescriptor>, pub rounded_clip_rect: DeviceRect, pub rounded_clip_radii: ClipRadius,
}
/// Describes surface properties used to composite a frame. This /// is used to compare compositions between frames. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(PartialEq, Clone)] pubstruct CompositeDescriptor { pub surfaces: Vec<CompositeSurfaceDescriptor>, pub external_surfaces_rect: DeviceRect,
}
/// A transform for either a picture cache or external compositor surface, stored /// in the `CompositeState` structure. This allows conversions from local rects /// to raster or device rects, without access to the spatial tree (e.g. during /// the render step where dirty rects are calculated). Since we know that we only /// handle scale and offset transforms for these types, we can store a single /// ScaleOffset rather than 4x4 matrix here for efficiency. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct CompositorTransform { // Map from local rect of a composite tile to the real backing surface coords
local_to_raster: ScaleOffset, // Map from surface coords to the final device space position
raster_to_device: ScaleOffset, // Combined local -> surface -> device transform
local_to_device: ScaleOffset,
}
/// The list of tiles to be drawn this frame #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct CompositeState { // TODO(gw): Consider splitting up CompositeState into separate struct types depending // on the selected compositing mode. Many of the fields in this state struct // are only applicable to either Native or Draw compositing mode. /// List of tiles to be drawn by the Draw compositor. /// Tiles are accumulated in this vector and sorted from front to back at the end of the /// frame. pub tiles: FrameVec<CompositeTile>, /// List of primitives that were promoted to be compositor surfaces. pub external_surfaces: FrameVec<ResolvedExternalSurface>, /// Used to generate z-id values for tiles in the Draw compositor mode. pub z_generator: ZBufferIdGenerator, // If false, we can't rely on the dirty rects in the CompositeTile // instances. This currently occurs during a scroll event, as a // signal to refresh the whole screen. This is only a temporary // measure until we integrate with OS compositors. In the meantime // it gives us the ability to partial present for any non-scroll // case as a simple win (e.g. video, animation etc). pub dirty_rects_are_valid: bool, /// The kind of compositor for picture cache tiles (e.g. drawn by WR, or OS compositor) pub compositor_kind: CompositorKind, /// List of registered occluders pub occluders: Occluders, /// Description of the surfaces and properties that are being composited. pub descriptor: CompositeDescriptor, /// Debugging information about the state of the pictures cached for regression testing. pub picture_cache_debug: PictureCacheDebugInfo, /// List of registered transforms used by picture cache or external surfaces pub transforms: FrameVec<CompositorTransform>, /// Whether we have low quality pinch zoom enabled
low_quality_pinch_zoom: bool, /// List of registered clips used by picture cache and/or external surfaces pub clips: FrameVec<CompositorClip>, /// Set to true when any tile is rasterized (has is_valid = false) pub did_rasterize_any_tile: bool,
}
impl CompositeState { /// Construct a new state for compositing picture tiles. This is created /// during each frame construction and passed to the renderer. pubfn new(
compositor_kind: CompositorKind,
max_depth_ids: i32,
dirty_rects_are_valid: bool,
low_quality_pinch_zoom: bool,
memory: &FrameMemory,
) -> Self { // Since CompositorClipIndex is NonZeroUSize, we need to // push a dummy entry in to this array. letmut clips = memory.new_vec();
clips.push(CompositorClip {
rect: DeviceRect::zero(),
radius: BorderRadius::zero(),
});
(
clip.rect.cast_unit(),
ClipRadius {
top_left: clip.radius.top_left.width.round() as i32,
top_right: clip.radius.top_right.width.round() as i32,
bottom_left: clip.radius.bottom_left.width.round() as i32,
bottom_right: clip.radius.bottom_right.width.round() as i32,
}
)
}
None => {
(default_rect, ClipRadius::EMPTY)
}
}
}
/// Register use of a transform for a picture cache tile or external surface pubfn register_transform(
&mutself,
local_to_raster: ScaleOffset,
raster_to_device: ScaleOffset,
) -> CompositorTransformIndex { let index = CompositorTransformIndex(self.transforms.len());
let local_to_device = local_to_raster.then(&raster_to_device);
/// Register use of a clip for a picture cache tile and/or external surface pubfn register_clip(
&mutself,
rect: DeviceRect,
radius: BorderRadius,
) -> CompositorClipIndex { let index = CompositorClipIndex(NonZeroUsize::new(self.clips.len()).expect("bug"));
/// Calculate the device-space rect of a local compositor surface rect pubfn get_device_rect(
&self,
local_rect: &PictureRect,
transform_index: CompositorTransformIndex,
) -> DeviceRect { let transform = &self.transforms[transform_index.0];
transform.local_to_device.map_rect(&local_rect).round()
}
/// Calculate the device-space rect of a local compositor surface rect, normalized /// to the origin of a given point pubfn get_surface_rect<T>(
&self,
local_sub_rect: &Box2D<f32, T>,
local_bounds: &Box2D<f32, T>,
transform_index: CompositorTransformIndex,
) -> DeviceRect { let transform = &self.transforms[transform_index.0];
let surface_bounds = transform.local_to_raster.map_rect(&local_bounds); let surface_rect = transform.local_to_raster.map_rect(&local_sub_rect);
/// Get the local -> device compositor transform pubfn get_device_transform(
&self,
transform_index: CompositorTransformIndex,
) -> ScaleOffset { let transform = &self.transforms[transform_index.0];
transform.local_to_device
}
/// Get the surface -> device compositor transform pubfn get_compositor_transform(
&self,
transform_index: CompositorTransformIndex,
) -> ScaleOffset { let transform = &self.transforms[transform_index.0];
transform.raster_to_device
}
/// Get the compositor clip pubfn get_compositor_clip(
&self,
clip_index: CompositorClipIndex,
) -> &CompositorClip {
&self.clips[clip_index.0.get()]
}
/// Register an occluder during picture cache updates that can be /// used during frame building to occlude tiles. pubfn register_occluder(
&mutself,
z_id: ZBufferId,
rect: WorldRect,
compositor_clip: Option<CompositorClipIndex>,
) { let rect = match compositor_clip {
Some(clip_index) => { let clip = self.get_compositor_clip(clip_index);
let inner_rect = match extract_inner_rect_safe(
&clip.rect,
&clip.radius,
) {
Some(rect) => rect,
None => return,
};
match inner_rect.cast_unit().intersection(&rect) {
Some(rect) => rect,
None => return,
}
}
None => {
rect
}
};
let world_rect = rect.round().to_i32();
self.occluders.push(world_rect, z_id);
}
/// Push a compositor surface on to the list of tiles to be passed to the compositor fn push_compositor_surface(
&mutself,
external_surface: &ExternalSurfaceDescriptor,
is_opaque: bool,
device_clip_rect: DeviceRect,
resource_cache: &ResourceCache,
gpu_buffer: &mut GpuBufferBuilderF,
deferred_resolves: &mut FrameVec<DeferredResolve>,
clip_index: Option<CompositorClipIndex>,
) { let clip_rect = external_surface
.clip_rect
.intersection(&device_clip_rect)
.unwrap_or_else(DeviceRect::zero);
// Skip compositor surfaces with empty clip rects. if clip_rect.is_empty() { return;
}
let required_plane_count = match external_surface.dependency {
ExternalSurfaceDependency::Yuv { format, .. } => {
format.get_plane_num()
},
ExternalSurfaceDependency::Rgb { .. } => { 1
}
};
for i in0 .. required_plane_count { let dependency = match external_surface.dependency {
ExternalSurfaceDependency::Yuv { image_dependencies, .. } => {
image_dependencies[i]
},
ExternalSurfaceDependency::Rgb { image_dependency, .. } => {
image_dependency
}
};
image_dependencies[i] = dependency;
}
// Get a new z_id for each compositor surface, to ensure correct ordering // when drawing with the simple (Draw) compositor, and to schedule compositing // of any required updates into the surfaces. let needs_external_surface_update = matchself.compositor_kind {
CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => true,
_ => external_surface.update_params.is_some(),
}; let external_surface_index = if needs_external_surface_update { let external_surface_index = self.compute_external_surface_dependencies(
&external_surface,
&image_dependencies,
required_plane_count,
resource_cache,
gpu_buffer,
deferred_resolves,
); if external_surface_index == ResolvedExternalSurfaceIndex::INVALID { return;
}
external_surface_index
} else {
ResolvedExternalSurfaceIndex::INVALID
};
let surface = CompositeTileSurface::ExternalSurface { external_surface_index }; let local_rect = external_surface.local_surface_size.cast_unit().into();
let (rounded_clip_rect, rounded_clip_radii) = self.compositor_clip_params(
clip_index,
clip_rect,
);
// Add a surface descriptor for each compositor surface. For the Draw // compositor, this is used to avoid composites being skipped by adding // a dependency on the compositor surface external image keys / generations. self.descriptor.surfaces.push(
CompositeSurfaceDescriptor {
surface_id: external_surface.native_surface_id,
clip_rect,
transform: self.get_compositor_transform(external_surface.transform_index),
image_dependencies: image_dependencies,
image_rendering: external_surface.image_rendering,
tile_descriptors: Vec::new(),
rounded_clip_rect,
rounded_clip_radii,
}
);
let device_rect = self.get_device_rect(&local_rect, external_surface.transform_index); self.descriptor.external_surfaces_rect = self.descriptor.external_surfaces_rect.union(&device_rect);
self.tiles.push(tile);
}
/// Add a picture cache to be composited pubfn push_surface(
&mutself,
tile_cache: &TileCacheInstance,
device_clip_rect: DeviceRect,
resource_cache: &ResourceCache,
gpu_buffer: &mut GpuBufferBuilderF,
deferred_resolves: &mut FrameVec<DeferredResolve>,
) { let slice_transform = self.get_compositor_transform(tile_cache.transform_index);
// Use the backdrop native surface we created and add that to the composite state. self.descriptor.surfaces.push(
CompositeSurfaceDescriptor {
surface_id: Some(backdrop_surface.id),
clip_rect: backdrop_surface.device_rect,
transform: slice_transform,
image_dependencies: [ImageDependency::INVALID; 3],
image_rendering,
tile_descriptors: Vec::new(),
rounded_clip_rect,
rounded_clip_radii,
}
);
}
// Add any underlay surfaces to the compositing tree for underlay in &tile_cache.underlays { self.push_compositor_surface(
underlay, true,
device_clip_rect,
resource_cache,
gpu_buffer,
deferred_resolves,
tile_cache.compositor_clip,
);
}
for sub_slice in &tile_cache.sub_slices { letmut surface_device_rect = DeviceRect::zero();
for tile in sub_slice.tiles.values() { if !tile.is_visible { // This can occur when a tile is found to be occluded during frame building. continue;
}
// Accumulate this tile into the overall surface bounds. This is used below // to clamp the size of the supplied clip rect to a reasonable value. // NOTE: This clip rect must include the device_valid_rect rather than // the tile device rect. This ensures that in the case of a picture // cache slice that is smaller than a single tile, the clip rect in // the composite descriptor will change if the position of that slice // is changed. Otherwise, WR may conclude that no composite is needed // if the tile itself was not invalidated due to changing content. // See bug #1675414 for more detail.
surface_device_rect = surface_device_rect.union(&tile.device_valid_rect);
}
// Append the visible tiles from this sub-slice self.tiles.extend_from_slice(&sub_slice.composite_tiles);
// If the clip rect is too large, it can cause accuracy and correctness problems // for some native compositors (specifically, CoreAnimation in this case). To // work around that, intersect the supplied clip rect with the current bounds // of the native surface, which ensures it is a reasonable size. let surface_clip_rect = device_clip_rect
.intersection(&surface_device_rect)
.unwrap_or(DeviceRect::zero());
// Only push tiles if they have valid clip rects. if !surface_clip_rect.is_empty() { let (rounded_clip_rect, rounded_clip_radii) = self.compositor_clip_params(
tile_cache.compositor_clip,
surface_clip_rect,
);
// Add opaque surface before any compositor surfaces if !sub_slice.opaque_tile_descriptors.is_empty() { self.descriptor.surfaces.push(
CompositeSurfaceDescriptor {
surface_id: sub_slice.native_surface.as_ref().map(|s| s.opaque),
clip_rect: surface_clip_rect,
transform: slice_transform,
image_dependencies: [ImageDependency::INVALID; 3],
image_rendering,
tile_descriptors: sub_slice.opaque_tile_descriptors.clone(),
rounded_clip_rect,
rounded_clip_radii,
}
);
}
// For each compositor surface that was promoted, build the // information required for the compositor to draw it for compositor_surface in &sub_slice.compositor_surfaces { let compositor_clip_index = if compositor_surface.descriptor.compositor_clip_index.is_some() {
assert!(tile_cache.compositor_clip.is_none());
compositor_surface.descriptor.compositor_clip_index
} else {
tile_cache.compositor_clip
};
/// Compare this state vs. a previous frame state, and invalidate dirty rects if /// the surface count has changed pubfn update_dirty_rect_validity(
&mutself,
old_descriptor: &CompositeDescriptor,
) { // TODO(gw): Make this more robust in other cases - there are other situations where // the surface count may be the same but we still need to invalidate the // dirty rects (e.g. if the surface ordering changed, or the external // surface itself is animated?)
if old_descriptor.surfaces.len() != self.descriptor.surfaces.len() { self.dirty_rects_are_valid = false; return;
}
// The entire area of external surfaces are treated as dirty, however, // if a surface has moved or shrunk that is no longer valid, as we // additionally need to ensure the area the surface used to occupy is // composited. if !self
.descriptor
.external_surfaces_rect
.contains_box(&old_descriptor.external_surfaces_rect)
{ self.dirty_rects_are_valid = false; return;
}
}
// Check if there are valid images added for each YUV plane if valid_plane_count < required_plane_count {
warn!("Warnings: skip a YUV/RGB compositor surface, found {}/{} valid images",
valid_plane_count,
required_plane_count,
); return ResolvedExternalSurfaceIndex::INVALID;
}
let external_surface_index = ResolvedExternalSurfaceIndex(self.external_surfaces.len());
// If the external surface descriptor reports that the native surface // needs to be updated, create an update params tuple for the renderer // to use. let update_params = external_surface.update_params.map(|surface_size| {
(
external_surface.native_surface_id.expect("bug: no native surface!"),
surface_size
)
});
match external_surface.dependency {
ExternalSurfaceDependency::Yuv{ color_space, format, channel_bit_depth, .. } => {
let image_buffer_kind = planes[0].texture.image_buffer_kind();
/// An arbitrary identifier for a native (OS compositor) surface #[repr(C)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct NativeSurfaceId(pub u64);
impl NativeSurfaceId { /// A special id for the native surface that is used for debug / profiler overlays. pubconst DEBUG_OVERLAY: NativeSurfaceId = NativeSurfaceId(u64::MAX);
}
impl NativeTileId { /// A special id for the native surface that is used for debug / profiler overlays. pubconst DEBUG_OVERLAY: NativeTileId = NativeTileId {
surface_id: NativeSurfaceId::DEBUG_OVERLAY,
x: 0,
y: 0,
};
}
/// Information about a bound surface that the native compositor /// returns to WR. #[repr(C)] #[derive(Copy, Clone)] pubstruct NativeSurfaceInfo { /// An offset into the surface that WR should draw. Some compositing /// implementations (notably, DirectComposition) use texture atlases /// when the surface sizes are small. In this case, an offset can /// be returned into the larger texture where WR should draw. This /// can be (0, 0) if texture atlases are not used. pub origin: DeviceIntPoint, /// The ID of the FBO that WR should bind to, in order to draw to /// the bound surface. On Windows (ANGLE) this will always be 0, /// since creating a p-buffer sets the default framebuffer to /// be the DirectComposition surface. On Mac, this will be non-zero, /// since it identifies the IOSurface that has been bound to draw to. // TODO(gw): This may need to be a larger / different type for WR // backends that are not GL. pub fbo_id: u32,
}
#[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct CompositorCapabilities { /// The virtual surface size used by the underlying platform. pub virtual_surface_size: i32, /// Whether the compositor requires redrawing on invalidation. pub redraw_on_invalidation: bool, /// The maximum number of dirty rects that can be provided per compositor /// surface update. If this is zero, the entire compositor surface for /// a given tile will be drawn if it's dirty. pub max_update_rects: usize, /// Whether or not this compositor will create surfaces for backdrops. pub supports_surface_for_backdrop: bool, /// Whether external compositor surface supports negative scaling. pub supports_external_compositor_surface_negative_scaling: bool,
}
impl Default for CompositorCapabilities { fn default() -> Self { // The default set of compositor capabilities for a given platform. // These should only be modified if a compositor diverges specifically // from the default behavior so that compositors don't have to track // which changes to this structure unless necessary.
CompositorCapabilities {
virtual_surface_size: 0,
redraw_on_invalidation: false, // Assume compositors can do at least partial update of surfaces. If not, // the native compositor should override this to be 0.
max_update_rects: 1,
supports_surface_for_backdrop: false,
supports_external_compositor_surface_negative_scaling: true,
}
}
}
/// The transform type to apply to Compositor surfaces. // TODO: Should transform from CompositorSurfacePixel instead, but this requires a cleanup of the // Compositor API to use CompositorSurface-space geometry instead of Device-space where necessary // to avoid a bunch of noisy cast_unit calls and make it actually type-safe. May be difficult due // to pervasive use of Device-space nomenclature inside WR. // pub struct CompositorSurfacePixel; pubtype CompositorSurfaceTransform = ScaleOffset;
/// Defines an interface to a native (OS level) compositor. If supplied /// by the client application, then picture cache slices will be /// composited by the OS compositor, rather than drawn via WR batches. pubtrait Compositor { /// Create a new OS compositor surface with the given properties. fn create_surface(
&mutself,
device: &mut Device,
id: NativeSurfaceId,
virtual_offset: DeviceIntPoint,
tile_size: DeviceIntSize,
is_opaque: bool,
);
/// Create a new OS compositor surface that can be used with an /// existing ExternalImageId, instead of being drawn to by WebRender. /// Surfaces created by this can only be used with attach_external_image, /// and not create_tile/destroy_tile/bind/unbind. fn create_external_surface(
&mutself,
device: &mut Device,
id: NativeSurfaceId,
is_opaque: bool,
);
/// Create a new OS backdrop surface that will display a color. fn create_backdrop_surface(
&mutself,
device: &mut Device,
id: NativeSurfaceId,
color: ColorF,
);
/// Destroy the surface with the specified id. WR may call this /// at any time the surface is no longer required (including during /// renderer deinit). It's the responsibility of the embedder /// to ensure that the surface is only freed once the GPU is /// no longer using the surface (if this isn't already handled /// by the operating system). fn destroy_surface(
&mutself,
device: &mut Device,
id: NativeSurfaceId,
);
/// Create a new OS compositor tile with the given properties. fn create_tile(
&mutself,
device: &mut Device,
id: NativeTileId,
);
/// Destroy an existing compositor tile. fn destroy_tile(
&mutself,
device: &mut Device,
id: NativeTileId,
);
/// Attaches an ExternalImageId to an OS compositor surface created /// by create_external_surface, and uses that as the contents of /// the surface. It is expected that a single surface will have /// many different images attached (like one for each video frame). fn attach_external_image(
&mutself,
device: &mut Device,
id: NativeSurfaceId,
external_image: ExternalImageId
);
/// Mark a tile as invalid before any surfaces are queued for /// composition and before it is updated with bind. This is useful /// for early composition, allowing for dependency tracking of which /// surfaces can be composited early while others are still updating. fn invalidate_tile(
&mutself,
_device: &mut Device,
_id: NativeTileId,
_valid_rect: DeviceIntRect
) {}
/// Bind this surface such that WR can issue OpenGL commands /// that will target the surface. Returns an (x, y) offset /// where WR should draw into the surface. This can be set /// to (0, 0) if the OS doesn't use texture atlases. The dirty /// rect is a local surface rect that specifies which part /// of the surface needs to be updated. If max_update_rects /// in CompositeConfig is 0, this will always be the size /// of the entire surface. The returned offset is only /// relevant to compositors that store surfaces in a texture /// atlas (that is, WR expects that the dirty rect doesn't /// affect the coordinates of the returned origin). fn bind(
&mutself,
device: &mut Device,
id: NativeTileId,
dirty_rect: DeviceIntRect,
valid_rect: DeviceIntRect,
) -> NativeSurfaceInfo;
/// Unbind the surface. This is called by WR when it has /// finished issuing OpenGL commands on the current surface. fn unbind(
&mutself,
device: &mut Device,
);
/// Begin the frame fn begin_frame(&mutself, device: &mut Device);
/// Add a surface to the visual tree to be composited. Visuals must /// be added every frame, between the begin/end transaction call. The /// z-order of the surfaces is determined by the order they are added /// to the visual tree. // TODO(gw): Adding visuals every frame makes the interface simple, // but may have performance implications on some compositors? // We might need to change the interface to maintain a visual // tree that can be mutated? // TODO(gw): We might need to add a concept of a hierachy in future. fn add_surface(
&mutself,
device: &mut Device,
id: NativeSurfaceId,
transform: CompositorSurfaceTransform,
clip_rect: DeviceIntRect,
image_rendering: ImageRendering,
rounded_clip_rect: DeviceIntRect,
rounded_clip_radii: ClipRadius,
);
/// Notify the compositor that all tiles have been invalidated and all /// native surfaces have been added, thus it is safe to start compositing /// valid surfaces. The dirty rects array allows native compositors that /// support partial present to skip copying unchanged areas. /// Optionally provides a set of rectangles for the areas known to be /// opaque, this is currently only computed if the caller is SwCompositor. fn start_compositing(
&mutself,
_device: &mut Device,
_clear_color: ColorF,
_dirty_rects: &[DeviceIntRect],
_opaque_rects: &[DeviceIntRect],
) {}
/// Commit any changes in the compositor tree for this frame. WR calls /// this once when all surface and visual updates are complete, to signal /// that the OS composite transaction should be applied. fn end_frame(&mutself, device: &mut Device);
/// Safely deinitialize any remaining resources owned by the compositor. fn deinit(&mutself, device: &mut Device);
/// Get the capabilities struct for this compositor. This is used to /// specify what features a compositor supports, depending on the /// underlying platform fn get_capabilities(&self, device: &mutDevice) -> CompositorCapabilities;
// Describes the configuration for an input layer that the compositor // implemention should prepare #[derive(Debug)] pubstruct CompositorInputLayer { // Device space location of the layer (pre-clip) pub offset: DeviceIntPoint, // Device space clip-rect of the layer pub clip_rect: DeviceIntRect, // Whether a content or external surface pub usage: CompositorSurfaceUsage, // If true, layer is opaque, blend can be disabled pub is_opaque: bool, pub rounded_clip_rect: DeviceIntRect, pub rounded_clip_radii: ClipRadius,
}
// Provides the parameters about the frame to the compositor implementation. // TODO(gw): Include information about picture cache slices and external surfaces. #[derive(Debug)] pubstruct CompositorInputConfig<'a> { pub enable_screenshot: bool, pub layers: &'a [CompositorInputLayer],
}
// Trait for implementors of swapchain based compositing. // TODO(gw): Extend to handle external surfaces, layers, swgl, etc. pubtrait LayerCompositor { // Prepare to composite a frame. Ensure that layers are constructed // to match the input config fn begin_frame(
&mutself,
input: &CompositorInputConfig,
) -> bool;
// Bind a layer (by index in the input config) to begin rendering // content to it. fn bind_layer(
&mutself,
index: usize,
dirty_rects: &[DeviceIntRect],
);
// Complete rendering of a layer and present / swap buffers fn present_layer(
&mutself,
index: usize,
dirty_rects: &[DeviceIntRect],
);
// Finish compositing this frame - commit the visual tree to the OS fn end_frame(&mutself);
// Get current information about the window, such as opacity fn get_window_properties(&self) -> WindowProperties;
}
/// Information about the underlying data buffer of a mapped tile. #[repr(C)] #[derive(Copy, Clone)] pubstruct MappedTileInfo { pub data: *mut c_void, pub stride: i32,
}
/// Descriptor for a locked surface that will be directly composited by SWGL. #[repr(C)] pubstruct SWGLCompositeSurfaceInfo { /// The number of YUV planes in the surface. 0 indicates non-YUV BGRA. /// 1 is interleaved YUV. 2 is NV12. 3 is planar YUV. pub yuv_planes: u32, /// Textures for planes of the surface, or 0 if not applicable. pub textures: [u32; 3], /// Color space of surface if using a YUV format. pub color_space: YuvRangedColorSpace, /// Color depth of surface if using a YUV format. pub color_depth: ColorDepth, /// The actual source surface size before transformation. pub size: DeviceIntSize,
}
/// A Compositor variant that supports mapping tiles into CPU memory. pubtrait MappableCompositor: Compositor { /// Map a tile's underlying buffer so it can be used as the backing for /// a SWGL framebuffer. This is intended to be a replacement for 'bind' /// in any compositors that intend to directly interoperate with SWGL /// while supporting some form of native layers. fn map_tile(
&mutself,
device: &mut Device,
id: NativeTileId,
dirty_rect: DeviceIntRect,
valid_rect: DeviceIntRect,
) -> Option<MappedTileInfo>;
/// Unmap a tile that was was previously mapped via map_tile to signal /// that SWGL is done rendering to the buffer. fn unmap_tile(&mutself, device: &mut Device);
/// Defines an interface to a non-native (application-level) Compositor which handles /// partial present. This is required if webrender must query the backbuffer's age. /// TODO: Use the Compositor trait for native and non-native compositors, and integrate /// this functionality there. pubtrait PartialPresentCompositor { /// Allows webrender to specify the total region that will be rendered to this frame, /// ie the frame's dirty region and some previous frames' dirty regions, if applicable /// (calculated using the buffer age). Must be called before anything has been rendered /// to the main framebuffer. fn set_buffer_damage_region(&mutself, rects: &[DeviceIntRect]);
}
/// Information about an opaque surface used to occlude tiles. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] struct Occluder {
z_id: ZBufferId,
world_rect: WorldIntRect,
}
// Whether this event is the start or end of a rectangle #[derive(Debug)] enum OcclusionEventKind {
Begin,
End,
}
// A list of events on the y-axis, with the rectangle range that it affects on the x-axis #[derive(Debug)] struct OcclusionEvent {
y: i32,
x_range: ops::Range<i32>,
kind: OcclusionEventKind,
}
/// This struct exists to provide a Default impl and allow #[serde(skip)] /// on the two frame vectors. Unfortunately FrameVec does not have a Default /// implementation (vectors only implement it with the global allocator). pubstruct OccludersScratchBuffers {
events: FrameVec<OcclusionEvent>,
active: FrameVec<ops::Range<i32>>,
}
/// List of registered occluders. /// /// Also store a couple of vectors for reuse. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct Occluders {
occluders: FrameVec<Occluder>,
// The two vectors in scratch are kept to avoid unnecessary reallocations in area(). #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
scratch: OccludersScratchBuffers,
}
/// Returns true if a tile with the specified rectangle and z_id /// is occluded by an opaque surface in front of it. pubfn is_tile_occluded(
&mutself,
z_id: ZBufferId,
world_rect: WorldRect,
) -> bool { // It's often the case that a tile is only occluded by considering multiple // picture caches in front of it (for example, the background tiles are // often occluded by a combination of the content slice + the scrollbar slices).
// The basic algorithm is: // For every occluder: // If this occluder is in front of the tile we are querying: // Clip the occluder rectangle to the query rectangle. // Calculate the total non-overlapping area of those clipped occluders. // If the cumulative area of those occluders is the same as the area of the query tile, // Then the entire tile must be occluded and can be skipped during rasterization and compositing.
// Get the reference area we will compare against. let world_rect = world_rect.round().to_i32(); let ref_area = world_rect.area();
// Calculate the non-overlapping area of the valid occluders. let cover_area = self.area(z_id, &world_rect);
debug_assert!(cover_area <= ref_area);
// Check if the tile area is completely covered
ref_area == cover_area
}
/// Return the total area covered by a set of occluders, accounting for /// overlapping areas between those rectangles. fn area(
&mutself,
z_id: ZBufferId,
clip_rect: &WorldIntRect,
) -> i32 { // This implementation is based on the article https://leetcode.com/articles/rectangle-area-ii/. // This is not a particularly efficient implementation (it skips building segment trees), however // we typically use this where the length of the rectangles array is < 10, so simplicity is more important.
// Step through each rectangle and build the y-axis event list for occluder in &self.occluders { // Only consider occluders in front of this rect if occluder.z_id.0 < z_id.0 { // Clip the source rect to the rectangle we care about, since we only // want to record area for the tile we are comparing to. iflet Some(rect) = occluder.world_rect.intersection(clip_rect) { let x0 = rect.min.x; let x1 = x0 + rect.width(); self.scratch.events.push(OcclusionEvent::new(rect.min.y, OcclusionEventKind::Begin, x0, x1)); self.scratch.events.push(OcclusionEvent::new(rect.min.y + rect.height(), OcclusionEventKind::End, x0, x1));
}
}
}
// If we didn't end up with any valid events, the area must be 0 ifself.scratch.events.is_empty() { return0;
}
// Sort the events by y-value self.scratch.events.sort_by_key(|e| e.y); letmut cur_y = self.scratch.events[0].y;
// Step through each y interval for event in &self.scratch.events { // This is the dimension of the y-axis we are accumulating areas for let dy = event.y - cur_y;
// If we have active events covering x-ranges in this y-interval, process them if dy != 0 && !self.scratch.active.is_empty() {
assert!(dy > 0);
// Step through the x-ranges, ordered by x0 of each event self.scratch.active.sort_by_key(|i| i.start); letmut query = 0; letmut cur = self.scratch.active[0].start;
// Accumulate the non-overlapping x-interval that contributes to area for this y-interval. for interval in &self.scratch.active {
cur = interval.start.max(cur);
query += (interval.end - cur).max(0);
cur = cur.max(interval.end);
}
// Accumulate total area for this y-interval
area += query * dy;
}
// Update the active events list match event.kind {
OcclusionEventKind::Begin => { self.scratch.active.push(event.x_range.clone());
}
OcclusionEventKind::End => { let index = self.scratch.active.iter().position(|i| *i == event.x_range).unwrap(); self.scratch.active.remove(index);
}
}
cur_y = event.y;
}
area
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-08-22)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.