webrender/
visibility.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! # Visibility pass
6//!
7//! TODO: document what this pass does!
8//!
9
10use api::DebugFlags;
11use api::units::*;
12use std::usize;
13use crate::clip::ClipStore;
14use crate::composite::CompositeState;
15use crate::profiler::TransactionProfile;
16use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
17use crate::clip::{ClipChainInstance, ClipTree};
18use crate::frame_builder::FrameBuilderConfig;
19use crate::gpu_cache::GpuCache;
20use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo, TileCacheInstance};
21use crate::picture::{SurfaceIndex, RasterConfig, SubSliceIndex};
22use crate::prim_store::{ClipTaskIndex, PictureIndex, PrimitiveInstanceKind};
23use crate::prim_store::{PrimitiveStore, PrimitiveInstance};
24use crate::render_backend::{DataStores, ScratchBuffer};
25use crate::render_task_graph::RenderTaskGraphBuilder;
26use crate::resource_cache::ResourceCache;
27use crate::scene::SceneProperties;
28use crate::space::SpaceMapper;
29use crate::util::MaxRect;
30
31pub struct FrameVisibilityContext<'a> {
32    pub spatial_tree: &'a SpatialTree,
33    pub global_screen_world_rect: WorldRect,
34    pub global_device_pixel_scale: DevicePixelScale,
35    pub debug_flags: DebugFlags,
36    pub scene_properties: &'a SceneProperties,
37    pub config: FrameBuilderConfig,
38    pub root_spatial_node_index: SpatialNodeIndex,
39}
40
41pub struct FrameVisibilityState<'a> {
42    pub clip_store: &'a mut ClipStore,
43    pub resource_cache: &'a mut ResourceCache,
44    pub gpu_cache: &'a mut GpuCache,
45    pub data_stores: &'a mut DataStores,
46    pub clip_tree: &'a mut ClipTree,
47    pub composite_state: &'a mut CompositeState,
48    pub rg_builder: &'a mut RenderTaskGraphBuilder,
49    pub prim_instances: &'a mut [PrimitiveInstance],
50    pub surfaces: &'a mut [SurfaceInfo],
51    /// A stack of currently active off-screen surfaces during the
52    /// visibility frame traversal.
53    pub surface_stack: Vec<(PictureIndex, SurfaceIndex)>,
54    pub profile: &'a mut TransactionProfile,
55    pub scratch: &'a mut ScratchBuffer,
56    pub visited_pictures: &'a mut[bool],
57}
58
59impl<'a> FrameVisibilityState<'a> {
60    pub fn push_surface(
61        &mut self,
62        pic_index: PictureIndex,
63        surface_index: SurfaceIndex,
64    ) {
65        self.surface_stack.push((pic_index, surface_index));
66    }
67
68    pub fn pop_surface(&mut self) {
69        self.surface_stack.pop().unwrap();
70    }
71}
72
73bitflags! {
74    /// A set of bitflags that can be set in the visibility information
75    /// for a primitive instance. This can be used to control how primitives
76    /// are treated during batching.
77    // TODO(gw): We should also move `is_compositor_surface` to be part of
78    //           this flags struct.
79    #[cfg_attr(feature = "capture", derive(Serialize))]
80    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
81    pub struct PrimitiveVisibilityFlags: u8 {
82        /// Implies that this primitive covers the entire picture cache slice,
83        /// and can thus be dropped during batching and drawn with clear color.
84        const IS_BACKDROP = 1;
85    }
86}
87
88/// Contains the current state of the primitive's visibility.
89#[derive(Debug)]
90#[cfg_attr(feature = "capture", derive(Serialize))]
91pub enum VisibilityState {
92    /// Uninitialized - this should never be encountered after prim reset
93    Unset,
94    /// Culled for being off-screen, or not possible to render (e.g. missing image resource)
95    Culled,
96    /// A picture that doesn't have a surface - primitives are composed into the
97    /// parent picture with a surface.
98    PassThrough,
99    /// A primitive that has been found to be visible
100    Visible {
101        /// A set of flags that define how this primitive should be handled
102        /// during batching of visible primitives.
103        vis_flags: PrimitiveVisibilityFlags,
104
105        /// Sub-slice within the picture cache that this prim exists on
106        sub_slice_index: SubSliceIndex,
107    },
108}
109
110/// Information stored for a visible primitive about the visible
111/// rect and associated clip information.
112#[derive(Debug)]
113#[cfg_attr(feature = "capture", derive(Serialize))]
114pub struct PrimitiveVisibility {
115    /// The clip chain instance that was built for this primitive.
116    pub clip_chain: ClipChainInstance,
117
118    /// Current visibility state of the primitive.
119    // TODO(gw): Move more of the fields from this struct into
120    //           the state enum.
121    pub state: VisibilityState,
122
123    /// An index into the clip task instances array in the primitive
124    /// store. If this is ClipTaskIndex::INVALID, then the primitive
125    /// has no clip mask. Otherwise, it may store the offset of the
126    /// global clip mask task for this primitive, or the first of
127    /// a list of clip task ids (one per segment).
128    pub clip_task_index: ClipTaskIndex,
129}
130
131impl PrimitiveVisibility {
132    pub fn new() -> Self {
133        PrimitiveVisibility {
134            state: VisibilityState::Unset,
135            clip_chain: ClipChainInstance::empty(),
136            clip_task_index: ClipTaskIndex::INVALID,
137        }
138    }
139
140    pub fn reset(&mut self) {
141        self.state = VisibilityState::Culled;
142        self.clip_task_index = ClipTaskIndex::INVALID;
143    }
144}
145
146pub fn update_prim_visibility(
147    pic_index: PictureIndex,
148    parent_surface_index: Option<SurfaceIndex>,
149    world_culling_rect: &WorldRect,
150    store: &PrimitiveStore,
151    is_root_tile_cache: bool,
152    frame_context: &FrameVisibilityContext,
153    frame_state: &mut FrameVisibilityState,
154    tile_cache: &mut Option<&mut TileCacheInstance>,
155 ) {
156    if frame_state.visited_pictures[pic_index.0] {
157        return;
158    }
159    frame_state.visited_pictures[pic_index.0] = true;
160    let pic = &store.pictures[pic_index.0];
161
162    let (surface_index, pop_surface) = match pic.raster_config {
163        Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { .. }, .. }) => {
164            (surface_index, false)
165        }
166        Some(ref raster_config) => {
167            frame_state.push_surface(
168                pic_index,
169                raster_config.surface_index,
170            );
171
172            if let Some(parent_surface_index) = parent_surface_index {
173                let parent_culling_rect = frame_state
174                    .surfaces[parent_surface_index.0]
175                    .culling_rect;
176
177                let surface = &mut frame_state
178                    .surfaces[raster_config.surface_index.0 as usize];
179
180                surface.update_culling_rect(
181                    parent_culling_rect,
182                    &raster_config.composite_mode,
183                    frame_context,
184                );
185            }
186
187            let surface_local_rect = frame_state.surfaces[raster_config.surface_index.0]
188                .unclipped_local_rect
189                .cast_unit();
190
191            // Let the picture cache know that we are pushing an off-screen
192            // surface, so it can treat dependencies of surface atomically.
193            if let Some(tile_cache) = tile_cache {
194                tile_cache.push_surface(
195                    surface_local_rect,
196                    pic.spatial_node_index,
197                    frame_context.spatial_tree,
198                );
199            }
200
201            (raster_config.surface_index, true)
202        }
203        None => {
204            (parent_surface_index.expect("bug: pass-through with no parent"), false)
205        }
206    };
207
208    let surface = &frame_state.surfaces[surface_index.0 as usize];
209    let surface_culling_rect = surface.culling_rect;
210
211    let device_pixel_scale = surface.device_pixel_scale;
212    let mut map_local_to_picture = surface.map_local_to_picture.clone();
213
214    let map_surface_to_vis = SpaceMapper::new_with_target(
215        // TODO: switch from root to raster space.
216        frame_context.root_spatial_node_index,
217        surface.surface_spatial_node_index,
218        surface.culling_rect,
219        frame_context.spatial_tree,
220    );
221    let visibility_spatial_node_index = surface.visibility_spatial_node_index;
222
223    for cluster in &pic.prim_list.clusters {
224        profile_scope!("cluster");
225
226        // Each prim instance must have reset called each frame, to clear
227        // indices into various scratch buffers. If this doesn't occur,
228        // the primitive may incorrectly be considered visible, which can
229        // cause unexpected conditions to occur later during the frame.
230        // Primitive instances are normally reset in the main loop below,
231        // but we must also reset them in the rare case that the cluster
232        // visibility has changed (due to an invalid transform and/or
233        // backface visibility changing for this cluster).
234        // TODO(gw): This is difficult to test for in CI - as a follow up,
235        //           we should add a debug flag that validates the prim
236        //           instance is always reset every frame to catch similar
237        //           issues in future.
238        for prim_instance in &mut frame_state.prim_instances[cluster.prim_range()] {
239            prim_instance.reset();
240        }
241
242        // Get the cluster and see if is visible
243        if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) {
244            continue;
245        }
246
247        map_local_to_picture.set_target_spatial_node(
248            cluster.spatial_node_index,
249            frame_context.spatial_tree,
250        );
251
252        for prim_instance_index in cluster.prim_range() {
253            if let PrimitiveInstanceKind::Picture { pic_index, .. } = frame_state.prim_instances[prim_instance_index].kind {
254                if !store.pictures[pic_index.0].is_visible(frame_context.spatial_tree) {
255                    continue;
256                }
257
258                let is_passthrough = match store.pictures[pic_index.0].raster_config {
259                    Some(..) => false,
260                    None => true,
261                };
262
263                if !is_passthrough {
264                    let clip_root = store
265                        .pictures[pic_index.0]
266                        .clip_root
267                        .unwrap_or_else(|| {
268                            // If we couldn't find a common ancestor then just use the
269                            // clip node of the picture primitive itself
270                            let leaf_id = frame_state.prim_instances[prim_instance_index].clip_leaf_id;
271                            frame_state.clip_tree.get_leaf(leaf_id).node_id
272                        }
273                    );
274
275                    frame_state.clip_tree.push_clip_root_node(clip_root);
276                }
277
278                update_prim_visibility(
279                    pic_index,
280                    Some(surface_index),
281                    world_culling_rect,
282                    store,
283                    false,
284                    frame_context,
285                    frame_state,
286                    tile_cache,
287                );
288
289                if is_passthrough {
290                    // Pass through pictures are always considered visible in all dirty tiles.
291                    frame_state.prim_instances[prim_instance_index].vis.state = VisibilityState::PassThrough;
292
293                    continue;
294                } else {
295                    frame_state.clip_tree.pop_clip_root();
296                }
297            }
298
299            let prim_instance = &mut frame_state.prim_instances[prim_instance_index];
300
301            let local_coverage_rect = frame_state.data_stores.get_local_prim_coverage_rect(
302                prim_instance,
303                &store.pictures,
304                frame_state.surfaces,
305            );
306
307            frame_state.clip_store.set_active_clips(
308                cluster.spatial_node_index,
309                map_local_to_picture.ref_spatial_node_index,
310                visibility_spatial_node_index,
311                prim_instance.clip_leaf_id,
312                &frame_context.spatial_tree,
313                &frame_state.data_stores.clip,
314                frame_state.clip_tree,
315            );
316
317            let clip_chain = frame_state
318                .clip_store
319                .build_clip_chain_instance(
320                    local_coverage_rect,
321                    &map_local_to_picture,
322                    &map_surface_to_vis,
323                    &frame_context.spatial_tree,
324                    frame_state.gpu_cache,
325                    frame_state.resource_cache,
326                    device_pixel_scale,
327                    &surface_culling_rect,
328                    &mut frame_state.data_stores.clip,
329                    frame_state.rg_builder,
330                    true,
331                );
332
333            prim_instance.vis.clip_chain = match clip_chain {
334                Some(clip_chain) => clip_chain,
335                None => {
336                    continue;
337                }
338            };
339
340            {
341                let prim_surface_index = frame_state.surface_stack.last().unwrap().1;
342                let prim_clip_chain = &prim_instance.vis.clip_chain;
343
344                // Accumulate the exact (clipped) local rect into the parent surface.
345                let surface = &mut frame_state.surfaces[prim_surface_index.0];
346                surface.clipped_local_rect = surface.clipped_local_rect.union(&prim_clip_chain.pic_coverage_rect);
347            }
348
349            prim_instance.vis.state = match tile_cache {
350                Some(tile_cache) => {
351                    tile_cache.update_prim_dependencies(
352                        prim_instance,
353                        cluster.spatial_node_index,
354                        // It's OK to pass the local_coverage_rect here as it's only
355                        // used by primitives (for compositor surfaces) that don't
356                        // have inflation anyway.
357                        local_coverage_rect,
358                        frame_context,
359                        frame_state.data_stores,
360                        frame_state.clip_store,
361                        &store.pictures,
362                        frame_state.resource_cache,
363                        &store.color_bindings,
364                        &frame_state.surface_stack,
365                        &mut frame_state.composite_state,
366                        &mut frame_state.gpu_cache,
367                        &mut frame_state.scratch.primitive,
368                        is_root_tile_cache,
369                        frame_state.surfaces,
370                        frame_state.profile,
371                    )
372                }
373                None => {
374                    VisibilityState::Visible {
375                        vis_flags: PrimitiveVisibilityFlags::empty(),
376                        sub_slice_index: SubSliceIndex::DEFAULT,
377                    }
378                }
379            };
380        }
381    }
382
383    if let Some(snapshot) = &pic.snapshot {
384        if snapshot.detached {
385            // If the snapshot is detached, then the contents of the stacking
386            // context will only be shown via the snapshot, so there is no point
387            // to rendering anything outside of the snapshot area.
388            let prim_surface_index = frame_state.surface_stack.last().unwrap().1;
389            let surface = &mut frame_state.surfaces[prim_surface_index.0];
390            let clip = snapshot.area.round_out().cast_unit();
391            surface.clipped_local_rect = surface.clipped_local_rect.intersection_unchecked(&clip);
392        }
393    }
394
395    if pop_surface {
396        frame_state.pop_surface();
397    }
398
399    if let Some(ref rc) = pic.raster_config {
400        if let Some(tile_cache) = tile_cache {
401            match rc.composite_mode {
402                PictureCompositeMode::TileCache { .. } => {}
403                _ => {
404                    // Pop the off-screen surface from the picture cache stack
405                    tile_cache.pop_surface();
406                }
407            }
408        }
409    }
410}
411
412pub fn compute_conservative_visible_rect(
413    clip_chain: &ClipChainInstance,
414    culling_rect: VisRect,
415    visibility_node_index: SpatialNodeIndex,
416    prim_spatial_node_index: SpatialNodeIndex,
417    spatial_tree: &SpatialTree,
418) -> LayoutRect {
419    // Mapping from picture space -> world space
420    let map_pic_to_vis: SpaceMapper<PicturePixel, VisPixel> = SpaceMapper::new_with_target(
421        visibility_node_index,
422        clip_chain.pic_spatial_node_index,
423        culling_rect,
424        spatial_tree,
425    );
426
427    // Mapping from local space -> picture space
428    let map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel> = SpaceMapper::new_with_target(
429        clip_chain.pic_spatial_node_index,
430        prim_spatial_node_index,
431        PictureRect::max_rect(),
432        spatial_tree,
433    );
434
435    // Unmap the world culling rect from world -> picture space. If this mapping fails due
436    // to matrix weirdness, best we can do is use the clip chain's local clip rect.
437    let pic_culling_rect = match map_pic_to_vis.unmap(&culling_rect) {
438        Some(rect) => rect,
439        None => return clip_chain.local_clip_rect,
440    };
441
442    // Intersect the unmapped world culling rect with the primitive's clip chain rect that
443    // is in picture space (the clip-chain already takes into account the bounds of the
444    // primitive local_rect and local_clip_rect). If there is no intersection here, the
445    // primitive is not visible at all.
446    let pic_culling_rect = match pic_culling_rect.intersection(&clip_chain.pic_coverage_rect) {
447        Some(rect) => rect,
448        None => return LayoutRect::zero(),
449    };
450
451    // Unmap the picture culling rect from picture -> local space. If this mapping fails due
452    // to matrix weirdness, best we can do is use the clip chain's local clip rect.
453    match map_local_to_pic.unmap(&pic_culling_rect) {
454        Some(rect) => rect,
455        None => clip_chain.local_clip_rect,
456    }
457}