quadtree.rs (17877B)
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 //! Quadtree-based dirty region tracking for tiles 6 //! 7 //! This module implements a quadtree data structure that tracks which regions 8 //! of a tile have been invalidated. The quadtree can dynamically split and merge 9 //! nodes based on invalidation patterns to optimize tracking. 10 11 use api::units::*; 12 use crate::debug_colors; 13 use crate::internal_types::FastHashMap; 14 use crate::prim_store::PrimitiveScratchBuffer; 15 use crate::space::SpaceMapper; 16 use crate::invalidation::{InvalidationReason, PrimitiveCompareResult}; 17 use crate::invalidation::cached_surface::{PrimitiveDescriptor, PrimitiveDependencyIndex}; 18 use crate::invalidation::compare::{PrimitiveComparer, PrimitiveComparisonKey}; 19 use crate::visibility::FrameVisibilityContext; 20 use std::mem; 21 22 23 /// Details for a node in a quadtree that tracks dirty rects for a tile. 24 #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] 25 #[cfg_attr(feature = "capture", derive(Serialize))] 26 #[cfg_attr(feature = "replay", derive(Deserialize))] 27 pub enum TileNodeKind { 28 Leaf { 29 /// The index buffer of primitives that affected this tile previous frame 30 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] 31 prev_indices: Vec<PrimitiveDependencyIndex>, 32 /// The index buffer of primitives that affect this tile on this frame 33 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] 34 curr_indices: Vec<PrimitiveDependencyIndex>, 35 /// A bitset of which of the last 64 frames have been dirty for this leaf. 36 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] 37 dirty_tracker: u64, 38 /// The number of frames since this node split or merged. 39 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] 40 frames_since_modified: usize, 41 }, 42 Node { 43 /// The four children of this node 44 children: Vec<TileNode>, 45 }, 46 } 47 48 /// The kind of modification that a tile wants to do 49 #[derive(Copy, Clone, PartialEq, Debug)] 50 enum TileModification { 51 Split, 52 Merge, 53 } 54 55 /// A node in the dirty rect tracking quadtree. 56 #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] 57 #[cfg_attr(feature = "capture", derive(Serialize))] 58 #[cfg_attr(feature = "replay", derive(Deserialize))] 59 pub struct TileNode { 60 /// Leaf or internal node 61 pub kind: TileNodeKind, 62 /// Rect of this node in the same space as the tile cache picture 63 pub rect: PictureBox2D, 64 } 65 66 impl TileNode { 67 /// Construct a new leaf node, with the given primitive dependency index buffer 68 pub fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self { 69 TileNode { 70 kind: TileNodeKind::Leaf { 71 prev_indices: Vec::new(), 72 curr_indices, 73 dirty_tracker: 0, 74 frames_since_modified: 0, 75 }, 76 rect: PictureBox2D::zero(), 77 } 78 } 79 80 /// Draw debug information about this tile node 81 pub fn draw_debug_rects( 82 &self, 83 pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>, 84 is_opaque: bool, 85 local_valid_rect: PictureRect, 86 scratch: &mut PrimitiveScratchBuffer, 87 global_device_pixel_scale: DevicePixelScale, 88 ) { 89 match self.kind { 90 TileNodeKind::Leaf { dirty_tracker, .. } => { 91 let color = if (dirty_tracker & 1) != 0 { 92 debug_colors::RED 93 } else if is_opaque { 94 debug_colors::GREEN 95 } else { 96 debug_colors::YELLOW 97 }; 98 99 if let Some(local_rect) = local_valid_rect.intersection(&self.rect) { 100 let world_rect = pic_to_world_mapper 101 .map(&local_rect) 102 .unwrap(); 103 let device_rect = world_rect * global_device_pixel_scale; 104 105 let outer_color = color.scale_alpha(0.3); 106 let inner_color = outer_color.scale_alpha(0.5); 107 scratch.push_debug_rect( 108 device_rect.inflate(-3.0, -3.0), 109 1, 110 outer_color, 111 inner_color 112 ); 113 } 114 } 115 TileNodeKind::Node { ref children, .. } => { 116 for child in children.iter() { 117 child.draw_debug_rects( 118 pic_to_world_mapper, 119 is_opaque, 120 local_valid_rect, 121 scratch, 122 global_device_pixel_scale, 123 ); 124 } 125 } 126 } 127 } 128 129 /// Calculate the four child rects for a given node 130 fn get_child_rects( 131 rect: &PictureBox2D, 132 result: &mut [PictureBox2D; 4], 133 ) { 134 let p0 = rect.min; 135 let p1 = rect.max; 136 let pc = p0 + rect.size() * 0.5; 137 138 *result = [ 139 PictureBox2D::new( 140 p0, 141 pc, 142 ), 143 PictureBox2D::new( 144 PicturePoint::new(pc.x, p0.y), 145 PicturePoint::new(p1.x, pc.y), 146 ), 147 PictureBox2D::new( 148 PicturePoint::new(p0.x, pc.y), 149 PicturePoint::new(pc.x, p1.y), 150 ), 151 PictureBox2D::new( 152 pc, 153 p1, 154 ), 155 ]; 156 } 157 158 /// Called during pre_update, to clear the current dependencies 159 pub fn clear( 160 &mut self, 161 rect: PictureBox2D, 162 ) { 163 self.rect = rect; 164 165 match self.kind { 166 TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => { 167 // Swap current dependencies to be the previous frame 168 mem::swap(prev_indices, curr_indices); 169 curr_indices.clear(); 170 // Note that another frame has passed in the dirty bit trackers 171 *dirty_tracker = *dirty_tracker << 1; 172 *frames_since_modified += 1; 173 } 174 TileNodeKind::Node { ref mut children, .. } => { 175 let mut child_rects = [PictureBox2D::zero(); 4]; 176 TileNode::get_child_rects(&rect, &mut child_rects); 177 assert_eq!(child_rects.len(), children.len()); 178 179 for (child, rect) in children.iter_mut().zip(child_rects.iter()) { 180 child.clear(*rect); 181 } 182 } 183 } 184 } 185 186 /// Add a primitive dependency to this node 187 pub fn add_prim( 188 &mut self, 189 index: PrimitiveDependencyIndex, 190 prim_rect: &PictureBox2D, 191 ) { 192 match self.kind { 193 TileNodeKind::Leaf { ref mut curr_indices, .. } => { 194 curr_indices.push(index); 195 } 196 TileNodeKind::Node { ref mut children, .. } => { 197 for child in children.iter_mut() { 198 if child.rect.intersects(prim_rect) { 199 child.add_prim(index, prim_rect); 200 } 201 } 202 } 203 } 204 } 205 206 /// Apply a merge or split operation to this tile, if desired 207 pub fn maybe_merge_or_split( 208 &mut self, 209 level: i32, 210 curr_prims: &[PrimitiveDescriptor], 211 max_split_levels: i32, 212 ) { 213 // Determine if this tile wants to split or merge 214 let mut tile_mod = None; 215 216 fn get_dirty_frames( 217 dirty_tracker: u64, 218 frames_since_modified: usize, 219 ) -> Option<u32> { 220 // Only consider splitting or merging at least 64 frames since we last changed 221 if frames_since_modified > 64 { 222 // Each bit in the tracker is a frame that was recently invalidated 223 Some(dirty_tracker.count_ones()) 224 } else { 225 None 226 } 227 } 228 229 match self.kind { 230 TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => { 231 // Only consider splitting if the tree isn't too deep. 232 if level < max_split_levels { 233 if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) { 234 // If the tile has invalidated > 50% of the recent number of frames, split. 235 if dirty_frames > 32 { 236 tile_mod = Some(TileModification::Split); 237 } 238 } 239 } 240 } 241 TileNodeKind::Node { ref children, .. } => { 242 // There's two conditions that cause a node to merge its children: 243 // (1) If _all_ the child nodes are constantly invalidating, then we are wasting 244 // CPU time tracking dependencies for each child, so merge them. 245 // (2) If _none_ of the child nodes are recently invalid, then the page content 246 // has probably changed, and we no longer need to track fine grained dependencies here. 247 248 let mut static_count = 0; 249 let mut changing_count = 0; 250 251 for child in children { 252 // Only consider merging nodes at the edge of the tree. 253 if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind { 254 if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) { 255 if dirty_frames == 0 { 256 // Hasn't been invalidated for some time 257 static_count += 1; 258 } else if dirty_frames == 64 { 259 // Is constantly being invalidated 260 changing_count += 1; 261 } 262 } 263 } 264 265 // Only merge if all the child tiles are in agreement. Otherwise, we have some 266 // that are invalidating / static, and it's worthwhile tracking dependencies for 267 // them individually. 268 if static_count == 4 || changing_count == 4 { 269 tile_mod = Some(TileModification::Merge); 270 } 271 } 272 } 273 } 274 275 match tile_mod { 276 Some(TileModification::Split) => { 277 // To split a node, take the current dependency index buffer for this node, and 278 // split it into child index buffers. 279 let curr_indices = match self.kind { 280 TileNodeKind::Node { .. } => { 281 unreachable!("bug - only leaves can split"); 282 } 283 TileNodeKind::Leaf { ref mut curr_indices, .. } => { 284 mem::take(curr_indices) 285 } 286 }; 287 288 let mut child_rects = [PictureBox2D::zero(); 4]; 289 TileNode::get_child_rects(&self.rect, &mut child_rects); 290 291 let mut child_indices = [ 292 Vec::new(), 293 Vec::new(), 294 Vec::new(), 295 Vec::new(), 296 ]; 297 298 // Step through the index buffer, and add primitives to each of the children 299 // that they intersect. 300 for index in curr_indices { 301 let prim = &curr_prims[index.0 as usize]; 302 for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) { 303 if prim.prim_clip_box.intersects(child_rect) { 304 indices.push(index); 305 } 306 } 307 } 308 309 // Create the child nodes and switch from leaf -> node. 310 let children = child_indices 311 .iter_mut() 312 .map(|i| TileNode::new_leaf(mem::replace(i, Vec::new()))) 313 .collect(); 314 315 self.kind = TileNodeKind::Node { 316 children, 317 }; 318 } 319 Some(TileModification::Merge) => { 320 // Construct a merged index buffer by collecting the dependency index buffers 321 // from each child, and merging them into a de-duplicated index buffer. 322 let merged_indices = match self.kind { 323 TileNodeKind::Node { ref mut children, .. } => { 324 let mut merged_indices = Vec::new(); 325 326 for child in children.iter() { 327 let child_indices = match child.kind { 328 TileNodeKind::Leaf { ref curr_indices, .. } => { 329 curr_indices 330 } 331 TileNodeKind::Node { .. } => { 332 unreachable!("bug: child is not a leaf"); 333 } 334 }; 335 merged_indices.extend_from_slice(child_indices); 336 } 337 338 merged_indices.sort(); 339 merged_indices.dedup(); 340 341 merged_indices 342 } 343 TileNodeKind::Leaf { .. } => { 344 unreachable!("bug - trying to merge a leaf"); 345 } 346 }; 347 348 // Switch from a node to a leaf, with the combined index buffer 349 self.kind = TileNodeKind::Leaf { 350 prev_indices: Vec::new(), 351 curr_indices: merged_indices, 352 dirty_tracker: 0, 353 frames_since_modified: 0, 354 }; 355 } 356 None => { 357 // If this node didn't merge / split, then recurse into children 358 // to see if they want to split / merge. 359 if let TileNodeKind::Node { ref mut children, .. } = self.kind { 360 for child in children.iter_mut() { 361 child.maybe_merge_or_split( 362 level+1, 363 curr_prims, 364 max_split_levels, 365 ); 366 } 367 } 368 } 369 } 370 } 371 372 /// Update the dirty state of this node, building the overall dirty rect 373 pub fn update_dirty_rects( 374 &mut self, 375 prev_prims: &[PrimitiveDescriptor], 376 curr_prims: &[PrimitiveDescriptor], 377 prim_comparer: &mut PrimitiveComparer, 378 dirty_rect: &mut PictureBox2D, 379 compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>, 380 invalidation_reason: &mut Option<InvalidationReason>, 381 frame_context: &FrameVisibilityContext, 382 ) { 383 match self.kind { 384 TileNodeKind::Node { ref mut children, .. } => { 385 for child in children.iter_mut() { 386 child.update_dirty_rects( 387 prev_prims, 388 curr_prims, 389 prim_comparer, 390 dirty_rect, 391 compare_cache, 392 invalidation_reason, 393 frame_context, 394 ); 395 } 396 } 397 TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => { 398 // If the index buffers are of different length, they must be different 399 if prev_indices.len() == curr_indices.len() { 400 // Walk each index buffer, comparing primitives 401 for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) { 402 let i0 = prev_index.0 as usize; 403 let i1 = curr_index.0 as usize; 404 405 // Compare the primitives, caching the result in a hash map 406 // to save comparisons in other tree nodes. 407 let key = PrimitiveComparisonKey { 408 prev_index: *prev_index, 409 curr_index: *curr_index, 410 }; 411 412 let prim_compare_result = *compare_cache 413 .entry(key) 414 .or_insert_with(|| { 415 let prev = &prev_prims[i0]; 416 let curr = &curr_prims[i1]; 417 prim_comparer.compare_prim(prev, curr) 418 }); 419 420 // If not the same, mark this node as dirty and update the dirty rect 421 if prim_compare_result != PrimitiveCompareResult::Equal { 422 if invalidation_reason.is_none() { 423 *invalidation_reason = Some(InvalidationReason::Content); 424 } 425 *dirty_rect = self.rect.union(dirty_rect); 426 *dirty_tracker = *dirty_tracker | 1; 427 break; 428 } 429 } 430 } else { 431 if invalidation_reason.is_none() { 432 *invalidation_reason = Some(InvalidationReason::PrimCount); 433 } 434 *dirty_rect = self.rect.union(dirty_rect); 435 *dirty_tracker = *dirty_tracker | 1; 436 } 437 } 438 } 439 } 440 }