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 //! # Visibility pass
6 //!
7 //! TODO: document what this pass does!
8 //!
9 
10 use api::{ColorF, DebugFlags};
11 use api::units::*;
12 use euclid::Scale;
13 use std::{usize, mem};
14 use crate::batch::BatchFilter;
15 use crate::clip::{ClipStore, ClipChainStack};
16 use crate::composite::CompositeState;
17 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex};
18 use crate::clip::{ClipInstance, ClipChainInstance};
19 use crate::debug_colors;
20 use crate::frame_builder::FrameBuilderConfig;
21 use crate::gpu_cache::GpuCache;
22 use crate::internal_types::FastHashMap;
23 use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo, TileCacheInstance};
24 use crate::picture::{PrimitiveList, SurfaceIndex, RasterConfig, SliceId};
25 use crate::prim_store::{ClipTaskIndex, PictureIndex, PrimitiveInstanceKind};
26 use crate::prim_store::{PrimitiveStore, PrimitiveInstance};
27 use crate::render_backend::{DataStores, ScratchBuffer};
28 use crate::resource_cache::ResourceCache;
29 use crate::scene::SceneProperties;
30 use crate::space::SpaceMapper;
31 use crate::internal_types::Filter;
32 use crate::util::{MaxRect};
33 
34 pub struct FrameVisibilityContext<'a> {
35     pub spatial_tree: &'a SpatialTree,
36     pub global_screen_world_rect: WorldRect,
37     pub global_device_pixel_scale: DevicePixelScale,
38     pub surfaces: &'a [SurfaceInfo],
39     pub debug_flags: DebugFlags,
40     pub scene_properties: &'a SceneProperties,
41     pub config: FrameBuilderConfig,
42 }
43 
44 pub struct FrameVisibilityState<'a> {
45     pub clip_store: &'a mut ClipStore,
46     pub resource_cache: &'a mut ResourceCache,
47     pub gpu_cache: &'a mut GpuCache,
48     pub scratch: &'a mut ScratchBuffer,
49     pub tile_cache: Option<Box<TileCacheInstance>>,
50     pub data_stores: &'a mut DataStores,
51     pub clip_chain_stack: ClipChainStack,
52     pub composite_state: &'a mut CompositeState,
53     /// A stack of currently active off-screen surfaces during the
54     /// visibility frame traversal.
55     pub surface_stack: Vec<SurfaceIndex>,
56 }
57 
58 impl<'a> FrameVisibilityState<'a> {
push_surface( &mut self, surface_index: SurfaceIndex, shared_clips: &[ClipInstance], spatial_tree: &SpatialTree, )59     pub fn push_surface(
60         &mut self,
61         surface_index: SurfaceIndex,
62         shared_clips: &[ClipInstance],
63         spatial_tree: &SpatialTree,
64     ) {
65         self.surface_stack.push(surface_index);
66         self.clip_chain_stack.push_surface(shared_clips, spatial_tree);
67     }
68 
pop_surface(&mut self)69     pub fn pop_surface(&mut self) {
70         self.surface_stack.pop().unwrap();
71         self.clip_chain_stack.pop_surface();
72     }
73 }
74 
75 bitflags! {
76     /// A set of bitflags that can be set in the visibility information
77     /// for a primitive instance. This can be used to control how primitives
78     /// are treated during batching.
79     // TODO(gw): We should also move `is_compositor_surface` to be part of
80     //           this flags struct.
81     #[cfg_attr(feature = "capture", derive(Serialize))]
82     pub struct PrimitiveVisibilityFlags: u8 {
83         /// Implies that this primitive covers the entire picture cache slice,
84         /// and can thus be dropped during batching and drawn with clear color.
85         const IS_BACKDROP = 1;
86     }
87 }
88 
89 /// Contains the current state of the primitive's visibility.
90 #[derive(Debug)]
91 #[cfg_attr(feature = "capture", derive(Serialize))]
92 pub enum VisibilityState {
93     /// Uninitialized - this should never be encountered after prim reset
94     Unset,
95     /// Culled for being off-screen, or not possible to render (e.g. missing image resource)
96     Culled,
97     /// A picture that doesn't have a surface - primitives are composed into the
98     /// parent picture with a surface.
99     PassThrough,
100     /// During picture cache dependency update, was found to be intersecting with one
101     /// or more visible tiles. The rect in picture cache space is stored here to allow
102     /// the detailed calculations below.
103     Coarse {
104         /// Information about which tile batchers this prim should be added to
105         filter: BatchFilter,
106 
107         /// A set of flags that define how this primitive should be handled
108         /// during batching of visible primitives.
109         vis_flags: PrimitiveVisibilityFlags,
110     },
111     /// Once coarse visibility is resolved, this will be set if the primitive
112     /// intersected any dirty rects, otherwise prim will be culled.
113     Detailed {
114         /// Information about which tile batchers this prim should be added to
115         filter: BatchFilter,
116 
117         /// A set of flags that define how this primitive should be handled
118         /// during batching of visible primitives.
119         vis_flags: PrimitiveVisibilityFlags,
120     },
121 }
122 
123 /// Information stored for a visible primitive about the visible
124 /// rect and associated clip information.
125 #[derive(Debug)]
126 #[cfg_attr(feature = "capture", derive(Serialize))]
127 pub struct PrimitiveVisibility {
128     /// The clip chain instance that was built for this primitive.
129     pub clip_chain: ClipChainInstance,
130 
131     /// Current visibility state of the primitive.
132     // TODO(gw): Move more of the fields from this struct into
133     //           the state enum.
134     pub state: VisibilityState,
135 
136     /// An index into the clip task instances array in the primitive
137     /// store. If this is ClipTaskIndex::INVALID, then the primitive
138     /// has no clip mask. Otherwise, it may store the offset of the
139     /// global clip mask task for this primitive, or the first of
140     /// a list of clip task ids (one per segment).
141     pub clip_task_index: ClipTaskIndex,
142 
143     /// The current combined local clip for this primitive, from
144     /// the primitive local clip above and the current clip chain.
145     pub combined_local_clip_rect: LayoutRect,
146 }
147 
148 impl PrimitiveVisibility {
new() -> Self149     pub fn new() -> Self {
150         PrimitiveVisibility {
151             state: VisibilityState::Unset,
152             clip_chain: ClipChainInstance::empty(),
153             clip_task_index: ClipTaskIndex::INVALID,
154             combined_local_clip_rect: LayoutRect::zero(),
155         }
156     }
157 
reset(&mut self)158     pub fn reset(&mut self) {
159         self.state = VisibilityState::Culled;
160         self.clip_task_index = ClipTaskIndex::INVALID;
161     }
162 }
163 
164 /// Update visibility pass - update each primitive visibility struct, and
165 /// build the clip chain instance if appropriate.
update_primitive_visibility( store: &mut PrimitiveStore, pic_index: PictureIndex, parent_surface_index: SurfaceIndex, world_culling_rect: &WorldRect, frame_context: &FrameVisibilityContext, frame_state: &mut FrameVisibilityState, tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>, is_root_tile_cache: bool, ) -> Option<PictureRect>166 pub fn update_primitive_visibility(
167     store: &mut PrimitiveStore,
168     pic_index: PictureIndex,
169     parent_surface_index: SurfaceIndex,
170     world_culling_rect: &WorldRect,
171     frame_context: &FrameVisibilityContext,
172     frame_state: &mut FrameVisibilityState,
173     tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
174     is_root_tile_cache: bool,
175 ) -> Option<PictureRect> {
176     profile_scope!("update_visibility");
177     let (mut prim_list, surface_index, apply_local_clip_rect, world_culling_rect, is_composite) = {
178         let pic = &mut store.pictures[pic_index.0];
179         let mut world_culling_rect = *world_culling_rect;
180 
181         let prim_list = mem::replace(&mut pic.prim_list, PrimitiveList::empty());
182         let (surface_index, is_composite) = match pic.raster_config {
183             Some(ref raster_config) => (raster_config.surface_index, true),
184             None => (parent_surface_index, false)
185         };
186 
187         match pic.raster_config {
188             Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => {
189                 let mut tile_cache = tile_caches
190                     .remove(&slice_id)
191                     .expect("bug: non-existent tile cache");
192 
193                 // If we have a tile cache for this picture, see if any of the
194                 // relative transforms have changed, which means we need to
195                 // re-map the dependencies of any child primitives.
196                 world_culling_rect = tile_cache.pre_update(
197                     layout_rect_as_picture_rect(&pic.estimated_local_rect),
198                     surface_index,
199                     frame_context,
200                     frame_state,
201                 );
202 
203                 // Push a new surface, supplying the list of clips that should be
204                 // ignored, since they are handled by clipping when drawing this surface.
205                 frame_state.push_surface(
206                     surface_index,
207                     &tile_cache.shared_clips,
208                     frame_context.spatial_tree,
209                 );
210                 frame_state.tile_cache = Some(tile_cache);
211             }
212             _ => {
213                 if is_composite {
214                     frame_state.push_surface(
215                         surface_index,
216                         &[],
217                         frame_context.spatial_tree,
218                     );
219 
220                     // Let the picture cache know that we are pushing an off-screen
221                     // surface, so it can treat dependencies of surface atomically.
222                     frame_state.tile_cache.as_mut().unwrap().push_surface(
223                         pic.estimated_local_rect,
224                         pic.spatial_node_index,
225                         frame_context.spatial_tree,
226                     );
227                 }
228             }
229         }
230 
231         (prim_list, surface_index, pic.apply_local_clip_rect, world_culling_rect, is_composite)
232     };
233 
234     let surface = &frame_context.surfaces[surface_index.0 as usize];
235 
236     let mut map_local_to_surface = surface
237         .map_local_to_surface
238         .clone();
239 
240     let map_surface_to_world = SpaceMapper::new_with_target(
241         ROOT_SPATIAL_NODE_INDEX,
242         surface.surface_spatial_node_index,
243         frame_context.global_screen_world_rect,
244         frame_context.spatial_tree,
245     );
246 
247     let mut surface_rect = PictureRect::zero();
248 
249     for cluster in &mut prim_list.clusters {
250         profile_scope!("cluster");
251         // Get the cluster and see if is visible
252         if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) {
253             // Each prim instance must have reset called each frame, to clear
254             // indices into various scratch buffers. If this doesn't occur,
255             // the primitive may incorrectly be considered visible, which can
256             // cause unexpected conditions to occur later during the frame.
257             // Primitive instances are normally reset in the main loop below,
258             // but we must also reset them in the rare case that the cluster
259             // visibility has changed (due to an invalid transform and/or
260             // backface visibility changing for this cluster).
261             // TODO(gw): This is difficult to test for in CI - as a follow up,
262             //           we should add a debug flag that validates the prim
263             //           instance is always reset every frame to catch similar
264             //           issues in future.
265             for prim_instance in &mut prim_list.prim_instances[cluster.prim_range()] {
266                 prim_instance.reset();
267             }
268             continue;
269         }
270 
271         map_local_to_surface.set_target_spatial_node(
272             cluster.spatial_node_index,
273             frame_context.spatial_tree,
274         );
275 
276         for prim_instance in &mut prim_list.prim_instances[cluster.prim_range()] {
277             prim_instance.reset();
278 
279             if prim_instance.is_chased() {
280                 #[cfg(debug_assertions)] // needed for ".id" part
281                 println!("\tpreparing {:?} in {:?}", prim_instance.id, pic_index);
282                 println!("\t{:?}", prim_instance.kind);
283             }
284 
285             let (is_passthrough, prim_local_rect, prim_shadowed_rect) = match prim_instance.kind {
286                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
287                     let (is_visible, is_passthrough) = {
288                         let pic = &store.pictures[pic_index.0];
289                         (pic.is_visible(), pic.raster_config.is_none())
290                     };
291 
292                     if !is_visible {
293                         continue;
294                     }
295 
296                     if is_passthrough {
297                         frame_state.clip_chain_stack.push_clip(
298                             prim_instance.clip_set.clip_chain_id,
299                             frame_state.clip_store,
300                         );
301                     }
302 
303                     let pic_surface_rect = update_primitive_visibility(
304                         store,
305                         pic_index,
306                         surface_index,
307                         &world_culling_rect,
308                         frame_context,
309                         frame_state,
310                         tile_caches,
311                         false,
312                     );
313 
314                     if is_passthrough {
315                         frame_state.clip_chain_stack.pop_clip();
316                     }
317 
318                     let pic = &store.pictures[pic_index.0];
319 
320                     if prim_instance.is_chased() && pic.estimated_local_rect != pic.precise_local_rect {
321                         println!("\testimate {:?} adjusted to {:?}", pic.estimated_local_rect, pic.precise_local_rect);
322                     }
323 
324                     let mut shadow_rect = pic.precise_local_rect;
325                     match pic.raster_config {
326                         Some(ref rc) => match rc.composite_mode {
327                             // If we have a drop shadow filter, we also need to include the shadow in
328                             // our shadowed local rect for the purpose of calculating the size of the
329                             // picture.
330                             PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
331                                 for shadow in shadows {
332                                     shadow_rect = shadow_rect.union(&pic.precise_local_rect.translate(shadow.offset));
333                                 }
334                             }
335                             _ => {}
336                         }
337                         None => {
338                             // If the primitive does not have its own raster config, we need to
339                             // propogate the surface rect calculation to the parent.
340                             if let Some(ref rect) = pic_surface_rect {
341                                 surface_rect = surface_rect.union(rect);
342                             }
343                         }
344                     }
345 
346                     (is_passthrough, pic.precise_local_rect, shadow_rect)
347                 }
348                 _ => {
349                     let prim_data = &frame_state.data_stores.as_common_data(&prim_instance);
350 
351                     (false, prim_data.prim_rect, prim_data.prim_rect)
352                 }
353             };
354 
355             if is_passthrough {
356                 // Pass through pictures are always considered visible in all dirty tiles.
357                 prim_instance.vis.state = VisibilityState::PassThrough;
358             } else {
359                 if prim_local_rect.width() <= 0.0 || prim_local_rect.height() <= 0.0 {
360                     if prim_instance.is_chased() {
361                         println!("\tculled for zero local rectangle");
362                     }
363                     continue;
364                 }
365 
366                 // Inflate the local rect for this primitive by the inflation factor of
367                 // the picture context and include the shadow offset. This ensures that
368                 // even if the primitive itstore is not visible, any effects from the
369                 // blur radius or shadow will be correctly taken into account.
370                 let inflation_factor = surface.inflation_factor;
371                 let local_rect = prim_shadowed_rect
372                     .inflate(inflation_factor, inflation_factor)
373                     .intersection(&prim_instance.clip_set.local_clip_rect);
374                 let local_rect = match local_rect {
375                     Some(local_rect) => local_rect,
376                     None => {
377                         if prim_instance.is_chased() {
378                             println!("\tculled for being out of the local clip rectangle: {:?}",
379                                      prim_instance.clip_set.local_clip_rect);
380                         }
381                         continue;
382                     }
383                 };
384 
385                 // Include the clip chain for this primitive in the current stack.
386                 frame_state.clip_chain_stack.push_clip(
387                     prim_instance.clip_set.clip_chain_id,
388                     frame_state.clip_store,
389                 );
390 
391                 frame_state.clip_store.set_active_clips(
392                     prim_instance.clip_set.local_clip_rect,
393                     cluster.spatial_node_index,
394                     map_local_to_surface.ref_spatial_node_index,
395                     frame_state.clip_chain_stack.current_clips_array(),
396                     &frame_context.spatial_tree,
397                     &frame_state.data_stores.clip,
398                 );
399 
400                 let clip_chain = frame_state
401                     .clip_store
402                     .build_clip_chain_instance(
403                         local_rect,
404                         &map_local_to_surface,
405                         &map_surface_to_world,
406                         &frame_context.spatial_tree,
407                         frame_state.gpu_cache,
408                         frame_state.resource_cache,
409                         surface.device_pixel_scale,
410                         &world_culling_rect,
411                         &mut frame_state.data_stores.clip,
412                         true,
413                         prim_instance.is_chased(),
414                     );
415 
416                 // Ensure the primitive clip is popped
417                 frame_state.clip_chain_stack.pop_clip();
418 
419                 prim_instance.vis.clip_chain = match clip_chain {
420                     Some(clip_chain) => clip_chain,
421                     None => {
422                         if prim_instance.is_chased() {
423                             println!("\tunable to build the clip chain, skipping");
424                         }
425                         continue;
426                     }
427                 };
428 
429                 if prim_instance.is_chased() {
430                     println!("\teffective clip chain from {:?} {}",
431                              prim_instance.vis.clip_chain.clips_range,
432                              if apply_local_clip_rect { "(applied)" } else { "" },
433                     );
434                     println!("\tpicture rect {:?} @{:?}",
435                              prim_instance.vis.clip_chain.pic_clip_rect,
436                              prim_instance.vis.clip_chain.pic_spatial_node_index,
437                     );
438                 }
439 
440                 prim_instance.vis.combined_local_clip_rect = if apply_local_clip_rect {
441                     prim_instance.vis.clip_chain.local_clip_rect
442                 } else {
443                     prim_instance.clip_set.local_clip_rect
444                 };
445 
446                 if prim_instance.vis.combined_local_clip_rect.is_empty() {
447                     if prim_instance.is_chased() {
448                         println!("\tculled for zero local clip rectangle");
449                     }
450                     continue;
451                 }
452 
453                 // Include the visible area for primitive, including any shadows, in
454                 // the area affected by the surface.
455                 match prim_instance.vis.combined_local_clip_rect.intersection(&local_rect) {
456                     Some(visible_rect) => {
457                         if let Some(rect) = map_local_to_surface.map(&visible_rect) {
458                             surface_rect = surface_rect.union(&rect);
459                         }
460                     }
461                     None => {
462                         if prim_instance.is_chased() {
463                             println!("\tculled for zero visible rectangle");
464                         }
465                         continue;
466                     }
467                 }
468 
469                 frame_state.tile_cache
470                     .as_mut()
471                     .unwrap()
472                     .update_prim_dependencies(
473                         prim_instance,
474                         cluster.spatial_node_index,
475                         prim_local_rect,
476                         frame_context,
477                         frame_state.data_stores,
478                         frame_state.clip_store,
479                         &store.pictures,
480                         frame_state.resource_cache,
481                         &store.color_bindings,
482                         &frame_state.surface_stack,
483                         &mut frame_state.composite_state,
484                         &mut frame_state.gpu_cache,
485                         is_root_tile_cache,
486                 );
487 
488                 // Skip post visibility prim update if this primitive was culled above.
489                 match prim_instance.vis.state {
490                     VisibilityState::Unset => panic!("bug: invalid state"),
491                     VisibilityState::Culled => continue,
492                     VisibilityState::Coarse { .. } | VisibilityState::Detailed { .. } | VisibilityState::PassThrough => {}
493                 }
494 
495                 // When the debug display is enabled, paint a colored rectangle around each
496                 // primitive.
497                 if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) {
498                     let debug_color = match prim_instance.kind {
499                         PrimitiveInstanceKind::Picture { .. } => ColorF::TRANSPARENT,
500                         PrimitiveInstanceKind::TextRun { .. } => debug_colors::RED,
501                         PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE,
502                         PrimitiveInstanceKind::NormalBorder { .. } |
503                         PrimitiveInstanceKind::ImageBorder { .. } => debug_colors::ORANGE,
504                         PrimitiveInstanceKind::Rectangle { .. } => ColorF { r: 0.8, g: 0.8, b: 0.8, a: 0.5 },
505                         PrimitiveInstanceKind::YuvImage { .. } => debug_colors::BLUE,
506                         PrimitiveInstanceKind::Image { .. } => debug_colors::BLUE,
507                         PrimitiveInstanceKind::LinearGradient { .. } => debug_colors::PINK,
508                         PrimitiveInstanceKind::CachedLinearGradient { .. } => debug_colors::PINK,
509                         PrimitiveInstanceKind::RadialGradient { .. } => debug_colors::PINK,
510                         PrimitiveInstanceKind::ConicGradient { .. } => debug_colors::PINK,
511                         PrimitiveInstanceKind::Clear { .. } => debug_colors::CYAN,
512                         PrimitiveInstanceKind::Backdrop { .. } => debug_colors::MEDIUMAQUAMARINE,
513                     };
514                     if debug_color.a != 0.0 {
515                         if let Some(rect) = calculate_prim_clipped_world_rect(
516                             &prim_instance.vis.clip_chain.pic_clip_rect,
517                             &world_culling_rect,
518                             &map_surface_to_world,
519                         ) {
520                             let debug_rect = rect * frame_context.global_device_pixel_scale;
521                             frame_state.scratch.primitive.push_debug_rect(debug_rect, debug_color, debug_color.scale_alpha(0.5));
522                         }
523                     }
524                 } else if frame_context.debug_flags.contains(::api::DebugFlags::OBSCURE_IMAGES) {
525                     let is_image = matches!(
526                         prim_instance.kind,
527                         PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::YuvImage { .. }
528                     );
529                     if is_image {
530                         // We allow "small" images, since they're generally UI elements.
531                         if let Some(rect) = calculate_prim_clipped_world_rect(
532                             &prim_instance.vis.clip_chain.pic_clip_rect,
533                             &world_culling_rect,
534                             &map_surface_to_world,
535                         ) {
536                             let rect = rect * frame_context.global_device_pixel_scale;
537                             if rect.width() > 70.0 && rect.height() > 70.0 {
538                                 frame_state.scratch.primitive.push_debug_rect(rect, debug_colors::PURPLE, debug_colors::PURPLE);
539                             }
540                         }
541                     }
542                 }
543 
544                 if prim_instance.is_chased() {
545                     println!("\tvisible with {:?}", prim_instance.vis.combined_local_clip_rect);
546                 }
547 
548                 // TODO(gw): This should probably be an instance method on PrimitiveInstance?
549                 update_prim_post_visibility(
550                     store,
551                     prim_instance,
552                     world_culling_rect,
553                     &map_surface_to_world,
554                 );
555             }
556         }
557     }
558 
559     // Similar to above, pop either the clip chain or root entry off the current clip stack.
560     if is_composite {
561         frame_state.pop_surface();
562     }
563 
564     let pic = &mut store.pictures[pic_index.0];
565     pic.prim_list = prim_list;
566 
567     // If the local rect changed (due to transforms in child primitives) then
568     // invalidate the GPU cache location to re-upload the new local rect
569     // and stretch size. Drop shadow filters also depend on the local rect
570     // size for the extra GPU cache data handle.
571     // TODO(gw): In future, if we support specifying a flag which gets the
572     //           stretch size from the segment rect in the shaders, we can
573     //           remove this invalidation here completely.
574     if let Some(ref rc) = pic.raster_config {
575         // Inflate the local bounding rect if required by the filter effect.
576         if pic.options.inflate_if_required {
577             surface_rect = rc.composite_mode.inflate_picture_rect(surface_rect, surface.scale_factors);
578         }
579 
580         // Layout space for the picture is picture space from the
581         // perspective of its child primitives.
582         pic.precise_local_rect = surface_rect * Scale::new(1.0);
583 
584         // If the precise rect changed since last frame, we need to invalidate
585         // any segments and gpu cache handles for drop-shadows.
586         // TODO(gw): Requiring storage of the `prev_precise_local_rect` here
587         //           is a total hack. It's required because `prev_precise_local_rect`
588         //           gets written to twice (during initial vis pass and also during
589         //           prepare pass). The proper longer term fix for this is to make
590         //           use of the conservative picture rect for segmenting (which should
591         //           be done during scene building).
592         if pic.precise_local_rect != pic.prev_precise_local_rect {
593             match rc.composite_mode {
594                 PictureCompositeMode::Filter(Filter::DropShadows(..)) => {
595                     for handle in &pic.extra_gpu_data_handles {
596                         frame_state.gpu_cache.invalidate(handle);
597                     }
598                 }
599                 _ => {}
600             }
601             // Invalidate any segments built for this picture, since the local
602             // rect has changed.
603             pic.segments_are_valid = false;
604             pic.prev_precise_local_rect = pic.precise_local_rect;
605         }
606 
607         match rc.composite_mode {
608             PictureCompositeMode::TileCache { .. } => {
609                 let mut tile_cache = frame_state.tile_cache.take().unwrap();
610 
611                 // Build the dirty region(s) for this tile cache.
612                 tile_cache.post_update(
613                     frame_context,
614                     frame_state,
615                 );
616 
617                 tile_caches.insert(SliceId::new(tile_cache.slice), tile_cache);
618             }
619             _ => {
620                 // Pop the off-screen surface from the picture cache stack
621                 frame_state.tile_cache.as_mut().unwrap().pop_surface();
622             }
623         }
624 
625         None
626     } else {
627         let parent_surface = &frame_context.surfaces[parent_surface_index.0 as usize];
628         let map_surface_to_parent_surface = SpaceMapper::new_with_target(
629             parent_surface.surface_spatial_node_index,
630             surface.surface_spatial_node_index,
631             PictureRect::max_rect(),
632             frame_context.spatial_tree,
633         );
634         map_surface_to_parent_surface.map(&surface_rect)
635     }
636 }
637 
638 
update_prim_post_visibility( store: &mut PrimitiveStore, prim_instance: &mut PrimitiveInstance, world_culling_rect: WorldRect, map_surface_to_world: &SpaceMapper<PicturePixel, WorldPixel>, )639 fn update_prim_post_visibility(
640     store: &mut PrimitiveStore,
641     prim_instance: &mut PrimitiveInstance,
642     world_culling_rect: WorldRect,
643     map_surface_to_world: &SpaceMapper<PicturePixel, WorldPixel>,
644 ) {
645     profile_scope!("update_prim_post_visibility");
646     match prim_instance.kind {
647         PrimitiveInstanceKind::Picture { pic_index, .. } => {
648             let pic = &mut store.pictures[pic_index.0];
649             // If this picture has a surface, determine the clipped bounding rect for it to
650             // minimize the size of the render target that is required.
651             if let Some(ref mut raster_config) = pic.raster_config {
652                 raster_config.clipped_bounding_rect = map_surface_to_world
653                     .map(&prim_instance.vis.clip_chain.pic_clip_rect)
654                     .and_then(|rect| {
655                         rect.intersection(&world_culling_rect)
656                     })
657                     .unwrap_or(WorldRect::zero());
658             }
659         }
660         PrimitiveInstanceKind::TextRun { .. } => {
661             // Text runs can't request resources early here, as we don't
662             // know until TileCache::post_update() whether we are drawing
663             // on an opaque surface.
664             // TODO(gw): We might be able to detect simple cases of this earlier,
665             //           during the picture traversal. But it's probably not worth it?
666         }
667         _ => {}
668     }
669 }
670 
compute_conservative_visible_rect( clip_chain: &ClipChainInstance, world_culling_rect: WorldRect, prim_spatial_node_index: SpatialNodeIndex, spatial_tree: &SpatialTree, ) -> LayoutRect671 pub fn compute_conservative_visible_rect(
672     clip_chain: &ClipChainInstance,
673     world_culling_rect: WorldRect,
674     prim_spatial_node_index: SpatialNodeIndex,
675     spatial_tree: &SpatialTree,
676 ) -> LayoutRect {
677     // Mapping from picture space -> world space
678     let map_pic_to_world: SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
679         ROOT_SPATIAL_NODE_INDEX,
680         clip_chain.pic_spatial_node_index,
681         world_culling_rect,
682         spatial_tree,
683     );
684 
685     // Mapping from local space -> picture space
686     let map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel> = SpaceMapper::new_with_target(
687         clip_chain.pic_spatial_node_index,
688         prim_spatial_node_index,
689         PictureRect::max_rect(),
690         spatial_tree,
691     );
692 
693     // Unmap the world culling rect from world -> picture space. If this mapping fails due
694     // to matrix weirdness, best we can do is use the clip chain's local clip rect.
695     let pic_culling_rect = match map_pic_to_world.unmap(&world_culling_rect) {
696         Some(rect) => rect,
697         None => return clip_chain.local_clip_rect,
698     };
699 
700     // Intersect the unmapped world culling rect with the primitive's clip chain rect that
701     // is in picture space (the clip-chain already takes into account the bounds of the
702     // primitive local_rect and local_clip_rect). If there is no intersection here, the
703     // primitive is not visible at all.
704     let pic_culling_rect = match pic_culling_rect.intersection(&clip_chain.pic_clip_rect) {
705         Some(rect) => rect,
706         None => return LayoutRect::zero(),
707     };
708 
709     // Unmap the picture culling rect from picture -> local space. If this mapping fails due
710     // to matrix weirdness, best we can do is use the clip chain's local clip rect.
711     match map_local_to_pic.unmap(&pic_culling_rect) {
712         Some(rect) => rect,
713         None => clip_chain.local_clip_rect,
714     }
715 }
716 
calculate_prim_clipped_world_rect( pic_clip_rect: &PictureRect, world_culling_rect: &WorldRect, map_surface_to_world: &SpaceMapper<PicturePixel, WorldPixel>, ) -> Option<WorldRect>717 fn calculate_prim_clipped_world_rect(
718     pic_clip_rect: &PictureRect,
719     world_culling_rect: &WorldRect,
720     map_surface_to_world: &SpaceMapper<PicturePixel, WorldPixel>,
721 ) -> Option<WorldRect> {
722     map_surface_to_world
723         .map(&pic_clip_rect)
724         .and_then(|world_rect| {
725             world_rect.intersection(world_culling_rect)
726         })
727 }
728