/* 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; use api::PropertyBindingId; use api::units::*; use smallvec::SmallVec; use crate::ItemUid; use crate::composite::CompositeState; use crate::internal_types::{FastHashMap, FrameId}; use crate::invalidation::compare::ImageDependency; use crate::invalidation::compare::{ColorBinding, OpacityBinding, OpacityBindingInfo, PrimitiveComparisonKey}; use crate::invalidation::compare::{SpatialNodeComparer, PrimitiveComparer, PrimitiveDependency, ColorBindingInfo}; use crate::invalidation::{InvalidationReason, PrimitiveCompareResult, quadtree::TileNode}; use crate::picture::{PictureCompositeMode, SurfaceIndex, clampf}; use crate::print_tree::PrintTreePrinter; use crate::resource_cache::ResourceCache; use crate::space::SpaceMapper; use crate::spatial_tree::SpatialNodeIndex; use crate::visibility::FrameVisibilityContext; use peek_poke::poke_into_vec; use std::mem; pub struct CachedSurface { pub current_descriptor: CachedSurfaceDescriptor, pub prev_descriptor: CachedSurfaceDescriptor, pub is_valid: bool, pub local_valid_rect: PictureBox2D, pub local_dirty_rect: PictureRect, pub local_rect: PictureRect, pub root: TileNode, pub background_color: Option, pub invalidation_reason: Option, pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>, } impl CachedSurface { pub fn new() -> Self { CachedSurface { current_descriptor: CachedSurfaceDescriptor::new(), prev_descriptor: CachedSurfaceDescriptor::new(), is_valid: false, local_valid_rect: PictureBox2D::zero(), local_dirty_rect: PictureRect::zero(), local_rect: PictureRect::zero(), root: TileNode::new_leaf(Vec::new()), background_color: None, invalidation_reason: None, sub_graphs: Vec::new(), } } pub fn print(&self, pt: &mut dyn PrintTreePrinter) { pt.add_item(format!("background_color: {:?}", self.background_color)); pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason)); self.current_descriptor.print(pt); } /// Setup state before primitive dependency calculation. pub fn pre_update( &mut self, background_color: Option, local_tile_rect: PictureRect, frame_id: FrameId, is_visible: bool, ) { // TODO(gw): This is a hack / fix for Box2D::union in euclid not working with // zero sized rect accumulation. Once that lands, we'll revert this // to be zero. self.local_valid_rect = PictureBox2D::new( PicturePoint::new( 1.0e32, 1.0e32), PicturePoint::new(-1.0e32, -1.0e32), ); self.invalidation_reason = None; self.sub_graphs.clear(); // If the tile isn't visible, early exit, skipping the normal set up to // validate dependencies. Instead, we will only compare the current tile // dependencies the next time it comes into view. if !is_visible { return; } if background_color != self.background_color { self.invalidate(None, InvalidationReason::BackgroundColor); self.background_color = background_color; } // Clear any dependencies so that when we rebuild them we // can compare if the tile has the same content. mem::swap( &mut self.current_descriptor, &mut self.prev_descriptor, ); self.current_descriptor.clear(); self.root.clear(local_tile_rect); self.current_descriptor.last_updated_frame_id = frame_id; } pub fn add_prim_dependency( &mut self, info: &PrimitiveDependencyInfo, local_tile_rect: PictureRect, ) { // Incorporate the bounding rect of the primitive in the local valid rect // for this tile. This is used to minimize the size of the scissor rect // during rasterization and the draw rect during composition of partial tiles. self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box); // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port, // which can cause invalidations when a new display list with changed // display port is received. To work around this, clamp the prim clip rect // to the tile boundaries - if the clip hasn't affected the tile, then the // changed clip can't affect the content of the primitive on this tile. // In future, we could consider supplying the display port clip from Gecko // in a different way (e.g. as a scroll frame clip) which still provides // the desired clip for checkerboarding, but doesn't require this extra // work below. // TODO(gw): This is a hot part of the code - we could probably optimize further by: // - Using min/max instead of clamps below (if we guarantee the rects are well formed) let pmin = local_tile_rect.min; let pmax = local_tile_rect.max; let prim_clip_box = PictureBox2D::new( PicturePoint::new( clampf(info.prim_clip_box.min.x, pmin.x, pmax.x), clampf(info.prim_clip_box.min.y, pmin.y, pmax.y), ), PicturePoint::new( clampf(info.prim_clip_box.max.x, pmin.x, pmax.x), clampf(info.prim_clip_box.max.y, pmin.y, pmax.y), ), ); // Update the tile descriptor, used for tile comparison during scene swaps. let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32); // Encode the deps for this primitive in the `dep_data` byte buffer. let dep_offset = self.current_descriptor.dep_data.len() as u32; let mut dep_count = 0; for clip in &info.clips { dep_count += 1; poke_into_vec( &PrimitiveDependency::Clip { clip: *clip, }, &mut self.current_descriptor.dep_data, ); } for spatial_node_index in &info.spatial_nodes { dep_count += 1; poke_into_vec( &PrimitiveDependency::SpatialNode { index: *spatial_node_index, }, &mut self.current_descriptor.dep_data, ); } for image in &info.images { dep_count += 1; poke_into_vec( &PrimitiveDependency::Image { image: *image, }, &mut self.current_descriptor.dep_data, ); } for binding in &info.opacity_bindings { dep_count += 1; poke_into_vec( &PrimitiveDependency::OpacityBinding { binding: *binding, }, &mut self.current_descriptor.dep_data, ); } if let Some(ref binding) = info.color_binding { dep_count += 1; poke_into_vec( &PrimitiveDependency::ColorBinding { binding: *binding, }, &mut self.current_descriptor.dep_data, ); } self.current_descriptor.prims.push(PrimitiveDescriptor { prim_uid: info.prim_uid, prim_clip_box, dep_offset, dep_count, }); // Add this primitive to the dirty rect quadtree. self.root.add_prim(prim_index, &info.prim_clip_box); } /// Check if the content of the previous and current tile descriptors match fn update_dirty_rects( &mut self, ctx: &TileUpdateDirtyContext, state: &mut TileUpdateDirtyState, invalidation_reason: &mut Option, frame_context: &FrameVisibilityContext, ) -> PictureRect { let mut prim_comparer = PrimitiveComparer::new( &self.prev_descriptor, &self.current_descriptor, state.resource_cache, state.spatial_node_comparer, ctx.opacity_bindings, ctx.color_bindings, ); let mut dirty_rect = PictureBox2D::zero(); self.root.update_dirty_rects( &self.prev_descriptor.prims, &self.current_descriptor.prims, &mut prim_comparer, &mut dirty_rect, state.compare_cache, invalidation_reason, frame_context, ); dirty_rect } /// Invalidate a tile based on change in content. This /// must be called even if the tile is not currently /// visible on screen. We might be able to improve this /// later by changing how ComparableVec is used. pub fn update_content_validity( &mut self, ctx: &TileUpdateDirtyContext, state: &mut TileUpdateDirtyState, frame_context: &FrameVisibilityContext, ) { // Check if the contents of the primitives, clips, and // other dependencies are the same. state.compare_cache.clear(); let mut invalidation_reason = None; let dirty_rect = self.update_dirty_rects( ctx, state, &mut invalidation_reason, frame_context, ); if !dirty_rect.is_empty() { self.invalidate( Some(dirty_rect), invalidation_reason.expect("bug: no invalidation_reason") ); } if ctx.invalidate_all { self.invalidate(None, InvalidationReason::ScaleChanged); } // TODO(gw): We can avoid invalidating the whole tile in some cases here, // but it should be a fairly rare invalidation case. if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect { self.invalidate(None, InvalidationReason::ValidRectChanged); state.composite_state.dirty_rects_are_valid = false; } } /// Invalidate this tile. If `invalidation_rect` is None, the entire /// tile is invalidated. pub fn invalidate( &mut self, invalidation_rect: Option, reason: InvalidationReason, ) { self.is_valid = false; match invalidation_rect { Some(rect) => { self.local_dirty_rect = self.local_dirty_rect.union(&rect); } None => { self.local_dirty_rect = self.local_rect; } } if self.invalidation_reason.is_none() { self.invalidation_reason = Some(reason); } } } // Immutable context passed to picture cache tiles during update_dirty_and_valid_rects pub struct TileUpdateDirtyContext<'a> { /// Maps from picture cache coords -> world space coords. pub pic_to_world_mapper: SpaceMapper, /// Global scale factor from world -> device pixels. pub global_device_pixel_scale: DevicePixelScale, /// Information about opacity bindings from the picture cache. pub opacity_bindings: &'a FastHashMap, /// Information about color bindings from the picture cache. pub color_bindings: &'a FastHashMap, /// The local rect of the overall picture cache pub local_rect: PictureRect, /// If true, the scale factor of the root transform for this picture /// cache changed, so we need to invalidate the tile and re-render. pub invalidate_all: bool, } // Mutable state passed to picture cache tiles during update_dirty_and_valid_rects pub struct TileUpdateDirtyState<'a> { /// Allow access to the texture cache for requesting tiles pub resource_cache: &'a mut ResourceCache, /// Current configuration and setup for compositing all the picture cache tiles in renderer. pub composite_state: &'a mut CompositeState, /// A cache of comparison results to avoid re-computation during invalidation. pub compare_cache: &'a mut FastHashMap, /// Information about transform node differences from last frame. pub spatial_node_comparer: &'a mut SpatialNodeComparer, } /// Information about the dependencies of a single primitive instance. pub struct PrimitiveDependencyInfo { /// Unique content identifier of the primitive. pub prim_uid: ItemUid, /// The (conservative) clipped area in picture space this primitive occupies. pub prim_clip_box: PictureBox2D, /// Image keys this primitive depends on. pub images: SmallVec<[ImageDependency; 8]>, /// Opacity bindings this primitive depends on. pub opacity_bindings: SmallVec<[OpacityBinding; 4]>, /// Color binding this primitive depends on. pub color_binding: Option, /// Clips that this primitive depends on. pub clips: SmallVec<[ItemUid; 8]>, /// Spatial nodes references by the clip dependencies of this primitive. pub spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>, } impl PrimitiveDependencyInfo { pub fn new( prim_uid: crate::intern::ItemUid, prim_clip_box: PictureBox2D, ) -> Self { PrimitiveDependencyInfo { prim_uid, prim_clip_box, images: smallvec::SmallVec::new(), opacity_bindings: smallvec::SmallVec::new(), color_binding: None, clips: smallvec::SmallVec::new(), spatial_nodes: smallvec::SmallVec::new(), } } } /// Information about a primitive that is a dependency for a cached surface. #[derive(Debug, Clone)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct PrimitiveDescriptor { pub prim_uid: ItemUid, pub prim_clip_box: PictureBox2D, // TODO(gw): These two fields could be packed as a u24/u8 pub dep_offset: u32, pub dep_count: u32, } impl PartialEq for PrimitiveDescriptor { fn eq(&self, other: &Self) -> bool { const EPSILON: f32 = 0.001; if self.prim_uid != other.prim_uid { return false; } use euclid::approxeq::ApproxEq; if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) { return false; } if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) { return false; } if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) { return false; } if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) { return false; } if self.dep_count != other.dep_count { return false; } true } } impl PartialEq for (&ItemUid, &PictureBox2D) { fn eq(&self, other: &PrimitiveDescriptor) -> bool { self.0 == &other.prim_uid && self.1 == &other.prim_clip_box } } /// An index into the prims array in a TileDescriptor. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct PrimitiveDependencyIndex(pub u32); /// Uniquely describes the content of this cached surface, in a way that can be /// (reasonably) efficiently hashed and compared. #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct CachedSurfaceDescriptor { /// List of primitive instance unique identifiers. The uid is guaranteed /// to uniquely describe the content of the primitive template, while /// the other parameters describe the clip chain and instance params. pub prims: Vec, /// Picture space rect that contains valid pixels region of this tile. pub local_valid_rect: PictureRect, /// 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, /// Packed per-prim dependency information pub dep_data: Vec, } impl CachedSurfaceDescriptor { pub fn new() -> Self { CachedSurfaceDescriptor { local_valid_rect: PictureRect::zero(), dep_data: Vec::new(), prims: Vec::new(), last_updated_frame_id: FrameId::INVALID, } } /// Print debug information about this tile descriptor to a tree printer. pub fn print(&self, pt: &mut dyn crate::print_tree::PrintTreePrinter) { pt.new_level("current_descriptor".to_string()); pt.new_level("prims".to_string()); for prim in &self.prims { pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid())); pt.add_item(format!("clip: p0={},{} p1={},{}", prim.prim_clip_box.min.x, prim.prim_clip_box.min.y, prim.prim_clip_box.max.x, prim.prim_clip_box.max.y, )); pt.end_level(); } pt.end_level(); pt.end_level(); } /// Clear the dependency information for a tile, when the dependencies /// are being rebuilt. pub fn clear(&mut self) { self.local_valid_rect = PictureRect::zero(); self.prims.clear(); self.dep_data.clear(); } }