From 0d8ed820725f711c55446f947aaab633345b49e1 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 8 Apr 2020 21:49:55 +0000 Subject: [PATCH 1/2] Bug 1627864 - Fix invalidation of tiles when the shape of the spatial node tree changes. r=nical,Bert,kvark Previously, primitive dependency checking would invalidate a tile if the spatial node index for a given primitive changed. However, if a new display list is sent that changes the shape of the spatial node tree this may cause unnecessary invalidations. For example, a new display list that inserts a new spatial node at the start of the tree could result in spatial node indices being different, even though the values of the transforms was the same. This patch changes the invalidation logic for spatial nodes to compare the transforms by value, rather than index, meaning that invalidations are avoided if the shape of the spatial tree has changed, but the values are consistent. Differential Revision: https://phabricator.services.mozilla.com/D69913 [ghsync] From https://hg.mozilla.org/mozilla-central/rev/498d2204d63739057b626da3239d130c412099e0 --- webrender/src/picture.rs | 282 ++++++++++++++++++++++++++------------- 1 file changed, 189 insertions(+), 93 deletions(-) diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 9dc072f4b7..1e3c895c4f 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -122,7 +122,7 @@ use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey}; use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex}; use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveVisibilityFlags}; use crate::print_tree::{PrintTree, PrintTreePrinter}; -use crate::render_backend::DataStores; +use crate::render_backend::{DataStores, FrameId}; use crate::render_task_graph::RenderTaskId; use crate::render_target::RenderTargetKind; use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode}; @@ -132,6 +132,7 @@ use crate::spatial_tree::CoordinateSystemId; use smallvec::SmallVec; use std::{mem, u8, marker, u32}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::collections::hash_map::Entry; use crate::texture_cache::TextureCacheHandle; use crate::util::{MaxRect, VecHelper, RectHelpers, MatrixHelpers}; use crate::filterdata::{FilterDataHandle}; @@ -264,7 +265,7 @@ pub struct PictureCacheState { /// The tiles retained by this picture cache. pub tiles: FastHashMap>, /// State of the spatial nodes from previous frame - spatial_nodes: FastHashMap, + spatial_node_comparer: SpatialNodeComparer, /// State of opacity bindings from previous frame opacity_bindings: FastHashMap, /// State of color bindings from previous frame @@ -281,6 +282,8 @@ pub struct PictureCacheState { pub external_native_surface_cache: FastHashMap, /// The retained virtual offset for this slice between display lists. virtual_offset: DeviceIntPoint, + /// Current frame ID of this picture cache + frame_id: FrameId, } pub struct PictureCacheRecycledAllocations { @@ -456,13 +459,104 @@ pub type OpacityBindingInfo = BindingInfo; pub type ColorBinding = Binding; pub type ColorBindingInfo = BindingInfo; -/// Information about the state of a spatial node value -#[derive(Debug)] -pub struct SpatialNodeDependency { - /// The current value retrieved from the spatial tree. - value: TransformKey, - /// True if it was changed (or is new) since the last frame build. - changed: bool, +/// A dependency for a transform is defined by the spatial node index + frame it was used +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SpatialNodeKey { + spatial_node_index: SpatialNodeIndex, + frame_id: FrameId, +} + +/// A helper for comparing spatial nodes between frames. The comparisons +/// are done by-value, so that if the shape of the spatial node tree +/// changes, invalidations aren't done simply due to the spatial node +/// index changing between display lists. +struct SpatialNodeComparer { + /// The root spatial node index of the tile cache + ref_spatial_node_index: SpatialNodeIndex, + /// Maintains a map of currently active transform keys + spatial_nodes: FastHashMap, + /// A cache of recent comparisons between prev and current spatial nodes + compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>, + /// A set of frames that we need to retain spatial node entries for + referenced_frames: FastHashSet, +} + +impl SpatialNodeComparer { + /// Construct a new comparer + fn new() -> Self { + SpatialNodeComparer { + ref_spatial_node_index: ROOT_SPATIAL_NODE_INDEX, + spatial_nodes: FastHashMap::default(), + compare_cache: FastHashMap::default(), + referenced_frames: FastHashSet::default(), + } + } + + /// Advance to the next frame + fn next_frame( + &mut self, + ref_spatial_node_index: SpatialNodeIndex, + ) { + // Drop any node information for unreferenced frames, to ensure that the + // hashmap doesn't grow indefinitely! + let referenced_frames = &self.referenced_frames; + self.spatial_nodes.retain(|key, _| { + referenced_frames.contains(&key.frame_id) + }); + + // Update the root spatial node for this comparer + self.ref_spatial_node_index = ref_spatial_node_index; + self.compare_cache.clear(); + self.referenced_frames.clear(); + } + + /// Register a transform that is used, and build the transform key for it if new. + fn register_used_transform( + &mut self, + spatial_node_index: SpatialNodeIndex, + frame_id: FrameId, + spatial_tree: &SpatialTree, + ) { + let key = SpatialNodeKey { + spatial_node_index, + frame_id, + }; + + if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) { + entry.insert( + get_transform_key( + spatial_node_index, + self.ref_spatial_node_index, + spatial_tree, + ) + ); + } + } + + /// Return true if the transforms for two given spatial nodes are considered equivalent + fn are_transforms_equivalent( + &mut self, + prev_spatial_node_key: &SpatialNodeKey, + curr_spatial_node_key: &SpatialNodeKey, + ) -> bool { + let key = (*prev_spatial_node_key, *curr_spatial_node_key); + let spatial_nodes = &self.spatial_nodes; + + *self.compare_cache + .entry(key) + .or_insert_with(|| { + let prev = &spatial_nodes[&prev_spatial_node_key]; + let curr = &spatial_nodes[&curr_spatial_node_key]; + curr == prev + }) + } + + /// Ensure that the comparer won't GC any nodes for a given frame id + fn retain_for_frame(&mut self, frame_id: FrameId) { + self.referenced_frames.insert(frame_id); + } } // Immutable context passed to picture cache tiles during pre_update @@ -482,6 +576,9 @@ struct TilePreUpdateContext { /// Current size of tiles in picture units. tile_size: PictureSize, + + /// The current frame id for this picture cache + frame_id: FrameId, } // Immutable context passed to picture cache tiles during post_update @@ -498,9 +595,6 @@ struct TilePostUpdateContext<'a> { /// The calculated backdrop information for this cache instance. backdrop: BackdropInfo, - /// Information about transform node differences from last frame. - spatial_nodes: &'a FastHashMap, - /// Information about opacity bindings from the picture cache. opacity_bindings: &'a FastHashMap, @@ -537,6 +631,9 @@ struct TilePostUpdateState<'a> { /// A cache of comparison results to avoid re-computation during invalidation. compare_cache: &'a mut FastHashMap, + + /// Information about transform node differences from last frame. + spatial_node_comparer: &'a mut SpatialNodeComparer, } /// Information about the dependencies of a single primitive instance. @@ -759,7 +856,7 @@ pub enum PrimitiveCompareResultDetail { }, /// The value of the transform changed Transform { - detail: CompareHelperResult, + detail: CompareHelperResult, }, /// An image dependency was dirty Image { @@ -894,6 +991,9 @@ pub struct Tile { fg_local_valid_rect: PictureRect, /// z-buffer id for this tile, which is one of z_id_opaque or z_id_alpha, depending on tile opacity pub z_id: ZBufferId, + /// The last frame this tile had its dependencies updated (dependency updating is + /// skipped if a tile is off-screen). + pub last_updated_frame_id: FrameId, } impl Tile { @@ -923,6 +1023,7 @@ impl Tile { bg_local_valid_rect: PictureRect::zero(), fg_local_valid_rect: PictureRect::zero(), z_id: ZBufferId::invalid(), + last_updated_frame_id: FrameId::INVALID, } } @@ -949,7 +1050,7 @@ impl Tile { &self.prev_descriptor, &self.current_descriptor, state.resource_cache, - ctx.spatial_nodes, + state.spatial_node_comparer, ctx.opacity_bindings, ctx.color_bindings, ); @@ -1086,6 +1187,10 @@ impl Tile { ); self.current_descriptor.clear(); self.root.clear(self.local_tile_rect); + + // Since this tile is determined to be visible, it will get updated + // dependencies, so update the frame id we are storing dependencies for. + self.last_updated_frame_id = ctx.frame_id; } /// Add dependencies for a given primitive to this tile. @@ -1130,7 +1235,14 @@ impl Tile { self.current_descriptor.clips.extend_from_slice(&info.clips); // Include any transforms that this primitive depends on. - self.current_descriptor.transforms.extend_from_slice(&info.spatial_nodes); + for spatial_node_index in &info.spatial_nodes { + self.current_descriptor.transforms.push( + SpatialNodeKey { + spatial_node_index: *spatial_node_index, + frame_id: self.last_updated_frame_id, + } + ); + } // Include any color bindings this primitive depends on. if info.color_binding.is_some() { @@ -1205,6 +1317,12 @@ impl Tile { state: &mut TilePostUpdateState, frame_context: &FrameVisibilityContext, ) -> bool { + // Register the frame id of this tile with the spatial node comparer, to ensure + // that it doesn't GC any spatial nodes from the comparer that are referenced + // by this tile. Must be done before we early exit below, so that we retain + // spatial node info even for tiles that are currently not visible. + state.spatial_node_comparer.retain_for_frame(self.last_updated_frame_id); + // If tile is not visible, just early out from here - we don't update dependencies // so don't want to invalidate, merge, split etc. The tile won't need to be drawn // (and thus updated / invalidated) until it is on screen again. @@ -1519,9 +1637,9 @@ impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq { &self, prev_count: u8, curr_count: u8, - f: F, + mut f: F, opt_detail: Option<&mut CompareHelperResult>, - ) -> bool where F: Fn(&T) -> bool { + ) -> bool where F: FnMut(&T, &T) -> bool { // If the number of items is different, trivial reject. if prev_count != curr_count { if let Some(detail) = opt_detail { *detail = CompareHelperResult::Count{ prev_count, curr_count }; } @@ -1546,14 +1664,7 @@ impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq { let prev_items = &self.prev_items[self.offset_prev .. end_prev]; for (curr, prev) in curr_items.iter().zip(prev_items.iter()) { - if prev != curr { - if let Some(detail) = opt_detail { - *detail = CompareHelperResult::NotEqual{ prev: *prev, curr: *curr }; - } - return false; - } - - if f(curr) { + if !f(prev, curr) { if let Some(detail) = opt_detail { *detail = CompareHelperResult::PredicateTrue{ curr: *curr }; } return false; } @@ -1597,7 +1708,7 @@ pub struct TileDescriptor { /// List of the effects of transforms that we care about /// tracking for this tile. - transforms: Vec, + transforms: Vec, /// Picture space rect that contains valid pixels region of this tile. local_valid_rect: PictureRect, @@ -2186,16 +2297,8 @@ pub struct TileCacheInstance { opacity_bindings: FastHashMap, /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. old_opacity_bindings: FastHashMap, - /// List of spatial nodes, with some extra information - /// about whether they changed since last frame. - spatial_nodes: FastHashMap, - /// Switch back and forth between old and new spatial nodes hashmaps to avoid re-allocating. - old_spatial_nodes: FastHashMap, - /// A set of spatial nodes that primitives / clips depend on found - /// during dependency creation. This is used to avoid trying to - /// calculate invalid relative transforms when building the spatial - /// nodes hash above. - used_spatial_nodes: FastHashSet, + /// A helper to compare transforms between previous and current frame. + spatial_node_comparer: SpatialNodeComparer, /// List of color bindings, with some extra information /// about whether they changed since last frame. color_bindings: FastHashMap, @@ -2269,6 +2372,8 @@ pub struct TileCacheInstance { pub z_id_opaque: ZBufferId, /// A cache of compositor surfaces that are retained between frames pub external_native_surface_cache: FastHashMap, + /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting + frame_id: FrameId, } impl TileCacheInstance { @@ -2298,9 +2403,7 @@ impl TileCacheInstance { ), opacity_bindings: FastHashMap::default(), old_opacity_bindings: FastHashMap::default(), - spatial_nodes: FastHashMap::default(), - old_spatial_nodes: FastHashMap::default(), - used_spatial_nodes: FastHashSet::default(), + spatial_node_comparer: SpatialNodeComparer::new(), color_bindings: FastHashMap::default(), old_color_bindings: FastHashMap::default(), dirty_region: DirtyRegion::new(), @@ -2332,6 +2435,7 @@ impl TileCacheInstance { external_surfaces: Vec::new(), z_id_opaque: ZBufferId::invalid(), external_native_surface_cache: FastHashMap::default(), + frame_id: FrameId::INVALID, } } @@ -2455,13 +2559,14 @@ impl TileCacheInstance { if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) { self.tiles.extend(prev_state.tiles); self.root_transform = prev_state.root_transform; - self.spatial_nodes = prev_state.spatial_nodes; + self.spatial_node_comparer = prev_state.spatial_node_comparer; self.opacity_bindings = prev_state.opacity_bindings; self.color_bindings = prev_state.color_bindings; self.current_tile_size = prev_state.current_tile_size; self.native_surface = prev_state.native_surface; self.external_native_surface_cache = prev_state.external_native_surface_cache; self.virtual_offset = prev_state.virtual_offset; + self.frame_id = prev_state.frame_id; fn recycle_map( ideal_len: usize, @@ -2494,6 +2599,14 @@ impl TileCacheInstance { ); } + // Advance the current frame ID counter for this picture cache (must be done + // after any retained prev state is taken above). + self.frame_id.advance(); + + // Notify the spatial node comparer that a new frame has started, and the + // current reference spatial node for this tile cache. + self.spatial_node_comparer.next_frame(self.spatial_node_index); + // At the start of the frame, step through each current compositor surface // and mark it as unused. Later, this is used to free old compositor surfaces. // TODO(gw): In future, we might make this more sophisticated - for example, @@ -2757,6 +2870,7 @@ impl TileCacheInstance { background_color: self.background_color, global_screen_world_rect: frame_context.global_screen_world_rect, tile_size: self.tile_size, + frame_id: self.frame_id, }; // Pre-update each tile @@ -3326,7 +3440,13 @@ impl TileCacheInstance { } // Record any new spatial nodes in the used list. - self.used_spatial_nodes.extend(&prim_info.spatial_nodes); + for spatial_node_index in &prim_info.spatial_nodes { + self.spatial_node_comparer.register_used_transform( + *spatial_node_index, + self.frame_id, + frame_context.spatial_tree, + ); + } // Truncate the lengths of dependency arrays to the max size we can handle. // Any arrays this size or longer will invalidate every frame. @@ -3485,40 +3605,6 @@ impl TileCacheInstance { frame_state.composite_state.dirty_rects_are_valid = false; } - // Diff the state of the spatial nodes between last frame build and now. - mem::swap(&mut self.spatial_nodes, &mut self.old_spatial_nodes); - - // TODO(gw): Maybe remove the used_spatial_nodes set and just mutate / create these - // diffs inside add_prim_dependency? - self.spatial_nodes.clear(); - for spatial_node_index in self.used_spatial_nodes.drain() { - // Get the current relative transform. - let mut value = get_transform_key( - spatial_node_index, - self.spatial_node_index, - frame_context.spatial_tree, - ); - - // Check if the transform has changed from last frame - let mut changed = true; - if let Some(old_info) = self.old_spatial_nodes.remove(&spatial_node_index) { - if old_info.value == value { - // Since the transform key equality check applies epsilon, if we - // consider the value to be the same, store that old value to avoid - // missing very slow drifts in the value over time. - // TODO(gw): We should change ComparableVec to use a trait for comparison - // rather than PartialEq. - value = old_info.value; - changed = false; - } - } - - self.spatial_nodes.insert(spatial_node_index, SpatialNodeDependency { - changed, - value, - }); - } - let pic_to_world_mapper = SpaceMapper::new_with_target( ROOT_SPATIAL_NODE_INDEX, self.spatial_node_index, @@ -3534,7 +3620,6 @@ impl TileCacheInstance { global_device_pixel_scale: frame_context.global_device_pixel_scale, local_clip_rect: self.local_clip_rect, backdrop: self.backdrop, - spatial_nodes: &self.spatial_nodes, opacity_bindings: &self.opacity_bindings, color_bindings: &self.color_bindings, current_tile_size: self.current_tile_size, @@ -3548,6 +3633,7 @@ impl TileCacheInstance { resource_cache: frame_state.resource_cache, composite_state: frame_state.composite_state, compare_cache: &mut self.compare_cache, + spatial_node_comparer: &mut self.spatial_node_comparer, }; // Step through each tile and invalidate if the dependencies have changed. Determine @@ -4403,7 +4489,7 @@ impl PicturePrimitive { tile_cache.slice, PictureCacheState { tiles: tile_cache.tiles, - spatial_nodes: tile_cache.spatial_nodes, + spatial_node_comparer: tile_cache.spatial_node_comparer, opacity_bindings: tile_cache.opacity_bindings, color_bindings: tile_cache.color_bindings, root_transform: tile_cache.root_transform, @@ -4411,6 +4497,7 @@ impl PicturePrimitive { native_surface: tile_cache.native_surface, external_native_surface_cache: tile_cache.external_native_surface_cache, virtual_offset: tile_cache.virtual_offset, + frame_id: tile_cache.frame_id, allocations: PictureCacheRecycledAllocations { old_opacity_bindings: tile_cache.old_opacity_bindings, old_color_bindings: tile_cache.old_color_bindings, @@ -6171,12 +6258,12 @@ impl ImageDependency { /// A helper struct to compare a primitive and all its sub-dependencies. struct PrimitiveComparer<'a> { clip_comparer: CompareHelper<'a, ItemUid>, - transform_comparer: CompareHelper<'a, SpatialNodeIndex>, + transform_comparer: CompareHelper<'a, SpatialNodeKey>, image_comparer: CompareHelper<'a, ImageDependency>, opacity_comparer: CompareHelper<'a, OpacityBinding>, color_comparer: CompareHelper<'a, ColorBinding>, resource_cache: &'a ResourceCache, - spatial_nodes: &'a FastHashMap, + spatial_node_comparer: &'a mut SpatialNodeComparer, opacity_bindings: &'a FastHashMap, color_bindings: &'a FastHashMap, } @@ -6186,7 +6273,7 @@ impl<'a> PrimitiveComparer<'a> { prev: &'a TileDescriptor, curr: &'a TileDescriptor, resource_cache: &'a ResourceCache, - spatial_nodes: &'a FastHashMap, + spatial_node_comparer: &'a mut SpatialNodeComparer, opacity_bindings: &'a FastHashMap, color_bindings: &'a FastHashMap, ) -> Self { @@ -6222,7 +6309,7 @@ impl<'a> PrimitiveComparer<'a> { opacity_comparer, color_comparer, resource_cache, - spatial_nodes, + spatial_node_comparer, opacity_bindings, color_bindings, } @@ -6260,7 +6347,7 @@ impl<'a> PrimitiveComparer<'a> { opt_detail: Option<&mut PrimitiveCompareResultDetail>, ) -> PrimitiveCompareResult { let resource_cache = self.resource_cache; - let spatial_nodes = self.spatial_nodes; + let spatial_node_comparer = &mut self.spatial_node_comparer; let opacity_bindings = self.opacity_bindings; let color_bindings = self.color_bindings; @@ -6277,8 +6364,8 @@ impl<'a> PrimitiveComparer<'a> { if !self.clip_comparer.is_same( prev.clip_dep_count, curr.clip_dep_count, - |_| { - false + |prev, curr| { + prev == curr }, if opt_detail.is_some() { Some(&mut clip_result) } else { None } ) { @@ -6291,8 +6378,8 @@ impl<'a> PrimitiveComparer<'a> { if !self.transform_comparer.is_same( prev.transform_dep_count, curr.transform_dep_count, - |curr| { - spatial_nodes[curr].changed + |prev, curr| { + spatial_node_comparer.are_transforms_equivalent(prev, curr) }, if opt_detail.is_some() { Some(&mut transform_result) } else { None }, ) { @@ -6307,8 +6394,9 @@ impl<'a> PrimitiveComparer<'a> { if !self.image_comparer.is_same( prev.image_dep_count, curr.image_dep_count, - |curr| { - resource_cache.get_image_generation(curr.key) != curr.generation + |prev, curr| { + prev == curr && + resource_cache.get_image_generation(curr.key) == curr.generation }, if opt_detail.is_some() { Some(&mut image_result) } else { None }, ) { @@ -6323,16 +6411,20 @@ impl<'a> PrimitiveComparer<'a> { if !self.opacity_comparer.is_same( prev.opacity_binding_dep_count, curr.opacity_binding_dep_count, - |curr| { + |prev, curr| { + if prev != curr { + return false; + } + if let OpacityBinding::Binding(id) = curr { if opacity_bindings .get(id) .map_or(true, |info| info.changed) { - return true; + return false; } } - false + true }, if opt_detail.is_some() { Some(&mut bind_result) } else { None }, ) { @@ -6347,12 +6439,16 @@ impl<'a> PrimitiveComparer<'a> { if !self.color_comparer.is_same( prev.color_binding_dep_count, curr.color_binding_dep_count, - |curr| { + |prev, curr| { + if prev != curr { + return false; + } + if let ColorBinding::Binding(id) = curr { if color_bindings .get(id) .map_or(true, |info| info.changed) { - return true; + return false; } } From ff553269e347d76a3ef192be3e2807b5eb56a47b Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 8 Apr 2020 21:50:04 +0000 Subject: [PATCH 2/2] Bug 1627816 - Fix skipping composites when tile surface changes. r=nical This fixes a case where the backing surface of a picture cache tile changes, but was still being considered a no-op frame, which skips the composite as an optimization if nothing has changed. In this case, the tile surface changes from a rasterized texture to a solid color that doesn't require a picture cache texture. The patch ensures that the composite surface descriptors that are used to detect no-op frame compositions include the backing surface for each of the tiles that make up this virtual surface. Differential Revision: https://phabricator.services.mozilla.com/D70138 [ghsync] From https://hg.mozilla.org/mozilla-central/rev/5b784c47235ccd535d694c1ec2caf5b341570ff1 --- webrender/src/composite.rs | 58 ++++++++++++++++++++++++++++++++++++-- webrender/src/picture.rs | 2 +- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/webrender/src/composite.rs b/webrender/src/composite.rs index 1e82c592cf..fab67657b6 100644 --- a/webrender/src/composite.rs +++ b/webrender/src/composite.rs @@ -9,7 +9,7 @@ use crate::batch::{resolve_image, get_buffer_kind}; use crate::gpu_cache::GpuCache; use crate::gpu_types::{ZBufferId, ZBufferIdGenerator}; use crate::internal_types::TextureSource; -use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileSurface}; +use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileId, TileSurface}; use crate::prim_store::DeferredResolve; use crate::renderer::ImageBufferKind; use crate::resource_cache::{ImageRequest, ResourceCache}; @@ -246,7 +246,40 @@ struct Occluder { device_rect: DeviceIntRect, } -/// Describes the properties that identify a tile composition uniquely. +/// 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)] @@ -259,6 +292,8 @@ pub struct CompositeSurfaceDescriptor { // thing that has changed is the generation of an compositor surface // image dependency. pub image_dependencies: [ImageDependency; 3], + // List of the surface information for each tile added to this virtual surface + pub tile_descriptors: Vec, } /// Describes surface properties used to composite a frame. This @@ -406,6 +441,8 @@ impl CompositeState { ) { 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(); for tile in tile_cache.tiles.values() { if !tile.is_visible { @@ -416,6 +453,11 @@ impl CompositeState { let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round(); let surface = tile.surface.as_ref().expect("no tile surface set!"); + 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) @@ -433,8 +475,10 @@ impl CompositeState { }; if is_opaque { + opaque_tile_descriptors.push(descriptor); visible_opaque_tile_count += 1; } else { + alpha_tile_descriptors.push(descriptor); visible_alpha_tile_count += 1; } @@ -450,6 +494,13 @@ impl CompositeState { 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); + // Add opaque surface before any compositor surfaces if visible_opaque_tile_count > 0 { self.descriptor.surfaces.push( @@ -458,6 +509,7 @@ impl CompositeState { offset: tile_cache.device_position, clip_rect: device_clip_rect, image_dependencies: [ImageDependency::INVALID; 3], + tile_descriptors: opaque_tile_descriptors, } ); } @@ -562,6 +614,7 @@ impl CompositeState { offset: tile.rect.origin, clip_rect: tile.clip_rect, image_dependencies: external_surface.image_dependencies, + tile_descriptors: Vec::new(), } ); @@ -576,6 +629,7 @@ impl CompositeState { offset: tile_cache.device_position, clip_rect: device_clip_rect, image_dependencies: [ImageDependency::INVALID; 3], + tile_descriptors: alpha_tile_descriptors, } ); } diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 1e3c895c4f..167bf72cf2 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -693,7 +693,7 @@ impl PrimitiveDependencyInfo { /// A stable ID for a given tile, to help debugging. These are also used /// as unique identifiers for tile surfaces when using a native compositor. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct TileId(pub usize);