commit 456ecdfabef1693737fce2795682bb1ca4dce3c4
parent 6c9c4294f1c5e8c1638e315c2dc92913615f6c3f
Author: Nicolas Silva <nical@fastmail.com>
Date: Fri, 12 Dec 2025 08:00:20 +0000
Bug 1998913 - Part 1 - Extract independent data structures. r=gfx-reviewers,gw
Differential Revision: https://phabricator.services.mozilla.com/D275936
Diffstat:
13 files changed, 1199 insertions(+), 1131 deletions(-)
diff --git a/gfx/wr/webrender/src/command_buffer.rs b/gfx/wr/webrender/src/command_buffer.rs
@@ -4,7 +4,7 @@
use api::units::PictureRect;
use crate::pattern::{PatternKind, PatternShaderInput};
-use crate::{spatial_tree::SpatialNodeIndex, render_task_graph::RenderTaskId, surface::SurfaceTileDescriptor, picture::TileKey, renderer::GpuBufferAddress, FastHashMap, prim_store::PrimitiveInstanceIndex};
+use crate::{spatial_tree::SpatialNodeIndex, render_task_graph::RenderTaskId, surface::SurfaceTileDescriptor, tile_cache::TileKey, renderer::GpuBufferAddress, FastHashMap, prim_store::PrimitiveInstanceIndex};
use crate::gpu_types::{QuadSegment, TransformPaletteId};
use crate::segment::EdgeAaSegmentMask;
diff --git a/gfx/wr/webrender/src/composite.rs b/gfx/wr/webrender/src/composite.rs
@@ -10,7 +10,8 @@ use crate::renderer::GpuBufferBuilderF;
use euclid::Box2D;
use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
use crate::internal_types::{FrameAllocator, FrameMemory, FrameVec, TextureSource};
-use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileId, TileSurface};
+use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileSurface};
+use crate::tile_cache::TileId;
use crate::prim_store::DeferredResolve;
use crate::resource_cache::{ImageRequest, ResourceCache};
use crate::util::{extract_inner_rect_safe, Preallocator, ScaleOffset};
diff --git a/gfx/wr/webrender/src/invalidation/mod.rs b/gfx/wr/webrender/src/invalidation/mod.rs
@@ -0,0 +1,155 @@
+/* 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/. */
+
+//! Invalidation tracking and dirty region management
+//!
+//! This module contains types and logic for tracking dirty regions and
+//! dependencies used to determine what needs to be redrawn each frame.
+
+use api::units::*;
+use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
+use crate::space::SpaceMapper;
+use crate::util::MaxRect;
+
+/// Represents the dirty region of a tile cache picture, relative to a
+/// "visibility" spatial node. At the moment the visibility node is
+/// world space, but the plan is to switch to raster space.
+///
+/// The plan is to move away from these world space representation and
+/// compute dirty regions in raster space instead.
+#[derive(Clone)]
+pub struct DirtyRegion {
+ /// The overall dirty rect, a combination of dirty_rects
+ pub combined: VisRect,
+
+ /// The corrdinate space used to do clipping, visibility, and
+ /// dirty rect calculations.
+ pub visibility_spatial_node: SpatialNodeIndex,
+ /// Spatial node of the picture this region represents.
+ local_spatial_node: SpatialNodeIndex,
+}
+
+impl DirtyRegion {
+ /// Construct a new dirty region tracker.
+ pub fn new(
+ visibility_spatial_node: SpatialNodeIndex,
+ local_spatial_node: SpatialNodeIndex,
+ ) -> Self {
+ DirtyRegion {
+ combined: VisRect::zero(),
+ visibility_spatial_node,
+ local_spatial_node,
+ }
+ }
+
+ /// Reset the dirty regions back to empty
+ pub fn reset(
+ &mut self,
+ visibility_spatial_node: SpatialNodeIndex,
+ local_spatial_node: SpatialNodeIndex,
+ ) {
+ self.combined = VisRect::zero();
+ self.visibility_spatial_node = visibility_spatial_node;
+ self.local_spatial_node = local_spatial_node;
+ }
+
+ /// Add a dirty region to the tracker. Returns the visibility mask that corresponds to
+ /// this region in the tracker.
+ pub fn add_dirty_region(
+ &mut self,
+ rect_in_pic_space: PictureRect,
+ spatial_tree: &SpatialTree,
+ ) {
+ let map_pic_to_raster = SpaceMapper::new_with_target(
+ self.visibility_spatial_node,
+ self.local_spatial_node,
+ VisRect::max_rect(),
+ spatial_tree,
+ );
+
+ let raster_rect = map_pic_to_raster
+ .map(&rect_in_pic_space)
+ .expect("bug");
+
+ // Include this in the overall dirty rect
+ self.combined = self.combined.union(&raster_rect);
+ }
+}
+
+/// Debugging information about why a tile was invalidated
+#[derive(Debug,Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum InvalidationReason {
+ /// The background color changed
+ BackgroundColor,
+ /// The opaque state of the backing native surface changed
+ SurfaceOpacityChanged,
+ /// There was no backing texture (evicted or never rendered)
+ NoTexture,
+ /// There was no backing native surface (never rendered, or recreated)
+ NoSurface,
+ /// The primitive count in the dependency list was different
+ PrimCount,
+ /// The content of one of the primitives was different
+ Content,
+ // The compositor type changed
+ CompositorKindChanged,
+ // The valid region of the tile changed
+ ValidRectChanged,
+ // The overall scale of the picture cache changed
+ ScaleChanged,
+ // The content of the sampling surface changed
+ SurfaceContentChanged,
+}
+
+/// The result of a primitive dependency comparison. Size is a u8
+/// since this is a hot path in the code, and keeping the data small
+/// is a performance win.
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(u8)]
+pub enum PrimitiveCompareResult {
+ /// Primitives match
+ Equal,
+ /// Something in the PrimitiveDescriptor was different
+ Descriptor,
+ /// The clip node content or spatial node changed
+ Clip,
+ /// The value of the transform changed
+ Transform,
+ /// An image dependency was dirty
+ Image,
+ /// The value of an opacity binding changed
+ OpacityBinding,
+ /// The value of a color binding changed
+ ColorBinding,
+}
+
+/// Optional extra information returned by is_same when
+/// logging is enabled.
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum CompareHelperResult<T> {
+ /// Primitives match
+ Equal,
+ /// Counts differ
+ Count {
+ prev_count: u8,
+ curr_count: u8,
+ },
+ /// Sentinel
+ Sentinel,
+ /// Two items are not equal
+ NotEqual {
+ prev: T,
+ curr: T,
+ },
+ /// User callback returned true on item
+ PredicateTrue {
+ curr: T
+ },
+}
diff --git a/gfx/wr/webrender/src/lib.rs b/gfx/wr/webrender/src/lib.rs
@@ -109,6 +109,7 @@ mod lru_cache;
mod pattern;
mod picture;
mod picture_graph;
+mod invalidation;
mod prepare;
mod prim_store;
mod print_tree;
@@ -191,9 +192,11 @@ pub use crate::screen_capture::{AsyncScreenshotHandle, RecordedFrameHandle};
pub use crate::texture_cache::TextureCacheConfig;
pub use api as webrender_api;
pub use webrender_build::shader::{ProgramSourceDigest, ShaderKind};
-pub use crate::picture::{TileDescriptor, TileId, InvalidationReason};
-pub use crate::picture::{PrimitiveCompareResult, CompareHelperResult};
-pub use crate::picture::{TileNode, TileNodeKind, TileOffset};
+pub use crate::tile_cache::{TileDescriptor, TileId};
+pub use crate::invalidation::InvalidationReason;
+pub use crate::invalidation::{PrimitiveCompareResult, CompareHelperResult};
+pub use crate::picture::{TileNode, TileNodeKind};
+pub use crate::tile_cache::TileOffset;
pub use crate::intern::ItemUid;
pub use crate::render_api::*;
pub use crate::tile_cache::{PictureCacheDebugInfo, DirtyTileDebugInfo, TileDebugInfo, SliceDebugInfo};
diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs
@@ -109,7 +109,7 @@ use crate::composite::{tile_kind, CompositeState, CompositeTileSurface, Composit
use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency, CompositeTileDescriptor, CompositeTile};
use crate::composite::{CompositorTransformIndex, CompositorSurfaceKind};
use crate::debug_colors;
-use euclid::{vec3, Point2D, Scale, Vector2D, Box2D};
+use euclid::{vec3, Scale, Vector2D, Box2D};
use euclid::approxeq::ApproxEq;
use crate::intern::ItemUid;
use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, FilterGraphOp, FilterGraphNode, Filter, FrameId};
@@ -133,20 +133,28 @@ use crate::scene::SceneProperties;
use crate::spatial_tree::CoordinateSystemId;
use crate::surface::{SurfaceDescriptor, SurfaceTileDescriptor};
use smallvec::SmallVec;
-use std::{mem, u8, marker, u32};
+use std::{mem, u8, u32};
use std::fmt::{Display, Error, Formatter};
-use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::hash_map::Entry;
use std::ops::Range;
use crate::picture_textures::PictureCacheTextureHandle;
use crate::util::{MaxRect, VecHelper, MatrixHelpers, Recycler, ScaleOffset};
use crate::filterdata::FilterDataHandle;
use crate::tile_cache::{SliceDebugInfo, TileDebugInfo, DirtyTileDebugInfo};
+use crate::tile_cache::{TileKey, TileId, TileRect, TileOffset, SubSliceIndex};
+use crate::invalidation::InvalidationReason;
+use crate::tile_cache::{MAX_SURFACE_SIZE, MAX_COMPOSITOR_SURFACES};
+use crate::tile_cache::{TileDescriptor, PrimitiveDescriptor, PrimitiveDependencyIndex};
+use crate::tile_cache::{TILE_SIZE_SCROLLBAR_VERTICAL, TILE_SIZE_SCROLLBAR_HORIZONTAL};
use crate::visibility::{PrimitiveVisibilityFlags, FrameVisibilityContext};
use crate::visibility::{VisibilityState, FrameVisibilityState};
use crate::scene_building::SliceFlags;
use core::time::Duration;
+pub use crate::invalidation::DirtyRegion;
+
+use crate::invalidation::PrimitiveCompareResult;
+
// Maximum blur radius for blur filter (different than box-shadow blur).
// Taken from FilterNodeSoftware.cpp in Gecko.
const MAX_BLUR_RADIUS: f32 = 100.;
@@ -249,51 +257,9 @@ impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
}
}
-/// Unit for tile coordinates.
-#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
-pub struct TileCoordinate;
-
-// Geometry types for tile coordinates.
-pub type TileOffset = Point2D<i32, TileCoordinate>;
-pub type TileRect = Box2D<i32, TileCoordinate>;
-
-/// The maximum number of compositor surfaces that are allowed per picture cache. This
-/// is an arbitrary number that should be enough for common cases, but low enough to
-/// prevent performance and memory usage drastically degrading in pathological cases.
-pub const MAX_COMPOSITOR_SURFACES: usize = 4;
-
-/// The size in device pixels of a normal cached tile.
-pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize {
- width: 1024,
- height: 512,
- _unit: marker::PhantomData,
-};
-
-/// The size in device pixels of a tile for horizontal scroll bars
-pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize {
- width: 1024,
- height: 32,
- _unit: marker::PhantomData,
-};
-
-/// The size in device pixels of a tile for vertical scroll bars
-pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
- width: 32,
- height: 1024,
- _unit: marker::PhantomData,
-};
-
-/// The maximum size per axis of a surface, in DevicePixel coordinates.
-/// Render tasks larger than this size are scaled down to fit, which may cause
-/// some blurriness.
-pub const MAX_SURFACE_SIZE: usize = 4096;
/// Maximum size of a compositor surface.
const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0;
-/// Used to get unique tile IDs, even when the tile cache is
-/// destroyed between display lists / scenes.
-static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
-
fn clamp(value: i32, low: i32, high: i32) -> i32 {
value.max(low).min(high)
}
@@ -302,12 +268,6 @@ fn clampf(value: f32, low: f32, high: f32) -> f32 {
value.max(low).min(high)
}
-/// An index into the prims array in a TileDescriptor.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PrimitiveDependencyIndex(pub u32);
-
/// Information about the state of a binding.
#[derive(Debug)]
pub struct BindingInfo<T> {
@@ -590,25 +550,6 @@ impl PrimitiveDependencyInfo {
}
}
-/// A stable ID for a given tile, to help debugging. These are also used
-/// as unique identifiers for tile surfaces when using a native compositor.
-#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Hash)]
-pub struct TileId(pub usize);
-
-/// Uniquely identifies a tile within a picture cache slice
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
-pub struct TileKey {
- // Tile index (x,y)
- pub tile_offset: TileOffset,
- // Sub-slice (z)
- pub sub_slice_index: SubSliceIndex,
-}
-
/// A descriptor for the kind of texture that a picture cache tile will
/// be drawn into.
#[derive(Debug)]
@@ -691,83 +632,6 @@ impl TileSurface {
}
}
-/// Optional extra information returned by is_same when
-/// logging is enabled.
-#[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum CompareHelperResult<T> {
- /// Primitives match
- Equal,
- /// Counts differ
- Count {
- prev_count: u8,
- curr_count: u8,
- },
- /// Sentinel
- Sentinel,
- /// Two items are not equal
- NotEqual {
- prev: T,
- curr: T,
- },
- /// User callback returned true on item
- PredicateTrue {
- curr: T
- },
-}
-
-/// The result of a primitive dependency comparison. Size is a u8
-/// since this is a hot path in the code, and keeping the data small
-/// is a performance win.
-#[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[repr(u8)]
-pub enum PrimitiveCompareResult {
- /// Primitives match
- Equal,
- /// Something in the PrimitiveDescriptor was different
- Descriptor,
- /// The clip node content or spatial node changed
- Clip,
- /// The value of the transform changed
- Transform,
- /// An image dependency was dirty
- Image,
- /// The value of an opacity binding changed
- OpacityBinding,
- /// The value of a color binding changed
- ColorBinding,
-}
-
-/// Debugging information about why a tile was invalidated
-#[derive(Debug,Clone)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum InvalidationReason {
- /// The background color changed
- BackgroundColor,
- /// The opaque state of the backing native surface changed
- SurfaceOpacityChanged,
- /// There was no backing texture (evicted or never rendered)
- NoTexture,
- /// There was no backing native surface (never rendered, or recreated)
- NoSurface,
- /// The primitive count in the dependency list was different
- PrimCount,
- /// The content of one of the primitives was different
- Content,
- // The compositor type changed
- CompositorKindChanged,
- // The valid region of the tile changed
- ValidRectChanged,
- // The overall scale of the picture cache changed
- ScaleChanged,
- // The content of the sampling surface changed
- SurfaceContentChanged,
-}
-
/// Information about a cached tile.
pub struct Tile {
/// The grid position of this tile within the picture cache
@@ -822,7 +686,7 @@ pub struct Tile {
impl Tile {
/// Construct a new, invalid tile.
fn new(tile_offset: TileOffset) -> Self {
- let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
+ let id = TileId(crate::tile_cache::next_tile_id());
Tile {
tile_offset,
@@ -1366,173 +1230,6 @@ impl Tile {
}
}
-/// Defines a key that uniquely identifies a primitive instance.
-#[derive(Debug, Clone)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PrimitiveDescriptor {
- pub prim_uid: ItemUid,
- pub prim_clip_box: PictureBox2D,
- // TODO(gw): These two fields could be packed as a u24/u8
- pub dep_offset: u32,
- pub dep_count: u32,
-}
-
-impl PartialEq for PrimitiveDescriptor {
- fn eq(&self, other: &Self) -> bool {
- const EPSILON: f32 = 0.001;
-
- if self.prim_uid != other.prim_uid {
- return false;
- }
-
- if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) {
- return false;
- }
- if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) {
- return false;
- }
- if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) {
- return false;
- }
- if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) {
- return false;
- }
-
- if self.dep_count != other.dep_count {
- return false;
- }
-
- true
- }
-}
-
-/// Uniquely describes the content of this tile, in a way that can be
-/// (reasonably) efficiently hashed and compared.
-#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct TileDescriptor {
- /// List of primitive instance unique identifiers. The uid is guaranteed
- /// to uniquely describe the content of the primitive template, while
- /// the other parameters describe the clip chain and instance params.
- prims: Vec<PrimitiveDescriptor>,
-
- /// Picture space rect that contains valid pixels region of this tile.
- pub local_valid_rect: PictureRect,
-
- /// The last frame this tile had its dependencies updated (dependency updating is
- /// skipped if a tile is off-screen).
- last_updated_frame_id: FrameId,
-
- /// Packed per-prim dependency information
- dep_data: Vec<u8>,
-}
-
-impl TileDescriptor {
- fn new() -> Self {
- TileDescriptor {
- local_valid_rect: PictureRect::zero(),
- dep_data: Vec::new(),
- prims: Vec::new(),
- last_updated_frame_id: FrameId::INVALID,
- }
- }
-
- /// Print debug information about this tile descriptor to a tree printer.
- fn print(&self, pt: &mut dyn PrintTreePrinter) {
- pt.new_level("current_descriptor".to_string());
-
- pt.new_level("prims".to_string());
- for prim in &self.prims {
- pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
- pt.add_item(format!("clip: p0={},{} p1={},{}",
- prim.prim_clip_box.min.x,
- prim.prim_clip_box.min.y,
- prim.prim_clip_box.max.x,
- prim.prim_clip_box.max.y,
- ));
- pt.end_level();
- }
- pt.end_level();
-
- pt.end_level();
- }
-
- /// Clear the dependency information for a tile, when the dependencies
- /// are being rebuilt.
- fn clear(&mut self) {
- self.local_valid_rect = PictureRect::zero();
- self.prims.clear();
- self.dep_data.clear();
- }
-}
-
-/// Represents the dirty region of a tile cache picture, relative to a
-/// "visibility" spatial node. At the moment the visibility node is
-/// world space, but the plan is to switch to raster space.
-///
-/// The plan is to move away from these world space representation and
-/// compute dirty regions in raster space instead.
-#[derive(Clone)]
-pub struct DirtyRegion {
- /// The overall dirty rect, a combination of dirty_rects
- pub combined: VisRect,
-
- /// The corrdinate space used to do clipping, visibility, and
- /// dirty rect calculations.
- pub visibility_spatial_node: SpatialNodeIndex,
- /// Spatial node of the picture this region represents.
- local_spatial_node: SpatialNodeIndex,
-}
-
-impl DirtyRegion {
- /// Construct a new dirty region tracker.
- pub fn new(
- visibility_spatial_node: SpatialNodeIndex,
- local_spatial_node: SpatialNodeIndex,
- ) -> Self {
- DirtyRegion {
- combined: VisRect::zero(),
- visibility_spatial_node,
- local_spatial_node,
- }
- }
-
- /// Reset the dirty regions back to empty
- pub fn reset(
- &mut self,
- visibility_spatial_node: SpatialNodeIndex,
- local_spatial_node: SpatialNodeIndex,
- ) {
- self.combined = VisRect::zero();
- self.visibility_spatial_node = visibility_spatial_node;
- self.local_spatial_node = local_spatial_node;
- }
-
- /// Add a dirty region to the tracker. Returns the visibility mask that corresponds to
- /// this region in the tracker.
- pub fn add_dirty_region(
- &mut self,
- rect_in_pic_space: PictureRect,
- spatial_tree: &SpatialTree,
- ) {
- let map_pic_to_raster = SpaceMapper::new_with_target(
- self.visibility_spatial_node,
- self.local_spatial_node,
- VisRect::max_rect(),
- spatial_tree,
- );
-
- let raster_rect = map_pic_to_raster
- .map(&rect_in_pic_space)
- .expect("bug");
-
- // Include this in the overall dirty rect
- self.combined = self.combined.union(&raster_rect);
- }
-}
-
// TODO(gw): Tidy this up by:
// - Add an Other variant for things like opaque gradient backdrops
#[derive(Debug, Copy, Clone)]
@@ -1656,32 +1353,6 @@ pub struct TileCacheParams {
pub yuv_image_surface_count: usize,
}
-/// Defines which sub-slice (effectively a z-index) a primitive exists on within
-/// a picture cache instance.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct SubSliceIndex(u8);
-
-impl SubSliceIndex {
- pub const DEFAULT: SubSliceIndex = SubSliceIndex(0);
-
- pub fn new(index: usize) -> Self {
- SubSliceIndex(index as u8)
- }
-
- /// Return true if this sub-slice is the primary sub-slice (for now, we assume
- /// that only the primary sub-slice may be opaque and support subpixel AA, for example).
- pub fn is_primary(&self) -> bool {
- self.0 == 0
- }
-
- /// Get an array index for this sub-slice
- pub fn as_usize(&self) -> usize {
- self.0 as usize
- }
-}
-
/// Wrapper struct around an external surface descriptor with a little more information
/// that the picture caching code needs.
pub struct CompositorSurface {
diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs
@@ -15,7 +15,8 @@ use crate::spatial_tree::SpatialNodeIndex;
use crate::frame_builder::FrameBuilderConfig;
use crate::gpu_types::{BorderInstance, UvRectKind, TransformPaletteId, BlurEdgeMode};
use crate::internal_types::{CacheTextureId, FastHashMap, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, SVGFE_CONVOLVE_VALUES_LIMIT, TextureSource, Swizzle};
-use crate::picture::{ResolvedSurfaceTexture, MAX_SURFACE_SIZE};
+use crate::picture::ResolvedSurfaceTexture;
+use crate::tile_cache::MAX_SURFACE_SIZE;
use crate::prim_store::ClipData;
use crate::prim_store::gradient::{
FastLinearGradientTask, RadialGradientTask,
diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/renderer/init.rs
@@ -20,7 +20,6 @@ use crate::glyph_cache::GlyphCache;
use glyph_rasterizer::{GlyphRasterThread, GlyphRasterizer, SharedFontResources};
use crate::gpu_types::PrimitiveInstanceData;
use crate::internal_types::{FastHashMap, FastHashSet};
-use crate::picture;
use crate::profiler::{self, Profiler, TransactionProfile};
use crate::device::query::{GpuProfiler, GpuDebugMethod};
use crate::render_backend::RenderBackend;
@@ -667,7 +666,7 @@ pub fn create_webrender_instance(
.map(|handler| handler.create_similar());
let texture_cache_config = options.texture_cache_config.clone();
- let mut picture_tile_size = options.picture_tile_size.unwrap_or(picture::TILE_SIZE_DEFAULT);
+ let mut picture_tile_size = options.picture_tile_size.unwrap_or(crate::tile_cache::TILE_SIZE_DEFAULT);
// Clamp the picture tile size to reasonable values.
picture_tile_size.width = picture_tile_size.width.max(128).min(4096);
picture_tile_size.height = picture_tile_size.height.max(128).min(4096);
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
@@ -78,7 +78,8 @@ use crate::internal_types::DebugOutput;
use crate::internal_types::{CacheTextureId, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
use crate::internal_types::{TextureCacheAllocInfo, TextureCacheAllocationKind, TextureUpdateList};
use crate::internal_types::{RenderTargetInfo, Swizzle, DeferredResolveIndex};
-use crate::picture::{ResolvedSurfaceTexture, TileId};
+use crate::picture::ResolvedSurfaceTexture;
+use crate::tile_cache::TileId;
use crate::prim_store::DeferredResolve;
use crate::profiler::{self, RenderCommandLog, GpuProfileTag, TransactionProfile};
use crate::profiler::{Profiler, add_event_marker, add_text_marker, thread_is_being_profiled};
diff --git a/gfx/wr/webrender/src/surface.rs b/gfx/wr/webrender/src/surface.rs
@@ -5,7 +5,8 @@
use api::units::*;
use crate::command_buffer::{CommandBufferBuilderKind, CommandBufferList, CommandBufferBuilder, CommandBufferIndex};
use crate::internal_types::FastHashMap;
-use crate::picture::{SurfaceInfo, SurfaceIndex, TileKey, SubSliceIndex, MAX_COMPOSITOR_SURFACES};
+use crate::picture::{SurfaceIndex, SurfaceInfo};
+use crate::tile_cache::{TileKey, SubSliceIndex, MAX_COMPOSITOR_SURFACES};
use crate::prim_store::PictureIndex;
use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder};
use crate::render_target::ResolveOp;
diff --git a/gfx/wr/webrender/src/tile_cache.rs b/gfx/wr/webrender/src/tile_cache.rs
@@ -1,779 +0,0 @@
-/* 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/. */
-
-use api::{BorderRadius, ClipId, ClipMode, ColorF, DebugFlags, PrimitiveFlags, QualitySettings, RasterSpace};
-use api::units::*;
-use crate::clip::{ClipItemKeyKind, ClipNodeId, ClipTreeBuilder};
-use crate::frame_builder::FrameBuilderConfig;
-use crate::internal_types::FastHashMap;
-use crate::picture::{PrimitiveList, PictureCompositeMode, PicturePrimitive, SliceId};
-use crate::picture::{Picture3DContext, TileCacheParams, TileOffset, PictureFlags};
-use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex};
-use crate::scene_building::SliceFlags;
-use crate::scene_builder_thread::Interners;
-use crate::spatial_tree::{SpatialNodeIndex, SceneSpatialTree};
-use crate::util::VecHelper;
-use std::mem;
-
-/*
- Types and functionality related to picture caching. In future, we'll
- move more and more of the existing functionality out of picture.rs
- and into here.
- */
-
-// If the page would create too many slices (an arbitrary definition where
-// it's assumed the GPU memory + compositing overhead would be too high)
-// then create a single picture cache for the remaining content. This at
-// least means that we can cache small content changes efficiently when
-// scrolling isn't occurring. Scrolling regions will be handled reasonably
-// efficiently by the dirty rect tracking (since it's likely that if the
-// page has so many slices there isn't a single major scroll region).
-const MAX_CACHE_SLICES: usize = 16;
-
-struct SliceDescriptor {
- prim_list: PrimitiveList,
- scroll_root: SpatialNodeIndex,
-}
-
-enum SliceKind {
- Default {
- secondary_slices: Vec<SliceDescriptor>,
- },
- Atomic {
- prim_list: PrimitiveList,
- },
-}
-
-impl SliceKind {
- fn default() -> Self {
- SliceKind::Default {
- secondary_slices: Vec::new(),
- }
- }
-}
-
-struct PrimarySlice {
- /// Whether this slice is atomic or has secondary slice(s)
- kind: SliceKind,
- /// Optional background color of this slice
- background_color: Option<ColorF>,
- /// Optional root clip for the iframe
- iframe_clip: Option<ClipId>,
- /// Information about how to draw and composite this slice
- slice_flags: SliceFlags,
-}
-
-impl PrimarySlice {
- fn new(
- slice_flags: SliceFlags,
- iframe_clip: Option<ClipId>,
- background_color: Option<ColorF>,
- ) -> Self {
- PrimarySlice {
- kind: SliceKind::default(),
- background_color,
- iframe_clip,
- slice_flags,
- }
- }
-
- fn has_too_many_slices(&self) -> bool {
- match self.kind {
- SliceKind::Atomic { .. } => false,
- SliceKind::Default { ref secondary_slices } => secondary_slices.len() > MAX_CACHE_SLICES,
- }
- }
-
- fn merge(&mut self) {
- self.slice_flags |= SliceFlags::IS_ATOMIC;
-
- let old = mem::replace(
- &mut self.kind,
- SliceKind::Default { secondary_slices: Vec::new() },
- );
-
- self.kind = match old {
- SliceKind::Default { mut secondary_slices } => {
- let mut prim_list = PrimitiveList::empty();
-
- for descriptor in secondary_slices.drain(..) {
- prim_list.merge(descriptor.prim_list);
- }
-
- SliceKind::Atomic {
- prim_list,
- }
- }
- atomic => atomic,
- }
- }
-}
-
-/// Used during scene building to construct the list of pending tile caches.
-pub struct TileCacheBuilder {
- /// List of tile caches that have been created so far (last in the list is currently active).
- primary_slices: Vec<PrimarySlice>,
- /// Cache the previous scroll root search for a spatial node, since they are often the same.
- prev_scroll_root_cache: (SpatialNodeIndex, SpatialNodeIndex),
- /// Handle to the root reference frame
- root_spatial_node_index: SpatialNodeIndex,
- /// Debug flags to provide to our TileCacheInstances.
- debug_flags: DebugFlags,
-}
-
-/// The output of a tile cache builder, containing all details needed to construct the
-/// tile cache(s) for the next scene, and retain tiles from the previous frame when sent
-/// send to the frame builder.
-pub struct TileCacheConfig {
- /// Mapping of slice id to the parameters needed to construct this tile cache.
- pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
- /// Number of picture cache slices that were created (for profiler)
- pub picture_cache_slice_count: usize,
-}
-
-impl TileCacheConfig {
- pub fn new(picture_cache_slice_count: usize) -> Self {
- TileCacheConfig {
- tile_caches: FastHashMap::default(),
- picture_cache_slice_count,
- }
- }
-}
-
-impl TileCacheBuilder {
- /// Construct a new tile cache builder.
- pub fn new(
- root_spatial_node_index: SpatialNodeIndex,
- background_color: Option<ColorF>,
- debug_flags: DebugFlags,
- ) -> Self {
- TileCacheBuilder {
- primary_slices: vec![PrimarySlice::new(SliceFlags::empty(), None, background_color)],
- prev_scroll_root_cache: (SpatialNodeIndex::INVALID, SpatialNodeIndex::INVALID),
- root_spatial_node_index,
- debug_flags,
- }
- }
-
- pub fn make_current_slice_atomic(&mut self) {
- self.primary_slices
- .last_mut()
- .unwrap()
- .merge();
- }
-
- /// Returns true if the current slice has no primitives added yet
- pub fn is_current_slice_empty(&self) -> bool {
- match self.primary_slices.last() {
- Some(slice) => {
- match slice.kind {
- SliceKind::Default { ref secondary_slices } => {
- secondary_slices.is_empty()
- }
- SliceKind::Atomic { ref prim_list } => {
- prim_list.is_empty()
- }
- }
- }
- None => {
- true
- }
- }
- }
-
- /// Set a barrier that forces a new tile cache next time a prim is added.
- pub fn add_tile_cache_barrier(
- &mut self,
- slice_flags: SliceFlags,
- iframe_clip: Option<ClipId>,
- ) {
- let new_slice = PrimarySlice::new(
- slice_flags,
- iframe_clip,
- None,
- );
-
- self.primary_slices.push(new_slice);
- }
-
- /// Create a new tile cache for an existing prim_list
- fn build_tile_cache(
- &mut self,
- prim_list: PrimitiveList,
- spatial_tree: &SceneSpatialTree,
- ) -> Option<SliceDescriptor> {
- if prim_list.is_empty() {
- return None;
- }
-
- // Iterate the clusters and determine which is the most commonly occurring
- // scroll root. This is a reasonable heuristic to decide which spatial node
- // should be considered the scroll root of this tile cache, in order to
- // minimize the invalidations that occur due to scrolling. It's often the
- // case that a blend container will have only a single scroll root.
- let mut scroll_root_occurrences = FastHashMap::default();
-
- for cluster in &prim_list.clusters {
- // If we encounter a cluster which has an unknown spatial node,
- // we don't include that in the set of spatial nodes that we
- // are trying to find scroll roots for. Later on, in finalize_picture,
- // the cluster spatial node will be updated to the selected scroll root.
- if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
- continue;
- }
-
- let scroll_root = find_scroll_root(
- cluster.spatial_node_index,
- &mut self.prev_scroll_root_cache,
- spatial_tree,
- true,
- );
-
- *scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1;
- }
-
- // We can't just select the most commonly occurring scroll root in this
- // primitive list. If that is a nested scroll root, there may be
- // primitives in the list that are outside that scroll root, which
- // can cause panics when calculating relative transforms. To ensure
- // this doesn't happen, only retain scroll root candidates that are
- // also ancestors of every other scroll root candidate.
- let scroll_roots: Vec<SpatialNodeIndex> = scroll_root_occurrences
- .keys()
- .cloned()
- .collect();
-
- scroll_root_occurrences.retain(|parent_spatial_node_index, _| {
- scroll_roots.iter().all(|child_spatial_node_index| {
- parent_spatial_node_index == child_spatial_node_index ||
- spatial_tree.is_ancestor(
- *parent_spatial_node_index,
- *child_spatial_node_index,
- )
- })
- });
-
- // Select the scroll root by finding the most commonly occurring one
- let scroll_root = scroll_root_occurrences
- .iter()
- .max_by_key(|entry | entry.1)
- .map(|(spatial_node_index, _)| *spatial_node_index)
- .unwrap_or(self.root_spatial_node_index);
-
- Some(SliceDescriptor {
- scroll_root,
- prim_list,
- })
- }
-
- /// Add a primitive, either to the current tile cache, or a new one, depending on various conditions.
- pub fn add_prim(
- &mut self,
- prim_instance: PrimitiveInstance,
- prim_rect: LayoutRect,
- spatial_node_index: SpatialNodeIndex,
- prim_flags: PrimitiveFlags,
- spatial_tree: &SceneSpatialTree,
- interners: &Interners,
- quality_settings: &QualitySettings,
- prim_instances: &mut Vec<PrimitiveInstance>,
- clip_tree_builder: &ClipTreeBuilder,
- ) {
- let primary_slice = self.primary_slices.last_mut().unwrap();
-
- match primary_slice.kind {
- SliceKind::Atomic { ref mut prim_list } => {
- prim_list.add_prim(
- prim_instance,
- prim_rect,
- spatial_node_index,
- prim_flags,
- prim_instances,
- clip_tree_builder,
- );
- }
- SliceKind::Default { ref mut secondary_slices } => {
- assert_ne!(spatial_node_index, SpatialNodeIndex::UNKNOWN);
-
- // Check if we want to create a new slice based on the current / next scroll root
- let scroll_root = find_scroll_root(
- spatial_node_index,
- &mut self.prev_scroll_root_cache,
- spatial_tree,
- // Allow sticky frames as scroll roots, unless our quality settings prefer
- // subpixel AA over performance.
- !quality_settings.force_subpixel_aa_where_possible,
- );
-
- let current_scroll_root = secondary_slices
- .last()
- .map(|p| p.scroll_root);
-
- let mut want_new_tile_cache = secondary_slices.is_empty();
-
- if let Some(current_scroll_root) = current_scroll_root {
- want_new_tile_cache |= match (current_scroll_root, scroll_root) {
- (_, _) if current_scroll_root == self.root_spatial_node_index && scroll_root == self.root_spatial_node_index => {
- // Both current slice and this cluster are fixed position, no need to cut
- false
- }
- (_, _) if current_scroll_root == self.root_spatial_node_index => {
- // A real scroll root is being established, so create a cache slice
- true
- }
- (_, _) if scroll_root == self.root_spatial_node_index => {
- // If quality settings force subpixel AA over performance, skip creating
- // a slice for the fixed position element(s) here.
- if quality_settings.force_subpixel_aa_where_possible {
- false
- } else {
- // A fixed position slice is encountered within a scroll root. Only create
- // a slice in this case if all the clips referenced by this cluster are also
- // fixed position. There's no real point in creating slices for these cases,
- // since we'll have to rasterize them as the scrolling clip moves anyway. It
- // also allows us to retain subpixel AA in these cases. For these types of
- // slices, the intra-slice dirty rect handling typically works quite well
- // (a common case is parallax scrolling effects).
- let mut create_slice = true;
-
- let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
- let mut current_node_id = leaf.node_id;
-
- while current_node_id != ClipNodeId::NONE {
- let node = clip_tree_builder.get_node(current_node_id);
-
- let clip_node_data = &interners.clip[node.handle];
-
- let spatial_root = find_scroll_root(
- clip_node_data.key.spatial_node_index,
- &mut self.prev_scroll_root_cache,
- spatial_tree,
- true,
- );
-
- if spatial_root != self.root_spatial_node_index {
- create_slice = false;
- break;
- }
-
- current_node_id = node.parent;
- }
-
- create_slice
- }
- }
- (curr_scroll_root, scroll_root) => {
- // Two scrolling roots - only need a new slice if they differ
- curr_scroll_root != scroll_root
- }
- };
- }
-
- if want_new_tile_cache {
- secondary_slices.push(SliceDescriptor {
- prim_list: PrimitiveList::empty(),
- scroll_root,
- });
- }
-
- secondary_slices
- .last_mut()
- .unwrap()
- .prim_list
- .add_prim(
- prim_instance,
- prim_rect,
- spatial_node_index,
- prim_flags,
- prim_instances,
- clip_tree_builder,
- );
- }
- }
- }
-
- /// Consume this object and build the list of tile cache primitives
- pub fn build(
- mut self,
- config: &FrameBuilderConfig,
- prim_store: &mut PrimitiveStore,
- spatial_tree: &SceneSpatialTree,
- prim_instances: &[PrimitiveInstance],
- clip_tree_builder: &mut ClipTreeBuilder,
- interners: &Interners,
- ) -> (TileCacheConfig, Vec<PictureIndex>) {
- let mut result = TileCacheConfig::new(self.primary_slices.len());
- let mut tile_cache_pictures = Vec::new();
- let primary_slices = std::mem::replace(&mut self.primary_slices, Vec::new());
-
- // TODO: At the moment, culling, clipping and invalidation are always
- // done in the root coordinate space. The plan is to move to doing it
- // (always or mostly) in raster space.
- let visibility_node = spatial_tree.root_reference_frame_index();
-
- for mut primary_slice in primary_slices {
-
- if primary_slice.has_too_many_slices() {
- primary_slice.merge();
- }
-
- match primary_slice.kind {
- SliceKind::Atomic { prim_list } => {
- if let Some(descriptor) = self.build_tile_cache(
- prim_list,
- spatial_tree,
- ) {
- create_tile_cache(
- self.debug_flags,
- primary_slice.slice_flags,
- descriptor.scroll_root,
- visibility_node,
- primary_slice.iframe_clip,
- descriptor.prim_list,
- primary_slice.background_color,
- prim_store,
- prim_instances,
- config,
- &mut result.tile_caches,
- &mut tile_cache_pictures,
- clip_tree_builder,
- interners,
- spatial_tree,
- );
- }
- }
- SliceKind::Default { secondary_slices } => {
- for descriptor in secondary_slices {
- create_tile_cache(
- self.debug_flags,
- primary_slice.slice_flags,
- descriptor.scroll_root,
- visibility_node,
- primary_slice.iframe_clip,
- descriptor.prim_list,
- primary_slice.background_color,
- prim_store,
- prim_instances,
- config,
- &mut result.tile_caches,
- &mut tile_cache_pictures,
- clip_tree_builder,
- interners,
- spatial_tree,
- );
- }
- }
- }
- }
-
- (result, tile_cache_pictures)
- }
-}
-
-/// Find the scroll root for a given spatial node
-fn find_scroll_root(
- spatial_node_index: SpatialNodeIndex,
- prev_scroll_root_cache: &mut (SpatialNodeIndex, SpatialNodeIndex),
- spatial_tree: &SceneSpatialTree,
- allow_sticky_frames: bool,
-) -> SpatialNodeIndex {
- if prev_scroll_root_cache.0 == spatial_node_index {
- return prev_scroll_root_cache.1;
- }
-
- let scroll_root = spatial_tree.find_scroll_root(spatial_node_index, allow_sticky_frames);
- *prev_scroll_root_cache = (spatial_node_index, scroll_root);
-
- scroll_root
-}
-
-/// Given a PrimitiveList and scroll root, construct a tile cache primitive instance
-/// that wraps the primitive list.
-fn create_tile_cache(
- debug_flags: DebugFlags,
- slice_flags: SliceFlags,
- scroll_root: SpatialNodeIndex,
- visibility_node: SpatialNodeIndex,
- iframe_clip: Option<ClipId>,
- prim_list: PrimitiveList,
- background_color: Option<ColorF>,
- prim_store: &mut PrimitiveStore,
- prim_instances: &[PrimitiveInstance],
- frame_builder_config: &FrameBuilderConfig,
- tile_caches: &mut FastHashMap<SliceId, TileCacheParams>,
- tile_cache_pictures: &mut Vec<PictureIndex>,
- clip_tree_builder: &mut ClipTreeBuilder,
- interners: &Interners,
- spatial_tree: &SceneSpatialTree,
-) {
- // Accumulate any clip instances from the iframe_clip into the shared clips
- // that will be applied by this tile cache during compositing.
- let mut additional_clips = Vec::new();
-
- if let Some(clip_id) = iframe_clip {
- additional_clips.push(clip_id);
- }
-
- // Find the best shared clip node that we can apply while compositing tiles,
- // rather than applying to each item individually.
-
- // Step 1: Walk the primitive list, and find the LCA of the clip-tree that
- // matches all primitives. This gives us our "best-case" shared
- // clip node that moves as many clips as possible to compositing.
- let mut shared_clip_node_id = None;
-
- for cluster in &prim_list.clusters {
- for prim_instance in &prim_instances[cluster.prim_range()] {
- let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
-
- // TODO(gw): Need to cache last clip-node id here?
- shared_clip_node_id = match shared_clip_node_id {
- Some(current) => {
- Some(clip_tree_builder.find_lowest_common_ancestor(current, leaf.node_id))
- }
- None => {
- Some(leaf.node_id)
- }
- }
- }
- }
-
- // Step 2: Now we need to walk up the shared clip node hierarchy, and remove clips
- // that we can't handle during compositing, such as:
- // (a) Non axis-aligned clips
- // (b) Box-shadow or image-mask clips
- // (c) Rounded rect clips.
- //
- // A follow up patch to this series will relax the condition on (c) to
- // allow tile caches to apply a single rounded-rect clip during compositing.
- let mut shared_clip_node_id = shared_clip_node_id.unwrap_or(ClipNodeId::NONE);
- let mut current_node_id = shared_clip_node_id;
- let mut rounded_rect_count = 0;
-
- // Walk up the hierarchy to the root of the clip-tree
- while current_node_id != ClipNodeId::NONE {
- let node = clip_tree_builder.get_node(current_node_id);
- let clip_node_data = &interners.clip[node.handle];
-
- // Check if this clip is in the root coord system (i.e. is axis-aligned with tile-cache)
- let is_rcs = spatial_tree.is_root_coord_system(clip_node_data.key.spatial_node_index);
-
- let node_valid = if is_rcs {
- match clip_node_data.key.kind {
- ClipItemKeyKind::BoxShadow(..) |
- ClipItemKeyKind::ImageMask(..) |
- ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
- ClipItemKeyKind::RoundedRectangle(_, _, ClipMode::ClipOut) => {
- // Has a box-shadow / image-mask, we can't handle this as a shared clip
- false
- }
- ClipItemKeyKind::RoundedRectangle(rect, radius, ClipMode::Clip) => {
- // The shader and CoreAnimation rely on certain constraints such
- // as uniform radii to be able to apply the clip during compositing.
- if BorderRadius::from(radius).can_use_fast_path_in(&rect.into()) {
- rounded_rect_count += 1;
-
- true
- } else {
- false
- }
- }
- ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => {
- // We can apply multiple (via combining) axis-aligned rectangle
- // clips to the shared compositing clip.
- true
- }
- }
- } else {
- // Has a complex transform, we can't handle this as a shared clip
- false
- };
-
- if node_valid {
- // This node was found to be one we can apply during compositing.
- if rounded_rect_count > 1 {
- // However, we plan to only support one rounded-rect clip. If
- // we have found > 1 rounded rect, drop children from the shared
- // clip, and continue looking up the chain.
- shared_clip_node_id = current_node_id;
- rounded_rect_count = 1;
- }
- } else {
- // Node was invalid, due to transform / clip type. Drop this clip
- // and reset the rounded rect count to 0, since we drop children
- // from here too.
- shared_clip_node_id = node.parent;
- rounded_rect_count = 0;
- }
-
- current_node_id = node.parent;
- }
-
- let shared_clip_leaf_id = Some(clip_tree_builder.build_for_tile_cache(
- shared_clip_node_id,
- &additional_clips,
- ));
-
- // Build a clip-chain for the tile cache, that contains any of the shared clips
- // we will apply when drawing the tiles. In all cases provided by Gecko, these
- // are rectangle clips with a scale/offset transform only, and get handled as
- // a simple local clip rect in the vertex shader. However, this should in theory
- // also work with any complex clips, such as rounded rects and image masks, by
- // producing a clip mask that is applied to the picture cache tiles.
-
- let slice = tile_cache_pictures.len();
-
- let background_color = if slice == 0 {
- background_color
- } else {
- None
- };
-
- let slice_id = SliceId::new(slice);
-
- // Store some information about the picture cache slice. This is used when we swap the
- // new scene into the frame builder to either reuse existing slices, or create new ones.
- tile_caches.insert(slice_id, TileCacheParams {
- debug_flags,
- slice,
- slice_flags,
- spatial_node_index: scroll_root,
- visibility_node_index: visibility_node,
- background_color,
- shared_clip_node_id,
- shared_clip_leaf_id,
- virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(),
- image_surface_count: prim_list.image_surface_count,
- yuv_image_surface_count: prim_list.yuv_image_surface_count,
- });
-
- let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image(
- Some(PictureCompositeMode::TileCache { slice_id }),
- Picture3DContext::Out,
- PrimitiveFlags::IS_BACKFACE_VISIBLE,
- prim_list,
- scroll_root,
- RasterSpace::Screen,
- PictureFlags::empty(),
- None,
- ));
-
- tile_cache_pictures.push(PictureIndex(pic_index));
-}
-
-/// Debug information about a set of picture cache slices, exposed via RenderResults
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PictureCacheDebugInfo {
- pub slices: FastHashMap<usize, SliceDebugInfo>,
-}
-
-impl PictureCacheDebugInfo {
- pub fn new() -> Self {
- PictureCacheDebugInfo {
- slices: FastHashMap::default(),
- }
- }
-
- /// Convenience method to retrieve a given slice. Deliberately panics
- /// if the slice isn't present.
- pub fn slice(&self, slice: usize) -> &SliceDebugInfo {
- &self.slices[&slice]
- }
-}
-
-impl Default for PictureCacheDebugInfo {
- fn default() -> PictureCacheDebugInfo {
- PictureCacheDebugInfo::new()
- }
-}
-
-/// Debug information about a set of picture cache tiles, exposed via RenderResults
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct SliceDebugInfo {
- pub tiles: FastHashMap<TileOffset, TileDebugInfo>,
-}
-
-impl SliceDebugInfo {
- pub fn new() -> Self {
- SliceDebugInfo {
- tiles: FastHashMap::default(),
- }
- }
-
- /// Convenience method to retrieve a given tile. Deliberately panics
- /// if the tile isn't present.
- pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo {
- &self.tiles[&TileOffset::new(x, y)]
- }
-}
-
-/// Debug information about a tile that was dirty and was rasterized
-#[derive(Debug, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct DirtyTileDebugInfo {
- pub local_valid_rect: PictureRect,
- pub local_dirty_rect: PictureRect,
-}
-
-/// Debug information about the state of a tile
-#[derive(Debug, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum TileDebugInfo {
- /// Tile was occluded by a tile in front of it
- Occluded,
- /// Tile was culled (not visible in current display port)
- Culled,
- /// Tile was valid (no rasterization was done) and visible
- Valid,
- /// Tile was dirty, and was updated
- Dirty(DirtyTileDebugInfo),
-}
-
-impl TileDebugInfo {
- pub fn is_occluded(&self) -> bool {
- match self {
- TileDebugInfo::Occluded => true,
- TileDebugInfo::Culled |
- TileDebugInfo::Valid |
- TileDebugInfo::Dirty(..) => false,
- }
- }
-
- pub fn is_valid(&self) -> bool {
- match self {
- TileDebugInfo::Valid => true,
- TileDebugInfo::Culled |
- TileDebugInfo::Occluded |
- TileDebugInfo::Dirty(..) => false,
- }
- }
-
- pub fn is_culled(&self) -> bool {
- match self {
- TileDebugInfo::Culled => true,
- TileDebugInfo::Valid |
- TileDebugInfo::Occluded |
- TileDebugInfo::Dirty(..) => false,
- }
- }
-
- pub fn as_dirty(&self) -> &DirtyTileDebugInfo {
- match self {
- TileDebugInfo::Occluded |
- TileDebugInfo::Culled |
- TileDebugInfo::Valid => {
- panic!("not a dirty tile!");
- }
- TileDebugInfo::Dirty(ref info) => {
- info
- }
- }
- }
-}
diff --git a/gfx/wr/webrender/src/tile_cache/mod.rs b/gfx/wr/webrender/src/tile_cache/mod.rs
@@ -0,0 +1,235 @@
+/* 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/. */
+
+//! Tile cache types and descriptors
+//!
+//! This module contains the core tile caching infrastructure including:
+//! - Tile identification and coordinate types
+//! - Tile descriptors that track primitive dependencies
+//! - Comparison results for invalidation tracking
+
+// Existing tile cache slice builder (was previously tile_cache.rs)
+pub mod slice_builder;
+
+use api::units::*;
+use crate::intern::ItemUid;
+use crate::internal_types::FrameId;
+use peek_poke::PeekPoke;
+use std::{marker, u32};
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+pub use self::slice_builder::{
+ TileCacheBuilder, TileCacheConfig,
+ PictureCacheDebugInfo, SliceDebugInfo, DirtyTileDebugInfo, TileDebugInfo,
+};
+
+pub use api::units::TileOffset;
+pub use api::units::TileRange as TileRect;
+
+/// The maximum number of compositor surfaces that are allowed per picture cache. This
+/// is an arbitrary number that should be enough for common cases, but low enough to
+/// prevent performance and memory usage drastically degrading in pathological cases.
+pub const MAX_COMPOSITOR_SURFACES: usize = 4;
+
+/// The size in device pixels of a normal cached tile.
+pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize {
+ width: 1024,
+ height: 512,
+ _unit: marker::PhantomData,
+};
+
+/// The size in device pixels of a tile for horizontal scroll bars
+pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize {
+ width: 1024,
+ height: 32,
+ _unit: marker::PhantomData,
+};
+
+/// The size in device pixels of a tile for vertical scroll bars
+pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
+ width: 32,
+ height: 1024,
+ _unit: marker::PhantomData,
+};
+
+/// The maximum size per axis of a surface, in DevicePixel coordinates.
+/// Render tasks larger than this size are scaled down to fit, which may cause
+/// some blurriness.
+pub const MAX_SURFACE_SIZE: usize = 4096;
+
+/// Used to get unique tile IDs, even when the tile cache is
+/// destroyed between display lists / scenes.
+static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
+
+/// A unique identifier for a tile. These are stable across display lists and
+/// scenes.
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct TileId(pub usize);
+
+impl TileId {
+ pub fn new() -> TileId {
+ TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed))
+ }
+}
+
+// Internal function used by picture.rs for creating TileIds
+#[doc(hidden)]
+pub fn next_tile_id() -> usize {
+ NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed)
+}
+
+/// Uniquely identifies a tile within a picture cache slice
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
+pub struct TileKey {
+ // Tile index (x,y)
+ pub tile_offset: TileOffset,
+ // Sub-slice (z)
+ pub sub_slice_index: SubSliceIndex,
+}
+
+/// An index into the prims array in a TileDescriptor.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveDependencyIndex(pub u32);
+
+/// Defines which sub-slice (effectively a z-index) a primitive exists on within
+/// a picture cache instance.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PeekPoke)]
+pub struct SubSliceIndex(pub u8);
+
+impl SubSliceIndex {
+ pub const DEFAULT: SubSliceIndex = SubSliceIndex(0);
+
+ pub fn new(index: usize) -> Self {
+ SubSliceIndex(index as u8)
+ }
+
+ /// Return true if this sub-slice is the primary sub-slice (for now, we assume
+ /// that only the primary sub-slice may be opaque and support subpixel AA, for example).
+ pub fn is_primary(&self) -> bool {
+ self.0 == 0
+ }
+
+ /// Get an array index for this sub-slice
+ pub fn as_usize(&self) -> usize {
+ self.0 as usize
+ }
+}
+
+
+/// Information about a primitive that is a dependency for a tile.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveDescriptor {
+ pub prim_uid: ItemUid,
+ pub prim_clip_box: PictureBox2D,
+ // TODO(gw): These two fields could be packed as a u24/u8
+ pub dep_offset: u32,
+ pub dep_count: u32,
+}
+
+impl PartialEq for PrimitiveDescriptor {
+ fn eq(&self, other: &Self) -> bool {
+ const EPSILON: f32 = 0.001;
+
+ if self.prim_uid != other.prim_uid {
+ return false;
+ }
+
+ use euclid::approxeq::ApproxEq;
+ if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) {
+ return false;
+ }
+ if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) {
+ return false;
+ }
+ if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) {
+ return false;
+ }
+ if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) {
+ return false;
+ }
+
+ if self.dep_count != other.dep_count {
+ return false;
+ }
+
+ true
+ }
+}
+
+impl PartialEq<PrimitiveDescriptor> for (&ItemUid, &PictureBox2D) {
+ fn eq(&self, other: &PrimitiveDescriptor) -> bool {
+ self.0 == &other.prim_uid && self.1 == &other.prim_clip_box
+ }
+}
+
+/// Uniquely describes the content of this tile, in a way that can be
+/// (reasonably) efficiently hashed and compared.
+#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct TileDescriptor {
+ /// List of primitive instance unique identifiers. The uid is guaranteed
+ /// to uniquely describe the content of the primitive template, while
+ /// the other parameters describe the clip chain and instance params.
+ pub prims: Vec<PrimitiveDescriptor>,
+
+ /// Picture space rect that contains valid pixels region of this tile.
+ pub local_valid_rect: PictureRect,
+
+ /// The last frame this tile had its dependencies updated (dependency updating is
+ /// skipped if a tile is off-screen).
+ pub last_updated_frame_id: FrameId,
+
+ /// Packed per-prim dependency information
+ pub dep_data: Vec<u8>,
+}
+
+impl TileDescriptor {
+ pub fn new() -> Self {
+ TileDescriptor {
+ local_valid_rect: PictureRect::zero(),
+ dep_data: Vec::new(),
+ prims: Vec::new(),
+ last_updated_frame_id: FrameId::INVALID,
+ }
+ }
+
+ /// Print debug information about this tile descriptor to a tree printer.
+ pub fn print(&self, pt: &mut dyn crate::print_tree::PrintTreePrinter) {
+ pt.new_level("current_descriptor".to_string());
+
+ pt.new_level("prims".to_string());
+ for prim in &self.prims {
+ pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
+ pt.add_item(format!("clip: p0={},{} p1={},{}",
+ prim.prim_clip_box.min.x,
+ prim.prim_clip_box.min.y,
+ prim.prim_clip_box.max.x,
+ prim.prim_clip_box.max.y,
+ ));
+ pt.end_level();
+ }
+ pt.end_level();
+
+ pt.end_level();
+ }
+
+ /// Clear the dependency information for a tile, when the dependencies
+ /// are being rebuilt.
+ pub fn clear(&mut self) {
+ self.local_valid_rect = PictureRect::zero();
+ self.prims.clear();
+ self.dep_data.clear();
+ }
+}
diff --git a/gfx/wr/webrender/src/tile_cache/slice_builder.rs b/gfx/wr/webrender/src/tile_cache/slice_builder.rs
@@ -0,0 +1,779 @@
+/* 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/. */
+
+use api::{BorderRadius, ClipId, ClipMode, ColorF, DebugFlags, PrimitiveFlags, QualitySettings, RasterSpace};
+use api::units::*;
+use crate::clip::{ClipItemKeyKind, ClipNodeId, ClipTreeBuilder};
+use crate::frame_builder::FrameBuilderConfig;
+use crate::internal_types::FastHashMap;
+use crate::picture::{PrimitiveList, PictureCompositeMode, PicturePrimitive, SliceId};
+use crate::picture::{Picture3DContext, TileCacheParams, PictureFlags};
+use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex};
+use crate::scene_building::SliceFlags;
+use crate::scene_builder_thread::Interners;
+use crate::spatial_tree::{SpatialNodeIndex, SceneSpatialTree};
+use crate::util::VecHelper;
+use std::mem;
+
+/*
+ Types and functionality related to picture caching. In future, we'll
+ move more and more of the existing functionality out of picture.rs
+ and into here.
+ */
+
+// If the page would create too many slices (an arbitrary definition where
+// it's assumed the GPU memory + compositing overhead would be too high)
+// then create a single picture cache for the remaining content. This at
+// least means that we can cache small content changes efficiently when
+// scrolling isn't occurring. Scrolling regions will be handled reasonably
+// efficiently by the dirty rect tracking (since it's likely that if the
+// page has so many slices there isn't a single major scroll region).
+const MAX_CACHE_SLICES: usize = 16;
+
+struct SliceDescriptor {
+ prim_list: PrimitiveList,
+ scroll_root: SpatialNodeIndex,
+}
+
+enum SliceKind {
+ Default {
+ secondary_slices: Vec<SliceDescriptor>,
+ },
+ Atomic {
+ prim_list: PrimitiveList,
+ },
+}
+
+impl SliceKind {
+ fn default() -> Self {
+ SliceKind::Default {
+ secondary_slices: Vec::new(),
+ }
+ }
+}
+
+struct PrimarySlice {
+ /// Whether this slice is atomic or has secondary slice(s)
+ kind: SliceKind,
+ /// Optional background color of this slice
+ background_color: Option<ColorF>,
+ /// Optional root clip for the iframe
+ iframe_clip: Option<ClipId>,
+ /// Information about how to draw and composite this slice
+ slice_flags: SliceFlags,
+}
+
+impl PrimarySlice {
+ fn new(
+ slice_flags: SliceFlags,
+ iframe_clip: Option<ClipId>,
+ background_color: Option<ColorF>,
+ ) -> Self {
+ PrimarySlice {
+ kind: SliceKind::default(),
+ background_color,
+ iframe_clip,
+ slice_flags,
+ }
+ }
+
+ fn has_too_many_slices(&self) -> bool {
+ match self.kind {
+ SliceKind::Atomic { .. } => false,
+ SliceKind::Default { ref secondary_slices } => secondary_slices.len() > MAX_CACHE_SLICES,
+ }
+ }
+
+ fn merge(&mut self) {
+ self.slice_flags |= SliceFlags::IS_ATOMIC;
+
+ let old = mem::replace(
+ &mut self.kind,
+ SliceKind::Default { secondary_slices: Vec::new() },
+ );
+
+ self.kind = match old {
+ SliceKind::Default { mut secondary_slices } => {
+ let mut prim_list = PrimitiveList::empty();
+
+ for descriptor in secondary_slices.drain(..) {
+ prim_list.merge(descriptor.prim_list);
+ }
+
+ SliceKind::Atomic {
+ prim_list,
+ }
+ }
+ atomic => atomic,
+ }
+ }
+}
+
+/// Used during scene building to construct the list of pending tile caches.
+pub struct TileCacheBuilder {
+ /// List of tile caches that have been created so far (last in the list is currently active).
+ primary_slices: Vec<PrimarySlice>,
+ /// Cache the previous scroll root search for a spatial node, since they are often the same.
+ prev_scroll_root_cache: (SpatialNodeIndex, SpatialNodeIndex),
+ /// Handle to the root reference frame
+ root_spatial_node_index: SpatialNodeIndex,
+ /// Debug flags to provide to our TileCacheInstances.
+ debug_flags: DebugFlags,
+}
+
+/// The output of a tile cache builder, containing all details needed to construct the
+/// tile cache(s) for the next scene, and retain tiles from the previous frame when sent
+/// send to the frame builder.
+pub struct TileCacheConfig {
+ /// Mapping of slice id to the parameters needed to construct this tile cache.
+ pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
+ /// Number of picture cache slices that were created (for profiler)
+ pub picture_cache_slice_count: usize,
+}
+
+impl TileCacheConfig {
+ pub fn new(picture_cache_slice_count: usize) -> Self {
+ TileCacheConfig {
+ tile_caches: FastHashMap::default(),
+ picture_cache_slice_count,
+ }
+ }
+}
+
+impl TileCacheBuilder {
+ /// Construct a new tile cache builder.
+ pub fn new(
+ root_spatial_node_index: SpatialNodeIndex,
+ background_color: Option<ColorF>,
+ debug_flags: DebugFlags,
+ ) -> Self {
+ TileCacheBuilder {
+ primary_slices: vec![PrimarySlice::new(SliceFlags::empty(), None, background_color)],
+ prev_scroll_root_cache: (SpatialNodeIndex::INVALID, SpatialNodeIndex::INVALID),
+ root_spatial_node_index,
+ debug_flags,
+ }
+ }
+
+ pub fn make_current_slice_atomic(&mut self) {
+ self.primary_slices
+ .last_mut()
+ .unwrap()
+ .merge();
+ }
+
+ /// Returns true if the current slice has no primitives added yet
+ pub fn is_current_slice_empty(&self) -> bool {
+ match self.primary_slices.last() {
+ Some(slice) => {
+ match slice.kind {
+ SliceKind::Default { ref secondary_slices } => {
+ secondary_slices.is_empty()
+ }
+ SliceKind::Atomic { ref prim_list } => {
+ prim_list.is_empty()
+ }
+ }
+ }
+ None => {
+ true
+ }
+ }
+ }
+
+ /// Set a barrier that forces a new tile cache next time a prim is added.
+ pub fn add_tile_cache_barrier(
+ &mut self,
+ slice_flags: SliceFlags,
+ iframe_clip: Option<ClipId>,
+ ) {
+ let new_slice = PrimarySlice::new(
+ slice_flags,
+ iframe_clip,
+ None,
+ );
+
+ self.primary_slices.push(new_slice);
+ }
+
+ /// Create a new tile cache for an existing prim_list
+ fn build_tile_cache(
+ &mut self,
+ prim_list: PrimitiveList,
+ spatial_tree: &SceneSpatialTree,
+ ) -> Option<SliceDescriptor> {
+ if prim_list.is_empty() {
+ return None;
+ }
+
+ // Iterate the clusters and determine which is the most commonly occurring
+ // scroll root. This is a reasonable heuristic to decide which spatial node
+ // should be considered the scroll root of this tile cache, in order to
+ // minimize the invalidations that occur due to scrolling. It's often the
+ // case that a blend container will have only a single scroll root.
+ let mut scroll_root_occurrences = FastHashMap::default();
+
+ for cluster in &prim_list.clusters {
+ // If we encounter a cluster which has an unknown spatial node,
+ // we don't include that in the set of spatial nodes that we
+ // are trying to find scroll roots for. Later on, in finalize_picture,
+ // the cluster spatial node will be updated to the selected scroll root.
+ if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
+ continue;
+ }
+
+ let scroll_root = find_scroll_root(
+ cluster.spatial_node_index,
+ &mut self.prev_scroll_root_cache,
+ spatial_tree,
+ true,
+ );
+
+ *scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1;
+ }
+
+ // We can't just select the most commonly occurring scroll root in this
+ // primitive list. If that is a nested scroll root, there may be
+ // primitives in the list that are outside that scroll root, which
+ // can cause panics when calculating relative transforms. To ensure
+ // this doesn't happen, only retain scroll root candidates that are
+ // also ancestors of every other scroll root candidate.
+ let scroll_roots: Vec<SpatialNodeIndex> = scroll_root_occurrences
+ .keys()
+ .cloned()
+ .collect();
+
+ scroll_root_occurrences.retain(|parent_spatial_node_index, _| {
+ scroll_roots.iter().all(|child_spatial_node_index| {
+ parent_spatial_node_index == child_spatial_node_index ||
+ spatial_tree.is_ancestor(
+ *parent_spatial_node_index,
+ *child_spatial_node_index,
+ )
+ })
+ });
+
+ // Select the scroll root by finding the most commonly occurring one
+ let scroll_root = scroll_root_occurrences
+ .iter()
+ .max_by_key(|entry | entry.1)
+ .map(|(spatial_node_index, _)| *spatial_node_index)
+ .unwrap_or(self.root_spatial_node_index);
+
+ Some(SliceDescriptor {
+ scroll_root,
+ prim_list,
+ })
+ }
+
+ /// Add a primitive, either to the current tile cache, or a new one, depending on various conditions.
+ pub fn add_prim(
+ &mut self,
+ prim_instance: PrimitiveInstance,
+ prim_rect: LayoutRect,
+ spatial_node_index: SpatialNodeIndex,
+ prim_flags: PrimitiveFlags,
+ spatial_tree: &SceneSpatialTree,
+ interners: &Interners,
+ quality_settings: &QualitySettings,
+ prim_instances: &mut Vec<PrimitiveInstance>,
+ clip_tree_builder: &ClipTreeBuilder,
+ ) {
+ let primary_slice = self.primary_slices.last_mut().unwrap();
+
+ match primary_slice.kind {
+ SliceKind::Atomic { ref mut prim_list } => {
+ prim_list.add_prim(
+ prim_instance,
+ prim_rect,
+ spatial_node_index,
+ prim_flags,
+ prim_instances,
+ clip_tree_builder,
+ );
+ }
+ SliceKind::Default { ref mut secondary_slices } => {
+ assert_ne!(spatial_node_index, SpatialNodeIndex::UNKNOWN);
+
+ // Check if we want to create a new slice based on the current / next scroll root
+ let scroll_root = find_scroll_root(
+ spatial_node_index,
+ &mut self.prev_scroll_root_cache,
+ spatial_tree,
+ // Allow sticky frames as scroll roots, unless our quality settings prefer
+ // subpixel AA over performance.
+ !quality_settings.force_subpixel_aa_where_possible,
+ );
+
+ let current_scroll_root = secondary_slices
+ .last()
+ .map(|p| p.scroll_root);
+
+ let mut want_new_tile_cache = secondary_slices.is_empty();
+
+ if let Some(current_scroll_root) = current_scroll_root {
+ want_new_tile_cache |= match (current_scroll_root, scroll_root) {
+ (_, _) if current_scroll_root == self.root_spatial_node_index && scroll_root == self.root_spatial_node_index => {
+ // Both current slice and this cluster are fixed position, no need to cut
+ false
+ }
+ (_, _) if current_scroll_root == self.root_spatial_node_index => {
+ // A real scroll root is being established, so create a cache slice
+ true
+ }
+ (_, _) if scroll_root == self.root_spatial_node_index => {
+ // If quality settings force subpixel AA over performance, skip creating
+ // a slice for the fixed position element(s) here.
+ if quality_settings.force_subpixel_aa_where_possible {
+ false
+ } else {
+ // A fixed position slice is encountered within a scroll root. Only create
+ // a slice in this case if all the clips referenced by this cluster are also
+ // fixed position. There's no real point in creating slices for these cases,
+ // since we'll have to rasterize them as the scrolling clip moves anyway. It
+ // also allows us to retain subpixel AA in these cases. For these types of
+ // slices, the intra-slice dirty rect handling typically works quite well
+ // (a common case is parallax scrolling effects).
+ let mut create_slice = true;
+
+ let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
+ let mut current_node_id = leaf.node_id;
+
+ while current_node_id != ClipNodeId::NONE {
+ let node = clip_tree_builder.get_node(current_node_id);
+
+ let clip_node_data = &interners.clip[node.handle];
+
+ let spatial_root = find_scroll_root(
+ clip_node_data.key.spatial_node_index,
+ &mut self.prev_scroll_root_cache,
+ spatial_tree,
+ true,
+ );
+
+ if spatial_root != self.root_spatial_node_index {
+ create_slice = false;
+ break;
+ }
+
+ current_node_id = node.parent;
+ }
+
+ create_slice
+ }
+ }
+ (curr_scroll_root, scroll_root) => {
+ // Two scrolling roots - only need a new slice if they differ
+ curr_scroll_root != scroll_root
+ }
+ };
+ }
+
+ if want_new_tile_cache {
+ secondary_slices.push(SliceDescriptor {
+ prim_list: PrimitiveList::empty(),
+ scroll_root,
+ });
+ }
+
+ secondary_slices
+ .last_mut()
+ .unwrap()
+ .prim_list
+ .add_prim(
+ prim_instance,
+ prim_rect,
+ spatial_node_index,
+ prim_flags,
+ prim_instances,
+ clip_tree_builder,
+ );
+ }
+ }
+ }
+
+ /// Consume this object and build the list of tile cache primitives
+ pub fn build(
+ mut self,
+ config: &FrameBuilderConfig,
+ prim_store: &mut PrimitiveStore,
+ spatial_tree: &SceneSpatialTree,
+ prim_instances: &[PrimitiveInstance],
+ clip_tree_builder: &mut ClipTreeBuilder,
+ interners: &Interners,
+ ) -> (TileCacheConfig, Vec<PictureIndex>) {
+ let mut result = TileCacheConfig::new(self.primary_slices.len());
+ let mut tile_cache_pictures = Vec::new();
+ let primary_slices = std::mem::replace(&mut self.primary_slices, Vec::new());
+
+ // TODO: At the moment, culling, clipping and invalidation are always
+ // done in the root coordinate space. The plan is to move to doing it
+ // (always or mostly) in raster space.
+ let visibility_node = spatial_tree.root_reference_frame_index();
+
+ for mut primary_slice in primary_slices {
+
+ if primary_slice.has_too_many_slices() {
+ primary_slice.merge();
+ }
+
+ match primary_slice.kind {
+ SliceKind::Atomic { prim_list } => {
+ if let Some(descriptor) = self.build_tile_cache(
+ prim_list,
+ spatial_tree,
+ ) {
+ create_tile_cache(
+ self.debug_flags,
+ primary_slice.slice_flags,
+ descriptor.scroll_root,
+ visibility_node,
+ primary_slice.iframe_clip,
+ descriptor.prim_list,
+ primary_slice.background_color,
+ prim_store,
+ prim_instances,
+ config,
+ &mut result.tile_caches,
+ &mut tile_cache_pictures,
+ clip_tree_builder,
+ interners,
+ spatial_tree,
+ );
+ }
+ }
+ SliceKind::Default { secondary_slices } => {
+ for descriptor in secondary_slices {
+ create_tile_cache(
+ self.debug_flags,
+ primary_slice.slice_flags,
+ descriptor.scroll_root,
+ visibility_node,
+ primary_slice.iframe_clip,
+ descriptor.prim_list,
+ primary_slice.background_color,
+ prim_store,
+ prim_instances,
+ config,
+ &mut result.tile_caches,
+ &mut tile_cache_pictures,
+ clip_tree_builder,
+ interners,
+ spatial_tree,
+ );
+ }
+ }
+ }
+ }
+
+ (result, tile_cache_pictures)
+ }
+}
+
+/// Find the scroll root for a given spatial node
+fn find_scroll_root(
+ spatial_node_index: SpatialNodeIndex,
+ prev_scroll_root_cache: &mut (SpatialNodeIndex, SpatialNodeIndex),
+ spatial_tree: &SceneSpatialTree,
+ allow_sticky_frames: bool,
+) -> SpatialNodeIndex {
+ if prev_scroll_root_cache.0 == spatial_node_index {
+ return prev_scroll_root_cache.1;
+ }
+
+ let scroll_root = spatial_tree.find_scroll_root(spatial_node_index, allow_sticky_frames);
+ *prev_scroll_root_cache = (spatial_node_index, scroll_root);
+
+ scroll_root
+}
+
+/// Given a PrimitiveList and scroll root, construct a tile cache primitive instance
+/// that wraps the primitive list.
+fn create_tile_cache(
+ debug_flags: DebugFlags,
+ slice_flags: SliceFlags,
+ scroll_root: SpatialNodeIndex,
+ visibility_node: SpatialNodeIndex,
+ iframe_clip: Option<ClipId>,
+ prim_list: PrimitiveList,
+ background_color: Option<ColorF>,
+ prim_store: &mut PrimitiveStore,
+ prim_instances: &[PrimitiveInstance],
+ frame_builder_config: &FrameBuilderConfig,
+ tile_caches: &mut FastHashMap<SliceId, TileCacheParams>,
+ tile_cache_pictures: &mut Vec<PictureIndex>,
+ clip_tree_builder: &mut ClipTreeBuilder,
+ interners: &Interners,
+ spatial_tree: &SceneSpatialTree,
+) {
+ // Accumulate any clip instances from the iframe_clip into the shared clips
+ // that will be applied by this tile cache during compositing.
+ let mut additional_clips = Vec::new();
+
+ if let Some(clip_id) = iframe_clip {
+ additional_clips.push(clip_id);
+ }
+
+ // Find the best shared clip node that we can apply while compositing tiles,
+ // rather than applying to each item individually.
+
+ // Step 1: Walk the primitive list, and find the LCA of the clip-tree that
+ // matches all primitives. This gives us our "best-case" shared
+ // clip node that moves as many clips as possible to compositing.
+ let mut shared_clip_node_id = None;
+
+ for cluster in &prim_list.clusters {
+ for prim_instance in &prim_instances[cluster.prim_range()] {
+ let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
+
+ // TODO(gw): Need to cache last clip-node id here?
+ shared_clip_node_id = match shared_clip_node_id {
+ Some(current) => {
+ Some(clip_tree_builder.find_lowest_common_ancestor(current, leaf.node_id))
+ }
+ None => {
+ Some(leaf.node_id)
+ }
+ }
+ }
+ }
+
+ // Step 2: Now we need to walk up the shared clip node hierarchy, and remove clips
+ // that we can't handle during compositing, such as:
+ // (a) Non axis-aligned clips
+ // (b) Box-shadow or image-mask clips
+ // (c) Rounded rect clips.
+ //
+ // A follow up patch to this series will relax the condition on (c) to
+ // allow tile caches to apply a single rounded-rect clip during compositing.
+ let mut shared_clip_node_id = shared_clip_node_id.unwrap_or(ClipNodeId::NONE);
+ let mut current_node_id = shared_clip_node_id;
+ let mut rounded_rect_count = 0;
+
+ // Walk up the hierarchy to the root of the clip-tree
+ while current_node_id != ClipNodeId::NONE {
+ let node = clip_tree_builder.get_node(current_node_id);
+ let clip_node_data = &interners.clip[node.handle];
+
+ // Check if this clip is in the root coord system (i.e. is axis-aligned with tile-cache)
+ let is_rcs = spatial_tree.is_root_coord_system(clip_node_data.key.spatial_node_index);
+
+ let node_valid = if is_rcs {
+ match clip_node_data.key.kind {
+ ClipItemKeyKind::BoxShadow(..) |
+ ClipItemKeyKind::ImageMask(..) |
+ ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
+ ClipItemKeyKind::RoundedRectangle(_, _, ClipMode::ClipOut) => {
+ // Has a box-shadow / image-mask, we can't handle this as a shared clip
+ false
+ }
+ ClipItemKeyKind::RoundedRectangle(rect, radius, ClipMode::Clip) => {
+ // The shader and CoreAnimation rely on certain constraints such
+ // as uniform radii to be able to apply the clip during compositing.
+ if BorderRadius::from(radius).can_use_fast_path_in(&rect.into()) {
+ rounded_rect_count += 1;
+
+ true
+ } else {
+ false
+ }
+ }
+ ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => {
+ // We can apply multiple (via combining) axis-aligned rectangle
+ // clips to the shared compositing clip.
+ true
+ }
+ }
+ } else {
+ // Has a complex transform, we can't handle this as a shared clip
+ false
+ };
+
+ if node_valid {
+ // This node was found to be one we can apply during compositing.
+ if rounded_rect_count > 1 {
+ // However, we plan to only support one rounded-rect clip. If
+ // we have found > 1 rounded rect, drop children from the shared
+ // clip, and continue looking up the chain.
+ shared_clip_node_id = current_node_id;
+ rounded_rect_count = 1;
+ }
+ } else {
+ // Node was invalid, due to transform / clip type. Drop this clip
+ // and reset the rounded rect count to 0, since we drop children
+ // from here too.
+ shared_clip_node_id = node.parent;
+ rounded_rect_count = 0;
+ }
+
+ current_node_id = node.parent;
+ }
+
+ let shared_clip_leaf_id = Some(clip_tree_builder.build_for_tile_cache(
+ shared_clip_node_id,
+ &additional_clips,
+ ));
+
+ // Build a clip-chain for the tile cache, that contains any of the shared clips
+ // we will apply when drawing the tiles. In all cases provided by Gecko, these
+ // are rectangle clips with a scale/offset transform only, and get handled as
+ // a simple local clip rect in the vertex shader. However, this should in theory
+ // also work with any complex clips, such as rounded rects and image masks, by
+ // producing a clip mask that is applied to the picture cache tiles.
+
+ let slice = tile_cache_pictures.len();
+
+ let background_color = if slice == 0 {
+ background_color
+ } else {
+ None
+ };
+
+ let slice_id = SliceId::new(slice);
+
+ // Store some information about the picture cache slice. This is used when we swap the
+ // new scene into the frame builder to either reuse existing slices, or create new ones.
+ tile_caches.insert(slice_id, TileCacheParams {
+ debug_flags,
+ slice,
+ slice_flags,
+ spatial_node_index: scroll_root,
+ visibility_node_index: visibility_node,
+ background_color,
+ shared_clip_node_id,
+ shared_clip_leaf_id,
+ virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(),
+ image_surface_count: prim_list.image_surface_count,
+ yuv_image_surface_count: prim_list.yuv_image_surface_count,
+ });
+
+ let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image(
+ Some(PictureCompositeMode::TileCache { slice_id }),
+ Picture3DContext::Out,
+ PrimitiveFlags::IS_BACKFACE_VISIBLE,
+ prim_list,
+ scroll_root,
+ RasterSpace::Screen,
+ PictureFlags::empty(),
+ None,
+ ));
+
+ tile_cache_pictures.push(PictureIndex(pic_index));
+}
+
+/// Debug information about a set of picture cache slices, exposed via RenderResults
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PictureCacheDebugInfo {
+ pub slices: FastHashMap<usize, SliceDebugInfo>,
+}
+
+impl PictureCacheDebugInfo {
+ pub fn new() -> Self {
+ PictureCacheDebugInfo {
+ slices: FastHashMap::default(),
+ }
+ }
+
+ /// Convenience method to retrieve a given slice. Deliberately panics
+ /// if the slice isn't present.
+ pub fn slice(&self, slice: usize) -> &SliceDebugInfo {
+ &self.slices[&slice]
+ }
+}
+
+impl Default for PictureCacheDebugInfo {
+ fn default() -> PictureCacheDebugInfo {
+ PictureCacheDebugInfo::new()
+ }
+}
+
+/// Debug information about a set of picture cache tiles, exposed via RenderResults
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SliceDebugInfo {
+ pub tiles: FastHashMap<TileOffset, TileDebugInfo>,
+}
+
+impl SliceDebugInfo {
+ pub fn new() -> Self {
+ SliceDebugInfo {
+ tiles: FastHashMap::default(),
+ }
+ }
+
+ /// Convenience method to retrieve a given tile. Deliberately panics
+ /// if the tile isn't present.
+ pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo {
+ &self.tiles[&TileOffset::new(x, y)]
+ }
+}
+
+/// Debug information about a tile that was dirty and was rasterized
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct DirtyTileDebugInfo {
+ pub local_valid_rect: PictureRect,
+ pub local_dirty_rect: PictureRect,
+}
+
+/// Debug information about the state of a tile
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum TileDebugInfo {
+ /// Tile was occluded by a tile in front of it
+ Occluded,
+ /// Tile was culled (not visible in current display port)
+ Culled,
+ /// Tile was valid (no rasterization was done) and visible
+ Valid,
+ /// Tile was dirty, and was updated
+ Dirty(DirtyTileDebugInfo),
+}
+
+impl TileDebugInfo {
+ pub fn is_occluded(&self) -> bool {
+ match self {
+ TileDebugInfo::Occluded => true,
+ TileDebugInfo::Culled |
+ TileDebugInfo::Valid |
+ TileDebugInfo::Dirty(..) => false,
+ }
+ }
+
+ pub fn is_valid(&self) -> bool {
+ match self {
+ TileDebugInfo::Valid => true,
+ TileDebugInfo::Culled |
+ TileDebugInfo::Occluded |
+ TileDebugInfo::Dirty(..) => false,
+ }
+ }
+
+ pub fn is_culled(&self) -> bool {
+ match self {
+ TileDebugInfo::Culled => true,
+ TileDebugInfo::Valid |
+ TileDebugInfo::Occluded |
+ TileDebugInfo::Dirty(..) => false,
+ }
+ }
+
+ pub fn as_dirty(&self) -> &DirtyTileDebugInfo {
+ match self {
+ TileDebugInfo::Occluded |
+ TileDebugInfo::Culled |
+ TileDebugInfo::Valid => {
+ panic!("not a dirty tile!");
+ }
+ TileDebugInfo::Dirty(ref info) => {
+ info
+ }
+ }
+ }
+}
diff --git a/gfx/wr/webrender/src/visibility.rs b/gfx/wr/webrender/src/visibility.rs
@@ -18,7 +18,8 @@ use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::clip::{ClipChainInstance, ClipTree};
use crate::frame_builder::FrameBuilderConfig;
use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo, TileCacheInstance};
-use crate::picture::{SurfaceIndex, RasterConfig, SubSliceIndex};
+use crate::picture::{SurfaceIndex, RasterConfig};
+use crate::tile_cache::SubSliceIndex;
use crate::prim_store::{ClipTaskIndex, PictureIndex, PrimitiveInstanceKind};
use crate::prim_store::{PrimitiveStore, PrimitiveInstance};
use crate::render_backend::{DataStores, ScratchBuffer};