tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

visibility.rs (17887B)


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