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