webrender/
tile_cache.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
5use api::{BorderRadius, ClipId, ClipMode, ColorF, DebugFlags, PrimitiveFlags, QualitySettings, RasterSpace};
6use api::units::*;
7use crate::clip::{ClipItemKeyKind, ClipNodeId, ClipTreeBuilder};
8use crate::frame_builder::FrameBuilderConfig;
9use crate::internal_types::FastHashMap;
10use crate::picture::{PrimitiveList, PictureCompositeMode, PicturePrimitive, SliceId};
11use crate::picture::{Picture3DContext, TileCacheParams, TileOffset, PictureFlags};
12use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex};
13use crate::scene_building::SliceFlags;
14use crate::scene_builder_thread::Interners;
15use crate::spatial_tree::{SpatialNodeIndex, SceneSpatialTree};
16use crate::util::VecHelper;
17use 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).
32const MAX_CACHE_SLICES: usize = 12;
33
34struct SliceDescriptor {
35    prim_list: PrimitiveList,
36    scroll_root: SpatialNodeIndex,
37}
38
39enum SliceKind {
40    Default {
41        secondary_slices: Vec<SliceDescriptor>,
42    },
43    Atomic {
44        prim_list: PrimitiveList,
45    },
46}
47
48impl SliceKind {
49    fn default() -> Self {
50        SliceKind::Default {
51            secondary_slices: Vec::new(),
52        }
53    }
54}
55
56struct 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
67impl 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.
114pub 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.
128pub 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
135impl 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
144impl 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
475fn 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.
493fn 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                        // TODO(gw): Enable this once also supported by native and swgl compositors
578                        false
579                    } else {
580                        false
581                    }
582                }
583                ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => {
584                    // We can apply multiple (via combining) axis-aligned rectangle
585                    // clips to the shared compositing clip.
586                    true
587                }
588            }
589        } else {
590            // Has a complex transform, we can't handle this as a shared clip
591            false
592        };
593
594        if node_valid {
595            // This node was found to be one we can apply during compositing.
596            if rounded_rect_count > 1 {
597                // However, we plan to only support one rounded-rect clip. If
598                // we have found > 1 rounded rect, drop children from the shared
599                // clip, and continue looking up the chain.
600                shared_clip_node_id = current_node_id;
601                rounded_rect_count = 1;
602            }
603        } else {
604            // Node was invalid, due to transform / clip type. Drop this clip
605            // and reset the rounded rect count to 0, since we drop children
606            // from here too.
607            shared_clip_node_id = node.parent;
608            rounded_rect_count = 0;
609        }
610
611        current_node_id = node.parent;
612    }
613
614    let shared_clip_leaf_id = Some(clip_tree_builder.build_for_tile_cache(
615        shared_clip_node_id,
616        &additional_clips,
617    ));
618
619    // Build a clip-chain for the tile cache, that contains any of the shared clips
620    // we will apply when drawing the tiles. In all cases provided by Gecko, these
621    // are rectangle clips with a scale/offset transform only, and get handled as
622    // a simple local clip rect in the vertex shader. However, this should in theory
623    // also work with any complex clips, such as rounded rects and image masks, by
624    // producing a clip mask that is applied to the picture cache tiles.
625
626    let slice = tile_cache_pictures.len();
627
628    let background_color = if slice == 0 {
629        background_color
630    } else {
631        None
632    };
633
634    let slice_id = SliceId::new(slice);
635
636    // Store some information about the picture cache slice. This is used when we swap the
637    // new scene into the frame builder to either reuse existing slices, or create new ones.
638    tile_caches.insert(slice_id, TileCacheParams {
639        debug_flags,
640        slice,
641        slice_flags,
642        spatial_node_index: scroll_root,
643        visibility_node_index: visibility_node,
644        background_color,
645        shared_clip_node_id,
646        shared_clip_leaf_id,
647        virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(),
648        image_surface_count: prim_list.image_surface_count,
649        yuv_image_surface_count: prim_list.yuv_image_surface_count,
650    });
651
652    let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image(
653        Some(PictureCompositeMode::TileCache { slice_id }),
654        Picture3DContext::Out,
655        PrimitiveFlags::IS_BACKFACE_VISIBLE,
656        prim_list,
657        scroll_root,
658        RasterSpace::Screen,
659        PictureFlags::empty(),
660        None,
661    ));
662
663    tile_cache_pictures.push(PictureIndex(pic_index));
664}
665
666/// Debug information about a set of picture cache slices, exposed via RenderResults
667#[derive(Debug)]
668#[cfg_attr(feature = "capture", derive(Serialize))]
669#[cfg_attr(feature = "replay", derive(Deserialize))]
670pub struct PictureCacheDebugInfo {
671    pub slices: FastHashMap<usize, SliceDebugInfo>,
672}
673
674impl PictureCacheDebugInfo {
675    pub fn new() -> Self {
676        PictureCacheDebugInfo {
677            slices: FastHashMap::default(),
678        }
679    }
680
681    /// Convenience method to retrieve a given slice. Deliberately panics
682    /// if the slice isn't present.
683    pub fn slice(&self, slice: usize) -> &SliceDebugInfo {
684        &self.slices[&slice]
685    }
686}
687
688impl Default for PictureCacheDebugInfo {
689    fn default() -> PictureCacheDebugInfo {
690        PictureCacheDebugInfo::new()
691    }
692}
693
694/// Debug information about a set of picture cache tiles, exposed via RenderResults
695#[derive(Debug)]
696#[cfg_attr(feature = "capture", derive(Serialize))]
697#[cfg_attr(feature = "replay", derive(Deserialize))]
698pub struct SliceDebugInfo {
699    pub tiles: FastHashMap<TileOffset, TileDebugInfo>,
700}
701
702impl SliceDebugInfo {
703    pub fn new() -> Self {
704        SliceDebugInfo {
705            tiles: FastHashMap::default(),
706        }
707    }
708
709    /// Convenience method to retrieve a given tile. Deliberately panics
710    /// if the tile isn't present.
711    pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo {
712        &self.tiles[&TileOffset::new(x, y)]
713    }
714}
715
716/// Debug information about a tile that was dirty and was rasterized
717#[derive(Debug, PartialEq)]
718#[cfg_attr(feature = "capture", derive(Serialize))]
719#[cfg_attr(feature = "replay", derive(Deserialize))]
720pub struct DirtyTileDebugInfo {
721    pub local_valid_rect: PictureRect,
722    pub local_dirty_rect: PictureRect,
723}
724
725/// Debug information about the state of a tile
726#[derive(Debug, PartialEq)]
727#[cfg_attr(feature = "capture", derive(Serialize))]
728#[cfg_attr(feature = "replay", derive(Deserialize))]
729pub enum TileDebugInfo {
730    /// Tile was occluded by a tile in front of it
731    Occluded,
732    /// Tile was culled (not visible in current display port)
733    Culled,
734    /// Tile was valid (no rasterization was done) and visible
735    Valid,
736    /// Tile was dirty, and was updated
737    Dirty(DirtyTileDebugInfo),
738}
739
740impl TileDebugInfo {
741    pub fn is_occluded(&self) -> bool {
742        match self {
743            TileDebugInfo::Occluded => true,
744            TileDebugInfo::Culled |
745            TileDebugInfo::Valid |
746            TileDebugInfo::Dirty(..) => false,
747        }
748    }
749
750    pub fn is_valid(&self) -> bool {
751        match self {
752            TileDebugInfo::Valid => true,
753            TileDebugInfo::Culled |
754            TileDebugInfo::Occluded |
755            TileDebugInfo::Dirty(..) => false,
756        }
757    }
758
759    pub fn is_culled(&self) -> bool {
760        match self {
761            TileDebugInfo::Culled => true,
762            TileDebugInfo::Valid |
763            TileDebugInfo::Occluded |
764            TileDebugInfo::Dirty(..) => false,
765        }
766    }
767
768    pub fn as_dirty(&self) -> &DirtyTileDebugInfo {
769        match self {
770            TileDebugInfo::Occluded |
771            TileDebugInfo::Culled |
772            TileDebugInfo::Valid => {
773                panic!("not a dirty tile!");
774            }
775            TileDebugInfo::Dirty(ref info) => {
776                info
777            }
778        }
779    }
780}