Skip to main content

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::renderer::GpuBufferBuilder;
17use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
18use crate::clip::{ClipChainInstance, ClipTree};
19use crate::composite::CompositorSurfaceKind;
20use crate::frame_builder::FrameBuilderConfig;
21use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo};
22use crate::tile_cache::TileCacheInstance;
23use crate::picture::{PictureScratch, SurfaceIndex, RasterConfig};
24use crate::tile_cache::SubSliceIndex;
25use crate::prim_store::{ClipTaskIndex, PictureIndex, PrimitiveKind, SegmentInstanceIndex};
26use crate::prim_store::{PrimitiveStore, PrimitiveInstance, PrimitiveInstanceIndex};
27use crate::prim_store::backdrop::BackdropRenderScratch;
28use crate::prim_store::borders::{ImageBorderScratch, NormalBorderScratch};
29use crate::prim_store::image::ImageScratch;
30use crate::prim_store::line_dec::LineDecorationScratch;
31use crate::prim_store::storage;
32use crate::prim_store::text_run::TextRunScratch;
33use crate::render_backend::{DataStores, ScratchBuffer};
34use crate::render_task_graph::RenderTaskGraphBuilder;
35use crate::resource_cache::ResourceCache;
36use crate::scene::SceneProperties;
37use crate::space::SpaceMapper;
38use crate::util::MaxRect;
39
40pub struct FrameVisibilityContext<'a> {
41    pub spatial_tree: &'a SpatialTree,
42    pub global_screen_world_rect: WorldRect,
43    pub global_device_pixel_scale: DevicePixelScale,
44    pub debug_flags: DebugFlags,
45    pub scene_properties: &'a SceneProperties,
46    pub config: FrameBuilderConfig,
47    pub root_spatial_node_index: SpatialNodeIndex,
48}
49
50pub struct FrameVisibilityState<'a> {
51    pub clip_store: &'a mut ClipStore,
52    pub resource_cache: &'a mut ResourceCache,
53    pub frame_gpu_data: &'a mut GpuBufferBuilder,
54    pub data_stores: &'a mut DataStores,
55    pub clip_tree: &'a mut ClipTree,
56    pub composite_state: &'a mut CompositeState,
57    pub rg_builder: &'a mut RenderTaskGraphBuilder,
58    pub prim_instances: &'a mut [PrimitiveInstance],
59    pub surfaces: &'a mut [SurfaceInfo],
60    /// A stack of currently active off-screen surfaces during the
61    /// visibility frame traversal.
62    pub surface_stack: Vec<(PictureIndex, SurfaceIndex)>,
63    pub profile: &'a mut TransactionProfile,
64    pub scratch: &'a mut ScratchBuffer,
65    pub visited_pictures: &'a mut[bool],
66}
67
68impl<'a> FrameVisibilityState<'a> {
69    pub fn push_surface(
70        &mut self,
71        pic_index: PictureIndex,
72        surface_index: SurfaceIndex,
73    ) {
74        self.surface_stack.push((pic_index, surface_index));
75    }
76
77    pub fn pop_surface(&mut self) {
78        self.surface_stack.pop().unwrap();
79    }
80}
81
82bitflags! {
83    /// A set of bitflags that can be set in the visibility information
84    /// for a primitive instance. This can be used to control how primitives
85    /// are treated during batching.
86    // TODO(gw): We should also move `is_compositor_surface` to be part of
87    //           this flags struct.
88    #[cfg_attr(feature = "capture", derive(Serialize))]
89    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
90    pub struct PrimitiveVisibilityFlags: u8 {
91        /// Implies that this primitive covers the entire picture cache slice,
92        /// and can thus be dropped during batching and drawn with clear color.
93        const IS_BACKDROP = 1;
94    }
95}
96
97/// Contains the current state of the primitive's visibility.
98#[derive(Debug, Copy, Clone)]
99#[cfg_attr(feature = "capture", derive(Serialize))]
100pub enum DrawState {
101    /// Uninitialized - this should never be encountered after prim reset
102    Unset,
103    /// Culled for being off-screen, or not possible to render (e.g. missing image resource)
104    Culled,
105    /// A picture that doesn't have a surface - primitives are composed into the
106    /// parent picture with a surface.
107    PassThrough,
108    /// A primitive that has been found to be visible
109    Visible {
110        /// A set of flags that define how this primitive should be handled
111        /// during batching of visible primitives.
112        vis_flags: PrimitiveVisibilityFlags,
113
114        /// Sub-slice within the picture cache that this prim exists on
115        sub_slice_index: SubSliceIndex,
116    },
117}
118
119/// Per-draw, per-kind scratch handle. Reaches the appropriate
120/// per-frame scratch entry for the drawn primitive's kind. The variant
121/// matches the prim's PrimitiveKind. None for kinds without per-frame
122/// scratch.
123#[derive(Debug, Copy, Clone)]
124#[cfg_attr(feature = "capture", derive(Serialize))]
125pub enum KindScratchHandle {
126    None,
127    LineDecoration(storage::Index<LineDecorationScratch>),
128    NormalBorder(storage::Index<NormalBorderScratch>),
129    ImageBorder(storage::Index<ImageBorderScratch>),
130    Image(storage::Index<ImageScratch>),
131    TextRun(storage::Index<TextRunScratch>),
132    Picture(storage::Index<PictureScratch>),
133    BackdropRender(storage::Index<BackdropRenderScratch>),
134}
135
136impl KindScratchHandle {
137    /// Extract the LineDecoration scratch index. Panics if the variant
138    /// doesn't match — readers in the LineDecoration arm of the
139    /// PrimitiveKind match know the variant by construction.
140    pub fn unwrap_line_decoration(&self) -> storage::Index<LineDecorationScratch> {
141        match *self {
142            KindScratchHandle::LineDecoration(h) => h,
143            _ => panic!("kind_scratch mismatch: expected LineDecoration, got {:?}", self),
144        }
145    }
146    pub fn unwrap_normal_border(&self) -> storage::Index<NormalBorderScratch> {
147        match *self {
148            KindScratchHandle::NormalBorder(h) => h,
149            _ => panic!("kind_scratch mismatch: expected NormalBorder, got {:?}", self),
150        }
151    }
152    pub fn unwrap_image_border(&self) -> storage::Index<ImageBorderScratch> {
153        match *self {
154            KindScratchHandle::ImageBorder(h) => h,
155            _ => panic!("kind_scratch mismatch: expected ImageBorder, got {:?}", self),
156        }
157    }
158    pub fn unwrap_image(&self) -> storage::Index<ImageScratch> {
159        match *self {
160            KindScratchHandle::Image(h) => h,
161            _ => panic!("kind_scratch mismatch: expected Image, got {:?}", self),
162        }
163    }
164    pub fn unwrap_text_run(&self) -> storage::Index<TextRunScratch> {
165        match *self {
166            KindScratchHandle::TextRun(h) => h,
167            _ => panic!("kind_scratch mismatch: expected TextRun, got {:?}", self),
168        }
169    }
170    pub fn unwrap_picture(&self) -> storage::Index<PictureScratch> {
171        match *self {
172            KindScratchHandle::Picture(h) => h,
173            _ => panic!("kind_scratch mismatch: expected Picture, got {:?}", self),
174        }
175    }
176    pub fn unwrap_backdrop_render(&self) -> storage::Index<BackdropRenderScratch> {
177        match *self {
178            KindScratchHandle::BackdropRender(h) => h,
179            _ => panic!("kind_scratch mismatch: expected BackdropRender, got {:?}", self),
180        }
181    }
182}
183
184/// Information stored for a visible primitive about the visible
185/// rect and associated clip information.
186#[derive(Debug, Copy, Clone)]
187#[cfg_attr(feature = "capture", derive(Serialize))]
188pub struct PrimitiveDrawHeader {
189    /// Back-reference to the prim instance this draw belongs to.
190    /// Currently redundant with the identity-indexed lookup from
191    /// `scratch.frame.draws[PrimitiveInstanceIndex.0]`, but reserved
192    /// for a follow-up that switches the storage to push-per-draw —
193    /// readers iterating draws directly will need this to reach the
194    /// instance.
195    pub prim_instance_index: PrimitiveInstanceIndex,
196
197    /// The clip chain instance that was built for this primitive.
198    pub clip_chain: ClipChainInstance,
199
200    /// Current visibility state of the primitive.
201    // TODO(gw): Move more of the fields from this struct into
202    //           the state enum.
203    pub state: DrawState,
204
205    /// An index into the clip task instances array in the primitive
206    /// store. If this is ClipTaskIndex::INVALID, then the primitive
207    /// has no clip mask. Otherwise, it may store the offset of the
208    /// global clip mask task for this primitive, or the first of
209    /// a list of clip task ids (one per segment).
210    pub clip_task_index: ClipTaskIndex,
211
212    /// Per-kind scratch handle for this draw. Variant matches the
213    /// drawn prim's `PrimitiveKind`; `None` for kinds without per-
214    /// frame scratch (e.g. ImageBorder, gradients, BackdropCapture,
215    /// BoxShadow, Rectangle/YuvImage).
216    pub kind_scratch: KindScratchHandle,
217
218    /// Index into PrimitiveFrameScratch.segment_instances for prims
219    /// that opt into segmented brush rendering (Rectangle, YuvImage,
220    /// non-tiled Image). UNUSED for prims that don't segment, or for
221    /// the trivial single-segment case. Built fresh each frame in
222    /// build_segments_if_needed.
223    pub segment_instance_index: SegmentInstanceIndex,
224
225    /// Per-frame compositing decision for Image / YuvImage primitives.
226    /// Set during the visibility pass by tile-cache promotion logic;
227    /// `Blit` for kinds that aren't candidates for compositor surfaces
228    /// or for draws that didn't get promoted this frame.
229    pub compositor_surface_kind: CompositorSurfaceKind,
230
231    /// Local-space rect of the primitive after device-pixel snapping has
232    /// been applied. Populated for every prim each frame by
233    /// `frame_snap::snap_frame_rects` (snapping
234    /// `PrimitiveInstance.unsnapped_prim_rect` against the current spatial
235    /// tree) before any visibility / prepare consumer reads it.
236    pub snapped_local_rect: LayoutRect,
237}
238
239impl PrimitiveDrawHeader {
240    /// Allocate a fresh draw header. `snapped_local_rect` is left at zero
241    /// here; the per-frame snap pass overwrites it before any consumer runs.
242    pub fn new() -> Self {
243        PrimitiveDrawHeader {
244            prim_instance_index: PrimitiveInstanceIndex::INVALID,
245            state: DrawState::Unset,
246            clip_chain: ClipChainInstance::empty(),
247            clip_task_index: ClipTaskIndex::INVALID,
248            kind_scratch: KindScratchHandle::None,
249            segment_instance_index: SegmentInstanceIndex::UNUSED,
250            compositor_surface_kind: CompositorSurfaceKind::Blit,
251            snapped_local_rect: LayoutRect::zero(),
252        }
253    }
254
255    pub fn reset(&mut self) {
256        self.state = DrawState::Culled;
257        self.clip_task_index = ClipTaskIndex::INVALID;
258        self.kind_scratch = KindScratchHandle::None;
259        self.segment_instance_index = SegmentInstanceIndex::UNUSED;
260        self.compositor_surface_kind = CompositorSurfaceKind::Blit;
261    }
262}
263
264pub fn update_prim_visibility(
265    pic_index: PictureIndex,
266    parent_surface_index: Option<SurfaceIndex>,
267    world_culling_rect: &WorldRect,
268    store: &PrimitiveStore,
269    is_root_tile_cache: bool,
270    frame_context: &FrameVisibilityContext,
271    frame_state: &mut FrameVisibilityState,
272    tile_cache: &mut Option<&mut TileCacheInstance>,
273 ) {
274    if frame_state.visited_pictures[pic_index.0] {
275        return;
276    }
277    frame_state.visited_pictures[pic_index.0] = true;
278    let pic = &store.pictures[pic_index.0];
279
280    let (surface_index, pop_surface) = match pic.raster_config {
281        Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { .. }, .. }) => {
282            (surface_index, false)
283        }
284        Some(ref raster_config) => {
285            frame_state.push_surface(
286                pic_index,
287                raster_config.surface_index,
288            );
289
290            if let Some(parent_surface_index) = parent_surface_index {
291                let parent_culling_rect = frame_state
292                    .surfaces[parent_surface_index.0]
293                    .culling_rect;
294
295                let surface = &mut frame_state
296                    .surfaces[raster_config.surface_index.0 as usize];
297
298                surface.update_culling_rect(
299                    parent_culling_rect,
300                    &raster_config.composite_mode,
301                    frame_context,
302                );
303            }
304
305            let surface_local_rect = frame_state.surfaces[raster_config.surface_index.0]
306                .unclipped_local_rect
307                .cast_unit();
308
309            // Let the picture cache know that we are pushing an off-screen
310            // surface, so it can treat dependencies of surface atomically.
311            if let Some(tile_cache) = tile_cache {
312                tile_cache.push_surface(
313                    surface_local_rect,
314                    pic.spatial_node_index,
315                    frame_context.spatial_tree,
316                );
317            }
318
319            (raster_config.surface_index, true)
320        }
321        None => {
322            (parent_surface_index.expect("bug: pass-through with no parent"), false)
323        }
324    };
325
326    let surface = &frame_state.surfaces[surface_index.0 as usize];
327    let surface_culling_rect = surface.culling_rect;
328
329    let mut map_local_to_picture = surface.map_local_to_picture.clone();
330
331    let map_surface_to_vis = SpaceMapper::new_with_target(
332        // TODO: switch from root to raster space.
333        frame_context.root_spatial_node_index,
334        surface.surface_spatial_node_index,
335        surface.culling_rect,
336        frame_context.spatial_tree,
337    );
338    let visibility_spatial_node_index = surface.visibility_spatial_node_index;
339
340    for cluster in &pic.prim_list.clusters {
341        profile_scope!("cluster");
342
343        // Each prim instance must have reset called each frame, to clear
344        // indices into various scratch buffers. If this doesn't occur,
345        // the primitive may incorrectly be considered visible, which can
346        // cause unexpected conditions to occur later during the frame.
347        // Primitive instances are normally reset in the main loop below,
348        // but we must also reset them in the rare case that the cluster
349        // visibility has changed (due to an invalid transform and/or
350        // backface visibility changing for this cluster).
351        // TODO(gw): This is difficult to test for in CI - as a follow up,
352        //           we should add a debug flag that validates the prim
353        //           instance is always reset every frame to catch similar
354        //           issues in future.
355        for idx in cluster.prim_range() {
356            frame_state.scratch.primitive.frame.draws[idx].reset();
357            frame_state.scratch.primitive.frame.draws[idx].prim_instance_index =
358                PrimitiveInstanceIndex(idx as u32);
359        }
360
361        // Get the cluster and see if is visible
362        if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) {
363            continue;
364        }
365
366        map_local_to_picture.set_target_spatial_node(
367            cluster.spatial_node_index,
368            frame_context.spatial_tree,
369        );
370
371        for prim_instance_index in cluster.prim_range() {
372            if let PrimitiveKind::Picture { pic_index, .. } = frame_state.prim_instances[prim_instance_index].kind {
373                if !store.pictures[pic_index.0].is_visible(frame_context.spatial_tree) {
374                    continue;
375                }
376
377                let is_passthrough = match store.pictures[pic_index.0].raster_config {
378                    Some(..) => false,
379                    None => true,
380                };
381
382                if !is_passthrough {
383                    let clip_root = store
384                        .pictures[pic_index.0]
385                        .clip_root
386                        .unwrap_or_else(|| {
387                            // If we couldn't find a common ancestor then just use the
388                            // clip node of the picture primitive itself
389                            let leaf_id = frame_state.prim_instances[prim_instance_index].clip_leaf_id;
390                            frame_state.clip_tree.get_leaf(leaf_id).node_id
391                        }
392                    );
393
394                    frame_state.clip_tree.push_clip_root_node(clip_root);
395                }
396
397                update_prim_visibility(
398                    pic_index,
399                    Some(surface_index),
400                    world_culling_rect,
401                    store,
402                    false,
403                    frame_context,
404                    frame_state,
405                    tile_cache,
406                );
407
408                if is_passthrough {
409                    // Pass through pictures are always considered visible in all dirty tiles.
410                    frame_state.scratch.primitive.frame.draws[prim_instance_index].state = DrawState::PassThrough;
411
412                    continue;
413                } else {
414                    frame_state.clip_tree.pop_clip_root();
415                }
416            }
417
418            let prim_instance = &mut frame_state.prim_instances[prim_instance_index];
419
420            let local_coverage_rect = frame_state.data_stores.get_local_prim_coverage_rect(
421                prim_instance,
422                frame_state.scratch.primitive.frame.draws[prim_instance_index].snapped_local_rect,
423                &store.pictures,
424                frame_state.surfaces,
425            );
426
427            frame_state.clip_store.set_active_clips(
428                cluster.spatial_node_index,
429                map_local_to_picture.ref_spatial_node_index,
430                visibility_spatial_node_index,
431                prim_instance.clip_leaf_id,
432                &frame_context.spatial_tree,
433                &frame_state.data_stores.clip,
434                frame_state.clip_tree,
435            );
436
437            let clip_chain = frame_state
438                .clip_store
439                .build_clip_chain_instance(
440                    local_coverage_rect,
441                    &map_local_to_picture,
442                    &map_surface_to_vis,
443                    &frame_context.spatial_tree,
444                    &mut frame_state.frame_gpu_data.f32,
445                    frame_state.resource_cache,
446                    &surface_culling_rect,
447                    &mut frame_state.data_stores.clip,
448                    frame_state.rg_builder,
449                    true,
450                );
451
452            frame_state.scratch.primitive.frame.draws[prim_instance_index].clip_chain = match clip_chain {
453                Some(clip_chain) => clip_chain,
454                None => {
455                    continue;
456                }
457            };
458
459            {
460                let prim_surface_index = frame_state.surface_stack.last().unwrap().1;
461                let prim_clip_chain = &frame_state.scratch.primitive.frame.draws[prim_instance_index].clip_chain;
462
463                // Accumulate the exact (clipped) local rect into the parent surface.
464                let surface = &mut frame_state.surfaces[prim_surface_index.0];
465                surface.clipped_local_rect = surface.clipped_local_rect.union(&prim_clip_chain.pic_coverage_rect);
466            }
467
468            let new_state = match tile_cache {
469                Some(tile_cache) => {
470                    tile_cache.update_prim_dependencies(
471                        PrimitiveInstanceIndex(prim_instance_index as u32),
472                        prim_instance,
473                        cluster.spatial_node_index,
474                        // It's OK to pass the local_coverage_rect here as it's only
475                        // used by primitives (for compositor surfaces) that don't
476                        // have inflation anyway.
477                        local_coverage_rect,
478                        frame_context,
479                        frame_state.data_stores,
480                        frame_state.clip_store,
481                        &store.pictures,
482                        frame_state.resource_cache,
483                        &frame_state.surface_stack,
484                        &mut frame_state.composite_state,
485                        &mut frame_state.frame_gpu_data.f32,
486                        &mut frame_state.scratch.primitive,
487                        is_root_tile_cache,
488                        frame_state.surfaces,
489                        frame_state.profile,
490                    )
491                }
492                None => {
493                    DrawState::Visible {
494                        vis_flags: PrimitiveVisibilityFlags::empty(),
495                        sub_slice_index: SubSliceIndex::DEFAULT,
496                    }
497                }
498            };
499            frame_state.scratch.primitive.frame.draws[prim_instance_index].state = new_state;
500        }
501    }
502
503    if let Some(snapshot) = &pic.snapshot {
504        if snapshot.detached {
505            // If the snapshot is detached, then the contents of the stacking
506            // context will only be shown via the snapshot, so there is no point
507            // to rendering anything outside of the snapshot area.
508            let prim_surface_index = frame_state.surface_stack.last().unwrap().1;
509            let surface = &mut frame_state.surfaces[prim_surface_index.0];
510            let clip = snapshot.area.round_out().cast_unit();
511            surface.clipped_local_rect = surface.clipped_local_rect.intersection_unchecked(&clip);
512        }
513    }
514
515    if pop_surface {
516        frame_state.pop_surface();
517    }
518
519    if let Some(ref rc) = pic.raster_config {
520        if let Some(tile_cache) = tile_cache {
521            match rc.composite_mode {
522                PictureCompositeMode::TileCache { .. } => {}
523                _ => {
524                    // Pop the off-screen surface from the picture cache stack
525                    tile_cache.pop_surface();
526                }
527            }
528        }
529    }
530}
531
532pub fn compute_conservative_visible_rect(
533    clip_chain: &ClipChainInstance,
534    culling_rect: VisRect,
535    visibility_node_index: SpatialNodeIndex,
536    prim_spatial_node_index: SpatialNodeIndex,
537    spatial_tree: &SpatialTree,
538) -> LayoutRect {
539    // Mapping from picture space -> world space
540    let map_pic_to_vis: SpaceMapper<PicturePixel, VisPixel> = SpaceMapper::new_with_target(
541        visibility_node_index,
542        clip_chain.pic_spatial_node_index,
543        culling_rect,
544        spatial_tree,
545    );
546
547    // Mapping from local space -> picture space
548    let map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel> = SpaceMapper::new_with_target(
549        clip_chain.pic_spatial_node_index,
550        prim_spatial_node_index,
551        PictureRect::max_rect(),
552        spatial_tree,
553    );
554
555    // Unmap the world culling rect from world -> picture space. If this mapping fails due
556    // to matrix weirdness, best we can do is use the clip chain's local clip rect.
557    let pic_culling_rect = match map_pic_to_vis.unmap(&culling_rect) {
558        Some(rect) => rect,
559        None => return clip_chain.local_clip_rect,
560    };
561
562    // Intersect the unmapped world culling rect with the primitive's clip chain rect that
563    // is in picture space (the clip-chain already takes into account the bounds of the
564    // primitive local_rect and local_clip_rect). If there is no intersection here, the
565    // primitive is not visible at all.
566    let pic_culling_rect = match pic_culling_rect.intersection(&clip_chain.pic_coverage_rect) {
567        Some(rect) => rect,
568        None => return LayoutRect::zero(),
569    };
570
571    // Unmap the picture culling rect from picture -> local space. If this mapping fails due
572    // to matrix weirdness, best we can do is use the clip chain's local clip rect.
573    match map_local_to_pic.unmap(&pic_culling_rect) {
574        Some(rect) => rect,
575        None => clip_chain.local_clip_rect,
576    }
577}