slice_builder.rs (29572B)
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::{BorderRadius, ClipId, ClipMode, ColorF, DebugFlags, PrimitiveFlags, QualitySettings, RasterSpace}; 6 use api::units::*; 7 use crate::clip::{ClipItemKeyKind, ClipNodeId, ClipTreeBuilder}; 8 use crate::frame_builder::FrameBuilderConfig; 9 use crate::internal_types::FastHashMap; 10 use crate::picture::{PrimitiveList, PictureCompositeMode, PicturePrimitive, Picture3DContext, PictureFlags}; 11 use crate::tile_cache::{SliceId, TileCacheParams}; 12 use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex}; 13 use crate::scene_building::SliceFlags; 14 use crate::scene_builder_thread::Interners; 15 use crate::spatial_tree::{SpatialNodeIndex, SceneSpatialTree}; 16 use crate::util::VecHelper; 17 use std::mem; 18 19 /* 20 Types and functionality related to picture caching. In future, we'll 21 move more and more of the existing functionality out of picture.rs 22 and into here. 23 */ 24 25 // If the page would create too many slices (an arbitrary definition where 26 // it's assumed the GPU memory + compositing overhead would be too high) 27 // then create a single picture cache for the remaining content. This at 28 // least means that we can cache small content changes efficiently when 29 // scrolling isn't occurring. Scrolling regions will be handled reasonably 30 // efficiently by the dirty rect tracking (since it's likely that if the 31 // page has so many slices there isn't a single major scroll region). 32 const MAX_CACHE_SLICES: usize = 16; 33 34 struct SliceDescriptor { 35 prim_list: PrimitiveList, 36 scroll_root: SpatialNodeIndex, 37 } 38 39 enum SliceKind { 40 Default { 41 secondary_slices: Vec<SliceDescriptor>, 42 }, 43 Atomic { 44 prim_list: PrimitiveList, 45 }, 46 } 47 48 impl SliceKind { 49 fn default() -> Self { 50 SliceKind::Default { 51 secondary_slices: Vec::new(), 52 } 53 } 54 } 55 56 struct PrimarySlice { 57 /// Whether this slice is atomic or has secondary slice(s) 58 kind: SliceKind, 59 /// Optional background color of this slice 60 background_color: Option<ColorF>, 61 /// Optional root clip for the iframe 62 iframe_clip: Option<ClipId>, 63 /// Information about how to draw and composite this slice 64 slice_flags: SliceFlags, 65 } 66 67 impl PrimarySlice { 68 fn new( 69 slice_flags: SliceFlags, 70 iframe_clip: Option<ClipId>, 71 background_color: Option<ColorF>, 72 ) -> Self { 73 PrimarySlice { 74 kind: SliceKind::default(), 75 background_color, 76 iframe_clip, 77 slice_flags, 78 } 79 } 80 81 fn has_too_many_slices(&self) -> bool { 82 match self.kind { 83 SliceKind::Atomic { .. } => false, 84 SliceKind::Default { ref secondary_slices } => secondary_slices.len() > MAX_CACHE_SLICES, 85 } 86 } 87 88 fn merge(&mut self) { 89 self.slice_flags |= SliceFlags::IS_ATOMIC; 90 91 let old = mem::replace( 92 &mut self.kind, 93 SliceKind::Default { secondary_slices: Vec::new() }, 94 ); 95 96 self.kind = match old { 97 SliceKind::Default { mut secondary_slices } => { 98 let mut prim_list = PrimitiveList::empty(); 99 100 for descriptor in secondary_slices.drain(..) { 101 prim_list.merge(descriptor.prim_list); 102 } 103 104 SliceKind::Atomic { 105 prim_list, 106 } 107 } 108 atomic => atomic, 109 } 110 } 111 } 112 113 /// Used during scene building to construct the list of pending tile caches. 114 pub struct TileCacheBuilder { 115 /// List of tile caches that have been created so far (last in the list is currently active). 116 primary_slices: Vec<PrimarySlice>, 117 /// Cache the previous scroll root search for a spatial node, since they are often the same. 118 prev_scroll_root_cache: (SpatialNodeIndex, SpatialNodeIndex), 119 /// Handle to the root reference frame 120 root_spatial_node_index: SpatialNodeIndex, 121 /// Debug flags to provide to our TileCacheInstances. 122 debug_flags: DebugFlags, 123 } 124 125 /// The output of a tile cache builder, containing all details needed to construct the 126 /// tile cache(s) for the next scene, and retain tiles from the previous frame when sent 127 /// send to the frame builder. 128 pub struct TileCacheConfig { 129 /// Mapping of slice id to the parameters needed to construct this tile cache. 130 pub tile_caches: FastHashMap<SliceId, TileCacheParams>, 131 /// Number of picture cache slices that were created (for profiler) 132 pub picture_cache_slice_count: usize, 133 } 134 135 impl TileCacheConfig { 136 pub fn new(picture_cache_slice_count: usize) -> Self { 137 TileCacheConfig { 138 tile_caches: FastHashMap::default(), 139 picture_cache_slice_count, 140 } 141 } 142 } 143 144 impl TileCacheBuilder { 145 /// Construct a new tile cache builder. 146 pub fn new( 147 root_spatial_node_index: SpatialNodeIndex, 148 background_color: Option<ColorF>, 149 debug_flags: DebugFlags, 150 ) -> Self { 151 TileCacheBuilder { 152 primary_slices: vec![PrimarySlice::new(SliceFlags::empty(), None, background_color)], 153 prev_scroll_root_cache: (SpatialNodeIndex::INVALID, SpatialNodeIndex::INVALID), 154 root_spatial_node_index, 155 debug_flags, 156 } 157 } 158 159 pub fn make_current_slice_atomic(&mut self) { 160 self.primary_slices 161 .last_mut() 162 .unwrap() 163 .merge(); 164 } 165 166 /// Returns true if the current slice has no primitives added yet 167 pub fn is_current_slice_empty(&self) -> bool { 168 match self.primary_slices.last() { 169 Some(slice) => { 170 match slice.kind { 171 SliceKind::Default { ref secondary_slices } => { 172 secondary_slices.is_empty() 173 } 174 SliceKind::Atomic { ref prim_list } => { 175 prim_list.is_empty() 176 } 177 } 178 } 179 None => { 180 true 181 } 182 } 183 } 184 185 /// Set a barrier that forces a new tile cache next time a prim is added. 186 pub fn add_tile_cache_barrier( 187 &mut self, 188 slice_flags: SliceFlags, 189 iframe_clip: Option<ClipId>, 190 ) { 191 let new_slice = PrimarySlice::new( 192 slice_flags, 193 iframe_clip, 194 None, 195 ); 196 197 self.primary_slices.push(new_slice); 198 } 199 200 /// Create a new tile cache for an existing prim_list 201 fn build_tile_cache( 202 &mut self, 203 prim_list: PrimitiveList, 204 spatial_tree: &SceneSpatialTree, 205 ) -> Option<SliceDescriptor> { 206 if prim_list.is_empty() { 207 return None; 208 } 209 210 // Iterate the clusters and determine which is the most commonly occurring 211 // scroll root. This is a reasonable heuristic to decide which spatial node 212 // should be considered the scroll root of this tile cache, in order to 213 // minimize the invalidations that occur due to scrolling. It's often the 214 // case that a blend container will have only a single scroll root. 215 let mut scroll_root_occurrences = FastHashMap::default(); 216 217 for cluster in &prim_list.clusters { 218 // If we encounter a cluster which has an unknown spatial node, 219 // we don't include that in the set of spatial nodes that we 220 // are trying to find scroll roots for. Later on, in finalize_picture, 221 // the cluster spatial node will be updated to the selected scroll root. 222 if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN { 223 continue; 224 } 225 226 let scroll_root = find_scroll_root( 227 cluster.spatial_node_index, 228 &mut self.prev_scroll_root_cache, 229 spatial_tree, 230 true, 231 ); 232 233 *scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1; 234 } 235 236 // We can't just select the most commonly occurring scroll root in this 237 // primitive list. If that is a nested scroll root, there may be 238 // primitives in the list that are outside that scroll root, which 239 // can cause panics when calculating relative transforms. To ensure 240 // this doesn't happen, only retain scroll root candidates that are 241 // also ancestors of every other scroll root candidate. 242 let scroll_roots: Vec<SpatialNodeIndex> = scroll_root_occurrences 243 .keys() 244 .cloned() 245 .collect(); 246 247 scroll_root_occurrences.retain(|parent_spatial_node_index, _| { 248 scroll_roots.iter().all(|child_spatial_node_index| { 249 parent_spatial_node_index == child_spatial_node_index || 250 spatial_tree.is_ancestor( 251 *parent_spatial_node_index, 252 *child_spatial_node_index, 253 ) 254 }) 255 }); 256 257 // Select the scroll root by finding the most commonly occurring one 258 let scroll_root = scroll_root_occurrences 259 .iter() 260 .max_by_key(|entry | entry.1) 261 .map(|(spatial_node_index, _)| *spatial_node_index) 262 .unwrap_or(self.root_spatial_node_index); 263 264 Some(SliceDescriptor { 265 scroll_root, 266 prim_list, 267 }) 268 } 269 270 /// Add a primitive, either to the current tile cache, or a new one, depending on various conditions. 271 pub fn add_prim( 272 &mut self, 273 prim_instance: PrimitiveInstance, 274 prim_rect: LayoutRect, 275 spatial_node_index: SpatialNodeIndex, 276 prim_flags: PrimitiveFlags, 277 spatial_tree: &SceneSpatialTree, 278 interners: &Interners, 279 quality_settings: &QualitySettings, 280 prim_instances: &mut Vec<PrimitiveInstance>, 281 clip_tree_builder: &ClipTreeBuilder, 282 ) { 283 let primary_slice = self.primary_slices.last_mut().unwrap(); 284 285 match primary_slice.kind { 286 SliceKind::Atomic { ref mut prim_list } => { 287 prim_list.add_prim( 288 prim_instance, 289 prim_rect, 290 spatial_node_index, 291 prim_flags, 292 prim_instances, 293 clip_tree_builder, 294 ); 295 } 296 SliceKind::Default { ref mut secondary_slices } => { 297 assert_ne!(spatial_node_index, SpatialNodeIndex::UNKNOWN); 298 299 // Check if we want to create a new slice based on the current / next scroll root 300 let scroll_root = find_scroll_root( 301 spatial_node_index, 302 &mut self.prev_scroll_root_cache, 303 spatial_tree, 304 // Allow sticky frames as scroll roots, unless our quality settings prefer 305 // subpixel AA over performance. 306 !quality_settings.force_subpixel_aa_where_possible, 307 ); 308 309 let current_scroll_root = secondary_slices 310 .last() 311 .map(|p| p.scroll_root); 312 313 let mut want_new_tile_cache = secondary_slices.is_empty(); 314 315 if let Some(current_scroll_root) = current_scroll_root { 316 want_new_tile_cache |= match (current_scroll_root, scroll_root) { 317 (_, _) if current_scroll_root == self.root_spatial_node_index && scroll_root == self.root_spatial_node_index => { 318 // Both current slice and this cluster are fixed position, no need to cut 319 false 320 } 321 (_, _) if current_scroll_root == self.root_spatial_node_index => { 322 // A real scroll root is being established, so create a cache slice 323 true 324 } 325 (_, _) if scroll_root == self.root_spatial_node_index => { 326 // If quality settings force subpixel AA over performance, skip creating 327 // a slice for the fixed position element(s) here. 328 if quality_settings.force_subpixel_aa_where_possible { 329 false 330 } else { 331 // A fixed position slice is encountered within a scroll root. Only create 332 // a slice in this case if all the clips referenced by this cluster are also 333 // fixed position. There's no real point in creating slices for these cases, 334 // since we'll have to rasterize them as the scrolling clip moves anyway. It 335 // also allows us to retain subpixel AA in these cases. For these types of 336 // slices, the intra-slice dirty rect handling typically works quite well 337 // (a common case is parallax scrolling effects). 338 let mut create_slice = true; 339 340 let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id); 341 let mut current_node_id = leaf.node_id; 342 343 while current_node_id != ClipNodeId::NONE { 344 let node = clip_tree_builder.get_node(current_node_id); 345 346 let clip_node_data = &interners.clip[node.handle]; 347 348 let spatial_root = find_scroll_root( 349 clip_node_data.key.spatial_node_index, 350 &mut self.prev_scroll_root_cache, 351 spatial_tree, 352 true, 353 ); 354 355 if spatial_root != self.root_spatial_node_index { 356 create_slice = false; 357 break; 358 } 359 360 current_node_id = node.parent; 361 } 362 363 create_slice 364 } 365 } 366 (curr_scroll_root, scroll_root) => { 367 // Two scrolling roots - only need a new slice if they differ 368 curr_scroll_root != scroll_root 369 } 370 }; 371 } 372 373 if want_new_tile_cache { 374 secondary_slices.push(SliceDescriptor { 375 prim_list: PrimitiveList::empty(), 376 scroll_root, 377 }); 378 } 379 380 secondary_slices 381 .last_mut() 382 .unwrap() 383 .prim_list 384 .add_prim( 385 prim_instance, 386 prim_rect, 387 spatial_node_index, 388 prim_flags, 389 prim_instances, 390 clip_tree_builder, 391 ); 392 } 393 } 394 } 395 396 /// Consume this object and build the list of tile cache primitives 397 pub fn build( 398 mut self, 399 config: &FrameBuilderConfig, 400 prim_store: &mut PrimitiveStore, 401 spatial_tree: &SceneSpatialTree, 402 prim_instances: &[PrimitiveInstance], 403 clip_tree_builder: &mut ClipTreeBuilder, 404 interners: &Interners, 405 ) -> (TileCacheConfig, Vec<PictureIndex>) { 406 let mut result = TileCacheConfig::new(self.primary_slices.len()); 407 let mut tile_cache_pictures = Vec::new(); 408 let primary_slices = std::mem::replace(&mut self.primary_slices, Vec::new()); 409 410 // TODO: At the moment, culling, clipping and invalidation are always 411 // done in the root coordinate space. The plan is to move to doing it 412 // (always or mostly) in raster space. 413 let visibility_node = spatial_tree.root_reference_frame_index(); 414 415 for mut primary_slice in primary_slices { 416 417 if primary_slice.has_too_many_slices() { 418 primary_slice.merge(); 419 } 420 421 match primary_slice.kind { 422 SliceKind::Atomic { prim_list } => { 423 if let Some(descriptor) = self.build_tile_cache( 424 prim_list, 425 spatial_tree, 426 ) { 427 create_tile_cache( 428 self.debug_flags, 429 primary_slice.slice_flags, 430 descriptor.scroll_root, 431 visibility_node, 432 primary_slice.iframe_clip, 433 descriptor.prim_list, 434 primary_slice.background_color, 435 prim_store, 436 prim_instances, 437 config, 438 &mut result.tile_caches, 439 &mut tile_cache_pictures, 440 clip_tree_builder, 441 interners, 442 spatial_tree, 443 ); 444 } 445 } 446 SliceKind::Default { secondary_slices } => { 447 for descriptor in secondary_slices { 448 create_tile_cache( 449 self.debug_flags, 450 primary_slice.slice_flags, 451 descriptor.scroll_root, 452 visibility_node, 453 primary_slice.iframe_clip, 454 descriptor.prim_list, 455 primary_slice.background_color, 456 prim_store, 457 prim_instances, 458 config, 459 &mut result.tile_caches, 460 &mut tile_cache_pictures, 461 clip_tree_builder, 462 interners, 463 spatial_tree, 464 ); 465 } 466 } 467 } 468 } 469 470 (result, tile_cache_pictures) 471 } 472 } 473 474 /// Find the scroll root for a given spatial node 475 fn find_scroll_root( 476 spatial_node_index: SpatialNodeIndex, 477 prev_scroll_root_cache: &mut (SpatialNodeIndex, SpatialNodeIndex), 478 spatial_tree: &SceneSpatialTree, 479 allow_sticky_frames: bool, 480 ) -> SpatialNodeIndex { 481 if prev_scroll_root_cache.0 == spatial_node_index { 482 return prev_scroll_root_cache.1; 483 } 484 485 let scroll_root = spatial_tree.find_scroll_root(spatial_node_index, allow_sticky_frames); 486 *prev_scroll_root_cache = (spatial_node_index, scroll_root); 487 488 scroll_root 489 } 490 491 /// Given a PrimitiveList and scroll root, construct a tile cache primitive instance 492 /// that wraps the primitive list. 493 fn create_tile_cache( 494 debug_flags: DebugFlags, 495 slice_flags: SliceFlags, 496 scroll_root: SpatialNodeIndex, 497 visibility_node: SpatialNodeIndex, 498 iframe_clip: Option<ClipId>, 499 prim_list: PrimitiveList, 500 background_color: Option<ColorF>, 501 prim_store: &mut PrimitiveStore, 502 prim_instances: &[PrimitiveInstance], 503 frame_builder_config: &FrameBuilderConfig, 504 tile_caches: &mut FastHashMap<SliceId, TileCacheParams>, 505 tile_cache_pictures: &mut Vec<PictureIndex>, 506 clip_tree_builder: &mut ClipTreeBuilder, 507 interners: &Interners, 508 spatial_tree: &SceneSpatialTree, 509 ) { 510 // Accumulate any clip instances from the iframe_clip into the shared clips 511 // that will be applied by this tile cache during compositing. 512 let mut additional_clips = Vec::new(); 513 514 if let Some(clip_id) = iframe_clip { 515 additional_clips.push(clip_id); 516 } 517 518 // Find the best shared clip node that we can apply while compositing tiles, 519 // rather than applying to each item individually. 520 521 // Step 1: Walk the primitive list, and find the LCA of the clip-tree that 522 // matches all primitives. This gives us our "best-case" shared 523 // clip node that moves as many clips as possible to compositing. 524 let mut shared_clip_node_id = None; 525 526 for cluster in &prim_list.clusters { 527 for prim_instance in &prim_instances[cluster.prim_range()] { 528 let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id); 529 530 // TODO(gw): Need to cache last clip-node id here? 531 shared_clip_node_id = match shared_clip_node_id { 532 Some(current) => { 533 Some(clip_tree_builder.find_lowest_common_ancestor(current, leaf.node_id)) 534 } 535 None => { 536 Some(leaf.node_id) 537 } 538 } 539 } 540 } 541 542 // Step 2: Now we need to walk up the shared clip node hierarchy, and remove clips 543 // that we can't handle during compositing, such as: 544 // (a) Non axis-aligned clips 545 // (b) Box-shadow or image-mask clips 546 // (c) Rounded rect clips. 547 // 548 // A follow up patch to this series will relax the condition on (c) to 549 // allow tile caches to apply a single rounded-rect clip during compositing. 550 let mut shared_clip_node_id = shared_clip_node_id.unwrap_or(ClipNodeId::NONE); 551 let mut current_node_id = shared_clip_node_id; 552 let mut rounded_rect_count = 0; 553 554 // Walk up the hierarchy to the root of the clip-tree 555 while current_node_id != ClipNodeId::NONE { 556 let node = clip_tree_builder.get_node(current_node_id); 557 let clip_node_data = &interners.clip[node.handle]; 558 559 // Check if this clip is in the root coord system (i.e. is axis-aligned with tile-cache) 560 let is_rcs = spatial_tree.is_root_coord_system(clip_node_data.key.spatial_node_index); 561 562 let node_valid = if is_rcs { 563 match clip_node_data.key.kind { 564 ClipItemKeyKind::BoxShadow(..) | 565 ClipItemKeyKind::ImageMask(..) | 566 ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) | 567 ClipItemKeyKind::RoundedRectangle(_, _, ClipMode::ClipOut) => { 568 // Has a box-shadow / image-mask, we can't handle this as a shared clip 569 false 570 } 571 ClipItemKeyKind::RoundedRectangle(rect, radius, ClipMode::Clip) => { 572 // The shader and CoreAnimation rely on certain constraints such 573 // as uniform radii to be able to apply the clip during compositing. 574 if BorderRadius::from(radius).can_use_fast_path_in(&rect.into()) { 575 rounded_rect_count += 1; 576 577 true 578 } else { 579 false 580 } 581 } 582 ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => { 583 // We can apply multiple (via combining) axis-aligned rectangle 584 // clips to the shared compositing clip. 585 true 586 } 587 } 588 } else { 589 // Has a complex transform, we can't handle this as a shared clip 590 false 591 }; 592 593 if node_valid { 594 // This node was found to be one we can apply during compositing. 595 if rounded_rect_count > 1 { 596 // However, we plan to only support one rounded-rect clip. If 597 // we have found > 1 rounded rect, drop children from the shared 598 // clip, and continue looking up the chain. 599 shared_clip_node_id = current_node_id; 600 rounded_rect_count = 1; 601 } 602 } else { 603 // Node was invalid, due to transform / clip type. Drop this clip 604 // and reset the rounded rect count to 0, since we drop children 605 // from here too. 606 shared_clip_node_id = node.parent; 607 rounded_rect_count = 0; 608 } 609 610 current_node_id = node.parent; 611 } 612 613 let shared_clip_leaf_id = Some(clip_tree_builder.build_for_tile_cache( 614 shared_clip_node_id, 615 &additional_clips, 616 )); 617 618 // Build a clip-chain for the tile cache, that contains any of the shared clips 619 // we will apply when drawing the tiles. In all cases provided by Gecko, these 620 // are rectangle clips with a scale/offset transform only, and get handled as 621 // a simple local clip rect in the vertex shader. However, this should in theory 622 // also work with any complex clips, such as rounded rects and image masks, by 623 // producing a clip mask that is applied to the picture cache tiles. 624 625 let slice = tile_cache_pictures.len(); 626 627 let background_color = if slice == 0 { 628 background_color 629 } else { 630 None 631 }; 632 633 let slice_id = SliceId::new(slice); 634 635 // Store some information about the picture cache slice. This is used when we swap the 636 // new scene into the frame builder to either reuse existing slices, or create new ones. 637 tile_caches.insert(slice_id, TileCacheParams { 638 debug_flags, 639 slice, 640 slice_flags, 641 spatial_node_index: scroll_root, 642 visibility_node_index: visibility_node, 643 background_color, 644 shared_clip_node_id, 645 shared_clip_leaf_id, 646 virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(), 647 image_surface_count: prim_list.image_surface_count, 648 yuv_image_surface_count: prim_list.yuv_image_surface_count, 649 }); 650 651 let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image( 652 Some(PictureCompositeMode::TileCache { slice_id }), 653 Picture3DContext::Out, 654 PrimitiveFlags::IS_BACKFACE_VISIBLE, 655 prim_list, 656 scroll_root, 657 RasterSpace::Screen, 658 PictureFlags::empty(), 659 None, 660 )); 661 662 tile_cache_pictures.push(PictureIndex(pic_index)); 663 } 664 665 /// Debug information about a set of picture cache slices, exposed via RenderResults 666 #[derive(Debug)] 667 #[cfg_attr(feature = "capture", derive(Serialize))] 668 #[cfg_attr(feature = "replay", derive(Deserialize))] 669 pub struct PictureCacheDebugInfo { 670 pub slices: FastHashMap<usize, SliceDebugInfo>, 671 } 672 673 impl PictureCacheDebugInfo { 674 pub fn new() -> Self { 675 PictureCacheDebugInfo { 676 slices: FastHashMap::default(), 677 } 678 } 679 680 /// Convenience method to retrieve a given slice. Deliberately panics 681 /// if the slice isn't present. 682 pub fn slice(&self, slice: usize) -> &SliceDebugInfo { 683 &self.slices[&slice] 684 } 685 } 686 687 impl Default for PictureCacheDebugInfo { 688 fn default() -> PictureCacheDebugInfo { 689 PictureCacheDebugInfo::new() 690 } 691 } 692 693 /// Debug information about a set of picture cache tiles, exposed via RenderResults 694 #[derive(Debug)] 695 #[cfg_attr(feature = "capture", derive(Serialize))] 696 #[cfg_attr(feature = "replay", derive(Deserialize))] 697 pub struct SliceDebugInfo { 698 pub tiles: FastHashMap<TileOffset, TileDebugInfo>, 699 } 700 701 impl SliceDebugInfo { 702 pub fn new() -> Self { 703 SliceDebugInfo { 704 tiles: FastHashMap::default(), 705 } 706 } 707 708 /// Convenience method to retrieve a given tile. Deliberately panics 709 /// if the tile isn't present. 710 pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo { 711 &self.tiles[&TileOffset::new(x, y)] 712 } 713 } 714 715 /// Debug information about a tile that was dirty and was rasterized 716 #[derive(Debug, PartialEq)] 717 #[cfg_attr(feature = "capture", derive(Serialize))] 718 #[cfg_attr(feature = "replay", derive(Deserialize))] 719 pub struct DirtyTileDebugInfo { 720 pub local_valid_rect: PictureRect, 721 pub local_dirty_rect: PictureRect, 722 } 723 724 /// Debug information about the state of a tile 725 #[derive(Debug, PartialEq)] 726 #[cfg_attr(feature = "capture", derive(Serialize))] 727 #[cfg_attr(feature = "replay", derive(Deserialize))] 728 pub enum TileDebugInfo { 729 /// Tile was occluded by a tile in front of it 730 Occluded, 731 /// Tile was culled (not visible in current display port) 732 Culled, 733 /// Tile was valid (no rasterization was done) and visible 734 Valid, 735 /// Tile was dirty, and was updated 736 Dirty(DirtyTileDebugInfo), 737 } 738 739 impl TileDebugInfo { 740 pub fn is_occluded(&self) -> bool { 741 match self { 742 TileDebugInfo::Occluded => true, 743 TileDebugInfo::Culled | 744 TileDebugInfo::Valid | 745 TileDebugInfo::Dirty(..) => false, 746 } 747 } 748 749 pub fn is_valid(&self) -> bool { 750 match self { 751 TileDebugInfo::Valid => true, 752 TileDebugInfo::Culled | 753 TileDebugInfo::Occluded | 754 TileDebugInfo::Dirty(..) => false, 755 } 756 } 757 758 pub fn is_culled(&self) -> bool { 759 match self { 760 TileDebugInfo::Culled => true, 761 TileDebugInfo::Valid | 762 TileDebugInfo::Occluded | 763 TileDebugInfo::Dirty(..) => false, 764 } 765 } 766 767 pub fn as_dirty(&self) -> &DirtyTileDebugInfo { 768 match self { 769 TileDebugInfo::Occluded | 770 TileDebugInfo::Culled | 771 TileDebugInfo::Valid => { 772 panic!("not a dirty tile!"); 773 } 774 TileDebugInfo::Dirty(ref info) => { 775 info 776 } 777 } 778 } 779 }