tor-browser

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

picture.rs (104446B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 //! A picture represents a dynamically rendered image.
      6 //!
      7 //! # Overview
      8 //!
      9 //! Pictures consists of:
     10 //!
     11 //! - A number of primitives that are drawn onto the picture.
     12 //! - A composite operation describing how to composite this
     13 //!   picture into its parent.
     14 //! - A configuration describing how to draw the primitives on
     15 //!   this picture (e.g. in screen space or local space).
     16 //!
     17 //! The tree of pictures are generated during scene building.
     18 //!
     19 //! Depending on their composite operations pictures can be rendered into
     20 //! intermediate targets or folded into their parent picture.
     21 //!
     22 //! ## Picture caching
     23 //!
     24 //! Pictures can be cached to reduce the amount of rasterization happening per
     25 //! frame.
     26 //!
     27 //! When picture caching is enabled, the scene is cut into a small number of slices,
     28 //! typically:
     29 //!
     30 //! - content slice
     31 //! - UI slice
     32 //! - background UI slice which is hidden by the other two slices most of the time.
     33 //!
     34 //! Each of these slice is made up of fixed-size large tiles of 2048x512 pixels
     35 //! (or 128x128 for the UI slice).
     36 //!
     37 //! Tiles can be either cached rasterized content into a texture or "clear tiles"
     38 //! that contain only a solid color rectangle rendered directly during the composite
     39 //! pass.
     40 //!
     41 //! ## Invalidation
     42 //!
     43 //! Each tile keeps track of the elements that affect it, which can be:
     44 //!
     45 //! - primitives
     46 //! - clips
     47 //! - image keys
     48 //! - opacity bindings
     49 //! - transforms
     50 //!
     51 //! These dependency lists are built each frame and compared to the previous frame to
     52 //! see if the tile changed.
     53 //!
     54 //! The tile's primitive dependency information is organized in a quadtree, each node
     55 //! storing an index buffer of tile primitive dependencies.
     56 //!
     57 //! The union of the invalidated leaves of each quadtree produces a per-tile dirty rect
     58 //! which defines the scissor rect used when replaying the tile's drawing commands and
     59 //! can be used for partial present.
     60 //!
     61 //! ## Display List shape
     62 //!
     63 //! WR will first look for an iframe item in the root stacking context to apply
     64 //! picture caching to. If that's not found, it will apply to the entire root
     65 //! stacking context of the display list. Apart from that, the format of the
     66 //! display list is not important to picture caching. Each time a new scroll root
     67 //! is encountered, a new picture cache slice will be created. If the display
     68 //! list contains more than some arbitrary number of slices (currently 8), the
     69 //! content will all be squashed into a single slice, in order to save GPU memory
     70 //! and compositing performance.
     71 //!
     72 //! ## Compositor Surfaces
     73 //!
     74 //! Sometimes, a primitive would prefer to exist as a native compositor surface.
     75 //! This allows a large and/or regularly changing primitive (such as a video, or
     76 //! webgl canvas) to be updated each frame without invalidating the content of
     77 //! tiles, and can provide a significant performance win and battery saving.
     78 //!
     79 //! Since drawing a primitive as a compositor surface alters the ordering of
     80 //! primitives in a tile, we use 'overlay tiles' to ensure correctness. If a
     81 //! tile has a compositor surface, _and_ that tile has primitives that overlap
     82 //! the compositor surface rect, the tile switches to be drawn in alpha mode.
     83 //!
     84 //! We rely on only promoting compositor surfaces that are opaque primitives.
     85 //! With this assumption, the tile(s) that intersect the compositor surface get
     86 //! a 'cutout' in the rectangle where the compositor surface exists (not the
     87 //! entire tile), allowing that tile to be drawn as an alpha tile after the
     88 //! compositor surface.
     89 //!
     90 //! Tiles are only drawn in overlay mode if there is content that exists on top
     91 //! of the compositor surface. Otherwise, we can draw the tiles in the normal fast
     92 //! path before the compositor surface is drawn. Use of the per-tile valid and
     93 //! dirty rects ensure that we do a minimal amount of per-pixel work here to
     94 //! blend the overlay tile (this is not always optimal right now, but will be
     95 //! improved as a follow up).
     96 
     97 use api::RasterSpace;
     98 use api::{DebugFlags, ColorF, PrimitiveFlags, SnapshotInfo};
     99 use api::units::*;
    100 use crate::command_buffer::PrimitiveCommand;
    101 use crate::renderer::GpuBufferBuilderF;
    102 use crate::box_shadow::BLUR_SAMPLE_SCALE;
    103 use crate::clip::{ClipNodeId, ClipTreeBuilder};
    104 use crate::spatial_tree::{SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
    105 use crate::composite::{tile_kind, CompositeTileSurface, CompositorKind, NativeTileId};
    106 use crate::composite::{CompositeTileDescriptor, CompositeTile};
    107 use crate::debug_colors;
    108 use euclid::{vec3, Scale, Vector2D, Box2D};
    109 use crate::internal_types::{FastHashMap, PlaneSplitter, Filter};
    110 use crate::internal_types::{PlaneSplitterIndex, PlaneSplitAnchor, TextureSource};
    111 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
    112 use plane_split::{Clipper, Polygon};
    113 use crate::prim_store::{PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
    114 use crate::prim_store::PrimitiveScratchBuffer;
    115 use crate::print_tree::PrintTreePrinter;
    116 use crate::render_backend::DataStores;
    117 use crate::render_task_graph::RenderTaskId;
    118 use crate::render_task::{RenderTask, RenderTaskLocation};
    119 use crate::render_task::{StaticRenderTaskSurface, RenderTaskKind};
    120 use crate::renderer::GpuBufferAddress;
    121 use crate::resource_cache::ResourceCache;
    122 use crate::space::SpaceMapper;
    123 use crate::scene::SceneProperties;
    124 use crate::spatial_tree::CoordinateSystemId;
    125 use crate::surface::{SurfaceDescriptor, SurfaceTileDescriptor, get_surface_rects};
    126 pub use crate::surface::{SurfaceIndex, SurfaceInfo, SubpixelMode};
    127 pub use crate::surface::{calculate_screen_uv, calculate_uv_rect_kind};
    128 use smallvec::SmallVec;
    129 use std::{mem, u8, u32};
    130 use std::ops::Range;
    131 use crate::picture_textures::PictureCacheTextureHandle;
    132 use crate::util::{MaxRect, Recycler, ScaleOffset};
    133 use crate::tile_cache::{SliceDebugInfo, TileDebugInfo, DirtyTileDebugInfo};
    134 use crate::tile_cache::{SliceId, TileCacheInstance, TileSurface, NativeSurface};
    135 use crate::tile_cache::{BackdropKind, BackdropSurface};
    136 use crate::tile_cache::{TileKey, SubSliceIndex};
    137 use crate::invalidation::InvalidationReason;
    138 use crate::tile_cache::MAX_SURFACE_SIZE;
    139 
    140 pub use crate::picture_composite_mode::{PictureCompositeMode, prepare_composite_mode};
    141 
    142 // Maximum blur radius for blur filter (different than box-shadow blur).
    143 // Taken from FilterNodeSoftware.cpp in Gecko.
    144 pub(crate) const MAX_BLUR_RADIUS: f32 = 100.;
    145 
    146 /// Maximum size of a compositor surface.
    147 pub const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0;
    148 
    149 pub fn clamp(value: i32, low: i32, high: i32) -> i32 {
    150    value.max(low).min(high)
    151 }
    152 
    153 pub fn clampf(value: f32, low: f32, high: f32) -> f32 {
    154    value.max(low).min(high)
    155 }
    156 
    157 /// A descriptor for the kind of texture that a picture cache tile will
    158 /// be drawn into.
    159 #[derive(Debug)]
    160 pub enum SurfaceTextureDescriptor {
    161    /// When using the WR compositor, the tile is drawn into an entry
    162    /// in the WR texture cache.
    163    TextureCache {
    164        handle: Option<PictureCacheTextureHandle>,
    165    },
    166    /// When using an OS compositor, the tile is drawn into a native
    167    /// surface identified by arbitrary id.
    168    Native {
    169        /// The arbitrary id of this tile.
    170        id: Option<NativeTileId>,
    171    },
    172 }
    173 
    174 /// This is the same as a `SurfaceTextureDescriptor` but has been resolved
    175 /// into a texture cache handle (if appropriate) that can be used by the
    176 /// batching and compositing code in the renderer.
    177 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
    178 #[cfg_attr(feature = "capture", derive(Serialize))]
    179 #[cfg_attr(feature = "replay", derive(Deserialize))]
    180 pub enum ResolvedSurfaceTexture {
    181    TextureCache {
    182        /// The texture ID to draw to.
    183        texture: TextureSource,
    184    },
    185    Native {
    186        /// The arbitrary id of this tile.
    187        id: NativeTileId,
    188        /// The size of the tile in device pixels.
    189        size: DeviceIntSize,
    190    }
    191 }
    192 
    193 impl SurfaceTextureDescriptor {
    194    /// Create a resolved surface texture for this descriptor
    195    pub fn resolve(
    196        &self,
    197        resource_cache: &ResourceCache,
    198        size: DeviceIntSize,
    199    ) -> ResolvedSurfaceTexture {
    200        match self {
    201            SurfaceTextureDescriptor::TextureCache { handle } => {
    202                let texture = resource_cache
    203                    .picture_textures
    204                    .get_texture_source(handle.as_ref().unwrap());
    205 
    206                ResolvedSurfaceTexture::TextureCache { texture }
    207            }
    208            SurfaceTextureDescriptor::Native { id } => {
    209                ResolvedSurfaceTexture::Native {
    210                    id: id.expect("bug: native surface not allocated"),
    211                    size,
    212                }
    213            }
    214        }
    215    }
    216 }
    217 
    218 pub struct PictureScratchBuffer {
    219    surface_stack: Vec<SurfaceIndex>,
    220 }
    221 
    222 impl Default for PictureScratchBuffer {
    223    fn default() -> Self {
    224        PictureScratchBuffer {
    225            surface_stack: Vec::new(),
    226        }
    227    }
    228 }
    229 
    230 impl PictureScratchBuffer {
    231    pub fn begin_frame(&mut self) {
    232        self.surface_stack.clear();
    233    }
    234 
    235    pub fn recycle(&mut self, recycler: &mut Recycler) {
    236        recycler.recycle_vec(&mut self.surface_stack);
    237    }
    238 }
    239 
    240 #[derive(Debug)]
    241 #[cfg_attr(feature = "capture", derive(Serialize))]
    242 pub struct RasterConfig {
    243    /// How this picture should be composited into
    244    /// the parent surface.
    245    // TODO(gw): We should remove this and just use what is in PicturePrimitive
    246    pub composite_mode: PictureCompositeMode,
    247    /// Index to the surface descriptor for this
    248    /// picture.
    249    pub surface_index: SurfaceIndex,
    250 }
    251 
    252 bitflags! {
    253    /// A set of flags describing why a picture may need a backing surface.
    254    #[cfg_attr(feature = "capture", derive(Serialize))]
    255    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
    256    pub struct BlitReason: u32 {
    257        /// Mix-blend-mode on a child that requires isolation.
    258        const BLEND_MODE = 1 << 0;
    259        /// Clip node that _might_ require a surface.
    260        const CLIP = 1 << 1;
    261        /// Preserve-3D requires a surface for plane-splitting.
    262        const PRESERVE3D = 1 << 2;
    263        /// A forced isolation request from gecko.
    264        const FORCED_ISOLATION = 1 << 3;
    265        /// We may need to render the picture into an image and cache it.
    266        const SNAPSHOT = 1 << 4;
    267    }
    268 }
    269 
    270 /// Enum value describing the place of a picture in a 3D context.
    271 #[derive(Clone, Debug)]
    272 #[cfg_attr(feature = "capture", derive(Serialize))]
    273 pub enum Picture3DContext<C> {
    274    /// The picture is not a part of 3D context sub-hierarchy.
    275    Out,
    276    /// The picture is a part of 3D context.
    277    In {
    278        /// Additional data per child for the case of this a root of 3D hierarchy.
    279        root_data: Option<Vec<C>>,
    280        /// The spatial node index of an "ancestor" element, i.e. one
    281        /// that establishes the transformed element's containing block.
    282        ///
    283        /// See CSS spec draft for more details:
    284        /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
    285        ancestor_index: SpatialNodeIndex,
    286        /// Index in the built scene's array of plane splitters.
    287        plane_splitter_index: PlaneSplitterIndex,
    288    },
    289 }
    290 
    291 /// Information about a preserve-3D hierarchy child that has been plane-split
    292 /// and ordered according to the view direction.
    293 #[derive(Clone, Debug)]
    294 #[cfg_attr(feature = "capture", derive(Serialize))]
    295 pub struct OrderedPictureChild {
    296    pub anchor: PlaneSplitAnchor,
    297    pub gpu_address: GpuBufferAddress,
    298 }
    299 
    300 bitflags! {
    301    /// A set of flags describing why a picture may need a backing surface.
    302    #[cfg_attr(feature = "capture", derive(Serialize))]
    303    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
    304    pub struct ClusterFlags: u32 {
    305        /// Whether this cluster is visible when the position node is a backface.
    306        const IS_BACKFACE_VISIBLE = 1;
    307        /// This flag is set during the first pass picture traversal, depending on whether
    308        /// the cluster is visible or not. It's read during the second pass when primitives
    309        /// consult their owning clusters to see if the primitive itself is visible.
    310        const IS_VISIBLE = 2;
    311    }
    312 }
    313 
    314 /// Descriptor for a cluster of primitives. For now, this is quite basic but will be
    315 /// extended to handle more spatial clustering of primitives.
    316 #[cfg_attr(feature = "capture", derive(Serialize))]
    317 pub struct PrimitiveCluster {
    318    /// The positioning node for this cluster.
    319    pub spatial_node_index: SpatialNodeIndex,
    320    /// The bounding rect of the cluster, in the local space of the spatial node.
    321    /// This is used to quickly determine the overall bounding rect for a picture
    322    /// during the first picture traversal, which is needed for local scale
    323    /// determination, and render task size calculations.
    324    bounding_rect: LayoutRect,
    325    /// a part of the cluster that we know to be opaque if any. Does not always
    326    /// describe the entire opaque region, but all content within that rect must
    327    /// be opaque.
    328    pub opaque_rect: LayoutRect,
    329    /// The range of primitive instance indices associated with this cluster.
    330    pub prim_range: Range<usize>,
    331    /// Various flags / state for this cluster.
    332    pub flags: ClusterFlags,
    333 }
    334 
    335 impl PrimitiveCluster {
    336    /// Construct a new primitive cluster for a given positioning node.
    337    fn new(
    338        spatial_node_index: SpatialNodeIndex,
    339        flags: ClusterFlags,
    340        first_instance_index: usize,
    341    ) -> Self {
    342        PrimitiveCluster {
    343            bounding_rect: LayoutRect::zero(),
    344            opaque_rect: LayoutRect::zero(),
    345            spatial_node_index,
    346            flags,
    347            prim_range: first_instance_index..first_instance_index
    348        }
    349    }
    350 
    351    /// Return true if this cluster is compatible with the given params
    352    pub fn is_compatible(
    353        &self,
    354        spatial_node_index: SpatialNodeIndex,
    355        flags: ClusterFlags,
    356        instance_index: usize,
    357    ) -> bool {
    358        self.flags == flags &&
    359        self.spatial_node_index == spatial_node_index &&
    360        instance_index == self.prim_range.end
    361    }
    362 
    363    pub fn prim_range(&self) -> Range<usize> {
    364        self.prim_range.clone()
    365    }
    366 
    367    /// Add a primitive instance to this cluster, at the start or end
    368    fn add_instance(
    369        &mut self,
    370        culling_rect: &LayoutRect,
    371        instance_index: usize,
    372    ) {
    373        debug_assert_eq!(instance_index, self.prim_range.end);
    374        self.bounding_rect = self.bounding_rect.union(culling_rect);
    375        self.prim_range.end += 1;
    376    }
    377 }
    378 
    379 /// A list of primitive instances that are added to a picture
    380 /// This ensures we can keep a list of primitives that
    381 /// are pictures, for a fast initial traversal of the picture
    382 /// tree without walking the instance list.
    383 #[cfg_attr(feature = "capture", derive(Serialize))]
    384 pub struct PrimitiveList {
    385    /// List of primitives grouped into clusters.
    386    pub clusters: Vec<PrimitiveCluster>,
    387    pub child_pictures: Vec<PictureIndex>,
    388    /// The number of Image compositor surfaces that were found when
    389    /// adding prims to this list, which might be rendered as overlays.
    390    pub image_surface_count: usize,
    391    /// The number of YuvImage compositor surfaces that were found when
    392    /// adding prims to this list, which might be rendered as overlays.
    393    pub yuv_image_surface_count: usize,
    394    pub needs_scissor_rect: bool,
    395 }
    396 
    397 impl PrimitiveList {
    398    /// Construct an empty primitive list. This is
    399    /// just used during the take_context / restore_context
    400    /// borrow check dance, which will be removed as the
    401    /// picture traversal pass is completed.
    402    pub fn empty() -> Self {
    403        PrimitiveList {
    404            clusters: Vec::new(),
    405            child_pictures: Vec::new(),
    406            image_surface_count: 0,
    407            yuv_image_surface_count: 0,
    408            needs_scissor_rect: false,
    409        }
    410    }
    411 
    412    pub fn merge(&mut self, other: PrimitiveList) {
    413        self.clusters.extend(other.clusters);
    414        self.child_pictures.extend(other.child_pictures);
    415        self.image_surface_count += other.image_surface_count;
    416        self.yuv_image_surface_count += other.yuv_image_surface_count;
    417        self.needs_scissor_rect |= other.needs_scissor_rect;
    418    }
    419 
    420    /// Add a primitive instance to the end of the list
    421    pub fn add_prim(
    422        &mut self,
    423        prim_instance: PrimitiveInstance,
    424        prim_rect: LayoutRect,
    425        spatial_node_index: SpatialNodeIndex,
    426        prim_flags: PrimitiveFlags,
    427        prim_instances: &mut Vec<PrimitiveInstance>,
    428        clip_tree_builder: &ClipTreeBuilder,
    429    ) {
    430        let mut flags = ClusterFlags::empty();
    431 
    432        // Pictures are always put into a new cluster, to make it faster to
    433        // iterate all pictures in a given primitive list.
    434        match prim_instance.kind {
    435            PrimitiveInstanceKind::Picture { pic_index, .. } => {
    436                self.child_pictures.push(pic_index);
    437            }
    438            PrimitiveInstanceKind::TextRun { .. } => {
    439                self.needs_scissor_rect = true;
    440            }
    441            PrimitiveInstanceKind::YuvImage { .. } => {
    442                // Any YUV image that requests a compositor surface is implicitly
    443                // opaque. Though we might treat this prim as an underlay, which
    444                // doesn't require an overlay surface, we add to the count anyway
    445                // in case we opt to present it as an overlay. This means we may
    446                // be allocating more subslices than we actually need, but it
    447                // gives us maximum flexibility.
    448                if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
    449                    self.yuv_image_surface_count += 1;
    450                }
    451            }
    452            PrimitiveInstanceKind::Image { .. } => {
    453                // For now, we assume that any image that wants a compositor surface
    454                // is transparent, and uses the existing overlay compositor surface
    455                // infrastructure. In future, we could detect opaque images, however
    456                // it's a little bit of work, as scene building doesn't have access
    457                // to the opacity state of an image key at this point.
    458                if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
    459                    self.image_surface_count += 1;
    460                }
    461            }
    462            _ => {}
    463        }
    464 
    465        if prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
    466            flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE);
    467        }
    468 
    469        let clip_leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
    470        let culling_rect = clip_leaf.local_clip_rect
    471            .intersection(&prim_rect)
    472            .unwrap_or_else(LayoutRect::zero);
    473 
    474        let instance_index = prim_instances.len();
    475        prim_instances.push(prim_instance);
    476 
    477        if let Some(cluster) = self.clusters.last_mut() {
    478            if cluster.is_compatible(spatial_node_index, flags, instance_index) {
    479                cluster.add_instance(&culling_rect, instance_index);
    480                return;
    481            }
    482        }
    483 
    484        // Same idea with clusters, using a different distribution.
    485        let clusters_len = self.clusters.len();
    486        if clusters_len == self.clusters.capacity() {
    487            let next_alloc = match clusters_len {
    488                1 ..= 15 => 16 - clusters_len,
    489                16 ..= 127 => 128 - clusters_len,
    490                _ => clusters_len * 2,
    491            };
    492 
    493            self.clusters.reserve(next_alloc);
    494        }
    495 
    496        let mut cluster = PrimitiveCluster::new(
    497            spatial_node_index,
    498            flags,
    499            instance_index,
    500        );
    501 
    502        cluster.add_instance(&culling_rect, instance_index);
    503        self.clusters.push(cluster);
    504    }
    505 
    506    /// Returns true if there are no clusters (and thus primitives)
    507    pub fn is_empty(&self) -> bool {
    508        self.clusters.is_empty()
    509    }
    510 }
    511 
    512 bitflags! {
    513    #[cfg_attr(feature = "capture", derive(Serialize))]
    514    /// Flags describing properties for a given PicturePrimitive
    515    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
    516    pub struct PictureFlags : u8 {
    517        /// This picture is a resolve target (doesn't actually render content itself,
    518        /// will have content copied in to it)
    519        const IS_RESOLVE_TARGET = 1 << 0;
    520        /// This picture establishes a sub-graph, which affects how SurfaceBuilder will
    521        /// set up dependencies in the render task graph
    522        const IS_SUB_GRAPH = 1 << 1;
    523        /// If set, this picture should not apply snapping via changing the raster root
    524        const DISABLE_SNAPPING = 1 << 2;
    525    }
    526 }
    527 
    528 #[cfg_attr(feature = "capture", derive(Serialize))]
    529 pub struct PicturePrimitive {
    530    /// List of primitives, and associated info for this picture.
    531    pub prim_list: PrimitiveList,
    532 
    533    /// If false and transform ends up showing the back of the picture,
    534    /// it will be considered invisible.
    535    pub is_backface_visible: bool,
    536 
    537    /// All render tasks have 0-2 input tasks.
    538    pub primary_render_task_id: Option<RenderTaskId>,
    539    /// If a mix-blend-mode, contains the render task for
    540    /// the readback of the framebuffer that we use to sample
    541    /// from in the mix-blend-mode shader.
    542    /// For drop-shadow filter, this will store the original
    543    /// picture task which would be rendered on screen after
    544    /// blur pass.
    545    /// This is also used by SVGFEBlend, SVGFEComposite and
    546    /// SVGFEDisplacementMap filters.
    547    pub secondary_render_task_id: Option<RenderTaskId>,
    548    /// How this picture should be composited.
    549    /// If None, don't composite - just draw directly on parent surface.
    550    pub composite_mode: Option<PictureCompositeMode>,
    551 
    552    pub raster_config: Option<RasterConfig>,
    553    pub context_3d: Picture3DContext<OrderedPictureChild>,
    554 
    555    // Optional cache handles for storing extra data
    556    // in the GPU cache, depending on the type of
    557    // picture.
    558    pub extra_gpu_data: SmallVec<[GpuBufferAddress; 1]>,
    559 
    560    /// The spatial node index of this picture when it is
    561    /// composited into the parent picture.
    562    pub spatial_node_index: SpatialNodeIndex,
    563 
    564    /// Store the state of the previous local rect
    565    /// for this picture. We need this in order to know when
    566    /// to invalidate segments / drop-shadow gpu cache handles.
    567    pub prev_local_rect: LayoutRect,
    568 
    569    /// If false, this picture needs to (re)build segments
    570    /// if it supports segment rendering. This can occur
    571    /// if the local rect of the picture changes due to
    572    /// transform animation and/or scrolling.
    573    pub segments_are_valid: bool,
    574 
    575    /// Set to true if we know for sure the picture is fully opaque.
    576    pub is_opaque: bool,
    577 
    578    /// Requested raster space for this picture
    579    pub raster_space: RasterSpace,
    580 
    581    /// Flags for this picture primitive
    582    pub flags: PictureFlags,
    583 
    584    /// The lowest common ancestor clip of all of the primitives in this
    585    /// picture, to be ignored when clipping those primitives and applied
    586    /// later when compositing the picture.
    587    pub clip_root: Option<ClipNodeId>,
    588 
    589    /// If provided, cache the content of this picture into an image
    590    /// associated with the image key.
    591    pub snapshot: Option<SnapshotInfo>,
    592 }
    593 
    594 impl PicturePrimitive {
    595    pub fn print<T: PrintTreePrinter>(
    596        &self,
    597        pictures: &[Self],
    598        self_index: PictureIndex,
    599        pt: &mut T,
    600    ) {
    601        pt.new_level(format!("{:?}", self_index));
    602        pt.add_item(format!("cluster_count: {:?}", self.prim_list.clusters.len()));
    603        pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
    604        pt.add_item(format!("raster_config: {:?}", self.raster_config));
    605        pt.add_item(format!("composite_mode: {:?}", self.composite_mode));
    606        pt.add_item(format!("flags: {:?}", self.flags));
    607 
    608        for child_pic_index in &self.prim_list.child_pictures {
    609            pictures[child_pic_index.0].print(pictures, *child_pic_index, pt);
    610        }
    611 
    612        pt.end_level();
    613    }
    614 
    615    pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) {
    616        match self.composite_mode {
    617            Some(PictureCompositeMode::Filter(ref mut filter)) => {
    618                match *filter {
    619                    Filter::Opacity(ref binding, ref mut value) => {
    620                        *value = properties.resolve_float(binding);
    621                    }
    622                    _ => {}
    623                }
    624            }
    625            _ => {}
    626        }
    627    }
    628 
    629    pub fn is_visible(
    630        &self,
    631        spatial_tree: &SpatialTree,
    632    ) -> bool {
    633        if let Some(PictureCompositeMode::Filter(ref filter)) = self.composite_mode {
    634            if !filter.is_visible() {
    635                return false;
    636            }
    637        }
    638 
    639        // For out-of-preserve-3d pictures, the backface visibility is determined by
    640        // the local transform only.
    641        // Note: we aren't taking the transform relative to the parent picture,
    642        // since picture tree can be more dense than the corresponding spatial tree.
    643        if !self.is_backface_visible {
    644            if let Picture3DContext::Out = self.context_3d {
    645                match spatial_tree.get_local_visible_face(self.spatial_node_index) {
    646                    VisibleFace::Front => {}
    647                    VisibleFace::Back => return false,
    648                }
    649            }
    650        }
    651 
    652        true
    653    }
    654 
    655    pub fn new_image(
    656        composite_mode: Option<PictureCompositeMode>,
    657        context_3d: Picture3DContext<OrderedPictureChild>,
    658        prim_flags: PrimitiveFlags,
    659        prim_list: PrimitiveList,
    660        spatial_node_index: SpatialNodeIndex,
    661        raster_space: RasterSpace,
    662        flags: PictureFlags,
    663        snapshot: Option<SnapshotInfo>,
    664    ) -> Self {
    665        PicturePrimitive {
    666            prim_list,
    667            primary_render_task_id: None,
    668            secondary_render_task_id: None,
    669            composite_mode,
    670            raster_config: None,
    671            context_3d,
    672            extra_gpu_data: SmallVec::new(),
    673            is_backface_visible: prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE),
    674            spatial_node_index,
    675            prev_local_rect: LayoutRect::zero(),
    676            segments_are_valid: false,
    677            is_opaque: false,
    678            raster_space,
    679            flags,
    680            clip_root: None,
    681            snapshot,
    682        }
    683    }
    684 
    685    pub fn take_context(
    686        &mut self,
    687        pic_index: PictureIndex,
    688        parent_surface_index: Option<SurfaceIndex>,
    689        parent_subpixel_mode: SubpixelMode,
    690        frame_state: &mut FrameBuildingState,
    691        frame_context: &FrameBuildingContext,
    692        data_stores: &mut DataStores,
    693        scratch: &mut PrimitiveScratchBuffer,
    694        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
    695    ) -> Option<(PictureContext, PictureState, PrimitiveList)> {
    696        frame_state.visited_pictures[pic_index.0] = true;
    697        self.primary_render_task_id = None;
    698        self.secondary_render_task_id = None;
    699 
    700        let dbg_flags = DebugFlags::PICTURE_CACHING_DBG | DebugFlags::PICTURE_BORDERS;
    701        if frame_context.debug_flags.intersects(dbg_flags) {
    702            self.draw_debug_overlay(
    703                parent_surface_index,
    704                frame_state,
    705                frame_context,
    706                tile_caches,
    707                scratch,
    708            );
    709        }
    710 
    711        if !self.is_visible(frame_context.spatial_tree) {
    712            return None;
    713        }
    714 
    715        profile_scope!("take_context");
    716 
    717        let surface_index = match self.raster_config {
    718            Some(ref raster_config) => raster_config.surface_index,
    719            None => parent_surface_index.expect("bug: no parent"),
    720        };
    721        let surface = &frame_state.surfaces[surface_index.0];
    722        let surface_spatial_node_index = surface.surface_spatial_node_index;
    723 
    724        let map_pic_to_world = SpaceMapper::new_with_target(
    725            frame_context.root_spatial_node_index,
    726            surface_spatial_node_index,
    727            frame_context.global_screen_world_rect,
    728            frame_context.spatial_tree,
    729        );
    730 
    731        let map_pic_to_vis = SpaceMapper::new_with_target(
    732            // TODO: switch from root to raster space.
    733            frame_context.root_spatial_node_index,
    734            surface_spatial_node_index,
    735            surface.culling_rect,
    736            frame_context.spatial_tree,
    737        );
    738 
    739        // TODO: When moving VisRect to raster space, compute the picture
    740        // bounds by projecting the parent surface's culling rect into the
    741        // current surface's raster space.
    742        let pic_bounds = map_pic_to_world
    743            .unmap(&map_pic_to_world.bounds)
    744            .unwrap_or_else(PictureRect::max_rect);
    745 
    746        let map_local_to_pic = SpaceMapper::new(
    747            surface_spatial_node_index,
    748            pic_bounds,
    749        );
    750 
    751        match self.raster_config {
    752            Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => {
    753                prepare_tiled_picture_surface(
    754                    surface_index,
    755                    slice_id,
    756                    surface_spatial_node_index,
    757                    &map_pic_to_world,
    758                    frame_context,
    759                    frame_state,
    760                    tile_caches,
    761                );
    762            }
    763            Some(ref mut raster_config) => {
    764                let (pic_rect, force_scissor_rect) = {
    765                    let surface = &frame_state.surfaces[raster_config.surface_index.0];
    766                    (surface.clipped_local_rect, surface.force_scissor_rect)
    767                };
    768 
    769                let parent_surface_index = parent_surface_index.expect("bug: no parent for child surface");
    770 
    771                // Layout space for the picture is picture space from the
    772                // perspective of its child primitives.
    773                let local_rect = pic_rect * Scale::new(1.0);
    774 
    775                // If the precise rect changed since last frame, we need to invalidate
    776                // any segments and gpu cache handles for drop-shadows.
    777                // TODO(gw): Requiring storage of the `prev_precise_local_rect` here
    778                //           is a total hack. It's required because `prev_precise_local_rect`
    779                //           gets written to twice (during initial vis pass and also during
    780                //           prepare pass). The proper longer term fix for this is to make
    781                //           use of the conservative picture rect for segmenting (which should
    782                //           be done during scene building).
    783                if local_rect != self.prev_local_rect {
    784                    // Invalidate any segments built for this picture, since the local
    785                    // rect has changed.
    786                    self.segments_are_valid = false;
    787                    self.prev_local_rect = local_rect;
    788                }
    789 
    790                let max_surface_size = frame_context
    791                    .fb_config
    792                    .max_surface_override
    793                    .unwrap_or(MAX_SURFACE_SIZE) as f32;
    794 
    795                let surface_rects = match get_surface_rects(
    796                    raster_config.surface_index,
    797                    &raster_config.composite_mode,
    798                    parent_surface_index,
    799                    &mut frame_state.surfaces,
    800                    frame_context.spatial_tree,
    801                    max_surface_size,
    802                    force_scissor_rect,
    803                ) {
    804                    Some(rects) => rects,
    805                    None => return None,
    806                };
    807 
    808                if let PictureCompositeMode::IntermediateSurface = raster_config.composite_mode {
    809                    if !scratch.required_sub_graphs.contains(&pic_index) {
    810                        return None;
    811                    }
    812                }
    813 
    814                let can_use_shared_surface = !self.flags.contains(PictureFlags::IS_RESOLVE_TARGET);
    815                let (surface_descriptor, render_tasks) = prepare_composite_mode(
    816                    &raster_config.composite_mode,
    817                    surface_index,
    818                    parent_surface_index,
    819                    &surface_rects,
    820                    &self.snapshot,
    821                    can_use_shared_surface,
    822                    frame_context,
    823                    frame_state,
    824                    data_stores,
    825                    &mut self.extra_gpu_data,
    826                );
    827 
    828                self.primary_render_task_id = render_tasks[0];
    829                self.secondary_render_task_id = render_tasks[1];
    830 
    831                let is_sub_graph = self.flags.contains(PictureFlags::IS_SUB_GRAPH);
    832 
    833                frame_state.surface_builder.push_surface(
    834                    raster_config.surface_index,
    835                    is_sub_graph,
    836                    surface_rects.clipped_local,
    837                    Some(surface_descriptor),
    838                    frame_state.surfaces,
    839                    frame_state.rg_builder,
    840                );
    841            }
    842            None => {}
    843        };
    844 
    845        let state = PictureState {
    846            map_local_to_pic,
    847            map_pic_to_vis,
    848        };
    849 
    850        let mut dirty_region_count = 0;
    851 
    852        // If this is a picture cache, push the dirty region to ensure any
    853        // child primitives are culled and clipped to the dirty rect(s).
    854        if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) = self.raster_config {
    855            let dirty_region = tile_caches[&slice_id].dirty_region.clone();
    856            frame_state.push_dirty_region(dirty_region);
    857 
    858            dirty_region_count += 1;
    859        }
    860 
    861        let subpixel_mode = compute_subpixel_mode(
    862            &self.raster_config,
    863            tile_caches,
    864            parent_subpixel_mode
    865        );
    866 
    867        let context = PictureContext {
    868            pic_index,
    869            raster_spatial_node_index: frame_state.surfaces[surface_index.0].raster_spatial_node_index,
    870            // TODO: switch the visibility spatial node from the root to raster space.
    871            visibility_spatial_node_index: frame_context.root_spatial_node_index,
    872            surface_spatial_node_index,
    873            surface_index,
    874            dirty_region_count,
    875            subpixel_mode,
    876        };
    877 
    878        let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
    879 
    880        Some((context, state, prim_list))
    881    }
    882 
    883    pub fn restore_context(
    884        &mut self,
    885        pic_index: PictureIndex,
    886        prim_list: PrimitiveList,
    887        context: PictureContext,
    888        prim_instances: &[PrimitiveInstance],
    889        frame_context: &FrameBuildingContext,
    890        frame_state: &mut FrameBuildingState,
    891    ) {
    892        // Pop any dirty regions this picture set
    893        for _ in 0 .. context.dirty_region_count {
    894            frame_state.pop_dirty_region();
    895        }
    896 
    897        if self.raster_config.is_some() {
    898            frame_state.surface_builder.pop_surface(
    899                pic_index,
    900                frame_state.rg_builder,
    901                frame_state.cmd_buffers,
    902            );
    903        }
    904 
    905        if let Picture3DContext::In { root_data: Some(ref mut list), plane_splitter_index, .. } = self.context_3d {
    906            let splitter = &mut frame_state.plane_splitters[plane_splitter_index.0];
    907 
    908            // Resolve split planes via BSP
    909            PicturePrimitive::resolve_split_planes(
    910                splitter,
    911                list,
    912                &mut frame_state.frame_gpu_data.f32,
    913                &frame_context.spatial_tree,
    914            );
    915 
    916            // Add the child prims to the relevant command buffers
    917            let mut cmd_buffer_targets = Vec::new();
    918            for child in list {
    919                let child_prim_instance = &prim_instances[child.anchor.instance_index.0 as usize];
    920 
    921                if frame_state.surface_builder.get_cmd_buffer_targets_for_prim(
    922                    &child_prim_instance.vis,
    923                    &mut cmd_buffer_targets,
    924                ) {
    925                    let prim_cmd = PrimitiveCommand::complex(
    926                        child.anchor.instance_index,
    927                        child.gpu_address
    928                    );
    929 
    930                    frame_state.push_prim(
    931                        &prim_cmd,
    932                        child.anchor.spatial_node_index,
    933                        &cmd_buffer_targets,
    934                    );
    935                }
    936            }
    937        }
    938 
    939        self.prim_list = prim_list;
    940    }
    941 
    942    /// Add a primitive instance to the plane splitter. The function would generate
    943    /// an appropriate polygon, clip it against the frustum, and register with the
    944    /// given plane splitter.
    945    pub fn add_split_plane(
    946        splitter: &mut PlaneSplitter,
    947        spatial_tree: &SpatialTree,
    948        prim_spatial_node_index: SpatialNodeIndex,
    949        // TODO: this is called "visibility" while transitioning from world to raster
    950        // space.
    951        visibility_spatial_node_index: SpatialNodeIndex,
    952        original_local_rect: LayoutRect,
    953        combined_local_clip_rect: &LayoutRect,
    954        dirty_rect: VisRect,
    955        plane_split_anchor: PlaneSplitAnchor,
    956    ) -> bool {
    957        let transform = spatial_tree.get_relative_transform(
    958            prim_spatial_node_index,
    959            visibility_spatial_node_index
    960        );
    961 
    962        let matrix = transform.clone().into_transform().cast().to_untyped();
    963 
    964        // Apply the local clip rect here, before splitting. This is
    965        // because the local clip rect can't be applied in the vertex
    966        // shader for split composites, since we are drawing polygons
    967        // rather that rectangles. The interpolation still works correctly
    968        // since we determine the UVs by doing a bilerp with a factor
    969        // from the original local rect.
    970        let local_rect = match original_local_rect
    971            .intersection(combined_local_clip_rect)
    972        {
    973            Some(rect) => rect.cast(),
    974            None => return false,
    975        };
    976        let dirty_rect = dirty_rect.cast();
    977 
    978        match transform {
    979            CoordinateSpaceMapping::Local => {
    980                let polygon = Polygon::from_rect(
    981                    local_rect.to_rect() * Scale::new(1.0),
    982                    plane_split_anchor,
    983                );
    984                splitter.add(polygon);
    985            }
    986            CoordinateSpaceMapping::ScaleOffset(scale_offset) if scale_offset.scale == Vector2D::new(1.0, 1.0) => {
    987                let inv_matrix = scale_offset.inverse().to_transform().cast();
    988                let polygon = Polygon::from_transformed_rect_with_inverse(
    989                    local_rect.to_rect().to_untyped(),
    990                    &matrix,
    991                    &inv_matrix,
    992                    plane_split_anchor,
    993                ).unwrap();
    994                splitter.add(polygon);
    995            }
    996            CoordinateSpaceMapping::ScaleOffset(_) |
    997            CoordinateSpaceMapping::Transform(_) => {
    998                let mut clipper = Clipper::new();
    999                let results = clipper.clip_transformed(
   1000                    Polygon::from_rect(
   1001                        local_rect.to_rect().to_untyped(),
   1002                        plane_split_anchor,
   1003                    ),
   1004                    &matrix,
   1005                    Some(dirty_rect.to_rect().to_untyped()),
   1006                );
   1007                if let Ok(results) = results {
   1008                    for poly in results {
   1009                        splitter.add(poly);
   1010                    }
   1011                }
   1012            }
   1013        }
   1014 
   1015        true
   1016    }
   1017 
   1018    fn resolve_split_planes(
   1019        splitter: &mut PlaneSplitter,
   1020        ordered: &mut Vec<OrderedPictureChild>,
   1021        gpu_buffer: &mut GpuBufferBuilderF,
   1022        spatial_tree: &SpatialTree,
   1023    ) {
   1024        ordered.clear();
   1025 
   1026        // Process the accumulated split planes and order them for rendering.
   1027        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
   1028        let sorted = splitter.sort(vec3(0.0, 0.0, 1.0));
   1029        ordered.reserve(sorted.len());
   1030        for poly in sorted {
   1031            let transform = match spatial_tree
   1032                .get_world_transform(poly.anchor.spatial_node_index)
   1033                .inverse()
   1034            {
   1035                Some(transform) => transform.into_transform(),
   1036                // logging this would be a bit too verbose
   1037                None => continue,
   1038            };
   1039 
   1040            let local_points = [
   1041                transform.transform_point3d(poly.points[0].cast_unit().to_f32()),
   1042                transform.transform_point3d(poly.points[1].cast_unit().to_f32()),
   1043                transform.transform_point3d(poly.points[2].cast_unit().to_f32()),
   1044                transform.transform_point3d(poly.points[3].cast_unit().to_f32()),
   1045            ];
   1046 
   1047            // If any of the points are un-transformable, just drop this
   1048            // plane from drawing.
   1049            if local_points.iter().any(|p| p.is_none()) {
   1050                continue;
   1051            }
   1052 
   1053            let p0 = local_points[0].unwrap();
   1054            let p1 = local_points[1].unwrap();
   1055            let p2 = local_points[2].unwrap();
   1056            let p3 = local_points[3].unwrap();
   1057 
   1058            let mut writer = gpu_buffer.write_blocks(2);
   1059            writer.push_one([p0.x, p0.y, p1.x, p1.y]);
   1060            writer.push_one([p2.x, p2.y, p3.x, p3.y]);
   1061            let gpu_address = writer.finish();
   1062 
   1063            ordered.push(OrderedPictureChild {
   1064                anchor: poly.anchor,
   1065                gpu_address,
   1066            });
   1067        }
   1068    }
   1069 
   1070    /// Called during initial picture traversal, before we know the
   1071    /// bounding rect of children. It is possible to determine the
   1072    /// surface / raster config now though.
   1073    pub fn assign_surface(
   1074        &mut self,
   1075        frame_context: &FrameBuildingContext,
   1076        parent_surface_index: Option<SurfaceIndex>,
   1077        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
   1078        surfaces: &mut Vec<SurfaceInfo>,
   1079    ) -> Option<SurfaceIndex> {
   1080        // Reset raster config in case we early out below.
   1081        self.raster_config = None;
   1082 
   1083        match self.composite_mode {
   1084            Some(ref composite_mode) => {
   1085                let surface_spatial_node_index = self.spatial_node_index;
   1086 
   1087                // Currently, we ensure that the scaling factor is >= 1.0 as a smaller scale factor can result in blurry output.
   1088                let mut min_scale;
   1089                let mut max_scale = 1.0e32;
   1090 
   1091                // If a raster root is established, this surface should be scaled based on the scale factors of the surface raster to parent raster transform.
   1092                // This scaling helps ensure that the content in this surface does not become blurry or pixelated when composited in the parent surface.
   1093 
   1094                let world_scale_factors = match parent_surface_index {
   1095                    Some(parent_surface_index) => {
   1096                        let parent_surface = &surfaces[parent_surface_index.0];
   1097 
   1098                        let local_to_surface = frame_context
   1099                            .spatial_tree
   1100                            .get_relative_transform(
   1101                                surface_spatial_node_index,
   1102                                parent_surface.surface_spatial_node_index,
   1103                            );
   1104 
   1105                        // Since we can't determine reasonable scale factors for transforms
   1106                        // with perspective, just use a scale of (1,1) for now, which is
   1107                        // what Gecko does when it choosed to supplies a scale factor anyway.
   1108                        // In future, we might be able to improve the quality here by taking
   1109                        // into account the screen rect after clipping, but for now this gives
   1110                        // better results than just taking the matrix scale factors.
   1111                        let scale_factors = if local_to_surface.is_perspective() {
   1112                            (1.0, 1.0)
   1113                        } else {
   1114                            local_to_surface.scale_factors()
   1115                        };
   1116 
   1117                        let scale_factors = (
   1118                            scale_factors.0 * parent_surface.world_scale_factors.0,
   1119                            scale_factors.1 * parent_surface.world_scale_factors.1,
   1120                        );
   1121 
   1122                        scale_factors
   1123                    }
   1124                    None => {
   1125                        let local_to_surface_scale_factors = frame_context
   1126                            .spatial_tree
   1127                            .get_relative_transform(
   1128                                surface_spatial_node_index,
   1129                                frame_context.spatial_tree.root_reference_frame_index(),
   1130                            )
   1131                            .scale_factors();
   1132 
   1133                        let scale_factors = (
   1134                            local_to_surface_scale_factors.0,
   1135                            local_to_surface_scale_factors.1,
   1136                        );
   1137 
   1138                        scale_factors
   1139                    }
   1140                };
   1141 
   1142                // TODO(gw): For now, we disable snapping on any sub-graph, as that implies
   1143                //           that the spatial / raster node must be the same as the parent
   1144                //           surface. In future, we may be able to support snapping in these
   1145                //           cases (if it's even useful?) or perhaps add a ENABLE_SNAPPING
   1146                //           picture flag, if the IS_SUB_GRAPH is ever useful in a different
   1147                //           context.
   1148                let allow_snapping = !self.flags.contains(PictureFlags::DISABLE_SNAPPING);
   1149 
   1150                // For some primitives (e.g. text runs) we can't rely on the bounding rect being
   1151                // exactly correct. For these cases, ensure we set a scissor rect when drawing
   1152                // this picture to a surface.
   1153                // TODO(gw) In future, we may be able to improve how the text run bounding rect is
   1154                // calculated so that we don't need to do this. We could either fix Gecko up to
   1155                // provide an exact bounds, or we could calculate the bounding rect internally in
   1156                // WR, which would be easier to do efficiently once we have retained text runs
   1157                // as part of the planned frame-tree interface changes.
   1158                let force_scissor_rect = self.prim_list.needs_scissor_rect;
   1159 
   1160                // Check if there is perspective or if an SVG filter is applied, and thus whether a new
   1161                // rasterization root should be established.
   1162                let (device_pixel_scale, raster_spatial_node_index, local_scale, world_scale_factors) = match composite_mode {
   1163                    PictureCompositeMode::TileCache { slice_id } => {
   1164                        let tile_cache = tile_caches.get_mut(&slice_id).unwrap();
   1165 
   1166                        // Get the complete scale-offset from local space to device space
   1167                        let local_to_device = get_relative_scale_offset(
   1168                            tile_cache.spatial_node_index,
   1169                            frame_context.root_spatial_node_index,
   1170                            frame_context.spatial_tree,
   1171                        );
   1172                        let local_to_cur_raster_scale = local_to_device.scale.x / tile_cache.current_raster_scale;
   1173 
   1174                        // We only update the raster scale if we're in high quality zoom mode, or there is no
   1175                        // pinch-zoom active, or the zoom has doubled or halved since the raster scale was
   1176                        // last updated. During a low-quality zoom we therefore typically retain the previous
   1177                        // scale factor, which avoids expensive re-rasterizations, except for when the zoom
   1178                        // has become too large or too small when we re-rasterize to avoid bluriness or a
   1179                        // proliferation of picture cache tiles. When the zoom ends we select a high quality
   1180                        // scale factor for the next frame to be drawn.
   1181                        if !frame_context.fb_config.low_quality_pinch_zoom
   1182                            || !frame_context
   1183                                .spatial_tree.get_spatial_node(tile_cache.spatial_node_index)
   1184                                .is_ancestor_or_self_zooming
   1185                            || local_to_cur_raster_scale <= 0.5
   1186                            || local_to_cur_raster_scale >= 2.0
   1187                        {
   1188                            tile_cache.current_raster_scale = local_to_device.scale.x;
   1189                        }
   1190 
   1191                        // We may need to minify when zooming out picture cache tiles
   1192                        min_scale = 0.0;
   1193 
   1194                        if frame_context.fb_config.low_quality_pinch_zoom {
   1195                            // Force the scale for this tile cache to be the currently selected
   1196                            // local raster scale, so we don't need to rasterize tiles during
   1197                            // the pinch-zoom.
   1198                            min_scale = tile_cache.current_raster_scale;
   1199                            max_scale = tile_cache.current_raster_scale;
   1200                        }
   1201 
   1202                        // Pick the largest scale factor of the transform for the scaling factor.
   1203                        let scaling_factor = world_scale_factors.0.max(world_scale_factors.1).max(min_scale).min(max_scale);
   1204 
   1205                        let device_pixel_scale = Scale::new(scaling_factor);
   1206 
   1207                        (device_pixel_scale, surface_spatial_node_index, (1.0, 1.0), world_scale_factors)
   1208                    }
   1209                    _ => {
   1210                        let surface_spatial_node = frame_context.spatial_tree.get_spatial_node(surface_spatial_node_index);
   1211 
   1212                        let enable_snapping =
   1213                            allow_snapping &&
   1214                            surface_spatial_node.coordinate_system_id == CoordinateSystemId::root() &&
   1215                            surface_spatial_node.snapping_transform.is_some();
   1216 
   1217                        if enable_snapping {
   1218                            let raster_spatial_node_index = frame_context.spatial_tree.root_reference_frame_index();
   1219 
   1220                            let local_to_raster_transform = frame_context
   1221                                .spatial_tree
   1222                                .get_relative_transform(
   1223                                    self.spatial_node_index,
   1224                                    raster_spatial_node_index,
   1225                                );
   1226 
   1227                            let local_scale = local_to_raster_transform.scale_factors();
   1228 
   1229                            (Scale::new(1.0), raster_spatial_node_index, local_scale, (1.0, 1.0))
   1230                        } else {
   1231                            // If client supplied a specific local scale, use that instead of
   1232                            // estimating from parent transform
   1233                            let world_scale_factors = match self.raster_space {
   1234                                RasterSpace::Screen => world_scale_factors,
   1235                                RasterSpace::Local(scale) => (scale, scale),
   1236                            };
   1237 
   1238                            let device_pixel_scale = Scale::new(
   1239                                world_scale_factors.0.max(world_scale_factors.1).min(max_scale)
   1240                            );
   1241 
   1242                            (device_pixel_scale, surface_spatial_node_index, (1.0, 1.0), world_scale_factors)
   1243                        }
   1244                    }
   1245                };
   1246 
   1247                let surface = SurfaceInfo::new(
   1248                    surface_spatial_node_index,
   1249                    raster_spatial_node_index,
   1250                    frame_context.global_screen_world_rect,
   1251                    &frame_context.spatial_tree,
   1252                    device_pixel_scale,
   1253                    world_scale_factors,
   1254                    local_scale,
   1255                    allow_snapping,
   1256                    force_scissor_rect,
   1257                );
   1258 
   1259                let surface_index = SurfaceIndex(surfaces.len());
   1260 
   1261                surfaces.push(surface);
   1262 
   1263                self.raster_config = Some(RasterConfig {
   1264                    composite_mode: composite_mode.clone(),
   1265                    surface_index,
   1266                });
   1267 
   1268                Some(surface_index)
   1269            }
   1270            None => {
   1271                None
   1272            }
   1273        }
   1274    }
   1275 
   1276    /// Called after updating child pictures during the initial
   1277    /// picture traversal. Bounding rects are propagated from
   1278    /// child pictures up to parent picture surfaces, so that the
   1279    /// parent bounding rect includes any dynamic picture bounds.
   1280    pub fn propagate_bounding_rect(
   1281        &mut self,
   1282        surface_index: SurfaceIndex,
   1283        parent_surface_index: Option<SurfaceIndex>,
   1284        surfaces: &mut [SurfaceInfo],
   1285        frame_context: &FrameBuildingContext,
   1286    ) {
   1287        let surface = &mut surfaces[surface_index.0];
   1288 
   1289        for cluster in &mut self.prim_list.clusters {
   1290            cluster.flags.remove(ClusterFlags::IS_VISIBLE);
   1291 
   1292            // Skip the cluster if backface culled.
   1293            if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) {
   1294                // For in-preserve-3d primitives and pictures, the backface visibility is
   1295                // evaluated relative to the containing block.
   1296                if let Picture3DContext::In { ancestor_index, .. } = self.context_3d {
   1297                    let mut face = VisibleFace::Front;
   1298                    frame_context.spatial_tree.get_relative_transform_with_face(
   1299                        cluster.spatial_node_index,
   1300                        ancestor_index,
   1301                        Some(&mut face),
   1302                    );
   1303                    if face == VisibleFace::Back {
   1304                        continue
   1305                    }
   1306                }
   1307            }
   1308 
   1309            // No point including this cluster if it can't be transformed
   1310            let spatial_node = &frame_context
   1311                .spatial_tree
   1312                .get_spatial_node(cluster.spatial_node_index);
   1313            if !spatial_node.invertible {
   1314                continue;
   1315            }
   1316 
   1317            // Map the cluster bounding rect into the space of the surface, and
   1318            // include it in the surface bounding rect.
   1319            surface.map_local_to_picture.set_target_spatial_node(
   1320                cluster.spatial_node_index,
   1321                frame_context.spatial_tree,
   1322            );
   1323 
   1324            // Mark the cluster visible, since it passed the invertible and
   1325            // backface checks.
   1326            cluster.flags.insert(ClusterFlags::IS_VISIBLE);
   1327            if let Some(cluster_rect) = surface.map_local_to_picture.map(&cluster.bounding_rect) {
   1328                surface.unclipped_local_rect = surface.unclipped_local_rect.union(&cluster_rect);
   1329            }
   1330        }
   1331 
   1332        // If this picture establishes a surface, then map the surface bounding
   1333        // rect into the parent surface coordinate space, and propagate that up
   1334        // to the parent.
   1335        if let Some(ref mut raster_config) = self.raster_config {
   1336            // Propagate up to parent surface, now that we know this surface's static rect
   1337            if let Some(parent_surface_index) = parent_surface_index {
   1338                let surface_rect = raster_config.composite_mode.get_coverage(
   1339                    surface,
   1340                    Some(surface.unclipped_local_rect.cast_unit()),
   1341                );
   1342 
   1343                let parent_surface = &mut surfaces[parent_surface_index.0];
   1344                parent_surface.map_local_to_picture.set_target_spatial_node(
   1345                    self.spatial_node_index,
   1346                    frame_context.spatial_tree,
   1347                );
   1348 
   1349                // Drop shadows draw both a content and shadow rect, so need to expand the local
   1350                // rect of any surfaces to be composited in parent surfaces correctly.
   1351 
   1352                if let Some(parent_surface_rect) = parent_surface
   1353                    .map_local_to_picture
   1354                    .map(&surface_rect)
   1355                {
   1356                    parent_surface.unclipped_local_rect =
   1357                        parent_surface.unclipped_local_rect.union(&parent_surface_rect);
   1358                }
   1359            }
   1360        }
   1361    }
   1362 
   1363    pub fn write_gpu_blocks(
   1364        &mut self,
   1365        frame_state: &mut FrameBuildingState,
   1366        data_stores: &mut DataStores,
   1367    ) {
   1368        let raster_config = match self.raster_config {
   1369            Some(ref mut raster_config) => raster_config,
   1370            None => {
   1371                return;
   1372            }
   1373        };
   1374 
   1375        raster_config.composite_mode.write_gpu_blocks(
   1376            &frame_state.surfaces[raster_config.surface_index.0],
   1377            &mut frame_state.frame_gpu_data,
   1378            data_stores,
   1379            &mut self.extra_gpu_data
   1380        );
   1381    }
   1382 
   1383    #[cold]
   1384    fn draw_debug_overlay(
   1385        &self,
   1386        parent_surface_index: Option<SurfaceIndex>,
   1387        frame_state: &FrameBuildingState,
   1388        frame_context: &FrameBuildingContext,
   1389        tile_caches: &FastHashMap<SliceId, Box<TileCacheInstance>>,
   1390        scratch: &mut PrimitiveScratchBuffer,
   1391    ) {
   1392        fn draw_debug_border(
   1393            local_rect: &PictureRect,
   1394            thickness: i32,
   1395            pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
   1396            global_device_pixel_scale: DevicePixelScale,
   1397            color: ColorF,
   1398            scratch: &mut PrimitiveScratchBuffer,
   1399        ) {
   1400            if let Some(world_rect) = pic_to_world_mapper.map(&local_rect) {
   1401                let device_rect = world_rect * global_device_pixel_scale;
   1402                scratch.push_debug_rect(
   1403                    device_rect,
   1404                    thickness,
   1405                    color,
   1406                    ColorF::TRANSPARENT,
   1407                );
   1408            }
   1409        }
   1410 
   1411        let flags = frame_context.debug_flags;
   1412        let draw_borders = flags.contains(DebugFlags::PICTURE_BORDERS);
   1413        let draw_tile_dbg = flags.contains(DebugFlags::PICTURE_CACHING_DBG);
   1414 
   1415        let surface_index = match &self.raster_config {
   1416            Some(raster_config) => raster_config.surface_index,
   1417            None => parent_surface_index.expect("bug: no parent"),
   1418        };
   1419        let surface_spatial_node_index = frame_state
   1420            .surfaces[surface_index.0]
   1421            .surface_spatial_node_index;
   1422 
   1423        let map_pic_to_world = SpaceMapper::new_with_target(
   1424            frame_context.root_spatial_node_index,
   1425            surface_spatial_node_index,
   1426            frame_context.global_screen_world_rect,
   1427            frame_context.spatial_tree,
   1428        );
   1429 
   1430        let Some(raster_config) = &self.raster_config else {
   1431            return;
   1432        };
   1433 
   1434        if draw_borders {
   1435            let layer_color;
   1436            if let PictureCompositeMode::TileCache { slice_id } = &raster_config.composite_mode {
   1437                // Tiled picture;
   1438                layer_color = ColorF::new(0.0, 1.0, 0.0, 0.8);
   1439 
   1440                let Some(tile_cache) = tile_caches.get(&slice_id) else {
   1441                    return;
   1442                };
   1443 
   1444                // Draw a rectangle for each tile.
   1445                for (_, sub_slice) in tile_cache.sub_slices.iter().enumerate() {
   1446                    for tile in sub_slice.tiles.values() {
   1447                        if !tile.is_visible {
   1448                            continue;
   1449                        }
   1450                        let rect = tile.cached_surface.local_rect.intersection(&tile_cache.local_rect);
   1451                        if let Some(rect) = rect {
   1452                            draw_debug_border(
   1453                                &rect,
   1454                                1,
   1455                                &map_pic_to_world,
   1456                                frame_context.global_device_pixel_scale,
   1457                                ColorF::new(0.0, 1.0, 0.0, 0.2),
   1458                                scratch,
   1459                            );
   1460                        }
   1461                    }
   1462                }
   1463            } else {
   1464                // Non-tiled picture
   1465                layer_color = ColorF::new(1.0, 0.0, 0.0, 0.5);
   1466            }
   1467 
   1468            // Draw a rectangle for the whole picture.
   1469            let pic_rect = frame_state
   1470                .surfaces[raster_config.surface_index.0]
   1471                .unclipped_local_rect;
   1472 
   1473            draw_debug_border(
   1474                &pic_rect,
   1475                3,
   1476                &map_pic_to_world,
   1477                frame_context.global_device_pixel_scale,
   1478                layer_color,
   1479                scratch,
   1480            );
   1481        }
   1482 
   1483        if draw_tile_dbg && self.is_visible(frame_context.spatial_tree) {
   1484            if let PictureCompositeMode::TileCache { slice_id } = &raster_config.composite_mode {
   1485                let Some(tile_cache) = tile_caches.get(&slice_id) else {
   1486                    return;
   1487                };
   1488                for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter().enumerate() {
   1489                    for tile in sub_slice.tiles.values() {
   1490                        if !tile.is_visible {
   1491                            continue;
   1492                        }
   1493                        tile.cached_surface.root.draw_debug_rects(
   1494                            &map_pic_to_world,
   1495                            tile.is_opaque,
   1496                            tile.cached_surface.current_descriptor.local_valid_rect,
   1497                            scratch,
   1498                            frame_context.global_device_pixel_scale,
   1499                        );
   1500 
   1501                        let label_offset = DeviceVector2D::new(
   1502                            20.0 + sub_slice_index as f32 * 20.0,
   1503                            30.0 + sub_slice_index as f32 * 20.0,
   1504                        );
   1505                        let tile_device_rect = tile.world_tile_rect
   1506                            * frame_context.global_device_pixel_scale;
   1507 
   1508                        if tile_device_rect.height() >= label_offset.y {
   1509                            let surface = tile.surface.as_ref().expect("no tile surface set!");
   1510 
   1511                            scratch.push_debug_string(
   1512                                tile_device_rect.min + label_offset,
   1513                                debug_colors::RED,
   1514                                format!("{:?}: s={} is_opaque={} surface={} sub={}",
   1515                                        tile.id,
   1516                                        tile_cache.slice,
   1517                                        tile.is_opaque,
   1518                                        surface.kind(),
   1519                                        sub_slice_index,
   1520                                ),
   1521                            );
   1522                        }
   1523                    }
   1524                }
   1525            }
   1526        }
   1527    }
   1528 }
   1529 
   1530 pub fn get_relative_scale_offset(
   1531    child_spatial_node_index: SpatialNodeIndex,
   1532    parent_spatial_node_index: SpatialNodeIndex,
   1533    spatial_tree: &SpatialTree,
   1534 ) -> ScaleOffset {
   1535    let transform = spatial_tree.get_relative_transform(
   1536        child_spatial_node_index,
   1537        parent_spatial_node_index,
   1538    );
   1539    let mut scale_offset = match transform {
   1540        CoordinateSpaceMapping::Local => ScaleOffset::identity(),
   1541        CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset,
   1542        CoordinateSpaceMapping::Transform(m) => {
   1543            ScaleOffset::from_transform(&m).expect("bug: pictures caches don't support complex transforms")
   1544        }
   1545    };
   1546 
   1547    // Compositors expect things to be aligned on device pixels. Logic at a higher level ensures that is
   1548    // true, but floating point inaccuracy can sometimes result in small differences, so remove
   1549    // them here.
   1550    scale_offset.offset = scale_offset.offset.round();
   1551 
   1552    scale_offset
   1553 }
   1554 
   1555 /// Update dirty rects, ensure that tiles have backing surfaces and build
   1556 /// the tile render tasks.
   1557 fn prepare_tiled_picture_surface(
   1558    surface_index: SurfaceIndex,
   1559    slice_id: SliceId,
   1560    surface_spatial_node_index: SpatialNodeIndex,
   1561    map_pic_to_world: &SpaceMapper<PicturePixel, WorldPixel>,
   1562    frame_context: &FrameBuildingContext,
   1563    frame_state: &mut FrameBuildingState,
   1564    tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
   1565 ) {
   1566    let tile_cache = tile_caches.get_mut(&slice_id).unwrap();
   1567    let mut debug_info = SliceDebugInfo::new();
   1568    let mut surface_render_tasks = FastHashMap::default();
   1569    let mut surface_local_dirty_rect = PictureRect::zero();
   1570    let device_pixel_scale = frame_state
   1571        .surfaces[surface_index.0]
   1572        .device_pixel_scale;
   1573    let mut at_least_one_tile_visible = false;
   1574 
   1575    // Get the overall world space rect of the picture cache. Used to clip
   1576    // the tile rects below for occlusion testing to the relevant area.
   1577    let world_clip_rect = map_pic_to_world
   1578        .map(&tile_cache.local_clip_rect)
   1579        .expect("bug: unable to map clip rect")
   1580        .round();
   1581    let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
   1582 
   1583    for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter_mut().enumerate() {
   1584        for tile in sub_slice.tiles.values_mut() {
   1585            // Ensure that the dirty rect doesn't extend outside the local valid rect.
   1586            tile.cached_surface.local_dirty_rect = tile.cached_surface.local_dirty_rect
   1587                .intersection(&tile.cached_surface.current_descriptor.local_valid_rect)
   1588                .unwrap_or_else(|| { tile.cached_surface.is_valid = true; PictureRect::zero() });
   1589 
   1590            let valid_rect = frame_state.composite_state.get_surface_rect(
   1591                &tile.cached_surface.current_descriptor.local_valid_rect,
   1592                &tile.cached_surface.local_rect,
   1593                tile_cache.transform_index,
   1594            ).to_i32();
   1595 
   1596            let scissor_rect = frame_state.composite_state.get_surface_rect(
   1597                &tile.cached_surface.local_dirty_rect,
   1598                &tile.cached_surface.local_rect,
   1599                tile_cache.transform_index,
   1600            ).to_i32().intersection(&valid_rect).unwrap_or_else(|| { Box2D::zero() });
   1601 
   1602            if tile.is_visible {
   1603                // Get the world space rect that this tile will actually occupy on screen
   1604                let world_draw_rect = world_clip_rect.intersection(&tile.world_valid_rect);
   1605 
   1606                // If that draw rect is occluded by some set of tiles in front of it,
   1607                // then mark it as not visible and skip drawing. When it's not occluded
   1608                // it will fail this test, and get rasterized by the render task setup
   1609                // code below.
   1610                match world_draw_rect {
   1611                    Some(world_draw_rect) => {
   1612                        let check_occluded_tiles = match frame_state.composite_state.compositor_kind {
   1613                            CompositorKind::Layer { .. } => true,
   1614                            CompositorKind::Native { .. } | CompositorKind::Draw { .. } => {
   1615                                // Only check for occlusion on visible tiles that are fixed position.
   1616                                tile_cache.spatial_node_index == frame_context.root_spatial_node_index
   1617                            }
   1618                        };
   1619                        if check_occluded_tiles &&
   1620                           frame_state.composite_state.occluders.is_tile_occluded(tile.z_id, world_draw_rect) {
   1621                            // If this tile has an allocated native surface, free it, since it's completely
   1622                            // occluded. We will need to re-allocate this surface if it becomes visible,
   1623                            // but that's likely to be rare (e.g. when there is no content display list
   1624                            // for a frame or two during a tab switch).
   1625                            let surface = tile.surface.as_mut().expect("no tile surface set!");
   1626 
   1627                            if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface {
   1628                                if let Some(id) = id.take() {
   1629                                    frame_state.resource_cache.destroy_compositor_tile(id);
   1630                                }
   1631                            }
   1632 
   1633                            tile.is_visible = false;
   1634 
   1635                            if frame_context.fb_config.testing {
   1636                                debug_info.tiles.insert(
   1637                                    tile.tile_offset,
   1638                                    TileDebugInfo::Occluded,
   1639                                );
   1640                            }
   1641 
   1642                            continue;
   1643                        }
   1644                    }
   1645                    None => {
   1646                        tile.is_visible = false;
   1647                    }
   1648                }
   1649 
   1650                // In extreme zoom/offset cases, we may end up with a local scissor/valid rect
   1651                // that becomes empty after transformation to device space (e.g. if the local
   1652                // rect height is 0.00001 and the compositor transform has large scale + offset).
   1653                // DirectComposition panics if we try to BeginDraw with an empty rect, so catch
   1654                // that here and mark the tile non-visible. This is a bit of a hack - we should
   1655                // ideally handle these in a more accurate way so we don't end up with an empty
   1656                // rect here.
   1657                if !tile.cached_surface.is_valid && (scissor_rect.is_empty() || valid_rect.is_empty()) {
   1658                    tile.is_visible = false;
   1659                }
   1660            }
   1661 
   1662            // If we get here, we want to ensure that the surface remains valid in the texture
   1663            // cache, _even if_ it's not visible due to clipping or being scrolled off-screen.
   1664            // This ensures that we retain valid tiles that are off-screen, but still in the
   1665            // display port of this tile cache instance.
   1666            if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() {
   1667                if let SurfaceTextureDescriptor::TextureCache { handle: Some(handle), .. } = descriptor {
   1668                    frame_state.resource_cache
   1669                        .picture_textures.request(handle);
   1670                }
   1671            }
   1672 
   1673            // If the tile has been found to be off-screen / clipped, skip any further processing.
   1674            if !tile.is_visible {
   1675                if frame_context.fb_config.testing {
   1676                    debug_info.tiles.insert(
   1677                        tile.tile_offset,
   1678                        TileDebugInfo::Culled,
   1679                    );
   1680                }
   1681 
   1682                continue;
   1683            }
   1684 
   1685            at_least_one_tile_visible = true;
   1686 
   1687            if let TileSurface::Texture { descriptor, .. } = tile.surface.as_mut().unwrap() {
   1688                match descriptor {
   1689                    SurfaceTextureDescriptor::TextureCache { ref handle, .. } => {
   1690                        let exists = handle.as_ref().map_or(false,
   1691                            |handle| frame_state.resource_cache.picture_textures.entry_exists(handle)
   1692                        );
   1693                        // Invalidate if the backing texture was evicted.
   1694                        if exists {
   1695                            // Request the backing texture so it won't get evicted this frame.
   1696                            // We specifically want to mark the tile texture as used, even
   1697                            // if it's detected not visible below and skipped. This is because
   1698                            // we maintain the set of tiles we care about based on visibility
   1699                            // during pre_update. If a tile still exists after that, we are
   1700                            // assuming that it's either visible or we want to retain it for
   1701                            // a while in case it gets scrolled back onto screen soon.
   1702                            // TODO(gw): Consider switching to manual eviction policy?
   1703                            frame_state.resource_cache
   1704                                .picture_textures
   1705                                .request(handle.as_ref().unwrap());
   1706                        } else {
   1707                            // If the texture was evicted on a previous frame, we need to assume
   1708                            // that the entire tile rect is dirty.
   1709                            tile.invalidate(None, InvalidationReason::NoTexture);
   1710                        }
   1711                    }
   1712                    SurfaceTextureDescriptor::Native { id, .. } => {
   1713                        if id.is_none() {
   1714                            // There is no current surface allocation, so ensure the entire tile is invalidated
   1715                            tile.invalidate(None, InvalidationReason::NoSurface);
   1716                        }
   1717                    }
   1718                }
   1719            }
   1720 
   1721            // Ensure - again - that the dirty rect doesn't extend outside the local valid rect,
   1722            // as the tile could have been invalidated since the first computation.
   1723            tile.cached_surface.local_dirty_rect = tile.cached_surface.local_dirty_rect
   1724                .intersection(&tile.cached_surface.current_descriptor.local_valid_rect)
   1725                .unwrap_or_else(|| { tile.cached_surface.is_valid = true; PictureRect::zero() });
   1726 
   1727            surface_local_dirty_rect = surface_local_dirty_rect.union(&tile.cached_surface.local_dirty_rect);
   1728 
   1729            // Update the world/device dirty rect
   1730            let world_dirty_rect = map_pic_to_world.map(&tile.cached_surface.local_dirty_rect).expect("bug");
   1731 
   1732            let device_rect = (tile.world_tile_rect * frame_context.global_device_pixel_scale).round();
   1733            tile.device_dirty_rect = (world_dirty_rect * frame_context.global_device_pixel_scale)
   1734                .round_out()
   1735                .intersection(&device_rect)
   1736                .unwrap_or_else(DeviceRect::zero);
   1737 
   1738            if tile.cached_surface.is_valid {
   1739                if frame_context.fb_config.testing {
   1740                    debug_info.tiles.insert(
   1741                        tile.tile_offset,
   1742                        TileDebugInfo::Valid,
   1743                    );
   1744                }
   1745            } else {
   1746                // Add this dirty rect to the dirty region tracker. This must be done outside the if statement below,
   1747                // so that we include in the dirty region tiles that are handled by a background color only (no
   1748                // surface allocation).
   1749                tile_cache.dirty_region.add_dirty_region(
   1750                    tile.cached_surface.local_dirty_rect,
   1751                    frame_context.spatial_tree,
   1752                );
   1753 
   1754                // Ensure that this texture is allocated.
   1755                if let TileSurface::Texture { ref mut descriptor } = tile.surface.as_mut().unwrap() {
   1756                    match descriptor {
   1757                        SurfaceTextureDescriptor::TextureCache { ref mut handle } => {
   1758 
   1759                            frame_state.resource_cache.picture_textures.update(
   1760                                tile_cache.current_tile_size,
   1761                                handle,
   1762                                &mut frame_state.resource_cache.texture_cache.next_id,
   1763                                &mut frame_state.resource_cache.texture_cache.pending_updates,
   1764                            );
   1765                        }
   1766                        SurfaceTextureDescriptor::Native { id } => {
   1767                            if id.is_none() {
   1768                                // Allocate a native surface id if we're in native compositing mode,
   1769                                // and we don't have a surface yet (due to first frame, or destruction
   1770                                // due to tile size changing etc).
   1771                                if sub_slice.native_surface.is_none() {
   1772                                    let opaque = frame_state
   1773                                        .resource_cache
   1774                                        .create_compositor_surface(
   1775                                            tile_cache.virtual_offset,
   1776                                            tile_cache.current_tile_size,
   1777                                            true,
   1778                                        );
   1779 
   1780                                    let alpha = frame_state
   1781                                        .resource_cache
   1782                                        .create_compositor_surface(
   1783                                            tile_cache.virtual_offset,
   1784                                            tile_cache.current_tile_size,
   1785                                            false,
   1786                                        );
   1787 
   1788                                    sub_slice.native_surface = Some(NativeSurface {
   1789                                        opaque,
   1790                                        alpha,
   1791                                    });
   1792                                }
   1793 
   1794                                // Create the tile identifier and allocate it.
   1795                                let surface_id = if tile.is_opaque {
   1796                                    sub_slice.native_surface.as_ref().unwrap().opaque
   1797                                } else {
   1798                                    sub_slice.native_surface.as_ref().unwrap().alpha
   1799                                };
   1800 
   1801                                let tile_id = NativeTileId {
   1802                                    surface_id,
   1803                                    x: tile.tile_offset.x,
   1804                                    y: tile.tile_offset.y,
   1805                                };
   1806 
   1807                                frame_state.resource_cache.create_compositor_tile(tile_id);
   1808 
   1809                                *id = Some(tile_id);
   1810                            }
   1811                        }
   1812                    }
   1813 
   1814                    // The cast_unit() here is because the `content_origin` is expected to be in
   1815                    // device pixels, however we're establishing raster roots for picture cache
   1816                    // tiles meaning the `content_origin` needs to be in the local space of that root.
   1817                    // TODO(gw): `content_origin` should actually be in RasterPixels to be consistent
   1818                    //           with both local / screen raster modes, but this involves a lot of
   1819                    //           changes to render task and picture code.
   1820                    let content_origin_f = tile.cached_surface.local_rect.min.cast_unit() * device_pixel_scale;
   1821                    let content_origin = content_origin_f.round();
   1822                    // TODO: these asserts used to have a threshold of 0.01 but failed intermittently the
   1823                    // gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html test on android.
   1824                    // moving the rectangles in space mapping conversion code to the Box2D representaton
   1825                    // made the failure happen more often.
   1826                    debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.15);
   1827                    debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.15);
   1828 
   1829                    let surface = descriptor.resolve(
   1830                        frame_state.resource_cache,
   1831                        tile_cache.current_tile_size,
   1832                    );
   1833 
   1834                    // Recompute the scissor rect as the tile could have been invalidated since the first computation.
   1835                    let scissor_rect = frame_state.composite_state.get_surface_rect(
   1836                        &tile.cached_surface.local_dirty_rect,
   1837                        &tile.cached_surface.local_rect,
   1838                        tile_cache.transform_index,
   1839                    ).to_i32();
   1840 
   1841                    let composite_task_size = tile_cache.current_tile_size;
   1842 
   1843                    let tile_key = TileKey {
   1844                        sub_slice_index: SubSliceIndex::new(sub_slice_index),
   1845                        tile_offset: tile.tile_offset,
   1846                    };
   1847 
   1848                    let mut clear_color = ColorF::TRANSPARENT;
   1849 
   1850                    if SubSliceIndex::new(sub_slice_index).is_primary() {
   1851                        if let Some(background_color) = tile_cache.background_color {
   1852                            clear_color = background_color;
   1853                        }
   1854 
   1855                        // If this picture cache has a spanning_opaque_color, we will use
   1856                        // that as the clear color. The primitive that was detected as a
   1857                        // spanning primitive will have been set with IS_BACKDROP, causing
   1858                        // it to be skipped and removing everything added prior to it
   1859                        // during batching.
   1860                        if let Some(color) = tile_cache.backdrop.spanning_opaque_color {
   1861                            clear_color = color;
   1862                        }
   1863                    }
   1864 
   1865                    let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
   1866 
   1867                    // TODO(gw): As a performance optimization, we could skip the resolve picture
   1868                    //           if the dirty rect is the same as the resolve rect (probably quite
   1869                    //           common for effects that scroll underneath a backdrop-filter, for example).
   1870                    let use_tile_composite = !tile.cached_surface.sub_graphs.is_empty();
   1871 
   1872                    if use_tile_composite {
   1873                        let mut local_content_rect = tile.cached_surface.local_dirty_rect;
   1874 
   1875                        for (sub_graph_rect, surface_stack) in &tile.cached_surface.sub_graphs {
   1876                            if let Some(dirty_sub_graph_rect) = sub_graph_rect.intersection(&tile.cached_surface.local_dirty_rect) {
   1877                                for (composite_mode, surface_index) in surface_stack {
   1878                                    let surface = &frame_state.surfaces[surface_index.0];
   1879 
   1880                                    let rect = composite_mode.get_coverage(
   1881                                        surface,
   1882                                        Some(dirty_sub_graph_rect.cast_unit()),
   1883                                    ).cast_unit();
   1884 
   1885                                    local_content_rect = local_content_rect.union(&rect);
   1886                                }
   1887                            }
   1888                        }
   1889 
   1890                        // We know that we'll never need to sample > 300 device pixels outside the tile
   1891                        // for blurring, so clamp the content rect here so that we don't try to allocate
   1892                        // a really large surface in the case of a drop-shadow with large offset.
   1893                        let max_content_rect = (tile.cached_surface.local_dirty_rect.cast_unit() * device_pixel_scale)
   1894                            .inflate(
   1895                                MAX_BLUR_RADIUS * BLUR_SAMPLE_SCALE,
   1896                                MAX_BLUR_RADIUS * BLUR_SAMPLE_SCALE,
   1897                            )
   1898                            .round_out()
   1899                            .to_i32();
   1900 
   1901                        let content_device_rect = (local_content_rect.cast_unit() * device_pixel_scale)
   1902                            .round_out()
   1903                            .to_i32();
   1904 
   1905                        let content_device_rect = content_device_rect
   1906                            .intersection(&max_content_rect)
   1907                            .expect("bug: no intersection with tile dirty rect: {content_device_rect:?} / {max_content_rect:?}");
   1908 
   1909                        let content_task_size = content_device_rect.size();
   1910                        let normalized_content_rect = content_task_size.into();
   1911 
   1912                        let inner_offset = content_origin + scissor_rect.min.to_vector().to_f32();
   1913                        let outer_offset = content_device_rect.min.to_f32();
   1914                        let sub_rect_offset = (inner_offset - outer_offset).round().to_i32();
   1915 
   1916                        let render_task_id = frame_state.rg_builder.add().init(
   1917                            RenderTask::new_dynamic(
   1918                                content_task_size,
   1919                                RenderTaskKind::new_picture(
   1920                                    content_task_size,
   1921                                    true,
   1922                                    content_device_rect.min.to_f32(),
   1923                                    surface_spatial_node_index,
   1924                                    // raster == surface implicitly for picture cache tiles
   1925                                    surface_spatial_node_index,
   1926                                    device_pixel_scale,
   1927                                    Some(normalized_content_rect),
   1928                                    None,
   1929                                    Some(clear_color),
   1930                                    cmd_buffer_index,
   1931                                    false,
   1932                                    None,
   1933                                )
   1934                            ),
   1935                        );
   1936 
   1937                        let composite_task_id = frame_state.rg_builder.add().init(
   1938                            RenderTask::new(
   1939                                RenderTaskLocation::Static {
   1940                                    surface: StaticRenderTaskSurface::PictureCache {
   1941                                        surface,
   1942                                    },
   1943                                    rect: composite_task_size.into(),
   1944                                },
   1945                                RenderTaskKind::new_tile_composite(
   1946                                    sub_rect_offset,
   1947                                    scissor_rect,
   1948                                    valid_rect,
   1949                                    clear_color,
   1950                                ),
   1951                            ),
   1952                        );
   1953 
   1954                        surface_render_tasks.insert(
   1955                            tile_key,
   1956                            SurfaceTileDescriptor {
   1957                                current_task_id: render_task_id,
   1958                                composite_task_id: Some(composite_task_id),
   1959                                dirty_rect: tile.cached_surface.local_dirty_rect,
   1960                            },
   1961                        );
   1962                    } else {
   1963                        let render_task_id = frame_state.rg_builder.add().init(
   1964                            RenderTask::new(
   1965                                RenderTaskLocation::Static {
   1966                                    surface: StaticRenderTaskSurface::PictureCache {
   1967                                        surface,
   1968                                    },
   1969                                    rect: composite_task_size.into(),
   1970                                },
   1971                                RenderTaskKind::new_picture(
   1972                                    composite_task_size,
   1973                                    true,
   1974                                    content_origin,
   1975                                    surface_spatial_node_index,
   1976                                    // raster == surface implicitly for picture cache tiles
   1977                                    surface_spatial_node_index,
   1978                                    device_pixel_scale,
   1979                                    Some(scissor_rect),
   1980                                    Some(valid_rect),
   1981                                    Some(clear_color),
   1982                                    cmd_buffer_index,
   1983                                    false,
   1984                                    None,
   1985                                )
   1986                            ),
   1987                        );
   1988 
   1989                        surface_render_tasks.insert(
   1990                            tile_key,
   1991                            SurfaceTileDescriptor {
   1992                                current_task_id: render_task_id,
   1993                                composite_task_id: None,
   1994                                dirty_rect: tile.cached_surface.local_dirty_rect,
   1995                            },
   1996                        );
   1997                    }
   1998                }
   1999 
   2000                if frame_context.fb_config.testing {
   2001                    debug_info.tiles.insert(
   2002                        tile.tile_offset,
   2003                        TileDebugInfo::Dirty(DirtyTileDebugInfo {
   2004                            local_valid_rect: tile.cached_surface.current_descriptor.local_valid_rect,
   2005                            local_dirty_rect: tile.cached_surface.local_dirty_rect,
   2006                        }),
   2007                    );
   2008                }
   2009            }
   2010 
   2011            let surface = tile.surface.as_ref().expect("no tile surface set!");
   2012 
   2013            let descriptor = CompositeTileDescriptor {
   2014                surface_kind: surface.into(),
   2015                tile_id: tile.id,
   2016            };
   2017 
   2018            let (surface, is_opaque) = match surface {
   2019                TileSurface::Color { color } => {
   2020                    (CompositeTileSurface::Color { color: *color }, true)
   2021                }
   2022                TileSurface::Texture { descriptor, .. } => {
   2023                    let surface = descriptor.resolve(frame_state.resource_cache, tile_cache.current_tile_size);
   2024                    (
   2025                        CompositeTileSurface::Texture { surface },
   2026                        tile.is_opaque
   2027                    )
   2028                }
   2029            };
   2030 
   2031            if is_opaque {
   2032                sub_slice.opaque_tile_descriptors.push(descriptor);
   2033            } else {
   2034                sub_slice.alpha_tile_descriptors.push(descriptor);
   2035            }
   2036 
   2037            let composite_tile = CompositeTile {
   2038                kind: tile_kind(&surface, is_opaque),
   2039                surface,
   2040                local_rect: tile.cached_surface.local_rect,
   2041                local_valid_rect: tile.cached_surface.current_descriptor.local_valid_rect,
   2042                local_dirty_rect: tile.cached_surface.local_dirty_rect,
   2043                device_clip_rect,
   2044                z_id: tile.z_id,
   2045                transform_index: tile_cache.transform_index,
   2046                clip_index: tile_cache.compositor_clip,
   2047                tile_id: Some(tile.id),
   2048            };
   2049 
   2050            sub_slice.composite_tiles.push(composite_tile);
   2051 
   2052            // Now that the tile is valid, reset the dirty rect.
   2053            tile.cached_surface.local_dirty_rect = PictureRect::zero();
   2054            tile.cached_surface.is_valid = true;
   2055        }
   2056 
   2057        // Sort the tile descriptor lists, since iterating values in the tile_cache.tiles
   2058        // hashmap doesn't provide any ordering guarantees, but we want to detect the
   2059        // composite descriptor as equal if the tiles list is the same, regardless of
   2060        // ordering.
   2061        sub_slice.opaque_tile_descriptors.sort_by_key(|desc| desc.tile_id);
   2062        sub_slice.alpha_tile_descriptors.sort_by_key(|desc| desc.tile_id);
   2063    }
   2064 
   2065    // Check to see if we should add backdrops as native surfaces.
   2066    let backdrop_rect = tile_cache.backdrop.backdrop_rect
   2067        .intersection(&tile_cache.local_rect)
   2068        .and_then(|r| {
   2069            r.intersection(&tile_cache.local_clip_rect)
   2070    });
   2071 
   2072    let mut backdrop_in_use_and_visible = false;
   2073    if let Some(backdrop_rect) = backdrop_rect {
   2074        let supports_surface_for_backdrop = match frame_state.composite_state.compositor_kind {
   2075            CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
   2076                false
   2077            }
   2078            CompositorKind::Native { capabilities, .. } => {
   2079                capabilities.supports_surface_for_backdrop
   2080            }
   2081        };
   2082        if supports_surface_for_backdrop && !tile_cache.found_prims_after_backdrop && at_least_one_tile_visible {
   2083            if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind {
   2084                backdrop_in_use_and_visible = true;
   2085 
   2086                // We're going to let the compositor handle the backdrop as a native surface.
   2087                // Hide all of our sub_slice tiles so they aren't also trying to draw it.
   2088                for sub_slice in &mut tile_cache.sub_slices {
   2089                    for tile in sub_slice.tiles.values_mut() {
   2090                        tile.is_visible = false;
   2091                    }
   2092                }
   2093 
   2094                // Destroy our backdrop surface if it doesn't match the new color.
   2095                // TODO: This is a performance hit for animated color backdrops.
   2096                if let Some(backdrop_surface) = &tile_cache.backdrop_surface {
   2097                    if backdrop_surface.color != color {
   2098                        frame_state.resource_cache.destroy_compositor_surface(backdrop_surface.id);
   2099                        tile_cache.backdrop_surface = None;
   2100                    }
   2101                }
   2102 
   2103                // Calculate the device_rect for the backdrop, which is just the backdrop_rect
   2104                // converted into world space and scaled to device pixels.
   2105                let world_backdrop_rect = map_pic_to_world.map(&backdrop_rect).expect("bug: unable to map backdrop rect");
   2106                let device_rect = (world_backdrop_rect * frame_context.global_device_pixel_scale).round();
   2107 
   2108                // If we already have a backdrop surface, update the device rect. Otherwise, create
   2109                // a backdrop surface.
   2110                if let Some(backdrop_surface) = &mut tile_cache.backdrop_surface {
   2111                    backdrop_surface.device_rect = device_rect;
   2112                } else {
   2113                    // Create native compositor surface with color for the backdrop and store the id.
   2114                    tile_cache.backdrop_surface = Some(BackdropSurface {
   2115                        id: frame_state.resource_cache.create_compositor_backdrop_surface(color),
   2116                        color,
   2117                        device_rect,
   2118                    });
   2119                }
   2120            }
   2121        }
   2122    }
   2123 
   2124    if !backdrop_in_use_and_visible {
   2125        if let Some(backdrop_surface) = &tile_cache.backdrop_surface {
   2126            // We've already allocated a backdrop surface, but we're not using it.
   2127            // Tell the compositor to get rid of it.
   2128            frame_state.resource_cache.destroy_compositor_surface(backdrop_surface.id);
   2129            tile_cache.backdrop_surface = None;
   2130        }
   2131    }
   2132 
   2133    // If invalidation debugging is enabled, dump the picture cache state to a tree printer.
   2134    if frame_context.debug_flags.contains(DebugFlags::INVALIDATION_DBG) {
   2135        tile_cache.print();
   2136    }
   2137 
   2138    // If testing mode is enabled, write some information about the current state
   2139    // of this picture cache (made available in RenderResults).
   2140    if frame_context.fb_config.testing {
   2141        frame_state.composite_state
   2142            .picture_cache_debug
   2143            .slices
   2144            .insert(
   2145                tile_cache.slice,
   2146                debug_info,
   2147            );
   2148    }
   2149 
   2150    let descriptor = SurfaceDescriptor::new_tiled(surface_render_tasks);
   2151 
   2152    frame_state.surface_builder.push_surface(
   2153        surface_index,
   2154        false,
   2155        surface_local_dirty_rect,
   2156        Some(descriptor),
   2157        frame_state.surfaces,
   2158        frame_state.rg_builder,
   2159    );
   2160 }
   2161 
   2162 fn compute_subpixel_mode(
   2163    raster_config: &Option<RasterConfig>,
   2164    tile_caches: &FastHashMap<SliceId, Box<TileCacheInstance>>,
   2165    parent_subpixel_mode: SubpixelMode,
   2166 ) -> SubpixelMode {
   2167 
   2168    // Disallow subpixel AA if an intermediate surface is needed.
   2169    // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
   2170    let subpixel_mode = match raster_config {
   2171        Some(RasterConfig { ref composite_mode, .. }) => {
   2172            let subpixel_mode = match composite_mode {
   2173                PictureCompositeMode::TileCache { slice_id } => {
   2174                    tile_caches[&slice_id].subpixel_mode
   2175                }
   2176                PictureCompositeMode::Blit(..) |
   2177                PictureCompositeMode::ComponentTransferFilter(..) |
   2178                PictureCompositeMode::Filter(..) |
   2179                PictureCompositeMode::MixBlend(..) |
   2180                PictureCompositeMode::IntermediateSurface |
   2181                PictureCompositeMode::SVGFEGraph(..) => {
   2182                    // TODO(gw): We can take advantage of the same logic that
   2183                    //           exists in the opaque rect detection for tile
   2184                    //           caches, to allow subpixel text on other surfaces
   2185                    //           that can be detected as opaque.
   2186                    SubpixelMode::Deny
   2187                }
   2188            };
   2189 
   2190            subpixel_mode
   2191        }
   2192        None => {
   2193            SubpixelMode::Allow
   2194        }
   2195    };
   2196 
   2197    // Still disable subpixel AA if parent forbids it
   2198    let subpixel_mode = match (parent_subpixel_mode, subpixel_mode) {
   2199        (SubpixelMode::Allow, SubpixelMode::Allow) => {
   2200            // Both parent and this surface unconditionally allow subpixel AA
   2201            SubpixelMode::Allow
   2202        }
   2203        (SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect, prohibited_rect }) => {
   2204            // Parent allows, but we are conditional subpixel AA
   2205            SubpixelMode::Conditional {
   2206                allowed_rect,
   2207                prohibited_rect,
   2208            }
   2209        }
   2210        (SubpixelMode::Conditional { allowed_rect, prohibited_rect }, SubpixelMode::Allow) => {
   2211            // Propagate conditional subpixel mode to child pictures that allow subpixel AA
   2212            SubpixelMode::Conditional {
   2213                allowed_rect,
   2214                prohibited_rect,
   2215            }
   2216        }
   2217        (SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => {
   2218            unreachable!("bug: only top level picture caches have conditional subpixel");
   2219        }
   2220        (SubpixelMode::Deny, _) | (_, SubpixelMode::Deny) => {
   2221            // Either parent or this surface explicitly deny subpixel, these take precedence
   2222            SubpixelMode::Deny
   2223        }
   2224    };
   2225 
   2226    subpixel_mode
   2227 }
   2228 
   2229 #[test]
   2230 fn test_large_surface_scale_1() {
   2231    use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
   2232 
   2233    let mut cst = SceneSpatialTree::new();
   2234    let root_reference_frame_index = cst.root_reference_frame_index();
   2235 
   2236    let mut spatial_tree = SpatialTree::new();
   2237    spatial_tree.apply_updates(cst.end_frame_and_get_pending_updates());
   2238    spatial_tree.update_tree(&SceneProperties::new());
   2239 
   2240    let map_local_to_picture = SpaceMapper::new_with_target(
   2241        root_reference_frame_index,
   2242        root_reference_frame_index,
   2243        PictureRect::max_rect(),
   2244        &spatial_tree,
   2245    );
   2246 
   2247    let mut surfaces = vec![
   2248        SurfaceInfo {
   2249            unclipped_local_rect: PictureRect::max_rect(),
   2250            clipped_local_rect: PictureRect::max_rect(),
   2251            is_opaque: true,
   2252            clipping_rect: PictureRect::max_rect(),
   2253            culling_rect: VisRect::max_rect(),
   2254            map_local_to_picture: map_local_to_picture.clone(),
   2255            raster_spatial_node_index: root_reference_frame_index,
   2256            surface_spatial_node_index: root_reference_frame_index,
   2257            visibility_spatial_node_index: root_reference_frame_index,
   2258            device_pixel_scale: DevicePixelScale::new(1.0),
   2259            world_scale_factors: (1.0, 1.0),
   2260            local_scale: (1.0, 1.0),
   2261            allow_snapping: true,
   2262            force_scissor_rect: false,
   2263        },
   2264        SurfaceInfo {
   2265            unclipped_local_rect: PictureRect::new(
   2266                PicturePoint::new(52.76350021362305, 0.0),
   2267                PicturePoint::new(159.6738739013672, 35.0),
   2268            ),
   2269            clipped_local_rect: PictureRect::max_rect(),
   2270            is_opaque: true,
   2271            clipping_rect: PictureRect::max_rect(),
   2272            culling_rect: VisRect::max_rect(),
   2273            map_local_to_picture,
   2274            raster_spatial_node_index: root_reference_frame_index,
   2275            surface_spatial_node_index: root_reference_frame_index,
   2276            visibility_spatial_node_index: root_reference_frame_index,
   2277            device_pixel_scale: DevicePixelScale::new(43.82798767089844),
   2278            world_scale_factors: (1.0, 1.0),
   2279            local_scale: (1.0, 1.0),
   2280            allow_snapping: true,
   2281            force_scissor_rect: false,
   2282        },
   2283    ];
   2284 
   2285    get_surface_rects(
   2286        SurfaceIndex(1),
   2287        &PictureCompositeMode::Blit(BlitReason::BLEND_MODE),
   2288        SurfaceIndex(0),
   2289        &mut surfaces,
   2290        &spatial_tree,
   2291        MAX_SURFACE_SIZE as f32,
   2292        false,
   2293    );
   2294 }
   2295 
   2296 #[test]
   2297 fn test_drop_filter_dirty_region_outside_prim() {
   2298    // Ensure that if we have a drop-filter where the content of the
   2299    // shadow is outside the dirty rect, but blurred pixels from that
   2300    // content will affect the dirty rect, that we correctly calculate
   2301    // the required region of the drop-filter input
   2302 
   2303    use api::Shadow;
   2304    use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
   2305 
   2306    let mut cst = SceneSpatialTree::new();
   2307    let root_reference_frame_index = cst.root_reference_frame_index();
   2308 
   2309    let mut spatial_tree = SpatialTree::new();
   2310    spatial_tree.apply_updates(cst.end_frame_and_get_pending_updates());
   2311    spatial_tree.update_tree(&SceneProperties::new());
   2312 
   2313    let map_local_to_picture = SpaceMapper::new_with_target(
   2314        root_reference_frame_index,
   2315        root_reference_frame_index,
   2316        PictureRect::max_rect(),
   2317        &spatial_tree,
   2318    );
   2319 
   2320    let mut surfaces = vec![
   2321        SurfaceInfo {
   2322            unclipped_local_rect: PictureRect::max_rect(),
   2323            clipped_local_rect: PictureRect::max_rect(),
   2324            is_opaque: true,
   2325            clipping_rect: PictureRect::max_rect(),
   2326            map_local_to_picture: map_local_to_picture.clone(),
   2327            raster_spatial_node_index: root_reference_frame_index,
   2328            surface_spatial_node_index: root_reference_frame_index,
   2329            visibility_spatial_node_index: root_reference_frame_index,
   2330            device_pixel_scale: DevicePixelScale::new(1.0),
   2331            world_scale_factors: (1.0, 1.0),
   2332            local_scale: (1.0, 1.0),
   2333            allow_snapping: true,
   2334            force_scissor_rect: false,
   2335            culling_rect: VisRect::max_rect(),
   2336        },
   2337        SurfaceInfo {
   2338            unclipped_local_rect: PictureRect::new(
   2339                PicturePoint::new(0.0, 0.0),
   2340                PicturePoint::new(750.0, 450.0),
   2341            ),
   2342            clipped_local_rect: PictureRect::new(
   2343                PicturePoint::new(0.0, 0.0),
   2344                PicturePoint::new(750.0, 450.0),
   2345            ),
   2346            is_opaque: true,
   2347            clipping_rect: PictureRect::max_rect(),
   2348            map_local_to_picture,
   2349            raster_spatial_node_index: root_reference_frame_index,
   2350            surface_spatial_node_index: root_reference_frame_index,
   2351            visibility_spatial_node_index: root_reference_frame_index,
   2352            device_pixel_scale: DevicePixelScale::new(1.0),
   2353            world_scale_factors: (1.0, 1.0),
   2354            local_scale: (1.0, 1.0),
   2355            allow_snapping: true,
   2356            force_scissor_rect: false,
   2357            culling_rect: VisRect::max_rect(),
   2358        },
   2359    ];
   2360 
   2361    let shadows = smallvec![
   2362        Shadow {
   2363            offset: LayoutVector2D::zero(),
   2364            color: ColorF::BLACK,
   2365            blur_radius: 75.0,
   2366        },
   2367    ];
   2368 
   2369    let composite_mode = PictureCompositeMode::Filter(Filter::DropShadows(shadows));
   2370 
   2371    // Ensure we get a valid and correct render task size when dirty region covers entire screen
   2372    let info = get_surface_rects(
   2373        SurfaceIndex(1),
   2374        &composite_mode,
   2375        SurfaceIndex(0),
   2376        &mut surfaces,
   2377        &spatial_tree,
   2378        MAX_SURFACE_SIZE as f32,
   2379        false,
   2380    ).expect("No surface rect");
   2381    assert_eq!(info.task_size, DeviceIntSize::new(1200, 900));
   2382 
   2383    // Ensure we get a valid and correct render task size when dirty region is outside filter content
   2384    surfaces[0].clipping_rect = PictureRect::new(
   2385        PicturePoint::new(768.0, 128.0),
   2386        PicturePoint::new(1024.0, 256.0),
   2387    );
   2388    let info = get_surface_rects(
   2389        SurfaceIndex(1),
   2390        &composite_mode,
   2391        SurfaceIndex(0),
   2392        &mut surfaces,
   2393        &spatial_tree,
   2394        MAX_SURFACE_SIZE as f32,
   2395        false,
   2396    ).expect("No surface rect");
   2397    assert_eq!(info.task_size, DeviceIntSize::new(432, 578));
   2398 }