tor-browser

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

commit ba574911f617033a26b0e10ecc8010db6f2dc398
parent dcd95a8a361720b8af02e30c6ff42eb1c410a6d3
Author: Nicolas Silva <nical@fastmail.com>
Date:   Fri, 12 Dec 2025 14:44:32 +0000

Bug 1998913 - Part 1 - Extract independent data structures. r=gfx-reviewers,gw

Differential Revision: https://phabricator.services.mozilla.com/D275936

Diffstat:
Mgfx/wr/webrender/src/command_buffer.rs | 2+-
Mgfx/wr/webrender/src/composite.rs | 3++-
Agfx/wr/webrender/src/invalidation/mod.rs | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgfx/wr/webrender/src/lib.rs | 9++++++---
Mgfx/wr/webrender/src/picture.rs | 353+++----------------------------------------------------------------------------
Mgfx/wr/webrender/src/render_task.rs | 3++-
Mgfx/wr/webrender/src/renderer/init.rs | 3+--
Mgfx/wr/webrender/src/renderer/mod.rs | 3++-
Mgfx/wr/webrender/src/resource_cache.rs | 2+-
Mgfx/wr/webrender/src/surface.rs | 3++-
Dgfx/wr/webrender/src/tile_cache.rs | 779-------------------------------------------------------------------------------
Agfx/wr/webrender/src/tile_cache/mod.rs | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agfx/wr/webrender/src/tile_cache/slice_builder.rs | 779+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgfx/wr/webrender/src/visibility.rs | 3++-
14 files changed, 1200 insertions(+), 1132 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/resource_cache.rs b/gfx/wr/webrender/src/resource_cache.rs @@ -574,7 +574,7 @@ impl ResourceCache { let cached_glyphs = GlyphCache::new(); let fonts = SharedFontResources::new(IdNamespace(0)); let picture_textures = PictureTextures::new( - crate::picture::TILE_SIZE_DEFAULT, + crate::tile_cache::TILE_SIZE_DEFAULT, TextureFilter::Nearest, ); 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};