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