summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/composite.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/composite.rs')
-rw-r--r--gfx/wr/webrender/src/composite.rs1250
1 files changed, 1250 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/composite.rs b/gfx/wr/webrender/src/composite.rs
new file mode 100644
index 0000000000..c75b0a19b5
--- /dev/null
+++ b/gfx/wr/webrender/src/composite.rs
@@ -0,0 +1,1250 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use api::{ColorF, YuvColorSpace, YuvFormat, ImageRendering, ExternalImageId, ImageBufferKind};
+use api::units::*;
+use crate::batch::{resolve_image};
+use euclid::Transform3D;
+use crate::gpu_cache::GpuCache;
+use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
+use crate::internal_types::TextureSource;
+use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileId, TileSurface};
+use crate::prim_store::DeferredResolve;
+use crate::resource_cache::{ImageRequest, ResourceCache};
+use crate::util::Preallocator;
+use crate::tile_cache::PictureCacheDebugInfo;
+use std::{ops, u64};
+
+/*
+ Types and definitions related to compositing picture cache tiles
+ and/or OS compositor integration.
+ */
+
+/// Describes details of an operation to apply to a native surface
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum NativeSurfaceOperationDetails {
+ CreateSurface {
+ id: NativeSurfaceId,
+ virtual_offset: DeviceIntPoint,
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ },
+ CreateExternalSurface {
+ id: NativeSurfaceId,
+ is_opaque: bool,
+ },
+ DestroySurface {
+ id: NativeSurfaceId,
+ },
+ CreateTile {
+ id: NativeTileId,
+ },
+ DestroyTile {
+ id: NativeTileId,
+ },
+ AttachExternalImage {
+ id: NativeSurfaceId,
+ external_image: ExternalImageId,
+ }
+}
+
+/// Describes an operation to apply to a native surface
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct 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))]
+pub enum CompositeTileSurface {
+ Texture {
+ surface: ResolvedSurfaceTexture,
+ },
+ Color {
+ color: ColorF,
+ },
+ Clear,
+ ExternalSurface {
+ external_surface_index: ResolvedExternalSurfaceIndex,
+ },
+}
+
+/// The surface format for a tile being composited.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum CompositeSurfaceFormat {
+ Rgba,
+ Yuv,
+}
+
+/// Describes the geometry and surface of a tile to be composited
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct CompositeTile {
+ pub surface: CompositeTileSurface,
+ pub rect: DeviceRect,
+ pub clip_rect: DeviceRect,
+ pub dirty_rect: DeviceRect,
+ pub valid_rect: DeviceRect,
+ pub transform: Option<CompositorSurfaceTransform>,
+ pub z_id: ZBufferId,
+}
+
+pub enum ExternalSurfaceDependency {
+ Yuv {
+ image_dependencies: [ImageDependency; 3],
+ color_space: YuvColorSpace,
+ format: YuvFormat,
+ rescale: f32,
+ },
+ Rgb {
+ image_dependency: ImageDependency,
+ flip_y: bool,
+ },
+}
+
+/// 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.
+pub struct ExternalSurfaceDescriptor {
+ // Rectangle of this surface in owning picture's coordinate space
+ pub local_rect: PictureRect,
+ // Rectangle of this surface in the compositor local space
+ // TODO(gw): Switch this to CompositorSurfaceRect (CompositorSurfacePixel) in compositor trait.
+ pub surface_rect: DeviceRect,
+ // Rectangle of this surface in true device pixels
+ pub device_rect: DeviceRect,
+ pub local_clip_rect: PictureRect,
+ pub clip_rect: DeviceRect,
+ pub transform: CompositorSurfaceTransform,
+ 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>,
+}
+
+/// 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)]
+pub struct ExternalPlaneDescriptor {
+ pub texture: TextureSource,
+ pub texture_layer: i32,
+ pub uv_rect: TexelRect,
+}
+
+impl ExternalPlaneDescriptor {
+ fn invalid() -> Self {
+ ExternalPlaneDescriptor {
+ texture: TextureSource::Invalid,
+ texture_layer: 0,
+ uv_rect: TexelRect::invalid(),
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct ResolvedExternalSurfaceIndex(pub usize);
+
+impl ResolvedExternalSurfaceIndex {
+ pub const INVALID: ResolvedExternalSurfaceIndex = ResolvedExternalSurfaceIndex(usize::MAX);
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ResolvedExternalSurfaceColorData {
+ Yuv {
+ // YUV specific information
+ image_dependencies: [ImageDependency; 3],
+ planes: [ExternalPlaneDescriptor; 3],
+ color_space: YuvColorSpace,
+ format: YuvFormat,
+ rescale: f32,
+ },
+ Rgb {
+ image_dependency: ImageDependency,
+ plane: ExternalPlaneDescriptor,
+ flip_y: bool,
+ },
+}
+
+/// 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))]
+pub struct 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)>,
+}
+
+/// Public interface specified in `RendererOptions` that configures
+/// how WR compositing will operate.
+pub enum 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>>,
+ },
+ /// 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 {
+ /// 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.
+ max_update_rects: usize,
+ /// A client provided interface to a native / OS compositor.
+ compositor: Box<dyn Compositor>,
+ }
+}
+
+impl CompositorConfig {
+ pub fn compositor(&mut self) -> Option<&mut Box<dyn Compositor>> {
+ match self {
+ CompositorConfig::Native { ref mut compositor, .. } => {
+ Some(compositor)
+ }
+ CompositorConfig::Draw { .. } => {
+ None
+ }
+ }
+ }
+
+ pub fn partial_present(&mut self) -> Option<&mut Box<dyn PartialPresentCompositor>> {
+ match self {
+ CompositorConfig::Native { .. } => {
+ None
+ }
+ CompositorConfig::Draw { ref mut partial_present, .. } => {
+ partial_present.as_mut()
+ }
+ }
+ }
+
+}
+
+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)]
+pub enum 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,
+ },
+ /// Native OS compositor.
+ Native {
+ /// Maximum dirty rects per compositor surface.
+ max_update_rects: usize,
+ /// The virtual surface size used by underlying platform.
+ virtual_surface_size: i32,
+ },
+}
+
+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,
+ }
+ }
+}
+
+impl CompositorKind {
+ pub fn get_virtual_surface_size(&self) -> i32 {
+ match self {
+ CompositorKind::Draw { .. } => 0,
+ CompositorKind::Native { virtual_surface_size, .. } => *virtual_surface_size,
+ }
+ }
+
+ // We currently only support transforms for Native compositors,
+ // bug 1655639 is filed for adding support to Draw.
+ pub fn supports_transforms(&self) -> bool {
+ match self {
+ CompositorKind::Draw { .. } => false,
+ CompositorKind::Native { .. } => true,
+ }
+ }
+}
+
+/// 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)]
+pub enum TileSurfaceKind {
+ Texture,
+ Color {
+ color: ColorF,
+ },
+ Clear,
+}
+
+impl From<&TileSurface> for TileSurfaceKind {
+ fn from(surface: &TileSurface) -> Self {
+ match surface {
+ TileSurface::Texture { .. } => TileSurfaceKind::Texture,
+ TileSurface::Color { color } => TileSurfaceKind::Color { color: *color },
+ TileSurface::Clear => TileSurfaceKind::Clear,
+ }
+ }
+}
+
+/// 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)]
+pub struct CompositeTileDescriptor {
+ pub tile_id: TileId,
+ pub surface_kind: TileSurfaceKind,
+}
+
+/// Describes the properties that identify a surface composition uniquely.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(PartialEq, Clone)]
+pub struct 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>,
+}
+
+/// 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)]
+pub struct CompositeDescriptor {
+ pub surfaces: Vec<CompositeSurfaceDescriptor>,
+}
+
+impl CompositeDescriptor {
+ /// Construct an empty descriptor.
+ pub fn empty() -> Self {
+ CompositeDescriptor {
+ surfaces: Vec::new(),
+ }
+ }
+}
+
+pub struct CompositeStatePreallocator {
+ opaque_tiles: Preallocator,
+ alpha_tiles: Preallocator,
+ clear_tiles: Preallocator,
+ external_surfaces: Preallocator,
+ occluders: Preallocator,
+ occluders_events: Preallocator,
+ occluders_active: Preallocator,
+ descriptor_surfaces: Preallocator,
+}
+
+impl CompositeStatePreallocator {
+ pub fn record(&mut self, state: &CompositeState) {
+ self.opaque_tiles.record_vec(&state.opaque_tiles);
+ self.alpha_tiles.record_vec(&state.alpha_tiles);
+ self.clear_tiles.record_vec(&state.clear_tiles);
+ self.external_surfaces.record_vec(&state.external_surfaces);
+ self.occluders.record_vec(&state.occluders.occluders);
+ self.occluders_events.record_vec(&state.occluders.events);
+ self.occluders_active.record_vec(&state.occluders.active);
+ self.descriptor_surfaces.record_vec(&state.descriptor.surfaces);
+ }
+
+ pub fn preallocate(&self, state: &mut CompositeState) {
+ self.opaque_tiles.preallocate_vec(&mut state.opaque_tiles);
+ self.alpha_tiles.preallocate_vec(&mut state.alpha_tiles);
+ self.clear_tiles.preallocate_vec(&mut state.clear_tiles);
+ self.external_surfaces.preallocate_vec(&mut state.external_surfaces);
+ self.occluders.preallocate_vec(&mut state.occluders.occluders);
+ self.occluders_events.preallocate_vec(&mut state.occluders.events);
+ self.occluders_active.preallocate_vec(&mut state.occluders.active);
+ self.descriptor_surfaces.preallocate_vec(&mut state.descriptor.surfaces);
+ }
+}
+
+impl Default for CompositeStatePreallocator {
+ fn default() -> Self {
+ CompositeStatePreallocator {
+ opaque_tiles: Preallocator::new(40),
+ alpha_tiles: Preallocator::new(16),
+ clear_tiles: Preallocator::new(0),
+ external_surfaces: Preallocator::new(0),
+ occluders: Preallocator::new(16),
+ occluders_events: Preallocator::new(32),
+ occluders_active: Preallocator::new(16),
+ descriptor_surfaces: Preallocator::new(8),
+ }
+ }
+}
+
+/// The list of tiles to be drawn this frame
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct 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 opaque tiles to be drawn by the Draw compositor.
+ pub opaque_tiles: Vec<CompositeTile>,
+ /// List of alpha tiles to be drawn by the Draw compositor.
+ pub alpha_tiles: Vec<CompositeTile>,
+ /// List of clear tiles to be drawn by the Draw compositor.
+ pub clear_tiles: Vec<CompositeTile>,
+ /// List of primitives that were promoted to be compositor surfaces.
+ pub external_surfaces: Vec<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,
+ /// The overall device pixel scale, used for tile occlusion conversions.
+ global_device_pixel_scale: DevicePixelScale,
+ /// 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,
+}
+
+impl CompositeState {
+ /// Construct a new state for compositing picture tiles. This is created
+ /// during each frame construction and passed to the renderer.
+ pub fn new(
+ compositor_kind: CompositorKind,
+ global_device_pixel_scale: DevicePixelScale,
+ max_depth_ids: i32,
+ dirty_rects_are_valid: bool,
+ ) -> Self {
+ CompositeState {
+ opaque_tiles: Vec::new(),
+ alpha_tiles: Vec::new(),
+ clear_tiles: Vec::new(),
+ z_generator: ZBufferIdGenerator::new(max_depth_ids),
+ dirty_rects_are_valid,
+ compositor_kind,
+ global_device_pixel_scale,
+ occluders: Occluders::new(),
+ descriptor: CompositeDescriptor::empty(),
+ external_surfaces: Vec::new(),
+ picture_cache_debug: PictureCacheDebugInfo::new(),
+ }
+ }
+
+ /// Register an occluder during picture cache updates that can be
+ /// used during frame building to occlude tiles.
+ pub fn register_occluder(
+ &mut self,
+ z_id: ZBufferId,
+ rect: WorldRect,
+ ) {
+ let device_rect = (rect * self.global_device_pixel_scale).round().to_i32();
+
+ self.occluders.push(device_rect, z_id);
+ }
+
+ /// Add a picture cache to be composited
+ pub fn push_surface(
+ &mut self,
+ tile_cache: &TileCacheInstance,
+ device_clip_rect: DeviceRect,
+ global_device_pixel_scale: DevicePixelScale,
+ resource_cache: &ResourceCache,
+ gpu_cache: &mut GpuCache,
+ deferred_resolves: &mut Vec<DeferredResolve>,
+ ) {
+ let mut visible_opaque_tile_count = 0;
+ let mut visible_alpha_tile_count = 0;
+ let mut opaque_tile_descriptors = Vec::new();
+ let mut alpha_tile_descriptors = Vec::new();
+ let mut surface_device_rect = DeviceRect::zero();
+
+ for tile in tile_cache.tiles.values() {
+ if !tile.is_visible {
+ // This can occur when a tile is found to be occluded during frame building.
+ continue;
+ }
+
+ let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round();
+ let surface = tile.surface.as_ref().expect("no tile surface set!");
+
+ // 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);
+
+ let descriptor = CompositeTileDescriptor {
+ surface_kind: surface.into(),
+ tile_id: tile.id,
+ };
+
+ let (surface, is_opaque) = match surface {
+ TileSurface::Color { color } => {
+ (CompositeTileSurface::Color { color: *color }, true)
+ }
+ TileSurface::Clear => {
+ (CompositeTileSurface::Clear, false)
+ }
+ TileSurface::Texture { descriptor, .. } => {
+ let surface = descriptor.resolve(resource_cache, tile_cache.current_tile_size);
+ (
+ CompositeTileSurface::Texture { surface },
+ // If a tile has compositor surface intersecting with it, we need to
+ // respect the tile.is_opaque property even if the overall tile cache
+ // is opaque. In this case, the tile.is_opaque property is required
+ // in order to ensure correct draw order with compositor surfaces.
+ tile.is_opaque || (!tile.has_compositor_surface && tile_cache.is_opaque()),
+ )
+ }
+ };
+
+ if is_opaque {
+ opaque_tile_descriptors.push(descriptor);
+ visible_opaque_tile_count += 1;
+ } else {
+ alpha_tile_descriptors.push(descriptor);
+ visible_alpha_tile_count += 1;
+ }
+
+ let tile = CompositeTile {
+ surface,
+ rect: device_rect,
+ valid_rect: tile.device_valid_rect.translate(-device_rect.origin.to_vector()),
+ dirty_rect: tile.device_dirty_rect.translate(-device_rect.origin.to_vector()),
+ clip_rect: device_clip_rect,
+ transform: None,
+ z_id: tile.z_id,
+ };
+
+ self.push_tile(tile, is_opaque);
+ }
+
+ // Sort the tile descriptor lists, since iterating values in the tile_cache.tiles
+ // hashmap doesn't provide any ordering guarantees, but we want to detect the
+ // composite descriptor as equal if the tiles list is the same, regardless of
+ // ordering.
+ opaque_tile_descriptors.sort_by_key(|desc| desc.tile_id);
+ alpha_tile_descriptors.sort_by_key(|desc| desc.tile_id);
+
+ // 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());
+
+ // Add opaque surface before any compositor surfaces
+ if visible_opaque_tile_count > 0 {
+ self.descriptor.surfaces.push(
+ CompositeSurfaceDescriptor {
+ surface_id: tile_cache.native_surface.as_ref().map(|s| s.opaque),
+ clip_rect: surface_clip_rect,
+ transform: CompositorSurfaceTransform::translation(
+ tile_cache.device_position.x,
+ tile_cache.device_position.y,
+ 0.0,
+ ),
+ image_dependencies: [ImageDependency::INVALID; 3],
+ image_rendering: ImageRendering::CrispEdges,
+ tile_descriptors: opaque_tile_descriptors,
+ }
+ );
+ }
+
+ // For each compositor surface that was promoted, build the
+ // information required for the compositor to draw it
+ for external_surface in &tile_cache.external_surfaces {
+ let clip_rect = external_surface
+ .clip_rect
+ .intersection(&device_clip_rect)
+ .unwrap_or_else(DeviceRect::zero);
+
+ let required_plane_count =
+ match external_surface.dependency {
+ ExternalSurfaceDependency::Yuv { format, .. } => {
+ format.get_plane_num()
+ },
+ ExternalSurfaceDependency::Rgb { .. } => {
+ 1
+ }
+ };
+
+ let mut image_dependencies = [ImageDependency::INVALID; 3];
+
+ for i in 0 .. 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 = match self.compositor_kind {
+ CompositorKind::Draw { .. } => 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_cache,
+ deferred_resolves,
+ );
+ if external_surface_index == ResolvedExternalSurfaceIndex::INVALID {
+ continue;
+ }
+ external_surface_index
+ } else {
+ ResolvedExternalSurfaceIndex::INVALID
+ };
+
+ let tile = CompositeTile {
+ surface: CompositeTileSurface::ExternalSurface { external_surface_index },
+ rect: external_surface.surface_rect,
+ valid_rect: external_surface.surface_rect.translate(-external_surface.surface_rect.origin.to_vector()),
+ dirty_rect: external_surface.surface_rect.translate(-external_surface.surface_rect.origin.to_vector()),
+ clip_rect,
+ transform: Some(external_surface.transform),
+ z_id: external_surface.z_id,
+ };
+
+ // 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: external_surface.transform,
+ image_dependencies: image_dependencies,
+ image_rendering: external_surface.image_rendering,
+ tile_descriptors: Vec::new(),
+ }
+ );
+
+ self.push_tile(tile, true);
+ }
+
+ // Add alpha / overlay tiles after compositor surfaces
+ if visible_alpha_tile_count > 0 {
+ self.descriptor.surfaces.push(
+ CompositeSurfaceDescriptor {
+ surface_id: tile_cache.native_surface.as_ref().map(|s| s.alpha),
+ clip_rect: surface_clip_rect,
+ transform: CompositorSurfaceTransform::translation(
+ tile_cache.device_position.x,
+ tile_cache.device_position.y,
+ 0.0,
+ ),
+ image_dependencies: [ImageDependency::INVALID; 3],
+ image_rendering: ImageRendering::CrispEdges,
+ tile_descriptors: alpha_tile_descriptors,
+ }
+ );
+ }
+ }
+
+ fn compute_external_surface_dependencies(
+ &mut self,
+ external_surface: &ExternalSurfaceDescriptor,
+ image_dependencies: &[ImageDependency; 3],
+ required_plane_count: usize,
+ resource_cache: &ResourceCache,
+ gpu_cache: &mut GpuCache,
+ deferred_resolves: &mut Vec<DeferredResolve>,
+ ) -> ResolvedExternalSurfaceIndex {
+ let mut planes = [
+ ExternalPlaneDescriptor::invalid(),
+ ExternalPlaneDescriptor::invalid(),
+ ExternalPlaneDescriptor::invalid(),
+ ];
+
+ let mut valid_plane_count = 0;
+ for i in 0 .. required_plane_count {
+ let request = ImageRequest {
+ key: image_dependencies[i].key,
+ rendering: external_surface.image_rendering,
+ tile: None,
+ };
+
+ let cache_item = resolve_image(
+ request,
+ resource_cache,
+ gpu_cache,
+ deferred_resolves,
+ );
+
+ if cache_item.texture_id != TextureSource::Invalid {
+ valid_plane_count += 1;
+ let plane = &mut planes[i];
+ *plane = ExternalPlaneDescriptor {
+ texture: cache_item.texture_id,
+ texture_layer: cache_item.texture_layer,
+ uv_rect: cache_item.uv_rect.into(),
+ };
+ }
+ }
+
+ // 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, rescale, .. } => {
+
+ let image_buffer_kind = planes[0].texture.image_buffer_kind();
+
+ self.external_surfaces.push(ResolvedExternalSurface {
+ color_data: ResolvedExternalSurfaceColorData::Yuv {
+ image_dependencies: *image_dependencies,
+ planes,
+ color_space,
+ format,
+ rescale,
+ },
+ image_buffer_kind,
+ update_params,
+ });
+ },
+ ExternalSurfaceDependency::Rgb{ flip_y, .. } => {
+
+ let image_buffer_kind = planes[0].texture.image_buffer_kind();
+
+ // Only propagate flip_y if the compositor doesn't support transforms,
+ // since otherwise it'll be handled as part of the transform.
+ self.external_surfaces.push(ResolvedExternalSurface {
+ color_data: ResolvedExternalSurfaceColorData::Rgb {
+ image_dependency: image_dependencies[0],
+ plane: planes[0],
+ flip_y: flip_y && !self.compositor_kind.supports_transforms(),
+ },
+ image_buffer_kind,
+ update_params,
+ });
+ },
+ }
+ external_surface_index
+ }
+
+ /// Add a tile to the appropriate array, depending on tile properties and compositor mode.
+ fn push_tile(
+ &mut self,
+ tile: CompositeTile,
+ is_opaque: bool,
+ ) {
+ match tile.surface {
+ CompositeTileSurface::Color { .. } => {
+ // Color tiles are, by definition, opaque. We might support non-opaque color
+ // tiles if we ever find pages that have a lot of these.
+ self.opaque_tiles.push(tile);
+ }
+ CompositeTileSurface::Clear => {
+ // Clear tiles have a special bucket
+ self.clear_tiles.push(tile);
+ }
+ CompositeTileSurface::Texture { .. } => {
+ // Texture surfaces get bucketed by opaque/alpha, for z-rejection
+ // on the Draw compositor mode.
+ if is_opaque {
+ self.opaque_tiles.push(tile);
+ } else {
+ self.alpha_tiles.push(tile);
+ }
+ }
+ CompositeTileSurface::ExternalSurface { .. } => {
+ self.opaque_tiles.push(tile);
+ }
+ }
+ }
+}
+
+/// 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))]
+pub struct NativeSurfaceId(pub u64);
+
+impl NativeSurfaceId {
+ /// A special id for the native surface that is used for debug / profiler overlays.
+ pub const DEBUG_OVERLAY: NativeSurfaceId = NativeSurfaceId(u64::MAX);
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct NativeTileId {
+ pub surface_id: NativeSurfaceId,
+ pub x: i32,
+ pub y: i32,
+}
+
+impl NativeTileId {
+ /// A special id for the native surface that is used for debug / profiler overlays.
+ pub const 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)]
+pub struct 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)]
+pub struct CompositorCapabilities {
+ pub virtual_surface_size: i32,
+}
+
+/// 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;
+// pub type CompositorSurfaceTransform = Transform3D<f32, CompositorSurfacePixel, DevicePixel>;
+pub type CompositorSurfaceTransform = Transform3D<f32, DevicePixel, DevicePixel>;
+
+/// 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.
+pub trait Compositor {
+ /// Create a new OS compositor surface with the given properties.
+ fn create_surface(
+ &mut self,
+ 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(
+ &mut self,
+ id: NativeSurfaceId,
+ is_opaque: bool,
+ );
+
+ /// 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(
+ &mut self,
+ id: NativeSurfaceId,
+ );
+
+ /// Create a new OS compositor tile with the given properties.
+ fn create_tile(
+ &mut self,
+ id: NativeTileId,
+ );
+
+ /// Destroy an existing compositor tile.
+ fn destroy_tile(
+ &mut self,
+ 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(
+ &mut self,
+ 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(
+ &mut self,
+ _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(
+ &mut self,
+ 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(
+ &mut self,
+ );
+
+ /// Begin the frame
+ fn begin_frame(&mut self);
+
+ /// 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(
+ &mut self,
+ id: NativeSurfaceId,
+ transform: CompositorSurfaceTransform,
+ clip_rect: DeviceIntRect,
+ image_rendering: ImageRendering,
+ );
+
+ /// 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(
+ &mut self,
+ _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(&mut self);
+
+ /// Enable/disable native compositor usage
+ fn enable_native_compositor(&mut self, enable: bool);
+
+ /// Safely deinitialize any remaining resources owned by the compositor.
+ fn deinit(&mut self);
+
+ /// 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) -> CompositorCapabilities;
+}
+
+/// 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.
+pub trait 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(&mut self, 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,
+ device_rect: DeviceIntRect,
+}
+
+// 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,
+}
+
+impl OcclusionEvent {
+ fn new(y: i32, kind: OcclusionEventKind, x0: i32, x1: i32) -> Self {
+ OcclusionEvent {
+ y,
+ x_range: ops::Range {
+ start: x0,
+ end: x1,
+ },
+ kind,
+ }
+ }
+}
+
+/// List of registered occluders.
+///
+/// Also store a couple of vectors for reuse.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct Occluders {
+ occluders: Vec<Occluder>,
+
+ // The two vectors below are kept to avoid unnecessary reallocations in area().
+
+ #[cfg_attr(feature = "serde", serde(skip))]
+ events: Vec<OcclusionEvent>,
+
+ #[cfg_attr(feature = "serde", serde(skip))]
+ active: Vec<ops::Range<i32>>,
+}
+
+impl Occluders {
+ fn new() -> Self {
+ Occluders {
+ occluders: Vec::new(),
+ events: Vec::new(),
+ active: Vec::new(),
+ }
+ }
+
+ fn push(&mut self, device_rect: DeviceIntRect, z_id: ZBufferId) {
+ self.occluders.push(Occluder { device_rect, z_id });
+ }
+
+ /// Returns true if a tile with the specified rectangle and z_id
+ /// is occluded by an opaque surface in front of it.
+ pub fn is_tile_occluded(
+ &mut self,
+ z_id: ZBufferId,
+ device_rect: DeviceRect,
+ ) -> 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 device_rect = device_rect.round().to_i32();
+ let ref_area = device_rect.size.width * device_rect.size.height;
+
+ // Calculate the non-overlapping area of the valid occluders.
+ let cover_area = self.area(z_id, &device_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(
+ &mut self,
+ z_id: ZBufferId,
+ clip_rect: &DeviceIntRect,
+ ) -> 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.
+
+ self.events.clear();
+ self.active.clear();
+
+ let mut area = 0;
+
+ // 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.
+ if let Some(rect) = occluder.device_rect.intersection(clip_rect) {
+ let x0 = rect.origin.x;
+ let x1 = x0 + rect.size.width;
+ self.events.push(OcclusionEvent::new(rect.origin.y, OcclusionEventKind::Begin, x0, x1));
+ self.events.push(OcclusionEvent::new(rect.origin.y + rect.size.height, OcclusionEventKind::End, x0, x1));
+ }
+ }
+ }
+
+ // If we didn't end up with any valid events, the area must be 0
+ if self.events.is_empty() {
+ return 0;
+ }
+
+ // Sort the events by y-value
+ self.events.sort_by_key(|e| e.y);
+ let mut cur_y = self.events[0].y;
+
+ // Step through each y interval
+ for event in &self.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.active.is_empty() {
+ assert!(dy > 0);
+
+ // Step through the x-ranges, ordered by x0 of each event
+ self.active.sort_by_key(|i| i.start);
+ let mut query = 0;
+ let mut cur = self.active[0].start;
+
+ // Accumulate the non-overlapping x-interval that contributes to area for this y-interval.
+ for interval in &self.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.active.push(event.x_range.clone());
+ }
+ OcclusionEventKind::End => {
+ let index = self.active.iter().position(|i| *i == event.x_range).unwrap();
+ self.active.remove(index);
+ }
+ }
+
+ cur_y = event.y;
+ }
+
+ area
+ }
+}