summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/visibility.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /gfx/wr/webrender/src/visibility.rs
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/wr/webrender/src/visibility.rs')
-rw-r--r--gfx/wr/webrender/src/visibility.rs385
1 files changed, 385 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/visibility.rs b/gfx/wr/webrender/src/visibility.rs
new file mode 100644
index 0000000000..fb1fee2944
--- /dev/null
+++ b/gfx/wr/webrender/src/visibility.rs
@@ -0,0 +1,385 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Visibility pass
+//!
+//! TODO: document what this pass does!
+//!
+
+use api::{DebugFlags};
+use api::units::*;
+use std::{usize};
+use crate::clip::ClipStore;
+use crate::composite::CompositeState;
+use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
+use crate::clip::{ClipChainInstance, ClipTree};
+use crate::frame_builder::FrameBuilderConfig;
+use crate::gpu_cache::GpuCache;
+use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo, TileCacheInstance};
+use crate::picture::{SurfaceIndex, RasterConfig, TileRect, SubSliceIndex};
+use crate::prim_store::{ClipTaskIndex, PictureIndex, PrimitiveInstanceKind};
+use crate::prim_store::{PrimitiveStore, PrimitiveInstance};
+use crate::render_backend::{DataStores, ScratchBuffer};
+use crate::resource_cache::ResourceCache;
+use crate::scene::SceneProperties;
+use crate::space::SpaceMapper;
+use crate::util::{MaxRect};
+
+pub struct FrameVisibilityContext<'a> {
+ pub spatial_tree: &'a SpatialTree,
+ pub global_screen_world_rect: WorldRect,
+ pub global_device_pixel_scale: DevicePixelScale,
+ pub debug_flags: DebugFlags,
+ pub scene_properties: &'a SceneProperties,
+ pub config: FrameBuilderConfig,
+ pub root_spatial_node_index: SpatialNodeIndex,
+}
+
+pub struct FrameVisibilityState<'a> {
+ pub clip_store: &'a mut ClipStore,
+ pub resource_cache: &'a mut ResourceCache,
+ pub gpu_cache: &'a mut GpuCache,
+ pub scratch: &'a mut ScratchBuffer,
+ pub data_stores: &'a mut DataStores,
+ pub clip_tree: &'a mut ClipTree,
+ pub composite_state: &'a mut CompositeState,
+ /// A stack of currently active off-screen surfaces during the
+ /// visibility frame traversal.
+ pub surface_stack: Vec<(PictureIndex, SurfaceIndex)>,
+}
+
+impl<'a> FrameVisibilityState<'a> {
+ pub fn push_surface(
+ &mut self,
+ pic_index: PictureIndex,
+ surface_index: SurfaceIndex,
+ ) {
+ self.surface_stack.push((pic_index, surface_index));
+ }
+
+ pub fn pop_surface(&mut self) {
+ self.surface_stack.pop().unwrap();
+ }
+}
+
+bitflags! {
+ /// A set of bitflags that can be set in the visibility information
+ /// for a primitive instance. This can be used to control how primitives
+ /// are treated during batching.
+ // TODO(gw): We should also move `is_compositor_surface` to be part of
+ // this flags struct.
+ #[cfg_attr(feature = "capture", derive(Serialize))]
+ pub struct PrimitiveVisibilityFlags: u8 {
+ /// Implies that this primitive covers the entire picture cache slice,
+ /// and can thus be dropped during batching and drawn with clear color.
+ const IS_BACKDROP = 1;
+ }
+}
+
+/// Contains the current state of the primitive's visibility.
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub enum VisibilityState {
+ /// Uninitialized - this should never be encountered after prim reset
+ Unset,
+ /// Culled for being off-screen, or not possible to render (e.g. missing image resource)
+ Culled,
+ /// A picture that doesn't have a surface - primitives are composed into the
+ /// parent picture with a surface.
+ PassThrough,
+ /// A primitive that has been found to be visible
+ Visible {
+ /// A set of flags that define how this primitive should be handled
+ /// during batching of visible primitives.
+ vis_flags: PrimitiveVisibilityFlags,
+
+ /// Tiles that this primitive intersects with
+ tile_rect: TileRect,
+
+ /// Sub-slice within the picture cache that this prim exists on
+ sub_slice_index: SubSliceIndex,
+ },
+}
+
+/// Information stored for a visible primitive about the visible
+/// rect and associated clip information.
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub struct PrimitiveVisibility {
+ /// The clip chain instance that was built for this primitive.
+ pub clip_chain: ClipChainInstance,
+
+ /// Current visibility state of the primitive.
+ // TODO(gw): Move more of the fields from this struct into
+ // the state enum.
+ pub state: VisibilityState,
+
+ /// An index into the clip task instances array in the primitive
+ /// store. If this is ClipTaskIndex::INVALID, then the primitive
+ /// has no clip mask. Otherwise, it may store the offset of the
+ /// global clip mask task for this primitive, or the first of
+ /// a list of clip task ids (one per segment).
+ pub clip_task_index: ClipTaskIndex,
+}
+
+impl PrimitiveVisibility {
+ pub fn new() -> Self {
+ PrimitiveVisibility {
+ state: VisibilityState::Unset,
+ clip_chain: ClipChainInstance::empty(),
+ clip_task_index: ClipTaskIndex::INVALID,
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.state = VisibilityState::Culled;
+ self.clip_task_index = ClipTaskIndex::INVALID;
+ }
+}
+
+pub fn update_prim_visibility(
+ pic_index: PictureIndex,
+ parent_surface_index: Option<SurfaceIndex>,
+ world_culling_rect: &WorldRect,
+ store: &PrimitiveStore,
+ prim_instances: &mut [PrimitiveInstance],
+ surfaces: &mut [SurfaceInfo],
+ is_root_tile_cache: bool,
+ frame_context: &FrameVisibilityContext,
+ frame_state: &mut FrameVisibilityState,
+ tile_cache: &mut TileCacheInstance,
+ ) {
+ let pic = &store.pictures[pic_index.0];
+
+ let (surface_index, pop_surface) = match pic.raster_config {
+ Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { .. }, .. }) => {
+ (surface_index, false)
+ }
+ Some(ref raster_config) => {
+ frame_state.push_surface(
+ pic_index,
+ raster_config.surface_index,
+ );
+
+ let surface_local_rect = surfaces[raster_config.surface_index.0]
+ .unclipped_local_rect
+ .cast_unit();
+
+ // Let the picture cache know that we are pushing an off-screen
+ // surface, so it can treat dependencies of surface atomically.
+ tile_cache.push_surface(
+ surface_local_rect,
+ pic.spatial_node_index,
+ frame_context.spatial_tree,
+ );
+
+ (raster_config.surface_index, true)
+ }
+ None => {
+ (parent_surface_index.expect("bug: pass-through with no parent"), false)
+ }
+ };
+
+ let surface = &surfaces[surface_index.0 as usize];
+ let device_pixel_scale = surface.device_pixel_scale;
+ let mut map_local_to_surface = surface.map_local_to_surface.clone();
+ let map_surface_to_world = SpaceMapper::new_with_target(
+ frame_context.root_spatial_node_index,
+ surface.surface_spatial_node_index,
+ frame_context.global_screen_world_rect,
+ frame_context.spatial_tree,
+ );
+
+ for cluster in &pic.prim_list.clusters {
+ profile_scope!("cluster");
+
+ // Each prim instance must have reset called each frame, to clear
+ // indices into various scratch buffers. If this doesn't occur,
+ // the primitive may incorrectly be considered visible, which can
+ // cause unexpected conditions to occur later during the frame.
+ // Primitive instances are normally reset in the main loop below,
+ // but we must also reset them in the rare case that the cluster
+ // visibility has changed (due to an invalid transform and/or
+ // backface visibility changing for this cluster).
+ // TODO(gw): This is difficult to test for in CI - as a follow up,
+ // we should add a debug flag that validates the prim
+ // instance is always reset every frame to catch similar
+ // issues in future.
+ for prim_instance in &mut prim_instances[cluster.prim_range()] {
+ prim_instance.reset();
+ }
+
+ // Get the cluster and see if is visible
+ if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) {
+ continue;
+ }
+
+ map_local_to_surface.set_target_spatial_node(
+ cluster.spatial_node_index,
+ frame_context.spatial_tree,
+ );
+
+ for prim_instance_index in cluster.prim_range() {
+ if let PrimitiveInstanceKind::Picture { pic_index, .. } = prim_instances[prim_instance_index].kind {
+ if !store.pictures[pic_index.0].is_visible(frame_context.spatial_tree) {
+ continue;
+ }
+
+ let is_passthrough = match store.pictures[pic_index.0].raster_config {
+ Some(..) => false,
+ None => true,
+ };
+
+ if !is_passthrough {
+ frame_state.clip_tree.push_clip_root_leaf(
+ prim_instances[prim_instance_index].clip_leaf_id,
+ );
+ }
+
+ update_prim_visibility(
+ pic_index,
+ Some(surface_index),
+ world_culling_rect,
+ store,
+ prim_instances,
+ surfaces,
+ false,
+ frame_context,
+ frame_state,
+ tile_cache,
+ );
+
+ if is_passthrough {
+ // Pass through pictures are always considered visible in all dirty tiles.
+ prim_instances[prim_instance_index].vis.state = VisibilityState::PassThrough;
+
+ continue;
+ } else {
+ frame_state.clip_tree.pop_clip_root();
+ }
+ }
+
+ let prim_instance = &mut prim_instances[prim_instance_index];
+
+ let local_coverage_rect = frame_state.data_stores.get_local_prim_coverage_rect(
+ prim_instance,
+ &store.pictures,
+ surfaces,
+ );
+
+ frame_state.clip_store.set_active_clips(
+ cluster.spatial_node_index,
+ map_local_to_surface.ref_spatial_node_index,
+ prim_instance.clip_leaf_id,
+ &frame_context.spatial_tree,
+ &frame_state.data_stores.clip,
+ frame_state.clip_tree,
+ );
+
+ let clip_chain = frame_state
+ .clip_store
+ .build_clip_chain_instance(
+ local_coverage_rect,
+ &map_local_to_surface,
+ &map_surface_to_world,
+ &frame_context.spatial_tree,
+ frame_state.gpu_cache,
+ frame_state.resource_cache,
+ device_pixel_scale,
+ &world_culling_rect,
+ &mut frame_state.data_stores.clip,
+ true,
+ );
+
+ prim_instance.vis.clip_chain = match clip_chain {
+ Some(clip_chain) => clip_chain,
+ None => {
+ continue;
+ }
+ };
+
+ tile_cache.update_prim_dependencies(
+ prim_instance,
+ cluster.spatial_node_index,
+ // It's OK to pass the local_coverage_rect here as it's only used by primitives
+ // (for compositor surfaces) that don't have inflation anyway.
+ local_coverage_rect,
+ frame_context,
+ frame_state.data_stores,
+ frame_state.clip_store,
+ &store.pictures,
+ frame_state.resource_cache,
+ &store.color_bindings,
+ &frame_state.surface_stack,
+ &mut frame_state.composite_state,
+ &mut frame_state.gpu_cache,
+ &mut frame_state.scratch.primitive,
+ is_root_tile_cache,
+ surfaces,
+ );
+ }
+ }
+
+ if pop_surface {
+ frame_state.pop_surface();
+ }
+
+ if let Some(ref rc) = pic.raster_config {
+ match rc.composite_mode {
+ PictureCompositeMode::TileCache { .. } => {}
+ _ => {
+ // Pop the off-screen surface from the picture cache stack
+ tile_cache.pop_surface();
+ }
+ }
+ }
+}
+
+pub fn compute_conservative_visible_rect(
+ clip_chain: &ClipChainInstance,
+ world_culling_rect: WorldRect,
+ prim_spatial_node_index: SpatialNodeIndex,
+ spatial_tree: &SpatialTree,
+) -> LayoutRect {
+ let root_spatial_node_index = spatial_tree.root_reference_frame_index();
+
+ // Mapping from picture space -> world space
+ let map_pic_to_world: SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
+ root_spatial_node_index,
+ clip_chain.pic_spatial_node_index,
+ world_culling_rect,
+ spatial_tree,
+ );
+
+ // Mapping from local space -> picture space
+ let map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel> = SpaceMapper::new_with_target(
+ clip_chain.pic_spatial_node_index,
+ prim_spatial_node_index,
+ PictureRect::max_rect(),
+ spatial_tree,
+ );
+
+ // Unmap the world culling rect from world -> picture space. If this mapping fails due
+ // to matrix weirdness, best we can do is use the clip chain's local clip rect.
+ let pic_culling_rect = match map_pic_to_world.unmap(&world_culling_rect) {
+ Some(rect) => rect,
+ None => return clip_chain.local_clip_rect,
+ };
+
+ // Intersect the unmapped world culling rect with the primitive's clip chain rect that
+ // is in picture space (the clip-chain already takes into account the bounds of the
+ // primitive local_rect and local_clip_rect). If there is no intersection here, the
+ // primitive is not visible at all.
+ let pic_culling_rect = match pic_culling_rect.intersection(&clip_chain.pic_coverage_rect) {
+ Some(rect) => rect,
+ None => return LayoutRect::zero(),
+ };
+
+ // Unmap the picture culling rect from picture -> local space. If this mapping fails due
+ // to matrix weirdness, best we can do is use the clip chain's local clip rect.
+ match map_local_to_pic.unmap(&pic_culling_rect) {
+ Some(rect) => rect,
+ None => clip_chain.local_clip_rect,
+ }
+}