/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Quadtree-based dirty region tracking for tiles //! //! This module implements a quadtree data structure that tracks which regions //! of a tile have been invalidated. The quadtree can dynamically split and merge //! nodes based on invalidation patterns to optimize tracking.
use api::units::*; usecrate::debug_colors; usecrate::internal_types::FastHashMap; usecrate::prim_store::PrimitiveScratchBuffer; usecrate::space::SpaceMapper; usecrate::invalidation::{InvalidationReason, PrimitiveCompareResult}; usecrate::invalidation::cached_surface::{PrimitiveDescriptor, PrimitiveDependencyIndex}; usecrate::invalidation::compare::{PrimitiveComparer, PrimitiveComparisonKey}; usecrate::visibility::FrameVisibilityContext; use std::mem;
/// Details for a node in a quadtree that tracks dirty rects for a tile. #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubenum TileNodeKind {
Leaf { /// The index buffer of primitives that affected this tile previous frame #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
prev_indices: Vec<PrimitiveDependencyIndex>, /// The index buffer of primitives that affect this tile on this frame #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
curr_indices: Vec<PrimitiveDependencyIndex>, /// A bitset of which of the last 64 frames have been dirty for this leaf. #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
dirty_tracker: u64, /// The number of frames since this node split or merged. #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
frames_since_modified: usize,
},
Node { /// The four children of this node
children: Vec<TileNode>,
},
}
/// The kind of modification that a tile wants to do #[derive(Copy, Clone, PartialEq, Debug)] enum TileModification {
Split,
Merge,
}
/// A node in the dirty rect tracking quadtree. #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct TileNode { /// Leaf or internal node pub kind: TileNodeKind, /// Rect of this node in the same space as the tile cache picture pub rect: PictureBox2D,
}
impl TileNode { /// Construct a new leaf node, with the given primitive dependency index buffer pubfn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self {
TileNode {
kind: TileNodeKind::Leaf {
prev_indices: Vec::new(),
curr_indices,
dirty_tracker: 0,
frames_since_modified: 0,
},
rect: PictureBox2D::zero(),
}
}
/// Draw debug information about this tile node pubfn draw_debug_rects(
&self,
pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
is_opaque: bool,
local_valid_rect: PictureRect,
scratch: &mut PrimitiveScratchBuffer,
global_device_pixel_scale: DevicePixelScale,
) { matchself.kind {
TileNodeKind::Leaf { dirty_tracker, .. } => { let color = if (dirty_tracker & 1) != 0 {
debug_colors::RED
} elseif is_opaque {
debug_colors::GREEN
} else {
debug_colors::YELLOW
};
iflet Some(local_rect) = local_valid_rect.intersection(&self.rect) { let world_rect = pic_to_world_mapper
.map(&local_rect)
.unwrap(); let device_rect = world_rect * global_device_pixel_scale;
/// Calculate the four child rects for a given node fn get_child_rects(
rect: &PictureBox2D,
result: &mut [PictureBox2D; 4],
) { let p0 = rect.min; let p1 = rect.max; let pc = p0 + rect.size() * 0.5;
/// Called during pre_update, to clear the current dependencies pubfn clear(
&mutself,
rect: PictureBox2D,
) { self.rect = rect;
matchself.kind {
TileNodeKind::Leaf { refmut prev_indices, refmut curr_indices, refmut dirty_tracker, refmut frames_since_modified } => { // Swap current dependencies to be the previous frame
mem::swap(prev_indices, curr_indices);
curr_indices.clear(); // Note that another frame has passed in the dirty bit trackers
*dirty_tracker = *dirty_tracker << 1;
*frames_since_modified += 1;
}
TileNodeKind::Node { refmut children, .. } => { letmut child_rects = [PictureBox2D::zero(); 4];
TileNode::get_child_rects(&rect, &mut child_rects);
assert_eq!(child_rects.len(), children.len());
for (child, rect) in children.iter_mut().zip(child_rects.iter()) {
child.clear(*rect);
}
}
}
}
/// Add a primitive dependency to this node pubfn add_prim(
&mutself,
index: PrimitiveDependencyIndex,
prim_rect: &PictureBox2D,
) { matchself.kind {
TileNodeKind::Leaf { refmut curr_indices, .. } => {
curr_indices.push(index);
}
TileNodeKind::Node { refmut children, .. } => { for child in children.iter_mut() { if child.rect.intersects(prim_rect) {
child.add_prim(index, prim_rect);
}
}
}
}
}
/// Apply a merge or split operation to this tile, if desired pubfn maybe_merge_or_split(
&mutself,
level: i32,
curr_prims: &[PrimitiveDescriptor],
max_split_levels: i32,
) { // Determine if this tile wants to split or merge letmut tile_mod = None;
fn get_dirty_frames(
dirty_tracker: u64,
frames_since_modified: usize,
) -> Option<u32> { // Only consider splitting or merging at least 64 frames since we last changed if frames_since_modified > 64 { // Each bit in the tracker is a frame that was recently invalidated
Some(dirty_tracker.count_ones())
} else {
None
}
}
matchself.kind {
TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => { // Only consider splitting if the tree isn't too deep. if level < max_split_levels { iflet Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) { // If the tile has invalidated > 50% of the recent number of frames, split. if dirty_frames > 32 {
tile_mod = Some(TileModification::Split);
}
}
}
}
TileNodeKind::Node { ref children, .. } => { // There's two conditions that cause a node to merge its children: // (1) If _all_ the child nodes are constantly invalidating, then we are wasting // CPU time tracking dependencies for each child, so merge them. // (2) If _none_ of the child nodes are recently invalid, then the page content // has probably changed, and we no longer need to track fine grained dependencies here.
for child in children { // Only consider merging nodes at the edge of the tree. iflet TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind { iflet Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) { if dirty_frames == 0 { // Hasn't been invalidated for some time
static_count += 1;
} elseif dirty_frames == 64 { // Is constantly being invalidated
changing_count += 1;
}
}
}
// Only merge if all the child tiles are in agreement. Otherwise, we have some // that are invalidating / static, and it's worthwhile tracking dependencies for // them individually. if static_count == 4 || changing_count == 4 {
tile_mod = Some(TileModification::Merge);
}
}
}
}
match tile_mod {
Some(TileModification::Split) => { // To split a node, take the current dependency index buffer for this node, and // split it into child index buffers. let curr_indices = matchself.kind {
TileNodeKind::Node { .. } => {
unreachable!("bug - only leaves can split");
}
TileNodeKind::Leaf { refmut curr_indices, .. } => {
mem::take(curr_indices)
}
};
// Step through the index buffer, and add primitives to each of the children // that they intersect. for index in curr_indices { let prim = &curr_prims[index.0as usize]; for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) { if prim.prim_clip_box.intersects(child_rect) {
indices.push(index);
}
}
}
// Create the child nodes and switch from leaf -> node. let children = child_indices
.iter_mut()
.map(|i| TileNode::new_leaf(mem::replace(i, Vec::new())))
.collect();
self.kind = TileNodeKind::Node {
children,
};
}
Some(TileModification::Merge) => { // Construct a merged index buffer by collecting the dependency index buffers // from each child, and merging them into a de-duplicated index buffer. let merged_indices = matchself.kind {
TileNodeKind::Node { refmut children, .. } => { letmut merged_indices = Vec::new();
for child in children.iter() { let child_indices = match child.kind {
TileNodeKind::Leaf { ref curr_indices, .. } => {
curr_indices
}
TileNodeKind::Node { .. } => {
unreachable!("bug: child is not a leaf");
}
};
merged_indices.extend_from_slice(child_indices);
}
merged_indices.sort();
merged_indices.dedup();
merged_indices
}
TileNodeKind::Leaf { .. } => {
unreachable!("bug - trying to merge a leaf");
}
};
// Switch from a node to a leaf, with the combined index buffer self.kind = TileNodeKind::Leaf {
prev_indices: Vec::new(),
curr_indices: merged_indices,
dirty_tracker: 0,
frames_since_modified: 0,
};
}
None => { // If this node didn't merge / split, then recurse into children // to see if they want to split / merge. iflet TileNodeKind::Node { refmut children, .. } = self.kind { for child in children.iter_mut() {
child.maybe_merge_or_split(
level+1,
curr_prims,
max_split_levels,
);
}
}
}
}
}
/// Update the dirty state of this node, building the overall dirty rect pubfn update_dirty_rects(
&mutself,
prev_prims: &[PrimitiveDescriptor],
curr_prims: &[PrimitiveDescriptor],
prim_comparer: &mut PrimitiveComparer,
dirty_rect: &mut PictureBox2D,
compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
invalidation_reason: &mut Option<InvalidationReason>,
frame_context: &FrameVisibilityContext,
) { matchself.kind {
TileNodeKind::Node { refmut children, .. } => { for child in children.iter_mut() {
child.update_dirty_rects(
prev_prims,
curr_prims,
prim_comparer,
dirty_rect,
compare_cache,
invalidation_reason,
frame_context,
);
}
}
TileNodeKind::Leaf { ref prev_indices, ref curr_indices, refmut dirty_tracker, .. } => { // If the index buffers are of different length, they must be different if prev_indices.len() == curr_indices.len() { // Walk each index buffer, comparing primitives for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) { let i0 = prev_index.0as usize; let i1 = curr_index.0as usize;
// Compare the primitives, caching the result in a hash map // to save comparisons in other tree nodes. let key = PrimitiveComparisonKey {
prev_index: *prev_index,
curr_index: *curr_index,
};
let prim_compare_result = *compare_cache
.entry(key)
.or_insert_with(|| { let prev = &prev_prims[i0]; let curr = &curr_prims[i1];
prim_comparer.compare_prim(prev, curr)
});
// If not the same, mark this node as dirty and update the dirty rect if prim_compare_result != PrimitiveCompareResult::Equal { if invalidation_reason.is_none() {
*invalidation_reason = Some(InvalidationReason::Content);
}
*dirty_rect = self.rect.union(dirty_rect);
*dirty_tracker = *dirty_tracker | 1; break;
}
}
} else { if invalidation_reason.is_none() {
*invalidation_reason = Some(InvalidationReason::PrimCount);
}
*dirty_rect = self.rect.union(dirty_rect);
*dirty_tracker = *dirty_tracker | 1;
}
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.