commit a3ba0470371be7fc18d6f922c20c790d0d6fd459
parent dfbf5f194d3ffd414f4e983c33268ab12eeee44d
Author: agoloman <agoloman@mozilla.com>
Date: Fri, 12 Dec 2025 10:54:29 +0200
Revert "Bug 1998913 - Part 4 - Move SVG filter code into its own module. r=gfx-reviewers,gw" for causing WR bustages.
This reverts commit dfbf5f194d3ffd414f4e983c33268ab12eeee44d.
Revert "Bug 1998913 - Part 3 - Extract surface-related code from picture.rs. r=gfx-reviewers,gw"
This reverts commit ae7e71f9d748c47898dbc244a5758809ef87fe4b.
Revert "Bug 1998913 - Part 2 - Extract dependency tracking and quadtree. r=gfx-reviewers,gw"
This reverts commit 531e824b59231ebd450e8ba25ac5206be710edaf.
Revert "Bug 1998913 - Part 1 - Extract independent data structures. r=gfx-reviewers,gw"
This reverts commit 456ecdfabef1693737fce2795682bb1ca4dce3c4.
Diffstat:
24 files changed, 6715 insertions(+), 6885 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, tile_cache::TileKey, renderer::GpuBufferAddress, FastHashMap, prim_store::PrimitiveInstanceIndex};
+use crate::{spatial_tree::SpatialNodeIndex, render_task_graph::RenderTaskId, surface::SurfaceTileDescriptor, picture::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,9 +10,7 @@ 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};
-use crate::tile_cache::{TileCacheInstance, TileSurface};
-use crate::tile_cache::TileId;
+use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileId, TileSurface};
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/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs
@@ -16,8 +16,7 @@ use crate::debug_item::DebugItem;
use crate::gpu_types::{ImageBrushPrimitiveData, PrimitiveHeaders, TransformPalette, ZBufferIdGenerator};
use crate::gpu_types::{QuadSegment, TransformData};
use crate::internal_types::{FastHashMap, PlaneSplitter, FrameStamp};
-use crate::picture::{DirtyRegion};
-use crate::tile_cache::{SliceId, TileCacheInstance};
+use crate::picture::{DirtyRegion, SliceId, TileCacheInstance};
use crate::picture::{SurfaceInfo, SurfaceIndex, ResolvedSurfaceTexture};
use crate::picture::{SubpixelMode, RasterConfig, PictureCompositeMode};
use crate::prepare::prepare_picture;
diff --git a/gfx/wr/webrender/src/internal_types.rs b/gfx/wr/webrender/src/internal_types.rs
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ColorF, DocumentId, ExternalImageId, PrimitiveFlags, Parameter, RenderReasons};
-use api::{ImageFormat, NotificationRequest, Shadow, FilterOp, ImageBufferKind};
+use api::{ImageFormat, NotificationRequest, Shadow, FilterOpGraphPictureBufferId, FilterOpGraphPictureReference, FilterOpGraphNode, FilterOp, ImageBufferKind};
use api::{FramePublishId, TextureCacheCategory};
use api::units::*;
use crate::render_api::DebugCommand;
@@ -15,7 +15,7 @@ use crate::frame_builder::Frame;
use crate::profiler::TransactionProfile;
use crate::spatial_tree::SpatialNodeIndex;
use crate::prim_store::PrimitiveInstanceIndex;
-use crate::svg_filter::{FilterGraphNode, FilterGraphOp, FilterGraphPictureReference};
+use crate::filterdata::FilterDataHandle;
use rustc_hash::FxHasher;
use plane_split::BspSplitter;
use smallvec::SmallVec;
@@ -215,6 +215,555 @@ pub struct PlaneSplitterIndex(pub usize);
/// An arbitrary number which we assume opacity is invisible below.
const OPACITY_EPSILON: f32 = 0.001;
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FilterGraphPictureReference {
+ /// Id of the picture in question in a namespace unique to this filter DAG,
+ /// some are special values like
+ /// FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic.
+ pub buffer_id: FilterOpGraphPictureBufferId,
+ /// Set by wrap_prim_with_filters to the subregion of the input node, may
+ /// also have been offset for feDropShadow or feOffset
+ pub subregion: LayoutRect,
+ /// During scene build this is the offset to apply to the input subregion
+ /// for feOffset, which can be optimized away by pushing its offset and
+ /// subregion crop to downstream nodes. This is always zero in render tasks
+ /// where it has already been applied to subregion by that point. Not used
+ /// in get_coverage_svgfe because source_padding/target_padding represent
+ /// the offset there.
+ pub offset: LayoutVector2D,
+ /// Equal to the inflate value of the referenced buffer, or 0
+ pub inflate: i16,
+ /// Padding on each side to represent how this input is read relative to the
+ /// node's output subregion, this represents what the operation needs to
+ /// read from ths input, which may be blurred or offset.
+ pub source_padding: LayoutRect,
+ /// Padding on each side to represent how this input affects the node's
+ /// subregion, this can be used to calculate target subregion based on
+ /// SourceGraphic subregion. This is usually equal to source_padding except
+ /// offset in the opposite direction, inflates typically do the same thing
+ /// to both types of padding.
+ pub target_padding: LayoutRect,
+}
+
+impl From<FilterOpGraphPictureReference> for FilterGraphPictureReference {
+ fn from(pic: FilterOpGraphPictureReference) -> Self {
+ FilterGraphPictureReference{
+ buffer_id: pic.buffer_id,
+ // All of these are set by wrap_prim_with_filters
+ subregion: LayoutRect::zero(),
+ offset: LayoutVector2D::zero(),
+ inflate: 0,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }
+ }
+}
+
+pub const SVGFE_CONVOLVE_DIAMETER_LIMIT: usize = 5;
+pub const SVGFE_CONVOLVE_VALUES_LIMIT: usize = SVGFE_CONVOLVE_DIAMETER_LIMIT *
+ SVGFE_CONVOLVE_DIAMETER_LIMIT;
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum FilterGraphOp {
+ /// Filter that copies the SourceGraphic image into the specified subregion,
+ /// This is intentionally the only way to get SourceGraphic into the graph,
+ /// as the filter region must be applied before it is used.
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - no inputs, no linear
+ SVGFESourceGraphic,
+ /// Filter that copies the SourceAlpha image into the specified subregion,
+ /// This is intentionally the only way to get SourceAlpha into the graph,
+ /// as the filter region must be applied before it is used.
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - no inputs, no linear
+ SVGFESourceAlpha,
+ /// Filter that does no transformation of the colors, used to implement a
+ /// few things like SVGFEOffset, and this is the default value in
+ /// impl_default_for_enums.
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input with offset
+ SVGFEIdentity,
+ /// represents CSS opacity property as a graph node like the rest of the
+ /// SVGFE* filters
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ SVGFEOpacity{valuebinding: api::PropertyBinding<f32>, value: f32},
+ /// convert a color image to an alpha channel - internal use; generated by
+ /// SVGFilterInstance::GetOrCreateSourceAlphaIndex().
+ SVGFEToAlpha,
+ /// combine 2 images with SVG_FEBLEND_MODE_DARKEN
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendDarken,
+ /// combine 2 images with SVG_FEBLEND_MODE_LIGHTEN
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendLighten,
+ /// combine 2 images with SVG_FEBLEND_MODE_MULTIPLY
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendMultiply,
+ /// combine 2 images with SVG_FEBLEND_MODE_NORMAL
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendNormal,
+ /// combine 2 images with SVG_FEBLEND_MODE_SCREEN
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendScreen,
+ /// combine 2 images with SVG_FEBLEND_MODE_OVERLAY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendOverlay,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR_DODGE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColorDodge,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR_BURN
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColorBurn,
+ /// combine 2 images with SVG_FEBLEND_MODE_HARD_LIGHT
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendHardLight,
+ /// combine 2 images with SVG_FEBLEND_MODE_SOFT_LIGHT
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendSoftLight,
+ /// combine 2 images with SVG_FEBLEND_MODE_DIFFERENCE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendDifference,
+ /// combine 2 images with SVG_FEBLEND_MODE_EXCLUSION
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendExclusion,
+ /// combine 2 images with SVG_FEBLEND_MODE_HUE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendHue,
+ /// combine 2 images with SVG_FEBLEND_MODE_SATURATION
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendSaturation,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColor,
+ /// combine 2 images with SVG_FEBLEND_MODE_LUMINOSITY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendLuminosity,
+ /// transform colors of image through 5x4 color matrix (transposed for
+ /// efficiency)
+ /// parameters: FilterGraphNode, matrix[5][4]
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feColorMatrixElement
+ SVGFEColorMatrix{values: [f32; 20]},
+ /// transform colors of image through configurable gradients with component
+ /// swizzle
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feComponentTransferElement
+ SVGFEComponentTransfer,
+ /// Processed version of SVGFEComponentTransfer with the FilterData
+ /// replaced by an interned handle, this is made in wrap_prim_with_filters.
+ /// Aside from the interned handle, creates_pixels indicates if the transfer
+ /// parameters will probably fill the entire subregion with non-zero alpha.
+ SVGFEComponentTransferInterned{handle: FilterDataHandle, creates_pixels: bool},
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode, k1, k2, k3, k4
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeArithmetic{k1: f32, k2: f32, k3: f32, k4: f32},
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeATop,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeIn,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Docs: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite
+ SVGFECompositeLighter,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeOut,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeOver,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeXOR,
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
+ /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeDuplicate{order_x: i32, order_y: i32,
+ kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
+ target_x: i32, target_y: i32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, preserve_alpha: i32},
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
+ /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeNone{order_x: i32, order_y: i32,
+ kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
+ target_x: i32, target_y: i32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, preserve_alpha: i32},
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
+ /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeWrap{order_x: i32, order_y: i32,
+ kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
+ target_x: i32, target_y: i32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, preserve_alpha: i32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// distant light source with specified direction
+ /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
+ SVGFEDiffuseLightingDistant{surface_scale: f32, diffuse_constant: f32,
+ kernel_unit_length_x: f32, kernel_unit_length_y: f32, azimuth: f32,
+ elevation: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// point light source at specified location
+ /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, x, y, z
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
+ SVGFEDiffuseLightingPoint{surface_scale: f32, diffuse_constant: f32,
+ kernel_unit_length_x: f32, kernel_unit_length_y: f32, x: f32, y: f32,
+ z: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// spot light source at specified location pointing at specified target
+ /// location with specified hotspot sharpness and cone angle
+ /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, x, y, z, pointsAtX, pointsAtY,
+ /// pointsAtZ, specularExponent, limitingConeAngle
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
+ SVGFEDiffuseLightingSpot{surface_scale: f32, diffuse_constant: f32,
+ kernel_unit_length_x: f32, kernel_unit_length_y: f32, x: f32, y: f32,
+ z: f32, points_at_x: f32, points_at_y: f32, points_at_z: f32,
+ cone_exponent: f32, limiting_cone_angle: f32},
+ /// calculate a distorted version of first input image using offset values
+ /// from second input image at specified intensity
+ /// parameters: FilterGraphNode, scale, xChannelSelector, yChannelSelector
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDisplacementMapElement
+ SVGFEDisplacementMap{scale: f32, x_channel_selector: u32,
+ y_channel_selector: u32},
+ /// create and merge a dropshadow version of the specified image's alpha
+ /// channel with specified offset and blur radius
+ /// parameters: FilterGraphNode, flood_color, flood_opacity, dx, dy,
+ /// stdDeviationX, stdDeviationY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDropShadowElement
+ SVGFEDropShadow{color: ColorF, dx: f32, dy: f32, std_deviation_x: f32,
+ std_deviation_y: f32},
+ /// synthesize a new image of specified size containing a solid color
+ /// parameters: FilterGraphNode, color
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEFloodElement
+ SVGFEFlood{color: ColorF},
+ /// create a blurred version of the input image
+ /// parameters: FilterGraphNode, stdDeviationX, stdDeviationY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEGaussianBlurElement
+ SVGFEGaussianBlur{std_deviation_x: f32, std_deviation_y: f32},
+ /// synthesize a new image based on a url (i.e. blob image source)
+ /// parameters: FilterGraphNode,
+ /// samplingFilter (see SamplingFilter in Types.h), transform
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEImageElement
+ SVGFEImage{sampling_filter: u32, matrix: [f32; 6]},
+ /// create a new image based on the input image with the contour stretched
+ /// outward (dilate operator)
+ /// parameters: FilterGraphNode, radiusX, radiusY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
+ SVGFEMorphologyDilate{radius_x: f32, radius_y: f32},
+ /// create a new image based on the input image with the contour shrunken
+ /// inward (erode operator)
+ /// parameters: FilterGraphNode, radiusX, radiusY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
+ SVGFEMorphologyErode{radius_x: f32, radius_y: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// distant light source with specified direction
+ /// parameters: FilerData, surfaceScale, specularConstant, specularExponent,
+ /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
+ SVGFESpecularLightingDistant{surface_scale: f32, specular_constant: f32,
+ specular_exponent: f32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, azimuth: f32, elevation: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// point light source at specified location
+ /// parameters: FilterGraphNode, surfaceScale, specularConstant,
+ /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
+ SVGFESpecularLightingPoint{surface_scale: f32, specular_constant: f32,
+ specular_exponent: f32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, x: f32, y: f32, z: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// spot light source at specified location pointing at specified target
+ /// location with specified hotspot sharpness and cone angle
+ /// parameters: FilterGraphNode, surfaceScale, specularConstant,
+ /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z,
+ /// pointsAtX, pointsAtY, pointsAtZ, specularExponent, limitingConeAngle
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
+ SVGFESpecularLightingSpot{surface_scale: f32, specular_constant: f32,
+ specular_exponent: f32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, x: f32, y: f32, z: f32, points_at_x: f32,
+ points_at_y: f32, points_at_z: f32, cone_exponent: f32,
+ limiting_cone_angle: f32},
+ /// create a new image based on the input image, repeated throughout the
+ /// output rectangle
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETileElement
+ SVGFETile,
+ /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
+ /// stitching mode
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithFractalNoiseWithNoStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
+ /// stitching mode
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithFractalNoiseWithStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Turbulence Noise (offset vectors)
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Turbulence Noise (offset vectors)
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithTurbulenceNoiseWithStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+}
+
+impl FilterGraphOp {
+ pub fn kind(&self) -> &'static str {
+ match *self {
+ FilterGraphOp::SVGFEBlendColor => "SVGFEBlendColor",
+ FilterGraphOp::SVGFEBlendColorBurn => "SVGFEBlendColorBurn",
+ FilterGraphOp::SVGFEBlendColorDodge => "SVGFEBlendColorDodge",
+ FilterGraphOp::SVGFEBlendDarken => "SVGFEBlendDarken",
+ FilterGraphOp::SVGFEBlendDifference => "SVGFEBlendDifference",
+ FilterGraphOp::SVGFEBlendExclusion => "SVGFEBlendExclusion",
+ FilterGraphOp::SVGFEBlendHardLight => "SVGFEBlendHardLight",
+ FilterGraphOp::SVGFEBlendHue => "SVGFEBlendHue",
+ FilterGraphOp::SVGFEBlendLighten => "SVGFEBlendLighten",
+ FilterGraphOp::SVGFEBlendLuminosity => "SVGFEBlendLuminosity",
+ FilterGraphOp::SVGFEBlendMultiply => "SVGFEBlendMultiply",
+ FilterGraphOp::SVGFEBlendNormal => "SVGFEBlendNormal",
+ FilterGraphOp::SVGFEBlendOverlay => "SVGFEBlendOverlay",
+ FilterGraphOp::SVGFEBlendSaturation => "SVGFEBlendSaturation",
+ FilterGraphOp::SVGFEBlendScreen => "SVGFEBlendScreen",
+ FilterGraphOp::SVGFEBlendSoftLight => "SVGFEBlendSoftLight",
+ FilterGraphOp::SVGFEColorMatrix{..} => "SVGFEColorMatrix",
+ FilterGraphOp::SVGFEComponentTransfer => "SVGFEComponentTransfer",
+ FilterGraphOp::SVGFEComponentTransferInterned{..} => "SVGFEComponentTransferInterned",
+ FilterGraphOp::SVGFECompositeArithmetic{..} => "SVGFECompositeArithmetic",
+ FilterGraphOp::SVGFECompositeATop => "SVGFECompositeATop",
+ FilterGraphOp::SVGFECompositeIn => "SVGFECompositeIn",
+ FilterGraphOp::SVGFECompositeLighter => "SVGFECompositeLighter",
+ FilterGraphOp::SVGFECompositeOut => "SVGFECompositeOut",
+ FilterGraphOp::SVGFECompositeOver => "SVGFECompositeOver",
+ FilterGraphOp::SVGFECompositeXOR => "SVGFECompositeXOR",
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => "SVGFEConvolveMatrixEdgeModeDuplicate",
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => "SVGFEConvolveMatrixEdgeModeNone",
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => "SVGFEConvolveMatrixEdgeModeWrap",
+ FilterGraphOp::SVGFEDiffuseLightingDistant{..} => "SVGFEDiffuseLightingDistant",
+ FilterGraphOp::SVGFEDiffuseLightingPoint{..} => "SVGFEDiffuseLightingPoint",
+ FilterGraphOp::SVGFEDiffuseLightingSpot{..} => "SVGFEDiffuseLightingSpot",
+ FilterGraphOp::SVGFEDisplacementMap{..} => "SVGFEDisplacementMap",
+ FilterGraphOp::SVGFEDropShadow{..} => "SVGFEDropShadow",
+ FilterGraphOp::SVGFEFlood{..} => "SVGFEFlood",
+ FilterGraphOp::SVGFEGaussianBlur{..} => "SVGFEGaussianBlur",
+ FilterGraphOp::SVGFEIdentity => "SVGFEIdentity",
+ FilterGraphOp::SVGFEImage{..} => "SVGFEImage",
+ FilterGraphOp::SVGFEMorphologyDilate{..} => "SVGFEMorphologyDilate",
+ FilterGraphOp::SVGFEMorphologyErode{..} => "SVGFEMorphologyErode",
+ FilterGraphOp::SVGFEOpacity{..} => "SVGFEOpacity",
+ FilterGraphOp::SVGFESourceAlpha => "SVGFESourceAlpha",
+ FilterGraphOp::SVGFESourceGraphic => "SVGFESourceGraphic",
+ FilterGraphOp::SVGFESpecularLightingDistant{..} => "SVGFESpecularLightingDistant",
+ FilterGraphOp::SVGFESpecularLightingPoint{..} => "SVGFESpecularLightingPoint",
+ FilterGraphOp::SVGFESpecularLightingSpot{..} => "SVGFESpecularLightingSpot",
+ FilterGraphOp::SVGFETile => "SVGFETile",
+ FilterGraphOp::SVGFEToAlpha => "SVGFEToAlpha",
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} => "SVGFETurbulenceWithFractalNoiseWithNoStitching",
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} => "SVGFETurbulenceWithFractalNoiseWithStitching",
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} => "SVGFETurbulenceWithTurbulenceNoiseWithNoStitching",
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => "SVGFETurbulenceWithTurbulenceNoiseWithStitching",
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FilterGraphNode {
+ /// Indicates this graph node was marked as necessary by the DAG optimizer
+ pub kept_by_optimizer: bool,
+ /// true if color_interpolation_filter == LinearRgb; shader will convert
+ /// sRGB texture pixel colors on load and convert back on store, for correct
+ /// interpolation
+ pub linear: bool,
+ /// padding for output rect if we need a border to get correct clamping, or
+ /// to account for larger final subregion than source rect (see bug 1869672)
+ pub inflate: i16,
+ /// virtualized picture input bindings, these refer to other filter outputs
+ /// by number within the graph, usually there is one element
+ pub inputs: Vec<FilterGraphPictureReference>,
+ /// clipping rect for filter node output
+ pub subregion: LayoutRect,
+}
+
+impl From<FilterOpGraphNode> for FilterGraphNode {
+ fn from(node: FilterOpGraphNode) -> Self {
+ let mut inputs: Vec<FilterGraphPictureReference> = Vec::new();
+ if node.input.buffer_id != FilterOpGraphPictureBufferId::None {
+ inputs.push(node.input.into());
+ }
+ if node.input2.buffer_id != FilterOpGraphPictureBufferId::None {
+ inputs.push(node.input2.into());
+ }
+ // If the op used by this node is a feMerge, it will add more inputs
+ // after this invocation.
+ FilterGraphNode{
+ linear: node.linear,
+ inputs,
+ subregion: node.subregion,
+ // These are computed later in scene_building
+ kept_by_optimizer: true,
+ inflate: 0,
+ }
+ }
+}
+
+
/// Equivalent to api::FilterOp with added internal information
#[derive(Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
diff --git a/gfx/wr/webrender/src/invalidation/dependency.rs b/gfx/wr/webrender/src/invalidation/dependency.rs
@@ -1,425 +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/. */
-
-//! Dependency tracking for tile invalidation
-//!
-//! This module contains types and logic for tracking dependencies that affect
-//! tile invalidation, including transform comparisons, spatial node tracking,
-//! and primitive comparison.
-
-use api::{ImageKey, PropertyBindingId, ColorU};
-use euclid::approxeq::ApproxEq;
-use crate::PrimitiveCompareResult;
-use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, CoordinateSpaceMapping};
-use crate::internal_types::{FastHashMap, FastHashSet, FrameId};
-use crate::intern::ItemUid;
-use crate::resource_cache::{ResourceCache, ImageGeneration};
-use crate::tile_cache::{TileDescriptor, PrimitiveDescriptor, PrimitiveDependencyIndex};
-use peek_poke::{PeekPoke, peek_from_slice};
-use std::collections::hash_map::Entry;
-
-/// A comparable transform matrix, that compares with epsilon checks.
-#[derive(Debug, Clone)]
-pub struct MatrixKey {
- pub m: [f32; 16],
-}
-
-impl PartialEq for MatrixKey {
- fn eq(&self, other: &Self) -> bool {
- const EPSILON: f32 = 0.001;
-
- // TODO(gw): It's possible that we may need to adjust the epsilon
- // to be tighter on most of the matrix, except the
- // translation parts?
- for (i, j) in self.m.iter().zip(other.m.iter()) {
- if !i.approx_eq_eps(j, &EPSILON) {
- return false;
- }
- }
-
- true
- }
-}
-
-/// A comparable scale-offset, that compares with epsilon checks.
-#[derive(Debug, Clone)]
-pub struct ScaleOffsetKey {
- pub sx: f32,
- pub sy: f32,
- pub tx: f32,
- pub ty: f32,
-}
-
-impl PartialEq for ScaleOffsetKey {
- fn eq(&self, other: &Self) -> bool {
- const EPSILON: f32 = 0.001;
-
- self.sx.approx_eq_eps(&other.sx, &EPSILON) &&
- self.sy.approx_eq_eps(&other.sy, &EPSILON) &&
- self.tx.approx_eq_eps(&other.tx, &EPSILON) &&
- self.ty.approx_eq_eps(&other.ty, &EPSILON)
- }
-}
-
-/// A comparable / hashable version of a coordinate space mapping. Used to determine
-/// if a transform dependency for a tile has changed.
-#[derive(Debug, PartialEq, Clone)]
-pub enum TransformKey {
- Local,
- ScaleOffset {
- so: ScaleOffsetKey,
- },
- Transform {
- m: MatrixKey,
- }
-}
-
-impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
- fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey {
- match transform {
- CoordinateSpaceMapping::Local => {
- TransformKey::Local
- }
- CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
- TransformKey::ScaleOffset {
- so: ScaleOffsetKey {
- sx: scale_offset.scale.x,
- sy: scale_offset.scale.y,
- tx: scale_offset.offset.x,
- ty: scale_offset.offset.y,
- }
- }
- }
- CoordinateSpaceMapping::Transform(ref m) => {
- TransformKey::Transform {
- m: MatrixKey {
- m: m.to_array(),
- },
- }
- }
- }
- }
-}
-
-/// Get the transform key for a spatial node relative to a cache spatial node.
-pub fn get_transform_key(
- spatial_node_index: SpatialNodeIndex,
- cache_spatial_node_index: SpatialNodeIndex,
- spatial_tree: &SpatialTree,
-) -> TransformKey {
- spatial_tree.get_relative_transform(
- spatial_node_index,
- cache_spatial_node_index,
- ).into()
-}
-
-
-/// Information about the state of a binding.
-#[derive(Debug)]
-pub struct BindingInfo<T> {
- /// The current value retrieved from dynamic scene properties.
- pub value: T,
- /// True if it was changed (or is new) since the last frame build.
- pub changed: bool,
-}
-
-/// Information stored in a tile descriptor for a binding.
-#[derive(Debug, PartialEq, Clone, Copy, PeekPoke)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum Binding<T> {
- Value(T),
- Binding(PropertyBindingId),
-}
-
-impl<T: Default> Default for Binding<T> {
- fn default() -> Self {
- Binding::Value(T::default())
- }
-}
-
-impl<T> From<api::PropertyBinding<T>> for Binding<T> {
- fn from(binding: api::PropertyBinding<T>) -> Binding<T> {
- match binding {
- api::PropertyBinding::Binding(key, _) => Binding::Binding(key.id),
- api::PropertyBinding::Value(value) => Binding::Value(value),
- }
- }
-}
-
-pub type OpacityBinding = Binding<f32>;
-pub type OpacityBindingInfo = BindingInfo<f32>;
-
-pub type ColorBinding = Binding<ColorU>;
-pub type ColorBindingInfo = BindingInfo<ColorU>;
-
-/// Types of dependencies that a primitive can have
-#[derive(PeekPoke)]
-pub enum PrimitiveDependency {
- OpacityBinding {
- binding: OpacityBinding,
- },
- ColorBinding {
- binding: ColorBinding,
- },
- SpatialNode {
- index: SpatialNodeIndex,
- },
- Clip {
- clip: ItemUid,
- },
- Image {
- image: ImageDependency,
- },
-}
-
-/// Information stored an image dependency
-#[derive(Debug, Copy, Clone, PartialEq, PeekPoke, Default)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ImageDependency {
- pub key: ImageKey,
- pub generation: ImageGeneration,
-}
-
-impl ImageDependency {
- pub const INVALID: ImageDependency = ImageDependency {
- key: ImageKey::DUMMY,
- generation: ImageGeneration::INVALID,
- };
-}
-
-/// A dependency for a transform is defined by the spatial node index + frame it was used
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PeekPoke, Default)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct SpatialNodeKey {
- pub spatial_node_index: SpatialNodeIndex,
- pub frame_id: FrameId,
-}
-
-/// A helper for comparing spatial nodes between frames. The comparisons
-/// are done by-value, so that if the shape of the spatial node tree
-/// changes, invalidations aren't done simply due to the spatial node
-/// index changing between display lists.
-pub struct SpatialNodeComparer {
- /// The root spatial node index of the tile cache
- ref_spatial_node_index: SpatialNodeIndex,
- /// Maintains a map of currently active transform keys
- spatial_nodes: FastHashMap<SpatialNodeKey, TransformKey>,
- /// A cache of recent comparisons between prev and current spatial nodes
- compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>,
- /// A set of frames that we need to retain spatial node entries for
- referenced_frames: FastHashSet<FrameId>,
-}
-
-impl SpatialNodeComparer {
- /// Construct a new comparer
- pub fn new() -> Self {
- SpatialNodeComparer {
- ref_spatial_node_index: SpatialNodeIndex::INVALID,
- spatial_nodes: FastHashMap::default(),
- compare_cache: FastHashMap::default(),
- referenced_frames: FastHashSet::default(),
- }
- }
-
- /// Advance to the next frame
- pub fn next_frame(
- &mut self,
- ref_spatial_node_index: SpatialNodeIndex,
- ) {
- // Drop any node information for unreferenced frames, to ensure that the
- // hashmap doesn't grow indefinitely!
- let referenced_frames = &self.referenced_frames;
- self.spatial_nodes.retain(|key, _| {
- referenced_frames.contains(&key.frame_id)
- });
-
- // Update the root spatial node for this comparer
- self.ref_spatial_node_index = ref_spatial_node_index;
- self.compare_cache.clear();
- self.referenced_frames.clear();
- }
-
- /// Register a transform that is used, and build the transform key for it if new.
- pub fn register_used_transform(
- &mut self,
- spatial_node_index: SpatialNodeIndex,
- frame_id: FrameId,
- spatial_tree: &SpatialTree,
- ) {
- let key = SpatialNodeKey {
- spatial_node_index,
- frame_id,
- };
-
- if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) {
- entry.insert(
- get_transform_key(
- spatial_node_index,
- self.ref_spatial_node_index,
- spatial_tree,
- )
- );
- }
- }
-
- /// Return true if the transforms for two given spatial nodes are considered equivalent
- pub fn are_transforms_equivalent(
- &mut self,
- prev_spatial_node_key: &SpatialNodeKey,
- curr_spatial_node_key: &SpatialNodeKey,
- ) -> bool {
- let key = (*prev_spatial_node_key, *curr_spatial_node_key);
- let spatial_nodes = &self.spatial_nodes;
-
- *self.compare_cache
- .entry(key)
- .or_insert_with(|| {
- let prev = &spatial_nodes[&prev_spatial_node_key];
- let curr = &spatial_nodes[&curr_spatial_node_key];
- curr == prev
- })
- }
-
- /// Ensure that the comparer won't GC any nodes for a given frame id
- pub fn retain_for_frame(&mut self, frame_id: FrameId) {
- self.referenced_frames.insert(frame_id);
- }
-}
-
-
-/// A key for storing primitive comparison results during tile dependency tests.
-#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-pub struct PrimitiveComparisonKey {
- pub prev_index: PrimitiveDependencyIndex,
- pub curr_index: PrimitiveDependencyIndex,
-}
-
-/// A helper struct to compare a primitive and all its sub-dependencies.
-pub struct PrimitiveComparer<'a> {
- prev_data: &'a [u8],
- curr_data: &'a [u8],
- prev_frame_id: FrameId,
- curr_frame_id: FrameId,
- resource_cache: &'a ResourceCache,
- spatial_node_comparer: &'a mut SpatialNodeComparer,
- opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
- color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
-}
-
-impl<'a> PrimitiveComparer<'a> {
- pub fn new(
- prev: &'a TileDescriptor,
- curr: &'a TileDescriptor,
- resource_cache: &'a ResourceCache,
- spatial_node_comparer: &'a mut SpatialNodeComparer,
- opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
- color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
- ) -> Self {
- PrimitiveComparer {
- prev_data: &prev.dep_data,
- curr_data: &curr.dep_data,
- prev_frame_id: prev.last_updated_frame_id,
- curr_frame_id: curr.last_updated_frame_id,
- resource_cache,
- spatial_node_comparer,
- opacity_bindings,
- color_bindings,
- }
- }
-
- /// Check if two primitive descriptors are the same.
- pub fn compare_prim(
- &mut self,
- prev_desc: &PrimitiveDescriptor,
- curr_desc: &PrimitiveDescriptor,
- ) -> PrimitiveCompareResult {
- let resource_cache = self.resource_cache;
- let spatial_node_comparer = &mut self.spatial_node_comparer;
- let opacity_bindings = self.opacity_bindings;
- let color_bindings = self.color_bindings;
-
- // Check equality of the PrimitiveDescriptor
- if prev_desc != curr_desc {
- return PrimitiveCompareResult::Descriptor;
- }
-
- let mut prev_dep_data = &self.prev_data[prev_desc.dep_offset as usize ..];
- let mut curr_dep_data = &self.curr_data[curr_desc.dep_offset as usize ..];
-
- let mut prev_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID };
- let mut curr_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID };
-
- debug_assert_eq!(prev_desc.dep_count, curr_desc.dep_count);
-
- for _ in 0 .. prev_desc.dep_count {
- prev_dep_data = peek_from_slice(prev_dep_data, &mut prev_dep);
- curr_dep_data = peek_from_slice(curr_dep_data, &mut curr_dep);
-
- match (&prev_dep, &curr_dep) {
- (PrimitiveDependency::Clip { clip: prev }, PrimitiveDependency::Clip { clip: curr }) => {
- if prev != curr {
- return PrimitiveCompareResult::Clip;
- }
- }
- (PrimitiveDependency::SpatialNode { index: prev }, PrimitiveDependency::SpatialNode { index: curr }) => {
- let prev_key = SpatialNodeKey {
- spatial_node_index: *prev,
- frame_id: self.prev_frame_id,
- };
- let curr_key = SpatialNodeKey {
- spatial_node_index: *curr,
- frame_id: self.curr_frame_id,
- };
- if !spatial_node_comparer.are_transforms_equivalent(&prev_key, &curr_key) {
- return PrimitiveCompareResult::Transform;
- }
- }
- (PrimitiveDependency::OpacityBinding { binding: prev }, PrimitiveDependency::OpacityBinding { binding: curr }) => {
- if prev != curr {
- return PrimitiveCompareResult::OpacityBinding;
- }
-
- if let OpacityBinding::Binding(id) = curr {
- if opacity_bindings
- .get(id)
- .map_or(true, |info| info.changed) {
- return PrimitiveCompareResult::OpacityBinding;
- }
- }
- }
- (PrimitiveDependency::ColorBinding { binding: prev }, PrimitiveDependency::ColorBinding { binding: curr }) => {
- if prev != curr {
- return PrimitiveCompareResult::ColorBinding;
- }
-
- if let ColorBinding::Binding(id) = curr {
- if color_bindings
- .get(id)
- .map_or(true, |info| info.changed) {
- return PrimitiveCompareResult::ColorBinding;
- }
- }
- }
- (PrimitiveDependency::Image { image: prev }, PrimitiveDependency::Image { image: curr }) => {
- if prev != curr {
- return PrimitiveCompareResult::Image;
- }
-
- if resource_cache.get_image_generation(curr.key) != curr.generation {
- return PrimitiveCompareResult::Image;
- }
- }
- _ => {
- // There was a mismatch between types of dependencies, so something changed
- return PrimitiveCompareResult::Descriptor;
- }
- }
- }
-
- PrimitiveCompareResult::Equal
- }
-}
diff --git a/gfx/wr/webrender/src/invalidation/mod.rs b/gfx/wr/webrender/src/invalidation/mod.rs
@@ -1,158 +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/. */
-
-//! 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.
-
-pub mod dependency;
-pub mod quadtree;
-
-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/invalidation/quadtree.rs b/gfx/wr/webrender/src/invalidation/quadtree.rs
@@ -1,440 +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/. */
-
-//! Quadtree-based dirty region tracking for tiles
-//!
-//! This module implements a quadtree data structure that tracks which regions
-//! of a tile have been invalidated. The quadtree can dynamically split and merge
-//! nodes based on invalidation patterns to optimize tracking.
-
-use api::units::*;
-use crate::debug_colors;
-use crate::internal_types::FastHashMap;
-use crate::prim_store::PrimitiveScratchBuffer;
-use crate::space::SpaceMapper;
-use crate::tile_cache::{PrimitiveDescriptor, PrimitiveDependencyIndex};
-use crate::invalidation::{InvalidationReason, PrimitiveCompareResult};
-use crate::invalidation::dependency::PrimitiveComparer;
-use crate::visibility::FrameVisibilityContext;
-use std::mem;
-
-
-/// Details for a node in a quadtree that tracks dirty rects for a tile.
-#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum TileNodeKind {
- Leaf {
- /// The index buffer of primitives that affected this tile previous frame
- #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
- prev_indices: Vec<PrimitiveDependencyIndex>,
- /// The index buffer of primitives that affect this tile on this frame
- #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
- curr_indices: Vec<PrimitiveDependencyIndex>,
- /// A bitset of which of the last 64 frames have been dirty for this leaf.
- #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
- dirty_tracker: u64,
- /// The number of frames since this node split or merged.
- #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
- frames_since_modified: usize,
- },
- Node {
- /// The four children of this node
- children: Vec<TileNode>,
- },
-}
-
-/// The kind of modification that a tile wants to do
-#[derive(Copy, Clone, PartialEq, Debug)]
-enum TileModification {
- Split,
- Merge,
-}
-
-/// A node in the dirty rect tracking quadtree.
-#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct TileNode {
- /// Leaf or internal node
- pub kind: TileNodeKind,
- /// Rect of this node in the same space as the tile cache picture
- pub rect: PictureBox2D,
-}
-
-impl TileNode {
- /// Construct a new leaf node, with the given primitive dependency index buffer
- pub fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self {
- TileNode {
- kind: TileNodeKind::Leaf {
- prev_indices: Vec::new(),
- curr_indices,
- dirty_tracker: 0,
- frames_since_modified: 0,
- },
- rect: PictureBox2D::zero(),
- }
- }
-
- /// Draw debug information about this tile node
- pub fn draw_debug_rects(
- &self,
- pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
- is_opaque: bool,
- local_valid_rect: PictureRect,
- scratch: &mut PrimitiveScratchBuffer,
- global_device_pixel_scale: DevicePixelScale,
- ) {
- match self.kind {
- TileNodeKind::Leaf { dirty_tracker, .. } => {
- let color = if (dirty_tracker & 1) != 0 {
- debug_colors::RED
- } else if is_opaque {
- debug_colors::GREEN
- } else {
- debug_colors::YELLOW
- };
-
- if let Some(local_rect) = local_valid_rect.intersection(&self.rect) {
- let world_rect = pic_to_world_mapper
- .map(&local_rect)
- .unwrap();
- let device_rect = world_rect * global_device_pixel_scale;
-
- let outer_color = color.scale_alpha(0.3);
- let inner_color = outer_color.scale_alpha(0.5);
- scratch.push_debug_rect(
- device_rect.inflate(-3.0, -3.0),
- 1,
- outer_color,
- inner_color
- );
- }
- }
- TileNodeKind::Node { ref children, .. } => {
- for child in children.iter() {
- child.draw_debug_rects(
- pic_to_world_mapper,
- is_opaque,
- local_valid_rect,
- scratch,
- global_device_pixel_scale,
- );
- }
- }
- }
- }
-
- /// Calculate the four child rects for a given node
- fn get_child_rects(
- rect: &PictureBox2D,
- result: &mut [PictureBox2D; 4],
- ) {
- let p0 = rect.min;
- let p1 = rect.max;
- let pc = p0 + rect.size() * 0.5;
-
- *result = [
- PictureBox2D::new(
- p0,
- pc,
- ),
- PictureBox2D::new(
- PicturePoint::new(pc.x, p0.y),
- PicturePoint::new(p1.x, pc.y),
- ),
- PictureBox2D::new(
- PicturePoint::new(p0.x, pc.y),
- PicturePoint::new(pc.x, p1.y),
- ),
- PictureBox2D::new(
- pc,
- p1,
- ),
- ];
- }
-
- /// Called during pre_update, to clear the current dependencies
- pub fn clear(
- &mut self,
- rect: PictureBox2D,
- ) {
- self.rect = rect;
-
- match self.kind {
- TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => {
- // Swap current dependencies to be the previous frame
- mem::swap(prev_indices, curr_indices);
- curr_indices.clear();
- // Note that another frame has passed in the dirty bit trackers
- *dirty_tracker = *dirty_tracker << 1;
- *frames_since_modified += 1;
- }
- TileNodeKind::Node { ref mut children, .. } => {
- let mut child_rects = [PictureBox2D::zero(); 4];
- TileNode::get_child_rects(&rect, &mut child_rects);
- assert_eq!(child_rects.len(), children.len());
-
- for (child, rect) in children.iter_mut().zip(child_rects.iter()) {
- child.clear(*rect);
- }
- }
- }
- }
-
- /// Add a primitive dependency to this node
- pub fn add_prim(
- &mut self,
- index: PrimitiveDependencyIndex,
- prim_rect: &PictureBox2D,
- ) {
- match self.kind {
- TileNodeKind::Leaf { ref mut curr_indices, .. } => {
- curr_indices.push(index);
- }
- TileNodeKind::Node { ref mut children, .. } => {
- for child in children.iter_mut() {
- if child.rect.intersects(prim_rect) {
- child.add_prim(index, prim_rect);
- }
- }
- }
- }
- }
-
- /// Apply a merge or split operation to this tile, if desired
- pub fn maybe_merge_or_split(
- &mut self,
- level: i32,
- curr_prims: &[PrimitiveDescriptor],
- max_split_levels: i32,
- ) {
- // Determine if this tile wants to split or merge
- let mut tile_mod = None;
-
- fn get_dirty_frames(
- dirty_tracker: u64,
- frames_since_modified: usize,
- ) -> Option<u32> {
- // Only consider splitting or merging at least 64 frames since we last changed
- if frames_since_modified > 64 {
- // Each bit in the tracker is a frame that was recently invalidated
- Some(dirty_tracker.count_ones())
- } else {
- None
- }
- }
-
- match self.kind {
- TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => {
- // Only consider splitting if the tree isn't too deep.
- if level < max_split_levels {
- if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
- // If the tile has invalidated > 50% of the recent number of frames, split.
- if dirty_frames > 32 {
- tile_mod = Some(TileModification::Split);
- }
- }
- }
- }
- TileNodeKind::Node { ref children, .. } => {
- // There's two conditions that cause a node to merge its children:
- // (1) If _all_ the child nodes are constantly invalidating, then we are wasting
- // CPU time tracking dependencies for each child, so merge them.
- // (2) If _none_ of the child nodes are recently invalid, then the page content
- // has probably changed, and we no longer need to track fine grained dependencies here.
-
- let mut static_count = 0;
- let mut changing_count = 0;
-
- for child in children {
- // Only consider merging nodes at the edge of the tree.
- if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind {
- if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
- if dirty_frames == 0 {
- // Hasn't been invalidated for some time
- static_count += 1;
- } else if dirty_frames == 64 {
- // Is constantly being invalidated
- changing_count += 1;
- }
- }
- }
-
- // Only merge if all the child tiles are in agreement. Otherwise, we have some
- // that are invalidating / static, and it's worthwhile tracking dependencies for
- // them individually.
- if static_count == 4 || changing_count == 4 {
- tile_mod = Some(TileModification::Merge);
- }
- }
- }
- }
-
- match tile_mod {
- Some(TileModification::Split) => {
- // To split a node, take the current dependency index buffer for this node, and
- // split it into child index buffers.
- let curr_indices = match self.kind {
- TileNodeKind::Node { .. } => {
- unreachable!("bug - only leaves can split");
- }
- TileNodeKind::Leaf { ref mut curr_indices, .. } => {
- mem::take(curr_indices)
- }
- };
-
- let mut child_rects = [PictureBox2D::zero(); 4];
- TileNode::get_child_rects(&self.rect, &mut child_rects);
-
- let mut child_indices = [
- Vec::new(),
- Vec::new(),
- Vec::new(),
- Vec::new(),
- ];
-
- // Step through the index buffer, and add primitives to each of the children
- // that they intersect.
- for index in curr_indices {
- let prim = &curr_prims[index.0 as usize];
- for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) {
- if prim.prim_clip_box.intersects(child_rect) {
- indices.push(index);
- }
- }
- }
-
- // Create the child nodes and switch from leaf -> node.
- let children = child_indices
- .iter_mut()
- .map(|i| TileNode::new_leaf(mem::replace(i, Vec::new())))
- .collect();
-
- self.kind = TileNodeKind::Node {
- children,
- };
- }
- Some(TileModification::Merge) => {
- // Construct a merged index buffer by collecting the dependency index buffers
- // from each child, and merging them into a de-duplicated index buffer.
- let merged_indices = match self.kind {
- TileNodeKind::Node { ref mut children, .. } => {
- let mut merged_indices = Vec::new();
-
- for child in children.iter() {
- let child_indices = match child.kind {
- TileNodeKind::Leaf { ref curr_indices, .. } => {
- curr_indices
- }
- TileNodeKind::Node { .. } => {
- unreachable!("bug: child is not a leaf");
- }
- };
- merged_indices.extend_from_slice(child_indices);
- }
-
- merged_indices.sort();
- merged_indices.dedup();
-
- merged_indices
- }
- TileNodeKind::Leaf { .. } => {
- unreachable!("bug - trying to merge a leaf");
- }
- };
-
- // Switch from a node to a leaf, with the combined index buffer
- self.kind = TileNodeKind::Leaf {
- prev_indices: Vec::new(),
- curr_indices: merged_indices,
- dirty_tracker: 0,
- frames_since_modified: 0,
- };
- }
- None => {
- // If this node didn't merge / split, then recurse into children
- // to see if they want to split / merge.
- if let TileNodeKind::Node { ref mut children, .. } = self.kind {
- for child in children.iter_mut() {
- child.maybe_merge_or_split(
- level+1,
- curr_prims,
- max_split_levels,
- );
- }
- }
- }
- }
- }
-
- /// Update the dirty state of this node, building the overall dirty rect
- pub fn update_dirty_rects(
- &mut self,
- prev_prims: &[PrimitiveDescriptor],
- curr_prims: &[PrimitiveDescriptor],
- prim_comparer: &mut PrimitiveComparer,
- dirty_rect: &mut PictureBox2D,
- compare_cache: &mut FastHashMap<crate::invalidation::dependency::PrimitiveComparisonKey, PrimitiveCompareResult>,
- invalidation_reason: &mut Option<InvalidationReason>,
- frame_context: &FrameVisibilityContext,
- ) {
- match self.kind {
- TileNodeKind::Node { ref mut children, .. } => {
- for child in children.iter_mut() {
- child.update_dirty_rects(
- prev_prims,
- curr_prims,
- prim_comparer,
- dirty_rect,
- compare_cache,
- invalidation_reason,
- frame_context,
- );
- }
- }
- TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => {
- // If the index buffers are of different length, they must be different
- if prev_indices.len() == curr_indices.len() {
- // Walk each index buffer, comparing primitives
- for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) {
- let i0 = prev_index.0 as usize;
- let i1 = curr_index.0 as usize;
-
- // Compare the primitives, caching the result in a hash map
- // to save comparisons in other tree nodes.
- let key = crate::invalidation::dependency::PrimitiveComparisonKey {
- prev_index: *prev_index,
- curr_index: *curr_index,
- };
-
- let prim_compare_result = *compare_cache
- .entry(key)
- .or_insert_with(|| {
- let prev = &prev_prims[i0];
- let curr = &curr_prims[i1];
- prim_comparer.compare_prim(prev, curr)
- });
-
- // If not the same, mark this node as dirty and update the dirty rect
- if prim_compare_result != PrimitiveCompareResult::Equal {
- if invalidation_reason.is_none() {
- *invalidation_reason = Some(InvalidationReason::Content);
- }
- *dirty_rect = self.rect.union(dirty_rect);
- *dirty_tracker = *dirty_tracker | 1;
- break;
- }
- }
- } else {
- if invalidation_reason.is_none() {
- *invalidation_reason = Some(InvalidationReason::PrimCount);
- }
- *dirty_rect = self.rect.union(dirty_rect);
- *dirty_tracker = *dirty_tracker | 1;
- }
- }
- }
- }
-}
diff --git a/gfx/wr/webrender/src/lib.rs b/gfx/wr/webrender/src/lib.rs
@@ -109,7 +109,6 @@ mod lru_cache;
mod pattern;
mod picture;
mod picture_graph;
-mod invalidation;
mod prepare;
mod prim_store;
mod print_tree;
@@ -140,7 +139,6 @@ mod rectangle_occlusion;
mod picture_textures;
mod frame_allocator;
mod bump_allocator;
-mod svg_filter;
///
pub mod intern;
@@ -193,11 +191,9 @@ 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::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::picture::{TileDescriptor, TileId, InvalidationReason};
+pub use crate::picture::{PrimitiveCompareResult, CompareHelperResult};
+pub use crate::picture::{TileNode, TileNodeKind, 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
@@ -94,130 +94,3955 @@
//! blend the overlay tile (this is not always optimal right now, but will be
//! improved as a follow up).
-use api::{MixBlendMode, PremultipliedColorF, RasterSpace};
-use api::{DebugFlags, ColorF, PrimitiveFlags, SnapshotInfo};
+use api::{BorderRadius, ClipMode, MixBlendMode, PremultipliedColorF, SVGFE_GRAPH_MAX};
+use api::{PropertyBinding, PropertyBindingId, FilterOpGraphPictureBufferId, RasterSpace};
+use api::{DebugFlags, ImageKey, ColorF, ColorU, PrimitiveFlags, SnapshotInfo};
+use api::{ImageRendering, ColorDepth, YuvRangedColorSpace, YuvFormat, AlphaType};
use api::units::*;
use crate::prim_store::image::AdjustedImageSource;
use crate::{command_buffer::PrimitiveCommand, render_task_graph::RenderTaskGraphBuilder, renderer::GpuBufferBuilderF};
use crate::box_shadow::BLUR_SAMPLE_SCALE;
-use crate::clip::{ClipNodeId, ClipTreeBuilder};
+use crate::clip::{ClipChainInstance, ClipItemKind, ClipLeafId, ClipNodeId, ClipSpaceConversion, ClipStore, ClipTreeBuilder};
+use crate::profiler::{self, add_text_marker, TransactionProfile};
use crate::spatial_tree::{SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
-use crate::composite::{tile_kind, CompositeState, CompositeTileSurface, CompositorKind, NativeTileId};
-use crate::composite::{CompositeTileDescriptor, CompositeTile};
+use crate::composite::{tile_kind, CompositeState, CompositeTileSurface, CompositorClipIndex, CompositorKind, NativeSurfaceId, NativeTileId};
+use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency, CompositeTileDescriptor, CompositeTile};
+use crate::composite::{CompositorTransformIndex, CompositorSurfaceKind};
use crate::debug_colors;
-use euclid::{vec3, Scale, Vector2D, Box2D};
-use crate::internal_types::{FastHashMap, PlaneSplitter, Filter};
+use euclid::{vec3, Point2D, Scale, Vector2D, Box2D};
+use euclid::approxeq::ApproxEq;
+use crate::intern::ItemUid;
+use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, FilterGraphOp, FilterGraphNode, Filter, FrameId};
use crate::internal_types::{PlaneSplitterIndex, PlaneSplitAnchor, TextureSource};
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
-use crate::gpu_types::{BlurEdgeMode, BrushSegmentGpuData, ImageBrushPrimitiveData};
-use crate::svg_filter::{FilterGraphOp, FilterGraphNode, get_coverage_source_svgfe, get_coverage_target_svgfe};
+use crate::gpu_types::{BlurEdgeMode, BrushSegmentGpuData, ImageBrushPrimitiveData, UvRectKind, ZBufferId};
+use peek_poke::{PeekPoke, poke_into_vec, peek_from_slice, ensure_red_zone};
use plane_split::{Clipper, Polygon};
-use crate::prim_store::{PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
-use crate::prim_store::PrimitiveScratchBuffer;
-use crate::print_tree::PrintTreePrinter;
+use crate::prim_store::{PrimitiveTemplateKind, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
+use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveScratchBuffer};
+use crate::print_tree::{PrintTree, PrintTreePrinter};
use crate::render_backend::DataStores;
use crate::render_task_graph::RenderTaskId;
use crate::render_target::RenderTargetKind;
use crate::render_task::{BlurTask, RenderTask, RenderTaskLocation, BlurTaskCache};
use crate::render_task::{StaticRenderTaskSurface, RenderTaskKind};
use crate::renderer::{BlendMode, GpuBufferAddress};
-use crate::resource_cache::ResourceCache;
+use crate::resource_cache::{ResourceCache, ImageGeneration, ImageRequest};
use crate::space::SpaceMapper;
use crate::scene::SceneProperties;
use crate::spatial_tree::CoordinateSystemId;
-use crate::surface::{SurfaceDescriptor, SurfaceTileDescriptor, get_surface_rects};
-pub use crate::surface::{SurfaceIndex, SurfaceInfo, SubpixelMode, SurfaceAllocInfo};
-pub use crate::surface::{calculate_screen_uv, calculate_uv_rect_kind};
+use crate::surface::{SurfaceDescriptor, SurfaceTileDescriptor};
use smallvec::SmallVec;
-use std::{mem, u8, u32};
+use std::{mem, u8, marker, 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, Recycler, ScaleOffset};
+use crate::util::{MaxRect, VecHelper, MatrixHelpers, Recycler, ScaleOffset};
use crate::filterdata::FilterDataHandle;
use crate::tile_cache::{SliceDebugInfo, TileDebugInfo, DirtyTileDebugInfo};
-use crate::tile_cache::{SliceId, TileCacheInstance, TileSurface, NativeSurface};
-use crate::tile_cache::{BackdropKind, BackdropSurface, Tile};
-use crate::tile_cache::{TileKey, SubSliceIndex};
-use crate::invalidation::InvalidationReason;
-use crate::tile_cache::MAX_SURFACE_SIZE;
+use crate::visibility::{PrimitiveVisibilityFlags, FrameVisibilityContext};
+use crate::visibility::{VisibilityState, FrameVisibilityState};
+use crate::scene_building::SliceFlags;
+use core::time::Duration;
-pub use crate::invalidation::DirtyRegion;
-pub use crate::invalidation::dependency::ImageDependency;
-pub use crate::invalidation::quadtree::{TileNode, TileNodeKind};
+// Maximum blur radius for blur filter (different than box-shadow blur).
+// Taken from FilterNodeSoftware.cpp in Gecko.
+const MAX_BLUR_RADIUS: f32 = 100.;
+
+/// Specify whether a surface allows subpixel AA text rendering.
+#[derive(Debug, Copy, Clone)]
+pub enum SubpixelMode {
+ /// This surface allows subpixel AA text
+ Allow,
+ /// Subpixel AA text cannot be drawn on this surface
+ Deny,
+ /// Subpixel AA can be drawn on this surface, if not intersecting
+ /// with the excluded regions, and inside the allowed rect.
+ Conditional {
+ allowed_rect: PictureRect,
+ prohibited_rect: PictureRect,
+ },
+}
+
+/// A comparable transform matrix, that compares with epsilon checks.
+#[derive(Debug, Clone)]
+struct MatrixKey {
+ m: [f32; 16],
+}
+
+impl PartialEq for MatrixKey {
+ fn eq(&self, other: &Self) -> bool {
+ const EPSILON: f32 = 0.001;
+
+ // TODO(gw): It's possible that we may need to adjust the epsilon
+ // to be tighter on most of the matrix, except the
+ // translation parts?
+ for (i, j) in self.m.iter().zip(other.m.iter()) {
+ if !i.approx_eq_eps(j, &EPSILON) {
+ return false;
+ }
+ }
+
+ true
+ }
+}
+
+/// A comparable scale-offset, that compares with epsilon checks.
+#[derive(Debug, Clone)]
+struct ScaleOffsetKey {
+ sx: f32,
+ sy: f32,
+ tx: f32,
+ ty: f32,
+}
+
+impl PartialEq for ScaleOffsetKey {
+ fn eq(&self, other: &Self) -> bool {
+ const EPSILON: f32 = 0.001;
+
+ self.sx.approx_eq_eps(&other.sx, &EPSILON) &&
+ self.sy.approx_eq_eps(&other.sy, &EPSILON) &&
+ self.tx.approx_eq_eps(&other.tx, &EPSILON) &&
+ self.ty.approx_eq_eps(&other.ty, &EPSILON)
+ }
+}
+
+/// A comparable / hashable version of a coordinate space mapping. Used to determine
+/// if a transform dependency for a tile has changed.
+#[derive(Debug, PartialEq, Clone)]
+enum TransformKey {
+ Local,
+ ScaleOffset {
+ so: ScaleOffsetKey,
+ },
+ Transform {
+ m: MatrixKey,
+ }
+}
+
+impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
+ fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey {
+ match transform {
+ CoordinateSpaceMapping::Local => {
+ TransformKey::Local
+ }
+ CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
+ TransformKey::ScaleOffset {
+ so: ScaleOffsetKey {
+ sx: scale_offset.scale.x,
+ sy: scale_offset.scale.y,
+ tx: scale_offset.offset.x,
+ ty: scale_offset.offset.y,
+ }
+ }
+ }
+ CoordinateSpaceMapping::Transform(ref m) => {
+ TransformKey::Transform {
+ m: MatrixKey {
+ m: m.to_array(),
+ },
+ }
+ }
+ }
+ }
+}
+
+/// 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)
+}
+
+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> {
+ /// The current value retrieved from dynamic scene properties.
+ value: T,
+ /// True if it was changed (or is new) since the last frame build.
+ changed: bool,
+}
+
+/// Information stored in a tile descriptor for a binding.
+#[derive(Debug, PartialEq, Clone, Copy, PeekPoke)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum Binding<T> {
+ Value(T),
+ Binding(PropertyBindingId),
+}
+
+impl<T: Default> Default for Binding<T> {
+ fn default() -> Self {
+ Binding::Value(T::default())
+ }
+}
+
+impl<T> From<PropertyBinding<T>> for Binding<T> {
+ fn from(binding: PropertyBinding<T>) -> Binding<T> {
+ match binding {
+ PropertyBinding::Binding(key, _) => Binding::Binding(key.id),
+ PropertyBinding::Value(value) => Binding::Value(value),
+ }
+ }
+}
+
+pub type OpacityBinding = Binding<f32>;
+pub type OpacityBindingInfo = BindingInfo<f32>;
+
+pub type ColorBinding = Binding<ColorU>;
+pub type ColorBindingInfo = BindingInfo<ColorU>;
+
+#[derive(PeekPoke)]
+enum PrimitiveDependency {
+ OpacityBinding {
+ binding: OpacityBinding,
+ },
+ ColorBinding {
+ binding: ColorBinding,
+ },
+ SpatialNode {
+ index: SpatialNodeIndex,
+ },
+ Clip {
+ clip: ItemUid,
+ },
+ Image {
+ image: ImageDependency,
+ },
+}
+
+/// A dependency for a transform is defined by the spatial node index + frame it was used
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PeekPoke, Default)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SpatialNodeKey {
+ spatial_node_index: SpatialNodeIndex,
+ frame_id: FrameId,
+}
+
+/// A helper for comparing spatial nodes between frames. The comparisons
+/// are done by-value, so that if the shape of the spatial node tree
+/// changes, invalidations aren't done simply due to the spatial node
+/// index changing between display lists.
+struct SpatialNodeComparer {
+ /// The root spatial node index of the tile cache
+ ref_spatial_node_index: SpatialNodeIndex,
+ /// Maintains a map of currently active transform keys
+ spatial_nodes: FastHashMap<SpatialNodeKey, TransformKey>,
+ /// A cache of recent comparisons between prev and current spatial nodes
+ compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>,
+ /// A set of frames that we need to retain spatial node entries for
+ referenced_frames: FastHashSet<FrameId>,
+}
+
+impl SpatialNodeComparer {
+ /// Construct a new comparer
+ fn new() -> Self {
+ SpatialNodeComparer {
+ ref_spatial_node_index: SpatialNodeIndex::INVALID,
+ spatial_nodes: FastHashMap::default(),
+ compare_cache: FastHashMap::default(),
+ referenced_frames: FastHashSet::default(),
+ }
+ }
+
+ /// Advance to the next frame
+ fn next_frame(
+ &mut self,
+ ref_spatial_node_index: SpatialNodeIndex,
+ ) {
+ // Drop any node information for unreferenced frames, to ensure that the
+ // hashmap doesn't grow indefinitely!
+ let referenced_frames = &self.referenced_frames;
+ self.spatial_nodes.retain(|key, _| {
+ referenced_frames.contains(&key.frame_id)
+ });
+
+ // Update the root spatial node for this comparer
+ self.ref_spatial_node_index = ref_spatial_node_index;
+ self.compare_cache.clear();
+ self.referenced_frames.clear();
+ }
+
+ /// Register a transform that is used, and build the transform key for it if new.
+ fn register_used_transform(
+ &mut self,
+ spatial_node_index: SpatialNodeIndex,
+ frame_id: FrameId,
+ spatial_tree: &SpatialTree,
+ ) {
+ let key = SpatialNodeKey {
+ spatial_node_index,
+ frame_id,
+ };
+
+ if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) {
+ entry.insert(
+ get_transform_key(
+ spatial_node_index,
+ self.ref_spatial_node_index,
+ spatial_tree,
+ )
+ );
+ }
+ }
+
+ /// Return true if the transforms for two given spatial nodes are considered equivalent
+ fn are_transforms_equivalent(
+ &mut self,
+ prev_spatial_node_key: &SpatialNodeKey,
+ curr_spatial_node_key: &SpatialNodeKey,
+ ) -> bool {
+ let key = (*prev_spatial_node_key, *curr_spatial_node_key);
+ let spatial_nodes = &self.spatial_nodes;
+
+ *self.compare_cache
+ .entry(key)
+ .or_insert_with(|| {
+ let prev = &spatial_nodes[&prev_spatial_node_key];
+ let curr = &spatial_nodes[&curr_spatial_node_key];
+ curr == prev
+ })
+ }
+
+ /// Ensure that the comparer won't GC any nodes for a given frame id
+ fn retain_for_frame(&mut self, frame_id: FrameId) {
+ self.referenced_frames.insert(frame_id);
+ }
+}
+
+// Immutable context passed to picture cache tiles during pre_update
+struct TilePreUpdateContext {
+ /// Maps from picture cache coords -> world space coords.
+ pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
+
+ /// The optional background color of the picture cache instance
+ background_color: Option<ColorF>,
+
+ /// The visible part of the screen in world coords.
+ global_screen_world_rect: WorldRect,
+
+ /// Current size of tiles in picture units.
+ tile_size: PictureSize,
+
+ /// The current frame id for this picture cache
+ frame_id: FrameId,
+}
+
+// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
+struct TileUpdateDirtyContext<'a> {
+ /// Maps from picture cache coords -> world space coords.
+ pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
+
+ /// Global scale factor from world -> device pixels.
+ global_device_pixel_scale: DevicePixelScale,
+
+ /// Information about opacity bindings from the picture cache.
+ opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
+
+ /// Information about color bindings from the picture cache.
+ color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
+
+ /// The local rect of the overall picture cache
+ local_rect: PictureRect,
+
+ /// If true, the scale factor of the root transform for this picture
+ /// cache changed, so we need to invalidate the tile and re-render.
+ invalidate_all: bool,
+}
+
+// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
+struct TileUpdateDirtyState<'a> {
+ /// Allow access to the texture cache for requesting tiles
+ resource_cache: &'a mut ResourceCache,
+
+ /// Current configuration and setup for compositing all the picture cache tiles in renderer.
+ composite_state: &'a mut CompositeState,
+
+ /// A cache of comparison results to avoid re-computation during invalidation.
+ compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
+
+ /// Information about transform node differences from last frame.
+ spatial_node_comparer: &'a mut SpatialNodeComparer,
+}
+
+// Immutable context passed to picture cache tiles during post_update
+struct TilePostUpdateContext<'a> {
+ /// The local clip rect (in picture space) of the entire picture cache
+ local_clip_rect: PictureRect,
+
+ /// The calculated backdrop information for this cache instance.
+ backdrop: Option<BackdropInfo>,
+
+ /// Current size in device pixels of tiles for this cache
+ current_tile_size: DeviceIntSize,
+
+ /// Pre-allocated z-id to assign to tiles during post_update.
+ z_id: ZBufferId,
+
+ /// The list of compositor underlays for this picture cache
+ underlays: &'a [ExternalSurfaceDescriptor],
+}
+
+// Mutable state passed to picture cache tiles during post_update
+struct TilePostUpdateState<'a> {
+ /// Allow access to the texture cache for requesting tiles
+ resource_cache: &'a mut ResourceCache,
+
+ /// Current configuration and setup for compositing all the picture cache tiles in renderer.
+ composite_state: &'a mut CompositeState,
+}
+
+/// Information about the dependencies of a single primitive instance.
+struct PrimitiveDependencyInfo {
+ /// Unique content identifier of the primitive.
+ prim_uid: ItemUid,
+
+ /// The (conservative) clipped area in picture space this primitive occupies.
+ prim_clip_box: PictureBox2D,
+
+ /// Image keys this primitive depends on.
+ images: SmallVec<[ImageDependency; 8]>,
+
+ /// Opacity bindings this primitive depends on.
+ opacity_bindings: SmallVec<[OpacityBinding; 4]>,
+
+ /// Color binding this primitive depends on.
+ color_binding: Option<ColorBinding>,
+
+ /// Clips that this primitive depends on.
+ clips: SmallVec<[ItemUid; 8]>,
+
+ /// Spatial nodes references by the clip dependencies of this primitive.
+ spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
+}
+
+impl PrimitiveDependencyInfo {
+ /// Construct dependency info for a new primitive.
+ fn new(
+ prim_uid: ItemUid,
+ prim_clip_box: PictureBox2D,
+ ) -> Self {
+ PrimitiveDependencyInfo {
+ prim_uid,
+ images: SmallVec::new(),
+ opacity_bindings: SmallVec::new(),
+ color_binding: None,
+ prim_clip_box,
+ clips: SmallVec::new(),
+ spatial_nodes: SmallVec::new(),
+ }
+ }
+}
+
+/// 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)]
+pub enum SurfaceTextureDescriptor {
+ /// When using the WR compositor, the tile is drawn into an entry
+ /// in the WR texture cache.
+ TextureCache {
+ handle: Option<PictureCacheTextureHandle>,
+ },
+ /// When using an OS compositor, the tile is drawn into a native
+ /// surface identified by arbitrary id.
+ Native {
+ /// The arbitrary id of this tile.
+ id: Option<NativeTileId>,
+ },
+}
+
+/// This is the same as a `SurfaceTextureDescriptor` but has been resolved
+/// into a texture cache handle (if appropriate) that can be used by the
+/// batching and compositing code in the renderer.
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ResolvedSurfaceTexture {
+ TextureCache {
+ /// The texture ID to draw to.
+ texture: TextureSource,
+ },
+ Native {
+ /// The arbitrary id of this tile.
+ id: NativeTileId,
+ /// The size of the tile in device pixels.
+ size: DeviceIntSize,
+ }
+}
+
+impl SurfaceTextureDescriptor {
+ /// Create a resolved surface texture for this descriptor
+ pub fn resolve(
+ &self,
+ resource_cache: &ResourceCache,
+ size: DeviceIntSize,
+ ) -> ResolvedSurfaceTexture {
+ match self {
+ SurfaceTextureDescriptor::TextureCache { handle } => {
+ let texture = resource_cache
+ .picture_textures
+ .get_texture_source(handle.as_ref().unwrap());
+
+ ResolvedSurfaceTexture::TextureCache { texture }
+ }
+ SurfaceTextureDescriptor::Native { id } => {
+ ResolvedSurfaceTexture::Native {
+ id: id.expect("bug: native surface not allocated"),
+ size,
+ }
+ }
+ }
+ }
+}
+
+/// The backing surface for this tile.
+#[derive(Debug)]
+pub enum TileSurface {
+ Texture {
+ /// Descriptor for the surface that this tile draws into.
+ descriptor: SurfaceTextureDescriptor,
+ },
+ Color {
+ color: ColorF,
+ },
+}
+
+impl TileSurface {
+ fn kind(&self) -> &'static str {
+ match *self {
+ TileSurface::Color { .. } => "Color",
+ TileSurface::Texture { .. } => "Texture",
+ }
+ }
+}
+
+/// 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
+ pub tile_offset: TileOffset,
+ /// The current world rect of this tile.
+ pub world_tile_rect: WorldRect,
+ /// The current local rect of this tile.
+ pub local_tile_rect: PictureRect,
+ /// The picture space dirty rect for this tile.
+ pub local_dirty_rect: PictureRect,
+ /// The device space dirty rect for this tile.
+ /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
+ /// expose these as multiple dirty rects, which will help in some cases.
+ pub device_dirty_rect: DeviceRect,
+ /// World space rect that contains valid pixels region of this tile.
+ pub world_valid_rect: WorldRect,
+ /// Device space rect that contains valid pixels region of this tile.
+ pub device_valid_rect: DeviceRect,
+ /// Uniquely describes the content of this tile, in a way that can be
+ /// (reasonably) efficiently hashed and compared.
+ pub current_descriptor: TileDescriptor,
+ /// The content descriptor for this tile from the previous frame.
+ pub prev_descriptor: TileDescriptor,
+ /// Handle to the backing surface for this tile.
+ pub surface: Option<TileSurface>,
+ /// If true, this tile is marked valid, and the existing texture
+ /// cache handle can be used. Tiles are invalidated during the
+ /// build_dirty_regions method.
+ pub is_valid: bool,
+ /// If true, this tile intersects with the currently visible screen
+ /// rect, and will be drawn.
+ pub is_visible: bool,
+ /// The tile id is stable between display lists and / or frames,
+ /// if the tile is retained. Useful for debugging tile evictions.
+ pub id: TileId,
+ /// If true, the tile was determined to be opaque, which means blending
+ /// can be disabled when drawing it.
+ pub is_opaque: bool,
+ /// Root node of the quadtree dirty rect tracker.
+ root: TileNode,
+ /// The last rendered background color on this tile.
+ background_color: Option<ColorF>,
+ /// The first reason the tile was invalidated this frame.
+ invalidation_reason: Option<InvalidationReason>,
+ /// The local space valid rect for all primitives that affect this tile.
+ pub local_valid_rect: PictureBox2D,
+ /// z-buffer id for this tile
+ pub z_id: ZBufferId,
+ pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>,
+}
+
+impl Tile {
+ /// Construct a new, invalid tile.
+ fn new(tile_offset: TileOffset) -> Self {
+ let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
+
+ Tile {
+ tile_offset,
+ local_tile_rect: PictureRect::zero(),
+ world_tile_rect: WorldRect::zero(),
+ world_valid_rect: WorldRect::zero(),
+ device_valid_rect: DeviceRect::zero(),
+ local_dirty_rect: PictureRect::zero(),
+ device_dirty_rect: DeviceRect::zero(),
+ surface: None,
+ current_descriptor: TileDescriptor::new(),
+ prev_descriptor: TileDescriptor::new(),
+ is_valid: false,
+ is_visible: false,
+ id,
+ is_opaque: false,
+ root: TileNode::new_leaf(Vec::new()),
+ background_color: None,
+ invalidation_reason: None,
+ local_valid_rect: PictureBox2D::zero(),
+ z_id: ZBufferId::invalid(),
+ sub_graphs: Vec::new(),
+ }
+ }
+
+ /// Print debug information about this tile to a tree printer.
+ fn print(&self, pt: &mut dyn PrintTreePrinter) {
+ pt.new_level(format!("Tile {:?}", self.id));
+ pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect));
+ pt.add_item(format!("background_color: {:?}", self.background_color));
+ pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
+ self.current_descriptor.print(pt);
+ pt.end_level();
+ }
+
+ /// Check if the content of the previous and current tile descriptors match
+ fn update_dirty_rects(
+ &mut self,
+ ctx: &TileUpdateDirtyContext,
+ state: &mut TileUpdateDirtyState,
+ invalidation_reason: &mut Option<InvalidationReason>,
+ frame_context: &FrameVisibilityContext,
+ ) -> PictureRect {
+ let mut prim_comparer = PrimitiveComparer::new(
+ &self.prev_descriptor,
+ &self.current_descriptor,
+ state.resource_cache,
+ state.spatial_node_comparer,
+ ctx.opacity_bindings,
+ ctx.color_bindings,
+ );
+
+ let mut dirty_rect = PictureBox2D::zero();
+ self.root.update_dirty_rects(
+ &self.prev_descriptor.prims,
+ &self.current_descriptor.prims,
+ &mut prim_comparer,
+ &mut dirty_rect,
+ state.compare_cache,
+ invalidation_reason,
+ frame_context,
+ );
+
+ dirty_rect
+ }
+
+ /// Invalidate a tile based on change in content. This
+ /// must be called even if the tile is not currently
+ /// visible on screen. We might be able to improve this
+ /// later by changing how ComparableVec is used.
+ fn update_content_validity(
+ &mut self,
+ ctx: &TileUpdateDirtyContext,
+ state: &mut TileUpdateDirtyState,
+ frame_context: &FrameVisibilityContext,
+ ) {
+ // Check if the contents of the primitives, clips, and
+ // other dependencies are the same.
+ state.compare_cache.clear();
+ let mut invalidation_reason = None;
+ let dirty_rect = self.update_dirty_rects(
+ ctx,
+ state,
+ &mut invalidation_reason,
+ frame_context,
+ );
+ if !dirty_rect.is_empty() {
+ self.invalidate(
+ Some(dirty_rect),
+ invalidation_reason.expect("bug: no invalidation_reason"),
+ );
+ }
+ if ctx.invalidate_all {
+ self.invalidate(None, InvalidationReason::ScaleChanged);
+ }
+ // TODO(gw): We can avoid invalidating the whole tile in some cases here,
+ // but it should be a fairly rare invalidation case.
+ if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
+ self.invalidate(None, InvalidationReason::ValidRectChanged);
+ state.composite_state.dirty_rects_are_valid = false;
+ }
+ }
+
+ /// Invalidate this tile. If `invalidation_rect` is None, the entire
+ /// tile is invalidated.
+ fn invalidate(
+ &mut self,
+ invalidation_rect: Option<PictureRect>,
+ reason: InvalidationReason,
+ ) {
+ self.is_valid = false;
+
+ match invalidation_rect {
+ Some(rect) => {
+ self.local_dirty_rect = self.local_dirty_rect.union(&rect);
+ }
+ None => {
+ self.local_dirty_rect = self.local_tile_rect;
+ }
+ }
+
+ if self.invalidation_reason.is_none() {
+ self.invalidation_reason = Some(reason);
+ }
+ }
+
+ /// Called during pre_update of a tile cache instance. Allows the
+ /// tile to setup state before primitive dependency calculations.
+ fn pre_update(
+ &mut self,
+ ctx: &TilePreUpdateContext,
+ ) {
+ self.local_tile_rect = PictureRect::new(
+ PicturePoint::new(
+ self.tile_offset.x as f32 * ctx.tile_size.width,
+ self.tile_offset.y as f32 * ctx.tile_size.height,
+ ),
+ PicturePoint::new(
+ (self.tile_offset.x + 1) as f32 * ctx.tile_size.width,
+ (self.tile_offset.y + 1) as f32 * ctx.tile_size.height,
+ ),
+ );
+ // TODO(gw): This is a hack / fix for Box2D::union in euclid not working with
+ // zero sized rect accumulation. Once that lands, we'll revert this
+ // to be zero.
+ self.local_valid_rect = PictureBox2D::new(
+ PicturePoint::new( 1.0e32, 1.0e32),
+ PicturePoint::new(-1.0e32, -1.0e32),
+ );
+ self.invalidation_reason = None;
+ self.sub_graphs.clear();
+
+ self.world_tile_rect = ctx.pic_to_world_mapper
+ .map(&self.local_tile_rect)
+ .expect("bug: map local tile rect");
+
+ // Check if this tile is currently on screen.
+ self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
+
+ // If the tile isn't visible, early exit, skipping the normal set up to
+ // validate dependencies. Instead, we will only compare the current tile
+ // dependencies the next time it comes into view.
+ if !self.is_visible {
+ return;
+ }
+
+ if ctx.background_color != self.background_color {
+ self.invalidate(None, InvalidationReason::BackgroundColor);
+ self.background_color = ctx.background_color;
+ }
+
+ // Clear any dependencies so that when we rebuild them we
+ // can compare if the tile has the same content.
+ mem::swap(
+ &mut self.current_descriptor,
+ &mut self.prev_descriptor,
+ );
+ self.current_descriptor.clear();
+ self.root.clear(self.local_tile_rect);
+
+ // Since this tile is determined to be visible, it will get updated
+ // dependencies, so update the frame id we are storing dependencies for.
+ self.current_descriptor.last_updated_frame_id = ctx.frame_id;
+ }
+
+ /// Add dependencies for a given primitive to this tile.
+ fn add_prim_dependency(
+ &mut self,
+ info: &PrimitiveDependencyInfo,
+ ) {
+ // If this tile isn't currently visible, we don't want to update the dependencies
+ // for this tile, as an optimization, since it won't be drawn anyway.
+ if !self.is_visible {
+ return;
+ }
+
+ // Incorporate the bounding rect of the primitive in the local valid rect
+ // for this tile. This is used to minimize the size of the scissor rect
+ // during rasterization and the draw rect during composition of partial tiles.
+ self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
+
+ // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port,
+ // which can cause invalidations when a new display list with changed
+ // display port is received. To work around this, clamp the prim clip rect
+ // to the tile boundaries - if the clip hasn't affected the tile, then the
+ // changed clip can't affect the content of the primitive on this tile.
+ // In future, we could consider supplying the display port clip from Gecko
+ // in a different way (e.g. as a scroll frame clip) which still provides
+ // the desired clip for checkerboarding, but doesn't require this extra
+ // work below.
+
+ // TODO(gw): This is a hot part of the code - we could probably optimize further by:
+ // - Using min/max instead of clamps below (if we guarantee the rects are well formed)
+
+ let tile_p0 = self.local_tile_rect.min;
+ let tile_p1 = self.local_tile_rect.max;
+
+ let prim_clip_box = PictureBox2D::new(
+ PicturePoint::new(
+ clampf(info.prim_clip_box.min.x, tile_p0.x, tile_p1.x),
+ clampf(info.prim_clip_box.min.y, tile_p0.y, tile_p1.y),
+ ),
+ PicturePoint::new(
+ clampf(info.prim_clip_box.max.x, tile_p0.x, tile_p1.x),
+ clampf(info.prim_clip_box.max.y, tile_p0.y, tile_p1.y),
+ ),
+ );
+
+ // Update the tile descriptor, used for tile comparison during scene swaps.
+ let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
+
+ // Encode the deps for this primitive in the `dep_data` byte buffer
+ let dep_offset = self.current_descriptor.dep_data.len() as u32;
+ let mut dep_count = 0;
+
+ for clip in &info.clips {
+ dep_count += 1;
+ poke_into_vec(
+ &PrimitiveDependency::Clip {
+ clip: *clip,
+ },
+ &mut self.current_descriptor.dep_data,
+ );
+ }
+
+ for spatial_node_index in &info.spatial_nodes {
+ dep_count += 1;
+ poke_into_vec(
+ &PrimitiveDependency::SpatialNode {
+ index: *spatial_node_index,
+ },
+ &mut self.current_descriptor.dep_data,
+ );
+ }
+
+ for image in &info.images {
+ dep_count += 1;
+ poke_into_vec(
+ &PrimitiveDependency::Image {
+ image: *image,
+ },
+ &mut self.current_descriptor.dep_data,
+ );
+ }
+
+ for binding in &info.opacity_bindings {
+ dep_count += 1;
+ poke_into_vec(
+ &PrimitiveDependency::OpacityBinding {
+ binding: *binding,
+ },
+ &mut self.current_descriptor.dep_data,
+ );
+ }
+
+ if let Some(ref binding) = info.color_binding {
+ dep_count += 1;
+ poke_into_vec(
+ &PrimitiveDependency::ColorBinding {
+ binding: *binding,
+ },
+ &mut self.current_descriptor.dep_data,
+ );
+ }
+
+ self.current_descriptor.prims.push(PrimitiveDescriptor {
+ prim_uid: info.prim_uid,
+ prim_clip_box,
+ dep_offset,
+ dep_count,
+ });
+
+ // Add this primitive to the dirty rect quadtree.
+ self.root.add_prim(prim_index, &info.prim_clip_box);
+ }
+
+ /// Called during tile cache instance post_update. Allows invalidation and dirty
+ /// rect calculation after primitive dependencies have been updated.
+ fn update_dirty_and_valid_rects(
+ &mut self,
+ ctx: &TileUpdateDirtyContext,
+ state: &mut TileUpdateDirtyState,
+ frame_context: &FrameVisibilityContext,
+ ) {
+ // Ensure peek-poke constraint is met, that `dep_data` is large enough
+ ensure_red_zone::<PrimitiveDependency>(&mut self.current_descriptor.dep_data);
+
+ // Register the frame id of this tile with the spatial node comparer, to ensure
+ // that it doesn't GC any spatial nodes from the comparer that are referenced
+ // by this tile. Must be done before we early exit below, so that we retain
+ // spatial node info even for tiles that are currently not visible.
+ state.spatial_node_comparer.retain_for_frame(self.current_descriptor.last_updated_frame_id);
+
+ // If tile is not visible, just early out from here - we don't update dependencies
+ // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
+ // (and thus updated / invalidated) until it is on screen again.
+ if !self.is_visible {
+ return;
+ }
+
+ // Calculate the overall valid rect for this tile.
+ self.current_descriptor.local_valid_rect = self.local_valid_rect;
+
+ // TODO(gw): In theory, the local tile rect should always have an
+ // intersection with the overall picture rect. In practice,
+ // due to some accuracy issues with how fract_offset (and
+ // fp accuracy) are used in the calling method, this isn't
+ // always true. In this case, it's safe to set the local
+ // valid rect to zero, which means it will be clipped out
+ // and not affect the scene. In future, we should fix the
+ // accuracy issue above, so that this assumption holds, but
+ // it shouldn't have any noticeable effect on performance
+ // or memory usage (textures should never get allocated).
+ self.current_descriptor.local_valid_rect = self.local_tile_rect
+ .intersection(&ctx.local_rect)
+ .and_then(|r| r.intersection(&self.current_descriptor.local_valid_rect))
+ .unwrap_or_else(PictureRect::zero);
+
+ // The device_valid_rect is referenced during `update_content_validity` so it
+ // must be updated here first.
+ self.world_valid_rect = ctx.pic_to_world_mapper
+ .map(&self.current_descriptor.local_valid_rect)
+ .expect("bug: map local valid rect");
+
+ // The device rect is guaranteed to be aligned on a device pixel - the round
+ // is just to deal with float accuracy. However, the valid rect is not
+ // always aligned to a device pixel. To handle this, round out to get all
+ // required pixels, and intersect with the tile device rect.
+ let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round();
+ self.device_valid_rect = (self.world_valid_rect * ctx.global_device_pixel_scale)
+ .round_out()
+ .intersection(&device_rect)
+ .unwrap_or_else(DeviceRect::zero);
+
+ // Invalidate the tile based on the content changing.
+ self.update_content_validity(ctx, state, frame_context);
+ }
+
+ /// Called during tile cache instance post_update. Allows invalidation and dirty
+ /// rect calculation after primitive dependencies have been updated.
+ fn post_update(
+ &mut self,
+ ctx: &TilePostUpdateContext,
+ state: &mut TilePostUpdateState,
+ frame_context: &FrameVisibilityContext,
+ ) {
+ // If tile is not visible, just early out from here - we don't update dependencies
+ // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
+ // (and thus updated / invalidated) until it is on screen again.
+ if !self.is_visible {
+ return;
+ }
+
+ // If there are no primitives there is no need to draw or cache it.
+ // Bug 1719232 - The final device valid rect does not always describe a non-empty
+ // region. Cull the tile as a workaround.
+ if self.current_descriptor.prims.is_empty() || self.device_valid_rect.is_empty() {
+ // If there is a native compositor surface allocated for this (now empty) tile
+ // it must be freed here, otherwise the stale tile with previous contents will
+ // be composited. If the tile subsequently gets new primitives added to it, the
+ // surface will be re-allocated when it's added to the composite draw list.
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() {
+ if let Some(id) = id.take() {
+ state.resource_cache.destroy_compositor_tile(id);
+ }
+ }
+
+ self.is_visible = false;
+ return;
+ }
+
+ // Check if this tile can be considered opaque. Opacity state must be updated only
+ // after all early out checks have been performed. Otherwise, we might miss updating
+ // the native surface next time this tile becomes visible.
+ let clipped_rect = self.current_descriptor.local_valid_rect
+ .intersection(&ctx.local_clip_rect)
+ .unwrap_or_else(PictureRect::zero);
+
+ let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
+ let has_opaque_backdrop = ctx.backdrop.map_or(false, |b| b.opaque_rect.contains_box(&clipped_rect));
+ let mut is_opaque = has_opaque_bg_color || has_opaque_backdrop;
+
+ // If this tile intersects with any underlay surfaces, we need to consider it
+ // translucent, since it will contain an alpha cutout
+ for underlay in ctx.underlays {
+ if clipped_rect.intersects(&underlay.local_rect) {
+ is_opaque = false;
+ break;
+ }
+ }
+
+ // Set the correct z_id for this tile
+ self.z_id = ctx.z_id;
+
+ if is_opaque != self.is_opaque {
+ // If opacity changed, the native compositor surface and all tiles get invalidated.
+ // (this does nothing if not using native compositor mode).
+ // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
+ // everything in this case. If it turns out that this isn't true, we could
+ // consider other options, such as per-tile opacity (natively supported
+ // on CoreAnimation, and supported if backed by non-virtual surfaces in
+ // DirectComposition).
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface {
+ if let Some(id) = id.take() {
+ state.resource_cache.destroy_compositor_tile(id);
+ }
+ }
+
+ // Invalidate the entire tile to force a redraw.
+ self.invalidate(None, InvalidationReason::SurfaceOpacityChanged);
+ self.is_opaque = is_opaque;
+ }
+
+ // Check if the selected composite mode supports dirty rect updates. For Draw composite
+ // mode, we can always update the content with smaller dirty rects, unless there is a
+ // driver bug to workaround. For native composite mode, we can only use dirty rects if
+ // the compositor supports partial surface updates.
+ let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
+ CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
+ (frame_context.config.gpu_supports_render_target_partial_update, true)
+ }
+ CompositorKind::Native { capabilities, .. } => {
+ (capabilities.max_update_rects > 0, false)
+ }
+ };
+
+ // TODO(gw): Consider using smaller tiles and/or tile splits for
+ // native compositors that don't support dirty rects.
+ if supports_dirty_rects {
+ // Only allow splitting for normal content sized tiles
+ if ctx.current_tile_size == state.resource_cache.picture_textures.default_tile_size() {
+ let max_split_level = 3;
+
+ // Consider splitting / merging dirty regions
+ self.root.maybe_merge_or_split(
+ 0,
+ &self.current_descriptor.prims,
+ max_split_level,
+ );
+ }
+ }
+
+ // The dirty rect will be set correctly by now. If the underlying platform
+ // doesn't support partial updates, and this tile isn't valid, force the dirty
+ // rect to be the size of the entire tile.
+ if !self.is_valid && !supports_dirty_rects {
+ self.local_dirty_rect = self.local_tile_rect;
+ }
+
+ // See if this tile is a simple color, in which case we can just draw
+ // it as a rect, and avoid allocating a texture surface and drawing it.
+ // TODO(gw): Initial native compositor interface doesn't support simple
+ // color tiles. We can definitely support this in DC, so this
+ // should be added as a follow up.
+ let is_simple_prim =
+ ctx.backdrop.map_or(false, |b| b.kind.is_some()) &&
+ self.current_descriptor.prims.len() == 1 &&
+ self.is_opaque &&
+ supports_simple_prims;
+
+ // Set up the backing surface for this tile.
+ let surface = if is_simple_prim {
+ // If we determine the tile can be represented by a color, set the
+ // surface unconditionally (this will drop any previously used
+ // texture cache backing surface).
+ match ctx.backdrop.unwrap().kind {
+ Some(BackdropKind::Color { color }) => {
+ TileSurface::Color {
+ color,
+ }
+ }
+ None => {
+ // This should be prevented by the is_simple_prim check above.
+ unreachable!();
+ }
+ }
+ } else {
+ // If this tile will be backed by a surface, we want to retain
+ // the texture handle from the previous frame, if possible. If
+ // the tile was previously a color, or not set, then just set
+ // up a new texture cache handle.
+ match self.surface.take() {
+ Some(TileSurface::Texture { descriptor }) => {
+ // Reuse the existing descriptor and vis mask
+ TileSurface::Texture {
+ descriptor,
+ }
+ }
+ Some(TileSurface::Color { .. }) | None => {
+ // This is the case where we are constructing a tile surface that
+ // involves drawing to a texture. Create the correct surface
+ // descriptor depending on the compositing mode that will read
+ // the output.
+ let descriptor = match state.composite_state.compositor_kind {
+ CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
+ // For a texture cache entry, create an invalid handle that
+ // will be allocated when update_picture_cache is called.
+ SurfaceTextureDescriptor::TextureCache {
+ handle: None,
+ }
+ }
+ CompositorKind::Native { .. } => {
+ // Create a native surface surface descriptor, but don't allocate
+ // a surface yet. The surface is allocated *after* occlusion
+ // culling occurs, so that only visible tiles allocate GPU memory.
+ SurfaceTextureDescriptor::Native {
+ id: None,
+ }
+ }
+ };
+
+ TileSurface::Texture {
+ descriptor,
+ }
+ }
+ }
+ };
+
+ // Store the current surface backing info for use during batching.
+ self.surface = Some(surface);
+ }
+}
+
+/// 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)]
+pub enum BackdropKind {
+ Color {
+ color: ColorF,
+ },
+}
+
+/// Stores information about the calculated opaque backdrop of this slice.
+#[derive(Debug, Copy, Clone)]
+pub struct BackdropInfo {
+ /// The picture space rectangle that is known to be opaque. This is used
+ /// to determine where subpixel AA can be used, and where alpha blending
+ /// can be disabled.
+ pub opaque_rect: PictureRect,
+ /// If the backdrop covers the entire slice with an opaque color, this
+ /// will be set and can be used as a clear color for the slice's tiles.
+ pub spanning_opaque_color: Option<ColorF>,
+ /// Kind of the backdrop
+ pub kind: Option<BackdropKind>,
+ /// The picture space rectangle of the backdrop, if kind is set.
+ pub backdrop_rect: PictureRect,
+}
+
+impl BackdropInfo {
+ fn empty() -> Self {
+ BackdropInfo {
+ opaque_rect: PictureRect::zero(),
+ spanning_opaque_color: None,
+ kind: None,
+ backdrop_rect: PictureRect::zero(),
+ }
+ }
+}
+
+/// Represents the native surfaces created for a picture cache, if using
+/// a native compositor. An opaque and alpha surface is always created,
+/// but tiles are added to a surface based on current opacity. If the
+/// calculated opacity of a tile changes, the tile is invalidated and
+/// attached to a different native surface. This means that we don't
+/// need to invalidate the entire surface if only some tiles are changing
+/// opacity. It also means we can take advantage of opaque tiles on cache
+/// slices where only some of the tiles are opaque. There is an assumption
+/// that creating a native surface is cheap, and only when a tile is added
+/// to a surface is there a significant cost. This assumption holds true
+/// for the current native compositor implementations on Windows and Mac.
+pub struct NativeSurface {
+ /// Native surface for opaque tiles
+ pub opaque: NativeSurfaceId,
+ /// Native surface for alpha tiles
+ pub alpha: NativeSurfaceId,
+}
+
+/// Hash key for an external native compositor surface
+#[derive(PartialEq, Eq, Hash)]
+pub struct ExternalNativeSurfaceKey {
+ /// The YUV/RGB image keys that are used to draw this surface.
+ pub image_keys: [ImageKey; 3],
+ /// If this is not an 'external' compositor surface created via
+ /// Compositor::create_external_surface, this is set to the
+ /// current device size of the surface.
+ pub size: Option<DeviceIntSize>,
+}
+
+/// Information about a native compositor surface cached between frames.
+pub struct ExternalNativeSurface {
+ /// If true, the surface was used this frame. Used for a simple form
+ /// of GC to remove old surfaces.
+ pub used_this_frame: bool,
+ /// The native compositor surface handle
+ pub native_surface_id: NativeSurfaceId,
+ /// List of image keys, and current image generations, that are drawn in this surface.
+ /// The image generations are used to check if the compositor surface is dirty and
+ /// needs to be updated.
+ pub image_dependencies: [ImageDependency; 3],
+}
+
+/// The key that identifies a tile cache instance. For now, it's simple the index of
+/// the slice as it was created during scene building.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SliceId(usize);
+
+impl SliceId {
+ pub fn new(index: usize) -> Self {
+ SliceId(index)
+ }
+}
+
+/// Information that is required to reuse or create a new tile cache. Created
+/// during scene building and passed to the render backend / frame builder.
+pub struct TileCacheParams {
+ // The current debug flags for the system.
+ pub debug_flags: DebugFlags,
+ // Index of the slice (also effectively the key of the tile cache, though we use SliceId where that matters)
+ pub slice: usize,
+ // Flags describing content of this cache (e.g. scrollbars)
+ pub slice_flags: SliceFlags,
+ // The anchoring spatial node / scroll root
+ pub spatial_node_index: SpatialNodeIndex,
+ // The space in which visibility/invalidation/clipping computations are done.
+ pub visibility_node_index: SpatialNodeIndex,
+ // Optional background color of this tilecache. If present, can be used as an optimization
+ // to enable opaque blending and/or subpixel AA in more places.
+ pub background_color: Option<ColorF>,
+ // Node in the clip-tree that defines where we exclude clips from child prims
+ pub shared_clip_node_id: ClipNodeId,
+ // Clip leaf that is used to build the clip-chain for this tile cache.
+ pub shared_clip_leaf_id: Option<ClipLeafId>,
+ // Virtual surface sizes are always square, so this represents both the width and height
+ pub virtual_surface_size: i32,
+ // The number of Image surfaces that are being requested for this tile cache.
+ // This is only a suggestion - the tile cache will clamp this as a reasonable number
+ // and only promote a limited number of surfaces.
+ pub image_surface_count: usize,
+ // The number of YuvImage surfaces that are being requested for this tile cache.
+ // This is only a suggestion - the tile cache will clamp this as a reasonable number
+ // and only promote a limited number of surfaces.
+ 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 {
+ // External surface descriptor used by compositing logic
+ pub descriptor: ExternalSurfaceDescriptor,
+ // The compositor surface rect + any intersecting prims. Later prims that intersect
+ // with this must be added to the next sub-slice.
+ prohibited_rect: PictureRect,
+ // If the compositor surface content is opaque.
+ pub is_opaque: bool,
+}
+
+/// A SubSlice represents a potentially overlapping set of tiles within a picture cache. Most
+/// picture cache instances will have only a single sub-slice. The exception to this is when
+/// a picture cache has compositor surfaces, in which case sub slices are used to interleave
+/// content under or order the compositor surface(s).
+pub struct SubSlice {
+ /// Hash of tiles present in this picture.
+ pub tiles: FastHashMap<TileOffset, Box<Tile>>,
+ /// The allocated compositor surfaces for this picture cache. May be None if
+ /// not using native compositor, or if the surface was destroyed and needs
+ /// to be reallocated next time this surface contains valid tiles.
+ pub native_surface: Option<NativeSurface>,
+ /// List of compositor surfaces that have been promoted from primitives
+ /// in this tile cache.
+ pub compositor_surfaces: Vec<CompositorSurface>,
+ /// List of visible tiles to be composited for this subslice
+ pub composite_tiles: Vec<CompositeTile>,
+ /// Compositor descriptors of visible, opaque tiles (used by composite_state.push_surface)
+ pub opaque_tile_descriptors: Vec<CompositeTileDescriptor>,
+ /// Compositor descriptors of visible, alpha tiles (used by composite_state.push_surface)
+ pub alpha_tile_descriptors: Vec<CompositeTileDescriptor>,
+}
+
+impl SubSlice {
+ /// Construct a new sub-slice
+ fn new() -> Self {
+ SubSlice {
+ tiles: FastHashMap::default(),
+ native_surface: None,
+ compositor_surfaces: Vec::new(),
+ composite_tiles: Vec::new(),
+ opaque_tile_descriptors: Vec::new(),
+ alpha_tile_descriptors: Vec::new(),
+ }
+ }
+
+ /// Reset the list of compositor surfaces that follow this sub-slice.
+ /// Built per-frame, since APZ may change whether an image is suitable to be a compositor surface.
+ fn reset(&mut self) {
+ self.compositor_surfaces.clear();
+ self.composite_tiles.clear();
+ self.opaque_tile_descriptors.clear();
+ self.alpha_tile_descriptors.clear();
+ }
+
+ /// Resize the tile grid to match a new tile bounds
+ fn resize(&mut self, new_tile_rect: TileRect) -> FastHashMap<TileOffset, Box<Tile>> {
+ let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
+ self.tiles.reserve(new_tile_rect.area() as usize);
+
+ for y in new_tile_rect.min.y .. new_tile_rect.max.y {
+ for x in new_tile_rect.min.x .. new_tile_rect.max.x {
+ let key = TileOffset::new(x, y);
+ let tile = old_tiles
+ .remove(&key)
+ .unwrap_or_else(|| {
+ Box::new(Tile::new(key))
+ });
+ self.tiles.insert(key, tile);
+ }
+ }
+
+ old_tiles
+ }
+}
+
+pub struct BackdropSurface {
+ pub id: NativeSurfaceId,
+ color: ColorF,
+ pub device_rect: DeviceRect,
+}
+
+/// Represents a cache of tiles that make up a picture primitives.
+pub struct TileCacheInstance {
+ // The current debug flags for the system.
+ pub debug_flags: DebugFlags,
+ /// Index of the tile cache / slice for this frame builder. It's determined
+ /// by the setup_picture_caching method during flattening, which splits the
+ /// picture tree into multiple slices. It's used as a simple input to the tile
+ /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed
+ /// between display lists - this seems very unlikely to occur on most pages, but
+ /// can be revisited if we ever notice that.
+ pub slice: usize,
+ /// Propagated information about the slice
+ pub slice_flags: SliceFlags,
+ /// The currently selected tile size to use for this cache
+ pub current_tile_size: DeviceIntSize,
+ /// The list of sub-slices in this tile cache
+ pub sub_slices: Vec<SubSlice>,
+ /// The positioning node for this tile cache.
+ pub spatial_node_index: SpatialNodeIndex,
+ /// The coordinate space to do visibility/clipping/invalidation in.
+ pub visibility_node_index: SpatialNodeIndex,
+ /// List of opacity bindings, with some extra information
+ /// about whether they changed since last frame.
+ opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
+ /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
+ old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
+ /// A helper to compare transforms between previous and current frame.
+ spatial_node_comparer: SpatialNodeComparer,
+ /// List of color bindings, with some extra information
+ /// about whether they changed since last frame.
+ color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
+ /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
+ old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
+ /// The current dirty region tracker for this picture.
+ pub dirty_region: DirtyRegion,
+ /// Current size of tiles in picture units.
+ tile_size: PictureSize,
+ /// Tile coords of the currently allocated grid.
+ tile_rect: TileRect,
+ /// Pre-calculated versions of the tile_rect above, used to speed up the
+ /// calculations in get_tile_coords_for_rect.
+ tile_bounds_p0: TileOffset,
+ tile_bounds_p1: TileOffset,
+ /// Local rect (unclipped) of the picture this cache covers.
+ pub local_rect: PictureRect,
+ /// The local clip rect, from the shared clips of this picture.
+ pub local_clip_rect: PictureRect,
+ /// Registered clip in CompositeState for this picture cache
+ pub compositor_clip: Option<CompositorClipIndex>,
+ /// The screen rect, transformed to local picture space.
+ pub screen_rect_in_pic_space: PictureRect,
+ /// The surface index that this tile cache will be drawn into.
+ surface_index: SurfaceIndex,
+ /// The background color from the renderer. If this is set opaque, we know it's
+ /// fine to clear the tiles to this and allow subpixel text on the first slice.
+ pub background_color: Option<ColorF>,
+ /// Information about the calculated backdrop content of this cache.
+ pub backdrop: BackdropInfo,
+ /// The allowed subpixel mode for this surface, which depends on the detected
+ /// opacity of the background.
+ pub subpixel_mode: SubpixelMode,
+ // Node in the clip-tree that defines where we exclude clips from child prims
+ pub shared_clip_node_id: ClipNodeId,
+ // Clip leaf that is used to build the clip-chain for this tile cache.
+ pub shared_clip_leaf_id: Option<ClipLeafId>,
+ /// The number of frames until this cache next evaluates what tile size to use.
+ /// If a picture rect size is regularly changing just around a size threshold,
+ /// we don't want to constantly invalidate and reallocate different tile size
+ /// configuration each frame.
+ frames_until_size_eval: usize,
+ /// For DirectComposition, virtual surfaces don't support negative coordinates. However,
+ /// picture cache tile coordinates can be negative. To handle this, we apply an offset
+ /// to each tile in DirectComposition. We want to change this as little as possible,
+ /// to avoid invalidating tiles. However, if we have a picture cache tile coordinate
+ /// which is outside the virtual surface bounds, we must change this to allow
+ /// correct remapping of the coordinates passed to BeginDraw in DC.
+ virtual_offset: DeviceIntPoint,
+ /// keep around the hash map used as compare_cache to avoid reallocating it each
+ /// frame.
+ compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
+ /// The currently considered tile size override. Used to check if we should
+ /// re-evaluate tile size, even if the frame timer hasn't expired.
+ tile_size_override: Option<DeviceIntSize>,
+ /// A cache of compositor surfaces that are retained between frames
+ pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
+ /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting
+ frame_id: FrameId,
+ /// Registered transform in CompositeState for this picture cache
+ pub transform_index: CompositorTransformIndex,
+ /// Current transform mapping local picture space to compositor surface raster space
+ local_to_raster: ScaleOffset,
+ /// Current transform mapping compositor surface raster space to final device space
+ raster_to_device: ScaleOffset,
+ /// If true, we need to invalidate all tiles during `post_update`
+ invalidate_all_tiles: bool,
+ /// The current raster scale for tiles in this cache
+ current_raster_scale: f32,
+ /// Depth of off-screen surfaces that are currently pushed during dependency updates
+ current_surface_traversal_depth: usize,
+ /// A list of extra dirty invalidation tests that can only be checked once we
+ /// know the dirty rect of all tiles
+ deferred_dirty_tests: Vec<DeferredDirtyTest>,
+ /// Is there a backdrop associated with this cache
+ found_prims_after_backdrop: bool,
+ pub backdrop_surface: Option<BackdropSurface>,
+ /// List of underlay compositor surfaces that exist in this picture cache
+ pub underlays: Vec<ExternalSurfaceDescriptor>,
+ /// "Region" (actually a spanning rect) containing all overlay promoted surfaces
+ pub overlay_region: PictureRect,
+ /// The number YuvImage prims in this cache, provided in our TileCacheParams.
+ pub yuv_images_count: usize,
+ /// The remaining number of YuvImage prims we will see this frame. We prioritize
+ /// promoting these before promoting any Image prims.
+ pub yuv_images_remaining: usize,
+}
+
+#[derive(Clone, Copy)]
+enum SurfacePromotionFailure {
+ ImageWaitingOnYuvImage,
+ NotPremultipliedAlpha,
+ OverlaySurfaceLimit,
+ OverlayNeedsMask,
+ UnderlayAlphaBackdrop,
+ UnderlaySurfaceLimit,
+ UnderlayIntersectsOverlay,
+ UnderlayLowQualityZoom,
+ NotRootTileCache,
+ ComplexTransform,
+ SliceAtomic,
+ SizeTooLarge,
+}
+
+impl Display for SurfacePromotionFailure {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ write!(
+ f,
+ "{}",
+ match *self {
+ SurfacePromotionFailure::ImageWaitingOnYuvImage => "Image prim waiting for all YuvImage prims to be considered for promotion",
+ SurfacePromotionFailure::NotPremultipliedAlpha => "does not use premultiplied alpha",
+ SurfacePromotionFailure::OverlaySurfaceLimit => "hit the overlay surface limit",
+ SurfacePromotionFailure::OverlayNeedsMask => "overlay not allowed for prim with mask",
+ SurfacePromotionFailure::UnderlayAlphaBackdrop => "underlay requires an opaque backdrop",
+ SurfacePromotionFailure::UnderlaySurfaceLimit => "hit the underlay surface limit",
+ SurfacePromotionFailure::UnderlayIntersectsOverlay => "underlay intersects already-promoted overlay",
+ SurfacePromotionFailure::UnderlayLowQualityZoom => "underlay not allowed during low-quality pinch zoom",
+ SurfacePromotionFailure::NotRootTileCache => "is not on a root tile cache",
+ SurfacePromotionFailure::ComplexTransform => "has a complex transform",
+ SurfacePromotionFailure::SliceAtomic => "slice is atomic",
+ SurfacePromotionFailure::SizeTooLarge => "surface is too large for compositor",
+ }.to_owned()
+ )
+ }
+}
+
+impl TileCacheInstance {
+ pub fn new(params: TileCacheParams) -> Self {
+ // Determine how many sub-slices we need. Clamp to an arbitrary limit to ensure
+ // we don't create a huge number of OS compositor tiles and sub-slices.
+ let sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1;
+
+ let mut sub_slices = Vec::with_capacity(sub_slice_count);
+ for _ in 0 .. sub_slice_count {
+ sub_slices.push(SubSlice::new());
+ }
+
+ TileCacheInstance {
+ debug_flags: params.debug_flags,
+ slice: params.slice,
+ slice_flags: params.slice_flags,
+ spatial_node_index: params.spatial_node_index,
+ visibility_node_index: params.visibility_node_index,
+ sub_slices,
+ opacity_bindings: FastHashMap::default(),
+ old_opacity_bindings: FastHashMap::default(),
+ spatial_node_comparer: SpatialNodeComparer::new(),
+ color_bindings: FastHashMap::default(),
+ old_color_bindings: FastHashMap::default(),
+ dirty_region: DirtyRegion::new(params.visibility_node_index, params.spatial_node_index),
+ tile_size: PictureSize::zero(),
+ tile_rect: TileRect::zero(),
+ tile_bounds_p0: TileOffset::zero(),
+ tile_bounds_p1: TileOffset::zero(),
+ local_rect: PictureRect::zero(),
+ local_clip_rect: PictureRect::zero(),
+ compositor_clip: None,
+ screen_rect_in_pic_space: PictureRect::zero(),
+ surface_index: SurfaceIndex(0),
+ background_color: params.background_color,
+ backdrop: BackdropInfo::empty(),
+ subpixel_mode: SubpixelMode::Allow,
+ shared_clip_node_id: params.shared_clip_node_id,
+ shared_clip_leaf_id: params.shared_clip_leaf_id,
+ current_tile_size: DeviceIntSize::zero(),
+ frames_until_size_eval: 0,
+ // Default to centering the virtual offset in the middle of the DC virtual surface
+ virtual_offset: DeviceIntPoint::new(
+ params.virtual_surface_size / 2,
+ params.virtual_surface_size / 2,
+ ),
+ compare_cache: FastHashMap::default(),
+ tile_size_override: None,
+ external_native_surface_cache: FastHashMap::default(),
+ frame_id: FrameId::INVALID,
+ transform_index: CompositorTransformIndex::INVALID,
+ raster_to_device: ScaleOffset::identity(),
+ local_to_raster: ScaleOffset::identity(),
+ invalidate_all_tiles: true,
+ current_raster_scale: 1.0,
+ current_surface_traversal_depth: 0,
+ deferred_dirty_tests: Vec::new(),
+ found_prims_after_backdrop: false,
+ backdrop_surface: None,
+ underlays: Vec::new(),
+ overlay_region: PictureRect::zero(),
+ yuv_images_count: params.yuv_image_surface_count,
+ yuv_images_remaining: 0,
+ }
+ }
+
+ /// Return the total number of tiles allocated by this tile cache
+ pub fn tile_count(&self) -> usize {
+ self.tile_rect.area() as usize * self.sub_slices.len()
+ }
+
+ /// Trims memory held by the tile cache, such as native surfaces.
+ pub fn memory_pressure(&mut self, resource_cache: &mut ResourceCache) {
+ for sub_slice in &mut self.sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
+ // Reseting the id to None with take() ensures that a new
+ // tile will be allocated during the next frame build.
+ if let Some(id) = id.take() {
+ resource_cache.destroy_compositor_tile(id);
+ }
+ }
+ }
+ if let Some(native_surface) = sub_slice.native_surface.take() {
+ resource_cache.destroy_compositor_surface(native_surface.opaque);
+ resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+ }
+ }
+
+ /// Reset this tile cache with the updated parameters from a new scene
+ /// that has arrived. This allows the tile cache to be retained across
+ /// new scenes.
+ pub fn prepare_for_new_scene(
+ &mut self,
+ params: TileCacheParams,
+ resource_cache: &mut ResourceCache,
+ ) {
+ // We should only receive updated state for matching slice key
+ assert_eq!(self.slice, params.slice);
+
+ // Determine how many sub-slices we need, based on how many compositor surface prims are
+ // in the supplied primitive list.
+ let required_sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1;
+
+ if self.sub_slices.len() != required_sub_slice_count {
+ self.tile_rect = TileRect::zero();
+
+ if self.sub_slices.len() > required_sub_slice_count {
+ let old_sub_slices = self.sub_slices.split_off(required_sub_slice_count);
+
+ for mut sub_slice in old_sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
+ if let Some(id) = id.take() {
+ resource_cache.destroy_compositor_tile(id);
+ }
+ }
+ }
+
+ if let Some(native_surface) = sub_slice.native_surface {
+ resource_cache.destroy_compositor_surface(native_surface.opaque);
+ resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+ }
+ } else {
+ while self.sub_slices.len() < required_sub_slice_count {
+ self.sub_slices.push(SubSlice::new());
+ }
+ }
+ }
+
+ // Store the parameters from the scene builder for this slice. Other
+ // params in the tile cache are retained and reused, or are always
+ // updated during pre/post_update.
+ self.slice_flags = params.slice_flags;
+ self.spatial_node_index = params.spatial_node_index;
+ self.background_color = params.background_color;
+ self.shared_clip_leaf_id = params.shared_clip_leaf_id;
+ self.shared_clip_node_id = params.shared_clip_node_id;
+
+ // Since the slice flags may have changed, ensure we re-evaluate the
+ // appropriate tile size for this cache next update.
+ self.frames_until_size_eval = 0;
+
+ // Update the number of YuvImage prims we have in the scene.
+ self.yuv_images_count = params.yuv_image_surface_count;
+ }
+
+ /// Destroy any manually managed resources before this picture cache is
+ /// destroyed, such as native compositor surfaces.
+ pub fn destroy(
+ self,
+ resource_cache: &mut ResourceCache,
+ ) {
+ for sub_slice in self.sub_slices {
+ if let Some(native_surface) = sub_slice.native_surface {
+ resource_cache.destroy_compositor_surface(native_surface.opaque);
+ resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+ }
+
+ for (_, external_surface) in self.external_native_surface_cache {
+ resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
+ }
+
+ if let Some(backdrop_surface) = &self.backdrop_surface {
+ resource_cache.destroy_compositor_surface(backdrop_surface.id);
+ }
+ }
+
+ /// Get the tile coordinates for a given rectangle.
+ fn get_tile_coords_for_rect(
+ &self,
+ rect: &PictureRect,
+ ) -> (TileOffset, TileOffset) {
+ // Get the tile coordinates in the picture space.
+ let mut p0 = TileOffset::new(
+ (rect.min.x / self.tile_size.width).floor() as i32,
+ (rect.min.y / self.tile_size.height).floor() as i32,
+ );
+
+ let mut p1 = TileOffset::new(
+ (rect.max.x / self.tile_size.width).ceil() as i32,
+ (rect.max.y / self.tile_size.height).ceil() as i32,
+ );
+
+ // Clamp the tile coordinates here to avoid looping over irrelevant tiles later on.
+ p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
+ p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
+ p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
+ p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
+
+ (p0, p1)
+ }
+
+ /// Update transforms, opacity, color bindings and tile rects.
+ pub fn pre_update(
+ &mut self,
+ surface_index: SurfaceIndex,
+ frame_context: &FrameVisibilityContext,
+ frame_state: &mut FrameVisibilityState,
+ ) -> WorldRect {
+ let surface = &frame_state.surfaces[surface_index.0];
+ let pic_rect = surface.unclipped_local_rect;
+
+ self.surface_index = surface_index;
+ self.local_rect = pic_rect;
+ self.local_clip_rect = PictureRect::max_rect();
+ self.deferred_dirty_tests.clear();
+ self.underlays.clear();
+ self.overlay_region = PictureRect::zero();
+ self.yuv_images_remaining = self.yuv_images_count;
+
+ for sub_slice in &mut self.sub_slices {
+ sub_slice.reset();
+ }
+
+ // Reset the opaque rect + subpixel mode, as they are calculated
+ // during the prim dependency checks.
+ self.backdrop = BackdropInfo::empty();
+
+ // Calculate the screen rect in picture space, for later comparison against
+ // backdrops, and prims potentially covering backdrops.
+ let pic_to_world_mapper = SpaceMapper::new_with_target(
+ frame_context.root_spatial_node_index,
+ self.spatial_node_index,
+ frame_context.global_screen_world_rect,
+ frame_context.spatial_tree,
+ );
+ self.screen_rect_in_pic_space = pic_to_world_mapper
+ .unmap(&frame_context.global_screen_world_rect)
+ .expect("unable to unmap screen rect");
+
+ let pic_to_vis_mapper = SpaceMapper::new_with_target(
+ // TODO: use the raster node instead of the root node.
+ frame_context.root_spatial_node_index,
+ self.spatial_node_index,
+ surface.culling_rect,
+ frame_context.spatial_tree,
+ );
+
+ // If there is a valid set of shared clips, build a clip chain instance for this,
+ // which will provide a local clip rect. This is useful for establishing things
+ // like whether the backdrop rect supplied by Gecko can be considered opaque.
+ if let Some(shared_clip_leaf_id) = self.shared_clip_leaf_id {
+ let map_local_to_picture = SpaceMapper::new(
+ self.spatial_node_index,
+ pic_rect,
+ );
+
+ frame_state.clip_store.set_active_clips(
+ self.spatial_node_index,
+ map_local_to_picture.ref_spatial_node_index,
+ surface.visibility_spatial_node_index,
+ shared_clip_leaf_id,
+ frame_context.spatial_tree,
+ &mut frame_state.data_stores.clip,
+ &frame_state.clip_tree,
+ );
+
+ let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance(
+ pic_rect.cast_unit(),
+ &map_local_to_picture,
+ &pic_to_vis_mapper,
+ frame_context.spatial_tree,
+ &mut frame_state.frame_gpu_data.f32,
+ frame_state.resource_cache,
+ frame_context.global_device_pixel_scale,
+ &surface.culling_rect,
+ &mut frame_state.data_stores.clip,
+ frame_state.rg_builder,
+ true,
+ );
+
+ // Ensure that if the entire picture cache is clipped out, the local
+ // clip rect is zero. This makes sure we don't register any occluders
+ // that are actually off-screen.
+ self.local_clip_rect = PictureRect::zero();
+ self.compositor_clip = None;
+
+ if let Some(clip_chain) = clip_chain_instance {
+ self.local_clip_rect = clip_chain.pic_coverage_rect;
+ self.compositor_clip = None;
+
+ if clip_chain.needs_mask {
+ for i in 0 .. clip_chain.clips_range.count {
+ let clip_instance = frame_state
+ .clip_store
+ .get_instance_from_range(&clip_chain.clips_range, i);
+ let clip_node = &frame_state.data_stores.clip[clip_instance.handle];
+
+ match clip_node.item.kind {
+ ClipItemKind::RoundedRectangle { rect, radius, mode } => {
+ assert_eq!(mode, ClipMode::Clip);
+
+ // Map the clip in to device space. We know from the shared
+ // clip creation logic it's in root coord system, so only a
+ // 2d axis-aligned transform can apply. For example, in the
+ // case of a pinch-zoom effect.
+ let map = ClipSpaceConversion::new(
+ frame_context.root_spatial_node_index,
+ clip_node.item.spatial_node_index,
+ frame_context.root_spatial_node_index,
+ frame_context.spatial_tree,
+ );
+
+ let (rect, radius) = match map {
+ ClipSpaceConversion::Local => {
+ (rect.cast_unit(), radius)
+ }
+ ClipSpaceConversion::ScaleOffset(scale_offset) => {
+ (
+ scale_offset.map_rect(&rect),
+ BorderRadius {
+ top_left: scale_offset.map_size(&radius.top_left),
+ top_right: scale_offset.map_size(&radius.top_right),
+ bottom_left: scale_offset.map_size(&radius.bottom_left),
+ bottom_right: scale_offset.map_size(&radius.bottom_right),
+ },
+ )
+ }
+ ClipSpaceConversion::Transform(..) => {
+ unreachable!();
+ }
+ };
+
+ self.compositor_clip = Some(frame_state.composite_state.register_clip(
+ rect,
+ radius,
+ ));
+
+ break;
+ }
+ _ => {
+ // The logic to check for shared clips excludes other mask
+ // clip types (box-shadow, image-mask) and ensures that the
+ // clip is in the root coord system (so rect clips can't
+ // produce a mask).
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Advance the current frame ID counter for this picture cache (must be done
+ // after any retained prev state is taken above).
+ self.frame_id.advance();
+
+ // Notify the spatial node comparer that a new frame has started, and the
+ // current reference spatial node for this tile cache.
+ self.spatial_node_comparer.next_frame(self.spatial_node_index);
+
+ // At the start of the frame, step through each current compositor surface
+ // and mark it as unused. Later, this is used to free old compositor surfaces.
+ // TODO(gw): In future, we might make this more sophisticated - for example,
+ // retaining them for >1 frame if unused, or retaining them in some
+ // kind of pool to reduce future allocations.
+ for external_native_surface in self.external_native_surface_cache.values_mut() {
+ external_native_surface.used_this_frame = false;
+ }
+
+ // Only evaluate what tile size to use fairly infrequently, so that we don't end
+ // up constantly invalidating and reallocating tiles if the picture rect size is
+ // changing near a threshold value.
+ if self.frames_until_size_eval == 0 ||
+ self.tile_size_override != frame_context.config.tile_size_override {
+
+ // Work out what size tile is appropriate for this picture cache.
+ let desired_tile_size = match frame_context.config.tile_size_override {
+ Some(tile_size_override) => {
+ tile_size_override
+ }
+ None => {
+ if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) {
+ if pic_rect.width() <= pic_rect.height() {
+ TILE_SIZE_SCROLLBAR_VERTICAL
+ } else {
+ TILE_SIZE_SCROLLBAR_HORIZONTAL
+ }
+ } else {
+ frame_state.resource_cache.picture_textures.default_tile_size()
+ }
+ }
+ };
+
+ // If the desired tile size has changed, then invalidate and drop any
+ // existing tiles.
+ if desired_tile_size != self.current_tile_size {
+ for sub_slice in &mut self.sub_slices {
+ // Destroy any native surfaces on the tiles that will be dropped due
+ // to resizing.
+ if let Some(native_surface) = sub_slice.native_surface.take() {
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+ sub_slice.tiles.clear();
+ }
+ self.tile_rect = TileRect::zero();
+ self.current_tile_size = desired_tile_size;
+ }
+
+ // Reset counter until next evaluating the desired tile size. This is an
+ // arbitrary value.
+ self.frames_until_size_eval = 120;
+ self.tile_size_override = frame_context.config.tile_size_override;
+ }
+
+ // Get the complete scale-offset from local space to device space
+ let local_to_device = get_relative_scale_offset(
+ self.spatial_node_index,
+ frame_context.root_spatial_node_index,
+ frame_context.spatial_tree,
+ );
+
+ // Get the compositor transform, which depends on pinch-zoom mode
+ let mut raster_to_device = local_to_device;
+
+ if frame_context.config.low_quality_pinch_zoom {
+ raster_to_device.scale.x /= self.current_raster_scale;
+ raster_to_device.scale.y /= self.current_raster_scale;
+ } else {
+ raster_to_device.scale.x = 1.0;
+ raster_to_device.scale.y = 1.0;
+ }
+
+ // Use that compositor transform to calculate a relative local to surface
+ let local_to_raster = local_to_device.then(&raster_to_device.inverse());
+
+ const EPSILON: f32 = 0.001;
+ let compositor_translation_changed =
+ !raster_to_device.offset.x.approx_eq_eps(&self.raster_to_device.offset.x, &EPSILON) ||
+ !raster_to_device.offset.y.approx_eq_eps(&self.raster_to_device.offset.y, &EPSILON);
+ let compositor_scale_changed =
+ !raster_to_device.scale.x.approx_eq_eps(&self.raster_to_device.scale.x, &EPSILON) ||
+ !raster_to_device.scale.y.approx_eq_eps(&self.raster_to_device.scale.y, &EPSILON);
+ let surface_scale_changed =
+ !local_to_raster.scale.x.approx_eq_eps(&self.local_to_raster.scale.x, &EPSILON) ||
+ !local_to_raster.scale.y.approx_eq_eps(&self.local_to_raster.scale.y, &EPSILON);
+
+ if compositor_translation_changed ||
+ compositor_scale_changed ||
+ surface_scale_changed ||
+ frame_context.config.force_invalidation {
+ frame_state.composite_state.dirty_rects_are_valid = false;
+ }
+
+ self.raster_to_device = raster_to_device;
+ self.local_to_raster = local_to_raster;
+ self.invalidate_all_tiles = surface_scale_changed || frame_context.config.force_invalidation;
+
+ // Do a hacky diff of opacity binding values from the last frame. This is
+ // used later on during tile invalidation tests.
+ let current_properties = frame_context.scene_properties.float_properties();
+ mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings);
+
+ self.opacity_bindings.clear();
+ for (id, value) in current_properties {
+ let changed = match self.old_opacity_bindings.get(id) {
+ Some(old_property) => !old_property.value.approx_eq(value),
+ None => true,
+ };
+ self.opacity_bindings.insert(*id, OpacityBindingInfo {
+ value: *value,
+ changed,
+ });
+ }
+
+ // Do a hacky diff of color binding values from the last frame. This is
+ // used later on during tile invalidation tests.
+ let current_properties = frame_context.scene_properties.color_properties();
+ mem::swap(&mut self.color_bindings, &mut self.old_color_bindings);
+
+ self.color_bindings.clear();
+ for (id, value) in current_properties {
+ let changed = match self.old_color_bindings.get(id) {
+ Some(old_property) => old_property.value != (*value).into(),
+ None => true,
+ };
+ self.color_bindings.insert(*id, ColorBindingInfo {
+ value: (*value).into(),
+ changed,
+ });
+ }
+
+ let world_tile_size = WorldSize::new(
+ self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
+ self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
+ );
+
+ self.tile_size = PictureSize::new(
+ world_tile_size.width / self.local_to_raster.scale.x,
+ world_tile_size.height / self.local_to_raster.scale.y,
+ );
+
+ // Inflate the needed rect a bit, so that we retain tiles that we have drawn
+ // but have just recently gone off-screen. This means that we avoid re-drawing
+ // tiles if the user is scrolling up and down small amounts, at the cost of
+ // a bit of extra texture memory.
+ let desired_rect_in_pic_space = self.screen_rect_in_pic_space
+ .inflate(0.0, 1.0 * self.tile_size.height);
+
+ let needed_rect_in_pic_space = desired_rect_in_pic_space
+ .intersection(&pic_rect)
+ .unwrap_or_else(Box2D::zero);
+
+ let p0 = needed_rect_in_pic_space.min;
+ let p1 = needed_rect_in_pic_space.max;
+
+ let x0 = (p0.x / self.tile_size.width).floor() as i32;
+ let x1 = (p1.x / self.tile_size.width).ceil() as i32;
+
+ let y0 = (p0.y / self.tile_size.height).floor() as i32;
+ let y1 = (p1.y / self.tile_size.height).ceil() as i32;
+
+ let new_tile_rect = TileRect {
+ min: TileOffset::new(x0, y0),
+ max: TileOffset::new(x1, y1),
+ };
+
+ // Determine whether the current bounds of the tile grid will exceed the
+ // bounds of the DC virtual surface, taking into account the current
+ // virtual offset. If so, we need to invalidate all tiles, and set up
+ // a new virtual offset, centered around the current tile grid.
+
+ let virtual_surface_size = frame_context.config.compositor_kind.get_virtual_surface_size();
+ // We only need to invalidate in this case if the underlying platform
+ // uses virtual surfaces.
+ if virtual_surface_size > 0 {
+ // Get the extremities of the tile grid after virtual offset is applied
+ let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width;
+ let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height;
+ let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width;
+ let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height;
+
+ let need_new_virtual_offset = tx0 < 0 ||
+ ty0 < 0 ||
+ tx1 >= virtual_surface_size ||
+ ty1 >= virtual_surface_size;
+
+ if need_new_virtual_offset {
+ // Calculate a new virtual offset, centered around the middle of the
+ // current tile grid. This means we won't need to invalidate and get
+ // a new offset for a long time!
+ self.virtual_offset = DeviceIntPoint::new(
+ (virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width,
+ (virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height,
+ );
+
+ // Invalidate all native tile surfaces. They will be re-allocated next time
+ // they are scheduled to be rasterized.
+ for sub_slice in &mut self.sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
+ if let Some(id) = id.take() {
+ frame_state.resource_cache.destroy_compositor_tile(id);
+ tile.surface = None;
+ // Invalidate the entire tile to force a redraw.
+ // TODO(gw): Add a new invalidation reason for virtual offset changing
+ tile.invalidate(None, InvalidationReason::CompositorKindChanged);
+ }
+ }
+ }
+
+ // Destroy the native virtual surfaces. They will be re-allocated next time a tile
+ // that references them is scheduled to draw.
+ if let Some(native_surface) = sub_slice.native_surface.take() {
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+ }
+ }
+ }
+
+ // Rebuild the tile grid if the picture cache rect has changed.
+ if new_tile_rect != self.tile_rect {
+ for sub_slice in &mut self.sub_slices {
+ let mut old_tiles = sub_slice.resize(new_tile_rect);
+
+ // When old tiles that remain after the loop, dirty rects are not valid.
+ if !old_tiles.is_empty() {
+ frame_state.composite_state.dirty_rects_are_valid = false;
+ }
+
+ // Any old tiles that remain after the loop above are going to be dropped. For
+ // simple composite mode, the texture cache handle will expire and be collected
+ // by the texture cache. For native compositor mode, we need to explicitly
+ // invoke a callback to the client to destroy that surface.
+ frame_state.composite_state.destroy_native_tiles(
+ old_tiles.values_mut(),
+ frame_state.resource_cache,
+ );
+ }
+ }
+
+ // This is duplicated information from tile_rect, but cached here to avoid
+ // redundant calculations during get_tile_coords_for_rect
+ self.tile_bounds_p0 = TileOffset::new(x0, y0);
+ self.tile_bounds_p1 = TileOffset::new(x1, y1);
+ self.tile_rect = new_tile_rect;
+
+ let mut world_culling_rect = WorldRect::zero();
+
+ let mut ctx = TilePreUpdateContext {
+ pic_to_world_mapper,
+ background_color: self.background_color,
+ global_screen_world_rect: frame_context.global_screen_world_rect,
+ tile_size: self.tile_size,
+ frame_id: self.frame_id,
+ };
+
+ // Pre-update each tile
+ for sub_slice in &mut self.sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ tile.pre_update(&ctx);
+
+ // Only include the tiles that are currently in view into the world culling
+ // rect. This is a very important optimization for a couple of reasons:
+ // (1) Primitives that intersect with tiles in the grid that are not currently
+ // visible can be skipped from primitive preparation, clip chain building
+ // and tile dependency updates.
+ // (2) When we need to allocate an off-screen surface for a child picture (for
+ // example a CSS filter) we clip the size of the GPU surface to the world
+ // culling rect below (to ensure we draw enough of it to be sampled by any
+ // tiles that reference it). Making the world culling rect only affected
+ // by visible tiles (rather than the entire virtual tile display port) can
+ // result in allocating _much_ smaller GPU surfaces for cases where the
+ // true off-screen surface size is very large.
+ if tile.is_visible {
+ world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
+ }
+ }
+
+ // The background color can only be applied to the first sub-slice.
+ ctx.background_color = None;
+ }
+
+ // If compositor mode is changed, need to drop all incompatible tiles.
+ match frame_context.config.compositor_kind {
+ CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
+ for sub_slice in &mut self.sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
+ if let Some(id) = id.take() {
+ frame_state.resource_cache.destroy_compositor_tile(id);
+ }
+ tile.surface = None;
+ // Invalidate the entire tile to force a redraw.
+ tile.invalidate(None, InvalidationReason::CompositorKindChanged);
+ }
+ }
+
+ if let Some(native_surface) = sub_slice.native_surface.take() {
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
+ frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
+ }
+ }
+
+ for (_, external_surface) in self.external_native_surface_cache.drain() {
+ frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
+ }
+ }
+ CompositorKind::Native { .. } => {
+ // This could hit even when compositor mode is not changed,
+ // then we need to check if there are incompatible tiles.
+ for sub_slice in &mut self.sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface {
+ tile.surface = None;
+ // Invalidate the entire tile to force a redraw.
+ tile.invalidate(None, InvalidationReason::CompositorKindChanged);
+ }
+ }
+ }
+ }
+ }
+
+ world_culling_rect
+ }
+
+ fn can_promote_to_surface(
+ &mut self,
+ prim_clip_chain: &ClipChainInstance,
+ prim_spatial_node_index: SpatialNodeIndex,
+ is_root_tile_cache: bool,
+ sub_slice_index: usize,
+ surface_kind: CompositorSurfaceKind,
+ pic_coverage_rect: PictureRect,
+ frame_context: &FrameVisibilityContext,
+ force: bool,
+ ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
+ use crate::picture::SurfacePromotionFailure::*;
+
+ // Each strategy has different restrictions on whether we can promote
+ match surface_kind {
+ CompositorSurfaceKind::Overlay => {
+ // For now, only support a small (arbitrary) number of compositor surfaces.
+ // Non-opaque compositor surfaces require sub-slices, as they are drawn
+ // as overlays.
+ if sub_slice_index == self.sub_slices.len() - 1 {
+ return Err(OverlaySurfaceLimit);
+ }
+
+ // If a complex clip is being applied to this primitive, it can't be
+ // promoted directly to a compositor surface.
+ if prim_clip_chain.needs_mask {
+ return Err(OverlayNeedsMask);
+ }
+ }
+ CompositorSurfaceKind::Underlay => {
+ // If a mask is needed, there are some restrictions.
+ if prim_clip_chain.needs_mask {
+ // Need an opaque region behind this prim. The opaque region doesn't
+ // need to span the entire visible region of the TileCacheInstance,
+ // which would set self.backdrop.kind, but that also qualifies.
+ if !self.backdrop.opaque_rect.contains_box(&pic_coverage_rect) {
+ let result = Err(UnderlayAlphaBackdrop);
+ // If we aren't forcing, give up and return Err.
+ if !force {
+ return result;
+ }
+
+ // Log this but don't return an error.
+ self.report_promotion_failure(result, pic_coverage_rect, true);
+ }
+
+ // Only one masked underlay allowed.
+ if !self.underlays.is_empty() {
+ return Err(UnderlaySurfaceLimit);
+ }
+ }
+
+ // Underlays can't appear on top of overlays, because they can't punch
+ // through the existing overlay.
+ if self.overlay_region.intersects(&pic_coverage_rect) {
+ let result = Err(UnderlayIntersectsOverlay);
+ // If we aren't forcing, give up and return Err.
+ if !force {
+ return result;
+ }
+
+ // Log this but don't return an error.
+ self.report_promotion_failure(result, pic_coverage_rect, true);
+ }
+
+ // Underlay cutouts are difficult to align with compositor surfaces when
+ // compositing during low-quality zoom, and the required invalidation
+ // whilst zooming would prevent low-quality zoom from working efficiently.
+ if frame_context.config.low_quality_pinch_zoom &&
+ frame_context.spatial_tree.get_spatial_node(prim_spatial_node_index).is_ancestor_or_self_zooming
+ {
+ return Err(UnderlayLowQualityZoom);
+ }
+ }
+ CompositorSurfaceKind::Blit => unreachable!(),
+ }
+
+ // If not on the root picture cache, it has some kind of
+ // complex effect (such as a filter, mix-blend-mode or 3d transform).
+ if !is_root_tile_cache {
+ return Err(NotRootTileCache);
+ }
+
+ let mapper : SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
+ frame_context.root_spatial_node_index,
+ prim_spatial_node_index,
+ frame_context.global_screen_world_rect,
+ &frame_context.spatial_tree);
+ let transform = mapper.get_transform();
+ if !transform.is_2d_scale_translation() {
+ let result = Err(ComplexTransform);
+ // Unfortunately, ComplexTransform absolutely prevents proper
+ // functioning of surface promotion. Treating this as a warning
+ // instead of an error will cause a crash in get_relative_scale_offset.
+ return result;
+ }
+
+ if self.slice_flags.contains(SliceFlags::IS_ATOMIC) {
+ return Err(SliceAtomic);
+ }
+
+ Ok(surface_kind)
+ }
+
+ fn setup_compositor_surfaces_yuv(
+ &mut self,
+ sub_slice_index: usize,
+ prim_info: &mut PrimitiveDependencyInfo,
+ flags: PrimitiveFlags,
+ local_prim_rect: LayoutRect,
+ prim_spatial_node_index: SpatialNodeIndex,
+ pic_coverage_rect: PictureRect,
+ frame_context: &FrameVisibilityContext,
+ image_dependencies: &[ImageDependency;3],
+ api_keys: &[ImageKey; 3],
+ resource_cache: &mut ResourceCache,
+ composite_state: &mut CompositeState,
+ gpu_buffer: &mut GpuBufferBuilderF,
+ image_rendering: ImageRendering,
+ color_depth: ColorDepth,
+ color_space: YuvRangedColorSpace,
+ format: YuvFormat,
+ surface_kind: CompositorSurfaceKind,
+ ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
+ for &key in api_keys {
+ if key != ImageKey::DUMMY {
+ // TODO: See comment in setup_compositor_surfaces_rgb.
+ resource_cache.request_image(ImageRequest {
+ key,
+ rendering: image_rendering,
+ tile: None,
+ },
+ gpu_buffer,
+ );
+ }
+ }
+
+ self.setup_compositor_surfaces_impl(
+ sub_slice_index,
+ prim_info,
+ flags,
+ local_prim_rect,
+ prim_spatial_node_index,
+ pic_coverage_rect,
+ frame_context,
+ ExternalSurfaceDependency::Yuv {
+ image_dependencies: *image_dependencies,
+ color_space,
+ format,
+ channel_bit_depth: color_depth.bit_depth(),
+ },
+ api_keys,
+ resource_cache,
+ composite_state,
+ image_rendering,
+ true,
+ surface_kind,
+ )
+ }
+
+ fn setup_compositor_surfaces_rgb(
+ &mut self,
+ sub_slice_index: usize,
+ prim_info: &mut PrimitiveDependencyInfo,
+ flags: PrimitiveFlags,
+ local_prim_rect: LayoutRect,
+ prim_spatial_node_index: SpatialNodeIndex,
+ pic_coverage_rect: PictureRect,
+ frame_context: &FrameVisibilityContext,
+ image_dependency: ImageDependency,
+ api_key: ImageKey,
+ resource_cache: &mut ResourceCache,
+ composite_state: &mut CompositeState,
+ gpu_buffer: &mut GpuBufferBuilderF,
+ image_rendering: ImageRendering,
+ is_opaque: bool,
+ surface_kind: CompositorSurfaceKind,
+ ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
+ let mut api_keys = [ImageKey::DUMMY; 3];
+ api_keys[0] = api_key;
+
+ // TODO: The picture compositing code requires images promoted
+ // into their own picture cache slices to be requested every
+ // frame even if they are not visible. However the image updates
+ // are only reached on the prepare pass for visible primitives.
+ // So we make sure to trigger an image request when promoting
+ // the image here.
+ resource_cache.request_image(ImageRequest {
+ key: api_key,
+ rendering: image_rendering,
+ tile: None,
+ },
+ gpu_buffer,
+ );
+
+ self.setup_compositor_surfaces_impl(
+ sub_slice_index,
+ prim_info,
+ flags,
+ local_prim_rect,
+ prim_spatial_node_index,
+ pic_coverage_rect,
+ frame_context,
+ ExternalSurfaceDependency::Rgb {
+ image_dependency,
+ },
+ &api_keys,
+ resource_cache,
+ composite_state,
+ image_rendering,
+ is_opaque,
+ surface_kind,
+ )
+ }
+
+ // returns false if composition is not available for this surface,
+ // and the non-compositor path should be used to draw it instead.
+ fn setup_compositor_surfaces_impl(
+ &mut self,
+ sub_slice_index: usize,
+ prim_info: &mut PrimitiveDependencyInfo,
+ flags: PrimitiveFlags,
+ local_prim_rect: LayoutRect,
+ prim_spatial_node_index: SpatialNodeIndex,
+ pic_coverage_rect: PictureRect,
+ frame_context: &FrameVisibilityContext,
+ dependency: ExternalSurfaceDependency,
+ api_keys: &[ImageKey; 3],
+ resource_cache: &mut ResourceCache,
+ composite_state: &mut CompositeState,
+ image_rendering: ImageRendering,
+ is_opaque: bool,
+ surface_kind: CompositorSurfaceKind,
+ ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
+ use crate::picture::SurfacePromotionFailure::*;
+
+ let map_local_to_picture = SpaceMapper::new_with_target(
+ self.spatial_node_index,
+ prim_spatial_node_index,
+ self.local_rect,
+ frame_context.spatial_tree,
+ );
+
+ // Map the primitive local rect into picture space.
+ let prim_rect = match map_local_to_picture.map(&local_prim_rect) {
+ Some(rect) => rect,
+ None => return Ok(surface_kind),
+ };
+
+ // If the rect is invalid, no need to create dependencies.
+ if prim_rect.is_empty() {
+ return Ok(surface_kind);
+ }
+
+ let pic_to_world_mapper = SpaceMapper::new_with_target(
+ frame_context.root_spatial_node_index,
+ self.spatial_node_index,
+ frame_context.global_screen_world_rect,
+ frame_context.spatial_tree,
+ );
+
+ let world_clip_rect = pic_to_world_mapper
+ .map(&prim_info.prim_clip_box)
+ .expect("bug: unable to map clip to world space");
+
+ let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
+ if !is_visible {
+ return Ok(surface_kind);
+ }
+
+ let prim_offset = ScaleOffset::from_offset(local_prim_rect.min.to_vector().cast_unit());
+
+ let local_prim_to_device = get_relative_scale_offset(
+ prim_spatial_node_index,
+ frame_context.root_spatial_node_index,
+ frame_context.spatial_tree,
+ );
+
+ let normalized_prim_to_device = prim_offset.then(&local_prim_to_device);
+
+ let local_to_raster = ScaleOffset::identity();
+ let raster_to_device = normalized_prim_to_device;
+
+ // If this primitive is an external image, and supports being used
+ // directly by a native compositor, then lookup the external image id
+ // so we can pass that through.
+ let mut external_image_id = if flags.contains(PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE)
+ && image_rendering == ImageRendering::Auto {
+ resource_cache.get_image_properties(api_keys[0])
+ .and_then(|properties| properties.external_image)
+ .and_then(|image| Some(image.id))
+ } else {
+ None
+ };
+
+
+ if let CompositorKind::Native { capabilities, .. } = composite_state.compositor_kind {
+ if external_image_id.is_some() &&
+ !capabilities.supports_external_compositor_surface_negative_scaling &&
+ (raster_to_device.scale.x < 0.0 || raster_to_device.scale.y < 0.0) {
+ external_image_id = None;
+ }
+ }
+
+ let compositor_transform_index = composite_state.register_transform(
+ local_to_raster,
+ raster_to_device,
+ );
+
+ let surface_size = composite_state.get_surface_rect(
+ &local_prim_rect,
+ &local_prim_rect,
+ compositor_transform_index,
+ ).size();
+
+ let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
+
+ if surface_size.width >= MAX_COMPOSITOR_SURFACES_SIZE ||
+ surface_size.height >= MAX_COMPOSITOR_SURFACES_SIZE {
+ return Err(SizeTooLarge);
+ }
+
+ // When using native compositing, we need to find an existing native surface
+ // handle to use, or allocate a new one. For existing native surfaces, we can
+ // also determine whether this needs to be updated, depending on whether the
+ // image generation(s) of the planes have changed since last composite.
+ let (native_surface_id, update_params) = match composite_state.compositor_kind {
+ CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
+ (None, None)
+ }
+ CompositorKind::Native { .. } => {
+ let native_surface_size = surface_size.to_i32();
+
+ let key = ExternalNativeSurfaceKey {
+ image_keys: *api_keys,
+ size: if external_image_id.is_some() { None } else { Some(native_surface_size) },
+ };
+
+ let native_surface = self.external_native_surface_cache
+ .entry(key)
+ .or_insert_with(|| {
+ // No existing surface, so allocate a new compositor surface.
+ let native_surface_id = match external_image_id {
+ Some(_external_image) => {
+ // If we have a suitable external image, then create an external
+ // surface to attach to.
+ resource_cache.create_compositor_external_surface(is_opaque)
+ }
+ None => {
+ // Otherwise create a normal compositor surface and a single
+ // compositor tile that covers the entire surface.
+ let native_surface_id =
+ resource_cache.create_compositor_surface(
+ DeviceIntPoint::zero(),
+ native_surface_size,
+ is_opaque,
+ );
+
+ let tile_id = NativeTileId {
+ surface_id: native_surface_id,
+ x: 0,
+ y: 0,
+ };
+ resource_cache.create_compositor_tile(tile_id);
+
+ native_surface_id
+ }
+ };
+
+ ExternalNativeSurface {
+ used_this_frame: true,
+ native_surface_id,
+ image_dependencies: [ImageDependency::INVALID; 3],
+ }
+ });
+
+ // Mark that the surface is referenced this frame so that the
+ // backing native surface handle isn't freed.
+ native_surface.used_this_frame = true;
+
+ let update_params = match external_image_id {
+ Some(external_image) => {
+ // If this is an external image surface, then there's no update
+ // to be done. Just attach the current external image to the surface
+ // and we're done.
+ resource_cache.attach_compositor_external_image(
+ native_surface.native_surface_id,
+ external_image,
+ );
+ None
+ }
+ None => {
+ // If the image dependencies match, there is no need to update
+ // the backing native surface.
+ match dependency {
+ ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => {
+ if image_dependencies == native_surface.image_dependencies {
+ None
+ } else {
+ Some(native_surface_size)
+ }
+ },
+ ExternalSurfaceDependency::Rgb{ image_dependency, .. } => {
+ if image_dependency == native_surface.image_dependencies[0] {
+ None
+ } else {
+ Some(native_surface_size)
+ }
+ },
+ }
+ }
+ };
+
+ (Some(native_surface.native_surface_id), update_params)
+ }
+ };
+
+ let descriptor = ExternalSurfaceDescriptor {
+ local_surface_size: local_prim_rect.size(),
+ local_rect: prim_rect,
+ local_clip_rect: prim_info.prim_clip_box,
+ dependency,
+ image_rendering,
+ clip_rect,
+ transform_index: compositor_transform_index,
+ z_id: ZBufferId::invalid(),
+ native_surface_id,
+ update_params,
+ external_image_id,
+ };
+
+ // If the surface is opaque, we can draw it an an underlay (which avoids
+ // additional sub-slice surfaces, and supports clip masks)
+ match surface_kind {
+ CompositorSurfaceKind::Underlay => {
+ self.underlays.push(descriptor);
+ }
+ CompositorSurfaceKind::Overlay => {
+ // For compositor surfaces, if we didn't find an earlier sub-slice to add to,
+ // we know we can append to the current slice.
+ assert!(sub_slice_index < self.sub_slices.len() - 1);
+ let sub_slice = &mut self.sub_slices[sub_slice_index];
+
+ // Each compositor surface allocates a unique z-id
+ sub_slice.compositor_surfaces.push(CompositorSurface {
+ prohibited_rect: pic_coverage_rect,
+ is_opaque,
+ descriptor,
+ });
+
+ // Add the pic_coverage_rect to the overlay region. This prevents
+ // future promoted surfaces from becoming underlays if they would
+ // intersect with the overlay region.
+ self.overlay_region = self.overlay_region.union(&pic_coverage_rect);
+ }
+ CompositorSurfaceKind::Blit => unreachable!(),
+ }
+
+ Ok(surface_kind)
+ }
+
+ /// Push an estimated rect for an off-screen surface during dependency updates. This is
+ /// a workaround / hack that allows the picture cache code to know when it should be
+ /// processing primitive dependencies as a single atomic unit. In future, we aim to remove
+ /// this hack by having the primitive dependencies stored _within_ each owning picture.
+ /// This is part of the work required to support child picture caching anyway!
+ pub fn push_surface(
+ &mut self,
+ estimated_local_rect: LayoutRect,
+ surface_spatial_node_index: SpatialNodeIndex,
+ spatial_tree: &SpatialTree,
+ ) {
+ // Only need to evaluate sub-slice regions if we have compositor surfaces present
+ if self.current_surface_traversal_depth == 0 && self.sub_slices.len() > 1 {
+ let map_local_to_picture = SpaceMapper::new_with_target(
+ self.spatial_node_index,
+ surface_spatial_node_index,
+ self.local_rect,
+ spatial_tree,
+ );
+
+ if let Some(pic_rect) = map_local_to_picture.map(&estimated_local_rect) {
+ // Find the first sub-slice we can add this primitive to (we want to add
+ // prims to the primary surface if possible, so they get subpixel AA).
+ for sub_slice in &mut self.sub_slices {
+ let mut intersects_prohibited_region = false;
+
+ for surface in &mut sub_slice.compositor_surfaces {
+ if pic_rect.intersects(&surface.prohibited_rect) {
+ surface.prohibited_rect = surface.prohibited_rect.union(&pic_rect);
+
+ intersects_prohibited_region = true;
+ }
+ }
+
+ if !intersects_prohibited_region {
+ break;
+ }
+ }
+ }
+ }
+
+ self.current_surface_traversal_depth += 1;
+ }
+
+ /// Pop an off-screen surface off the stack during dependency updates
+ pub fn pop_surface(&mut self) {
+ self.current_surface_traversal_depth -= 1;
+ }
+
+ fn report_promotion_failure(&self,
+ result: Result<CompositorSurfaceKind, SurfacePromotionFailure>,
+ rect: PictureRect,
+ ignored: bool) {
+ if !self.debug_flags.contains(DebugFlags::SURFACE_PROMOTION_LOGGING) || result.is_ok() {
+ return;
+ }
+
+ // Report this as a warning.
+ // TODO: Find a way to expose this to web authors.
+ let outcome = if ignored { "failure ignored" } else { "failed" };
+ warn!("Surface promotion of prim at {:?} {outcome} with: {}.", rect, result.unwrap_err());
+ }
+
+ /// Update the dependencies for each tile for a given primitive instance.
+ pub fn update_prim_dependencies(
+ &mut self,
+ prim_instance: &mut PrimitiveInstance,
+ prim_spatial_node_index: SpatialNodeIndex,
+ local_prim_rect: LayoutRect,
+ frame_context: &FrameVisibilityContext,
+ data_stores: &DataStores,
+ clip_store: &ClipStore,
+ pictures: &[PicturePrimitive],
+ resource_cache: &mut ResourceCache,
+ color_bindings: &ColorBindingStorage,
+ surface_stack: &[(PictureIndex, SurfaceIndex)],
+ composite_state: &mut CompositeState,
+ gpu_buffer: &mut GpuBufferBuilderF,
+ scratch: &mut PrimitiveScratchBuffer,
+ is_root_tile_cache: bool,
+ surfaces: &mut [SurfaceInfo],
+ profile: &mut TransactionProfile,
+ ) -> VisibilityState {
+ use crate::picture::SurfacePromotionFailure::*;
+
+ // This primitive exists on the last element on the current surface stack.
+ profile_scope!("update_prim_dependencies");
+ let prim_surface_index = surface_stack.last().unwrap().1;
+ let prim_clip_chain = &prim_instance.vis.clip_chain;
+
+ // If the primitive is directly drawn onto this picture cache surface, then
+ // the pic_coverage_rect is in the same space. If not, we need to map it from
+ // the intermediate picture space into the picture cache space.
+ let on_picture_surface = prim_surface_index == self.surface_index;
+ let pic_coverage_rect = if on_picture_surface {
+ prim_clip_chain.pic_coverage_rect
+ } else {
+ // We want to get the rect in the tile cache picture space that this primitive
+ // occupies, in order to enable correct invalidation regions. Each surface
+ // that exists in the chain between this primitive and the tile cache surface
+ // may have an arbitrary inflation factor (for example, in the case of a series
+ // of nested blur elements). To account for this, step through the current
+ // surface stack, mapping the primitive rect into each picture space, including
+ // the inflation factor from each intermediate surface.
+ let mut current_pic_coverage_rect = prim_clip_chain.pic_coverage_rect;
+ let mut current_spatial_node_index = surfaces[prim_surface_index.0]
+ .surface_spatial_node_index;
+
+ for (pic_index, surface_index) in surface_stack.iter().rev() {
+ let surface = &surfaces[surface_index.0];
+ let pic = &pictures[pic_index.0];
+
+ let map_local_to_parent = SpaceMapper::new_with_target(
+ surface.surface_spatial_node_index,
+ current_spatial_node_index,
+ surface.unclipped_local_rect,
+ frame_context.spatial_tree,
+ );
+
+ // Map the rect into the parent surface, and inflate if this surface requires
+ // it. If the rect can't be mapping (e.g. due to an invalid transform) then
+ // just bail out from the dependencies and cull this primitive.
+ current_pic_coverage_rect = match map_local_to_parent.map(¤t_pic_coverage_rect) {
+ Some(rect) => {
+ // TODO(gw): The casts here are a hack. We have some interface inconsistencies
+ // between layout/picture rects which don't really work with the
+ // current unit system, since sometimes the local rect of a picture
+ // is a LayoutRect, and sometimes it's a PictureRect. Consider how
+ // we can improve this?
+ pic.composite_mode.as_ref().unwrap().get_coverage(
+ surface,
+ Some(rect.cast_unit()),
+ ).cast_unit()
+ }
+ None => {
+ return VisibilityState::Culled;
+ }
+ };
+
+ current_spatial_node_index = surface.surface_spatial_node_index;
+ }
+
+ current_pic_coverage_rect
+ };
+
+ // Get the tile coordinates in the picture space.
+ let (p0, p1) = self.get_tile_coords_for_rect(&pic_coverage_rect);
+
+ // If the primitive is outside the tiling rects, it's known to not
+ // be visible.
+ if p0.x == p1.x || p0.y == p1.y {
+ return VisibilityState::Culled;
+ }
+
+ // Build the list of resources that this primitive has dependencies on.
+ let mut prim_info = PrimitiveDependencyInfo::new(
+ prim_instance.uid(),
+ pic_coverage_rect,
+ );
+
+ let mut sub_slice_index = self.sub_slices.len() - 1;
+
+ // Only need to evaluate sub-slice regions if we have compositor surfaces present
+ if sub_slice_index > 0 {
+ // Find the first sub-slice we can add this primitive to (we want to add
+ // prims to the primary surface if possible, so they get subpixel AA).
+ for (i, sub_slice) in self.sub_slices.iter_mut().enumerate() {
+ let mut intersects_prohibited_region = false;
+
+ for surface in &mut sub_slice.compositor_surfaces {
+ if pic_coverage_rect.intersects(&surface.prohibited_rect) {
+ surface.prohibited_rect = surface.prohibited_rect.union(&pic_coverage_rect);
+
+ intersects_prohibited_region = true;
+ }
+ }
+
+ if !intersects_prohibited_region {
+ sub_slice_index = i;
+ break;
+ }
+ }
+ }
+
+ // Include the prim spatial node, if differs relative to cache root.
+ if prim_spatial_node_index != self.spatial_node_index {
+ prim_info.spatial_nodes.push(prim_spatial_node_index);
+ }
+
+ // If there was a clip chain, add any clip dependencies to the list for this tile.
+ let clip_instances = &clip_store
+ .clip_node_instances[prim_clip_chain.clips_range.to_range()];
+ for clip_instance in clip_instances {
+ let clip = &data_stores.clip[clip_instance.handle];
+
+ prim_info.clips.push(clip_instance.handle.uid());
+
+ // If the clip has the same spatial node, the relative transform
+ // will always be the same, so there's no need to depend on it.
+ if clip.item.spatial_node_index != self.spatial_node_index
+ && !prim_info.spatial_nodes.contains(&clip.item.spatial_node_index) {
+ prim_info.spatial_nodes.push(clip.item.spatial_node_index);
+ }
+ }
+
+ // Certain primitives may select themselves to be a backdrop candidate, which is
+ // then applied below.
+ let mut backdrop_candidate = None;
+
+ // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
+ // use it to calculate the local bounding rect for the tiles. If we include them
+ // then we may calculate a bounding rect that is too large, since it won't include
+ // the clip bounds of the picture. Excluding them from the bounding rect here
+ // fixes any correctness issues (the clips themselves are considered when we
+ // consider the bounds of the primitives that are *children* of the picture),
+ // however it does potentially result in some un-necessary invalidations of a
+ // tile (in cases where the picture local rect affects the tile, but the clip
+ // rect eventually means it doesn't affect that tile).
+ // TODO(gw): Get picture clips earlier (during the initial picture traversal
+ // pass) so that we can calculate these correctly.
+ match prim_instance.kind {
+ PrimitiveInstanceKind::Picture { pic_index,.. } => {
+ // Pictures can depend on animated opacity bindings.
+ let pic = &pictures[pic_index.0];
+ if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.composite_mode {
+ prim_info.opacity_bindings.push(binding.into());
+ }
+ }
+ PrimitiveInstanceKind::Rectangle { data_handle, color_binding_index, .. } => {
+ // Rectangles can only form a backdrop candidate if they are known opaque.
+ // TODO(gw): We could resolve the opacity binding here, but the common
+ // case for background rects is that they don't have animated opacity.
+ let PrimitiveTemplateKind::Rectangle { color, .. } = data_stores.prim[data_handle].kind;
+ let color = frame_context.scene_properties.resolve_color(&color);
+ if color.a >= 1.0 {
+ backdrop_candidate = Some(BackdropInfo {
+ opaque_rect: pic_coverage_rect,
+ spanning_opaque_color: None,
+ kind: Some(BackdropKind::Color { color }),
+ backdrop_rect: pic_coverage_rect,
+ });
+ }
+
+ if color_binding_index != ColorBindingIndex::INVALID {
+ prim_info.color_binding = Some(color_bindings[color_binding_index].into());
+ }
+ }
+ PrimitiveInstanceKind::Image { data_handle, ref mut compositor_surface_kind, .. } => {
+ let image_key = &data_stores.image[data_handle];
+ let image_data = &image_key.kind;
+
+ // For now, assume that for compositor surface purposes, any RGBA image may be
+ // translucent. See the comment in `add_prim` in this source file for more
+ // details. We'll leave the `is_opaque` code branches here, but disabled, as
+ // in future we will want to support this case correctly.
+ let mut is_opaque = false;
+
+ if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
+ // For an image to be a possible opaque backdrop, it must:
+ // - Have a valid, opaque image descriptor
+ // - Not use tiling (since they can fail to draw)
+ // - Not having any spacing / padding
+ // - Have opaque alpha in the instance (flattened) color
+ if image_properties.descriptor.is_opaque() &&
+ image_properties.tiling.is_none() &&
+ image_data.tile_spacing == LayoutSize::zero() &&
+ image_data.color.a >= 1.0 {
+ backdrop_candidate = Some(BackdropInfo {
+ opaque_rect: pic_coverage_rect,
+ spanning_opaque_color: None,
+ kind: None,
+ backdrop_rect: PictureRect::zero(),
+ });
+ }
+
+ is_opaque = image_properties.descriptor.is_opaque();
+ }
+
+ let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit);
+ if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
+ // Only consider promoting Images if all of our YuvImages have been
+ // processed (whether they were promoted or not).
+ if self.yuv_images_remaining > 0 {
+ promotion_result = Err(ImageWaitingOnYuvImage);
+ } else {
+ promotion_result = self.can_promote_to_surface(prim_clip_chain,
+ prim_spatial_node_index,
+ is_root_tile_cache,
+ sub_slice_index,
+ CompositorSurfaceKind::Overlay,
+ pic_coverage_rect,
+ frame_context,
+ false);
+ }
+
+ // Native OS compositors (DC and CA, at least) support premultiplied alpha
+ // only. If we have an image that's not pre-multiplied alpha, we can't promote it.
+ if image_data.alpha_type == AlphaType::Alpha {
+ promotion_result = Err(NotPremultipliedAlpha);
+ }
+
+ if let Ok(kind) = promotion_result {
+ promotion_result = self.setup_compositor_surfaces_rgb(
+ sub_slice_index,
+ &mut prim_info,
+ image_key.common.flags,
+ local_prim_rect,
+ prim_spatial_node_index,
+ pic_coverage_rect,
+ frame_context,
+ ImageDependency {
+ key: image_data.key,
+ generation: resource_cache.get_image_generation(image_data.key),
+ },
+ image_data.key,
+ resource_cache,
+ composite_state,
+ gpu_buffer,
+ image_data.image_rendering,
+ is_opaque,
+ kind,
+ );
+ }
+ }
+
+ if let Ok(kind) = promotion_result {
+ *compositor_surface_kind = kind;
+
+ if kind == CompositorSurfaceKind::Overlay {
+ profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS);
+ return VisibilityState::Culled;
+ }
+
+ assert!(kind == CompositorSurfaceKind::Blit, "Image prims should either be overlays or blits.");
+ } else {
+ // In Err case, we handle as a blit, and proceed.
+ self.report_promotion_failure(promotion_result, pic_coverage_rect, false);
+ *compositor_surface_kind = CompositorSurfaceKind::Blit;
+ }
+
+ if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
+ profile.inc(profiler::COMPOSITOR_SURFACE_BLITS);
+ }
+
+ prim_info.images.push(ImageDependency {
+ key: image_data.key,
+ generation: resource_cache.get_image_generation(image_data.key),
+ });
+ }
+ PrimitiveInstanceKind::YuvImage { data_handle, ref mut compositor_surface_kind, .. } => {
+ let prim_data = &data_stores.yuv_image[data_handle];
+
+ let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit);
+ if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
+ // Note if this is one of the YuvImages we were considering for
+ // surface promotion. We only care for primitives that were added
+ // to us, indicated by is_root_tile_cache. Those are the only ones
+ // that were added to the TileCacheParams that configured the
+ // current scene.
+ if is_root_tile_cache {
+ self.yuv_images_remaining -= 1;
+ }
+
+ // Should we force the promotion of this surface? We'll force it if promotion
+ // is necessary for correct color display.
+ let force = prim_data.kind.color_depth.bit_depth() > 8;
+
+ let promotion_attempts =
+ [CompositorSurfaceKind::Overlay, CompositorSurfaceKind::Underlay];
+
+ for kind in promotion_attempts {
+ // Since this might be an attempt after an earlier error, clear the flag
+ // so that we are allowed to report another error.
+ promotion_result = self.can_promote_to_surface(
+ prim_clip_chain,
+ prim_spatial_node_index,
+ is_root_tile_cache,
+ sub_slice_index,
+ kind,
+ pic_coverage_rect,
+ frame_context,
+ force);
+ if promotion_result.is_ok() {
+ break;
+ }
+
+ // We couldn't promote, but did we give up because the slice is marked
+ // atomic? If that was the reason, and the YuvImage is wide color,
+ // failing to promote will flatten the colors and look terrible. Let's
+ // ignore the atomic slice restriction in such a case.
+ if let Err(SliceAtomic) = promotion_result {
+ if prim_data.kind. color_depth != ColorDepth::Color8 {
+ // Let's promote with the attempted kind.
+ promotion_result = Ok(kind);
+ break;
+ }
+ }
+ }
+
+ // TODO(gw): When we support RGBA images for external surfaces, we also
+ // need to check if opaque (YUV images are implicitly opaque).
+
+ // If this primitive is being promoted to a surface, construct an external
+ // surface descriptor for use later during batching and compositing. We only
+ // add the image keys for this primitive as a dependency if this is _not_
+ // a promoted surface, since we don't want the tiles to invalidate when the
+ // video content changes, if it's a compositor surface!
+ if let Ok(kind) = promotion_result {
+ // Build dependency for each YUV plane, with current image generation for
+ // later detection of when the composited surface has changed.
+ let mut image_dependencies = [ImageDependency::INVALID; 3];
+ for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
+ *dep = ImageDependency {
+ key,
+ generation: resource_cache.get_image_generation(key),
+ }
+ }
+
+ promotion_result = self.setup_compositor_surfaces_yuv(
+ sub_slice_index,
+ &mut prim_info,
+ prim_data.common.flags,
+ local_prim_rect,
+ prim_spatial_node_index,
+ pic_coverage_rect,
+ frame_context,
+ &image_dependencies,
+ &prim_data.kind.yuv_key,
+ resource_cache,
+ composite_state,
+ gpu_buffer,
+ prim_data.kind.image_rendering,
+ prim_data.kind.color_depth,
+ prim_data.kind.color_space.with_range(prim_data.kind.color_range),
+ prim_data.kind.format,
+ kind,
+ );
+ }
+ }
+
+ // Store on the YUV primitive instance whether this is a promoted surface.
+ // This is used by the batching code to determine whether to draw the
+ // image to the content tiles, or just a transparent z-write.
+ if let Ok(kind) = promotion_result {
+ *compositor_surface_kind = kind;
+ if kind == CompositorSurfaceKind::Overlay {
+ profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS);
+ return VisibilityState::Culled;
+ }
+
+ profile.inc(profiler::COMPOSITOR_SURFACE_UNDERLAYS);
+ } else {
+ // In Err case, we handle as a blit, and proceed.
+ self.report_promotion_failure(promotion_result, pic_coverage_rect, false);
+ *compositor_surface_kind = CompositorSurfaceKind::Blit;
+ if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
+ profile.inc(profiler::COMPOSITOR_SURFACE_BLITS);
+ }
+ }
+
+ if *compositor_surface_kind == CompositorSurfaceKind::Blit {
+ prim_info.images.extend(
+ prim_data.kind.yuv_key.iter().map(|key| {
+ ImageDependency {
+ key: *key,
+ generation: resource_cache.get_image_generation(*key),
+ }
+ })
+ );
+ }
+ }
+ PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
+ let border_data = &data_stores.image_border[data_handle].kind;
+ prim_info.images.push(ImageDependency {
+ key: border_data.request.key,
+ generation: resource_cache.get_image_generation(border_data.request.key),
+ });
+ }
+ PrimitiveInstanceKind::LinearGradient { data_handle, .. }
+ | PrimitiveInstanceKind::CachedLinearGradient { data_handle, .. } => {
+ let gradient_data = &data_stores.linear_grad[data_handle];
+ if gradient_data.stops_opacity.is_opaque
+ && gradient_data.tile_spacing == LayoutSize::zero()
+ {
+ backdrop_candidate = Some(BackdropInfo {
+ opaque_rect: pic_coverage_rect,
+ spanning_opaque_color: None,
+ kind: None,
+ backdrop_rect: PictureRect::zero(),
+ });
+ }
+ }
+ PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
+ let gradient_data = &data_stores.conic_grad[data_handle];
+ if gradient_data.stops_opacity.is_opaque
+ && gradient_data.tile_spacing == LayoutSize::zero()
+ {
+ backdrop_candidate = Some(BackdropInfo {
+ opaque_rect: pic_coverage_rect,
+ spanning_opaque_color: None,
+ kind: None,
+ backdrop_rect: PictureRect::zero(),
+ });
+ }
+ }
+ PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
+ let gradient_data = &data_stores.radial_grad[data_handle];
+ if gradient_data.stops_opacity.is_opaque
+ && gradient_data.tile_spacing == LayoutSize::zero()
+ {
+ backdrop_candidate = Some(BackdropInfo {
+ opaque_rect: pic_coverage_rect,
+ spanning_opaque_color: None,
+ kind: None,
+ backdrop_rect: PictureRect::zero(),
+ });
+ }
+ }
+ PrimitiveInstanceKind::BackdropCapture { .. } => {}
+ PrimitiveInstanceKind::BackdropRender { pic_index, .. } => {
+ // If the area that the backdrop covers in the space of the surface it draws on
+ // is empty, skip any sub-graph processing. This is not just a performance win,
+ // it also ensures that we don't do a deferred dirty test that invalidates a tile
+ // even if the tile isn't actually dirty, which can cause panics later in the
+ // WR pipeline.
+ if !pic_coverage_rect.is_empty() {
+ // Mark that we need the sub-graph this render depends on so that
+ // we don't skip it during the prepare pass
+ scratch.required_sub_graphs.insert(pic_index);
+
+ // If this is a sub-graph, register the bounds on any affected tiles
+ // so we know how much to expand the content tile by.
+ let sub_slice = &mut self.sub_slices[sub_slice_index];
+
+ let mut surface_info = Vec::new();
+ for (pic_index, surface_index) in surface_stack.iter().rev() {
+ let pic = &pictures[pic_index.0];
+ surface_info.push((pic.composite_mode.as_ref().unwrap().clone(), *surface_index));
+ }
+
+ for y in p0.y .. p1.y {
+ for x in p0.x .. p1.x {
+ let key = TileOffset::new(x, y);
+ let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
+ tile.sub_graphs.push((pic_coverage_rect, surface_info.clone()));
+ }
+ }
+
+ // For backdrop-filter, we need to check if any of the dirty rects
+ // in tiles that are affected by the filter primitive are dirty.
+ self.deferred_dirty_tests.push(DeferredDirtyTest {
+ tile_rect: TileRect::new(p0, p1),
+ prim_rect: pic_coverage_rect,
+ });
+ }
+ }
+ PrimitiveInstanceKind::LineDecoration { .. } |
+ PrimitiveInstanceKind::NormalBorder { .. } |
+ PrimitiveInstanceKind::BoxShadow { .. } |
+ PrimitiveInstanceKind::TextRun { .. } => {
+ // These don't contribute dependencies
+ }
+ };
+
+ // Calculate the screen rect in local space. When we calculate backdrops, we
+ // care only that they cover the visible rect (based off the local clip), and
+ // don't have any overlapping prims in the visible rect.
+ let visible_local_clip_rect = self.local_clip_rect.intersection(&self.screen_rect_in_pic_space).unwrap_or_default();
+ if pic_coverage_rect.intersects(&visible_local_clip_rect) {
+ self.found_prims_after_backdrop = true;
+ }
+
+ // If this primitive considers itself a backdrop candidate, apply further
+ // checks to see if it matches all conditions to be a backdrop.
+ let mut vis_flags = PrimitiveVisibilityFlags::empty();
+ let sub_slice = &mut self.sub_slices[sub_slice_index];
+ if let Some(mut backdrop_candidate) = backdrop_candidate {
+ // Update whether the surface that this primitive exists on
+ // can be considered opaque. Any backdrop kind other than
+ // a clear primitive (e.g. color, gradient, image) can be
+ // considered.
+ match backdrop_candidate.kind {
+ Some(BackdropKind::Color { .. }) | None => {
+ let surface = &mut surfaces[prim_surface_index.0];
+
+ let is_same_coord_system = frame_context.spatial_tree.is_matching_coord_system(
+ prim_spatial_node_index,
+ surface.surface_spatial_node_index,
+ );
+
+ // To be an opaque backdrop, it must:
+ // - Be the same coordinate system (axis-aligned)
+ // - Have no clip mask
+ // - Have a rect that covers the surface local rect
+ if is_same_coord_system &&
+ !prim_clip_chain.needs_mask &&
+ prim_clip_chain.pic_coverage_rect.contains_box(&surface.unclipped_local_rect)
+ {
+ // Note that we use `prim_clip_chain.pic_clip_rect` here rather
+ // than `backdrop_candidate.opaque_rect`. The former is in the
+ // local space of the surface, the latter is in the local space
+ // of the top level tile-cache.
+ surface.is_opaque = true;
+ }
+ }
+ }
+
+ // Check a number of conditions to see if we can consider this
+ // primitive as an opaque backdrop rect. Several of these are conservative
+ // checks and could be relaxed in future. However, these checks
+ // are quick and capture the common cases of background rects and images.
+ // Specifically, we currently require:
+ // - The primitive is on the main picture cache surface.
+ // - Same coord system as picture cache (ensures rects are axis-aligned).
+ // - No clip masks exist.
+ let same_coord_system = frame_context.spatial_tree.is_matching_coord_system(
+ prim_spatial_node_index,
+ self.spatial_node_index,
+ );
+
+ let is_suitable_backdrop = same_coord_system && on_picture_surface;
+
+ if sub_slice_index == 0 &&
+ is_suitable_backdrop &&
+ sub_slice.compositor_surfaces.is_empty() {
+
+ // If the backdrop candidate has a clip-mask, try to extract an opaque inner
+ // rect that is safe to use for subpixel rendering
+ if prim_clip_chain.needs_mask {
+ backdrop_candidate.opaque_rect = clip_store
+ .get_inner_rect_for_clip_chain(
+ prim_clip_chain,
+ &data_stores.clip,
+ frame_context.spatial_tree,
+ )
+ .unwrap_or(PictureRect::zero());
+ }
+
+ // We set the backdrop opaque_rect here, indicating the coverage area, which
+ // is useful for calculate_subpixel_mode. We will only set the backdrop kind
+ // if it covers the visible rect.
+ if backdrop_candidate.opaque_rect.contains_box(&self.backdrop.opaque_rect) {
+ self.backdrop.opaque_rect = backdrop_candidate.opaque_rect;
+ }
+
+ if let Some(kind) = backdrop_candidate.kind {
+ if backdrop_candidate.opaque_rect.contains_box(&visible_local_clip_rect) {
+ self.found_prims_after_backdrop = false;
+ self.backdrop.kind = Some(kind);
+ self.backdrop.backdrop_rect = backdrop_candidate.opaque_rect;
+
+ // If we have a color backdrop that spans the entire local rect, mark
+ // the visibility flags of the primitive so it is skipped during batching
+ // (and also clears any previous primitives). Additionally, update our
+ // background color to match the backdrop color, which will ensure that
+ // our tiles are cleared to this color.
+ let BackdropKind::Color { color } = kind;
+ if backdrop_candidate.opaque_rect.contains_box(&self.local_rect) {
+ vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP;
+ self.backdrop.spanning_opaque_color = Some(color);
+ }
+ }
+ }
+ }
+ }
-// Maximum blur radius for blur filter (different than box-shadow blur).
-// Taken from FilterNodeSoftware.cpp in Gecko.
-const MAX_BLUR_RADIUS: f32 = 100.;
+ // Record any new spatial nodes in the used list.
+ for spatial_node_index in &prim_info.spatial_nodes {
+ self.spatial_node_comparer.register_used_transform(
+ *spatial_node_index,
+ self.frame_id,
+ frame_context.spatial_tree,
+ );
+ }
-/// Maximum size of a compositor surface.
-pub const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0;
+ // Normalize the tile coordinates before adding to tile dependencies.
+ // For each affected tile, mark any of the primitive dependencies.
+ for y in p0.y .. p1.y {
+ for x in p0.x .. p1.x {
+ // TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile?
+ let key = TileOffset::new(x, y);
+ let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
-pub fn clamp(value: i32, low: i32, high: i32) -> i32 {
- value.max(low).min(high)
-}
+ tile.add_prim_dependency(&prim_info);
+ }
+ }
-pub fn clampf(value: f32, low: f32, high: f32) -> f32 {
- value.max(low).min(high)
-}
+ VisibilityState::Visible {
+ vis_flags,
+ sub_slice_index: SubSliceIndex::new(sub_slice_index),
+ }
+ }
-/// A descriptor for the kind of texture that a picture cache tile will
-/// be drawn into.
-#[derive(Debug)]
-pub enum SurfaceTextureDescriptor {
- /// When using the WR compositor, the tile is drawn into an entry
- /// in the WR texture cache.
- TextureCache {
- handle: Option<PictureCacheTextureHandle>,
- },
- /// When using an OS compositor, the tile is drawn into a native
- /// surface identified by arbitrary id.
- Native {
- /// The arbitrary id of this tile.
- id: Option<NativeTileId>,
- },
-}
+ /// Print debug information about this picture cache to a tree printer.
+ fn print(&self) {
+ // TODO(gw): This initial implementation is very basic - just printing
+ // the picture cache state to stdout. In future, we can
+ // make this dump each frame to a file, and produce a report
+ // stating which frames had invalidations. This will allow
+ // diff'ing the invalidation states in a visual tool.
+ let mut pt = PrintTree::new("Picture Cache");
-/// This is the same as a `SurfaceTextureDescriptor` but has been resolved
-/// into a texture cache handle (if appropriate) that can be used by the
-/// batching and compositing code in the renderer.
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum ResolvedSurfaceTexture {
- TextureCache {
- /// The texture ID to draw to.
- texture: TextureSource,
- },
- Native {
- /// The arbitrary id of this tile.
- id: NativeTileId,
- /// The size of the tile in device pixels.
- size: DeviceIntSize,
+ pt.new_level(format!("Slice {:?}", self.slice));
+
+ pt.add_item(format!("background_color: {:?}", self.background_color));
+
+ for (sub_slice_index, sub_slice) in self.sub_slices.iter().enumerate() {
+ pt.new_level(format!("SubSlice {:?}", sub_slice_index));
+
+ for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y {
+ for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x {
+ let key = TileOffset::new(x, y);
+ let tile = &sub_slice.tiles[&key];
+ tile.print(&mut pt);
+ }
+ }
+
+ pt.end_level();
+ }
+
+ pt.end_level();
}
-}
-impl SurfaceTextureDescriptor {
- /// Create a resolved surface texture for this descriptor
- pub fn resolve(
- &self,
- resource_cache: &ResourceCache,
- size: DeviceIntSize,
- ) -> ResolvedSurfaceTexture {
- match self {
- SurfaceTextureDescriptor::TextureCache { handle } => {
- let texture = resource_cache
- .picture_textures
- .get_texture_source(handle.as_ref().unwrap());
+ fn calculate_subpixel_mode(&self) -> SubpixelMode {
+ // We can only consider the full opaque cases if there's no underlays
+ if self.underlays.is_empty() {
+ let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
- ResolvedSurfaceTexture::TextureCache { texture }
+ // If the overall tile cache is known opaque, subpixel AA is allowed everywhere
+ if has_opaque_bg_color {
+ return SubpixelMode::Allow;
}
- SurfaceTextureDescriptor::Native { id } => {
- ResolvedSurfaceTexture::Native {
- id: id.expect("bug: native surface not allocated"),
- size,
+
+ // If the opaque backdrop rect covers the entire tile cache surface,
+ // we can allow subpixel AA anywhere, skipping the per-text-run tests
+ // later on during primitive preparation.
+ if self.backdrop.opaque_rect.contains_box(&self.local_rect) {
+ return SubpixelMode::Allow;
+ }
+ }
+
+ // If we didn't find any valid opaque backdrop, no subpixel AA allowed
+ if self.backdrop.opaque_rect.is_empty() {
+ return SubpixelMode::Deny;
+ }
+
+ // Calculate a prohibited rect where we won't allow subpixel AA.
+ // TODO(gw): This is conservative - it will disallow subpixel AA if there
+ // are two underlay surfaces with text placed in between them. That's
+ // probably unlikely to be an issue in practice, but maybe we should support
+ // an array of prohibted rects?
+ let prohibited_rect = self
+ .underlays
+ .iter()
+ .fold(
+ PictureRect::zero(),
+ |acc, underlay| {
+ acc.union(&underlay.local_rect)
+ }
+ );
+
+ // If none of the simple cases above match, we need test where we can support subpixel AA.
+ // TODO(gw): In future, it may make sense to have > 1 inclusion rect,
+ // but this handles the common cases.
+ // TODO(gw): If a text run gets animated such that it's moving in a way that is
+ // sometimes intersecting with the video rect, this can result in subpixel
+ // AA flicking on/off for that text run. It's probably very rare, but
+ // something we should handle in future.
+ SubpixelMode::Conditional {
+ allowed_rect: self.backdrop.opaque_rect,
+ prohibited_rect,
+ }
+ }
+
+ /// Apply any updates after prim dependency updates. This applies
+ /// any late tile invalidations, and sets up the dirty rect and
+ /// set of tile blits.
+ pub fn post_update(
+ &mut self,
+ frame_context: &FrameVisibilityContext,
+ composite_state: &mut CompositeState,
+ resource_cache: &mut ResourceCache,
+ ) {
+ assert!(self.current_surface_traversal_depth == 0);
+
+ // TODO: Switch from the root node ot raster space.
+ let visibility_node = frame_context.spatial_tree.root_reference_frame_index();
+
+ self.dirty_region.reset(visibility_node, self.spatial_node_index);
+ self.subpixel_mode = self.calculate_subpixel_mode();
+
+ self.transform_index = composite_state.register_transform(
+ self.local_to_raster,
+ // TODO(gw): Once we support scaling of picture cache tiles during compositing,
+ // that transform gets plugged in here!
+ self.raster_to_device,
+ );
+
+ let map_pic_to_world = SpaceMapper::new_with_target(
+ frame_context.root_spatial_node_index,
+ self.spatial_node_index,
+ frame_context.global_screen_world_rect,
+ frame_context.spatial_tree,
+ );
+
+ // A simple GC of the native external surface cache, to remove and free any
+ // surfaces that were not referenced during the update_prim_dependencies pass.
+ self.external_native_surface_cache.retain(|_, surface| {
+ if !surface.used_this_frame {
+ // If we removed an external surface, we need to mark the dirty rects as
+ // invalid so a full composite occurs on the next frame.
+ composite_state.dirty_rects_are_valid = false;
+
+ resource_cache.destroy_compositor_surface(surface.native_surface_id);
+ }
+
+ surface.used_this_frame
+ });
+
+ let pic_to_world_mapper = SpaceMapper::new_with_target(
+ frame_context.root_spatial_node_index,
+ self.spatial_node_index,
+ frame_context.global_screen_world_rect,
+ frame_context.spatial_tree,
+ );
+
+ let ctx = TileUpdateDirtyContext {
+ pic_to_world_mapper,
+ global_device_pixel_scale: frame_context.global_device_pixel_scale,
+ opacity_bindings: &self.opacity_bindings,
+ color_bindings: &self.color_bindings,
+ local_rect: self.local_rect,
+ invalidate_all: self.invalidate_all_tiles,
+ };
+
+ let mut state = TileUpdateDirtyState {
+ resource_cache,
+ composite_state,
+ compare_cache: &mut self.compare_cache,
+ spatial_node_comparer: &mut self.spatial_node_comparer,
+ };
+
+ // Step through each tile and invalidate if the dependencies have changed. Determine
+ // the current opacity setting and whether it's changed.
+ for sub_slice in &mut self.sub_slices {
+ for tile in sub_slice.tiles.values_mut() {
+ tile.update_dirty_and_valid_rects(&ctx, &mut state, frame_context);
+ }
+ }
+
+ // Process any deferred dirty checks
+ for sub_slice in &mut self.sub_slices {
+ for dirty_test in self.deferred_dirty_tests.drain(..) {
+ // Calculate the total dirty rect from all tiles that this primitive affects
+ let mut total_dirty_rect = PictureRect::zero();
+
+ for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
+ for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
+ let key = TileOffset::new(x, y);
+ let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
+ total_dirty_rect = total_dirty_rect.union(&tile.local_dirty_rect);
+ }
+ }
+
+ // If that dirty rect intersects with the local rect of the primitive
+ // being checked, invalidate that region in all of the affected tiles.
+ // TODO(gw): This is somewhat conservative, we could be more clever
+ // here and avoid invalidating every tile when this changes.
+ // We could also store the dirty rect only when the prim
+ // is encountered, so that we don't invalidate if something
+ // *after* the query in the rendering order affects invalidation.
+ if total_dirty_rect.intersects(&dirty_test.prim_rect) {
+ for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
+ for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
+ let key = TileOffset::new(x, y);
+ let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
+ tile.invalidate(
+ Some(dirty_test.prim_rect),
+ InvalidationReason::SurfaceContentChanged,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ let mut ctx = TilePostUpdateContext {
+ local_clip_rect: self.local_clip_rect,
+ backdrop: None,
+ current_tile_size: self.current_tile_size,
+ z_id: ZBufferId::invalid(),
+ underlays: &self.underlays,
+ };
+
+ let mut state = TilePostUpdateState {
+ resource_cache,
+ composite_state,
+ };
+
+ for (i, sub_slice) in self.sub_slices.iter_mut().enumerate().rev() {
+ // The backdrop is only relevant for the first sub-slice
+ if i == 0 {
+ ctx.backdrop = Some(self.backdrop);
+ }
+
+ for compositor_surface in sub_slice.compositor_surfaces.iter_mut().rev() {
+ compositor_surface.descriptor.z_id = state.composite_state.z_generator.next();
+ }
+
+ ctx.z_id = state.composite_state.z_generator.next();
+
+ for tile in sub_slice.tiles.values_mut() {
+ tile.post_update(&ctx, &mut state, frame_context);
+ }
+ }
+
+ // Assign z-order for each underlay
+ for underlay in self.underlays.iter_mut().rev() {
+ underlay.z_id = state.composite_state.z_generator.next();
+ }
+
+ // Register any opaque external compositor surfaces as potential occluders. This
+ // is especially useful when viewing video in full-screen mode, as it is
+ // able to occlude every background tile (avoiding allocation, rasterizion
+ // and compositing).
+
+ // Register any underlays as occluders where possible
+ for underlay in &self.underlays {
+ if let Some(world_surface_rect) = underlay.get_occluder_rect(
+ &self.local_clip_rect,
+ &map_pic_to_world,
+ ) {
+ composite_state.register_occluder(
+ underlay.z_id,
+ world_surface_rect,
+ self.compositor_clip,
+ );
+ }
+ }
+
+ for sub_slice in &self.sub_slices {
+ for compositor_surface in &sub_slice.compositor_surfaces {
+ if compositor_surface.is_opaque {
+ if let Some(world_surface_rect) = compositor_surface.descriptor.get_occluder_rect(
+ &self.local_clip_rect,
+ &map_pic_to_world,
+ ) {
+ composite_state.register_occluder(
+ compositor_surface.descriptor.z_id,
+ world_surface_rect,
+ self.compositor_clip,
+ );
+ }
}
}
}
+
+ // Register the opaque region of this tile cache as an occluder, which
+ // is used later in the frame to occlude other tiles.
+ if !self.backdrop.opaque_rect.is_empty() {
+ let z_id_backdrop = composite_state.z_generator.next();
+
+ let backdrop_rect = self.backdrop.opaque_rect
+ .intersection(&self.local_rect)
+ .and_then(|r| {
+ r.intersection(&self.local_clip_rect)
+ });
+
+ if let Some(backdrop_rect) = backdrop_rect {
+ let world_backdrop_rect = map_pic_to_world
+ .map(&backdrop_rect)
+ .expect("bug: unable to map backdrop to world space");
+
+ // Since we register the entire backdrop rect, use the opaque z-id for the
+ // picture cache slice.
+ composite_state.register_occluder(
+ z_id_backdrop,
+ world_backdrop_rect,
+ self.compositor_clip,
+ );
+ }
+ }
}
}
@@ -243,6 +4068,259 @@ impl PictureScratchBuffer {
}
}
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SurfaceIndex(pub usize);
+
+/// Information about an offscreen surface. For now,
+/// it contains information about the size and coordinate
+/// system of the surface. In the future, it will contain
+/// information about the contents of the surface, which
+/// will allow surfaces to be cached / retained between
+/// frames and display lists.
+pub struct SurfaceInfo {
+ /// A local rect defining the size of this surface, in the
+ /// coordinate system of the parent surface. This contains
+ /// the unclipped bounding rect of child primitives.
+ pub unclipped_local_rect: PictureRect,
+ /// The local space coverage of child primitives after they are
+ /// are clipped to their owning clip-chain.
+ pub clipped_local_rect: PictureRect,
+ /// The (conservative) valid part of this surface rect. Used
+ /// to reduce the size of render target allocation.
+ pub clipping_rect: PictureRect,
+ /// The rectangle to use for culling and clipping.
+ pub culling_rect: VisRect,
+ /// Helper structs for mapping local rects in different
+ /// coordinate systems into the picture coordinates.
+ pub map_local_to_picture: SpaceMapper<LayoutPixel, PicturePixel>,
+ /// The positioning node for the surface itself,
+ pub surface_spatial_node_index: SpatialNodeIndex,
+ /// The rasterization root for this surface.
+ pub raster_spatial_node_index: SpatialNodeIndex,
+ /// The spatial node for culling and clipping (anything using VisPixel).
+ /// TODO: Replace with the raster spatial node.
+ pub visibility_spatial_node_index: SpatialNodeIndex,
+ /// The device pixel ratio specific to this surface.
+ pub device_pixel_scale: DevicePixelScale,
+ /// The scale factors of the surface to world transform.
+ pub world_scale_factors: (f32, f32),
+ /// Local scale factors surface to raster transform
+ pub local_scale: (f32, f32),
+ /// If true, we know this surface is completely opaque.
+ pub is_opaque: bool,
+ /// If true, allow snapping on this and child surfaces
+ pub allow_snapping: bool,
+ /// If true, the scissor rect must be set when drawing this surface
+ pub force_scissor_rect: bool,
+}
+
+impl SurfaceInfo {
+ pub fn new(
+ surface_spatial_node_index: SpatialNodeIndex,
+ raster_spatial_node_index: SpatialNodeIndex,
+ world_rect: WorldRect,
+ spatial_tree: &SpatialTree,
+ device_pixel_scale: DevicePixelScale,
+ world_scale_factors: (f32, f32),
+ local_scale: (f32, f32),
+ allow_snapping: bool,
+ force_scissor_rect: bool,
+ ) -> Self {
+ let map_surface_to_world = SpaceMapper::new_with_target(
+ spatial_tree.root_reference_frame_index(),
+ surface_spatial_node_index,
+ world_rect,
+ spatial_tree,
+ );
+
+ let pic_bounds = map_surface_to_world
+ .unmap(&map_surface_to_world.bounds)
+ .unwrap_or_else(PictureRect::max_rect);
+
+ let map_local_to_picture = SpaceMapper::new(
+ surface_spatial_node_index,
+ pic_bounds,
+ );
+
+ // TODO: replace the root with raster space.
+ let visibility_spatial_node_index = spatial_tree.root_reference_frame_index();
+
+ SurfaceInfo {
+ unclipped_local_rect: PictureRect::zero(),
+ clipped_local_rect: PictureRect::zero(),
+ is_opaque: false,
+ clipping_rect: PictureRect::zero(),
+ map_local_to_picture,
+ raster_spatial_node_index,
+ surface_spatial_node_index,
+ visibility_spatial_node_index,
+ device_pixel_scale,
+ world_scale_factors,
+ local_scale,
+ allow_snapping,
+ force_scissor_rect,
+ // TODO: At the moment all culling is done in world space but
+ // but the plan is to move it to raster space.
+ culling_rect: world_rect.cast_unit(),
+ }
+ }
+
+ /// Clamps the blur radius depending on scale factors.
+ pub fn clamp_blur_radius(
+ &self,
+ x_blur_radius: f32,
+ y_blur_radius: f32,
+ ) -> (f32, f32) {
+ // Clamping must occur after scale factors are applied, but scale factors are not applied
+ // until later on. To clamp the blur radius, we first apply the scale factors and then clamp
+ // and finally revert the scale factors.
+
+ let sx_blur_radius = x_blur_radius * self.local_scale.0;
+ let sy_blur_radius = y_blur_radius * self.local_scale.1;
+
+ let largest_scaled_blur_radius = f32::max(
+ sx_blur_radius * self.world_scale_factors.0,
+ sy_blur_radius * self.world_scale_factors.1,
+ );
+
+ if largest_scaled_blur_radius > MAX_BLUR_RADIUS {
+ let sf = MAX_BLUR_RADIUS / largest_scaled_blur_radius;
+ (x_blur_radius * sf, y_blur_radius * sf)
+ } else {
+ // Return the original blur radius to avoid any rounding errors
+ (x_blur_radius, y_blur_radius)
+ }
+ }
+
+ pub fn update_culling_rect(
+ &mut self,
+ parent_culling_rect: VisRect,
+ composite_mode: &PictureCompositeMode,
+ frame_context: &FrameVisibilityContext,
+ ) {
+ // Set the default culling rect to be the parent, in case we fail
+ // any mappings below due to weird perspective or invalid transforms.
+ self.culling_rect = parent_culling_rect;
+
+ if let PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate, .. }) = composite_mode {
+ if *should_inflate {
+ // Space mapping vis <-> picture space
+ let map_surface_to_vis = SpaceMapper::new_with_target(
+ // TODO: switch from root to raster space.
+ frame_context.root_spatial_node_index,
+ self.surface_spatial_node_index,
+ parent_culling_rect,
+ frame_context.spatial_tree,
+ );
+
+ // Unmap the parent culling rect to surface space. Note that this may be
+ // quite conservative in the case of a complex transform, especially perspective.
+ if let Some(local_parent_culling_rect) = map_surface_to_vis.unmap(&parent_culling_rect) {
+ let (width_factor, height_factor) = self.clamp_blur_radius(*width, *height);
+
+ // Inflate by the local-space amount this surface extends.
+ let expanded_rect: PictureBox2D = local_parent_culling_rect.inflate(
+ width_factor.ceil() * BLUR_SAMPLE_SCALE,
+ height_factor.ceil() * BLUR_SAMPLE_SCALE,
+ );
+
+ // Map back to the expected vis-space culling rect
+ if let Some(rect) = map_surface_to_vis.map(&expanded_rect) {
+ self.culling_rect = rect;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn map_to_device_rect(
+ &self,
+ picture_rect: &PictureRect,
+ spatial_tree: &SpatialTree,
+ ) -> DeviceRect {
+ let raster_rect = if self.raster_spatial_node_index != self.surface_spatial_node_index {
+ // Currently, the surface's spatial node can be different from its raster node only
+ // for surfaces in the root coordinate system for snapping reasons.
+ // See `PicturePrimitive::assign_surface`.
+ assert_eq!(self.device_pixel_scale.0, 1.0);
+ assert_eq!(self.raster_spatial_node_index, spatial_tree.root_reference_frame_index());
+
+ let pic_to_raster = SpaceMapper::new_with_target(
+ self.raster_spatial_node_index,
+ self.surface_spatial_node_index,
+ WorldRect::max_rect(),
+ spatial_tree,
+ );
+
+ pic_to_raster.map(&picture_rect).unwrap()
+ } else {
+ picture_rect.cast_unit()
+ };
+
+ raster_rect * self.device_pixel_scale
+ }
+
+ /// Clip and transform a local rect to a device rect suitable for allocating
+ /// a child off-screen surface of this surface (e.g. for clip-masks)
+ pub fn get_surface_rect(
+ &self,
+ local_rect: &PictureRect,
+ spatial_tree: &SpatialTree,
+ ) -> Option<DeviceIntRect> {
+ let local_rect = match local_rect.intersection(&self.clipping_rect) {
+ Some(rect) => rect,
+ None => return None,
+ };
+
+ let raster_rect = if self.raster_spatial_node_index != self.surface_spatial_node_index {
+ assert_eq!(self.device_pixel_scale.0, 1.0);
+
+ let local_to_world = SpaceMapper::new_with_target(
+ spatial_tree.root_reference_frame_index(),
+ self.surface_spatial_node_index,
+ WorldRect::max_rect(),
+ spatial_tree,
+ );
+
+ local_to_world.map(&local_rect).unwrap()
+ } else {
+ // The content should have been culled out earlier.
+ assert!(self.device_pixel_scale.0 > 0.0);
+
+ local_rect.cast_unit()
+ };
+
+ let surface_rect = (raster_rect * self.device_pixel_scale).round_out().to_i32();
+ if surface_rect.is_empty() {
+ // The local_rect computed above may have non-empty size that is very
+ // close to zero. Due to limited arithmetic precision, the SpaceMapper
+ // might transform the near-zero-sized rect into a zero-sized one.
+ return None;
+ }
+
+ Some(surface_rect)
+ }
+}
+
+/// Information from `get_surface_rects` about the allocated size, UV sampling
+/// parameters etc for an off-screen surface
+#[derive(Debug)]
+struct SurfaceAllocInfo {
+ task_size: DeviceIntSize,
+ needs_scissor_rect: bool,
+ clipped: DeviceRect,
+ unclipped: DeviceRect,
+ // Only used for SVGFEGraph currently, this is the source pixels needed to
+ // render the pixels in clipped.
+ source: DeviceRect,
+ // Only used for SVGFEGraph, this is the same as clipped before rounding.
+ clipped_notsnapped: DeviceRect,
+ clipped_local: PictureRect,
+ uv_rect_kind: UvRectKind,
+}
+
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct RasterConfig {
@@ -341,7 +4419,7 @@ impl PictureCompositeMode {
// Return prim_subregion for use in get_local_prim_rect, which
// is the polygon size.
// This must match surface_rects.unclipped_local.
- get_coverage_target_svgfe(filters, surface_rect.cast_unit())
+ self.get_coverage_target_svgfe(filters, surface_rect.cast_unit())
}
_ => {
surface_rect
@@ -394,8 +4472,8 @@ impl PictureCompositeMode {
PictureCompositeMode::SVGFEGraph(ref filters) => {
// surface_rect may be for source or target, so invalidate based
// on both interpretations
- let target_subregion = get_coverage_source_svgfe(filters, surface_rect.cast());
- let source_subregion = get_coverage_target_svgfe(filters, surface_rect.cast());
+ let target_subregion = self.get_coverage_source_svgfe(filters, surface_rect.cast());
+ let source_subregion = self.get_coverage_target_svgfe(filters, surface_rect.cast());
target_subregion.union(&source_subregion)
}
_ => {
@@ -433,6 +4511,288 @@ impl PictureCompositeMode {
PictureCompositeMode::Filter(Filter::SVGGraphNode(..)) => "Filter::SVGGraphNode",
}
}
+
+ /// Here we transform source rect to target rect for SVGFEGraph by walking
+ /// the whole graph and propagating subregions based on the provided
+ /// invalidation rect, and we want it to be a tight fit so we don't waste
+ /// time applying multiple filters to pixels that do not contribute to the
+ /// invalidated rect.
+ ///
+ /// The interesting parts of the handling of SVG filters are:
+ /// * scene_building.rs : wrap_prim_with_filters
+ /// * picture.rs : get_coverage_target_svgfe (you are here)
+ /// * picture.rs : get_coverage_source_svgfe
+ /// * render_task.rs : new_svg_filter_graph
+ /// * render_target.rs : add_svg_filter_node_instances
+ pub fn get_coverage_target_svgfe(
+ &self,
+ filters: &[(FilterGraphNode, FilterGraphOp)],
+ surface_rect: LayoutRect,
+ ) -> LayoutRect {
+
+ // The value of BUFFER_LIMIT here must be the same as in
+ // scene_building.rs, or we'll hit asserts here.
+ const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
+
+ // We need to evaluate the subregions based on the proposed
+ // SourceGraphic rect as it isn't known at scene build time.
+ let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT];
+ for (id, (node, op)) in filters.iter().enumerate() {
+ let full_subregion = node.subregion;
+ let mut used_subregion = LayoutRect::zero();
+ for input in &node.inputs {
+ match input.buffer_id {
+ FilterOpGraphPictureBufferId::BufferId(id) => {
+ assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
+ // This id lookup should always succeed.
+ let input_subregion = subregion_by_buffer_id[id as usize];
+ // Now add the padding that transforms from
+ // source to target, this was determined during
+ // scene build based on the operation.
+ let input_subregion =
+ LayoutRect::new(
+ LayoutPoint::new(
+ input_subregion.min.x + input.target_padding.min.x,
+ input_subregion.min.y + input.target_padding.min.y,
+ ),
+ LayoutPoint::new(
+ input_subregion.max.x + input.target_padding.max.x,
+ input_subregion.max.y + input.target_padding.max.y,
+ ),
+ );
+ used_subregion = used_subregion
+ .union(&input_subregion);
+ }
+ FilterOpGraphPictureBufferId::None => {
+ panic!("Unsupported BufferId type");
+ }
+ }
+ }
+ // We can clip the used subregion to the node subregion
+ used_subregion = used_subregion
+ .intersection(&full_subregion)
+ .unwrap_or(LayoutRect::zero());
+ match op {
+ FilterGraphOp::SVGFEBlendColor => {}
+ FilterGraphOp::SVGFEBlendColorBurn => {}
+ FilterGraphOp::SVGFEBlendColorDodge => {}
+ FilterGraphOp::SVGFEBlendDarken => {}
+ FilterGraphOp::SVGFEBlendDifference => {}
+ FilterGraphOp::SVGFEBlendExclusion => {}
+ FilterGraphOp::SVGFEBlendHardLight => {}
+ FilterGraphOp::SVGFEBlendHue => {}
+ FilterGraphOp::SVGFEBlendLighten => {}
+ FilterGraphOp::SVGFEBlendLuminosity => {}
+ FilterGraphOp::SVGFEBlendMultiply => {}
+ FilterGraphOp::SVGFEBlendNormal => {}
+ FilterGraphOp::SVGFEBlendOverlay => {}
+ FilterGraphOp::SVGFEBlendSaturation => {}
+ FilterGraphOp::SVGFEBlendScreen => {}
+ FilterGraphOp::SVGFEBlendSoftLight => {}
+ FilterGraphOp::SVGFEColorMatrix { values } => {
+ if values[19] > 0.0 {
+ // Manipulating alpha offset can easily create new
+ // pixels outside of input subregions
+ used_subregion = full_subregion;
+ add_text_marker(
+ "SVGFEColorMatrix",
+ "SVGFEColorMatrix with non-zero alpha offset, using full subregion",
+ Duration::from_millis(1));
+ }
+ }
+ FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
+ FilterGraphOp::SVGFEComponentTransferInterned{handle: _, creates_pixels} => {
+ // Check if the value of alpha[0] is modified, if so
+ // the whole subregion is used because it will be
+ // creating new pixels outside of input subregions
+ if *creates_pixels {
+ used_subregion = full_subregion;
+ add_text_marker(
+ "SVGFEComponentTransfer",
+ "SVGFEComponentTransfer with non-zero minimum alpha, using full subregion",
+ Duration::from_millis(1));
+ }
+ }
+ FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => {
+ // Optimization opportunity - some inputs may be
+ // smaller subregions due to the way the math works,
+ // k1 is the intersection of the two inputs, k2 is
+ // the first input only, k3 is the second input
+ // only, and k4 changes the whole subregion.
+ //
+ // See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC
+ // in FilterSupport.cpp
+ //
+ // We can at least ignore the entire node if
+ // everything is zero.
+ if *k1 <= 0.0 &&
+ *k2 <= 0.0 &&
+ *k3 <= 0.0 {
+ used_subregion = LayoutRect::zero();
+ }
+ // Check if alpha is added to pixels as it means it
+ // can fill pixels outside input subregions
+ if *k4 > 0.0 {
+ used_subregion = full_subregion;
+ add_text_marker(
+ "SVGFECompositeArithmetic",
+ "SVGFECompositeArithmetic with non-zero offset, using full subregion",
+ Duration::from_millis(1));
+ }
+ }
+ FilterGraphOp::SVGFECompositeATop => {}
+ FilterGraphOp::SVGFECompositeIn => {}
+ FilterGraphOp::SVGFECompositeLighter => {}
+ FilterGraphOp::SVGFECompositeOut => {}
+ FilterGraphOp::SVGFECompositeOver => {}
+ FilterGraphOp::SVGFECompositeXOR => {}
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {}
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {}
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {}
+ FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {}
+ FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {}
+ FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {}
+ FilterGraphOp::SVGFEDisplacementMap{..} => {}
+ FilterGraphOp::SVGFEDropShadow{..} => {}
+ FilterGraphOp::SVGFEFlood { color } => {
+ // Subregion needs to be set to the full node
+ // subregion for fills (unless the fill is a no-op)
+ if color.a > 0.0 {
+ used_subregion = full_subregion;
+ }
+ }
+ FilterGraphOp::SVGFEGaussianBlur{..} => {}
+ FilterGraphOp::SVGFEIdentity => {}
+ FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => {
+ // TODO: calculate the actual subregion
+ used_subregion = full_subregion;
+ }
+ FilterGraphOp::SVGFEMorphologyDilate{..} => {}
+ FilterGraphOp::SVGFEMorphologyErode{..} => {}
+ FilterGraphOp::SVGFEOpacity { valuebinding: _valuebinding, value } => {
+ // If fully transparent, we can ignore this node
+ if *value <= 0.0 {
+ used_subregion = LayoutRect::zero();
+ }
+ }
+ FilterGraphOp::SVGFESourceAlpha |
+ FilterGraphOp::SVGFESourceGraphic => {
+ used_subregion = surface_rect;
+ }
+ FilterGraphOp::SVGFESpecularLightingDistant{..} => {}
+ FilterGraphOp::SVGFESpecularLightingPoint{..} => {}
+ FilterGraphOp::SVGFESpecularLightingSpot{..} => {}
+ FilterGraphOp::SVGFETile => {
+ // feTile fills the entire output with
+ // source pixels, so it's effectively a flood.
+ used_subregion = full_subregion;
+ }
+ FilterGraphOp::SVGFEToAlpha => {}
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
+ // Turbulence produces pixel values throughout the
+ // node subregion.
+ used_subregion = full_subregion;
+ }
+ }
+ // Store the subregion so later nodes can refer back
+ // to this and propagate rects properly
+ assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
+ subregion_by_buffer_id[id] = used_subregion;
+ }
+ subregion_by_buffer_id[filters.len() - 1]
+ }
+
+ /// Here we transform target rect to source rect for SVGFEGraph by walking
+ /// the whole graph and propagating subregions based on the provided
+ /// invalidation rect, and we want it to be a tight fit so we don't waste
+ /// time applying multiple filters to pixels that do not contribute to the
+ /// invalidated rect.
+ ///
+ /// The interesting parts of the handling of SVG filters are:
+ /// * scene_building.rs : wrap_prim_with_filters
+ /// * picture.rs : get_coverage_target_svgfe
+ /// * picture.rs : get_coverage_source_svgfe (you are here)
+ /// * render_task.rs : new_svg_filter_graph
+ /// * render_target.rs : add_svg_filter_node_instances
+ pub fn get_coverage_source_svgfe(
+ &self,
+ filters: &[(FilterGraphNode, FilterGraphOp)],
+ surface_rect: LayoutRect,
+ ) -> LayoutRect {
+
+ // The value of BUFFER_LIMIT here must be the same as in
+ // scene_building.rs, or we'll hit asserts here.
+ const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
+
+ // We're solving the source rect from target rect (e.g. due
+ // to invalidation of a region, we need to know how much of
+ // SourceGraphic is needed to draw that region accurately),
+ // so we need to walk the DAG in reverse and accumulate the source
+ // subregion for each input onto the referenced node, which can then
+ // propagate that to its inputs when it is iterated.
+ let mut source_subregion = LayoutRect::zero();
+ let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] =
+ [LayoutRect::zero(); BUFFER_LIMIT];
+ let final_buffer_id = filters.len() - 1;
+ assert!(final_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
+ subregion_by_buffer_id[final_buffer_id] = surface_rect;
+ for (node_buffer_id, (node, op)) in filters.iter().enumerate().rev() {
+ // This is the subregion this node outputs, we can clip
+ // the inputs based on source_padding relative to this,
+ // and accumulate a new subregion for them.
+ assert!(node_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
+ let full_subregion = node.subregion;
+ let mut used_subregion =
+ subregion_by_buffer_id[node_buffer_id];
+ // We can clip the propagated subregion to the node subregion before
+ // we add source_padding for each input and propogate to them
+ used_subregion = used_subregion
+ .intersection(&full_subregion)
+ .unwrap_or(LayoutRect::zero());
+ if !used_subregion.is_empty() {
+ for input in &node.inputs {
+ let input_subregion = LayoutRect::new(
+ LayoutPoint::new(
+ used_subregion.min.x + input.source_padding.min.x,
+ used_subregion.min.y + input.source_padding.min.y,
+ ),
+ LayoutPoint::new(
+ used_subregion.max.x + input.source_padding.max.x,
+ used_subregion.max.y + input.source_padding.max.y,
+ ),
+ );
+ match input.buffer_id {
+ FilterOpGraphPictureBufferId::BufferId(id) => {
+ // Add the used area to the input, later when
+ // the referneced node is iterated as a node it
+ // will propagate the used bounds.
+ subregion_by_buffer_id[id as usize] =
+ subregion_by_buffer_id[id as usize]
+ .union(&input_subregion);
+ }
+ FilterOpGraphPictureBufferId::None => {}
+ }
+ }
+ }
+ // If this is the SourceGraphic or SourceAlpha, we now have the
+ // source subregion we're looking for. If both exist in the
+ // same graph, we need to combine them, so don't merely replace.
+ match op {
+ FilterGraphOp::SVGFESourceAlpha |
+ FilterGraphOp::SVGFESourceGraphic => {
+ source_subregion = source_subregion.union(&used_subregion);
+ }
+ _ => {}
+ }
+ }
+
+ // Note that this can be zero if SourceGraphic/SourceAlpha is not used
+ // in this graph.
+ source_subregion
+ }
}
/// Enum value describing the place of a picture in a 3D context.
@@ -2939,6 +7299,595 @@ impl PicturePrimitive {
}
}
+fn get_transform_key(
+ spatial_node_index: SpatialNodeIndex,
+ cache_spatial_node_index: SpatialNodeIndex,
+ spatial_tree: &SpatialTree,
+) -> TransformKey {
+ spatial_tree.get_relative_transform(
+ spatial_node_index,
+ cache_spatial_node_index,
+ ).into()
+}
+
+/// A key for storing primitive comparison results during tile dependency tests.
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+struct PrimitiveComparisonKey {
+ prev_index: PrimitiveDependencyIndex,
+ curr_index: PrimitiveDependencyIndex,
+}
+
+/// Information stored an image dependency
+#[derive(Debug, Copy, Clone, PartialEq, PeekPoke, Default)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ImageDependency {
+ pub key: ImageKey,
+ pub generation: ImageGeneration,
+}
+
+impl ImageDependency {
+ pub const INVALID: ImageDependency = ImageDependency {
+ key: ImageKey::DUMMY,
+ generation: ImageGeneration::INVALID,
+ };
+}
+
+/// In some cases, we need to know the dirty rect of all tiles in order
+/// to correctly invalidate a primitive.
+#[derive(Debug)]
+struct DeferredDirtyTest {
+ /// The tile rect that the primitive being checked affects
+ tile_rect: TileRect,
+ /// The picture-cache local rect of the primitive being checked
+ prim_rect: PictureRect,
+}
+
+/// A helper struct to compare a primitive and all its sub-dependencies.
+struct PrimitiveComparer<'a> {
+ prev_data: &'a [u8],
+ curr_data: &'a [u8],
+ prev_frame_id: FrameId,
+ curr_frame_id: FrameId,
+ resource_cache: &'a ResourceCache,
+ spatial_node_comparer: &'a mut SpatialNodeComparer,
+ opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
+ color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
+}
+
+impl<'a> PrimitiveComparer<'a> {
+ fn new(
+ prev: &'a TileDescriptor,
+ curr: &'a TileDescriptor,
+ resource_cache: &'a ResourceCache,
+ spatial_node_comparer: &'a mut SpatialNodeComparer,
+ opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
+ color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
+ ) -> Self {
+ PrimitiveComparer {
+ prev_data: &prev.dep_data,
+ curr_data: &curr.dep_data,
+ prev_frame_id: prev.last_updated_frame_id,
+ curr_frame_id: curr.last_updated_frame_id,
+ resource_cache,
+ spatial_node_comparer,
+ opacity_bindings,
+ color_bindings,
+ }
+ }
+
+ /// Check if two primitive descriptors are the same.
+ fn compare_prim(
+ &mut self,
+ prev_desc: &PrimitiveDescriptor,
+ curr_desc: &PrimitiveDescriptor,
+ ) -> PrimitiveCompareResult {
+ let resource_cache = self.resource_cache;
+ let spatial_node_comparer = &mut self.spatial_node_comparer;
+ let opacity_bindings = self.opacity_bindings;
+ let color_bindings = self.color_bindings;
+
+ // Check equality of the PrimitiveDescriptor
+ if prev_desc != curr_desc {
+ return PrimitiveCompareResult::Descriptor;
+ }
+
+ let mut prev_dep_data = &self.prev_data[prev_desc.dep_offset as usize ..];
+ let mut curr_dep_data = &self.curr_data[curr_desc.dep_offset as usize ..];
+
+ let mut prev_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID };
+ let mut curr_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID };
+
+ debug_assert_eq!(prev_desc.dep_count, curr_desc.dep_count);
+
+ for _ in 0 .. prev_desc.dep_count {
+ prev_dep_data = peek_from_slice(prev_dep_data, &mut prev_dep);
+ curr_dep_data = peek_from_slice(curr_dep_data, &mut curr_dep);
+
+ match (&prev_dep, &curr_dep) {
+ (PrimitiveDependency::Clip { clip: prev }, PrimitiveDependency::Clip { clip: curr }) => {
+ if prev != curr {
+ return PrimitiveCompareResult::Clip;
+ }
+ }
+ (PrimitiveDependency::SpatialNode { index: prev }, PrimitiveDependency::SpatialNode { index: curr }) => {
+ let prev_key = SpatialNodeKey {
+ spatial_node_index: *prev,
+ frame_id: self.prev_frame_id,
+ };
+ let curr_key = SpatialNodeKey {
+ spatial_node_index: *curr,
+ frame_id: self.curr_frame_id,
+ };
+ if !spatial_node_comparer.are_transforms_equivalent(&prev_key, &curr_key) {
+ return PrimitiveCompareResult::Transform;
+ }
+ }
+ (PrimitiveDependency::OpacityBinding { binding: prev }, PrimitiveDependency::OpacityBinding { binding: curr }) => {
+ if prev != curr {
+ return PrimitiveCompareResult::OpacityBinding;
+ }
+
+ if let OpacityBinding::Binding(id) = curr {
+ if opacity_bindings
+ .get(id)
+ .map_or(true, |info| info.changed) {
+ return PrimitiveCompareResult::OpacityBinding;
+ }
+ }
+ }
+ (PrimitiveDependency::ColorBinding { binding: prev }, PrimitiveDependency::ColorBinding { binding: curr }) => {
+ if prev != curr {
+ return PrimitiveCompareResult::ColorBinding;
+ }
+
+ if let ColorBinding::Binding(id) = curr {
+ if color_bindings
+ .get(id)
+ .map_or(true, |info| info.changed) {
+ return PrimitiveCompareResult::ColorBinding;
+ }
+ }
+ }
+ (PrimitiveDependency::Image { image: prev }, PrimitiveDependency::Image { image: curr }) => {
+ if prev != curr {
+ return PrimitiveCompareResult::Image;
+ }
+
+ if resource_cache.get_image_generation(curr.key) != curr.generation {
+ return PrimitiveCompareResult::Image;
+ }
+ }
+ _ => {
+ // There was a mismatch between types of dependencies, so something changed
+ return PrimitiveCompareResult::Descriptor;
+ }
+ }
+ }
+
+ PrimitiveCompareResult::Equal
+ }
+}
+
+/// Details for a node in a quadtree that tracks dirty rects for a tile.
+#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum TileNodeKind {
+ Leaf {
+ /// The index buffer of primitives that affected this tile previous frame
+ #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
+ prev_indices: Vec<PrimitiveDependencyIndex>,
+ /// The index buffer of primitives that affect this tile on this frame
+ #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
+ curr_indices: Vec<PrimitiveDependencyIndex>,
+ /// A bitset of which of the last 64 frames have been dirty for this leaf.
+ #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
+ dirty_tracker: u64,
+ /// The number of frames since this node split or merged.
+ #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
+ frames_since_modified: usize,
+ },
+ Node {
+ /// The four children of this node
+ children: Vec<TileNode>,
+ },
+}
+
+/// The kind of modification that a tile wants to do
+#[derive(Copy, Clone, PartialEq, Debug)]
+enum TileModification {
+ Split,
+ Merge,
+}
+
+/// A node in the dirty rect tracking quadtree.
+#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct TileNode {
+ /// Leaf or internal node
+ pub kind: TileNodeKind,
+ /// Rect of this node in the same space as the tile cache picture
+ pub rect: PictureBox2D,
+}
+
+impl TileNode {
+ /// Construct a new leaf node, with the given primitive dependency index buffer
+ fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self {
+ TileNode {
+ kind: TileNodeKind::Leaf {
+ prev_indices: Vec::new(),
+ curr_indices,
+ dirty_tracker: 0,
+ frames_since_modified: 0,
+ },
+ rect: PictureBox2D::zero(),
+ }
+ }
+
+ /// Draw debug information about this tile node
+ fn draw_debug_rects(
+ &self,
+ pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
+ is_opaque: bool,
+ local_valid_rect: PictureRect,
+ scratch: &mut PrimitiveScratchBuffer,
+ global_device_pixel_scale: DevicePixelScale,
+ ) {
+ match self.kind {
+ TileNodeKind::Leaf { dirty_tracker, .. } => {
+ let color = if (dirty_tracker & 1) != 0 {
+ debug_colors::RED
+ } else if is_opaque {
+ debug_colors::GREEN
+ } else {
+ debug_colors::YELLOW
+ };
+
+ if let Some(local_rect) = local_valid_rect.intersection(&self.rect) {
+ let world_rect = pic_to_world_mapper
+ .map(&local_rect)
+ .unwrap();
+ let device_rect = world_rect * global_device_pixel_scale;
+
+ let outer_color = color.scale_alpha(0.3);
+ let inner_color = outer_color.scale_alpha(0.5);
+ scratch.push_debug_rect(
+ device_rect.inflate(-3.0, -3.0),
+ 1,
+ outer_color,
+ inner_color
+ );
+ }
+ }
+ TileNodeKind::Node { ref children, .. } => {
+ for child in children.iter() {
+ child.draw_debug_rects(
+ pic_to_world_mapper,
+ is_opaque,
+ local_valid_rect,
+ scratch,
+ global_device_pixel_scale,
+ );
+ }
+ }
+ }
+ }
+
+ /// Calculate the four child rects for a given node
+ fn get_child_rects(
+ rect: &PictureBox2D,
+ result: &mut [PictureBox2D; 4],
+ ) {
+ let p0 = rect.min;
+ let p1 = rect.max;
+ let pc = p0 + rect.size() * 0.5;
+
+ *result = [
+ PictureBox2D::new(
+ p0,
+ pc,
+ ),
+ PictureBox2D::new(
+ PicturePoint::new(pc.x, p0.y),
+ PicturePoint::new(p1.x, pc.y),
+ ),
+ PictureBox2D::new(
+ PicturePoint::new(p0.x, pc.y),
+ PicturePoint::new(pc.x, p1.y),
+ ),
+ PictureBox2D::new(
+ pc,
+ p1,
+ ),
+ ];
+ }
+
+ /// Called during pre_update, to clear the current dependencies
+ fn clear(
+ &mut self,
+ rect: PictureBox2D,
+ ) {
+ self.rect = rect;
+
+ match self.kind {
+ TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => {
+ // Swap current dependencies to be the previous frame
+ mem::swap(prev_indices, curr_indices);
+ curr_indices.clear();
+ // Note that another frame has passed in the dirty bit trackers
+ *dirty_tracker = *dirty_tracker << 1;
+ *frames_since_modified += 1;
+ }
+ TileNodeKind::Node { ref mut children, .. } => {
+ let mut child_rects = [PictureBox2D::zero(); 4];
+ TileNode::get_child_rects(&rect, &mut child_rects);
+ assert_eq!(child_rects.len(), children.len());
+
+ for (child, rect) in children.iter_mut().zip(child_rects.iter()) {
+ child.clear(*rect);
+ }
+ }
+ }
+ }
+
+ /// Add a primitive dependency to this node
+ fn add_prim(
+ &mut self,
+ index: PrimitiveDependencyIndex,
+ prim_rect: &PictureBox2D,
+ ) {
+ match self.kind {
+ TileNodeKind::Leaf { ref mut curr_indices, .. } => {
+ curr_indices.push(index);
+ }
+ TileNodeKind::Node { ref mut children, .. } => {
+ for child in children.iter_mut() {
+ if child.rect.intersects(prim_rect) {
+ child.add_prim(index, prim_rect);
+ }
+ }
+ }
+ }
+ }
+
+ /// Apply a merge or split operation to this tile, if desired
+ fn maybe_merge_or_split(
+ &mut self,
+ level: i32,
+ curr_prims: &[PrimitiveDescriptor],
+ max_split_levels: i32,
+ ) {
+ // Determine if this tile wants to split or merge
+ let mut tile_mod = None;
+
+ fn get_dirty_frames(
+ dirty_tracker: u64,
+ frames_since_modified: usize,
+ ) -> Option<u32> {
+ // Only consider splitting or merging at least 64 frames since we last changed
+ if frames_since_modified > 64 {
+ // Each bit in the tracker is a frame that was recently invalidated
+ Some(dirty_tracker.count_ones())
+ } else {
+ None
+ }
+ }
+
+ match self.kind {
+ TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => {
+ // Only consider splitting if the tree isn't too deep.
+ if level < max_split_levels {
+ if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
+ // If the tile has invalidated > 50% of the recent number of frames, split.
+ if dirty_frames > 32 {
+ tile_mod = Some(TileModification::Split);
+ }
+ }
+ }
+ }
+ TileNodeKind::Node { ref children, .. } => {
+ // There's two conditions that cause a node to merge its children:
+ // (1) If _all_ the child nodes are constantly invalidating, then we are wasting
+ // CPU time tracking dependencies for each child, so merge them.
+ // (2) If _none_ of the child nodes are recently invalid, then the page content
+ // has probably changed, and we no longer need to track fine grained dependencies here.
+
+ let mut static_count = 0;
+ let mut changing_count = 0;
+
+ for child in children {
+ // Only consider merging nodes at the edge of the tree.
+ if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind {
+ if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
+ if dirty_frames == 0 {
+ // Hasn't been invalidated for some time
+ static_count += 1;
+ } else if dirty_frames == 64 {
+ // Is constantly being invalidated
+ changing_count += 1;
+ }
+ }
+ }
+
+ // Only merge if all the child tiles are in agreement. Otherwise, we have some
+ // that are invalidating / static, and it's worthwhile tracking dependencies for
+ // them individually.
+ if static_count == 4 || changing_count == 4 {
+ tile_mod = Some(TileModification::Merge);
+ }
+ }
+ }
+ }
+
+ match tile_mod {
+ Some(TileModification::Split) => {
+ // To split a node, take the current dependency index buffer for this node, and
+ // split it into child index buffers.
+ let curr_indices = match self.kind {
+ TileNodeKind::Node { .. } => {
+ unreachable!("bug - only leaves can split");
+ }
+ TileNodeKind::Leaf { ref mut curr_indices, .. } => {
+ curr_indices.take()
+ }
+ };
+
+ let mut child_rects = [PictureBox2D::zero(); 4];
+ TileNode::get_child_rects(&self.rect, &mut child_rects);
+
+ let mut child_indices = [
+ Vec::new(),
+ Vec::new(),
+ Vec::new(),
+ Vec::new(),
+ ];
+
+ // Step through the index buffer, and add primitives to each of the children
+ // that they intersect.
+ for index in curr_indices {
+ let prim = &curr_prims[index.0 as usize];
+ for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) {
+ if prim.prim_clip_box.intersects(child_rect) {
+ indices.push(index);
+ }
+ }
+ }
+
+ // Create the child nodes and switch from leaf -> node.
+ let children = child_indices
+ .iter_mut()
+ .map(|i| TileNode::new_leaf(mem::replace(i, Vec::new())))
+ .collect();
+
+ self.kind = TileNodeKind::Node {
+ children,
+ };
+ }
+ Some(TileModification::Merge) => {
+ // Construct a merged index buffer by collecting the dependency index buffers
+ // from each child, and merging them into a de-duplicated index buffer.
+ let merged_indices = match self.kind {
+ TileNodeKind::Node { ref mut children, .. } => {
+ let mut merged_indices = Vec::new();
+
+ for child in children.iter() {
+ let child_indices = match child.kind {
+ TileNodeKind::Leaf { ref curr_indices, .. } => {
+ curr_indices
+ }
+ TileNodeKind::Node { .. } => {
+ unreachable!("bug: child is not a leaf");
+ }
+ };
+ merged_indices.extend_from_slice(child_indices);
+ }
+
+ merged_indices.sort();
+ merged_indices.dedup();
+
+ merged_indices
+ }
+ TileNodeKind::Leaf { .. } => {
+ unreachable!("bug - trying to merge a leaf");
+ }
+ };
+
+ // Switch from a node to a leaf, with the combined index buffer
+ self.kind = TileNodeKind::Leaf {
+ prev_indices: Vec::new(),
+ curr_indices: merged_indices,
+ dirty_tracker: 0,
+ frames_since_modified: 0,
+ };
+ }
+ None => {
+ // If this node didn't merge / split, then recurse into children
+ // to see if they want to split / merge.
+ if let TileNodeKind::Node { ref mut children, .. } = self.kind {
+ for child in children.iter_mut() {
+ child.maybe_merge_or_split(
+ level+1,
+ curr_prims,
+ max_split_levels,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ /// Update the dirty state of this node, building the overall dirty rect
+ fn update_dirty_rects(
+ &mut self,
+ prev_prims: &[PrimitiveDescriptor],
+ curr_prims: &[PrimitiveDescriptor],
+ prim_comparer: &mut PrimitiveComparer,
+ dirty_rect: &mut PictureBox2D,
+ compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
+ invalidation_reason: &mut Option<InvalidationReason>,
+ frame_context: &FrameVisibilityContext,
+ ) {
+ match self.kind {
+ TileNodeKind::Node { ref mut children, .. } => {
+ for child in children.iter_mut() {
+ child.update_dirty_rects(
+ prev_prims,
+ curr_prims,
+ prim_comparer,
+ dirty_rect,
+ compare_cache,
+ invalidation_reason,
+ frame_context,
+ );
+ }
+ }
+ TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => {
+ // If the index buffers are of different length, they must be different
+ if prev_indices.len() == curr_indices.len() {
+ // Walk each index buffer, comparing primitives
+ for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) {
+ let i0 = prev_index.0 as usize;
+ let i1 = curr_index.0 as usize;
+
+ // Compare the primitives, caching the result in a hash map
+ // to save comparisons in other tree nodes.
+ let key = PrimitiveComparisonKey {
+ prev_index: *prev_index,
+ curr_index: *curr_index,
+ };
+
+ let prim_compare_result = *compare_cache
+ .entry(key)
+ .or_insert_with(|| {
+ let prev = &prev_prims[i0];
+ let curr = &curr_prims[i1];
+ prim_comparer.compare_prim(prev, curr)
+ });
+
+ // If not the same, mark this node as dirty and update the dirty rect
+ if prim_compare_result != PrimitiveCompareResult::Equal {
+ if invalidation_reason.is_none() {
+ *invalidation_reason = Some(InvalidationReason::Content);
+ }
+ *dirty_rect = self.rect.union(dirty_rect);
+ *dirty_tracker = *dirty_tracker | 1;
+ break;
+ }
+ }
+ } else {
+ if invalidation_reason.is_none() {
+ *invalidation_reason = Some(InvalidationReason::PrimCount);
+ }
+ *dirty_rect = self.rect.union(dirty_rect);
+ *dirty_tracker = *dirty_tracker | 1;
+ }
+ }
+ }
+ }
+}
+
impl CompositeState {
// A helper function to destroy all native surfaces for a given list of tiles
pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Box<Tile>>>(
@@ -2965,7 +7914,7 @@ impl CompositeState {
}
}
-pub fn get_relative_scale_offset(
+fn get_relative_scale_offset(
child_spatial_node_index: SpatialNodeIndex,
parent_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
@@ -2990,6 +7939,319 @@ pub fn get_relative_scale_offset(
scale_offset
}
+pub fn calculate_screen_uv(
+ p: DevicePoint,
+ clipped: DeviceRect,
+) -> DeviceHomogeneousVector {
+ // TODO(gw): Switch to a simple mix, no bilerp / homogeneous vec needed anymore
+ DeviceHomogeneousVector::new(
+ (p.x - clipped.min.x) / (clipped.max.x - clipped.min.x),
+ (p.y - clipped.min.y) / (clipped.max.y - clipped.min.y),
+ 0.0,
+ 1.0,
+ )
+}
+
+fn get_surface_rects(
+ surface_index: SurfaceIndex,
+ composite_mode: &PictureCompositeMode,
+ parent_surface_index: SurfaceIndex,
+ surfaces: &mut [SurfaceInfo],
+ spatial_tree: &SpatialTree,
+ max_surface_size: f32,
+ force_scissor_rect: bool,
+) -> Option<SurfaceAllocInfo> {
+ let parent_surface = &surfaces[parent_surface_index.0];
+
+ let local_to_parent = SpaceMapper::new_with_target(
+ parent_surface.surface_spatial_node_index,
+ surfaces[surface_index.0].surface_spatial_node_index,
+ parent_surface.clipping_rect,
+ spatial_tree,
+ );
+
+ let local_clip_rect = local_to_parent
+ .unmap(&parent_surface.clipping_rect)
+ .unwrap_or(PictureRect::max_rect())
+ .cast_unit();
+
+ let surface = &mut surfaces[surface_index.0];
+
+ let (clipped_local, unclipped_local, source_local) = match composite_mode {
+ PictureCompositeMode::SVGFEGraph(ref filters) => {
+ // We need to get the primitive rect, and get_coverage_target_svgfe
+ // requires the provided rect is in user space (defined in SVG spec)
+ // for subregion calculations to work properly
+ //
+ // Calculate the target rect from source rect, note that this can
+ // produce a valid target rect even with an empty source rect in the
+ // case of filters like feFlood, feComponentTransfer, feColorMatrix,
+ // feImage and feTurbulence which can fill their whole subregion
+ // even if given empty SourceGraphic. It can also produce a smaller
+ // rect than source if subregions or filter region apply clipping to
+ // the intermediate pictures or the final picture.
+ let prim_subregion = composite_mode.get_rect(surface, None);
+
+ // Clip the prim_subregion by the clip_rect, this will be put into
+ // surface_rects.clipped.
+ let visible_subregion: LayoutRect =
+ prim_subregion.cast_unit()
+ .intersection(&local_clip_rect)
+ .unwrap_or(PictureRect::zero())
+ .cast_unit();
+
+ // If the visible_subregion was empty to begin with, or clipped away
+ // entirely, then there is nothing to do here, this is the hot path
+ // for culling of composited pictures.
+ if visible_subregion.is_empty() {
+ return None;
+ }
+
+ // Calculate the subregion for how much of SourceGraphic we may need
+ // to produce to satisfy the invalidation rect, then clip it by the
+ // original primitive rect because we have no reason to produce any
+ // out of bounds pixels; they would just be blank anyway.
+ let source_potential_subregion = composite_mode.get_coverage_source_svgfe(
+ filters, visible_subregion.cast_unit());
+ let source_subregion =
+ source_potential_subregion
+ .intersection(&surface.unclipped_local_rect.cast_unit())
+ .unwrap_or(LayoutRect::zero());
+
+ // For some reason, code assumes that the clipped_local rect we make
+ // here will enclose the source_subregion, and also be a valid
+ // prim_subregion, so we have to union the two rects to meet those
+ // expectations. This is an optimization opportunity - figure out
+ // how to make just the visible_subregion work here.
+ let coverage_subregion = source_subregion.union(&visible_subregion);
+
+ (coverage_subregion.cast_unit(), prim_subregion.cast_unit(), source_subregion.cast_unit())
+ }
+ PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
+ let local_prim_rect = surface.clipped_local_rect;
+
+ let mut required_local_rect = local_prim_rect
+ .intersection(&local_clip_rect)
+ .unwrap_or(PictureRect::zero());
+
+ for shadow in shadows {
+ let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
+ shadow.blur_radius,
+ shadow.blur_radius,
+ );
+ let blur_inflation_x = blur_radius_x * BLUR_SAMPLE_SCALE;
+ let blur_inflation_y = blur_radius_y * BLUR_SAMPLE_SCALE;
+
+ let local_shadow_rect = local_prim_rect
+ .translate(shadow.offset.cast_unit())
+ .inflate(blur_inflation_x, blur_inflation_y);
+
+ if let Some(clipped_shadow_rect) = local_clip_rect.intersection(&local_shadow_rect) {
+ let required_shadow_rect = clipped_shadow_rect.inflate(blur_inflation_x, blur_inflation_y);
+
+ let local_clipped_shadow_rect = required_shadow_rect.translate(-shadow.offset.cast_unit());
+
+ required_local_rect = required_local_rect.union(&local_clipped_shadow_rect);
+ }
+ }
+
+ let unclipped = composite_mode.get_rect(surface, None);
+ let clipped = required_local_rect;
+
+ let clipped = match clipped.intersection(&unclipped.cast_unit()) {
+ Some(rect) => rect,
+ None => return None,
+ };
+
+ (clipped, unclipped, clipped)
+ }
+ _ => {
+ let surface_origin = surface.clipped_local_rect.min.to_vector().cast_unit();
+
+ let normalized_prim_rect = composite_mode
+ .get_rect(surface, None)
+ .translate(-surface_origin);
+
+ let normalized_clip_rect = local_clip_rect
+ .cast_unit()
+ .translate(-surface_origin);
+
+ let norm_clipped_rect = match normalized_prim_rect.intersection(&normalized_clip_rect) {
+ Some(rect) => rect,
+ None => return None,
+ };
+
+ let norm_clipped_rect = composite_mode.get_rect(surface, Some(norm_clipped_rect));
+
+ let norm_clipped_rect = match norm_clipped_rect.intersection(&normalized_prim_rect) {
+ Some(rect) => rect,
+ None => return None,
+ };
+
+ let unclipped = normalized_prim_rect.translate(surface_origin);
+ let clipped = norm_clipped_rect.translate(surface_origin);
+
+ (clipped.cast_unit(), unclipped.cast_unit(), clipped.cast_unit())
+ }
+ };
+
+ // We need to put the clipped, unclipped and source rects in the chosen
+ // raster spatial node if possible, so that it will be rendered at the
+ // proper pixel scale with antialiasing, otherwise it would be blurry.
+ let (mut clipped, mut unclipped, mut source) = if surface.raster_spatial_node_index != surface.surface_spatial_node_index {
+ // Transform surface into the chosen raster spatial node
+ assert_eq!(surface.device_pixel_scale.0, 1.0);
+
+ let local_to_world = SpaceMapper::new_with_target(
+ spatial_tree.root_reference_frame_index(),
+ surface.surface_spatial_node_index,
+ WorldRect::max_rect(),
+ spatial_tree,
+ );
+
+ let clipped = local_to_world.map(&clipped_local.cast_unit()).unwrap() * surface.device_pixel_scale;
+ let unclipped = local_to_world.map(&unclipped_local).unwrap() * surface.device_pixel_scale;
+ let source = local_to_world.map(&source_local.cast_unit()).unwrap() * surface.device_pixel_scale;
+
+ (clipped, unclipped, source)
+ } else {
+ // Surface is already in the chosen raster spatial node
+ let clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
+ let unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
+ let source = source_local.cast_unit() * surface.device_pixel_scale;
+
+ (clipped, unclipped, source)
+ };
+ let mut clipped_snapped = clipped.round_out();
+ let mut source_snapped = source.round_out();
+
+ // We need to make sure the surface size does not exceed max_surface_size,
+ // if it would exceed it we actually want to keep the surface in its local
+ // space and stop worrying about it being a little blurry.
+ //
+ // Since both clipped and source are subject to the same limit, we can just
+ // pick the largest axis from all rects involved.
+ //
+ // Importantly, surfaces that are exactly at max_surface_size are relatively
+ // common for some reason, so we don't want to use a conservative limit.
+ //
+ // If you change this, test with:
+ // ./mach crashtest layout/svg/crashtests/387290-1.svg
+ let max_dimension =
+ clipped_snapped.width().max(
+ clipped_snapped.height().max(
+ source_snapped.width().max(
+ source_snapped.height()
+ ))).ceil();
+ if max_dimension > max_surface_size {
+ // We have to recalculate max_dimension for the local space we'll be
+ // using as we're no longer rasterizing in the parent space
+ let max_dimension =
+ clipped_local.width().max(
+ clipped_local.height().max(
+ source_local.width().max(
+ source_local.height()
+ ))).ceil();
+ surface.raster_spatial_node_index = surface.surface_spatial_node_index;
+ surface.device_pixel_scale = Scale::new(max_surface_size / max_dimension);
+ surface.local_scale = (1.0, 1.0);
+
+ let add_markers = profiler::thread_is_being_profiled();
+ if add_markers {
+ let new_clipped = (clipped_local.cast_unit() * surface.device_pixel_scale).round();
+ let new_source = (source_local.cast_unit() * surface.device_pixel_scale).round();
+ profiler::add_text_marker("SurfaceSizeLimited",
+ format!("Surface for {:?} reduced from raster {:?} (source {:?}) to local {:?} (source {:?})",
+ composite_mode.kind(),
+ clipped.size(), source.size(),
+ new_clipped, new_source).as_str(),
+ Duration::from_secs_f32(new_clipped.width() * new_clipped.height() / 1000000000.0));
+ }
+
+ clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
+ unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
+ source = source_local.cast_unit() * surface.device_pixel_scale;
+ clipped_snapped = clipped.round();
+ source_snapped = source.round();
+ }
+
+ let task_size = clipped_snapped.size().to_i32();
+ // We must avoid hitting the assert here at all costs because panics here
+ // will repeatedly crash the GPU Process, making the whole app unusable,
+ // so make sure task_size <= max_surface_size, it's possible that we lose a
+ // pixel here if the max_dimension threshold was not optimal.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1948939 for more info.
+ let task_size = task_size.min(DeviceIntSize::new(max_surface_size as i32, max_surface_size as i32));
+ debug_assert!(
+ task_size.width <= max_surface_size as i32 &&
+ task_size.height <= max_surface_size as i32,
+ "task_size {:?} for {:?} must be within max_surface_size {}",
+ task_size,
+ composite_mode.kind(),
+ max_surface_size);
+
+ let uv_rect_kind = calculate_uv_rect_kind(
+ clipped_snapped,
+ unclipped,
+ );
+
+ // If the task size is zero sized, skip creation and drawing of it
+ if task_size.width == 0 || task_size.height == 0 {
+ return None;
+ }
+
+ // If the final clipped surface rect is not the same or larger as the unclipped
+ // local rect of the surface, we need to enable scissor rect (which disables
+ // merging batches between this and other render tasks allocated to the same
+ // render target). This is conservative - we could do better in future by
+ // distinguishing between clips that affect the surface itself vs. clips on
+ // child primitives that don't affect this.
+ let needs_scissor_rect = force_scissor_rect || !clipped_local.contains_box(&surface.unclipped_local_rect);
+
+ Some(SurfaceAllocInfo {
+ task_size,
+ needs_scissor_rect,
+ clipped: clipped_snapped,
+ unclipped,
+ source: source_snapped,
+ clipped_notsnapped: clipped,
+ clipped_local,
+ uv_rect_kind,
+ })
+}
+
+pub fn calculate_uv_rect_kind(
+ clipped: DeviceRect,
+ unclipped: DeviceRect,
+) -> UvRectKind {
+ let top_left = calculate_screen_uv(
+ unclipped.top_left().cast_unit(),
+ clipped,
+ );
+
+ let top_right = calculate_screen_uv(
+ unclipped.top_right().cast_unit(),
+ clipped,
+ );
+
+ let bottom_left = calculate_screen_uv(
+ unclipped.bottom_left().cast_unit(),
+ clipped,
+ );
+
+ let bottom_right = calculate_screen_uv(
+ unclipped.bottom_right().cast_unit(),
+ clipped,
+ );
+
+ UvRectKind::Quad {
+ top_left,
+ top_right,
+ bottom_left,
+ bottom_right,
+ }
+}
+
#[test]
fn test_large_surface_scale_1() {
use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
diff --git a/gfx/wr/webrender/src/picture_graph.rs b/gfx/wr/webrender/src/picture_graph.rs
@@ -6,7 +6,7 @@ use crate::frame_builder::FrameBuildingContext;
use crate::internal_types::FastHashMap;
use crate::prim_store::PictureIndex;
use crate::picture::{PicturePrimitive, SurfaceIndex, SurfaceInfo};
-use crate::tile_cache::{TileCacheInstance, SliceId};
+use crate::picture::{TileCacheInstance, SliceId};
use smallvec::SmallVec;
#[derive(Debug)]
diff --git a/gfx/wr/webrender/src/prepare.rs b/gfx/wr/webrender/src/prepare.rs
@@ -23,9 +23,8 @@ use crate::clip::{ClipDataStore, ClipNodeFlags, ClipChainInstance, ClipItemKind}
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
use crate::gpu_types::{BrushFlags, LinearGradientBrushData};
use crate::internal_types::{FastHashMap, PlaneSplitAnchor, Filter};
-use crate::picture::{ClusterFlags, PictureCompositeMode, PicturePrimitive};
-use crate::picture::{PrimitiveList, PrimitiveCluster, SurfaceIndex, SubpixelMode, Picture3DContext};
-use crate::tile_cache::{SliceId, TileCacheInstance};
+use crate::picture::{ClusterFlags, PictureCompositeMode, PicturePrimitive, SliceId};
+use crate::picture::{PrimitiveList, PrimitiveCluster, SurfaceIndex, TileCacheInstance, SubpixelMode, Picture3DContext};
use crate::prim_store::line_dec::MAX_LINE_DECORATION_RESOLUTION;
use crate::prim_store::*;
use crate::quad;
diff --git a/gfx/wr/webrender/src/prim_store/picture.rs b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -11,8 +11,8 @@ use crate::scene_building::IsVisible;
use crate::gpu_types::BlurEdgeMode;
use crate::intern::ItemUid;
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
-use crate::internal_types::{LayoutPrimitiveInfo, Filter};
-use crate::svg_filter::{FilterGraphPictureReference, FilterGraphOp, FilterGraphNode, SVGFE_CONVOLVE_VALUES_LIMIT};
+use crate::internal_types::{LayoutPrimitiveInfo, FilterGraphPictureReference,
+ FilterGraphOp, FilterGraphNode, SVGFE_CONVOLVE_VALUES_LIMIT, Filter};
use crate::picture::PictureCompositeMode;
use crate::prim_store::{
PrimitiveInstanceKind, PrimitiveStore, VectorKey,
diff --git a/gfx/wr/webrender/src/render_backend.rs b/gfx/wr/webrender/src/render_backend.rs
@@ -36,8 +36,7 @@ use crate::intern::DataStore;
use crate::internal_types::DebugOutput;
use crate::internal_types::{FastHashMap, FrameId, FrameStamp, RenderedDocument, ResultMsg};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
-use crate::picture::{PictureScratchBuffer, SurfaceInfo, RasterConfig};
-use crate::tile_cache::{SliceId, TileCacheInstance, TileCacheParams};
+use crate::picture::{PictureScratchBuffer, SliceId, TileCacheInstance, TileCacheParams, SurfaceInfo, RasterConfig};
use crate::picture::PicturePrimitive;
use crate::prim_store::{PrimitiveScratchBuffer, PrimitiveInstance};
use crate::prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData};
diff --git a/gfx/wr/webrender/src/render_target.rs b/gfx/wr/webrender/src/render_target.rs
@@ -16,10 +16,8 @@ use crate::frame_builder::FrameGlobalResources;
use crate::gpu_types::{BorderInstance, SVGFEFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
use crate::gpu_types::{TransformPalette, ZBufferIdGenerator, MaskInstance, ClipSpace, BlurEdgeMode};
use crate::gpu_types::{ZBufferId, QuadSegment, PrimitiveInstanceData, TransformPaletteId};
-use crate::internal_types::{CacheTextureId, FastHashMap, FrameAllocator, FrameMemory, FrameVec, TextureSource};
-use crate::svg_filter::FilterGraphOp;
-use crate::picture::{SurfaceInfo, ResolvedSurfaceTexture};
-use crate::tile_cache::{SliceId, TileCacheInstance};
+use crate::internal_types::{CacheTextureId, FastHashMap, FilterGraphOp, FrameAllocator, FrameMemory, FrameVec, TextureSource};
+use crate::picture::{SliceId, SurfaceInfo, ResolvedSurfaceTexture, TileCacheInstance};
use crate::quad;
use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PrimitiveScratchBuffer};
use crate::prim_store::gradient::{
diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs
@@ -14,10 +14,8 @@ use crate::profiler::{add_text_marker};
use crate::spatial_tree::SpatialNodeIndex;
use crate::frame_builder::FrameBuilderConfig;
use crate::gpu_types::{BorderInstance, UvRectKind, TransformPaletteId, BlurEdgeMode};
-use crate::internal_types::{CacheTextureId, FastHashMap, TextureSource, Swizzle};
-use crate::svg_filter::{FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, SVGFE_CONVOLVE_VALUES_LIMIT};
-use crate::picture::ResolvedSurfaceTexture;
-use crate::tile_cache::MAX_SURFACE_SIZE;
+use crate::internal_types::{CacheTextureId, FastHashMap, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, SVGFE_CONVOLVE_VALUES_LIMIT, TextureSource, Swizzle};
+use crate::picture::{ResolvedSurfaceTexture, 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,6 +20,7 @@ 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;
@@ -666,7 +667,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(crate::tile_cache::TILE_SIZE_DEFAULT);
+ let mut picture_tile_size = options.picture_tile_size.unwrap_or(picture::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,8 +78,7 @@ 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;
-use crate::tile_cache::TileId;
+use crate::picture::{ResolvedSurfaceTexture, 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/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs
@@ -60,8 +60,7 @@ use crate::frame_builder::FrameBuilderConfig;
use glyph_rasterizer::{FontInstance, SharedFontResources};
use crate::hit_test::HitTestingScene;
use crate::intern::Interner;
-use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, PlaneSplitterIndex, PipelineInstanceId};
-use crate::svg_filter::{FilterGraphNode, FilterGraphOp, FilterGraphPictureReference};
+use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, PlaneSplitterIndex, PipelineInstanceId};
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, SurfaceInfo, PictureFlags};
use crate::picture_graph::PictureGraph;
diff --git a/gfx/wr/webrender/src/surface.rs b/gfx/wr/webrender/src/surface.rs
@@ -2,303 +2,23 @@
* 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/. */
-//! Contains functionality to help building the render task graph from a series of off-screen
-//! surfaces that are created during the prepare pass, and other surface related types and
-//! helpers.
-
use api::units::*;
-use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::command_buffer::{CommandBufferBuilderKind, CommandBufferList, CommandBufferBuilder, CommandBufferIndex};
-use crate::gpu_types::UvRectKind;
-use crate::internal_types::{FastHashMap, Filter};
-use crate::picture::PictureCompositeMode;
-use crate::svg_filter::get_coverage_source_svgfe;
-use crate::tile_cache::{TileKey, SubSliceIndex, MAX_COMPOSITOR_SURFACES};
+use crate::internal_types::FastHashMap;
+use crate::picture::{SurfaceInfo, SurfaceIndex, TileKey, SubSliceIndex, MAX_COMPOSITOR_SURFACES};
use crate::prim_store::PictureIndex;
-use crate::profiler;
use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder};
use crate::render_target::ResolveOp;
use crate::render_task::{RenderTask, RenderTaskKind, RenderTaskLocation};
-use crate::space::SpaceMapper;
-use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
-use crate::util::MaxRect;
-use crate::visibility::{VisibilityState, PrimitiveVisibility, FrameVisibilityContext};
-use core::time::Duration;
-use euclid::Scale;
-
-
-/// Maximum blur radius for blur filter
-const MAX_BLUR_RADIUS: f32 = 100.;
-
-/// An index into the surface array
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct SurfaceIndex(pub usize);
-
-/// Specify whether a surface allows subpixel AA text rendering.
-#[derive(Debug, Copy, Clone)]
-pub enum SubpixelMode {
- /// This surface allows subpixel AA text
- Allow,
- /// Subpixel AA text cannot be drawn on this surface
- Deny,
- /// Subpixel AA can be drawn on this surface, if not intersecting
- /// with the excluded regions, and inside the allowed rect.
- Conditional {
- allowed_rect: PictureRect,
- prohibited_rect: PictureRect,
- },
-}
-
-/// Information about an offscreen surface. For now,
-/// it contains information about the size and coordinate
-/// system of the surface. In the future, it will contain
-/// information about the contents of the surface, which
-/// will allow surfaces to be cached / retained between
-/// frames and display lists.
-pub struct SurfaceInfo {
- /// A local rect defining the size of this surface, in the
- /// coordinate system of the parent surface. This contains
- /// the unclipped bounding rect of child primitives.
- pub unclipped_local_rect: PictureRect,
- /// The local space coverage of child primitives after they are
- /// are clipped to their owning clip-chain.
- pub clipped_local_rect: PictureRect,
- /// The (conservative) valid part of this surface rect. Used
- /// to reduce the size of render target allocation.
- pub clipping_rect: PictureRect,
- /// The rectangle to use for culling and clipping.
- pub culling_rect: VisRect,
- /// Helper structs for mapping local rects in different
- /// coordinate systems into the picture coordinates.
- pub map_local_to_picture: SpaceMapper<LayoutPixel, PicturePixel>,
- /// The positioning node for the surface itself,
- pub surface_spatial_node_index: SpatialNodeIndex,
- /// The rasterization root for this surface.
- pub raster_spatial_node_index: SpatialNodeIndex,
- /// The spatial node for culling and clipping (anything using VisPixel).
- /// TODO: Replace with the raster spatial node.
- pub visibility_spatial_node_index: SpatialNodeIndex,
- /// The device pixel ratio specific to this surface.
- pub device_pixel_scale: DevicePixelScale,
- /// The scale factors of the surface to world transform.
- pub world_scale_factors: (f32, f32),
- /// Local scale factors surface to raster transform
- pub local_scale: (f32, f32),
- /// If true, we know this surface is completely opaque.
- pub is_opaque: bool,
- /// If true, allow snapping on this and child surfaces
- pub allow_snapping: bool,
- /// If true, the scissor rect must be set when drawing this surface
- pub force_scissor_rect: bool,
-}
-
-impl SurfaceInfo {
- pub fn new(
- surface_spatial_node_index: SpatialNodeIndex,
- raster_spatial_node_index: SpatialNodeIndex,
- world_rect: WorldRect,
- spatial_tree: &SpatialTree,
- device_pixel_scale: DevicePixelScale,
- world_scale_factors: (f32, f32),
- local_scale: (f32, f32),
- allow_snapping: bool,
- force_scissor_rect: bool,
- ) -> Self {
- let map_surface_to_world = SpaceMapper::new_with_target(
- spatial_tree.root_reference_frame_index(),
- surface_spatial_node_index,
- world_rect,
- spatial_tree,
- );
-
- let pic_bounds = map_surface_to_world
- .unmap(&map_surface_to_world.bounds)
- .unwrap_or_else(PictureRect::max_rect);
-
- let map_local_to_picture = SpaceMapper::new(
- surface_spatial_node_index,
- pic_bounds,
- );
-
- // TODO: replace the root with raster space.
- let visibility_spatial_node_index = spatial_tree.root_reference_frame_index();
-
- SurfaceInfo {
- unclipped_local_rect: PictureRect::zero(),
- clipped_local_rect: PictureRect::zero(),
- is_opaque: false,
- clipping_rect: PictureRect::zero(),
- map_local_to_picture,
- raster_spatial_node_index,
- surface_spatial_node_index,
- visibility_spatial_node_index,
- device_pixel_scale,
- world_scale_factors,
- local_scale,
- allow_snapping,
- force_scissor_rect,
- // TODO: At the moment all culling is done in world space but
- // but the plan is to move it to raster space.
- culling_rect: world_rect.cast_unit(),
- }
- }
-
- /// Clamps the blur radius depending on scale factors.
- pub fn clamp_blur_radius(
- &self,
- x_blur_radius: f32,
- y_blur_radius: f32,
- ) -> (f32, f32) {
- // Clamping must occur after scale factors are applied, but scale factors are not applied
- // until later on. To clamp the blur radius, we first apply the scale factors and then clamp
- // and finally revert the scale factors.
-
- let sx_blur_radius = x_blur_radius * self.local_scale.0;
- let sy_blur_radius = y_blur_radius * self.local_scale.1;
-
- let largest_scaled_blur_radius = f32::max(
- sx_blur_radius * self.world_scale_factors.0,
- sy_blur_radius * self.world_scale_factors.1,
- );
-
- if largest_scaled_blur_radius > MAX_BLUR_RADIUS {
- let sf = MAX_BLUR_RADIUS / largest_scaled_blur_radius;
- (x_blur_radius * sf, y_blur_radius * sf)
- } else {
- // Return the original blur radius to avoid any rounding errors
- (x_blur_radius, y_blur_radius)
- }
- }
-
- pub fn update_culling_rect(
- &mut self,
- parent_culling_rect: VisRect,
- composite_mode: &PictureCompositeMode,
- frame_context: &FrameVisibilityContext,
- ) {
- // Set the default culling rect to be the parent, in case we fail
- // any mappings below due to weird perspective or invalid transforms.
- self.culling_rect = parent_culling_rect;
-
- if let PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate, .. }) = composite_mode {
- if *should_inflate {
- // Space mapping vis <-> picture space
- let map_surface_to_vis = SpaceMapper::new_with_target(
- // TODO: switch from root to raster space.
- frame_context.root_spatial_node_index,
- self.surface_spatial_node_index,
- parent_culling_rect,
- frame_context.spatial_tree,
- );
-
- // Unmap the parent culling rect to surface space. Note that this may be
- // quite conservative in the case of a complex transform, especially perspective.
- if let Some(local_parent_culling_rect) = map_surface_to_vis.unmap(&parent_culling_rect) {
- let (width_factor, height_factor) = self.clamp_blur_radius(*width, *height);
-
- // Inflate by the local-space amount this surface extends.
- let expanded_rect: PictureBox2D = local_parent_culling_rect.inflate(
- width_factor.ceil() * BLUR_SAMPLE_SCALE,
- height_factor.ceil() * BLUR_SAMPLE_SCALE,
- );
-
- // Map back to the expected vis-space culling rect
- if let Some(rect) = map_surface_to_vis.map(&expanded_rect) {
- self.culling_rect = rect;
- }
- }
- }
- }
- }
-
- pub fn map_to_device_rect(
- &self,
- picture_rect: &PictureRect,
- spatial_tree: &SpatialTree,
- ) -> DeviceRect {
- let raster_rect = if self.raster_spatial_node_index != self.surface_spatial_node_index {
- // Currently, the surface's spatial node can be different from its raster node only
- // for surfaces in the root coordinate system for snapping reasons.
- // See `PicturePrimitive::assign_surface`.
- assert_eq!(self.device_pixel_scale.0, 1.0);
- assert_eq!(self.raster_spatial_node_index, spatial_tree.root_reference_frame_index());
-
- let pic_to_raster = SpaceMapper::new_with_target(
- self.raster_spatial_node_index,
- self.surface_spatial_node_index,
- WorldRect::max_rect(),
- spatial_tree,
- );
-
- pic_to_raster.map(&picture_rect).unwrap()
- } else {
- picture_rect.cast_unit()
- };
-
- raster_rect * self.device_pixel_scale
- }
-
- /// Clip and transform a local rect to a device rect suitable for allocating
- /// a child off-screen surface of this surface (e.g. for clip-masks)
- pub fn get_surface_rect(
- &self,
- local_rect: &PictureRect,
- spatial_tree: &SpatialTree,
- ) -> Option<DeviceIntRect> {
- let local_rect = match local_rect.intersection(&self.clipping_rect) {
- Some(rect) => rect,
- None => return None,
- };
-
- let raster_rect = if self.raster_spatial_node_index != self.surface_spatial_node_index {
- assert_eq!(self.device_pixel_scale.0, 1.0);
-
- let local_to_world = SpaceMapper::new_with_target(
- spatial_tree.root_reference_frame_index(),
- self.surface_spatial_node_index,
- WorldRect::max_rect(),
- spatial_tree,
- );
-
- local_to_world.map(&local_rect).unwrap()
- } else {
- // The content should have been culled out earlier.
- assert!(self.device_pixel_scale.0 > 0.0);
-
- local_rect.cast_unit()
- };
-
- let surface_rect = (raster_rect * self.device_pixel_scale).round_out().to_i32();
- if surface_rect.is_empty() {
- // The local_rect computed above may have non-empty size that is very
- // close to zero. Due to limited arithmetic precision, the SpaceMapper
- // might transform the near-zero-sized rect into a zero-sized one.
- return None;
- }
-
- Some(surface_rect)
- }
-}
-
-/// Information from `get_surface_rects` about the allocated size, UV sampling
-/// parameters etc for an off-screen surface
-#[derive(Debug)]
-pub struct SurfaceAllocInfo {
- pub task_size: DeviceIntSize,
- pub needs_scissor_rect: bool,
- pub clipped: DeviceRect,
- pub unclipped: DeviceRect,
- // Only used for SVGFEGraph currently, this is the source pixels needed to
- // render the pixels in clipped.
- pub source: DeviceRect,
- // Only used for SVGFEGraph, this is the same as clipped before rounding.
- pub clipped_notsnapped: DeviceRect,
- pub clipped_local: PictureRect,
- pub uv_rect_kind: UvRectKind,
-}
+use crate::visibility::{VisibilityState, PrimitiveVisibility};
+/*
+ Contains functionality to help building the render task graph from a series of off-screen
+ surfaces that are created during the prepare pass. For now, it maintains existing behavior.
+ A future patch will add support for surface sub-graphs, while ensuring the render task
+ graph itself is built correctly with dependencies regardless of the surface kind (chained,
+ tiled, simple).
+ */
// Information about the render task(s) for a given tile
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -897,317 +617,3 @@ impl SurfaceBuilder {
assert!(self.builder_stack.is_empty());
}
}
-
-
-pub fn calculate_screen_uv(
- p: DevicePoint,
- clipped: DeviceRect,
-) -> DeviceHomogeneousVector {
- // TODO(gw): Switch to a simple mix, no bilerp / homogeneous vec needed anymore
- DeviceHomogeneousVector::new(
- (p.x - clipped.min.x) / (clipped.max.x - clipped.min.x),
- (p.y - clipped.min.y) / (clipped.max.y - clipped.min.y),
- 0.0,
- 1.0,
- )
-}
-
-pub fn get_surface_rects(
- surface_index: SurfaceIndex,
- composite_mode: &PictureCompositeMode,
- parent_surface_index: SurfaceIndex,
- surfaces: &mut [SurfaceInfo],
- spatial_tree: &SpatialTree,
- max_surface_size: f32,
- force_scissor_rect: bool,
-) -> Option<SurfaceAllocInfo> {
- let parent_surface = &surfaces[parent_surface_index.0];
-
- let local_to_parent = SpaceMapper::new_with_target(
- parent_surface.surface_spatial_node_index,
- surfaces[surface_index.0].surface_spatial_node_index,
- parent_surface.clipping_rect,
- spatial_tree,
- );
-
- let local_clip_rect = local_to_parent
- .unmap(&parent_surface.clipping_rect)
- .unwrap_or(PictureRect::max_rect())
- .cast_unit();
-
- let surface = &mut surfaces[surface_index.0];
-
- let (clipped_local, unclipped_local, source_local) = match composite_mode {
- PictureCompositeMode::SVGFEGraph(ref filters) => {
- // We need to get the primitive rect, and get_coverage_target_svgfe
- // requires the provided rect is in user space (defined in SVG spec)
- // for subregion calculations to work properly
- //
- // Calculate the target rect from source rect, note that this can
- // produce a valid target rect even with an empty source rect in the
- // case of filters like feFlood, feComponentTransfer, feColorMatrix,
- // feImage and feTurbulence which can fill their whole subregion
- // even if given empty SourceGraphic. It can also produce a smaller
- // rect than source if subregions or filter region apply clipping to
- // the intermediate pictures or the final picture.
- let prim_subregion = composite_mode.get_rect(surface, None);
-
- // Clip the prim_subregion by the clip_rect, this will be put into
- // surface_rects.clipped.
- let visible_subregion: LayoutRect =
- prim_subregion.cast_unit()
- .intersection(&local_clip_rect)
- .unwrap_or(PictureRect::zero())
- .cast_unit();
-
- // If the visible_subregion was empty to begin with, or clipped away
- // entirely, then there is nothing to do here, this is the hot path
- // for culling of composited pictures.
- if visible_subregion.is_empty() {
- return None;
- }
-
- // Calculate the subregion for how much of SourceGraphic we may need
- // to produce to satisfy the invalidation rect, then clip it by the
- // original primitive rect because we have no reason to produce any
- // out of bounds pixels; they would just be blank anyway.
- let source_potential_subregion = get_coverage_source_svgfe(
- filters, visible_subregion.cast_unit());
- let source_subregion =
- source_potential_subregion
- .intersection(&surface.unclipped_local_rect.cast_unit())
- .unwrap_or(LayoutRect::zero());
-
- // For some reason, code assumes that the clipped_local rect we make
- // here will enclose the source_subregion, and also be a valid
- // prim_subregion, so we have to union the two rects to meet those
- // expectations. This is an optimization opportunity - figure out
- // how to make just the visible_subregion work here.
- let coverage_subregion = source_subregion.union(&visible_subregion);
-
- (coverage_subregion.cast_unit(), prim_subregion.cast_unit(), source_subregion.cast_unit())
- }
- PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
- let local_prim_rect = surface.clipped_local_rect;
-
- let mut required_local_rect = local_prim_rect
- .intersection(&local_clip_rect)
- .unwrap_or(PictureRect::zero());
-
- for shadow in shadows {
- let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
- shadow.blur_radius,
- shadow.blur_radius,
- );
- let blur_inflation_x = blur_radius_x * BLUR_SAMPLE_SCALE;
- let blur_inflation_y = blur_radius_y * BLUR_SAMPLE_SCALE;
-
- let local_shadow_rect = local_prim_rect
- .translate(shadow.offset.cast_unit())
- .inflate(blur_inflation_x, blur_inflation_y);
-
- if let Some(clipped_shadow_rect) = local_clip_rect.intersection(&local_shadow_rect) {
- let required_shadow_rect = clipped_shadow_rect.inflate(blur_inflation_x, blur_inflation_y);
-
- let local_clipped_shadow_rect = required_shadow_rect.translate(-shadow.offset.cast_unit());
-
- required_local_rect = required_local_rect.union(&local_clipped_shadow_rect);
- }
- }
-
- let unclipped = composite_mode.get_rect(surface, None);
- let clipped = required_local_rect;
-
- let clipped = match clipped.intersection(&unclipped.cast_unit()) {
- Some(rect) => rect,
- None => return None,
- };
-
- (clipped, unclipped, clipped)
- }
- _ => {
- let surface_origin = surface.clipped_local_rect.min.to_vector().cast_unit();
-
- let normalized_prim_rect = composite_mode
- .get_rect(surface, None)
- .translate(-surface_origin);
-
- let normalized_clip_rect = local_clip_rect
- .cast_unit()
- .translate(-surface_origin);
-
- let norm_clipped_rect = match normalized_prim_rect.intersection(&normalized_clip_rect) {
- Some(rect) => rect,
- None => return None,
- };
-
- let norm_clipped_rect = composite_mode.get_rect(surface, Some(norm_clipped_rect));
-
- let norm_clipped_rect = match norm_clipped_rect.intersection(&normalized_prim_rect) {
- Some(rect) => rect,
- None => return None,
- };
-
- let unclipped = normalized_prim_rect.translate(surface_origin);
- let clipped = norm_clipped_rect.translate(surface_origin);
-
- (clipped.cast_unit(), unclipped.cast_unit(), clipped.cast_unit())
- }
- };
-
- // We need to put the clipped, unclipped and source rects in the chosen
- // raster spatial node if possible, so that it will be rendered at the
- // proper pixel scale with antialiasing, otherwise it would be blurry.
- let (mut clipped, mut unclipped, mut source) = if surface.raster_spatial_node_index != surface.surface_spatial_node_index {
- // Transform surface into the chosen raster spatial node
- assert_eq!(surface.device_pixel_scale.0, 1.0);
-
- let local_to_world = SpaceMapper::new_with_target(
- spatial_tree.root_reference_frame_index(),
- surface.surface_spatial_node_index,
- WorldRect::max_rect(),
- spatial_tree,
- );
-
- let clipped = local_to_world.map(&clipped_local.cast_unit()).unwrap() * surface.device_pixel_scale;
- let unclipped = local_to_world.map(&unclipped_local).unwrap() * surface.device_pixel_scale;
- let source = local_to_world.map(&source_local.cast_unit()).unwrap() * surface.device_pixel_scale;
-
- (clipped, unclipped, source)
- } else {
- // Surface is already in the chosen raster spatial node
- let clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
- let unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
- let source = source_local.cast_unit() * surface.device_pixel_scale;
-
- (clipped, unclipped, source)
- };
- let mut clipped_snapped = clipped.round_out();
- let mut source_snapped = source.round_out();
-
- // We need to make sure the surface size does not exceed max_surface_size,
- // if it would exceed it we actually want to keep the surface in its local
- // space and stop worrying about it being a little blurry.
- //
- // Since both clipped and source are subject to the same limit, we can just
- // pick the largest axis from all rects involved.
- //
- // Importantly, surfaces that are exactly at max_surface_size are relatively
- // common for some reason, so we don't want to use a conservative limit.
- //
- // If you change this, test with:
- // ./mach crashtest layout/svg/crashtests/387290-1.svg
- let max_dimension =
- clipped_snapped.width().max(
- clipped_snapped.height().max(
- source_snapped.width().max(
- source_snapped.height()
- ))).ceil();
- if max_dimension > max_surface_size {
- // We have to recalculate max_dimension for the local space we'll be
- // using as we're no longer rasterizing in the parent space
- let max_dimension =
- clipped_local.width().max(
- clipped_local.height().max(
- source_local.width().max(
- source_local.height()
- ))).ceil();
- surface.raster_spatial_node_index = surface.surface_spatial_node_index;
- surface.device_pixel_scale = Scale::new(max_surface_size / max_dimension);
- surface.local_scale = (1.0, 1.0);
-
- let add_markers = profiler::thread_is_being_profiled();
- if add_markers {
- let new_clipped = (clipped_local.cast_unit() * surface.device_pixel_scale).round();
- let new_source = (source_local.cast_unit() * surface.device_pixel_scale).round();
- profiler::add_text_marker("SurfaceSizeLimited",
- format!("Surface for {:?} reduced from raster {:?} (source {:?}) to local {:?} (source {:?})",
- composite_mode.kind(),
- clipped.size(), source.size(),
- new_clipped, new_source).as_str(),
- Duration::from_secs_f32(new_clipped.width() * new_clipped.height() / 1000000000.0));
- }
-
- clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
- unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
- source = source_local.cast_unit() * surface.device_pixel_scale;
- clipped_snapped = clipped.round();
- source_snapped = source.round();
- }
-
- let task_size = clipped_snapped.size().to_i32();
- // We must avoid hitting the assert here at all costs because panics here
- // will repeatedly crash the GPU Process, making the whole app unusable,
- // so make sure task_size <= max_surface_size, it's possible that we lose a
- // pixel here if the max_dimension threshold was not optimal.
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=1948939 for more info.
- let task_size = task_size.min(DeviceIntSize::new(max_surface_size as i32, max_surface_size as i32));
- debug_assert!(
- task_size.width <= max_surface_size as i32 &&
- task_size.height <= max_surface_size as i32,
- "task_size {:?} for {:?} must be within max_surface_size {}",
- task_size,
- composite_mode.kind(),
- max_surface_size);
-
- let uv_rect_kind = calculate_uv_rect_kind(
- clipped_snapped,
- unclipped,
- );
-
- // If the task size is zero sized, skip creation and drawing of it
- if task_size.width == 0 || task_size.height == 0 {
- return None;
- }
-
- // If the final clipped surface rect is not the same or larger as the unclipped
- // local rect of the surface, we need to enable scissor rect (which disables
- // merging batches between this and other render tasks allocated to the same
- // render target). This is conservative - we could do better in future by
- // distinguishing between clips that affect the surface itself vs. clips on
- // child primitives that don't affect this.
- let needs_scissor_rect = force_scissor_rect || !clipped_local.contains_box(&surface.unclipped_local_rect);
-
- Some(SurfaceAllocInfo {
- task_size,
- needs_scissor_rect,
- clipped: clipped_snapped,
- unclipped,
- source: source_snapped,
- clipped_notsnapped: clipped,
- clipped_local,
- uv_rect_kind,
- })
-}
-
-pub fn calculate_uv_rect_kind(
- clipped: DeviceRect,
- unclipped: DeviceRect,
-) -> UvRectKind {
- let top_left = calculate_screen_uv(
- unclipped.top_left().cast_unit(),
- clipped,
- );
-
- let top_right = calculate_screen_uv(
- unclipped.top_right().cast_unit(),
- clipped,
- );
-
- let bottom_left = calculate_screen_uv(
- unclipped.bottom_left().cast_unit(),
- clipped,
- );
-
- let bottom_right = calculate_screen_uv(
- unclipped.bottom_right().cast_unit(),
- clipped,
- );
-
- UvRectKind::Quad {
- top_left,
- top_right,
- bottom_left,
- bottom_right,
- }
-}
diff --git a/gfx/wr/webrender/src/svg_filter.rs b/gfx/wr/webrender/src/svg_filter.rs
@@ -1,839 +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::{FilterOpGraphPictureReference, FilterOpGraphNode, ColorF};
-use api::SVGFE_GRAPH_MAX;
-use api::units::*;
-use api::FilterOpGraphPictureBufferId;
-use crate::profiler::add_text_marker;
-use crate::filterdata::FilterDataHandle;
-use core::time::Duration;
-
-#[derive(Clone, Copy, Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FilterGraphPictureReference {
- /// Id of the picture in question in a namespace unique to this filter DAG,
- /// some are special values like
- /// FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic.
- pub buffer_id: FilterOpGraphPictureBufferId,
- /// Set by wrap_prim_with_filters to the subregion of the input node, may
- /// also have been offset for feDropShadow or feOffset
- pub subregion: LayoutRect,
- /// During scene build this is the offset to apply to the input subregion
- /// for feOffset, which can be optimized away by pushing its offset and
- /// subregion crop to downstream nodes. This is always zero in render tasks
- /// where it has already been applied to subregion by that point. Not used
- /// in get_coverage_svgfe because source_padding/target_padding represent
- /// the offset there.
- pub offset: LayoutVector2D,
- /// Equal to the inflate value of the referenced buffer, or 0
- pub inflate: i16,
- /// Padding on each side to represent how this input is read relative to the
- /// node's output subregion, this represents what the operation needs to
- /// read from ths input, which may be blurred or offset.
- pub source_padding: LayoutRect,
- /// Padding on each side to represent how this input affects the node's
- /// subregion, this can be used to calculate target subregion based on
- /// SourceGraphic subregion. This is usually equal to source_padding except
- /// offset in the opposite direction, inflates typically do the same thing
- /// to both types of padding.
- pub target_padding: LayoutRect,
-}
-
-impl From<FilterOpGraphPictureReference> for FilterGraphPictureReference {
- fn from(pic: FilterOpGraphPictureReference) -> Self {
- FilterGraphPictureReference{
- buffer_id: pic.buffer_id,
- // All of these are set by wrap_prim_with_filters
- subregion: LayoutRect::zero(),
- offset: LayoutVector2D::zero(),
- inflate: 0,
- source_padding: LayoutRect::zero(),
- target_padding: LayoutRect::zero(),
- }
- }
-}
-
-pub const SVGFE_CONVOLVE_DIAMETER_LIMIT: usize = 5;
-pub const SVGFE_CONVOLVE_VALUES_LIMIT: usize = SVGFE_CONVOLVE_DIAMETER_LIMIT *
- SVGFE_CONVOLVE_DIAMETER_LIMIT;
-
-#[derive(Clone, Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum FilterGraphOp {
- /// Filter that copies the SourceGraphic image into the specified subregion,
- /// This is intentionally the only way to get SourceGraphic into the graph,
- /// as the filter region must be applied before it is used.
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - no inputs, no linear
- SVGFESourceGraphic,
- /// Filter that copies the SourceAlpha image into the specified subregion,
- /// This is intentionally the only way to get SourceAlpha into the graph,
- /// as the filter region must be applied before it is used.
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - no inputs, no linear
- SVGFESourceAlpha,
- /// Filter that does no transformation of the colors, used to implement a
- /// few things like SVGFEOffset, and this is the default value in
- /// impl_default_for_enums.
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input with offset
- SVGFEIdentity,
- /// represents CSS opacity property as a graph node like the rest of the
- /// SVGFE* filters
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- SVGFEOpacity{valuebinding: api::PropertyBinding<f32>, value: f32},
- /// convert a color image to an alpha channel - internal use; generated by
- /// SVGFilterInstance::GetOrCreateSourceAlphaIndex().
- SVGFEToAlpha,
- /// combine 2 images with SVG_FEBLEND_MODE_DARKEN
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
- SVGFEBlendDarken,
- /// combine 2 images with SVG_FEBLEND_MODE_LIGHTEN
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
- SVGFEBlendLighten,
- /// combine 2 images with SVG_FEBLEND_MODE_MULTIPLY
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
- SVGFEBlendMultiply,
- /// combine 2 images with SVG_FEBLEND_MODE_NORMAL
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
- SVGFEBlendNormal,
- /// combine 2 images with SVG_FEBLEND_MODE_SCREEN
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
- SVGFEBlendScreen,
- /// combine 2 images with SVG_FEBLEND_MODE_OVERLAY
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendOverlay,
- /// combine 2 images with SVG_FEBLEND_MODE_COLOR_DODGE
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendColorDodge,
- /// combine 2 images with SVG_FEBLEND_MODE_COLOR_BURN
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendColorBurn,
- /// combine 2 images with SVG_FEBLEND_MODE_HARD_LIGHT
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendHardLight,
- /// combine 2 images with SVG_FEBLEND_MODE_SOFT_LIGHT
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendSoftLight,
- /// combine 2 images with SVG_FEBLEND_MODE_DIFFERENCE
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendDifference,
- /// combine 2 images with SVG_FEBLEND_MODE_EXCLUSION
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendExclusion,
- /// combine 2 images with SVG_FEBLEND_MODE_HUE
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendHue,
- /// combine 2 images with SVG_FEBLEND_MODE_SATURATION
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendSaturation,
- /// combine 2 images with SVG_FEBLEND_MODE_COLOR
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendColor,
- /// combine 2 images with SVG_FEBLEND_MODE_LUMINOSITY
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
- SVGFEBlendLuminosity,
- /// transform colors of image through 5x4 color matrix (transposed for
- /// efficiency)
- /// parameters: FilterGraphNode, matrix[5][4]
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feColorMatrixElement
- SVGFEColorMatrix{values: [f32; 20]},
- /// transform colors of image through configurable gradients with component
- /// swizzle
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feComponentTransferElement
- SVGFEComponentTransfer,
- /// Processed version of SVGFEComponentTransfer with the FilterData
- /// replaced by an interned handle, this is made in wrap_prim_with_filters.
- /// Aside from the interned handle, creates_pixels indicates if the transfer
- /// parameters will probably fill the entire subregion with non-zero alpha.
- SVGFEComponentTransferInterned{handle: FilterDataHandle, creates_pixels: bool},
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterGraphNode, k1, k2, k3, k4
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
- SVGFECompositeArithmetic{k1: f32, k2: f32, k3: f32, k4: f32},
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
- SVGFECompositeATop,
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
- SVGFECompositeIn,
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterOpGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Docs: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite
- SVGFECompositeLighter,
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
- SVGFECompositeOut,
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
- SVGFECompositeOver,
- /// composite 2 images with chosen composite mode with parameters for that
- /// mode
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
- SVGFECompositeXOR,
- /// transform image through convolution matrix of up to 25 values (spec
- /// allows more but for performance reasons we do not)
- /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
- /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
- /// preserveAlpha
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
- SVGFEConvolveMatrixEdgeModeDuplicate{order_x: i32, order_y: i32,
- kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
- target_x: i32, target_y: i32, kernel_unit_length_x: f32,
- kernel_unit_length_y: f32, preserve_alpha: i32},
- /// transform image through convolution matrix of up to 25 values (spec
- /// allows more but for performance reasons we do not)
- /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
- /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
- /// preserveAlpha
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
- SVGFEConvolveMatrixEdgeModeNone{order_x: i32, order_y: i32,
- kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
- target_x: i32, target_y: i32, kernel_unit_length_x: f32,
- kernel_unit_length_y: f32, preserve_alpha: i32},
- /// transform image through convolution matrix of up to 25 values (spec
- /// allows more but for performance reasons we do not)
- /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
- /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
- /// preserveAlpha
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
- SVGFEConvolveMatrixEdgeModeWrap{order_x: i32, order_y: i32,
- kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
- target_x: i32, target_y: i32, kernel_unit_length_x: f32,
- kernel_unit_length_y: f32, preserve_alpha: i32},
- /// calculate lighting based on heightmap image with provided values for a
- /// distant light source with specified direction
- /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
- /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
- /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
- SVGFEDiffuseLightingDistant{surface_scale: f32, diffuse_constant: f32,
- kernel_unit_length_x: f32, kernel_unit_length_y: f32, azimuth: f32,
- elevation: f32},
- /// calculate lighting based on heightmap image with provided values for a
- /// point light source at specified location
- /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
- /// kernelUnitLengthX, kernelUnitLengthY, x, y, z
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
- /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
- SVGFEDiffuseLightingPoint{surface_scale: f32, diffuse_constant: f32,
- kernel_unit_length_x: f32, kernel_unit_length_y: f32, x: f32, y: f32,
- z: f32},
- /// calculate lighting based on heightmap image with provided values for a
- /// spot light source at specified location pointing at specified target
- /// location with specified hotspot sharpness and cone angle
- /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
- /// kernelUnitLengthX, kernelUnitLengthY, x, y, z, pointsAtX, pointsAtY,
- /// pointsAtZ, specularExponent, limitingConeAngle
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
- /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
- SVGFEDiffuseLightingSpot{surface_scale: f32, diffuse_constant: f32,
- kernel_unit_length_x: f32, kernel_unit_length_y: f32, x: f32, y: f32,
- z: f32, points_at_x: f32, points_at_y: f32, points_at_z: f32,
- cone_exponent: f32, limiting_cone_angle: f32},
- /// calculate a distorted version of first input image using offset values
- /// from second input image at specified intensity
- /// parameters: FilterGraphNode, scale, xChannelSelector, yChannelSelector
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDisplacementMapElement
- SVGFEDisplacementMap{scale: f32, x_channel_selector: u32,
- y_channel_selector: u32},
- /// create and merge a dropshadow version of the specified image's alpha
- /// channel with specified offset and blur radius
- /// parameters: FilterGraphNode, flood_color, flood_opacity, dx, dy,
- /// stdDeviationX, stdDeviationY
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDropShadowElement
- SVGFEDropShadow{color: ColorF, dx: f32, dy: f32, std_deviation_x: f32,
- std_deviation_y: f32},
- /// synthesize a new image of specified size containing a solid color
- /// parameters: FilterGraphNode, color
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEFloodElement
- SVGFEFlood{color: ColorF},
- /// create a blurred version of the input image
- /// parameters: FilterGraphNode, stdDeviationX, stdDeviationY
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEGaussianBlurElement
- SVGFEGaussianBlur{std_deviation_x: f32, std_deviation_y: f32},
- /// synthesize a new image based on a url (i.e. blob image source)
- /// parameters: FilterGraphNode,
- /// samplingFilter (see SamplingFilter in Types.h), transform
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEImageElement
- SVGFEImage{sampling_filter: u32, matrix: [f32; 6]},
- /// create a new image based on the input image with the contour stretched
- /// outward (dilate operator)
- /// parameters: FilterGraphNode, radiusX, radiusY
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
- SVGFEMorphologyDilate{radius_x: f32, radius_y: f32},
- /// create a new image based on the input image with the contour shrunken
- /// inward (erode operator)
- /// parameters: FilterGraphNode, radiusX, radiusY
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
- SVGFEMorphologyErode{radius_x: f32, radius_y: f32},
- /// calculate lighting based on heightmap image with provided values for a
- /// distant light source with specified direction
- /// parameters: FilerData, surfaceScale, specularConstant, specularExponent,
- /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
- /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
- SVGFESpecularLightingDistant{surface_scale: f32, specular_constant: f32,
- specular_exponent: f32, kernel_unit_length_x: f32,
- kernel_unit_length_y: f32, azimuth: f32, elevation: f32},
- /// calculate lighting based on heightmap image with provided values for a
- /// point light source at specified location
- /// parameters: FilterGraphNode, surfaceScale, specularConstant,
- /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
- /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
- SVGFESpecularLightingPoint{surface_scale: f32, specular_constant: f32,
- specular_exponent: f32, kernel_unit_length_x: f32,
- kernel_unit_length_y: f32, x: f32, y: f32, z: f32},
- /// calculate lighting based on heightmap image with provided values for a
- /// spot light source at specified location pointing at specified target
- /// location with specified hotspot sharpness and cone angle
- /// parameters: FilterGraphNode, surfaceScale, specularConstant,
- /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z,
- /// pointsAtX, pointsAtY, pointsAtZ, specularExponent, limitingConeAngle
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
- /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
- SVGFESpecularLightingSpot{surface_scale: f32, specular_constant: f32,
- specular_exponent: f32, kernel_unit_length_x: f32,
- kernel_unit_length_y: f32, x: f32, y: f32, z: f32, points_at_x: f32,
- points_at_y: f32, points_at_z: f32, cone_exponent: f32,
- limiting_cone_angle: f32},
- /// create a new image based on the input image, repeated throughout the
- /// output rectangle
- /// parameters: FilterGraphNode
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETileElement
- SVGFETile,
- /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
- /// stitching mode
- /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
- /// seed
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
- SVGFETurbulenceWithFractalNoiseWithNoStitching{base_frequency_x: f32,
- base_frequency_y: f32, num_octaves: u32, seed: u32},
- /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
- /// stitching mode
- /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
- /// seed
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
- SVGFETurbulenceWithFractalNoiseWithStitching{base_frequency_x: f32,
- base_frequency_y: f32, num_octaves: u32, seed: u32},
- /// synthesize a new image based on Turbulence Noise (offset vectors)
- /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
- /// seed
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
- SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{base_frequency_x: f32,
- base_frequency_y: f32, num_octaves: u32, seed: u32},
- /// synthesize a new image based on Turbulence Noise (offset vectors)
- /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
- /// seed
- /// SVG filter semantics - selectable input(s), selectable between linear
- /// (default) and sRGB color space for calculations
- /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
- SVGFETurbulenceWithTurbulenceNoiseWithStitching{base_frequency_x: f32,
- base_frequency_y: f32, num_octaves: u32, seed: u32},
-}
-
-impl FilterGraphOp {
- pub fn kind(&self) -> &'static str {
- match *self {
- FilterGraphOp::SVGFEBlendColor => "SVGFEBlendColor",
- FilterGraphOp::SVGFEBlendColorBurn => "SVGFEBlendColorBurn",
- FilterGraphOp::SVGFEBlendColorDodge => "SVGFEBlendColorDodge",
- FilterGraphOp::SVGFEBlendDarken => "SVGFEBlendDarken",
- FilterGraphOp::SVGFEBlendDifference => "SVGFEBlendDifference",
- FilterGraphOp::SVGFEBlendExclusion => "SVGFEBlendExclusion",
- FilterGraphOp::SVGFEBlendHardLight => "SVGFEBlendHardLight",
- FilterGraphOp::SVGFEBlendHue => "SVGFEBlendHue",
- FilterGraphOp::SVGFEBlendLighten => "SVGFEBlendLighten",
- FilterGraphOp::SVGFEBlendLuminosity => "SVGFEBlendLuminosity",
- FilterGraphOp::SVGFEBlendMultiply => "SVGFEBlendMultiply",
- FilterGraphOp::SVGFEBlendNormal => "SVGFEBlendNormal",
- FilterGraphOp::SVGFEBlendOverlay => "SVGFEBlendOverlay",
- FilterGraphOp::SVGFEBlendSaturation => "SVGFEBlendSaturation",
- FilterGraphOp::SVGFEBlendScreen => "SVGFEBlendScreen",
- FilterGraphOp::SVGFEBlendSoftLight => "SVGFEBlendSoftLight",
- FilterGraphOp::SVGFEColorMatrix{..} => "SVGFEColorMatrix",
- FilterGraphOp::SVGFEComponentTransfer => "SVGFEComponentTransfer",
- FilterGraphOp::SVGFEComponentTransferInterned{..} => "SVGFEComponentTransferInterned",
- FilterGraphOp::SVGFECompositeArithmetic{..} => "SVGFECompositeArithmetic",
- FilterGraphOp::SVGFECompositeATop => "SVGFECompositeATop",
- FilterGraphOp::SVGFECompositeIn => "SVGFECompositeIn",
- FilterGraphOp::SVGFECompositeLighter => "SVGFECompositeLighter",
- FilterGraphOp::SVGFECompositeOut => "SVGFECompositeOut",
- FilterGraphOp::SVGFECompositeOver => "SVGFECompositeOver",
- FilterGraphOp::SVGFECompositeXOR => "SVGFECompositeXOR",
- FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => "SVGFEConvolveMatrixEdgeModeDuplicate",
- FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => "SVGFEConvolveMatrixEdgeModeNone",
- FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => "SVGFEConvolveMatrixEdgeModeWrap",
- FilterGraphOp::SVGFEDiffuseLightingDistant{..} => "SVGFEDiffuseLightingDistant",
- FilterGraphOp::SVGFEDiffuseLightingPoint{..} => "SVGFEDiffuseLightingPoint",
- FilterGraphOp::SVGFEDiffuseLightingSpot{..} => "SVGFEDiffuseLightingSpot",
- FilterGraphOp::SVGFEDisplacementMap{..} => "SVGFEDisplacementMap",
- FilterGraphOp::SVGFEDropShadow{..} => "SVGFEDropShadow",
- FilterGraphOp::SVGFEFlood{..} => "SVGFEFlood",
- FilterGraphOp::SVGFEGaussianBlur{..} => "SVGFEGaussianBlur",
- FilterGraphOp::SVGFEIdentity => "SVGFEIdentity",
- FilterGraphOp::SVGFEImage{..} => "SVGFEImage",
- FilterGraphOp::SVGFEMorphologyDilate{..} => "SVGFEMorphologyDilate",
- FilterGraphOp::SVGFEMorphologyErode{..} => "SVGFEMorphologyErode",
- FilterGraphOp::SVGFEOpacity{..} => "SVGFEOpacity",
- FilterGraphOp::SVGFESourceAlpha => "SVGFESourceAlpha",
- FilterGraphOp::SVGFESourceGraphic => "SVGFESourceGraphic",
- FilterGraphOp::SVGFESpecularLightingDistant{..} => "SVGFESpecularLightingDistant",
- FilterGraphOp::SVGFESpecularLightingPoint{..} => "SVGFESpecularLightingPoint",
- FilterGraphOp::SVGFESpecularLightingSpot{..} => "SVGFESpecularLightingSpot",
- FilterGraphOp::SVGFETile => "SVGFETile",
- FilterGraphOp::SVGFEToAlpha => "SVGFEToAlpha",
- FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} => "SVGFETurbulenceWithFractalNoiseWithNoStitching",
- FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} => "SVGFETurbulenceWithFractalNoiseWithStitching",
- FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} => "SVGFETurbulenceWithTurbulenceNoiseWithNoStitching",
- FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => "SVGFETurbulenceWithTurbulenceNoiseWithStitching",
- }
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FilterGraphNode {
- /// Indicates this graph node was marked as necessary by the DAG optimizer
- pub kept_by_optimizer: bool,
- /// true if color_interpolation_filter == LinearRgb; shader will convert
- /// sRGB texture pixel colors on load and convert back on store, for correct
- /// interpolation
- pub linear: bool,
- /// padding for output rect if we need a border to get correct clamping, or
- /// to account for larger final subregion than source rect (see bug 1869672)
- pub inflate: i16,
- /// virtualized picture input bindings, these refer to other filter outputs
- /// by number within the graph, usually there is one element
- pub inputs: Vec<FilterGraphPictureReference>,
- /// clipping rect for filter node output
- pub subregion: LayoutRect,
-}
-
-impl From<FilterOpGraphNode> for FilterGraphNode {
- fn from(node: FilterOpGraphNode) -> Self {
- let mut inputs: Vec<FilterGraphPictureReference> = Vec::new();
- if node.input.buffer_id != FilterOpGraphPictureBufferId::None {
- inputs.push(node.input.into());
- }
- if node.input2.buffer_id != FilterOpGraphPictureBufferId::None {
- inputs.push(node.input2.into());
- }
- // If the op used by this node is a feMerge, it will add more inputs
- // after this invocation.
- FilterGraphNode{
- linear: node.linear,
- inputs,
- subregion: node.subregion,
- // These are computed later in scene_building
- kept_by_optimizer: true,
- inflate: 0,
- }
- }
-}
-
-/// Here we transform source rect to target rect for SVGFEGraph by walking
-/// the whole graph and propagating subregions based on the provided
-/// invalidation rect, and we want it to be a tight fit so we don't waste
-/// time applying multiple filters to pixels that do not contribute to the
-/// invalidated rect.
-///
-/// The interesting parts of the handling of SVG filters are:
-/// * scene_building.rs : wrap_prim_with_filters
-/// * picture.rs : get_coverage_target_svgfe (you are here)
-/// * picture.rs : get_coverage_source_svgfe
-/// * render_task.rs : new_svg_filter_graph
-/// * render_target.rs : add_svg_filter_node_instances
-pub fn get_coverage_target_svgfe(
- filters: &[(FilterGraphNode, FilterGraphOp)],
- surface_rect: LayoutRect,
-) -> LayoutRect {
-
- // The value of BUFFER_LIMIT here must be the same as in
- // scene_building.rs, or we'll hit asserts here.
- const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
-
- // We need to evaluate the subregions based on the proposed
- // SourceGraphic rect as it isn't known at scene build time.
- let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT];
- for (id, (node, op)) in filters.iter().enumerate() {
- let full_subregion = node.subregion;
- let mut used_subregion = LayoutRect::zero();
- for input in &node.inputs {
- match input.buffer_id {
- FilterOpGraphPictureBufferId::BufferId(id) => {
- assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
- // This id lookup should always succeed.
- let input_subregion = subregion_by_buffer_id[id as usize];
- // Now add the padding that transforms from
- // source to target, this was determined during
- // scene build based on the operation.
- let input_subregion =
- LayoutRect::new(
- LayoutPoint::new(
- input_subregion.min.x + input.target_padding.min.x,
- input_subregion.min.y + input.target_padding.min.y,
- ),
- LayoutPoint::new(
- input_subregion.max.x + input.target_padding.max.x,
- input_subregion.max.y + input.target_padding.max.y,
- ),
- );
- used_subregion = used_subregion
- .union(&input_subregion);
- }
- FilterOpGraphPictureBufferId::None => {
- panic!("Unsupported BufferId type");
- }
- }
- }
- // We can clip the used subregion to the node subregion
- used_subregion = used_subregion
- .intersection(&full_subregion)
- .unwrap_or(LayoutRect::zero());
- match op {
- FilterGraphOp::SVGFEBlendColor => {}
- FilterGraphOp::SVGFEBlendColorBurn => {}
- FilterGraphOp::SVGFEBlendColorDodge => {}
- FilterGraphOp::SVGFEBlendDarken => {}
- FilterGraphOp::SVGFEBlendDifference => {}
- FilterGraphOp::SVGFEBlendExclusion => {}
- FilterGraphOp::SVGFEBlendHardLight => {}
- FilterGraphOp::SVGFEBlendHue => {}
- FilterGraphOp::SVGFEBlendLighten => {}
- FilterGraphOp::SVGFEBlendLuminosity => {}
- FilterGraphOp::SVGFEBlendMultiply => {}
- FilterGraphOp::SVGFEBlendNormal => {}
- FilterGraphOp::SVGFEBlendOverlay => {}
- FilterGraphOp::SVGFEBlendSaturation => {}
- FilterGraphOp::SVGFEBlendScreen => {}
- FilterGraphOp::SVGFEBlendSoftLight => {}
- FilterGraphOp::SVGFEColorMatrix { values } => {
- if values[19] > 0.0 {
- // Manipulating alpha offset can easily create new
- // pixels outside of input subregions
- used_subregion = full_subregion;
- add_text_marker(
- "SVGFEColorMatrix",
- "SVGFEColorMatrix with non-zero alpha offset, using full subregion",
- Duration::from_millis(1));
- }
- }
- FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
- FilterGraphOp::SVGFEComponentTransferInterned{handle: _, creates_pixels} => {
- // Check if the value of alpha[0] is modified, if so
- // the whole subregion is used because it will be
- // creating new pixels outside of input subregions
- if *creates_pixels {
- used_subregion = full_subregion;
- add_text_marker(
- "SVGFEComponentTransfer",
- "SVGFEComponentTransfer with non-zero minimum alpha, using full subregion",
- Duration::from_millis(1));
- }
- }
- FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => {
- // Optimization opportunity - some inputs may be
- // smaller subregions due to the way the math works,
- // k1 is the intersection of the two inputs, k2 is
- // the first input only, k3 is the second input
- // only, and k4 changes the whole subregion.
- //
- // See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC
- // in FilterSupport.cpp
- //
- // We can at least ignore the entire node if
- // everything is zero.
- if *k1 <= 0.0 &&
- *k2 <= 0.0 &&
- *k3 <= 0.0 {
- used_subregion = LayoutRect::zero();
- }
- // Check if alpha is added to pixels as it means it
- // can fill pixels outside input subregions
- if *k4 > 0.0 {
- used_subregion = full_subregion;
- add_text_marker(
- "SVGFECompositeArithmetic",
- "SVGFECompositeArithmetic with non-zero offset, using full subregion",
- Duration::from_millis(1));
- }
- }
- FilterGraphOp::SVGFECompositeATop => {}
- FilterGraphOp::SVGFECompositeIn => {}
- FilterGraphOp::SVGFECompositeLighter => {}
- FilterGraphOp::SVGFECompositeOut => {}
- FilterGraphOp::SVGFECompositeOver => {}
- FilterGraphOp::SVGFECompositeXOR => {}
- FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {}
- FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {}
- FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {}
- FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {}
- FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {}
- FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {}
- FilterGraphOp::SVGFEDisplacementMap{..} => {}
- FilterGraphOp::SVGFEDropShadow{..} => {}
- FilterGraphOp::SVGFEFlood { color } => {
- // Subregion needs to be set to the full node
- // subregion for fills (unless the fill is a no-op)
- if color.a > 0.0 {
- used_subregion = full_subregion;
- }
- }
- FilterGraphOp::SVGFEGaussianBlur{..} => {}
- FilterGraphOp::SVGFEIdentity => {}
- FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => {
- // TODO: calculate the actual subregion
- used_subregion = full_subregion;
- }
- FilterGraphOp::SVGFEMorphologyDilate{..} => {}
- FilterGraphOp::SVGFEMorphologyErode{..} => {}
- FilterGraphOp::SVGFEOpacity { valuebinding: _valuebinding, value } => {
- // If fully transparent, we can ignore this node
- if *value <= 0.0 {
- used_subregion = LayoutRect::zero();
- }
- }
- FilterGraphOp::SVGFESourceAlpha |
- FilterGraphOp::SVGFESourceGraphic => {
- used_subregion = surface_rect;
- }
- FilterGraphOp::SVGFESpecularLightingDistant{..} => {}
- FilterGraphOp::SVGFESpecularLightingPoint{..} => {}
- FilterGraphOp::SVGFESpecularLightingSpot{..} => {}
- FilterGraphOp::SVGFETile => {
- // feTile fills the entire output with
- // source pixels, so it's effectively a flood.
- used_subregion = full_subregion;
- }
- FilterGraphOp::SVGFEToAlpha => {}
- FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
- FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
- FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
- FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
- // Turbulence produces pixel values throughout the
- // node subregion.
- used_subregion = full_subregion;
- }
- }
- // Store the subregion so later nodes can refer back
- // to this and propagate rects properly
- assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
- subregion_by_buffer_id[id] = used_subregion;
- }
- subregion_by_buffer_id[filters.len() - 1]
-}
-
-/// Here we transform target rect to source rect for SVGFEGraph by walking
-/// the whole graph and propagating subregions based on the provided
-/// invalidation rect, and we want it to be a tight fit so we don't waste
-/// time applying multiple filters to pixels that do not contribute to the
-/// invalidated rect.
-///
-/// The interesting parts of the handling of SVG filters are:
-/// * scene_building.rs : wrap_prim_with_filters
-/// * picture.rs : get_coverage_target_svgfe
-/// * picture.rs : get_coverage_source_svgfe (you are here)
-/// * render_task.rs : new_svg_filter_graph
-/// * render_target.rs : add_svg_filter_node_instances
-pub fn get_coverage_source_svgfe(
- filters: &[(FilterGraphNode, FilterGraphOp)],
- surface_rect: LayoutRect,
-) -> LayoutRect {
-
- // The value of BUFFER_LIMIT here must be the same as in
- // scene_building.rs, or we'll hit asserts here.
- const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
-
- // We're solving the source rect from target rect (e.g. due
- // to invalidation of a region, we need to know how much of
- // SourceGraphic is needed to draw that region accurately),
- // so we need to walk the DAG in reverse and accumulate the source
- // subregion for each input onto the referenced node, which can then
- // propagate that to its inputs when it is iterated.
- let mut source_subregion = LayoutRect::zero();
- let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] =
- [LayoutRect::zero(); BUFFER_LIMIT];
- let final_buffer_id = filters.len() - 1;
- assert!(final_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
- subregion_by_buffer_id[final_buffer_id] = surface_rect;
- for (node_buffer_id, (node, op)) in filters.iter().enumerate().rev() {
- // This is the subregion this node outputs, we can clip
- // the inputs based on source_padding relative to this,
- // and accumulate a new subregion for them.
- assert!(node_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
- let full_subregion = node.subregion;
- let mut used_subregion =
- subregion_by_buffer_id[node_buffer_id];
- // We can clip the propagated subregion to the node subregion before
- // we add source_padding for each input and propogate to them
- used_subregion = used_subregion
- .intersection(&full_subregion)
- .unwrap_or(LayoutRect::zero());
- if !used_subregion.is_empty() {
- for input in &node.inputs {
- let input_subregion = LayoutRect::new(
- LayoutPoint::new(
- used_subregion.min.x + input.source_padding.min.x,
- used_subregion.min.y + input.source_padding.min.y,
- ),
- LayoutPoint::new(
- used_subregion.max.x + input.source_padding.max.x,
- used_subregion.max.y + input.source_padding.max.y,
- ),
- );
- match input.buffer_id {
- FilterOpGraphPictureBufferId::BufferId(id) => {
- // Add the used area to the input, later when
- // the referneced node is iterated as a node it
- // will propagate the used bounds.
- subregion_by_buffer_id[id as usize] =
- subregion_by_buffer_id[id as usize]
- .union(&input_subregion);
- }
- FilterOpGraphPictureBufferId::None => {}
- }
- }
- }
- // If this is the SourceGraphic or SourceAlpha, we now have the
- // source subregion we're looking for. If both exist in the
- // same graph, we need to combine them, so don't merely replace.
- match op {
- FilterGraphOp::SVGFESourceAlpha |
- FilterGraphOp::SVGFESourceGraphic => {
- source_subregion = source_subregion.union(&used_subregion);
- }
- _ => {}
- }
- }
-
- // Note that this can be zero if SourceGraphic/SourceAlpha is not used
- // in this graph.
- source_subregion
-}
diff --git a/gfx/wr/webrender/src/tile_cache.rs b/gfx/wr/webrender/src/tile_cache.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, 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
@@ -1,3509 +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/. */
-
-//! 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::{AlphaType, BorderRadius, ClipMode, ColorF, ColorDepth, DebugFlags, ImageKey, ImageRendering};
-use api::{PropertyBindingId, PrimitiveFlags, YuvFormat, YuvRangedColorSpace};
-use api::units::*;
-use crate::clip::{ClipNodeId, ClipLeafId, ClipItemKind, ClipSpaceConversion, ClipChainInstance, ClipStore};
-use crate::composite::{CompositorKind, CompositeState, CompositorSurfaceKind, ExternalSurfaceDescriptor};
-use crate::composite::{ExternalSurfaceDependency, NativeSurfaceId, NativeTileId};
-use crate::composite::{CompositorClipIndex, CompositorTransformIndex};
-use crate::composite::{CompositeTileDescriptor, CompositeTile};
-use crate::gpu_types::ZBufferId;
-use crate::intern::ItemUid;
-use crate::internal_types::{FastHashMap, FrameId, Filter};
-use crate::invalidation::{InvalidationReason, DirtyRegion, PrimitiveCompareResult, quadtree::TileNode};
-use crate::invalidation::dependency::{PrimitiveComparer, PrimitiveDependency, ImageDependency};
-use crate::invalidation::dependency::{SpatialNodeComparer, PrimitiveComparisonKey};
-use crate::invalidation::dependency::{OpacityBindingInfo, ColorBindingInfo, OpacityBinding, ColorBinding};
-use crate::picture::{SurfaceTextureDescriptor, PictureCompositeMode, SurfaceIndex, clamp, clampf};
-use crate::picture::{get_relative_scale_offset, PicturePrimitive};
-use crate::picture::MAX_COMPOSITOR_SURFACES_SIZE;
-use crate::prim_store::{PrimitiveInstance, PrimitiveInstanceKind, PrimitiveScratchBuffer, PictureIndex};
-use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveTemplateKind};
-use crate::print_tree::{PrintTreePrinter, PrintTree};
-use crate::{profiler, render_backend::DataStores};
-use crate::profiler::TransactionProfile;
-use crate::renderer::GpuBufferBuilderF;
-use crate::resource_cache::{ResourceCache, ImageRequest};
-use crate::scene_building::SliceFlags;
-use crate::space::SpaceMapper;
-use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
-use crate::surface::{SubpixelMode, SurfaceInfo};
-use crate::util::{ScaleOffset, MatrixHelpers, MaxRect};
-use crate::visibility::{FrameVisibilityContext, FrameVisibilityState, VisibilityState, PrimitiveVisibilityFlags};
-use euclid::approxeq::ApproxEq;
-use euclid::Box2D;
-use peek_poke::{PeekPoke, poke_into_vec, ensure_red_zone};
-use smallvec::SmallVec;
-use std::fmt::{Display, Error, Formatter};
-use std::{marker, mem, 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();
- }
-}
-
-
-/// The key that identifies a tile cache instance. For now, it's simple the index of
-/// the slice as it was created during scene building.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct SliceId(usize);
-
-impl SliceId {
- pub fn new(index: usize) -> Self {
- SliceId(index)
- }
-}
-
-/// Information that is required to reuse or create a new tile cache. Created
-/// during scene building and passed to the render backend / frame builder.
-pub struct TileCacheParams {
- // The current debug flags for the system.
- pub debug_flags: DebugFlags,
- // Index of the slice (also effectively the key of the tile cache, though we use SliceId where that matters)
- pub slice: usize,
- // Flags describing content of this cache (e.g. scrollbars)
- pub slice_flags: SliceFlags,
- // The anchoring spatial node / scroll root
- pub spatial_node_index: SpatialNodeIndex,
- // The space in which visibility/invalidation/clipping computations are done.
- pub visibility_node_index: SpatialNodeIndex,
- // Optional background color of this tilecache. If present, can be used as an optimization
- // to enable opaque blending and/or subpixel AA in more places.
- pub background_color: Option<ColorF>,
- // Node in the clip-tree that defines where we exclude clips from child prims
- pub shared_clip_node_id: ClipNodeId,
- // Clip leaf that is used to build the clip-chain for this tile cache.
- pub shared_clip_leaf_id: Option<ClipLeafId>,
- // Virtual surface sizes are always square, so this represents both the width and height
- pub virtual_surface_size: i32,
- // The number of Image surfaces that are being requested for this tile cache.
- // This is only a suggestion - the tile cache will clamp this as a reasonable number
- // and only promote a limited number of surfaces.
- pub image_surface_count: usize,
- // The number of YuvImage surfaces that are being requested for this tile cache.
- // This is only a suggestion - the tile cache will clamp this as a reasonable number
- // and only promote a limited number of surfaces.
- pub yuv_image_surface_count: usize,
-}
-
-/// The backing surface for this tile.
-#[derive(Debug)]
-pub enum TileSurface {
- Texture {
- /// Descriptor for the surface that this tile draws into.
- descriptor: SurfaceTextureDescriptor,
- },
- Color {
- color: ColorF,
- },
-}
-
-impl TileSurface {
- pub fn kind(&self) -> &'static str {
- match *self {
- TileSurface::Color { .. } => "Color",
- TileSurface::Texture { .. } => "Texture",
- }
- }
-}
-
-/// Information about a cached tile.
-pub struct Tile {
- /// The grid position of this tile within the picture cache
- pub tile_offset: TileOffset,
- /// The current world rect of this tile.
- pub world_tile_rect: WorldRect,
- /// The current local rect of this tile.
- pub local_tile_rect: PictureRect,
- /// The picture space dirty rect for this tile.
- pub local_dirty_rect: PictureRect,
- /// The device space dirty rect for this tile.
- /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
- /// expose these as multiple dirty rects, which will help in some cases.
- pub device_dirty_rect: DeviceRect,
- /// World space rect that contains valid pixels region of this tile.
- pub world_valid_rect: WorldRect,
- /// Device space rect that contains valid pixels region of this tile.
- pub device_valid_rect: DeviceRect,
- /// Uniquely describes the content of this tile, in a way that can be
- /// (reasonably) efficiently hashed and compared.
- pub current_descriptor: TileDescriptor,
- /// The content descriptor for this tile from the previous frame.
- pub prev_descriptor: TileDescriptor,
- /// Handle to the backing surface for this tile.
- pub surface: Option<TileSurface>,
- /// If true, this tile is marked valid, and the existing texture
- /// cache handle can be used. Tiles are invalidated during the
- /// build_dirty_regions method.
- pub is_valid: bool,
- /// If true, this tile intersects with the currently visible screen
- /// rect, and will be drawn.
- pub is_visible: bool,
- /// The tile id is stable between display lists and / or frames,
- /// if the tile is retained. Useful for debugging tile evictions.
- pub id: TileId,
- /// If true, the tile was determined to be opaque, which means blending
- /// can be disabled when drawing it.
- pub is_opaque: bool,
- /// Root node of the quadtree dirty rect tracker.
- pub root: TileNode,
- /// The last rendered background color on this tile.
- background_color: Option<ColorF>,
- /// The first reason the tile was invalidated this frame.
- invalidation_reason: Option<InvalidationReason>,
- /// The local space valid rect for all primitives that affect this tile.
- pub local_valid_rect: PictureBox2D,
- /// z-buffer id for this tile
- pub z_id: ZBufferId,
- pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>,
-}
-
-impl Tile {
- /// Construct a new, invalid tile.
- fn new(tile_offset: TileOffset) -> Self {
- let id = TileId(crate::tile_cache::next_tile_id());
-
- Tile {
- tile_offset,
- local_tile_rect: PictureRect::zero(),
- world_tile_rect: WorldRect::zero(),
- world_valid_rect: WorldRect::zero(),
- device_valid_rect: DeviceRect::zero(),
- local_dirty_rect: PictureRect::zero(),
- device_dirty_rect: DeviceRect::zero(),
- surface: None,
- current_descriptor: TileDescriptor::new(),
- prev_descriptor: TileDescriptor::new(),
- is_valid: false,
- is_visible: false,
- id,
- is_opaque: false,
- root: TileNode::new_leaf(Vec::new()),
- background_color: None,
- invalidation_reason: None,
- local_valid_rect: PictureBox2D::zero(),
- z_id: ZBufferId::invalid(),
- sub_graphs: Vec::new(),
- }
- }
-
- /// Print debug information about this tile to a tree printer.
- fn print(&self, pt: &mut dyn PrintTreePrinter) {
- pt.new_level(format!("Tile {:?}", self.id));
- pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect));
- pt.add_item(format!("background_color: {:?}", self.background_color));
- pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
- self.current_descriptor.print(pt);
- pt.end_level();
- }
-
- /// Check if the content of the previous and current tile descriptors match
- fn update_dirty_rects(
- &mut self,
- ctx: &TileUpdateDirtyContext,
- state: &mut TileUpdateDirtyState,
- invalidation_reason: &mut Option<InvalidationReason>,
- frame_context: &FrameVisibilityContext,
- ) -> PictureRect {
- let mut prim_comparer = PrimitiveComparer::new(
- &self.prev_descriptor,
- &self.current_descriptor,
- state.resource_cache,
- state.spatial_node_comparer,
- ctx.opacity_bindings,
- ctx.color_bindings,
- );
-
- let mut dirty_rect = PictureBox2D::zero();
- self.root.update_dirty_rects(
- &self.prev_descriptor.prims,
- &self.current_descriptor.prims,
- &mut prim_comparer,
- &mut dirty_rect,
- state.compare_cache,
- invalidation_reason,
- frame_context,
- );
-
- dirty_rect
- }
-
- /// Invalidate a tile based on change in content. This
- /// must be called even if the tile is not currently
- /// visible on screen. We might be able to improve this
- /// later by changing how ComparableVec is used.
- fn update_content_validity(
- &mut self,
- ctx: &TileUpdateDirtyContext,
- state: &mut TileUpdateDirtyState,
- frame_context: &FrameVisibilityContext,
- ) {
- // Check if the contents of the primitives, clips, and
- // other dependencies are the same.
- state.compare_cache.clear();
- let mut invalidation_reason = None;
- let dirty_rect = self.update_dirty_rects(
- ctx,
- state,
- &mut invalidation_reason,
- frame_context,
- );
- if !dirty_rect.is_empty() {
- self.invalidate(
- Some(dirty_rect),
- invalidation_reason.expect("bug: no invalidation_reason"),
- );
- }
- if ctx.invalidate_all {
- self.invalidate(None, InvalidationReason::ScaleChanged);
- }
- // TODO(gw): We can avoid invalidating the whole tile in some cases here,
- // but it should be a fairly rare invalidation case.
- if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
- self.invalidate(None, InvalidationReason::ValidRectChanged);
- state.composite_state.dirty_rects_are_valid = false;
- }
- }
-
- /// Invalidate this tile. If `invalidation_rect` is None, the entire
- /// tile is invalidated.
- pub fn invalidate(
- &mut self,
- invalidation_rect: Option<PictureRect>,
- reason: InvalidationReason,
- ) {
- self.is_valid = false;
-
- match invalidation_rect {
- Some(rect) => {
- self.local_dirty_rect = self.local_dirty_rect.union(&rect);
- }
- None => {
- self.local_dirty_rect = self.local_tile_rect;
- }
- }
-
- if self.invalidation_reason.is_none() {
- self.invalidation_reason = Some(reason);
- }
- }
-
- /// Called during pre_update of a tile cache instance. Allows the
- /// tile to setup state before primitive dependency calculations.
- fn pre_update(
- &mut self,
- ctx: &TilePreUpdateContext,
- ) {
- self.local_tile_rect = PictureRect::new(
- PicturePoint::new(
- self.tile_offset.x as f32 * ctx.tile_size.width,
- self.tile_offset.y as f32 * ctx.tile_size.height,
- ),
- PicturePoint::new(
- (self.tile_offset.x + 1) as f32 * ctx.tile_size.width,
- (self.tile_offset.y + 1) as f32 * ctx.tile_size.height,
- ),
- );
- // TODO(gw): This is a hack / fix for Box2D::union in euclid not working with
- // zero sized rect accumulation. Once that lands, we'll revert this
- // to be zero.
- self.local_valid_rect = PictureBox2D::new(
- PicturePoint::new( 1.0e32, 1.0e32),
- PicturePoint::new(-1.0e32, -1.0e32),
- );
- self.invalidation_reason = None;
- self.sub_graphs.clear();
-
- self.world_tile_rect = ctx.pic_to_world_mapper
- .map(&self.local_tile_rect)
- .expect("bug: map local tile rect");
-
- // Check if this tile is currently on screen.
- self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
-
- // If the tile isn't visible, early exit, skipping the normal set up to
- // validate dependencies. Instead, we will only compare the current tile
- // dependencies the next time it comes into view.
- if !self.is_visible {
- return;
- }
-
- if ctx.background_color != self.background_color {
- self.invalidate(None, InvalidationReason::BackgroundColor);
- self.background_color = ctx.background_color;
- }
-
- // Clear any dependencies so that when we rebuild them we
- // can compare if the tile has the same content.
- mem::swap(
- &mut self.current_descriptor,
- &mut self.prev_descriptor,
- );
- self.current_descriptor.clear();
- self.root.clear(self.local_tile_rect);
-
- // Since this tile is determined to be visible, it will get updated
- // dependencies, so update the frame id we are storing dependencies for.
- self.current_descriptor.last_updated_frame_id = ctx.frame_id;
- }
-
- /// Add dependencies for a given primitive to this tile.
- fn add_prim_dependency(
- &mut self,
- info: &PrimitiveDependencyInfo,
- ) {
- // If this tile isn't currently visible, we don't want to update the dependencies
- // for this tile, as an optimization, since it won't be drawn anyway.
- if !self.is_visible {
- return;
- }
-
- // Incorporate the bounding rect of the primitive in the local valid rect
- // for this tile. This is used to minimize the size of the scissor rect
- // during rasterization and the draw rect during composition of partial tiles.
- self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
-
- // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port,
- // which can cause invalidations when a new display list with changed
- // display port is received. To work around this, clamp the prim clip rect
- // to the tile boundaries - if the clip hasn't affected the tile, then the
- // changed clip can't affect the content of the primitive on this tile.
- // In future, we could consider supplying the display port clip from Gecko
- // in a different way (e.g. as a scroll frame clip) which still provides
- // the desired clip for checkerboarding, but doesn't require this extra
- // work below.
-
- // TODO(gw): This is a hot part of the code - we could probably optimize further by:
- // - Using min/max instead of clamps below (if we guarantee the rects are well formed)
-
- let tile_p0 = self.local_tile_rect.min;
- let tile_p1 = self.local_tile_rect.max;
-
- let prim_clip_box = PictureBox2D::new(
- PicturePoint::new(
- clampf(info.prim_clip_box.min.x, tile_p0.x, tile_p1.x),
- clampf(info.prim_clip_box.min.y, tile_p0.y, tile_p1.y),
- ),
- PicturePoint::new(
- clampf(info.prim_clip_box.max.x, tile_p0.x, tile_p1.x),
- clampf(info.prim_clip_box.max.y, tile_p0.y, tile_p1.y),
- ),
- );
-
- // Update the tile descriptor, used for tile comparison during scene swaps.
- let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
-
- // Encode the deps for this primitive in the `dep_data` byte buffer
- let dep_offset = self.current_descriptor.dep_data.len() as u32;
- let mut dep_count = 0;
-
- for clip in &info.clips {
- dep_count += 1;
- poke_into_vec(
- &PrimitiveDependency::Clip {
- clip: *clip,
- },
- &mut self.current_descriptor.dep_data,
- );
- }
-
- for spatial_node_index in &info.spatial_nodes {
- dep_count += 1;
- poke_into_vec(
- &PrimitiveDependency::SpatialNode {
- index: *spatial_node_index,
- },
- &mut self.current_descriptor.dep_data,
- );
- }
-
- for image in &info.images {
- dep_count += 1;
- poke_into_vec(
- &PrimitiveDependency::Image {
- image: *image,
- },
- &mut self.current_descriptor.dep_data,
- );
- }
-
- for binding in &info.opacity_bindings {
- dep_count += 1;
- poke_into_vec(
- &PrimitiveDependency::OpacityBinding {
- binding: *binding,
- },
- &mut self.current_descriptor.dep_data,
- );
- }
-
- if let Some(ref binding) = info.color_binding {
- dep_count += 1;
- poke_into_vec(
- &PrimitiveDependency::ColorBinding {
- binding: *binding,
- },
- &mut self.current_descriptor.dep_data,
- );
- }
-
- self.current_descriptor.prims.push(PrimitiveDescriptor {
- prim_uid: info.prim_uid,
- prim_clip_box,
- dep_offset,
- dep_count,
- });
-
- // Add this primitive to the dirty rect quadtree.
- self.root.add_prim(prim_index, &info.prim_clip_box);
- }
-
- /// Called during tile cache instance post_update. Allows invalidation and dirty
- /// rect calculation after primitive dependencies have been updated.
- fn update_dirty_and_valid_rects(
- &mut self,
- ctx: &TileUpdateDirtyContext,
- state: &mut TileUpdateDirtyState,
- frame_context: &FrameVisibilityContext,
- ) {
- // Ensure peek-poke constraint is met, that `dep_data` is large enough
- ensure_red_zone::<PrimitiveDependency>(&mut self.current_descriptor.dep_data);
-
- // Register the frame id of this tile with the spatial node comparer, to ensure
- // that it doesn't GC any spatial nodes from the comparer that are referenced
- // by this tile. Must be done before we early exit below, so that we retain
- // spatial node info even for tiles that are currently not visible.
- state.spatial_node_comparer.retain_for_frame(self.current_descriptor.last_updated_frame_id);
-
- // If tile is not visible, just early out from here - we don't update dependencies
- // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
- // (and thus updated / invalidated) until it is on screen again.
- if !self.is_visible {
- return;
- }
-
- // Calculate the overall valid rect for this tile.
- self.current_descriptor.local_valid_rect = self.local_valid_rect;
-
- // TODO(gw): In theory, the local tile rect should always have an
- // intersection with the overall picture rect. In practice,
- // due to some accuracy issues with how fract_offset (and
- // fp accuracy) are used in the calling method, this isn't
- // always true. In this case, it's safe to set the local
- // valid rect to zero, which means it will be clipped out
- // and not affect the scene. In future, we should fix the
- // accuracy issue above, so that this assumption holds, but
- // it shouldn't have any noticeable effect on performance
- // or memory usage (textures should never get allocated).
- self.current_descriptor.local_valid_rect = self.local_tile_rect
- .intersection(&ctx.local_rect)
- .and_then(|r| r.intersection(&self.current_descriptor.local_valid_rect))
- .unwrap_or_else(PictureRect::zero);
-
- // The device_valid_rect is referenced during `update_content_validity` so it
- // must be updated here first.
- self.world_valid_rect = ctx.pic_to_world_mapper
- .map(&self.current_descriptor.local_valid_rect)
- .expect("bug: map local valid rect");
-
- // The device rect is guaranteed to be aligned on a device pixel - the round
- // is just to deal with float accuracy. However, the valid rect is not
- // always aligned to a device pixel. To handle this, round out to get all
- // required pixels, and intersect with the tile device rect.
- let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round();
- self.device_valid_rect = (self.world_valid_rect * ctx.global_device_pixel_scale)
- .round_out()
- .intersection(&device_rect)
- .unwrap_or_else(DeviceRect::zero);
-
- // Invalidate the tile based on the content changing.
- self.update_content_validity(ctx, state, frame_context);
- }
-
- /// Called during tile cache instance post_update. Allows invalidation and dirty
- /// rect calculation after primitive dependencies have been updated.
- fn post_update(
- &mut self,
- ctx: &TilePostUpdateContext,
- state: &mut TilePostUpdateState,
- frame_context: &FrameVisibilityContext,
- ) {
- // If tile is not visible, just early out from here - we don't update dependencies
- // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
- // (and thus updated / invalidated) until it is on screen again.
- if !self.is_visible {
- return;
- }
-
- // If there are no primitives there is no need to draw or cache it.
- // Bug 1719232 - The final device valid rect does not always describe a non-empty
- // region. Cull the tile as a workaround.
- if self.current_descriptor.prims.is_empty() || self.device_valid_rect.is_empty() {
- // If there is a native compositor surface allocated for this (now empty) tile
- // it must be freed here, otherwise the stale tile with previous contents will
- // be composited. If the tile subsequently gets new primitives added to it, the
- // surface will be re-allocated when it's added to the composite draw list.
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() {
- if let Some(id) = id.take() {
- state.resource_cache.destroy_compositor_tile(id);
- }
- }
-
- self.is_visible = false;
- return;
- }
-
- // Check if this tile can be considered opaque. Opacity state must be updated only
- // after all early out checks have been performed. Otherwise, we might miss updating
- // the native surface next time this tile becomes visible.
- let clipped_rect = self.current_descriptor.local_valid_rect
- .intersection(&ctx.local_clip_rect)
- .unwrap_or_else(PictureRect::zero);
-
- let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
- let has_opaque_backdrop = ctx.backdrop.map_or(false, |b| b.opaque_rect.contains_box(&clipped_rect));
- let mut is_opaque = has_opaque_bg_color || has_opaque_backdrop;
-
- // If this tile intersects with any underlay surfaces, we need to consider it
- // translucent, since it will contain an alpha cutout
- for underlay in ctx.underlays {
- if clipped_rect.intersects(&underlay.local_rect) {
- is_opaque = false;
- break;
- }
- }
-
- // Set the correct z_id for this tile
- self.z_id = ctx.z_id;
-
- if is_opaque != self.is_opaque {
- // If opacity changed, the native compositor surface and all tiles get invalidated.
- // (this does nothing if not using native compositor mode).
- // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
- // everything in this case. If it turns out that this isn't true, we could
- // consider other options, such as per-tile opacity (natively supported
- // on CoreAnimation, and supported if backed by non-virtual surfaces in
- // DirectComposition).
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface {
- if let Some(id) = id.take() {
- state.resource_cache.destroy_compositor_tile(id);
- }
- }
-
- // Invalidate the entire tile to force a redraw.
- self.invalidate(None, InvalidationReason::SurfaceOpacityChanged);
- self.is_opaque = is_opaque;
- }
-
- // Check if the selected composite mode supports dirty rect updates. For Draw composite
- // mode, we can always update the content with smaller dirty rects, unless there is a
- // driver bug to workaround. For native composite mode, we can only use dirty rects if
- // the compositor supports partial surface updates.
- let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
- CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
- (frame_context.config.gpu_supports_render_target_partial_update, true)
- }
- CompositorKind::Native { capabilities, .. } => {
- (capabilities.max_update_rects > 0, false)
- }
- };
-
- // TODO(gw): Consider using smaller tiles and/or tile splits for
- // native compositors that don't support dirty rects.
- if supports_dirty_rects {
- // Only allow splitting for normal content sized tiles
- if ctx.current_tile_size == state.resource_cache.picture_textures.default_tile_size() {
- let max_split_level = 3;
-
- // Consider splitting / merging dirty regions
- self.root.maybe_merge_or_split(
- 0,
- &self.current_descriptor.prims,
- max_split_level,
- );
- }
- }
-
- // The dirty rect will be set correctly by now. If the underlying platform
- // doesn't support partial updates, and this tile isn't valid, force the dirty
- // rect to be the size of the entire tile.
- if !self.is_valid && !supports_dirty_rects {
- self.local_dirty_rect = self.local_tile_rect;
- }
-
- // See if this tile is a simple color, in which case we can just draw
- // it as a rect, and avoid allocating a texture surface and drawing it.
- // TODO(gw): Initial native compositor interface doesn't support simple
- // color tiles. We can definitely support this in DC, so this
- // should be added as a follow up.
- let is_simple_prim =
- ctx.backdrop.map_or(false, |b| b.kind.is_some()) &&
- self.current_descriptor.prims.len() == 1 &&
- self.is_opaque &&
- supports_simple_prims;
-
- // Set up the backing surface for this tile.
- let surface = if is_simple_prim {
- // If we determine the tile can be represented by a color, set the
- // surface unconditionally (this will drop any previously used
- // texture cache backing surface).
- match ctx.backdrop.unwrap().kind {
- Some(BackdropKind::Color { color }) => {
- TileSurface::Color {
- color,
- }
- }
- None => {
- // This should be prevented by the is_simple_prim check above.
- unreachable!();
- }
- }
- } else {
- // If this tile will be backed by a surface, we want to retain
- // the texture handle from the previous frame, if possible. If
- // the tile was previously a color, or not set, then just set
- // up a new texture cache handle.
- match self.surface.take() {
- Some(TileSurface::Texture { descriptor }) => {
- // Reuse the existing descriptor and vis mask
- TileSurface::Texture {
- descriptor,
- }
- }
- Some(TileSurface::Color { .. }) | None => {
- // This is the case where we are constructing a tile surface that
- // involves drawing to a texture. Create the correct surface
- // descriptor depending on the compositing mode that will read
- // the output.
- let descriptor = match state.composite_state.compositor_kind {
- CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
- // For a texture cache entry, create an invalid handle that
- // will be allocated when update_picture_cache is called.
- SurfaceTextureDescriptor::TextureCache {
- handle: None,
- }
- }
- CompositorKind::Native { .. } => {
- // Create a native surface surface descriptor, but don't allocate
- // a surface yet. The surface is allocated *after* occlusion
- // culling occurs, so that only visible tiles allocate GPU memory.
- SurfaceTextureDescriptor::Native {
- id: None,
- }
- }
- };
-
- TileSurface::Texture {
- descriptor,
- }
- }
- }
- };
-
- // Store the current surface backing info for use during batching.
- self.surface = Some(surface);
- }
-}
-
-// TODO(gw): Tidy this up by:
-// - Add an Other variant for things like opaque gradient backdrops
-#[derive(Debug, Copy, Clone)]
-pub enum BackdropKind {
- Color {
- color: ColorF,
- },
-}
-
-/// Stores information about the calculated opaque backdrop of this slice.
-#[derive(Debug, Copy, Clone)]
-pub struct BackdropInfo {
- /// The picture space rectangle that is known to be opaque. This is used
- /// to determine where subpixel AA can be used, and where alpha blending
- /// can be disabled.
- pub opaque_rect: PictureRect,
- /// If the backdrop covers the entire slice with an opaque color, this
- /// will be set and can be used as a clear color for the slice's tiles.
- pub spanning_opaque_color: Option<ColorF>,
- /// Kind of the backdrop
- pub kind: Option<BackdropKind>,
- /// The picture space rectangle of the backdrop, if kind is set.
- pub backdrop_rect: PictureRect,
-}
-
-impl BackdropInfo {
- fn empty() -> Self {
- BackdropInfo {
- opaque_rect: PictureRect::zero(),
- spanning_opaque_color: None,
- kind: None,
- backdrop_rect: PictureRect::zero(),
- }
- }
-}
-
-/// Represents the native surfaces created for a picture cache, if using
-/// a native compositor. An opaque and alpha surface is always created,
-/// but tiles are added to a surface based on current opacity. If the
-/// calculated opacity of a tile changes, the tile is invalidated and
-/// attached to a different native surface. This means that we don't
-/// need to invalidate the entire surface if only some tiles are changing
-/// opacity. It also means we can take advantage of opaque tiles on cache
-/// slices where only some of the tiles are opaque. There is an assumption
-/// that creating a native surface is cheap, and only when a tile is added
-/// to a surface is there a significant cost. This assumption holds true
-/// for the current native compositor implementations on Windows and Mac.
-pub struct NativeSurface {
- /// Native surface for opaque tiles
- pub opaque: NativeSurfaceId,
- /// Native surface for alpha tiles
- pub alpha: NativeSurfaceId,
-}
-
-/// Hash key for an external native compositor surface
-#[derive(PartialEq, Eq, Hash)]
-pub struct ExternalNativeSurfaceKey {
- /// The YUV/RGB image keys that are used to draw this surface.
- pub image_keys: [ImageKey; 3],
- /// If this is not an 'external' compositor surface created via
- /// Compositor::create_external_surface, this is set to the
- /// current device size of the surface.
- pub size: Option<DeviceIntSize>,
-}
-
-/// Information about a native compositor surface cached between frames.
-pub struct ExternalNativeSurface {
- /// If true, the surface was used this frame. Used for a simple form
- /// of GC to remove old surfaces.
- pub used_this_frame: bool,
- /// The native compositor surface handle
- pub native_surface_id: NativeSurfaceId,
- /// List of image keys, and current image generations, that are drawn in this surface.
- /// The image generations are used to check if the compositor surface is dirty and
- /// needs to be updated.
- pub image_dependencies: [ImageDependency; 3],
-}
-
-/// Wrapper struct around an external surface descriptor with a little more information
-/// that the picture caching code needs.
-pub struct CompositorSurface {
- // External surface descriptor used by compositing logic
- pub descriptor: ExternalSurfaceDescriptor,
- // The compositor surface rect + any intersecting prims. Later prims that intersect
- // with this must be added to the next sub-slice.
- prohibited_rect: PictureRect,
- // If the compositor surface content is opaque.
- pub is_opaque: bool,
-}
-
-pub struct BackdropSurface {
- pub id: NativeSurfaceId,
- pub color: ColorF,
- pub device_rect: DeviceRect,
-}
-
-/// In some cases, we need to know the dirty rect of all tiles in order
-/// to correctly invalidate a primitive.
-#[derive(Debug)]
-pub struct DeferredDirtyTest {
- /// The tile rect that the primitive being checked affects
- pub tile_rect: TileRect,
- /// The picture-cache local rect of the primitive being checked
- pub prim_rect: PictureRect,
-}
-
-/// Represents a cache of tiles that make up a picture primitives.
-pub struct TileCacheInstance {
- // The current debug flags for the system.
- pub debug_flags: DebugFlags,
- /// Index of the tile cache / slice for this frame builder. It's determined
- /// by the setup_picture_caching method during flattening, which splits the
- /// picture tree into multiple slices. It's used as a simple input to the tile
- /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed
- /// between display lists - this seems very unlikely to occur on most pages, but
- /// can be revisited if we ever notice that.
- pub slice: usize,
- /// Propagated information about the slice
- pub slice_flags: SliceFlags,
- /// The currently selected tile size to use for this cache
- pub current_tile_size: DeviceIntSize,
- /// The list of sub-slices in this tile cache
- pub sub_slices: Vec<SubSlice>,
- /// The positioning node for this tile cache.
- pub spatial_node_index: SpatialNodeIndex,
- /// The coordinate space to do visibility/clipping/invalidation in.
- pub visibility_node_index: SpatialNodeIndex,
- /// List of opacity bindings, with some extra information
- /// about whether they changed since last frame.
- opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
- /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
- old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
- /// A helper to compare transforms between previous and current frame.
- spatial_node_comparer: SpatialNodeComparer,
- /// List of color bindings, with some extra information
- /// about whether they changed since last frame.
- color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
- /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
- old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
- /// The current dirty region tracker for this picture.
- pub dirty_region: DirtyRegion,
- /// Current size of tiles in picture units.
- tile_size: PictureSize,
- /// Tile coords of the currently allocated grid.
- tile_rect: TileRect,
- /// Pre-calculated versions of the tile_rect above, used to speed up the
- /// calculations in get_tile_coords_for_rect.
- tile_bounds_p0: TileOffset,
- tile_bounds_p1: TileOffset,
- /// Local rect (unclipped) of the picture this cache covers.
- pub local_rect: PictureRect,
- /// The local clip rect, from the shared clips of this picture.
- pub local_clip_rect: PictureRect,
- /// Registered clip in CompositeState for this picture cache
- pub compositor_clip: Option<CompositorClipIndex>,
- /// The screen rect, transformed to local picture space.
- pub screen_rect_in_pic_space: PictureRect,
- /// The surface index that this tile cache will be drawn into.
- surface_index: SurfaceIndex,
- /// The background color from the renderer. If this is set opaque, we know it's
- /// fine to clear the tiles to this and allow subpixel text on the first slice.
- pub background_color: Option<ColorF>,
- /// Information about the calculated backdrop content of this cache.
- pub backdrop: BackdropInfo,
- /// The allowed subpixel mode for this surface, which depends on the detected
- /// opacity of the background.
- pub subpixel_mode: SubpixelMode,
- // Node in the clip-tree that defines where we exclude clips from child prims
- pub shared_clip_node_id: ClipNodeId,
- // Clip leaf that is used to build the clip-chain for this tile cache.
- pub shared_clip_leaf_id: Option<ClipLeafId>,
- /// The number of frames until this cache next evaluates what tile size to use.
- /// If a picture rect size is regularly changing just around a size threshold,
- /// we don't want to constantly invalidate and reallocate different tile size
- /// configuration each frame.
- frames_until_size_eval: usize,
- /// For DirectComposition, virtual surfaces don't support negative coordinates. However,
- /// picture cache tile coordinates can be negative. To handle this, we apply an offset
- /// to each tile in DirectComposition. We want to change this as little as possible,
- /// to avoid invalidating tiles. However, if we have a picture cache tile coordinate
- /// which is outside the virtual surface bounds, we must change this to allow
- /// correct remapping of the coordinates passed to BeginDraw in DC.
- pub virtual_offset: DeviceIntPoint,
- /// keep around the hash map used as compare_cache to avoid reallocating it each
- /// frame.
- compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
- /// The currently considered tile size override. Used to check if we should
- /// re-evaluate tile size, even if the frame timer hasn't expired.
- tile_size_override: Option<DeviceIntSize>,
- /// A cache of compositor surfaces that are retained between frames
- pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
- /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting
- frame_id: FrameId,
- /// Registered transform in CompositeState for this picture cache
- pub transform_index: CompositorTransformIndex,
- /// Current transform mapping local picture space to compositor surface raster space
- local_to_raster: ScaleOffset,
- /// Current transform mapping compositor surface raster space to final device space
- raster_to_device: ScaleOffset,
- /// If true, we need to invalidate all tiles during `post_update`
- invalidate_all_tiles: bool,
- /// The current raster scale for tiles in this cache
- pub current_raster_scale: f32,
- /// Depth of off-screen surfaces that are currently pushed during dependency updates
- current_surface_traversal_depth: usize,
- /// A list of extra dirty invalidation tests that can only be checked once we
- /// know the dirty rect of all tiles
- deferred_dirty_tests: Vec<DeferredDirtyTest>,
- /// Is there a backdrop associated with this cache
- pub found_prims_after_backdrop: bool,
- pub backdrop_surface: Option<BackdropSurface>,
- /// List of underlay compositor surfaces that exist in this picture cache
- pub underlays: Vec<ExternalSurfaceDescriptor>,
- /// "Region" (actually a spanning rect) containing all overlay promoted surfaces
- pub overlay_region: PictureRect,
- /// The number YuvImage prims in this cache, provided in our TileCacheParams.
- pub yuv_images_count: usize,
- /// The remaining number of YuvImage prims we will see this frame. We prioritize
- /// promoting these before promoting any Image prims.
- pub yuv_images_remaining: usize,
-}
-
-impl TileCacheInstance {
- pub fn new(params: TileCacheParams) -> Self {
- // Determine how many sub-slices we need. Clamp to an arbitrary limit to ensure
- // we don't create a huge number of OS compositor tiles and sub-slices.
- let sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1;
-
- let mut sub_slices = Vec::with_capacity(sub_slice_count);
- for _ in 0 .. sub_slice_count {
- sub_slices.push(SubSlice::new());
- }
-
- TileCacheInstance {
- debug_flags: params.debug_flags,
- slice: params.slice,
- slice_flags: params.slice_flags,
- spatial_node_index: params.spatial_node_index,
- visibility_node_index: params.visibility_node_index,
- sub_slices,
- opacity_bindings: FastHashMap::default(),
- old_opacity_bindings: FastHashMap::default(),
- spatial_node_comparer: SpatialNodeComparer::new(),
- color_bindings: FastHashMap::default(),
- old_color_bindings: FastHashMap::default(),
- dirty_region: DirtyRegion::new(params.visibility_node_index, params.spatial_node_index),
- tile_size: PictureSize::zero(),
- tile_rect: TileRect::zero(),
- tile_bounds_p0: TileOffset::zero(),
- tile_bounds_p1: TileOffset::zero(),
- local_rect: PictureRect::zero(),
- local_clip_rect: PictureRect::zero(),
- compositor_clip: None,
- screen_rect_in_pic_space: PictureRect::zero(),
- surface_index: SurfaceIndex(0),
- background_color: params.background_color,
- backdrop: BackdropInfo::empty(),
- subpixel_mode: SubpixelMode::Allow,
- shared_clip_node_id: params.shared_clip_node_id,
- shared_clip_leaf_id: params.shared_clip_leaf_id,
- current_tile_size: DeviceIntSize::zero(),
- frames_until_size_eval: 0,
- // Default to centering the virtual offset in the middle of the DC virtual surface
- virtual_offset: DeviceIntPoint::new(
- params.virtual_surface_size / 2,
- params.virtual_surface_size / 2,
- ),
- compare_cache: FastHashMap::default(),
- tile_size_override: None,
- external_native_surface_cache: FastHashMap::default(),
- frame_id: FrameId::INVALID,
- transform_index: CompositorTransformIndex::INVALID,
- raster_to_device: ScaleOffset::identity(),
- local_to_raster: ScaleOffset::identity(),
- invalidate_all_tiles: true,
- current_raster_scale: 1.0,
- current_surface_traversal_depth: 0,
- deferred_dirty_tests: Vec::new(),
- found_prims_after_backdrop: false,
- backdrop_surface: None,
- underlays: Vec::new(),
- overlay_region: PictureRect::zero(),
- yuv_images_count: params.yuv_image_surface_count,
- yuv_images_remaining: 0,
- }
- }
-
- /// Return the total number of tiles allocated by this tile cache
- pub fn tile_count(&self) -> usize {
- self.tile_rect.area() as usize * self.sub_slices.len()
- }
-
- /// Trims memory held by the tile cache, such as native surfaces.
- pub fn memory_pressure(&mut self, resource_cache: &mut ResourceCache) {
- for sub_slice in &mut self.sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
- // Reseting the id to None with take() ensures that a new
- // tile will be allocated during the next frame build.
- if let Some(id) = id.take() {
- resource_cache.destroy_compositor_tile(id);
- }
- }
- }
- if let Some(native_surface) = sub_slice.native_surface.take() {
- resource_cache.destroy_compositor_surface(native_surface.opaque);
- resource_cache.destroy_compositor_surface(native_surface.alpha);
- }
- }
- }
-
- /// Reset this tile cache with the updated parameters from a new scene
- /// that has arrived. This allows the tile cache to be retained across
- /// new scenes.
- pub fn prepare_for_new_scene(
- &mut self,
- params: TileCacheParams,
- resource_cache: &mut ResourceCache,
- ) {
- // We should only receive updated state for matching slice key
- assert_eq!(self.slice, params.slice);
-
- // Determine how many sub-slices we need, based on how many compositor surface prims are
- // in the supplied primitive list.
- let required_sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1;
-
- if self.sub_slices.len() != required_sub_slice_count {
- self.tile_rect = TileRect::zero();
-
- if self.sub_slices.len() > required_sub_slice_count {
- let old_sub_slices = self.sub_slices.split_off(required_sub_slice_count);
-
- for mut sub_slice in old_sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
- if let Some(id) = id.take() {
- resource_cache.destroy_compositor_tile(id);
- }
- }
- }
-
- if let Some(native_surface) = sub_slice.native_surface {
- resource_cache.destroy_compositor_surface(native_surface.opaque);
- resource_cache.destroy_compositor_surface(native_surface.alpha);
- }
- }
- } else {
- while self.sub_slices.len() < required_sub_slice_count {
- self.sub_slices.push(SubSlice::new());
- }
- }
- }
-
- // Store the parameters from the scene builder for this slice. Other
- // params in the tile cache are retained and reused, or are always
- // updated during pre/post_update.
- self.slice_flags = params.slice_flags;
- self.spatial_node_index = params.spatial_node_index;
- self.background_color = params.background_color;
- self.shared_clip_leaf_id = params.shared_clip_leaf_id;
- self.shared_clip_node_id = params.shared_clip_node_id;
-
- // Since the slice flags may have changed, ensure we re-evaluate the
- // appropriate tile size for this cache next update.
- self.frames_until_size_eval = 0;
-
- // Update the number of YuvImage prims we have in the scene.
- self.yuv_images_count = params.yuv_image_surface_count;
- }
-
- /// Destroy any manually managed resources before this picture cache is
- /// destroyed, such as native compositor surfaces.
- pub fn destroy(
- self,
- resource_cache: &mut ResourceCache,
- ) {
- for sub_slice in self.sub_slices {
- if let Some(native_surface) = sub_slice.native_surface {
- resource_cache.destroy_compositor_surface(native_surface.opaque);
- resource_cache.destroy_compositor_surface(native_surface.alpha);
- }
- }
-
- for (_, external_surface) in self.external_native_surface_cache {
- resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
- }
-
- if let Some(backdrop_surface) = &self.backdrop_surface {
- resource_cache.destroy_compositor_surface(backdrop_surface.id);
- }
- }
-
- /// Get the tile coordinates for a given rectangle.
- fn get_tile_coords_for_rect(
- &self,
- rect: &PictureRect,
- ) -> (TileOffset, TileOffset) {
- // Get the tile coordinates in the picture space.
- let mut p0 = TileOffset::new(
- (rect.min.x / self.tile_size.width).floor() as i32,
- (rect.min.y / self.tile_size.height).floor() as i32,
- );
-
- let mut p1 = TileOffset::new(
- (rect.max.x / self.tile_size.width).ceil() as i32,
- (rect.max.y / self.tile_size.height).ceil() as i32,
- );
-
- // Clamp the tile coordinates here to avoid looping over irrelevant tiles later on.
- p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
- p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
- p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
- p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
-
- (p0, p1)
- }
-
- /// Update transforms, opacity, color bindings and tile rects.
- pub fn pre_update(
- &mut self,
- surface_index: SurfaceIndex,
- frame_context: &FrameVisibilityContext,
- frame_state: &mut FrameVisibilityState,
- ) -> WorldRect {
- let surface = &frame_state.surfaces[surface_index.0];
- let pic_rect = surface.unclipped_local_rect;
-
- self.surface_index = surface_index;
- self.local_rect = pic_rect;
- self.local_clip_rect = PictureRect::max_rect();
- self.deferred_dirty_tests.clear();
- self.underlays.clear();
- self.overlay_region = PictureRect::zero();
- self.yuv_images_remaining = self.yuv_images_count;
-
- for sub_slice in &mut self.sub_slices {
- sub_slice.reset();
- }
-
- // Reset the opaque rect + subpixel mode, as they are calculated
- // during the prim dependency checks.
- self.backdrop = BackdropInfo::empty();
-
- // Calculate the screen rect in picture space, for later comparison against
- // backdrops, and prims potentially covering backdrops.
- let pic_to_world_mapper = SpaceMapper::new_with_target(
- frame_context.root_spatial_node_index,
- self.spatial_node_index,
- frame_context.global_screen_world_rect,
- frame_context.spatial_tree,
- );
- self.screen_rect_in_pic_space = pic_to_world_mapper
- .unmap(&frame_context.global_screen_world_rect)
- .expect("unable to unmap screen rect");
-
- let pic_to_vis_mapper = SpaceMapper::new_with_target(
- // TODO: use the raster node instead of the root node.
- frame_context.root_spatial_node_index,
- self.spatial_node_index,
- surface.culling_rect,
- frame_context.spatial_tree,
- );
-
- // If there is a valid set of shared clips, build a clip chain instance for this,
- // which will provide a local clip rect. This is useful for establishing things
- // like whether the backdrop rect supplied by Gecko can be considered opaque.
- if let Some(shared_clip_leaf_id) = self.shared_clip_leaf_id {
- let map_local_to_picture = SpaceMapper::new(
- self.spatial_node_index,
- pic_rect,
- );
-
- frame_state.clip_store.set_active_clips(
- self.spatial_node_index,
- map_local_to_picture.ref_spatial_node_index,
- surface.visibility_spatial_node_index,
- shared_clip_leaf_id,
- frame_context.spatial_tree,
- &mut frame_state.data_stores.clip,
- &frame_state.clip_tree,
- );
-
- let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance(
- pic_rect.cast_unit(),
- &map_local_to_picture,
- &pic_to_vis_mapper,
- frame_context.spatial_tree,
- &mut frame_state.frame_gpu_data.f32,
- frame_state.resource_cache,
- frame_context.global_device_pixel_scale,
- &surface.culling_rect,
- &mut frame_state.data_stores.clip,
- frame_state.rg_builder,
- true,
- );
-
- // Ensure that if the entire picture cache is clipped out, the local
- // clip rect is zero. This makes sure we don't register any occluders
- // that are actually off-screen.
- self.local_clip_rect = PictureRect::zero();
- self.compositor_clip = None;
-
- if let Some(clip_chain) = clip_chain_instance {
- self.local_clip_rect = clip_chain.pic_coverage_rect;
- self.compositor_clip = None;
-
- if clip_chain.needs_mask {
- for i in 0 .. clip_chain.clips_range.count {
- let clip_instance = frame_state
- .clip_store
- .get_instance_from_range(&clip_chain.clips_range, i);
- let clip_node = &frame_state.data_stores.clip[clip_instance.handle];
-
- match clip_node.item.kind {
- ClipItemKind::RoundedRectangle { rect, radius, mode } => {
- assert_eq!(mode, ClipMode::Clip);
-
- // Map the clip in to device space. We know from the shared
- // clip creation logic it's in root coord system, so only a
- // 2d axis-aligned transform can apply. For example, in the
- // case of a pinch-zoom effect.
- let map = ClipSpaceConversion::new(
- frame_context.root_spatial_node_index,
- clip_node.item.spatial_node_index,
- frame_context.root_spatial_node_index,
- frame_context.spatial_tree,
- );
-
- let (rect, radius) = match map {
- ClipSpaceConversion::Local => {
- (rect.cast_unit(), radius)
- }
- ClipSpaceConversion::ScaleOffset(scale_offset) => {
- (
- scale_offset.map_rect(&rect),
- BorderRadius {
- top_left: scale_offset.map_size(&radius.top_left),
- top_right: scale_offset.map_size(&radius.top_right),
- bottom_left: scale_offset.map_size(&radius.bottom_left),
- bottom_right: scale_offset.map_size(&radius.bottom_right),
- },
- )
- }
- ClipSpaceConversion::Transform(..) => {
- unreachable!();
- }
- };
-
- self.compositor_clip = Some(frame_state.composite_state.register_clip(
- rect,
- radius,
- ));
-
- break;
- }
- _ => {
- // The logic to check for shared clips excludes other mask
- // clip types (box-shadow, image-mask) and ensures that the
- // clip is in the root coord system (so rect clips can't
- // produce a mask).
- }
- }
- }
- }
- }
- }
-
- // Advance the current frame ID counter for this picture cache (must be done
- // after any retained prev state is taken above).
- self.frame_id.advance();
-
- // Notify the spatial node comparer that a new frame has started, and the
- // current reference spatial node for this tile cache.
- self.spatial_node_comparer.next_frame(self.spatial_node_index);
-
- // At the start of the frame, step through each current compositor surface
- // and mark it as unused. Later, this is used to free old compositor surfaces.
- // TODO(gw): In future, we might make this more sophisticated - for example,
- // retaining them for >1 frame if unused, or retaining them in some
- // kind of pool to reduce future allocations.
- for external_native_surface in self.external_native_surface_cache.values_mut() {
- external_native_surface.used_this_frame = false;
- }
-
- // Only evaluate what tile size to use fairly infrequently, so that we don't end
- // up constantly invalidating and reallocating tiles if the picture rect size is
- // changing near a threshold value.
- if self.frames_until_size_eval == 0 ||
- self.tile_size_override != frame_context.config.tile_size_override {
-
- // Work out what size tile is appropriate for this picture cache.
- let desired_tile_size = match frame_context.config.tile_size_override {
- Some(tile_size_override) => {
- tile_size_override
- }
- None => {
- if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) {
- if pic_rect.width() <= pic_rect.height() {
- TILE_SIZE_SCROLLBAR_VERTICAL
- } else {
- TILE_SIZE_SCROLLBAR_HORIZONTAL
- }
- } else {
- frame_state.resource_cache.picture_textures.default_tile_size()
- }
- }
- };
-
- // If the desired tile size has changed, then invalidate and drop any
- // existing tiles.
- if desired_tile_size != self.current_tile_size {
- for sub_slice in &mut self.sub_slices {
- // Destroy any native surfaces on the tiles that will be dropped due
- // to resizing.
- if let Some(native_surface) = sub_slice.native_surface.take() {
- frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
- frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
- }
- sub_slice.tiles.clear();
- }
- self.tile_rect = TileRect::zero();
- self.current_tile_size = desired_tile_size;
- }
-
- // Reset counter until next evaluating the desired tile size. This is an
- // arbitrary value.
- self.frames_until_size_eval = 120;
- self.tile_size_override = frame_context.config.tile_size_override;
- }
-
- // Get the complete scale-offset from local space to device space
- let local_to_device = get_relative_scale_offset(
- self.spatial_node_index,
- frame_context.root_spatial_node_index,
- frame_context.spatial_tree,
- );
-
- // Get the compositor transform, which depends on pinch-zoom mode
- let mut raster_to_device = local_to_device;
-
- if frame_context.config.low_quality_pinch_zoom {
- raster_to_device.scale.x /= self.current_raster_scale;
- raster_to_device.scale.y /= self.current_raster_scale;
- } else {
- raster_to_device.scale.x = 1.0;
- raster_to_device.scale.y = 1.0;
- }
-
- // Use that compositor transform to calculate a relative local to surface
- let local_to_raster = local_to_device.then(&raster_to_device.inverse());
-
- const EPSILON: f32 = 0.001;
- let compositor_translation_changed =
- !raster_to_device.offset.x.approx_eq_eps(&self.raster_to_device.offset.x, &EPSILON) ||
- !raster_to_device.offset.y.approx_eq_eps(&self.raster_to_device.offset.y, &EPSILON);
- let compositor_scale_changed =
- !raster_to_device.scale.x.approx_eq_eps(&self.raster_to_device.scale.x, &EPSILON) ||
- !raster_to_device.scale.y.approx_eq_eps(&self.raster_to_device.scale.y, &EPSILON);
- let surface_scale_changed =
- !local_to_raster.scale.x.approx_eq_eps(&self.local_to_raster.scale.x, &EPSILON) ||
- !local_to_raster.scale.y.approx_eq_eps(&self.local_to_raster.scale.y, &EPSILON);
-
- if compositor_translation_changed ||
- compositor_scale_changed ||
- surface_scale_changed ||
- frame_context.config.force_invalidation {
- frame_state.composite_state.dirty_rects_are_valid = false;
- }
-
- self.raster_to_device = raster_to_device;
- self.local_to_raster = local_to_raster;
- self.invalidate_all_tiles = surface_scale_changed || frame_context.config.force_invalidation;
-
- // Do a hacky diff of opacity binding values from the last frame. This is
- // used later on during tile invalidation tests.
- let current_properties = frame_context.scene_properties.float_properties();
- mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings);
-
- self.opacity_bindings.clear();
- for (id, value) in current_properties {
- let changed = match self.old_opacity_bindings.get(id) {
- Some(old_property) => !old_property.value.approx_eq(value),
- None => true,
- };
- self.opacity_bindings.insert(*id, OpacityBindingInfo {
- value: *value,
- changed,
- });
- }
-
- // Do a hacky diff of color binding values from the last frame. This is
- // used later on during tile invalidation tests.
- let current_properties = frame_context.scene_properties.color_properties();
- mem::swap(&mut self.color_bindings, &mut self.old_color_bindings);
-
- self.color_bindings.clear();
- for (id, value) in current_properties {
- let changed = match self.old_color_bindings.get(id) {
- Some(old_property) => old_property.value != (*value).into(),
- None => true,
- };
- self.color_bindings.insert(*id, ColorBindingInfo {
- value: (*value).into(),
- changed,
- });
- }
-
- let world_tile_size = WorldSize::new(
- self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
- self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
- );
-
- self.tile_size = PictureSize::new(
- world_tile_size.width / self.local_to_raster.scale.x,
- world_tile_size.height / self.local_to_raster.scale.y,
- );
-
- // Inflate the needed rect a bit, so that we retain tiles that we have drawn
- // but have just recently gone off-screen. This means that we avoid re-drawing
- // tiles if the user is scrolling up and down small amounts, at the cost of
- // a bit of extra texture memory.
- let desired_rect_in_pic_space = self.screen_rect_in_pic_space
- .inflate(0.0, 1.0 * self.tile_size.height);
-
- let needed_rect_in_pic_space = desired_rect_in_pic_space
- .intersection(&pic_rect)
- .unwrap_or_else(Box2D::zero);
-
- let p0 = needed_rect_in_pic_space.min;
- let p1 = needed_rect_in_pic_space.max;
-
- let x0 = (p0.x / self.tile_size.width).floor() as i32;
- let x1 = (p1.x / self.tile_size.width).ceil() as i32;
-
- let y0 = (p0.y / self.tile_size.height).floor() as i32;
- let y1 = (p1.y / self.tile_size.height).ceil() as i32;
-
- let new_tile_rect = TileRect {
- min: TileOffset::new(x0, y0),
- max: TileOffset::new(x1, y1),
- };
-
- // Determine whether the current bounds of the tile grid will exceed the
- // bounds of the DC virtual surface, taking into account the current
- // virtual offset. If so, we need to invalidate all tiles, and set up
- // a new virtual offset, centered around the current tile grid.
-
- let virtual_surface_size = frame_context.config.compositor_kind.get_virtual_surface_size();
- // We only need to invalidate in this case if the underlying platform
- // uses virtual surfaces.
- if virtual_surface_size > 0 {
- // Get the extremities of the tile grid after virtual offset is applied
- let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width;
- let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height;
- let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width;
- let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height;
-
- let need_new_virtual_offset = tx0 < 0 ||
- ty0 < 0 ||
- tx1 >= virtual_surface_size ||
- ty1 >= virtual_surface_size;
-
- if need_new_virtual_offset {
- // Calculate a new virtual offset, centered around the middle of the
- // current tile grid. This means we won't need to invalidate and get
- // a new offset for a long time!
- self.virtual_offset = DeviceIntPoint::new(
- (virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width,
- (virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height,
- );
-
- // Invalidate all native tile surfaces. They will be re-allocated next time
- // they are scheduled to be rasterized.
- for sub_slice in &mut self.sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
- if let Some(id) = id.take() {
- frame_state.resource_cache.destroy_compositor_tile(id);
- tile.surface = None;
- // Invalidate the entire tile to force a redraw.
- // TODO(gw): Add a new invalidation reason for virtual offset changing
- tile.invalidate(None, InvalidationReason::CompositorKindChanged);
- }
- }
- }
-
- // Destroy the native virtual surfaces. They will be re-allocated next time a tile
- // that references them is scheduled to draw.
- if let Some(native_surface) = sub_slice.native_surface.take() {
- frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
- frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
- }
- }
- }
- }
-
- // Rebuild the tile grid if the picture cache rect has changed.
- if new_tile_rect != self.tile_rect {
- for sub_slice in &mut self.sub_slices {
- let mut old_tiles = sub_slice.resize(new_tile_rect);
-
- // When old tiles that remain after the loop, dirty rects are not valid.
- if !old_tiles.is_empty() {
- frame_state.composite_state.dirty_rects_are_valid = false;
- }
-
- // Any old tiles that remain after the loop above are going to be dropped. For
- // simple composite mode, the texture cache handle will expire and be collected
- // by the texture cache. For native compositor mode, we need to explicitly
- // invoke a callback to the client to destroy that surface.
- frame_state.composite_state.destroy_native_tiles(
- old_tiles.values_mut(),
- frame_state.resource_cache,
- );
- }
- }
-
- // This is duplicated information from tile_rect, but cached here to avoid
- // redundant calculations during get_tile_coords_for_rect
- self.tile_bounds_p0 = TileOffset::new(x0, y0);
- self.tile_bounds_p1 = TileOffset::new(x1, y1);
- self.tile_rect = new_tile_rect;
-
- let mut world_culling_rect = WorldRect::zero();
-
- let mut ctx = TilePreUpdateContext {
- pic_to_world_mapper,
- background_color: self.background_color,
- global_screen_world_rect: frame_context.global_screen_world_rect,
- tile_size: self.tile_size,
- frame_id: self.frame_id,
- };
-
- // Pre-update each tile
- for sub_slice in &mut self.sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- tile.pre_update(&ctx);
-
- // Only include the tiles that are currently in view into the world culling
- // rect. This is a very important optimization for a couple of reasons:
- // (1) Primitives that intersect with tiles in the grid that are not currently
- // visible can be skipped from primitive preparation, clip chain building
- // and tile dependency updates.
- // (2) When we need to allocate an off-screen surface for a child picture (for
- // example a CSS filter) we clip the size of the GPU surface to the world
- // culling rect below (to ensure we draw enough of it to be sampled by any
- // tiles that reference it). Making the world culling rect only affected
- // by visible tiles (rather than the entire virtual tile display port) can
- // result in allocating _much_ smaller GPU surfaces for cases where the
- // true off-screen surface size is very large.
- if tile.is_visible {
- world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
- }
- }
-
- // The background color can only be applied to the first sub-slice.
- ctx.background_color = None;
- }
-
- // If compositor mode is changed, need to drop all incompatible tiles.
- match frame_context.config.compositor_kind {
- CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
- for sub_slice in &mut self.sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
- if let Some(id) = id.take() {
- frame_state.resource_cache.destroy_compositor_tile(id);
- }
- tile.surface = None;
- // Invalidate the entire tile to force a redraw.
- tile.invalidate(None, InvalidationReason::CompositorKindChanged);
- }
- }
-
- if let Some(native_surface) = sub_slice.native_surface.take() {
- frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
- frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
- }
- }
-
- for (_, external_surface) in self.external_native_surface_cache.drain() {
- frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
- }
- }
- CompositorKind::Native { .. } => {
- // This could hit even when compositor mode is not changed,
- // then we need to check if there are incompatible tiles.
- for sub_slice in &mut self.sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface {
- tile.surface = None;
- // Invalidate the entire tile to force a redraw.
- tile.invalidate(None, InvalidationReason::CompositorKindChanged);
- }
- }
- }
- }
- }
-
- world_culling_rect
- }
-
- fn can_promote_to_surface(
- &mut self,
- prim_clip_chain: &ClipChainInstance,
- prim_spatial_node_index: SpatialNodeIndex,
- is_root_tile_cache: bool,
- sub_slice_index: usize,
- surface_kind: CompositorSurfaceKind,
- pic_coverage_rect: PictureRect,
- frame_context: &FrameVisibilityContext,
- force: bool,
- ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
- use SurfacePromotionFailure::*;
-
- // Each strategy has different restrictions on whether we can promote
- match surface_kind {
- CompositorSurfaceKind::Overlay => {
- // For now, only support a small (arbitrary) number of compositor surfaces.
- // Non-opaque compositor surfaces require sub-slices, as they are drawn
- // as overlays.
- if sub_slice_index == self.sub_slices.len() - 1 {
- return Err(OverlaySurfaceLimit);
- }
-
- // If a complex clip is being applied to this primitive, it can't be
- // promoted directly to a compositor surface.
- if prim_clip_chain.needs_mask {
- return Err(OverlayNeedsMask);
- }
- }
- CompositorSurfaceKind::Underlay => {
- // If a mask is needed, there are some restrictions.
- if prim_clip_chain.needs_mask {
- // Need an opaque region behind this prim. The opaque region doesn't
- // need to span the entire visible region of the TileCacheInstance,
- // which would set self.backdrop.kind, but that also qualifies.
- if !self.backdrop.opaque_rect.contains_box(&pic_coverage_rect) {
- let result = Err(UnderlayAlphaBackdrop);
- // If we aren't forcing, give up and return Err.
- if !force {
- return result;
- }
-
- // Log this but don't return an error.
- self.report_promotion_failure(result, pic_coverage_rect, true);
- }
-
- // Only one masked underlay allowed.
- if !self.underlays.is_empty() {
- return Err(UnderlaySurfaceLimit);
- }
- }
-
- // Underlays can't appear on top of overlays, because they can't punch
- // through the existing overlay.
- if self.overlay_region.intersects(&pic_coverage_rect) {
- let result = Err(UnderlayIntersectsOverlay);
- // If we aren't forcing, give up and return Err.
- if !force {
- return result;
- }
-
- // Log this but don't return an error.
- self.report_promotion_failure(result, pic_coverage_rect, true);
- }
-
- // Underlay cutouts are difficult to align with compositor surfaces when
- // compositing during low-quality zoom, and the required invalidation
- // whilst zooming would prevent low-quality zoom from working efficiently.
- if frame_context.config.low_quality_pinch_zoom &&
- frame_context.spatial_tree.get_spatial_node(prim_spatial_node_index).is_ancestor_or_self_zooming
- {
- return Err(UnderlayLowQualityZoom);
- }
- }
- CompositorSurfaceKind::Blit => unreachable!(),
- }
-
- // If not on the root picture cache, it has some kind of
- // complex effect (such as a filter, mix-blend-mode or 3d transform).
- if !is_root_tile_cache {
- return Err(NotRootTileCache);
- }
-
- let mapper : SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
- frame_context.root_spatial_node_index,
- prim_spatial_node_index,
- frame_context.global_screen_world_rect,
- &frame_context.spatial_tree);
- let transform = mapper.get_transform();
- if !transform.is_2d_scale_translation() {
- let result = Err(ComplexTransform);
- // Unfortunately, ComplexTransform absolutely prevents proper
- // functioning of surface promotion. Treating this as a warning
- // instead of an error will cause a crash in get_relative_scale_offset.
- return result;
- }
-
- if self.slice_flags.contains(SliceFlags::IS_ATOMIC) {
- return Err(SliceAtomic);
- }
-
- Ok(surface_kind)
- }
-
- fn setup_compositor_surfaces_yuv(
- &mut self,
- sub_slice_index: usize,
- prim_info: &mut PrimitiveDependencyInfo,
- flags: PrimitiveFlags,
- local_prim_rect: LayoutRect,
- prim_spatial_node_index: SpatialNodeIndex,
- pic_coverage_rect: PictureRect,
- frame_context: &FrameVisibilityContext,
- image_dependencies: &[ImageDependency;3],
- api_keys: &[ImageKey; 3],
- resource_cache: &mut ResourceCache,
- composite_state: &mut CompositeState,
- gpu_buffer: &mut GpuBufferBuilderF,
- image_rendering: ImageRendering,
- color_depth: ColorDepth,
- color_space: YuvRangedColorSpace,
- format: YuvFormat,
- surface_kind: CompositorSurfaceKind,
- ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
- for &key in api_keys {
- if key != ImageKey::DUMMY {
- // TODO: See comment in setup_compositor_surfaces_rgb.
- resource_cache.request_image(ImageRequest {
- key,
- rendering: image_rendering,
- tile: None,
- },
- gpu_buffer,
- );
- }
- }
-
- self.setup_compositor_surfaces_impl(
- sub_slice_index,
- prim_info,
- flags,
- local_prim_rect,
- prim_spatial_node_index,
- pic_coverage_rect,
- frame_context,
- ExternalSurfaceDependency::Yuv {
- image_dependencies: *image_dependencies,
- color_space,
- format,
- channel_bit_depth: color_depth.bit_depth(),
- },
- api_keys,
- resource_cache,
- composite_state,
- image_rendering,
- true,
- surface_kind,
- )
- }
-
- fn setup_compositor_surfaces_rgb(
- &mut self,
- sub_slice_index: usize,
- prim_info: &mut PrimitiveDependencyInfo,
- flags: PrimitiveFlags,
- local_prim_rect: LayoutRect,
- prim_spatial_node_index: SpatialNodeIndex,
- pic_coverage_rect: PictureRect,
- frame_context: &FrameVisibilityContext,
- image_dependency: ImageDependency,
- api_key: ImageKey,
- resource_cache: &mut ResourceCache,
- composite_state: &mut CompositeState,
- gpu_buffer: &mut GpuBufferBuilderF,
- image_rendering: ImageRendering,
- is_opaque: bool,
- surface_kind: CompositorSurfaceKind,
- ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
- let mut api_keys = [ImageKey::DUMMY; 3];
- api_keys[0] = api_key;
-
- // TODO: The picture compositing code requires images promoted
- // into their own picture cache slices to be requested every
- // frame even if they are not visible. However the image updates
- // are only reached on the prepare pass for visible primitives.
- // So we make sure to trigger an image request when promoting
- // the image here.
- resource_cache.request_image(ImageRequest {
- key: api_key,
- rendering: image_rendering,
- tile: None,
- },
- gpu_buffer,
- );
-
- self.setup_compositor_surfaces_impl(
- sub_slice_index,
- prim_info,
- flags,
- local_prim_rect,
- prim_spatial_node_index,
- pic_coverage_rect,
- frame_context,
- ExternalSurfaceDependency::Rgb {
- image_dependency,
- },
- &api_keys,
- resource_cache,
- composite_state,
- image_rendering,
- is_opaque,
- surface_kind,
- )
- }
-
- // returns false if composition is not available for this surface,
- // and the non-compositor path should be used to draw it instead.
- fn setup_compositor_surfaces_impl(
- &mut self,
- sub_slice_index: usize,
- prim_info: &mut PrimitiveDependencyInfo,
- flags: PrimitiveFlags,
- local_prim_rect: LayoutRect,
- prim_spatial_node_index: SpatialNodeIndex,
- pic_coverage_rect: PictureRect,
- frame_context: &FrameVisibilityContext,
- dependency: ExternalSurfaceDependency,
- api_keys: &[ImageKey; 3],
- resource_cache: &mut ResourceCache,
- composite_state: &mut CompositeState,
- image_rendering: ImageRendering,
- is_opaque: bool,
- surface_kind: CompositorSurfaceKind,
- ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
- use SurfacePromotionFailure::*;
-
- let map_local_to_picture = SpaceMapper::new_with_target(
- self.spatial_node_index,
- prim_spatial_node_index,
- self.local_rect,
- frame_context.spatial_tree,
- );
-
- // Map the primitive local rect into picture space.
- let prim_rect = match map_local_to_picture.map(&local_prim_rect) {
- Some(rect) => rect,
- None => return Ok(surface_kind),
- };
-
- // If the rect is invalid, no need to create dependencies.
- if prim_rect.is_empty() {
- return Ok(surface_kind);
- }
-
- let pic_to_world_mapper = SpaceMapper::new_with_target(
- frame_context.root_spatial_node_index,
- self.spatial_node_index,
- frame_context.global_screen_world_rect,
- frame_context.spatial_tree,
- );
-
- let world_clip_rect = pic_to_world_mapper
- .map(&prim_info.prim_clip_box)
- .expect("bug: unable to map clip to world space");
-
- let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
- if !is_visible {
- return Ok(surface_kind);
- }
-
- let prim_offset = ScaleOffset::from_offset(local_prim_rect.min.to_vector().cast_unit());
-
- let local_prim_to_device = get_relative_scale_offset(
- prim_spatial_node_index,
- frame_context.root_spatial_node_index,
- frame_context.spatial_tree,
- );
-
- let normalized_prim_to_device = prim_offset.then(&local_prim_to_device);
-
- let local_to_raster = ScaleOffset::identity();
- let raster_to_device = normalized_prim_to_device;
-
- // If this primitive is an external image, and supports being used
- // directly by a native compositor, then lookup the external image id
- // so we can pass that through.
- let mut external_image_id = if flags.contains(PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE)
- && image_rendering == ImageRendering::Auto {
- resource_cache.get_image_properties(api_keys[0])
- .and_then(|properties| properties.external_image)
- .and_then(|image| Some(image.id))
- } else {
- None
- };
-
-
- if let CompositorKind::Native { capabilities, .. } = composite_state.compositor_kind {
- if external_image_id.is_some() &&
- !capabilities.supports_external_compositor_surface_negative_scaling &&
- (raster_to_device.scale.x < 0.0 || raster_to_device.scale.y < 0.0) {
- external_image_id = None;
- }
- }
-
- let compositor_transform_index = composite_state.register_transform(
- local_to_raster,
- raster_to_device,
- );
-
- let surface_size = composite_state.get_surface_rect(
- &local_prim_rect,
- &local_prim_rect,
- compositor_transform_index,
- ).size();
-
- let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
-
- if surface_size.width >= MAX_COMPOSITOR_SURFACES_SIZE ||
- surface_size.height >= MAX_COMPOSITOR_SURFACES_SIZE {
- return Err(SizeTooLarge);
- }
-
- // When using native compositing, we need to find an existing native surface
- // handle to use, or allocate a new one. For existing native surfaces, we can
- // also determine whether this needs to be updated, depending on whether the
- // image generation(s) of the planes have changed since last composite.
- let (native_surface_id, update_params) = match composite_state.compositor_kind {
- CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
- (None, None)
- }
- CompositorKind::Native { .. } => {
- let native_surface_size = surface_size.to_i32();
-
- let key = ExternalNativeSurfaceKey {
- image_keys: *api_keys,
- size: if external_image_id.is_some() { None } else { Some(native_surface_size) },
- };
-
- let native_surface = self.external_native_surface_cache
- .entry(key)
- .or_insert_with(|| {
- // No existing surface, so allocate a new compositor surface.
- let native_surface_id = match external_image_id {
- Some(_external_image) => {
- // If we have a suitable external image, then create an external
- // surface to attach to.
- resource_cache.create_compositor_external_surface(is_opaque)
- }
- None => {
- // Otherwise create a normal compositor surface and a single
- // compositor tile that covers the entire surface.
- let native_surface_id =
- resource_cache.create_compositor_surface(
- DeviceIntPoint::zero(),
- native_surface_size,
- is_opaque,
- );
-
- let tile_id = NativeTileId {
- surface_id: native_surface_id,
- x: 0,
- y: 0,
- };
- resource_cache.create_compositor_tile(tile_id);
-
- native_surface_id
- }
- };
-
- ExternalNativeSurface {
- used_this_frame: true,
- native_surface_id,
- image_dependencies: [ImageDependency::INVALID; 3],
- }
- });
-
- // Mark that the surface is referenced this frame so that the
- // backing native surface handle isn't freed.
- native_surface.used_this_frame = true;
-
- let update_params = match external_image_id {
- Some(external_image) => {
- // If this is an external image surface, then there's no update
- // to be done. Just attach the current external image to the surface
- // and we're done.
- resource_cache.attach_compositor_external_image(
- native_surface.native_surface_id,
- external_image,
- );
- None
- }
- None => {
- // If the image dependencies match, there is no need to update
- // the backing native surface.
- match dependency {
- ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => {
- if image_dependencies == native_surface.image_dependencies {
- None
- } else {
- Some(native_surface_size)
- }
- },
- ExternalSurfaceDependency::Rgb{ image_dependency, .. } => {
- if image_dependency == native_surface.image_dependencies[0] {
- None
- } else {
- Some(native_surface_size)
- }
- },
- }
- }
- };
-
- (Some(native_surface.native_surface_id), update_params)
- }
- };
-
- let descriptor = ExternalSurfaceDescriptor {
- local_surface_size: local_prim_rect.size(),
- local_rect: prim_rect,
- local_clip_rect: prim_info.prim_clip_box,
- dependency,
- image_rendering,
- clip_rect,
- transform_index: compositor_transform_index,
- z_id: ZBufferId::invalid(),
- native_surface_id,
- update_params,
- external_image_id,
- };
-
- // If the surface is opaque, we can draw it an an underlay (which avoids
- // additional sub-slice surfaces, and supports clip masks)
- match surface_kind {
- CompositorSurfaceKind::Underlay => {
- self.underlays.push(descriptor);
- }
- CompositorSurfaceKind::Overlay => {
- // For compositor surfaces, if we didn't find an earlier sub-slice to add to,
- // we know we can append to the current slice.
- assert!(sub_slice_index < self.sub_slices.len() - 1);
- let sub_slice = &mut self.sub_slices[sub_slice_index];
-
- // Each compositor surface allocates a unique z-id
- sub_slice.compositor_surfaces.push(CompositorSurface {
- prohibited_rect: pic_coverage_rect,
- is_opaque,
- descriptor,
- });
-
- // Add the pic_coverage_rect to the overlay region. This prevents
- // future promoted surfaces from becoming underlays if they would
- // intersect with the overlay region.
- self.overlay_region = self.overlay_region.union(&pic_coverage_rect);
- }
- CompositorSurfaceKind::Blit => unreachable!(),
- }
-
- Ok(surface_kind)
- }
-
- /// Push an estimated rect for an off-screen surface during dependency updates. This is
- /// a workaround / hack that allows the picture cache code to know when it should be
- /// processing primitive dependencies as a single atomic unit. In future, we aim to remove
- /// this hack by having the primitive dependencies stored _within_ each owning picture.
- /// This is part of the work required to support child picture caching anyway!
- pub fn push_surface(
- &mut self,
- estimated_local_rect: LayoutRect,
- surface_spatial_node_index: SpatialNodeIndex,
- spatial_tree: &SpatialTree,
- ) {
- // Only need to evaluate sub-slice regions if we have compositor surfaces present
- if self.current_surface_traversal_depth == 0 && self.sub_slices.len() > 1 {
- let map_local_to_picture = SpaceMapper::new_with_target(
- self.spatial_node_index,
- surface_spatial_node_index,
- self.local_rect,
- spatial_tree,
- );
-
- if let Some(pic_rect) = map_local_to_picture.map(&estimated_local_rect) {
- // Find the first sub-slice we can add this primitive to (we want to add
- // prims to the primary surface if possible, so they get subpixel AA).
- for sub_slice in &mut self.sub_slices {
- let mut intersects_prohibited_region = false;
-
- for surface in &mut sub_slice.compositor_surfaces {
- if pic_rect.intersects(&surface.prohibited_rect) {
- surface.prohibited_rect = surface.prohibited_rect.union(&pic_rect);
-
- intersects_prohibited_region = true;
- }
- }
-
- if !intersects_prohibited_region {
- break;
- }
- }
- }
- }
-
- self.current_surface_traversal_depth += 1;
- }
-
- /// Pop an off-screen surface off the stack during dependency updates
- pub fn pop_surface(&mut self) {
- self.current_surface_traversal_depth -= 1;
- }
-
- fn report_promotion_failure(&self,
- result: Result<CompositorSurfaceKind, SurfacePromotionFailure>,
- rect: PictureRect,
- ignored: bool) {
- if !self.debug_flags.contains(DebugFlags::SURFACE_PROMOTION_LOGGING) || result.is_ok() {
- return;
- }
-
- // Report this as a warning.
- // TODO: Find a way to expose this to web authors.
- let outcome = if ignored { "failure ignored" } else { "failed" };
- warn!("Surface promotion of prim at {:?} {outcome} with: {}.", rect, result.unwrap_err());
- }
-
- /// Update the dependencies for each tile for a given primitive instance.
- pub fn update_prim_dependencies(
- &mut self,
- prim_instance: &mut PrimitiveInstance,
- prim_spatial_node_index: SpatialNodeIndex,
- local_prim_rect: LayoutRect,
- frame_context: &FrameVisibilityContext,
- data_stores: &DataStores,
- clip_store: &ClipStore,
- pictures: &[PicturePrimitive],
- resource_cache: &mut ResourceCache,
- color_bindings: &ColorBindingStorage,
- surface_stack: &[(PictureIndex, SurfaceIndex)],
- composite_state: &mut CompositeState,
- gpu_buffer: &mut GpuBufferBuilderF,
- scratch: &mut PrimitiveScratchBuffer,
- is_root_tile_cache: bool,
- surfaces: &mut [SurfaceInfo],
- profile: &mut TransactionProfile,
- ) -> VisibilityState {
- use SurfacePromotionFailure::*;
-
- // This primitive exists on the last element on the current surface stack.
- profile_scope!("update_prim_dependencies");
- let prim_surface_index = surface_stack.last().unwrap().1;
- let prim_clip_chain = &prim_instance.vis.clip_chain;
-
- // If the primitive is directly drawn onto this picture cache surface, then
- // the pic_coverage_rect is in the same space. If not, we need to map it from
- // the intermediate picture space into the picture cache space.
- let on_picture_surface = prim_surface_index == self.surface_index;
- let pic_coverage_rect = if on_picture_surface {
- prim_clip_chain.pic_coverage_rect
- } else {
- // We want to get the rect in the tile cache picture space that this primitive
- // occupies, in order to enable correct invalidation regions. Each surface
- // that exists in the chain between this primitive and the tile cache surface
- // may have an arbitrary inflation factor (for example, in the case of a series
- // of nested blur elements). To account for this, step through the current
- // surface stack, mapping the primitive rect into each picture space, including
- // the inflation factor from each intermediate surface.
- let mut current_pic_coverage_rect = prim_clip_chain.pic_coverage_rect;
- let mut current_spatial_node_index = surfaces[prim_surface_index.0]
- .surface_spatial_node_index;
-
- for (pic_index, surface_index) in surface_stack.iter().rev() {
- let surface = &surfaces[surface_index.0];
- let pic = &pictures[pic_index.0];
-
- let map_local_to_parent = SpaceMapper::new_with_target(
- surface.surface_spatial_node_index,
- current_spatial_node_index,
- surface.unclipped_local_rect,
- frame_context.spatial_tree,
- );
-
- // Map the rect into the parent surface, and inflate if this surface requires
- // it. If the rect can't be mapping (e.g. due to an invalid transform) then
- // just bail out from the dependencies and cull this primitive.
- current_pic_coverage_rect = match map_local_to_parent.map(¤t_pic_coverage_rect) {
- Some(rect) => {
- // TODO(gw): The casts here are a hack. We have some interface inconsistencies
- // between layout/picture rects which don't really work with the
- // current unit system, since sometimes the local rect of a picture
- // is a LayoutRect, and sometimes it's a PictureRect. Consider how
- // we can improve this?
- pic.composite_mode.as_ref().unwrap().get_coverage(
- surface,
- Some(rect.cast_unit()),
- ).cast_unit()
- }
- None => {
- return VisibilityState::Culled;
- }
- };
-
- current_spatial_node_index = surface.surface_spatial_node_index;
- }
-
- current_pic_coverage_rect
- };
-
- // Get the tile coordinates in the picture space.
- let (p0, p1) = self.get_tile_coords_for_rect(&pic_coverage_rect);
-
- // If the primitive is outside the tiling rects, it's known to not
- // be visible.
- if p0.x == p1.x || p0.y == p1.y {
- return VisibilityState::Culled;
- }
-
- // Build the list of resources that this primitive has dependencies on.
- let mut prim_info = PrimitiveDependencyInfo::new(
- prim_instance.uid(),
- pic_coverage_rect,
- );
-
- let mut sub_slice_index = self.sub_slices.len() - 1;
-
- // Only need to evaluate sub-slice regions if we have compositor surfaces present
- if sub_slice_index > 0 {
- // Find the first sub-slice we can add this primitive to (we want to add
- // prims to the primary surface if possible, so they get subpixel AA).
- for (i, sub_slice) in self.sub_slices.iter_mut().enumerate() {
- let mut intersects_prohibited_region = false;
-
- for surface in &mut sub_slice.compositor_surfaces {
- if pic_coverage_rect.intersects(&surface.prohibited_rect) {
- surface.prohibited_rect = surface.prohibited_rect.union(&pic_coverage_rect);
-
- intersects_prohibited_region = true;
- }
- }
-
- if !intersects_prohibited_region {
- sub_slice_index = i;
- break;
- }
- }
- }
-
- // Include the prim spatial node, if differs relative to cache root.
- if prim_spatial_node_index != self.spatial_node_index {
- prim_info.spatial_nodes.push(prim_spatial_node_index);
- }
-
- // If there was a clip chain, add any clip dependencies to the list for this tile.
- let clip_instances = &clip_store
- .clip_node_instances[prim_clip_chain.clips_range.to_range()];
- for clip_instance in clip_instances {
- let clip = &data_stores.clip[clip_instance.handle];
-
- prim_info.clips.push(clip_instance.handle.uid());
-
- // If the clip has the same spatial node, the relative transform
- // will always be the same, so there's no need to depend on it.
- if clip.item.spatial_node_index != self.spatial_node_index
- && !prim_info.spatial_nodes.contains(&clip.item.spatial_node_index) {
- prim_info.spatial_nodes.push(clip.item.spatial_node_index);
- }
- }
-
- // Certain primitives may select themselves to be a backdrop candidate, which is
- // then applied below.
- let mut backdrop_candidate = None;
-
- // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
- // use it to calculate the local bounding rect for the tiles. If we include them
- // then we may calculate a bounding rect that is too large, since it won't include
- // the clip bounds of the picture. Excluding them from the bounding rect here
- // fixes any correctness issues (the clips themselves are considered when we
- // consider the bounds of the primitives that are *children* of the picture),
- // however it does potentially result in some un-necessary invalidations of a
- // tile (in cases where the picture local rect affects the tile, but the clip
- // rect eventually means it doesn't affect that tile).
- // TODO(gw): Get picture clips earlier (during the initial picture traversal
- // pass) so that we can calculate these correctly.
- match prim_instance.kind {
- PrimitiveInstanceKind::Picture { pic_index,.. } => {
- // Pictures can depend on animated opacity bindings.
- let pic = &pictures[pic_index.0];
- if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.composite_mode {
- prim_info.opacity_bindings.push(binding.into());
- }
- }
- PrimitiveInstanceKind::Rectangle { data_handle, color_binding_index, .. } => {
- // Rectangles can only form a backdrop candidate if they are known opaque.
- // TODO(gw): We could resolve the opacity binding here, but the common
- // case for background rects is that they don't have animated opacity.
- let PrimitiveTemplateKind::Rectangle { color, .. } = data_stores.prim[data_handle].kind;
- let color = frame_context.scene_properties.resolve_color(&color);
- if color.a >= 1.0 {
- backdrop_candidate = Some(BackdropInfo {
- opaque_rect: pic_coverage_rect,
- spanning_opaque_color: None,
- kind: Some(BackdropKind::Color { color }),
- backdrop_rect: pic_coverage_rect,
- });
- }
-
- if color_binding_index != ColorBindingIndex::INVALID {
- prim_info.color_binding = Some(color_bindings[color_binding_index].into());
- }
- }
- PrimitiveInstanceKind::Image { data_handle, ref mut compositor_surface_kind, .. } => {
- let image_key = &data_stores.image[data_handle];
- let image_data = &image_key.kind;
-
- // For now, assume that for compositor surface purposes, any RGBA image may be
- // translucent. See the comment in `add_prim` in this source file for more
- // details. We'll leave the `is_opaque` code branches here, but disabled, as
- // in future we will want to support this case correctly.
- let mut is_opaque = false;
-
- if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
- // For an image to be a possible opaque backdrop, it must:
- // - Have a valid, opaque image descriptor
- // - Not use tiling (since they can fail to draw)
- // - Not having any spacing / padding
- // - Have opaque alpha in the instance (flattened) color
- if image_properties.descriptor.is_opaque() &&
- image_properties.tiling.is_none() &&
- image_data.tile_spacing == LayoutSize::zero() &&
- image_data.color.a >= 1.0 {
- backdrop_candidate = Some(BackdropInfo {
- opaque_rect: pic_coverage_rect,
- spanning_opaque_color: None,
- kind: None,
- backdrop_rect: PictureRect::zero(),
- });
- }
-
- is_opaque = image_properties.descriptor.is_opaque();
- }
-
- let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit);
- if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
- // Only consider promoting Images if all of our YuvImages have been
- // processed (whether they were promoted or not).
- if self.yuv_images_remaining > 0 {
- promotion_result = Err(ImageWaitingOnYuvImage);
- } else {
- promotion_result = self.can_promote_to_surface(prim_clip_chain,
- prim_spatial_node_index,
- is_root_tile_cache,
- sub_slice_index,
- CompositorSurfaceKind::Overlay,
- pic_coverage_rect,
- frame_context,
- false);
- }
-
- // Native OS compositors (DC and CA, at least) support premultiplied alpha
- // only. If we have an image that's not pre-multiplied alpha, we can't promote it.
- if image_data.alpha_type == AlphaType::Alpha {
- promotion_result = Err(NotPremultipliedAlpha);
- }
-
- if let Ok(kind) = promotion_result {
- promotion_result = self.setup_compositor_surfaces_rgb(
- sub_slice_index,
- &mut prim_info,
- image_key.common.flags,
- local_prim_rect,
- prim_spatial_node_index,
- pic_coverage_rect,
- frame_context,
- ImageDependency {
- key: image_data.key,
- generation: resource_cache.get_image_generation(image_data.key),
- },
- image_data.key,
- resource_cache,
- composite_state,
- gpu_buffer,
- image_data.image_rendering,
- is_opaque,
- kind,
- );
- }
- }
-
- if let Ok(kind) = promotion_result {
- *compositor_surface_kind = kind;
-
- if kind == CompositorSurfaceKind::Overlay {
- profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS);
- return VisibilityState::Culled;
- }
-
- assert!(kind == CompositorSurfaceKind::Blit, "Image prims should either be overlays or blits.");
- } else {
- // In Err case, we handle as a blit, and proceed.
- self.report_promotion_failure(promotion_result, pic_coverage_rect, false);
- *compositor_surface_kind = CompositorSurfaceKind::Blit;
- }
-
- if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
- profile.inc(profiler::COMPOSITOR_SURFACE_BLITS);
- }
-
- prim_info.images.push(ImageDependency {
- key: image_data.key,
- generation: resource_cache.get_image_generation(image_data.key),
- });
- }
- PrimitiveInstanceKind::YuvImage { data_handle, ref mut compositor_surface_kind, .. } => {
- let prim_data = &data_stores.yuv_image[data_handle];
-
- let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit);
- if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
- // Note if this is one of the YuvImages we were considering for
- // surface promotion. We only care for primitives that were added
- // to us, indicated by is_root_tile_cache. Those are the only ones
- // that were added to the TileCacheParams that configured the
- // current scene.
- if is_root_tile_cache {
- self.yuv_images_remaining -= 1;
- }
-
- // Should we force the promotion of this surface? We'll force it if promotion
- // is necessary for correct color display.
- let force = prim_data.kind.color_depth.bit_depth() > 8;
-
- let promotion_attempts =
- [CompositorSurfaceKind::Overlay, CompositorSurfaceKind::Underlay];
-
- for kind in promotion_attempts {
- // Since this might be an attempt after an earlier error, clear the flag
- // so that we are allowed to report another error.
- promotion_result = self.can_promote_to_surface(
- prim_clip_chain,
- prim_spatial_node_index,
- is_root_tile_cache,
- sub_slice_index,
- kind,
- pic_coverage_rect,
- frame_context,
- force);
- if promotion_result.is_ok() {
- break;
- }
-
- // We couldn't promote, but did we give up because the slice is marked
- // atomic? If that was the reason, and the YuvImage is wide color,
- // failing to promote will flatten the colors and look terrible. Let's
- // ignore the atomic slice restriction in such a case.
- if let Err(SliceAtomic) = promotion_result {
- if prim_data.kind. color_depth != ColorDepth::Color8 {
- // Let's promote with the attempted kind.
- promotion_result = Ok(kind);
- break;
- }
- }
- }
-
- // TODO(gw): When we support RGBA images for external surfaces, we also
- // need to check if opaque (YUV images are implicitly opaque).
-
- // If this primitive is being promoted to a surface, construct an external
- // surface descriptor for use later during batching and compositing. We only
- // add the image keys for this primitive as a dependency if this is _not_
- // a promoted surface, since we don't want the tiles to invalidate when the
- // video content changes, if it's a compositor surface!
- if let Ok(kind) = promotion_result {
- // Build dependency for each YUV plane, with current image generation for
- // later detection of when the composited surface has changed.
- let mut image_dependencies = [ImageDependency::INVALID; 3];
- for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
- *dep = ImageDependency {
- key,
- generation: resource_cache.get_image_generation(key),
- }
- }
-
- promotion_result = self.setup_compositor_surfaces_yuv(
- sub_slice_index,
- &mut prim_info,
- prim_data.common.flags,
- local_prim_rect,
- prim_spatial_node_index,
- pic_coverage_rect,
- frame_context,
- &image_dependencies,
- &prim_data.kind.yuv_key,
- resource_cache,
- composite_state,
- gpu_buffer,
- prim_data.kind.image_rendering,
- prim_data.kind.color_depth,
- prim_data.kind.color_space.with_range(prim_data.kind.color_range),
- prim_data.kind.format,
- kind,
- );
- }
- }
-
- // Store on the YUV primitive instance whether this is a promoted surface.
- // This is used by the batching code to determine whether to draw the
- // image to the content tiles, or just a transparent z-write.
- if let Ok(kind) = promotion_result {
- *compositor_surface_kind = kind;
- if kind == CompositorSurfaceKind::Overlay {
- profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS);
- return VisibilityState::Culled;
- }
-
- profile.inc(profiler::COMPOSITOR_SURFACE_UNDERLAYS);
- } else {
- // In Err case, we handle as a blit, and proceed.
- self.report_promotion_failure(promotion_result, pic_coverage_rect, false);
- *compositor_surface_kind = CompositorSurfaceKind::Blit;
- if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
- profile.inc(profiler::COMPOSITOR_SURFACE_BLITS);
- }
- }
-
- if *compositor_surface_kind == CompositorSurfaceKind::Blit {
- prim_info.images.extend(
- prim_data.kind.yuv_key.iter().map(|key| {
- ImageDependency {
- key: *key,
- generation: resource_cache.get_image_generation(*key),
- }
- })
- );
- }
- }
- PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
- let border_data = &data_stores.image_border[data_handle].kind;
- prim_info.images.push(ImageDependency {
- key: border_data.request.key,
- generation: resource_cache.get_image_generation(border_data.request.key),
- });
- }
- PrimitiveInstanceKind::LinearGradient { data_handle, .. }
- | PrimitiveInstanceKind::CachedLinearGradient { data_handle, .. } => {
- let gradient_data = &data_stores.linear_grad[data_handle];
- if gradient_data.stops_opacity.is_opaque
- && gradient_data.tile_spacing == LayoutSize::zero()
- {
- backdrop_candidate = Some(BackdropInfo {
- opaque_rect: pic_coverage_rect,
- spanning_opaque_color: None,
- kind: None,
- backdrop_rect: PictureRect::zero(),
- });
- }
- }
- PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
- let gradient_data = &data_stores.conic_grad[data_handle];
- if gradient_data.stops_opacity.is_opaque
- && gradient_data.tile_spacing == LayoutSize::zero()
- {
- backdrop_candidate = Some(BackdropInfo {
- opaque_rect: pic_coverage_rect,
- spanning_opaque_color: None,
- kind: None,
- backdrop_rect: PictureRect::zero(),
- });
- }
- }
- PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
- let gradient_data = &data_stores.radial_grad[data_handle];
- if gradient_data.stops_opacity.is_opaque
- && gradient_data.tile_spacing == LayoutSize::zero()
- {
- backdrop_candidate = Some(BackdropInfo {
- opaque_rect: pic_coverage_rect,
- spanning_opaque_color: None,
- kind: None,
- backdrop_rect: PictureRect::zero(),
- });
- }
- }
- PrimitiveInstanceKind::BackdropCapture { .. } => {}
- PrimitiveInstanceKind::BackdropRender { pic_index, .. } => {
- // If the area that the backdrop covers in the space of the surface it draws on
- // is empty, skip any sub-graph processing. This is not just a performance win,
- // it also ensures that we don't do a deferred dirty test that invalidates a tile
- // even if the tile isn't actually dirty, which can cause panics later in the
- // WR pipeline.
- if !pic_coverage_rect.is_empty() {
- // Mark that we need the sub-graph this render depends on so that
- // we don't skip it during the prepare pass
- scratch.required_sub_graphs.insert(pic_index);
-
- // If this is a sub-graph, register the bounds on any affected tiles
- // so we know how much to expand the content tile by.
- let sub_slice = &mut self.sub_slices[sub_slice_index];
-
- let mut surface_info = Vec::new();
- for (pic_index, surface_index) in surface_stack.iter().rev() {
- let pic = &pictures[pic_index.0];
- surface_info.push((pic.composite_mode.as_ref().unwrap().clone(), *surface_index));
- }
-
- for y in p0.y .. p1.y {
- for x in p0.x .. p1.x {
- let key = TileOffset::new(x, y);
- let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
- tile.sub_graphs.push((pic_coverage_rect, surface_info.clone()));
- }
- }
-
- // For backdrop-filter, we need to check if any of the dirty rects
- // in tiles that are affected by the filter primitive are dirty.
- self.deferred_dirty_tests.push(DeferredDirtyTest {
- tile_rect: TileRect::new(p0, p1),
- prim_rect: pic_coverage_rect,
- });
- }
- }
- PrimitiveInstanceKind::LineDecoration { .. } |
- PrimitiveInstanceKind::NormalBorder { .. } |
- PrimitiveInstanceKind::BoxShadow { .. } |
- PrimitiveInstanceKind::TextRun { .. } => {
- // These don't contribute dependencies
- }
- };
-
- // Calculate the screen rect in local space. When we calculate backdrops, we
- // care only that they cover the visible rect (based off the local clip), and
- // don't have any overlapping prims in the visible rect.
- let visible_local_clip_rect = self.local_clip_rect.intersection(&self.screen_rect_in_pic_space).unwrap_or_default();
- if pic_coverage_rect.intersects(&visible_local_clip_rect) {
- self.found_prims_after_backdrop = true;
- }
-
- // If this primitive considers itself a backdrop candidate, apply further
- // checks to see if it matches all conditions to be a backdrop.
- let mut vis_flags = PrimitiveVisibilityFlags::empty();
- let sub_slice = &mut self.sub_slices[sub_slice_index];
- if let Some(mut backdrop_candidate) = backdrop_candidate {
- // Update whether the surface that this primitive exists on
- // can be considered opaque. Any backdrop kind other than
- // a clear primitive (e.g. color, gradient, image) can be
- // considered.
- match backdrop_candidate.kind {
- Some(BackdropKind::Color { .. }) | None => {
- let surface = &mut surfaces[prim_surface_index.0];
-
- let is_same_coord_system = frame_context.spatial_tree.is_matching_coord_system(
- prim_spatial_node_index,
- surface.surface_spatial_node_index,
- );
-
- // To be an opaque backdrop, it must:
- // - Be the same coordinate system (axis-aligned)
- // - Have no clip mask
- // - Have a rect that covers the surface local rect
- if is_same_coord_system &&
- !prim_clip_chain.needs_mask &&
- prim_clip_chain.pic_coverage_rect.contains_box(&surface.unclipped_local_rect)
- {
- // Note that we use `prim_clip_chain.pic_clip_rect` here rather
- // than `backdrop_candidate.opaque_rect`. The former is in the
- // local space of the surface, the latter is in the local space
- // of the top level tile-cache.
- surface.is_opaque = true;
- }
- }
- }
-
- // Check a number of conditions to see if we can consider this
- // primitive as an opaque backdrop rect. Several of these are conservative
- // checks and could be relaxed in future. However, these checks
- // are quick and capture the common cases of background rects and images.
- // Specifically, we currently require:
- // - The primitive is on the main picture cache surface.
- // - Same coord system as picture cache (ensures rects are axis-aligned).
- // - No clip masks exist.
- let same_coord_system = frame_context.spatial_tree.is_matching_coord_system(
- prim_spatial_node_index,
- self.spatial_node_index,
- );
-
- let is_suitable_backdrop = same_coord_system && on_picture_surface;
-
- if sub_slice_index == 0 &&
- is_suitable_backdrop &&
- sub_slice.compositor_surfaces.is_empty() {
-
- // If the backdrop candidate has a clip-mask, try to extract an opaque inner
- // rect that is safe to use for subpixel rendering
- if prim_clip_chain.needs_mask {
- backdrop_candidate.opaque_rect = clip_store
- .get_inner_rect_for_clip_chain(
- prim_clip_chain,
- &data_stores.clip,
- frame_context.spatial_tree,
- )
- .unwrap_or(PictureRect::zero());
- }
-
- // We set the backdrop opaque_rect here, indicating the coverage area, which
- // is useful for calculate_subpixel_mode. We will only set the backdrop kind
- // if it covers the visible rect.
- if backdrop_candidate.opaque_rect.contains_box(&self.backdrop.opaque_rect) {
- self.backdrop.opaque_rect = backdrop_candidate.opaque_rect;
- }
-
- if let Some(kind) = backdrop_candidate.kind {
- if backdrop_candidate.opaque_rect.contains_box(&visible_local_clip_rect) {
- self.found_prims_after_backdrop = false;
- self.backdrop.kind = Some(kind);
- self.backdrop.backdrop_rect = backdrop_candidate.opaque_rect;
-
- // If we have a color backdrop that spans the entire local rect, mark
- // the visibility flags of the primitive so it is skipped during batching
- // (and also clears any previous primitives). Additionally, update our
- // background color to match the backdrop color, which will ensure that
- // our tiles are cleared to this color.
- let BackdropKind::Color { color } = kind;
- if backdrop_candidate.opaque_rect.contains_box(&self.local_rect) {
- vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP;
- self.backdrop.spanning_opaque_color = Some(color);
- }
- }
- }
- }
- }
-
- // Record any new spatial nodes in the used list.
- for spatial_node_index in &prim_info.spatial_nodes {
- self.spatial_node_comparer.register_used_transform(
- *spatial_node_index,
- self.frame_id,
- frame_context.spatial_tree,
- );
- }
-
- // Normalize the tile coordinates before adding to tile dependencies.
- // For each affected tile, mark any of the primitive dependencies.
- for y in p0.y .. p1.y {
- for x in p0.x .. p1.x {
- // TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile?
- let key = TileOffset::new(x, y);
- let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
-
- tile.add_prim_dependency(&prim_info);
- }
- }
-
- VisibilityState::Visible {
- vis_flags,
- sub_slice_index: SubSliceIndex::new(sub_slice_index),
- }
- }
-
- /// Print debug information about this picture cache to a tree printer.
- pub fn print(&self) {
- // TODO(gw): This initial implementation is very basic - just printing
- // the picture cache state to stdout. In future, we can
- // make this dump each frame to a file, and produce a report
- // stating which frames had invalidations. This will allow
- // diff'ing the invalidation states in a visual tool.
- let mut pt = PrintTree::new("Picture Cache");
-
- pt.new_level(format!("Slice {:?}", self.slice));
-
- pt.add_item(format!("background_color: {:?}", self.background_color));
-
- for (sub_slice_index, sub_slice) in self.sub_slices.iter().enumerate() {
- pt.new_level(format!("SubSlice {:?}", sub_slice_index));
-
- for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y {
- for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x {
- let key = TileOffset::new(x, y);
- let tile = &sub_slice.tiles[&key];
- tile.print(&mut pt);
- }
- }
-
- pt.end_level();
- }
-
- pt.end_level();
- }
-
- fn calculate_subpixel_mode(&self) -> SubpixelMode {
- // We can only consider the full opaque cases if there's no underlays
- if self.underlays.is_empty() {
- let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
-
- // If the overall tile cache is known opaque, subpixel AA is allowed everywhere
- if has_opaque_bg_color {
- return SubpixelMode::Allow;
- }
-
- // If the opaque backdrop rect covers the entire tile cache surface,
- // we can allow subpixel AA anywhere, skipping the per-text-run tests
- // later on during primitive preparation.
- if self.backdrop.opaque_rect.contains_box(&self.local_rect) {
- return SubpixelMode::Allow;
- }
- }
-
- // If we didn't find any valid opaque backdrop, no subpixel AA allowed
- if self.backdrop.opaque_rect.is_empty() {
- return SubpixelMode::Deny;
- }
-
- // Calculate a prohibited rect where we won't allow subpixel AA.
- // TODO(gw): This is conservative - it will disallow subpixel AA if there
- // are two underlay surfaces with text placed in between them. That's
- // probably unlikely to be an issue in practice, but maybe we should support
- // an array of prohibted rects?
- let prohibited_rect = self
- .underlays
- .iter()
- .fold(
- PictureRect::zero(),
- |acc, underlay| {
- acc.union(&underlay.local_rect)
- }
- );
-
- // If none of the simple cases above match, we need test where we can support subpixel AA.
- // TODO(gw): In future, it may make sense to have > 1 inclusion rect,
- // but this handles the common cases.
- // TODO(gw): If a text run gets animated such that it's moving in a way that is
- // sometimes intersecting with the video rect, this can result in subpixel
- // AA flicking on/off for that text run. It's probably very rare, but
- // something we should handle in future.
- SubpixelMode::Conditional {
- allowed_rect: self.backdrop.opaque_rect,
- prohibited_rect,
- }
- }
-
- /// Apply any updates after prim dependency updates. This applies
- /// any late tile invalidations, and sets up the dirty rect and
- /// set of tile blits.
- pub fn post_update(
- &mut self,
- frame_context: &FrameVisibilityContext,
- composite_state: &mut CompositeState,
- resource_cache: &mut ResourceCache,
- ) {
- assert!(self.current_surface_traversal_depth == 0);
-
- // TODO: Switch from the root node ot raster space.
- let visibility_node = frame_context.spatial_tree.root_reference_frame_index();
-
- self.dirty_region.reset(visibility_node, self.spatial_node_index);
- self.subpixel_mode = self.calculate_subpixel_mode();
-
- self.transform_index = composite_state.register_transform(
- self.local_to_raster,
- // TODO(gw): Once we support scaling of picture cache tiles during compositing,
- // that transform gets plugged in here!
- self.raster_to_device,
- );
-
- let map_pic_to_world = SpaceMapper::new_with_target(
- frame_context.root_spatial_node_index,
- self.spatial_node_index,
- frame_context.global_screen_world_rect,
- frame_context.spatial_tree,
- );
-
- // A simple GC of the native external surface cache, to remove and free any
- // surfaces that were not referenced during the update_prim_dependencies pass.
- self.external_native_surface_cache.retain(|_, surface| {
- if !surface.used_this_frame {
- // If we removed an external surface, we need to mark the dirty rects as
- // invalid so a full composite occurs on the next frame.
- composite_state.dirty_rects_are_valid = false;
-
- resource_cache.destroy_compositor_surface(surface.native_surface_id);
- }
-
- surface.used_this_frame
- });
-
- let pic_to_world_mapper = SpaceMapper::new_with_target(
- frame_context.root_spatial_node_index,
- self.spatial_node_index,
- frame_context.global_screen_world_rect,
- frame_context.spatial_tree,
- );
-
- let ctx = TileUpdateDirtyContext {
- pic_to_world_mapper,
- global_device_pixel_scale: frame_context.global_device_pixel_scale,
- opacity_bindings: &self.opacity_bindings,
- color_bindings: &self.color_bindings,
- local_rect: self.local_rect,
- invalidate_all: self.invalidate_all_tiles,
- };
-
- let mut state = TileUpdateDirtyState {
- resource_cache,
- composite_state,
- compare_cache: &mut self.compare_cache,
- spatial_node_comparer: &mut self.spatial_node_comparer,
- };
-
- // Step through each tile and invalidate if the dependencies have changed. Determine
- // the current opacity setting and whether it's changed.
- for sub_slice in &mut self.sub_slices {
- for tile in sub_slice.tiles.values_mut() {
- tile.update_dirty_and_valid_rects(&ctx, &mut state, frame_context);
- }
- }
-
- // Process any deferred dirty checks
- for sub_slice in &mut self.sub_slices {
- for dirty_test in self.deferred_dirty_tests.drain(..) {
- // Calculate the total dirty rect from all tiles that this primitive affects
- let mut total_dirty_rect = PictureRect::zero();
-
- for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
- for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
- let key = TileOffset::new(x, y);
- let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
- total_dirty_rect = total_dirty_rect.union(&tile.local_dirty_rect);
- }
- }
-
- // If that dirty rect intersects with the local rect of the primitive
- // being checked, invalidate that region in all of the affected tiles.
- // TODO(gw): This is somewhat conservative, we could be more clever
- // here and avoid invalidating every tile when this changes.
- // We could also store the dirty rect only when the prim
- // is encountered, so that we don't invalidate if something
- // *after* the query in the rendering order affects invalidation.
- if total_dirty_rect.intersects(&dirty_test.prim_rect) {
- for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
- for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
- let key = TileOffset::new(x, y);
- let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
- tile.invalidate(
- Some(dirty_test.prim_rect),
- InvalidationReason::SurfaceContentChanged,
- );
- }
- }
- }
- }
- }
-
- let mut ctx = TilePostUpdateContext {
- local_clip_rect: self.local_clip_rect,
- backdrop: None,
- current_tile_size: self.current_tile_size,
- z_id: ZBufferId::invalid(),
- underlays: &self.underlays,
- };
-
- let mut state = TilePostUpdateState {
- resource_cache,
- composite_state,
- };
-
- for (i, sub_slice) in self.sub_slices.iter_mut().enumerate().rev() {
- // The backdrop is only relevant for the first sub-slice
- if i == 0 {
- ctx.backdrop = Some(self.backdrop);
- }
-
- for compositor_surface in sub_slice.compositor_surfaces.iter_mut().rev() {
- compositor_surface.descriptor.z_id = state.composite_state.z_generator.next();
- }
-
- ctx.z_id = state.composite_state.z_generator.next();
-
- for tile in sub_slice.tiles.values_mut() {
- tile.post_update(&ctx, &mut state, frame_context);
- }
- }
-
- // Assign z-order for each underlay
- for underlay in self.underlays.iter_mut().rev() {
- underlay.z_id = state.composite_state.z_generator.next();
- }
-
- // Register any opaque external compositor surfaces as potential occluders. This
- // is especially useful when viewing video in full-screen mode, as it is
- // able to occlude every background tile (avoiding allocation, rasterizion
- // and compositing).
-
- // Register any underlays as occluders where possible
- for underlay in &self.underlays {
- if let Some(world_surface_rect) = underlay.get_occluder_rect(
- &self.local_clip_rect,
- &map_pic_to_world,
- ) {
- composite_state.register_occluder(
- underlay.z_id,
- world_surface_rect,
- self.compositor_clip,
- );
- }
- }
-
- for sub_slice in &self.sub_slices {
- for compositor_surface in &sub_slice.compositor_surfaces {
- if compositor_surface.is_opaque {
- if let Some(world_surface_rect) = compositor_surface.descriptor.get_occluder_rect(
- &self.local_clip_rect,
- &map_pic_to_world,
- ) {
- composite_state.register_occluder(
- compositor_surface.descriptor.z_id,
- world_surface_rect,
- self.compositor_clip,
- );
- }
- }
- }
- }
-
- // Register the opaque region of this tile cache as an occluder, which
- // is used later in the frame to occlude other tiles.
- if !self.backdrop.opaque_rect.is_empty() {
- let z_id_backdrop = composite_state.z_generator.next();
-
- let backdrop_rect = self.backdrop.opaque_rect
- .intersection(&self.local_rect)
- .and_then(|r| {
- r.intersection(&self.local_clip_rect)
- });
-
- if let Some(backdrop_rect) = backdrop_rect {
- let world_backdrop_rect = map_pic_to_world
- .map(&backdrop_rect)
- .expect("bug: unable to map backdrop to world space");
-
- // Since we register the entire backdrop rect, use the opaque z-id for the
- // picture cache slice.
- composite_state.register_occluder(
- z_id_backdrop,
- world_backdrop_rect,
- self.compositor_clip,
- );
- }
- }
- }
-}
-
-
-/// A SubSlice represents a potentially overlapping set of tiles within a picture cache. Most
-/// picture cache instances will have only a single sub-slice. The exception to this is when
-/// a picture cache has compositor surfaces, in which case sub slices are used to interleave
-/// content under or order the compositor surface(s).
-pub struct SubSlice {
- /// Hash of tiles present in this picture.
- pub tiles: FastHashMap<TileOffset, Box<Tile>>,
- /// The allocated compositor surfaces for this picture cache. May be None if
- /// not using native compositor, or if the surface was destroyed and needs
- /// to be reallocated next time this surface contains valid tiles.
- pub native_surface: Option<NativeSurface>,
- /// List of compositor surfaces that have been promoted from primitives
- /// in this tile cache.
- pub compositor_surfaces: Vec<CompositorSurface>,
- /// List of visible tiles to be composited for this subslice
- pub composite_tiles: Vec<CompositeTile>,
- /// Compositor descriptors of visible, opaque tiles (used by composite_state.push_surface)
- pub opaque_tile_descriptors: Vec<CompositeTileDescriptor>,
- /// Compositor descriptors of visible, alpha tiles (used by composite_state.push_surface)
- pub alpha_tile_descriptors: Vec<CompositeTileDescriptor>,
-}
-
-impl SubSlice {
- /// Construct a new sub-slice
- fn new() -> Self {
- SubSlice {
- tiles: FastHashMap::default(),
- native_surface: None,
- compositor_surfaces: Vec::new(),
- composite_tiles: Vec::new(),
- opaque_tile_descriptors: Vec::new(),
- alpha_tile_descriptors: Vec::new(),
- }
- }
-
- /// Reset the list of compositor surfaces that follow this sub-slice.
- /// Built per-frame, since APZ may change whether an image is suitable to be a compositor surface.
- fn reset(&mut self) {
- self.compositor_surfaces.clear();
- self.composite_tiles.clear();
- self.opaque_tile_descriptors.clear();
- self.alpha_tile_descriptors.clear();
- }
-
- /// Resize the tile grid to match a new tile bounds
- fn resize(&mut self, new_tile_rect: TileRect) -> FastHashMap<TileOffset, Box<Tile>> {
- let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
- self.tiles.reserve(new_tile_rect.area() as usize);
-
- for y in new_tile_rect.min.y .. new_tile_rect.max.y {
- for x in new_tile_rect.min.x .. new_tile_rect.max.x {
- let key = TileOffset::new(x, y);
- let tile = old_tiles
- .remove(&key)
- .unwrap_or_else(|| {
- Box::new(Tile::new(key))
- });
- self.tiles.insert(key, tile);
- }
- }
-
- old_tiles
- }
-}
-
-#[derive(Clone, Copy)]
-enum SurfacePromotionFailure {
- ImageWaitingOnYuvImage,
- NotPremultipliedAlpha,
- OverlaySurfaceLimit,
- OverlayNeedsMask,
- UnderlayAlphaBackdrop,
- UnderlaySurfaceLimit,
- UnderlayIntersectsOverlay,
- UnderlayLowQualityZoom,
- NotRootTileCache,
- ComplexTransform,
- SliceAtomic,
- SizeTooLarge,
-}
-
-impl Display for SurfacePromotionFailure {
- fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
- write!(
- f,
- "{}",
- match *self {
- SurfacePromotionFailure::ImageWaitingOnYuvImage => "Image prim waiting for all YuvImage prims to be considered for promotion",
- SurfacePromotionFailure::NotPremultipliedAlpha => "does not use premultiplied alpha",
- SurfacePromotionFailure::OverlaySurfaceLimit => "hit the overlay surface limit",
- SurfacePromotionFailure::OverlayNeedsMask => "overlay not allowed for prim with mask",
- SurfacePromotionFailure::UnderlayAlphaBackdrop => "underlay requires an opaque backdrop",
- SurfacePromotionFailure::UnderlaySurfaceLimit => "hit the underlay surface limit",
- SurfacePromotionFailure::UnderlayIntersectsOverlay => "underlay intersects already-promoted overlay",
- SurfacePromotionFailure::UnderlayLowQualityZoom => "underlay not allowed during low-quality pinch zoom",
- SurfacePromotionFailure::NotRootTileCache => "is not on a root tile cache",
- SurfacePromotionFailure::ComplexTransform => "has a complex transform",
- SurfacePromotionFailure::SliceAtomic => "slice is atomic",
- SurfacePromotionFailure::SizeTooLarge => "surface is too large for compositor",
- }.to_owned()
- )
- }
-}
-
-// Immutable context passed to picture cache tiles during pre_update
-struct TilePreUpdateContext {
- /// Maps from picture cache coords -> world space coords.
- pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
-
- /// The optional background color of the picture cache instance
- background_color: Option<ColorF>,
-
- /// The visible part of the screen in world coords.
- global_screen_world_rect: WorldRect,
-
- /// Current size of tiles in picture units.
- tile_size: PictureSize,
-
- /// The current frame id for this picture cache
- frame_id: FrameId,
-}
-
-// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
-struct TileUpdateDirtyContext<'a> {
- /// Maps from picture cache coords -> world space coords.
- pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
-
- /// Global scale factor from world -> device pixels.
- global_device_pixel_scale: DevicePixelScale,
-
- /// Information about opacity bindings from the picture cache.
- opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
-
- /// Information about color bindings from the picture cache.
- color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
-
- /// The local rect of the overall picture cache
- local_rect: PictureRect,
-
- /// If true, the scale factor of the root transform for this picture
- /// cache changed, so we need to invalidate the tile and re-render.
- invalidate_all: bool,
-}
-
-// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
-struct TileUpdateDirtyState<'a> {
- /// Allow access to the texture cache for requesting tiles
- resource_cache: &'a mut ResourceCache,
-
- /// Current configuration and setup for compositing all the picture cache tiles in renderer.
- composite_state: &'a mut CompositeState,
-
- /// A cache of comparison results to avoid re-computation during invalidation.
- compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
-
- /// Information about transform node differences from last frame.
- spatial_node_comparer: &'a mut SpatialNodeComparer,
-}
-
-// Immutable context passed to picture cache tiles during post_update
-struct TilePostUpdateContext<'a> {
- /// The local clip rect (in picture space) of the entire picture cache
- local_clip_rect: PictureRect,
-
- /// The calculated backdrop information for this cache instance.
- backdrop: Option<BackdropInfo>,
-
- /// Current size in device pixels of tiles for this cache
- current_tile_size: DeviceIntSize,
-
- /// Pre-allocated z-id to assign to tiles during post_update.
- z_id: ZBufferId,
-
- /// The list of compositor underlays for this picture cache
- underlays: &'a [ExternalSurfaceDescriptor],
-}
-
-// Mutable state passed to picture cache tiles during post_update
-struct TilePostUpdateState<'a> {
- /// Allow access to the texture cache for requesting tiles
- resource_cache: &'a mut ResourceCache,
-
- /// Current configuration and setup for compositing all the picture cache tiles in renderer.
- composite_state: &'a mut CompositeState,
-}
-
-/// Information about the dependencies of a single primitive instance.
-struct PrimitiveDependencyInfo {
- /// Unique content identifier of the primitive.
- prim_uid: ItemUid,
-
- /// The (conservative) clipped area in picture space this primitive occupies.
- prim_clip_box: PictureBox2D,
-
- /// Image keys this primitive depends on.
- images: SmallVec<[ImageDependency; 8]>,
-
- /// Opacity bindings this primitive depends on.
- opacity_bindings: SmallVec<[OpacityBinding; 4]>,
-
- /// Color binding this primitive depends on.
- color_binding: Option<ColorBinding>,
-
- /// Clips that this primitive depends on.
- clips: SmallVec<[ItemUid; 8]>,
-
- /// Spatial nodes references by the clip dependencies of this primitive.
- spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
-}
-
-impl PrimitiveDependencyInfo {
- /// Construct dependency info for a new primitive.
- fn new(
- prim_uid: ItemUid,
- prim_clip_box: PictureBox2D,
- ) -> Self {
- PrimitiveDependencyInfo {
- prim_uid,
- images: SmallVec::new(),
- opacity_bindings: SmallVec::new(),
- color_binding: None,
- prim_clip_box,
- clips: SmallVec::new(),
- spatial_nodes: SmallVec::new(),
- }
- }
-}
diff --git a/gfx/wr/webrender/src/tile_cache/slice_builder.rs b/gfx/wr/webrender/src/tile_cache/slice_builder.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, Picture3DContext, PictureFlags};
-use crate::tile_cache::{SliceId, TileCacheParams};
-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
@@ -17,10 +17,8 @@ use crate::renderer::GpuBufferBuilder;
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::clip::{ClipChainInstance, ClipTree};
use crate::frame_builder::FrameBuilderConfig;
-use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo};
-use crate::tile_cache::TileCacheInstance;
-use crate::picture::{SurfaceIndex, RasterConfig};
-use crate::tile_cache::SubSliceIndex;
+use crate::picture::{PictureCompositeMode, ClusterFlags, SurfaceInfo, TileCacheInstance};
+use crate::picture::{SurfaceIndex, RasterConfig, SubSliceIndex};
use crate::prim_store::{ClipTaskIndex, PictureIndex, PrimitiveInstanceKind};
use crate::prim_store::{PrimitiveStore, PrimitiveInstance};
use crate::render_backend::{DataStores, ScratchBuffer};