Skip to main content

webrender/invalidation/
quadtree.rs

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
11use api::units::*;
12use crate::debug_colors;
13use crate::internal_types::FastHashMap;
14use crate::prim_store::PrimitiveScratchBuffer;
15use crate::space::SpaceMapper;
16use crate::invalidation::{InvalidationReason, PrimitiveCompareResult};
17use crate::invalidation::cached_surface::{PrimitiveDescriptor, PrimitiveDependencyIndex};
18use crate::invalidation::compare::{PrimitiveComparer, PrimitiveComparisonKey};
19use crate::visibility::FrameVisibilityContext;
20use 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))]
27pub 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)]
50enum 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))]
59pub 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
66impl 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}