tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }