cached_surface.rs (17969B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use api::ColorF; 6 use api::PropertyBindingId; 7 use api::units::*; 8 use smallvec::SmallVec; 9 use crate::ItemUid; 10 use crate::composite::CompositeState; 11 use crate::internal_types::{FastHashMap, FrameId}; 12 use crate::invalidation::compare::ImageDependency; 13 use crate::invalidation::compare::{ColorBinding, OpacityBinding, OpacityBindingInfo, PrimitiveComparisonKey}; 14 use crate::invalidation::compare::{SpatialNodeComparer, PrimitiveComparer, PrimitiveDependency, ColorBindingInfo}; 15 use crate::invalidation::{InvalidationReason, PrimitiveCompareResult, quadtree::TileNode}; 16 use crate::picture::{PictureCompositeMode, SurfaceIndex, clampf}; 17 use crate::print_tree::PrintTreePrinter; 18 use crate::resource_cache::ResourceCache; 19 use crate::space::SpaceMapper; 20 use crate::spatial_tree::SpatialNodeIndex; 21 use crate::visibility::FrameVisibilityContext; 22 use peek_poke::poke_into_vec; 23 use std::mem; 24 25 pub struct CachedSurface { 26 pub current_descriptor: CachedSurfaceDescriptor, 27 pub prev_descriptor: CachedSurfaceDescriptor, 28 pub is_valid: bool, 29 pub local_valid_rect: PictureBox2D, 30 pub local_dirty_rect: PictureRect, 31 pub local_rect: PictureRect, 32 pub root: TileNode, 33 pub background_color: Option<ColorF>, 34 pub invalidation_reason: Option<InvalidationReason>, 35 pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>, 36 } 37 38 impl CachedSurface { 39 pub fn new() -> Self { 40 CachedSurface { 41 current_descriptor: CachedSurfaceDescriptor::new(), 42 prev_descriptor: CachedSurfaceDescriptor::new(), 43 is_valid: false, 44 local_valid_rect: PictureBox2D::zero(), 45 local_dirty_rect: PictureRect::zero(), 46 local_rect: PictureRect::zero(), 47 root: TileNode::new_leaf(Vec::new()), 48 background_color: None, 49 invalidation_reason: None, 50 sub_graphs: Vec::new(), 51 } 52 } 53 54 pub fn print(&self, pt: &mut dyn PrintTreePrinter) { 55 pt.add_item(format!("background_color: {:?}", self.background_color)); 56 pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason)); 57 self.current_descriptor.print(pt); 58 } 59 60 /// Setup state before primitive dependency calculation. 61 pub fn pre_update( 62 &mut self, 63 background_color: Option<ColorF>, 64 local_tile_rect: PictureRect, 65 frame_id: FrameId, 66 is_visible: bool, 67 ) { 68 // TODO(gw): This is a hack / fix for Box2D::union in euclid not working with 69 // zero sized rect accumulation. Once that lands, we'll revert this 70 // to be zero. 71 self.local_valid_rect = PictureBox2D::new( 72 PicturePoint::new( 1.0e32, 1.0e32), 73 PicturePoint::new(-1.0e32, -1.0e32), 74 ); 75 self.invalidation_reason = None; 76 self.sub_graphs.clear(); 77 78 // If the tile isn't visible, early exit, skipping the normal set up to 79 // validate dependencies. Instead, we will only compare the current tile 80 // dependencies the next time it comes into view. 81 if !is_visible { 82 return; 83 } 84 85 if background_color != self.background_color { 86 self.invalidate(None, InvalidationReason::BackgroundColor); 87 self.background_color = background_color; 88 } 89 90 // Clear any dependencies so that when we rebuild them we 91 // can compare if the tile has the same content. 92 mem::swap( 93 &mut self.current_descriptor, 94 &mut self.prev_descriptor, 95 ); 96 self.current_descriptor.clear(); 97 self.root.clear(local_tile_rect); 98 99 self.current_descriptor.last_updated_frame_id = frame_id; 100 } 101 102 pub fn add_prim_dependency( 103 &mut self, 104 info: &PrimitiveDependencyInfo, 105 local_tile_rect: PictureRect, 106 ) { 107 // Incorporate the bounding rect of the primitive in the local valid rect 108 // for this tile. This is used to minimize the size of the scissor rect 109 // during rasterization and the draw rect during composition of partial tiles. 110 self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box); 111 112 // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port, 113 // which can cause invalidations when a new display list with changed 114 // display port is received. To work around this, clamp the prim clip rect 115 // to the tile boundaries - if the clip hasn't affected the tile, then the 116 // changed clip can't affect the content of the primitive on this tile. 117 // In future, we could consider supplying the display port clip from Gecko 118 // in a different way (e.g. as a scroll frame clip) which still provides 119 // the desired clip for checkerboarding, but doesn't require this extra 120 // work below. 121 122 // TODO(gw): This is a hot part of the code - we could probably optimize further by: 123 // - Using min/max instead of clamps below (if we guarantee the rects are well formed) 124 125 let pmin = local_tile_rect.min; 126 let pmax = local_tile_rect.max; 127 128 let prim_clip_box = PictureBox2D::new( 129 PicturePoint::new( 130 clampf(info.prim_clip_box.min.x, pmin.x, pmax.x), 131 clampf(info.prim_clip_box.min.y, pmin.y, pmax.y), 132 ), 133 PicturePoint::new( 134 clampf(info.prim_clip_box.max.x, pmin.x, pmax.x), 135 clampf(info.prim_clip_box.max.y, pmin.y, pmax.y), 136 ), 137 ); 138 139 // Update the tile descriptor, used for tile comparison during scene swaps. 140 let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32); 141 142 // Encode the deps for this primitive in the `dep_data` byte buffer. 143 let dep_offset = self.current_descriptor.dep_data.len() as u32; 144 let mut dep_count = 0; 145 146 for clip in &info.clips { 147 dep_count += 1; 148 poke_into_vec( 149 &PrimitiveDependency::Clip { 150 clip: *clip, 151 }, 152 &mut self.current_descriptor.dep_data, 153 ); 154 } 155 156 for spatial_node_index in &info.spatial_nodes { 157 dep_count += 1; 158 poke_into_vec( 159 &PrimitiveDependency::SpatialNode { 160 index: *spatial_node_index, 161 }, 162 &mut self.current_descriptor.dep_data, 163 ); 164 } 165 166 for image in &info.images { 167 dep_count += 1; 168 poke_into_vec( 169 &PrimitiveDependency::Image { 170 image: *image, 171 }, 172 &mut self.current_descriptor.dep_data, 173 ); 174 } 175 176 for binding in &info.opacity_bindings { 177 dep_count += 1; 178 poke_into_vec( 179 &PrimitiveDependency::OpacityBinding { 180 binding: *binding, 181 }, 182 &mut self.current_descriptor.dep_data, 183 ); 184 } 185 186 if let Some(ref binding) = info.color_binding { 187 dep_count += 1; 188 poke_into_vec( 189 &PrimitiveDependency::ColorBinding { 190 binding: *binding, 191 }, 192 &mut self.current_descriptor.dep_data, 193 ); 194 } 195 196 self.current_descriptor.prims.push(PrimitiveDescriptor { 197 prim_uid: info.prim_uid, 198 prim_clip_box, 199 dep_offset, 200 dep_count, 201 }); 202 203 // Add this primitive to the dirty rect quadtree. 204 self.root.add_prim(prim_index, &info.prim_clip_box); 205 } 206 207 /// Check if the content of the previous and current tile descriptors match 208 fn update_dirty_rects( 209 &mut self, 210 ctx: &TileUpdateDirtyContext, 211 state: &mut TileUpdateDirtyState, 212 invalidation_reason: &mut Option<InvalidationReason>, 213 frame_context: &FrameVisibilityContext, 214 ) -> PictureRect { 215 let mut prim_comparer = PrimitiveComparer::new( 216 &self.prev_descriptor, 217 &self.current_descriptor, 218 state.resource_cache, 219 state.spatial_node_comparer, 220 ctx.opacity_bindings, 221 ctx.color_bindings, 222 ); 223 224 let mut dirty_rect = PictureBox2D::zero(); 225 self.root.update_dirty_rects( 226 &self.prev_descriptor.prims, 227 &self.current_descriptor.prims, 228 &mut prim_comparer, 229 &mut dirty_rect, 230 state.compare_cache, 231 invalidation_reason, 232 frame_context, 233 ); 234 235 dirty_rect 236 } 237 238 /// Invalidate a tile based on change in content. This 239 /// must be called even if the tile is not currently 240 /// visible on screen. We might be able to improve this 241 /// later by changing how ComparableVec is used. 242 pub fn update_content_validity( 243 &mut self, 244 ctx: &TileUpdateDirtyContext, 245 state: &mut TileUpdateDirtyState, 246 frame_context: &FrameVisibilityContext, 247 ) { 248 // Check if the contents of the primitives, clips, and 249 // other dependencies are the same. 250 state.compare_cache.clear(); 251 let mut invalidation_reason = None; 252 let dirty_rect = self.update_dirty_rects( 253 ctx, 254 state, 255 &mut invalidation_reason, 256 frame_context, 257 ); 258 259 if !dirty_rect.is_empty() { 260 self.invalidate( 261 Some(dirty_rect), 262 invalidation_reason.expect("bug: no invalidation_reason") 263 ); 264 } 265 if ctx.invalidate_all { 266 self.invalidate(None, InvalidationReason::ScaleChanged); 267 } 268 // TODO(gw): We can avoid invalidating the whole tile in some cases here, 269 // but it should be a fairly rare invalidation case. 270 if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect { 271 self.invalidate(None, InvalidationReason::ValidRectChanged); 272 state.composite_state.dirty_rects_are_valid = false; 273 } 274 } 275 276 /// Invalidate this tile. If `invalidation_rect` is None, the entire 277 /// tile is invalidated. 278 pub fn invalidate( 279 &mut self, 280 invalidation_rect: Option<PictureRect>, 281 reason: InvalidationReason, 282 ) { 283 self.is_valid = false; 284 285 match invalidation_rect { 286 Some(rect) => { 287 self.local_dirty_rect = self.local_dirty_rect.union(&rect); 288 } 289 None => { 290 self.local_dirty_rect = self.local_rect; 291 } 292 } 293 294 if self.invalidation_reason.is_none() { 295 self.invalidation_reason = Some(reason); 296 } 297 } 298 } 299 300 // Immutable context passed to picture cache tiles during update_dirty_and_valid_rects 301 pub struct TileUpdateDirtyContext<'a> { 302 /// Maps from picture cache coords -> world space coords. 303 pub pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>, 304 305 /// Global scale factor from world -> device pixels. 306 pub global_device_pixel_scale: DevicePixelScale, 307 308 /// Information about opacity bindings from the picture cache. 309 pub opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>, 310 311 /// Information about color bindings from the picture cache. 312 pub color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>, 313 314 /// The local rect of the overall picture cache 315 pub local_rect: PictureRect, 316 317 /// If true, the scale factor of the root transform for this picture 318 /// cache changed, so we need to invalidate the tile and re-render. 319 pub invalidate_all: bool, 320 } 321 322 // Mutable state passed to picture cache tiles during update_dirty_and_valid_rects 323 pub struct TileUpdateDirtyState<'a> { 324 /// Allow access to the texture cache for requesting tiles 325 pub resource_cache: &'a mut ResourceCache, 326 327 /// Current configuration and setup for compositing all the picture cache tiles in renderer. 328 pub composite_state: &'a mut CompositeState, 329 330 /// A cache of comparison results to avoid re-computation during invalidation. 331 pub compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>, 332 333 /// Information about transform node differences from last frame. 334 pub spatial_node_comparer: &'a mut SpatialNodeComparer, 335 } 336 337 /// Information about the dependencies of a single primitive instance. 338 pub struct PrimitiveDependencyInfo { 339 /// Unique content identifier of the primitive. 340 pub prim_uid: ItemUid, 341 342 /// The (conservative) clipped area in picture space this primitive occupies. 343 pub prim_clip_box: PictureBox2D, 344 345 /// Image keys this primitive depends on. 346 pub images: SmallVec<[ImageDependency; 8]>, 347 348 /// Opacity bindings this primitive depends on. 349 pub opacity_bindings: SmallVec<[OpacityBinding; 4]>, 350 351 /// Color binding this primitive depends on. 352 pub color_binding: Option<ColorBinding>, 353 354 /// Clips that this primitive depends on. 355 pub clips: SmallVec<[ItemUid; 8]>, 356 357 /// Spatial nodes references by the clip dependencies of this primitive. 358 pub spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>, 359 } 360 361 impl PrimitiveDependencyInfo { 362 pub fn new( 363 prim_uid: crate::intern::ItemUid, 364 prim_clip_box: PictureBox2D, 365 ) -> Self { 366 PrimitiveDependencyInfo { 367 prim_uid, 368 prim_clip_box, 369 images: smallvec::SmallVec::new(), 370 opacity_bindings: smallvec::SmallVec::new(), 371 color_binding: None, 372 clips: smallvec::SmallVec::new(), 373 spatial_nodes: smallvec::SmallVec::new(), 374 } 375 } 376 } 377 378 /// Information about a primitive that is a dependency for a cached surface. 379 #[derive(Debug, Clone)] 380 #[cfg_attr(feature = "capture", derive(Serialize))] 381 #[cfg_attr(feature = "replay", derive(Deserialize))] 382 pub struct PrimitiveDescriptor { 383 pub prim_uid: ItemUid, 384 pub prim_clip_box: PictureBox2D, 385 // TODO(gw): These two fields could be packed as a u24/u8 386 pub dep_offset: u32, 387 pub dep_count: u32, 388 } 389 390 impl PartialEq for PrimitiveDescriptor { 391 fn eq(&self, other: &Self) -> bool { 392 const EPSILON: f32 = 0.001; 393 394 if self.prim_uid != other.prim_uid { 395 return false; 396 } 397 398 use euclid::approxeq::ApproxEq; 399 if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) { 400 return false; 401 } 402 if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) { 403 return false; 404 } 405 if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) { 406 return false; 407 } 408 if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) { 409 return false; 410 } 411 412 if self.dep_count != other.dep_count { 413 return false; 414 } 415 416 true 417 } 418 } 419 420 impl PartialEq<PrimitiveDescriptor> for (&ItemUid, &PictureBox2D) { 421 fn eq(&self, other: &PrimitiveDescriptor) -> bool { 422 self.0 == &other.prim_uid && self.1 == &other.prim_clip_box 423 } 424 } 425 426 /// An index into the prims array in a TileDescriptor. 427 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 428 #[cfg_attr(feature = "capture", derive(Serialize))] 429 #[cfg_attr(feature = "replay", derive(Deserialize))] 430 pub struct PrimitiveDependencyIndex(pub u32); 431 432 /// Uniquely describes the content of this cached surface, in a way that can be 433 /// (reasonably) efficiently hashed and compared. 434 #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] 435 #[cfg_attr(feature = "capture", derive(Serialize))] 436 #[cfg_attr(feature = "replay", derive(Deserialize))] 437 pub struct CachedSurfaceDescriptor { 438 /// List of primitive instance unique identifiers. The uid is guaranteed 439 /// to uniquely describe the content of the primitive template, while 440 /// the other parameters describe the clip chain and instance params. 441 pub prims: Vec<PrimitiveDescriptor>, 442 443 /// Picture space rect that contains valid pixels region of this tile. 444 pub local_valid_rect: PictureRect, 445 446 /// The last frame this tile had its dependencies updated (dependency updating is 447 /// skipped if a tile is off-screen). 448 pub last_updated_frame_id: FrameId, 449 450 /// Packed per-prim dependency information 451 pub dep_data: Vec<u8>, 452 } 453 454 impl CachedSurfaceDescriptor { 455 pub fn new() -> Self { 456 CachedSurfaceDescriptor { 457 local_valid_rect: PictureRect::zero(), 458 dep_data: Vec::new(), 459 prims: Vec::new(), 460 last_updated_frame_id: FrameId::INVALID, 461 } 462 } 463 464 /// Print debug information about this tile descriptor to a tree printer. 465 pub fn print(&self, pt: &mut dyn crate::print_tree::PrintTreePrinter) { 466 pt.new_level("current_descriptor".to_string()); 467 468 pt.new_level("prims".to_string()); 469 for prim in &self.prims { 470 pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid())); 471 pt.add_item(format!("clip: p0={},{} p1={},{}", 472 prim.prim_clip_box.min.x, 473 prim.prim_clip_box.min.y, 474 prim.prim_clip_box.max.x, 475 prim.prim_clip_box.max.y, 476 )); 477 pt.end_level(); 478 } 479 pt.end_level(); 480 481 pt.end_level(); 482 } 483 484 /// Clear the dependency information for a tile, when the dependencies 485 /// are being rebuilt. 486 pub fn clear(&mut self) { 487 self.local_valid_rect = PictureRect::zero(); 488 self.prims.clear(); 489 self.dep_data.clear(); 490 } 491 }