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