azul_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::{ColorF, PrimitiveFlags, QualitySettings};
6use api::units::*;
7use crate::clip::{ClipChainId, ClipNodeKind, ClipStore, ClipInstance};
8use crate::frame_builder::FrameBuilderConfig;
9use crate::internal_types::{FastHashMap, FastHashSet};
10use crate::picture::{PrimitiveList, PictureCompositeMode, PictureOptions, PicturePrimitive, SliceId};
11use crate::picture::{Picture3DContext, TileCacheParams, TileOffset};
12use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex};
13use crate::scene_building::SliceFlags;
14use crate::scene_builder_thread::Interners;
15use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex, SpatialTree};
16use crate::util::VecHelper;
17
18/*
19 Types and functionality related to picture caching. In future, we'll
20 move more and more of the existing functionality out of picture.rs
21 and into here.
22 */
23
24// If the page would create too many slices (an arbitrary definition where
25// it's assumed the GPU memory + compositing overhead would be too high)
26// then create a single picture cache for the remaining content. This at
27// least means that we can cache small content changes efficiently when
28// scrolling isn't occurring. Scrolling regions will be handled reasonably
29// efficiently by the dirty rect tracking (since it's likely that if the
30// page has so many slices there isn't a single major scroll region).
31const MAX_CACHE_SLICES: usize = 12;
32
33/// Created during scene building, describes how to create a tile cache for a given slice.
34pub struct PendingTileCache {
35    /// List of primitives that are part of this slice
36    pub prim_list: PrimitiveList,
37    /// Parameters that define the tile cache (such as background color, shared clips, reference spatial node)
38    pub params: TileCacheParams,
39    /// An additional clip chain that get applied to the shared clips unconditionally for this tile cache
40    pub iframe_clip: Option<ClipChainId>,
41}
42
43/// Used during scene building to construct the list of pending tile caches.
44pub struct TileCacheBuilder {
45    /// When Some(..), a new tile cache will be created for the next primitive.
46    force_new_tile_cache: Option<SliceFlags>,
47    /// List of tile caches that have been created so far (last in the list is currently active).
48    pending_tile_caches: Vec<PendingTileCache>,
49
50    /// Cache the previous scroll root search for a spatial node, since they are often the same.
51    prev_scroll_root_cache: (SpatialNodeIndex, SpatialNodeIndex),
52    /// A buffer for collecting clips for a clip-chain. Retained here to avoid memory allocations in add_prim.
53    prim_clips_buffer: Vec<ClipInstance>,
54    /// Cache the last clip-chain that was added to the shared clips as it's often the same between prims.
55    last_checked_clip_chain: ClipChainId,
56}
57
58/// The output of a tile cache builder, containing all details needed to construct the
59/// tile cache(s) for the next scene, and retain tiles from the previous frame when sent
60/// send to the frame builder.
61pub struct TileCacheConfig {
62    /// Mapping of slice id to the parameters needed to construct this tile cache.
63    pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
64    /// A set of any spatial nodes that are attached to either a picture cache
65    /// root, or a clip node on the picture cache primitive. These are used
66    /// to detect cases where picture caching must be disabled. This is mostly
67    /// a temporary workaround for some existing wrench tests. I don't think
68    /// Gecko ever produces picture cache slices with complex transforms, so
69    /// in future we should prevent this in the public API and remove this hack.
70    pub picture_cache_spatial_nodes: FastHashSet<SpatialNodeIndex>,
71    /// Number of picture cache slices that were created (for profiler)
72    pub picture_cache_slice_count: usize,
73}
74
75impl TileCacheConfig {
76    pub fn new(picture_cache_slice_count: usize) -> Self {
77        TileCacheConfig {
78            tile_caches: FastHashMap::default(),
79            picture_cache_spatial_nodes: FastHashSet::default(),
80            picture_cache_slice_count,
81        }
82    }
83}
84
85impl TileCacheBuilder {
86    /// Construct a new tile cache builder.
87    pub fn new() -> Self {
88        TileCacheBuilder {
89            force_new_tile_cache: None,
90            pending_tile_caches: Vec::new(),
91            prev_scroll_root_cache: (ROOT_SPATIAL_NODE_INDEX, ROOT_SPATIAL_NODE_INDEX),
92            prim_clips_buffer: Vec::new(),
93            last_checked_clip_chain: ClipChainId::INVALID,
94        }
95    }
96
97    /// Set a barrier that forces a new tile cache next time a prim is added.
98    pub fn add_tile_cache_barrier(
99        &mut self,
100        slice_flags: SliceFlags,
101    ) {
102        self.force_new_tile_cache = Some(slice_flags);
103    }
104
105    /// Returns true if it's OK to add a container tile cache (will return false
106    /// if too many slices have been created).
107    pub fn can_add_container_tile_cache(&self) -> bool {
108        // See the logic and comments around MAX_CACHE_SLICES in add_prim
109        // to explain why < MAX_CACHE_SLICES-1 is used.
110        self.pending_tile_caches.len() < MAX_CACHE_SLICES-1
111    }
112
113    /// Create a new tile cache for an existing prim_list
114    pub fn add_tile_cache(
115        &mut self,
116        prim_list: PrimitiveList,
117        clip_chain_id: ClipChainId,
118        spatial_tree: &SpatialTree,
119        clip_store: &ClipStore,
120        interners: &Interners,
121        config: &FrameBuilderConfig,
122        iframe_clip: Option<ClipChainId>,
123        slice_flags: SliceFlags,
124    ) {
125        assert!(self.can_add_container_tile_cache());
126
127        if prim_list.is_empty() {
128            return;
129        }
130
131        // Iterate the clusters and determine which is the most commonly occurring
132        // scroll root. This is a reasonable heuristic to decide which spatial node
133        // should be considered the scroll root of this tile cache, in order to
134        // minimize the invalidations that occur due to scrolling. It's often the
135        // case that a blend container will have only a single scroll root.
136        let mut scroll_root_occurrences = FastHashMap::default();
137
138        for cluster in &prim_list.clusters {
139            let scroll_root = self.find_scroll_root(
140                cluster.spatial_node_index,
141                spatial_tree,
142            );
143
144            *scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1;
145        }
146
147        // We can't just select the most commonly occurring scroll root in this
148        // primitive list. If that is a nested scroll root, there may be
149        // primitives in the list that are outside that scroll root, which
150        // can cause panics when calculating relative transforms. To ensure
151        // this doesn't happen, only retain scroll root candidates that are
152        // also ancestors of every other scroll root candidate.
153        let scroll_roots: Vec<SpatialNodeIndex> = scroll_root_occurrences
154            .keys()
155            .cloned()
156            .collect();
157
158        scroll_root_occurrences.retain(|parent_spatial_node_index, _| {
159            scroll_roots.iter().all(|child_spatial_node_index| {
160                parent_spatial_node_index == child_spatial_node_index ||
161                spatial_tree.is_ancestor(
162                    *parent_spatial_node_index,
163                    *child_spatial_node_index,
164                )
165            })
166        });
167
168        // Select the scroll root by finding the most commonly occurring one
169        let scroll_root = scroll_root_occurrences
170            .iter()
171            .max_by_key(|entry | entry.1)
172            .map(|(spatial_node_index, _)| *spatial_node_index)
173            .unwrap_or(ROOT_SPATIAL_NODE_INDEX);
174
175        let mut first = true;
176        let prim_clips_buffer = &mut self.prim_clips_buffer;
177        let mut shared_clips = Vec::new();
178
179        // Work out which clips are shared by all prim instances and can thus be applied
180        // at the tile cache level. In future, we aim to remove this limitation by knowing
181        // during initial scene build which are the relevant compositor clips, but for now
182        // this is unlikely to be a significant cost.
183        for cluster in &prim_list.clusters {
184            for prim_instance in &prim_list.prim_instances[cluster.prim_range()] {
185                if first {
186                    add_clips(
187                        scroll_root,
188                        prim_instance.clip_set.clip_chain_id,
189                        &mut shared_clips,
190                        clip_store,
191                        interners,
192                        spatial_tree,
193                    );
194
195                    self.last_checked_clip_chain = prim_instance.clip_set.clip_chain_id;
196                    first = false;
197                } else {
198                    if self.last_checked_clip_chain != prim_instance.clip_set.clip_chain_id {
199                        prim_clips_buffer.clear();
200
201                        add_clips(
202                            scroll_root,
203                            prim_instance.clip_set.clip_chain_id,
204                            prim_clips_buffer,
205                            clip_store,
206                            interners,
207                            spatial_tree,
208                        );
209
210                        shared_clips.retain(|h1: &ClipInstance| {
211                            let uid = h1.handle.uid();
212                            prim_clips_buffer.iter().any(|h2| {
213                                uid == h2.handle.uid() &&
214                                h1.spatial_node_index == h2.spatial_node_index
215                            })
216                        });
217
218                        self.last_checked_clip_chain = prim_instance.clip_set.clip_chain_id;
219                    }
220                }
221            }
222        }
223
224        // If a blend-container has any clips on the stacking context we are removing,
225        // we need to ensure those clips are added to the shared clips applied to the
226        // tile cache we are creating.
227        let mut current_clip_chain_id = clip_chain_id;
228        while current_clip_chain_id != ClipChainId::NONE {
229            let clip_chain_node = &clip_store
230                .clip_chain_nodes[current_clip_chain_id.0 as usize];
231
232            let clip_node_data = &interners.clip[clip_chain_node.handle];
233            if let ClipNodeKind::Rectangle = clip_node_data.clip_node_kind {
234                shared_clips.push(ClipInstance::new(clip_chain_node.handle, clip_chain_node.spatial_node_index));
235            }
236
237            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
238        }
239
240        // Construct the new tile cache and add to the list to be built
241        let slice = self.pending_tile_caches.len();
242
243        let params = TileCacheParams {
244            slice,
245            slice_flags,
246            spatial_node_index: scroll_root,
247            background_color: None,
248            shared_clips,
249            shared_clip_chain: ClipChainId::NONE,
250            virtual_surface_size: config.compositor_kind.get_virtual_surface_size(),
251            compositor_surface_count: prim_list.compositor_surface_count,
252        };
253
254        self.pending_tile_caches.push(PendingTileCache {
255            prim_list,
256            params,
257            iframe_clip,
258        });
259
260        // Add a tile cache barrier so that the next prim definitely gets added to a
261        // new tile cache, even if it's otherwise compatible with the blend container.
262        self.force_new_tile_cache = Some(SliceFlags::empty());
263    }
264
265    /// Add a primitive, either to the current tile cache, or a new one, depending on various conditions.
266    pub fn add_prim(
267        &mut self,
268        prim_instance: PrimitiveInstance,
269        prim_rect: LayoutRect,
270        spatial_node_index: SpatialNodeIndex,
271        prim_flags: PrimitiveFlags,
272        spatial_tree: &SpatialTree,
273        clip_store: &ClipStore,
274        interners: &Interners,
275        config: &FrameBuilderConfig,
276        quality_settings: &QualitySettings,
277        iframe_clip: Option<ClipChainId>,
278    ) {
279        // Check if we want to create a new slice based on the current / next scroll root
280        let scroll_root = self.find_scroll_root(spatial_node_index, spatial_tree);
281
282        // Also create a new slice if there was a barrier previously set
283        let mut want_new_tile_cache =
284            self.force_new_tile_cache.is_some() ||
285            self.pending_tile_caches.is_empty();
286
287        let current_scroll_root = self.pending_tile_caches
288            .last()
289            .map(|p| p.params.spatial_node_index);
290
291        if let Some(current_scroll_root) = current_scroll_root {
292            want_new_tile_cache |= match (current_scroll_root, scroll_root) {
293                (ROOT_SPATIAL_NODE_INDEX, ROOT_SPATIAL_NODE_INDEX) => {
294                    // Both current slice and this cluster are fixed position, no need to cut
295                    false
296                }
297                (ROOT_SPATIAL_NODE_INDEX, _) => {
298                    // A real scroll root is being established, so create a cache slice
299                    true
300                }
301                (_, ROOT_SPATIAL_NODE_INDEX) => {
302                    // If quality settings force subpixel AA over performance, skip creating
303                    // a slice for the fixed position element(s) here.
304                    if quality_settings.force_subpixel_aa_where_possible {
305                        false
306                    } else {
307                        // A fixed position slice is encountered within a scroll root. Only create
308                        // a slice in this case if all the clips referenced by this cluster are also
309                        // fixed position. There's no real point in creating slices for these cases,
310                        // since we'll have to rasterize them as the scrolling clip moves anyway. It
311                        // also allows us to retain subpixel AA in these cases. For these types of
312                        // slices, the intra-slice dirty rect handling typically works quite well
313                        // (a common case is parallax scrolling effects).
314                        let mut create_slice = true;
315                        let mut current_clip_chain_id = prim_instance.clip_set.clip_chain_id;
316
317                        while current_clip_chain_id != ClipChainId::NONE {
318                            let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
319                            let spatial_root = self.find_scroll_root(clip_chain_node.spatial_node_index, spatial_tree);
320                            if spatial_root != ROOT_SPATIAL_NODE_INDEX {
321                                create_slice = false;
322                                break;
323                            }
324                            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
325                        }
326
327                        create_slice
328                    }
329                }
330                (curr_scroll_root, scroll_root) => {
331                    // Two scrolling roots - only need a new slice if they differ
332                    curr_scroll_root != scroll_root
333                }
334            };
335
336            // Update the list of clips that apply to this primitive instance, to track which are the
337            // shared clips for this tile cache that can be applied during compositing.
338            if self.last_checked_clip_chain != prim_instance.clip_set.clip_chain_id {
339                let prim_clips_buffer = &mut self.prim_clips_buffer;
340                prim_clips_buffer.clear();
341                add_clips(
342                    current_scroll_root,
343                    prim_instance.clip_set.clip_chain_id,
344                    prim_clips_buffer,
345                    clip_store,
346                    interners,
347                    spatial_tree,
348                );
349
350                let current_shared_clips = &self.pending_tile_caches
351                    .last()
352                    .unwrap()
353                    .params
354                    .shared_clips;
355
356                // If the shared clips are not compatible, create a new slice.
357                // TODO(gw): Does Gecko ever supply duplicate or out-of-order
358                //           shared clips? It doesn't seem to, but if it does,
359                //           we will need to be more clever here to check if
360                //           the shared clips are compatible.
361                want_new_tile_cache |= current_shared_clips != prim_clips_buffer;
362
363                self.last_checked_clip_chain = prim_instance.clip_set.clip_chain_id;
364            }
365        }
366
367        if want_new_tile_cache {
368            let slice = self.pending_tile_caches.len();
369
370            // If we have exceeded the maximum number of slices, skip creating a new
371            // one and the primitive will be added to the last slice.
372            if slice < MAX_CACHE_SLICES {
373                // When we reach the last valid slice that can be created, it is created as
374                // a fixed slice without shared clips, ensuring that we can safely add any
375                // subsequent primitives to it. This doesn't seem to occur on any real
376                // world content (only contrived test cases), where this acts as a fail safe
377                // to ensure we don't allocate too much GPU memory for surface caches.
378                // However, if we _do_ ever see this occur on real world content, we could
379                // probably consider increasing the max cache slices a bit more than the
380                // current limit.
381                let (params, iframe_clip) = if slice == MAX_CACHE_SLICES-1 {
382                    let params = TileCacheParams {
383                        slice,
384                        slice_flags: SliceFlags::empty(),
385                        spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
386                        background_color: None,
387                        shared_clips: Vec::new(),
388                        shared_clip_chain: ClipChainId::NONE,
389                        virtual_surface_size: config.compositor_kind.get_virtual_surface_size(),
390                        compositor_surface_count: 0,
391                    };
392
393                    (params, None)
394                } else {
395                    let slice_flags = self.force_new_tile_cache.unwrap_or(SliceFlags::empty());
396
397                    let background_color = if slice == 0 {
398                        config.background_color
399                    } else {
400                        None
401                    };
402
403                    let mut shared_clips = Vec::new();
404                    add_clips(
405                        scroll_root,
406                        prim_instance.clip_set.clip_chain_id,
407                        &mut shared_clips,
408                        clip_store,
409                        interners,
410                        spatial_tree,
411                    );
412
413                    self.last_checked_clip_chain = prim_instance.clip_set.clip_chain_id;
414
415                    let params = TileCacheParams {
416                        slice,
417                        slice_flags,
418                        spatial_node_index: scroll_root,
419                        background_color,
420                        shared_clips,
421                        shared_clip_chain: ClipChainId::NONE,
422                        virtual_surface_size: config.compositor_kind.get_virtual_surface_size(),
423                        compositor_surface_count: 0,
424                    };
425
426                    (params, iframe_clip)
427                };
428
429                self.pending_tile_caches.push(PendingTileCache {
430                    prim_list: PrimitiveList::empty(),
431                    params,
432                    iframe_clip,
433                });
434
435                self.force_new_tile_cache = None;
436            }
437        }
438
439        self.pending_tile_caches
440            .last_mut()
441            .unwrap()
442            .prim_list
443            .add_prim(
444                prim_instance,
445                prim_rect,
446                spatial_node_index,
447                prim_flags,
448            );
449    }
450
451    /// Consume this object and build the list of tile cache primitives
452    pub fn build(
453        self,
454        config: &FrameBuilderConfig,
455        clip_store: &mut ClipStore,
456        prim_store: &mut PrimitiveStore,
457        interners: &Interners,
458    ) -> (TileCacheConfig, Vec<PictureIndex>) {
459        let mut result = TileCacheConfig::new(self.pending_tile_caches.len());
460        let mut tile_cache_pictures = Vec::new();
461
462        for mut pending_tile_cache in self.pending_tile_caches {
463            // Accumulate any clip instances from the iframe_clip into the shared clips
464            // that will be applied by this tile cache during compositing.
465            if let Some(clip_chain_id) = pending_tile_cache.iframe_clip {
466                add_all_rect_clips(
467                    clip_chain_id,
468                    &mut pending_tile_cache.params.shared_clips,
469                    clip_store,
470                    interners,
471                );
472            }
473
474            let pic_index = create_tile_cache(
475                pending_tile_cache.params.slice,
476                pending_tile_cache.params.slice_flags,
477                pending_tile_cache.params.spatial_node_index,
478                pending_tile_cache.prim_list,
479                pending_tile_cache.params.background_color,
480                pending_tile_cache.params.shared_clips,
481                prim_store,
482                clip_store,
483                &mut result.picture_cache_spatial_nodes,
484                config,
485                &mut result.tile_caches,
486            );
487
488            tile_cache_pictures.push(pic_index);
489        }
490
491        (result, tile_cache_pictures)
492    }
493
494    /// Find the scroll root for a given spatial node
495    fn find_scroll_root(
496        &mut self,
497        spatial_node_index: SpatialNodeIndex,
498        spatial_tree: &SpatialTree,
499    ) -> SpatialNodeIndex {
500        if self.prev_scroll_root_cache.0 == spatial_node_index {
501            return self.prev_scroll_root_cache.1;
502        }
503
504        let scroll_root = spatial_tree.find_scroll_root(spatial_node_index);
505        self.prev_scroll_root_cache = (spatial_node_index, scroll_root);
506
507        scroll_root
508    }
509}
510
511// Helper fn to collect clip handles from a given clip chain.
512fn add_clips(
513    scroll_root: SpatialNodeIndex,
514    clip_chain_id: ClipChainId,
515    prim_clips: &mut Vec<ClipInstance>,
516    clip_store: &ClipStore,
517    interners: &Interners,
518    spatial_tree: &SpatialTree,
519) {
520    let mut current_clip_chain_id = clip_chain_id;
521
522    while current_clip_chain_id != ClipChainId::NONE {
523        let clip_chain_node = &clip_store
524            .clip_chain_nodes[current_clip_chain_id.0 as usize];
525
526        let clip_node_data = &interners.clip[clip_chain_node.handle];
527        if let ClipNodeKind::Rectangle = clip_node_data.clip_node_kind {
528            if spatial_tree.is_ancestor(
529                clip_chain_node.spatial_node_index,
530                scroll_root,
531            ) {
532                prim_clips.push(ClipInstance::new(clip_chain_node.handle, clip_chain_node.spatial_node_index));
533            }
534        }
535
536        current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
537    }
538}
539
540// Walk a clip-chain, and accumulate all clip instances into supplied `prim_clips` array.
541fn add_all_rect_clips(
542    clip_chain_id: ClipChainId,
543    prim_clips: &mut Vec<ClipInstance>,
544    clip_store: &ClipStore,
545    interners: &Interners,
546) {
547    let mut current_clip_chain_id = clip_chain_id;
548
549    while current_clip_chain_id != ClipChainId::NONE {
550        let clip_chain_node = &clip_store
551            .clip_chain_nodes[current_clip_chain_id.0 as usize];
552
553        let clip_node_data = &interners.clip[clip_chain_node.handle];
554        if let ClipNodeKind::Rectangle = clip_node_data.clip_node_kind {
555            prim_clips.push(ClipInstance::new(clip_chain_node.handle, clip_chain_node.spatial_node_index));
556        }
557
558        current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
559    }
560}
561
562/// Given a PrimitiveList and scroll root, construct a tile cache primitive instance
563/// that wraps the primitive list.
564fn create_tile_cache(
565    slice: usize,
566    slice_flags: SliceFlags,
567    scroll_root: SpatialNodeIndex,
568    prim_list: PrimitiveList,
569    background_color: Option<ColorF>,
570    shared_clips: Vec<ClipInstance>,
571    prim_store: &mut PrimitiveStore,
572    clip_store: &mut ClipStore,
573    picture_cache_spatial_nodes: &mut FastHashSet<SpatialNodeIndex>,
574    frame_builder_config: &FrameBuilderConfig,
575    tile_caches: &mut FastHashMap<SliceId, TileCacheParams>,
576) -> PictureIndex {
577    // Add this spatial node to the list to check for complex transforms
578    // at the start of a frame build.
579    picture_cache_spatial_nodes.insert(scroll_root);
580
581    // Build a clip-chain for the tile cache, that contains any of the shared clips
582    // we will apply when drawing the tiles. In all cases provided by Gecko, these
583    // are rectangle clips with a scale/offset transform only, and get handled as
584    // a simple local clip rect in the vertex shader. However, this should in theory
585    // also work with any complex clips, such as rounded rects and image masks, by
586    // producing a clip mask that is applied to the picture cache tiles.
587
588    let mut parent_clip_chain_id = ClipChainId::NONE;
589    for clip_instance in &shared_clips {
590        // Add this spatial node to the list to check for complex transforms
591        // at the start of a frame build.
592        picture_cache_spatial_nodes.insert(clip_instance.spatial_node_index);
593
594        parent_clip_chain_id = clip_store.add_clip_chain_node(
595            clip_instance.handle,
596            clip_instance.spatial_node_index,
597            parent_clip_chain_id,
598        );
599    }
600
601    let slice_id = SliceId::new(slice);
602
603    // Store some information about the picture cache slice. This is used when we swap the
604    // new scene into the frame builder to either reuse existing slices, or create new ones.
605    tile_caches.insert(slice_id, TileCacheParams {
606        slice,
607        slice_flags,
608        spatial_node_index: scroll_root,
609        background_color,
610        shared_clips,
611        shared_clip_chain: parent_clip_chain_id,
612        virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(),
613        compositor_surface_count: prim_list.compositor_surface_count,
614    });
615
616    let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image(
617        Some(PictureCompositeMode::TileCache { slice_id }),
618        Picture3DContext::Out,
619        true,
620        PrimitiveFlags::IS_BACKFACE_VISIBLE,
621        prim_list,
622        scroll_root,
623        PictureOptions::default(),
624    ));
625
626    PictureIndex(pic_index)
627}
628
629/// Debug information about a set of picture cache slices, exposed via RenderResults
630#[derive(Debug)]
631#[cfg_attr(feature = "capture", derive(Serialize))]
632#[cfg_attr(feature = "replay", derive(Deserialize))]
633pub struct PictureCacheDebugInfo {
634    pub slices: FastHashMap<usize, SliceDebugInfo>,
635}
636
637impl PictureCacheDebugInfo {
638    pub fn new() -> Self {
639        PictureCacheDebugInfo {
640            slices: FastHashMap::default(),
641        }
642    }
643
644    /// Convenience method to retrieve a given slice. Deliberately panics
645    /// if the slice isn't present.
646    pub fn slice(&self, slice: usize) -> &SliceDebugInfo {
647        &self.slices[&slice]
648    }
649}
650
651impl Default for PictureCacheDebugInfo {
652    fn default() -> PictureCacheDebugInfo {
653        PictureCacheDebugInfo::new()
654    }
655}
656
657/// Debug information about a set of picture cache tiles, exposed via RenderResults
658#[derive(Debug)]
659#[cfg_attr(feature = "capture", derive(Serialize))]
660#[cfg_attr(feature = "replay", derive(Deserialize))]
661pub struct SliceDebugInfo {
662    pub tiles: FastHashMap<TileOffset, TileDebugInfo>,
663}
664
665impl SliceDebugInfo {
666    pub fn new() -> Self {
667        SliceDebugInfo {
668            tiles: FastHashMap::default(),
669        }
670    }
671
672    /// Convenience method to retrieve a given tile. Deliberately panics
673    /// if the tile isn't present.
674    pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo {
675        &self.tiles[&TileOffset::new(x, y)]
676    }
677}
678
679/// Debug information about a tile that was dirty and was rasterized
680#[derive(Debug, PartialEq)]
681#[cfg_attr(feature = "capture", derive(Serialize))]
682#[cfg_attr(feature = "replay", derive(Deserialize))]
683pub struct DirtyTileDebugInfo {
684    pub local_valid_rect: PictureRect,
685    pub local_dirty_rect: PictureRect,
686}
687
688/// Debug information about the state of a tile
689#[derive(Debug, PartialEq)]
690#[cfg_attr(feature = "capture", derive(Serialize))]
691#[cfg_attr(feature = "replay", derive(Deserialize))]
692pub enum TileDebugInfo {
693    /// Tile was occluded by a tile in front of it
694    Occluded,
695    /// Tile was culled (not visible in current display port)
696    Culled,
697    /// Tile was valid (no rasterization was done) and visible
698    Valid,
699    /// Tile was dirty, and was updated
700    Dirty(DirtyTileDebugInfo),
701}
702
703impl TileDebugInfo {
704    pub fn is_occluded(&self) -> bool {
705        match self {
706            TileDebugInfo::Occluded => true,
707            TileDebugInfo::Culled |
708            TileDebugInfo::Valid |
709            TileDebugInfo::Dirty(..) => false,
710        }
711    }
712
713    pub fn is_valid(&self) -> bool {
714        match self {
715            TileDebugInfo::Valid => true,
716            TileDebugInfo::Culled |
717            TileDebugInfo::Occluded |
718            TileDebugInfo::Dirty(..) => false,
719        }
720    }
721
722    pub fn is_culled(&self) -> bool {
723        match self {
724            TileDebugInfo::Culled => true,
725            TileDebugInfo::Valid |
726            TileDebugInfo::Occluded |
727            TileDebugInfo::Dirty(..) => false,
728        }
729    }
730
731    pub fn as_dirty(&self) -> &DirtyTileDebugInfo {
732        match self {
733            TileDebugInfo::Occluded |
734            TileDebugInfo::Culled |
735            TileDebugInfo::Valid => {
736                panic!("not a dirty tile!");
737            }
738            TileDebugInfo::Dirty(ref info) => {
739                info
740            }
741        }
742    }
743}