webrender/
picture.rs

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
97use api::{BorderRadius, ClipMode, FilterPrimitiveKind, MixBlendMode, PremultipliedColorF, SVGFE_GRAPH_MAX};
98use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FilterOpGraphPictureBufferId, RasterSpace};
99use api::{DebugFlags, ImageKey, ColorF, ColorU, PrimitiveFlags, SnapshotInfo};
100use api::{ImageRendering, ColorDepth, YuvRangedColorSpace, YuvFormat, AlphaType};
101use api::units::*;
102use crate::prim_store::image::AdjustedImageSource;
103use crate::{command_buffer::PrimitiveCommand, render_task_graph::RenderTaskGraphBuilder, renderer::GpuBufferBuilderF};
104use crate::box_shadow::BLUR_SAMPLE_SCALE;
105use crate::clip::{ClipChainInstance, ClipItemKind, ClipLeafId, ClipNodeId, ClipSpaceConversion, ClipStore, ClipTreeBuilder};
106use crate::profiler::{self, TransactionProfile};
107use crate::spatial_tree::{SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
108use crate::composite::{tile_kind, CompositeState, CompositeTileSurface, CompositorClipIndex, CompositorKind, NativeSurfaceId, NativeTileId};
109use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency, CompositeTileDescriptor, CompositeTile};
110use crate::composite::{CompositorTransformIndex, CompositorSurfaceKind};
111use crate::debug_colors;
112use euclid::{vec3, Point2D, Scale, Vector2D, Box2D};
113use euclid::approxeq::ApproxEq;
114use crate::filterdata::SFilterData;
115use crate::intern::ItemUid;
116use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, FilterGraphOp, FilterGraphNode, Filter, FrameId};
117use crate::internal_types::{PlaneSplitterIndex, PlaneSplitAnchor, TextureSource};
118use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
119use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
120use crate::gpu_types::{UvRectKind, ZBufferId};
121use peek_poke::{PeekPoke, poke_into_vec, peek_from_slice, ensure_red_zone};
122use plane_split::{Clipper, Polygon};
123use crate::prim_store::{PrimitiveTemplateKind, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
124use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveScratchBuffer};
125use crate::print_tree::{PrintTree, PrintTreePrinter};
126use crate::render_backend::DataStores;
127use crate::render_task_graph::RenderTaskId;
128use crate::render_target::RenderTargetKind;
129use crate::render_task::{BlurTask, RenderTask, RenderTaskLocation, BlurTaskCache};
130use crate::render_task::{StaticRenderTaskSurface, RenderTaskKind};
131use crate::renderer::BlendMode;
132use crate::resource_cache::{ResourceCache, ImageGeneration, ImageRequest};
133use crate::space::SpaceMapper;
134use crate::scene::SceneProperties;
135use crate::spatial_tree::CoordinateSystemId;
136use crate::surface::{SurfaceDescriptor, SurfaceTileDescriptor};
137use smallvec::SmallVec;
138use std::{mem, u8, marker, u32};
139use std::fmt::{Display, Error, Formatter};
140use std::sync::atomic::{AtomicUsize, Ordering};
141use std::collections::hash_map::Entry;
142use std::ops::Range;
143use crate::picture_textures::PictureCacheTextureHandle;
144use crate::util::{MaxRect, VecHelper, MatrixHelpers, Recycler, ScaleOffset};
145use crate::filterdata::FilterDataHandle;
146use crate::tile_cache::{SliceDebugInfo, TileDebugInfo, DirtyTileDebugInfo};
147use crate::visibility::{PrimitiveVisibilityFlags, FrameVisibilityContext};
148use crate::visibility::{VisibilityState, FrameVisibilityState};
149use crate::scene_building::SliceFlags;
150use core::time::Duration;
151
152// Maximum blur radius for blur filter (different than box-shadow blur).
153// Taken from FilterNodeSoftware.cpp in Gecko.
154const MAX_BLUR_RADIUS: f32 = 100.;
155
156/// Specify whether a surface allows subpixel AA text rendering.
157#[derive(Debug, Copy, Clone)]
158pub enum SubpixelMode {
159    /// This surface allows subpixel AA text
160    Allow,
161    /// Subpixel AA text cannot be drawn on this surface
162    Deny,
163    /// Subpixel AA can be drawn on this surface, if not intersecting
164    /// with the excluded regions, and inside the allowed rect.
165    Conditional {
166        allowed_rect: PictureRect,
167        prohibited_rect: PictureRect,
168    },
169}
170
171/// A comparable transform matrix, that compares with epsilon checks.
172#[derive(Debug, Clone)]
173struct MatrixKey {
174    m: [f32; 16],
175}
176
177impl PartialEq for MatrixKey {
178    fn eq(&self, other: &Self) -> bool {
179        const EPSILON: f32 = 0.001;
180
181        // TODO(gw): It's possible that we may need to adjust the epsilon
182        //           to be tighter on most of the matrix, except the
183        //           translation parts?
184        for (i, j) in self.m.iter().zip(other.m.iter()) {
185            if !i.approx_eq_eps(j, &EPSILON) {
186                return false;
187            }
188        }
189
190        true
191    }
192}
193
194/// A comparable scale-offset, that compares with epsilon checks.
195#[derive(Debug, Clone)]
196struct ScaleOffsetKey {
197    sx: f32,
198    sy: f32,
199    tx: f32,
200    ty: f32,
201}
202
203impl PartialEq for ScaleOffsetKey {
204    fn eq(&self, other: &Self) -> bool {
205        const EPSILON: f32 = 0.001;
206
207        self.sx.approx_eq_eps(&other.sx, &EPSILON) &&
208        self.sy.approx_eq_eps(&other.sy, &EPSILON) &&
209        self.tx.approx_eq_eps(&other.tx, &EPSILON) &&
210        self.ty.approx_eq_eps(&other.ty, &EPSILON)
211    }
212}
213
214/// A comparable / hashable version of a coordinate space mapping. Used to determine
215/// if a transform dependency for a tile has changed.
216#[derive(Debug, PartialEq, Clone)]
217enum TransformKey {
218    Local,
219    ScaleOffset {
220        so: ScaleOffsetKey,
221    },
222    Transform {
223        m: MatrixKey,
224    }
225}
226
227impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
228    fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey {
229        match transform {
230            CoordinateSpaceMapping::Local => {
231                TransformKey::Local
232            }
233            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
234                TransformKey::ScaleOffset {
235                    so: ScaleOffsetKey {
236                        sx: scale_offset.scale.x,
237                        sy: scale_offset.scale.y,
238                        tx: scale_offset.offset.x,
239                        ty: scale_offset.offset.y,
240                    }
241                }
242            }
243            CoordinateSpaceMapping::Transform(ref m) => {
244                TransformKey::Transform {
245                    m: MatrixKey {
246                        m: m.to_array(),
247                    },
248                }
249            }
250        }
251    }
252}
253
254/// Unit for tile coordinates.
255#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
256pub struct TileCoordinate;
257
258// Geometry types for tile coordinates.
259pub type TileOffset = Point2D<i32, TileCoordinate>;
260pub type TileRect = Box2D<i32, TileCoordinate>;
261
262/// The maximum number of compositor surfaces that are allowed per picture cache. This
263/// is an arbitrary number that should be enough for common cases, but low enough to
264/// prevent performance and memory usage drastically degrading in pathological cases.
265pub const MAX_COMPOSITOR_SURFACES: usize = 4;
266
267/// The size in device pixels of a normal cached tile.
268pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize {
269    width: 1024,
270    height: 512,
271    _unit: marker::PhantomData,
272};
273
274/// The size in device pixels of a tile for horizontal scroll bars
275pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize {
276    width: 1024,
277    height: 32,
278    _unit: marker::PhantomData,
279};
280
281/// The size in device pixels of a tile for vertical scroll bars
282pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
283    width: 32,
284    height: 1024,
285    _unit: marker::PhantomData,
286};
287
288/// The maximum size per axis of a surface, in DevicePixel coordinates.
289/// Render tasks larger than this size are scaled down to fit, which may cause
290/// some blurriness.
291pub const MAX_SURFACE_SIZE: usize = 4096;
292/// Maximum size of a compositor surface.
293const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0;
294
295/// Used to get unique tile IDs, even when the tile cache is
296/// destroyed between display lists / scenes.
297static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
298
299fn clamp(value: i32, low: i32, high: i32) -> i32 {
300    value.max(low).min(high)
301}
302
303fn clampf(value: f32, low: f32, high: f32) -> f32 {
304    value.max(low).min(high)
305}
306
307/// An index into the prims array in a TileDescriptor.
308#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
309#[cfg_attr(feature = "capture", derive(Serialize))]
310#[cfg_attr(feature = "replay", derive(Deserialize))]
311pub struct PrimitiveDependencyIndex(pub u32);
312
313/// Information about the state of a binding.
314#[derive(Debug)]
315pub struct BindingInfo<T> {
316    /// The current value retrieved from dynamic scene properties.
317    value: T,
318    /// True if it was changed (or is new) since the last frame build.
319    changed: bool,
320}
321
322/// Information stored in a tile descriptor for a binding.
323#[derive(Debug, PartialEq, Clone, Copy, PeekPoke)]
324#[cfg_attr(feature = "capture", derive(Serialize))]
325#[cfg_attr(feature = "replay", derive(Deserialize))]
326pub enum Binding<T> {
327    Value(T),
328    Binding(PropertyBindingId),
329}
330
331impl<T: Default> Default for Binding<T> {
332    fn default() -> Self {
333        Binding::Value(T::default())
334    }
335}
336
337impl<T> From<PropertyBinding<T>> for Binding<T> {
338    fn from(binding: PropertyBinding<T>) -> Binding<T> {
339        match binding {
340            PropertyBinding::Binding(key, _) => Binding::Binding(key.id),
341            PropertyBinding::Value(value) => Binding::Value(value),
342        }
343    }
344}
345
346pub type OpacityBinding = Binding<f32>;
347pub type OpacityBindingInfo = BindingInfo<f32>;
348
349pub type ColorBinding = Binding<ColorU>;
350pub type ColorBindingInfo = BindingInfo<ColorU>;
351
352#[derive(PeekPoke)]
353enum PrimitiveDependency {
354    OpacityBinding {
355        binding: OpacityBinding,
356    },
357    ColorBinding {
358        binding: ColorBinding,
359    },
360    SpatialNode {
361        index: SpatialNodeIndex,
362    },
363    Clip {
364        clip: ItemUid,
365    },
366    Image {
367        image: ImageDependency,
368    },
369}
370
371/// A dependency for a transform is defined by the spatial node index + frame it was used
372#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PeekPoke, Default)]
373#[cfg_attr(feature = "capture", derive(Serialize))]
374#[cfg_attr(feature = "replay", derive(Deserialize))]
375pub struct SpatialNodeKey {
376    spatial_node_index: SpatialNodeIndex,
377    frame_id: FrameId,
378}
379
380/// A helper for comparing spatial nodes between frames. The comparisons
381/// are done by-value, so that if the shape of the spatial node tree
382/// changes, invalidations aren't done simply due to the spatial node
383/// index changing between display lists.
384struct SpatialNodeComparer {
385    /// The root spatial node index of the tile cache
386    ref_spatial_node_index: SpatialNodeIndex,
387    /// Maintains a map of currently active transform keys
388    spatial_nodes: FastHashMap<SpatialNodeKey, TransformKey>,
389    /// A cache of recent comparisons between prev and current spatial nodes
390    compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>,
391    /// A set of frames that we need to retain spatial node entries for
392    referenced_frames: FastHashSet<FrameId>,
393}
394
395impl SpatialNodeComparer {
396    /// Construct a new comparer
397    fn new() -> Self {
398        SpatialNodeComparer {
399            ref_spatial_node_index: SpatialNodeIndex::INVALID,
400            spatial_nodes: FastHashMap::default(),
401            compare_cache: FastHashMap::default(),
402            referenced_frames: FastHashSet::default(),
403        }
404    }
405
406    /// Advance to the next frame
407    fn next_frame(
408        &mut self,
409        ref_spatial_node_index: SpatialNodeIndex,
410    ) {
411        // Drop any node information for unreferenced frames, to ensure that the
412        // hashmap doesn't grow indefinitely!
413        let referenced_frames = &self.referenced_frames;
414        self.spatial_nodes.retain(|key, _| {
415            referenced_frames.contains(&key.frame_id)
416        });
417
418        // Update the root spatial node for this comparer
419        self.ref_spatial_node_index = ref_spatial_node_index;
420        self.compare_cache.clear();
421        self.referenced_frames.clear();
422    }
423
424    /// Register a transform that is used, and build the transform key for it if new.
425    fn register_used_transform(
426        &mut self,
427        spatial_node_index: SpatialNodeIndex,
428        frame_id: FrameId,
429        spatial_tree: &SpatialTree,
430    ) {
431        let key = SpatialNodeKey {
432            spatial_node_index,
433            frame_id,
434        };
435
436        if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) {
437            entry.insert(
438                get_transform_key(
439                    spatial_node_index,
440                    self.ref_spatial_node_index,
441                    spatial_tree,
442                )
443            );
444        }
445    }
446
447    /// Return true if the transforms for two given spatial nodes are considered equivalent
448    fn are_transforms_equivalent(
449        &mut self,
450        prev_spatial_node_key: &SpatialNodeKey,
451        curr_spatial_node_key: &SpatialNodeKey,
452    ) -> bool {
453        let key = (*prev_spatial_node_key, *curr_spatial_node_key);
454        let spatial_nodes = &self.spatial_nodes;
455
456        *self.compare_cache
457            .entry(key)
458            .or_insert_with(|| {
459                let prev = &spatial_nodes[&prev_spatial_node_key];
460                let curr = &spatial_nodes[&curr_spatial_node_key];
461                curr == prev
462            })
463    }
464
465    /// Ensure that the comparer won't GC any nodes for a given frame id
466    fn retain_for_frame(&mut self, frame_id: FrameId) {
467        self.referenced_frames.insert(frame_id);
468    }
469}
470
471// Immutable context passed to picture cache tiles during pre_update
472struct TilePreUpdateContext {
473    /// Maps from picture cache coords -> world space coords.
474    pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
475
476    /// The optional background color of the picture cache instance
477    background_color: Option<ColorF>,
478
479    /// The visible part of the screen in world coords.
480    global_screen_world_rect: WorldRect,
481
482    /// Current size of tiles in picture units.
483    tile_size: PictureSize,
484
485    /// The current frame id for this picture cache
486    frame_id: FrameId,
487}
488
489// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
490struct TileUpdateDirtyContext<'a> {
491    /// Maps from picture cache coords -> world space coords.
492    pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
493
494    /// Global scale factor from world -> device pixels.
495    global_device_pixel_scale: DevicePixelScale,
496
497    /// Information about opacity bindings from the picture cache.
498    opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
499
500    /// Information about color bindings from the picture cache.
501    color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
502
503    /// The local rect of the overall picture cache
504    local_rect: PictureRect,
505
506    /// If true, the scale factor of the root transform for this picture
507    /// cache changed, so we need to invalidate the tile and re-render.
508    invalidate_all: bool,
509}
510
511// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
512struct TileUpdateDirtyState<'a> {
513    /// Allow access to the texture cache for requesting tiles
514    resource_cache: &'a mut ResourceCache,
515
516    /// Current configuration and setup for compositing all the picture cache tiles in renderer.
517    composite_state: &'a mut CompositeState,
518
519    /// A cache of comparison results to avoid re-computation during invalidation.
520    compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
521
522    /// Information about transform node differences from last frame.
523    spatial_node_comparer: &'a mut SpatialNodeComparer,
524}
525
526// Immutable context passed to picture cache tiles during post_update
527struct TilePostUpdateContext<'a> {
528    /// The local clip rect (in picture space) of the entire picture cache
529    local_clip_rect: PictureRect,
530
531    /// The calculated backdrop information for this cache instance.
532    backdrop: Option<BackdropInfo>,
533
534    /// Current size in device pixels of tiles for this cache
535    current_tile_size: DeviceIntSize,
536
537    /// Pre-allocated z-id to assign to tiles during post_update.
538    z_id: ZBufferId,
539
540    /// The list of compositor underlays for this picture cache
541    underlays: &'a [ExternalSurfaceDescriptor],
542}
543
544// Mutable state passed to picture cache tiles during post_update
545struct TilePostUpdateState<'a> {
546    /// Allow access to the texture cache for requesting tiles
547    resource_cache: &'a mut ResourceCache,
548
549    /// Current configuration and setup for compositing all the picture cache tiles in renderer.
550    composite_state: &'a mut CompositeState,
551}
552
553/// Information about the dependencies of a single primitive instance.
554struct PrimitiveDependencyInfo {
555    /// Unique content identifier of the primitive.
556    prim_uid: ItemUid,
557
558    /// The (conservative) clipped area in picture space this primitive occupies.
559    prim_clip_box: PictureBox2D,
560
561    /// Image keys this primitive depends on.
562    images: SmallVec<[ImageDependency; 8]>,
563
564    /// Opacity bindings this primitive depends on.
565    opacity_bindings: SmallVec<[OpacityBinding; 4]>,
566
567    /// Color binding this primitive depends on.
568    color_binding: Option<ColorBinding>,
569
570    /// Clips that this primitive depends on.
571    clips: SmallVec<[ItemUid; 8]>,
572
573    /// Spatial nodes references by the clip dependencies of this primitive.
574    spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
575}
576
577impl PrimitiveDependencyInfo {
578    /// Construct dependency info for a new primitive.
579    fn new(
580        prim_uid: ItemUid,
581        prim_clip_box: PictureBox2D,
582    ) -> Self {
583        PrimitiveDependencyInfo {
584            prim_uid,
585            images: SmallVec::new(),
586            opacity_bindings: SmallVec::new(),
587            color_binding: None,
588            prim_clip_box,
589            clips: SmallVec::new(),
590            spatial_nodes: SmallVec::new(),
591        }
592    }
593}
594
595/// A stable ID for a given tile, to help debugging. These are also used
596/// as unique identifiers for tile surfaces when using a native compositor.
597#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
598#[cfg_attr(feature = "capture", derive(Serialize))]
599#[cfg_attr(feature = "replay", derive(Deserialize))]
600pub struct TileId(pub usize);
601
602/// Uniquely identifies a tile within a picture cache slice
603#[cfg_attr(feature = "capture", derive(Serialize))]
604#[cfg_attr(feature = "replay", derive(Deserialize))]
605#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
606pub struct TileKey {
607    // Tile index (x,y)
608    pub tile_offset: TileOffset,
609    // Sub-slice (z)
610    pub sub_slice_index: SubSliceIndex,
611}
612
613/// A descriptor for the kind of texture that a picture cache tile will
614/// be drawn into.
615#[derive(Debug)]
616pub enum SurfaceTextureDescriptor {
617    /// When using the WR compositor, the tile is drawn into an entry
618    /// in the WR texture cache.
619    TextureCache {
620        handle: Option<PictureCacheTextureHandle>,
621    },
622    /// When using an OS compositor, the tile is drawn into a native
623    /// surface identified by arbitrary id.
624    Native {
625        /// The arbitrary id of this tile.
626        id: Option<NativeTileId>,
627    },
628}
629
630/// This is the same as a `SurfaceTextureDescriptor` but has been resolved
631/// into a texture cache handle (if appropriate) that can be used by the
632/// batching and compositing code in the renderer.
633#[derive(Clone, Debug, Eq, PartialEq, Hash)]
634#[cfg_attr(feature = "capture", derive(Serialize))]
635#[cfg_attr(feature = "replay", derive(Deserialize))]
636pub enum ResolvedSurfaceTexture {
637    TextureCache {
638        /// The texture ID to draw to.
639        texture: TextureSource,
640    },
641    Native {
642        /// The arbitrary id of this tile.
643        id: NativeTileId,
644        /// The size of the tile in device pixels.
645        size: DeviceIntSize,
646    }
647}
648
649impl SurfaceTextureDescriptor {
650    /// Create a resolved surface texture for this descriptor
651    pub fn resolve(
652        &self,
653        resource_cache: &ResourceCache,
654        size: DeviceIntSize,
655    ) -> ResolvedSurfaceTexture {
656        match self {
657            SurfaceTextureDescriptor::TextureCache { handle } => {
658                let texture = resource_cache
659                    .picture_textures
660                    .get_texture_source(handle.as_ref().unwrap());
661
662                ResolvedSurfaceTexture::TextureCache { texture }
663            }
664            SurfaceTextureDescriptor::Native { id } => {
665                ResolvedSurfaceTexture::Native {
666                    id: id.expect("bug: native surface not allocated"),
667                    size,
668                }
669            }
670        }
671    }
672}
673
674/// The backing surface for this tile.
675#[derive(Debug)]
676pub enum TileSurface {
677    Texture {
678        /// Descriptor for the surface that this tile draws into.
679        descriptor: SurfaceTextureDescriptor,
680    },
681    Color {
682        color: ColorF,
683    },
684    Clear,
685}
686
687impl TileSurface {
688    fn kind(&self) -> &'static str {
689        match *self {
690            TileSurface::Color { .. } => "Color",
691            TileSurface::Texture { .. } => "Texture",
692            TileSurface::Clear => "Clear",
693        }
694    }
695}
696
697/// Optional extra information returned by is_same when
698/// logging is enabled.
699#[derive(Debug, Copy, Clone, PartialEq)]
700#[cfg_attr(feature = "capture", derive(Serialize))]
701#[cfg_attr(feature = "replay", derive(Deserialize))]
702pub enum CompareHelperResult<T> {
703    /// Primitives match
704    Equal,
705    /// Counts differ
706    Count {
707        prev_count: u8,
708        curr_count: u8,
709    },
710    /// Sentinel
711    Sentinel,
712    /// Two items are not equal
713    NotEqual {
714        prev: T,
715        curr: T,
716    },
717    /// User callback returned true on item
718    PredicateTrue {
719        curr: T
720    },
721}
722
723/// The result of a primitive dependency comparison. Size is a u8
724/// since this is a hot path in the code, and keeping the data small
725/// is a performance win.
726#[derive(Debug, Copy, Clone, PartialEq)]
727#[cfg_attr(feature = "capture", derive(Serialize))]
728#[cfg_attr(feature = "replay", derive(Deserialize))]
729#[repr(u8)]
730pub enum PrimitiveCompareResult {
731    /// Primitives match
732    Equal,
733    /// Something in the PrimitiveDescriptor was different
734    Descriptor,
735    /// The clip node content or spatial node changed
736    Clip,
737    /// The value of the transform changed
738    Transform,
739    /// An image dependency was dirty
740    Image,
741    /// The value of an opacity binding changed
742    OpacityBinding,
743    /// The value of a color binding changed
744    ColorBinding,
745}
746
747/// Debugging information about why a tile was invalidated
748#[derive(Debug,Clone)]
749#[cfg_attr(feature = "capture", derive(Serialize))]
750#[cfg_attr(feature = "replay", derive(Deserialize))]
751pub enum InvalidationReason {
752    /// The background color changed
753    BackgroundColor,
754    /// The opaque state of the backing native surface changed
755    SurfaceOpacityChanged,
756    /// There was no backing texture (evicted or never rendered)
757    NoTexture,
758    /// There was no backing native surface (never rendered, or recreated)
759    NoSurface,
760    /// The primitive count in the dependency list was different
761    PrimCount,
762    /// The content of one of the primitives was different
763    Content,
764    // The compositor type changed
765    CompositorKindChanged,
766    // The valid region of the tile changed
767    ValidRectChanged,
768    // The overall scale of the picture cache changed
769    ScaleChanged,
770    // The content of the sampling surface changed
771    SurfaceContentChanged,
772}
773
774/// Information about a cached tile.
775pub struct Tile {
776    /// The grid position of this tile within the picture cache
777    pub tile_offset: TileOffset,
778    /// The current world rect of this tile.
779    pub world_tile_rect: WorldRect,
780    /// The current local rect of this tile.
781    pub local_tile_rect: PictureRect,
782    /// The picture space dirty rect for this tile.
783    pub local_dirty_rect: PictureRect,
784    /// The device space dirty rect for this tile.
785    /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
786    ///           expose these as multiple dirty rects, which will help in some cases.
787    pub device_dirty_rect: DeviceRect,
788    /// World space rect that contains valid pixels region of this tile.
789    pub world_valid_rect: WorldRect,
790    /// Device space rect that contains valid pixels region of this tile.
791    pub device_valid_rect: DeviceRect,
792    /// Uniquely describes the content of this tile, in a way that can be
793    /// (reasonably) efficiently hashed and compared.
794    pub current_descriptor: TileDescriptor,
795    /// The content descriptor for this tile from the previous frame.
796    pub prev_descriptor: TileDescriptor,
797    /// Handle to the backing surface for this tile.
798    pub surface: Option<TileSurface>,
799    /// If true, this tile is marked valid, and the existing texture
800    /// cache handle can be used. Tiles are invalidated during the
801    /// build_dirty_regions method.
802    pub is_valid: bool,
803    /// If true, this tile intersects with the currently visible screen
804    /// rect, and will be drawn.
805    pub is_visible: bool,
806    /// The tile id is stable between display lists and / or frames,
807    /// if the tile is retained. Useful for debugging tile evictions.
808    pub id: TileId,
809    /// If true, the tile was determined to be opaque, which means blending
810    /// can be disabled when drawing it.
811    pub is_opaque: bool,
812    /// Root node of the quadtree dirty rect tracker.
813    root: TileNode,
814    /// The last rendered background color on this tile.
815    background_color: Option<ColorF>,
816    /// The first reason the tile was invalidated this frame.
817    invalidation_reason: Option<InvalidationReason>,
818    /// The local space valid rect for all primitives that affect this tile.
819    pub local_valid_rect: PictureBox2D,
820    /// z-buffer id for this tile
821    pub z_id: ZBufferId,
822    pub sub_graphs: Vec<(PictureRect, Vec<(PictureCompositeMode, SurfaceIndex)>)>,
823}
824
825impl Tile {
826    /// Construct a new, invalid tile.
827    fn new(tile_offset: TileOffset) -> Self {
828        let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
829
830        Tile {
831            tile_offset,
832            local_tile_rect: PictureRect::zero(),
833            world_tile_rect: WorldRect::zero(),
834            world_valid_rect: WorldRect::zero(),
835            device_valid_rect: DeviceRect::zero(),
836            local_dirty_rect: PictureRect::zero(),
837            device_dirty_rect: DeviceRect::zero(),
838            surface: None,
839            current_descriptor: TileDescriptor::new(),
840            prev_descriptor: TileDescriptor::new(),
841            is_valid: false,
842            is_visible: false,
843            id,
844            is_opaque: false,
845            root: TileNode::new_leaf(Vec::new()),
846            background_color: None,
847            invalidation_reason: None,
848            local_valid_rect: PictureBox2D::zero(),
849            z_id: ZBufferId::invalid(),
850            sub_graphs: Vec::new(),
851        }
852    }
853
854    /// Print debug information about this tile to a tree printer.
855    fn print(&self, pt: &mut dyn PrintTreePrinter) {
856        pt.new_level(format!("Tile {:?}", self.id));
857        pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect));
858        pt.add_item(format!("background_color: {:?}", self.background_color));
859        pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
860        self.current_descriptor.print(pt);
861        pt.end_level();
862    }
863
864    /// Check if the content of the previous and current tile descriptors match
865    fn update_dirty_rects(
866        &mut self,
867        ctx: &TileUpdateDirtyContext,
868        state: &mut TileUpdateDirtyState,
869        invalidation_reason: &mut Option<InvalidationReason>,
870        frame_context: &FrameVisibilityContext,
871    ) -> PictureRect {
872        let mut prim_comparer = PrimitiveComparer::new(
873            &self.prev_descriptor,
874            &self.current_descriptor,
875            state.resource_cache,
876            state.spatial_node_comparer,
877            ctx.opacity_bindings,
878            ctx.color_bindings,
879        );
880
881        let mut dirty_rect = PictureBox2D::zero();
882        self.root.update_dirty_rects(
883            &self.prev_descriptor.prims,
884            &self.current_descriptor.prims,
885            &mut prim_comparer,
886            &mut dirty_rect,
887            state.compare_cache,
888            invalidation_reason,
889            frame_context,
890        );
891
892        dirty_rect
893    }
894
895    /// Invalidate a tile based on change in content. This
896    /// must be called even if the tile is not currently
897    /// visible on screen. We might be able to improve this
898    /// later by changing how ComparableVec is used.
899    fn update_content_validity(
900        &mut self,
901        ctx: &TileUpdateDirtyContext,
902        state: &mut TileUpdateDirtyState,
903        frame_context: &FrameVisibilityContext,
904    ) {
905        // Check if the contents of the primitives, clips, and
906        // other dependencies are the same.
907        state.compare_cache.clear();
908        let mut invalidation_reason = None;
909        let dirty_rect = self.update_dirty_rects(
910            ctx,
911            state,
912            &mut invalidation_reason,
913            frame_context,
914        );
915        if !dirty_rect.is_empty() {
916            self.invalidate(
917                Some(dirty_rect),
918                invalidation_reason.expect("bug: no invalidation_reason"),
919            );
920        }
921        if ctx.invalidate_all {
922            self.invalidate(None, InvalidationReason::ScaleChanged);
923        }
924        // TODO(gw): We can avoid invalidating the whole tile in some cases here,
925        //           but it should be a fairly rare invalidation case.
926        if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
927            self.invalidate(None, InvalidationReason::ValidRectChanged);
928            state.composite_state.dirty_rects_are_valid = false;
929        }
930    }
931
932    /// Invalidate this tile. If `invalidation_rect` is None, the entire
933    /// tile is invalidated.
934    fn invalidate(
935        &mut self,
936        invalidation_rect: Option<PictureRect>,
937        reason: InvalidationReason,
938    ) {
939        self.is_valid = false;
940
941        match invalidation_rect {
942            Some(rect) => {
943                self.local_dirty_rect = self.local_dirty_rect.union(&rect);
944            }
945            None => {
946                self.local_dirty_rect = self.local_tile_rect;
947            }
948        }
949
950        if self.invalidation_reason.is_none() {
951            self.invalidation_reason = Some(reason);
952        }
953    }
954
955    /// Called during pre_update of a tile cache instance. Allows the
956    /// tile to setup state before primitive dependency calculations.
957    fn pre_update(
958        &mut self,
959        ctx: &TilePreUpdateContext,
960    ) {
961        self.local_tile_rect = PictureRect::new(
962            PicturePoint::new(
963                self.tile_offset.x as f32 * ctx.tile_size.width,
964                self.tile_offset.y as f32 * ctx.tile_size.height,
965            ),
966            PicturePoint::new(
967                (self.tile_offset.x + 1) as f32 * ctx.tile_size.width,
968                (self.tile_offset.y + 1) as f32 * ctx.tile_size.height,
969            ),
970        );
971        // TODO(gw): This is a hack / fix for Box2D::union in euclid not working with
972        //           zero sized rect accumulation. Once that lands, we'll revert this
973        //           to be zero.
974        self.local_valid_rect = PictureBox2D::new(
975            PicturePoint::new( 1.0e32,  1.0e32),
976            PicturePoint::new(-1.0e32, -1.0e32),
977        );
978        self.invalidation_reason  = None;
979        self.sub_graphs.clear();
980
981        self.world_tile_rect = ctx.pic_to_world_mapper
982            .map(&self.local_tile_rect)
983            .expect("bug: map local tile rect");
984
985        // Check if this tile is currently on screen.
986        self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
987
988        // If the tile isn't visible, early exit, skipping the normal set up to
989        // validate dependencies. Instead, we will only compare the current tile
990        // dependencies the next time it comes into view.
991        if !self.is_visible {
992            return;
993        }
994
995        if ctx.background_color != self.background_color {
996            self.invalidate(None, InvalidationReason::BackgroundColor);
997            self.background_color = ctx.background_color;
998        }
999
1000        // Clear any dependencies so that when we rebuild them we
1001        // can compare if the tile has the same content.
1002        mem::swap(
1003            &mut self.current_descriptor,
1004            &mut self.prev_descriptor,
1005        );
1006        self.current_descriptor.clear();
1007        self.root.clear(self.local_tile_rect);
1008
1009        // Since this tile is determined to be visible, it will get updated
1010        // dependencies, so update the frame id we are storing dependencies for.
1011        self.current_descriptor.last_updated_frame_id = ctx.frame_id;
1012    }
1013
1014    /// Add dependencies for a given primitive to this tile.
1015    fn add_prim_dependency(
1016        &mut self,
1017        info: &PrimitiveDependencyInfo,
1018    ) {
1019        // If this tile isn't currently visible, we don't want to update the dependencies
1020        // for this tile, as an optimization, since it won't be drawn anyway.
1021        if !self.is_visible {
1022            return;
1023        }
1024
1025        // Incorporate the bounding rect of the primitive in the local valid rect
1026        // for this tile. This is used to minimize the size of the scissor rect
1027        // during rasterization and the draw rect during composition of partial tiles.
1028        self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
1029
1030        // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port,
1031        //           which can cause invalidations when a new display list with changed
1032        //           display port is received. To work around this, clamp the prim clip rect
1033        //           to the tile boundaries - if the clip hasn't affected the tile, then the
1034        //           changed clip can't affect the content of the primitive on this tile.
1035        //           In future, we could consider supplying the display port clip from Gecko
1036        //           in a different way (e.g. as a scroll frame clip) which still provides
1037        //           the desired clip for checkerboarding, but doesn't require this extra
1038        //           work below.
1039
1040        // TODO(gw): This is a hot part of the code - we could probably optimize further by:
1041        //           - Using min/max instead of clamps below (if we guarantee the rects are well formed)
1042
1043        let tile_p0 = self.local_tile_rect.min;
1044        let tile_p1 = self.local_tile_rect.max;
1045
1046        let prim_clip_box = PictureBox2D::new(
1047            PicturePoint::new(
1048                clampf(info.prim_clip_box.min.x, tile_p0.x, tile_p1.x),
1049                clampf(info.prim_clip_box.min.y, tile_p0.y, tile_p1.y),
1050            ),
1051            PicturePoint::new(
1052                clampf(info.prim_clip_box.max.x, tile_p0.x, tile_p1.x),
1053                clampf(info.prim_clip_box.max.y, tile_p0.y, tile_p1.y),
1054            ),
1055        );
1056
1057        // Update the tile descriptor, used for tile comparison during scene swaps.
1058        let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
1059
1060        // Encode the deps for this primitive in the `dep_data` byte buffer
1061        let dep_offset = self.current_descriptor.dep_data.len() as u32;
1062        let mut dep_count = 0;
1063
1064        for clip in &info.clips {
1065            dep_count += 1;
1066            poke_into_vec(
1067                &PrimitiveDependency::Clip {
1068                    clip: *clip,
1069                },
1070                &mut self.current_descriptor.dep_data,
1071            );
1072        }
1073
1074        for spatial_node_index in &info.spatial_nodes {
1075            dep_count += 1;
1076            poke_into_vec(
1077                &PrimitiveDependency::SpatialNode {
1078                    index: *spatial_node_index,
1079                },
1080                &mut self.current_descriptor.dep_data,
1081            );
1082        }
1083
1084        for image in &info.images {
1085            dep_count += 1;
1086            poke_into_vec(
1087                &PrimitiveDependency::Image {
1088                    image: *image,
1089                },
1090                &mut self.current_descriptor.dep_data,
1091            );
1092        }
1093
1094        for binding in &info.opacity_bindings {
1095            dep_count += 1;
1096            poke_into_vec(
1097                &PrimitiveDependency::OpacityBinding {
1098                    binding: *binding,
1099                },
1100                &mut self.current_descriptor.dep_data,
1101            );
1102        }
1103
1104        if let Some(ref binding) = info.color_binding {
1105            dep_count += 1;
1106            poke_into_vec(
1107                &PrimitiveDependency::ColorBinding {
1108                    binding: *binding,
1109                },
1110                &mut self.current_descriptor.dep_data,
1111            );
1112        }
1113
1114        self.current_descriptor.prims.push(PrimitiveDescriptor {
1115            prim_uid: info.prim_uid,
1116            prim_clip_box,
1117            dep_offset,
1118            dep_count,
1119        });
1120
1121        // Add this primitive to the dirty rect quadtree.
1122        self.root.add_prim(prim_index, &info.prim_clip_box);
1123    }
1124
1125    /// Called during tile cache instance post_update. Allows invalidation and dirty
1126    /// rect calculation after primitive dependencies have been updated.
1127    fn update_dirty_and_valid_rects(
1128        &mut self,
1129        ctx: &TileUpdateDirtyContext,
1130        state: &mut TileUpdateDirtyState,
1131        frame_context: &FrameVisibilityContext,
1132    ) {
1133        // Ensure peek-poke constraint is met, that `dep_data` is large enough
1134        ensure_red_zone::<PrimitiveDependency>(&mut self.current_descriptor.dep_data);
1135
1136        // Register the frame id of this tile with the spatial node comparer, to ensure
1137        // that it doesn't GC any spatial nodes from the comparer that are referenced
1138        // by this tile. Must be done before we early exit below, so that we retain
1139        // spatial node info even for tiles that are currently not visible.
1140        state.spatial_node_comparer.retain_for_frame(self.current_descriptor.last_updated_frame_id);
1141
1142        // If tile is not visible, just early out from here - we don't update dependencies
1143        // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
1144        // (and thus updated / invalidated) until it is on screen again.
1145        if !self.is_visible {
1146            return;
1147        }
1148
1149        // Calculate the overall valid rect for this tile.
1150        self.current_descriptor.local_valid_rect = self.local_valid_rect;
1151
1152        // TODO(gw): In theory, the local tile rect should always have an
1153        //           intersection with the overall picture rect. In practice,
1154        //           due to some accuracy issues with how fract_offset (and
1155        //           fp accuracy) are used in the calling method, this isn't
1156        //           always true. In this case, it's safe to set the local
1157        //           valid rect to zero, which means it will be clipped out
1158        //           and not affect the scene. In future, we should fix the
1159        //           accuracy issue above, so that this assumption holds, but
1160        //           it shouldn't have any noticeable effect on performance
1161        //           or memory usage (textures should never get allocated).
1162        self.current_descriptor.local_valid_rect = self.local_tile_rect
1163            .intersection(&ctx.local_rect)
1164            .and_then(|r| r.intersection(&self.current_descriptor.local_valid_rect))
1165            .unwrap_or_else(PictureRect::zero);
1166
1167        // The device_valid_rect is referenced during `update_content_validity` so it
1168        // must be updated here first.
1169        self.world_valid_rect = ctx.pic_to_world_mapper
1170            .map(&self.current_descriptor.local_valid_rect)
1171            .expect("bug: map local valid rect");
1172
1173        // The device rect is guaranteed to be aligned on a device pixel - the round
1174        // is just to deal with float accuracy. However, the valid rect is not
1175        // always aligned to a device pixel. To handle this, round out to get all
1176        // required pixels, and intersect with the tile device rect.
1177        let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round();
1178        self.device_valid_rect = (self.world_valid_rect * ctx.global_device_pixel_scale)
1179            .round_out()
1180            .intersection(&device_rect)
1181            .unwrap_or_else(DeviceRect::zero);
1182
1183        // Invalidate the tile based on the content changing.
1184        self.update_content_validity(ctx, state, frame_context);
1185    }
1186
1187    /// Called during tile cache instance post_update. Allows invalidation and dirty
1188    /// rect calculation after primitive dependencies have been updated.
1189    fn post_update(
1190        &mut self,
1191        ctx: &TilePostUpdateContext,
1192        state: &mut TilePostUpdateState,
1193        frame_context: &FrameVisibilityContext,
1194    ) {
1195        // If tile is not visible, just early out from here - we don't update dependencies
1196        // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
1197        // (and thus updated / invalidated) until it is on screen again.
1198        if !self.is_visible {
1199            return;
1200        }
1201
1202        // If there are no primitives there is no need to draw or cache it.
1203        // Bug 1719232 - The final device valid rect does not always describe a non-empty
1204        // region. Cull the tile as a workaround.
1205        if self.current_descriptor.prims.is_empty() || self.device_valid_rect.is_empty() {
1206            // If there is a native compositor surface allocated for this (now empty) tile
1207            // it must be freed here, otherwise the stale tile with previous contents will
1208            // be composited. If the tile subsequently gets new primitives added to it, the
1209            // surface will be re-allocated when it's added to the composite draw list.
1210            if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() {
1211                if let Some(id) = id.take() {
1212                    state.resource_cache.destroy_compositor_tile(id);
1213                }
1214            }
1215
1216            self.is_visible = false;
1217            return;
1218        }
1219
1220        // Check if this tile can be considered opaque. Opacity state must be updated only
1221        // after all early out checks have been performed. Otherwise, we might miss updating
1222        // the native surface next time this tile becomes visible.
1223        let clipped_rect = self.current_descriptor.local_valid_rect
1224            .intersection(&ctx.local_clip_rect)
1225            .unwrap_or_else(PictureRect::zero);
1226
1227        let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
1228        let has_opaque_backdrop = ctx.backdrop.map_or(false, |b| b.opaque_rect.contains_box(&clipped_rect));
1229        let mut is_opaque = has_opaque_bg_color || has_opaque_backdrop;
1230
1231        // If this tile intersects with any underlay surfaces, we need to consider it
1232        // translucent, since it will contain an alpha cutout
1233        for underlay in ctx.underlays {
1234            if clipped_rect.intersects(&underlay.local_rect) {
1235                is_opaque = false;
1236                break;
1237            }
1238        }
1239
1240        // Set the correct z_id for this tile
1241        self.z_id = ctx.z_id;
1242
1243        if is_opaque != self.is_opaque {
1244            // If opacity changed, the native compositor surface and all tiles get invalidated.
1245            // (this does nothing if not using native compositor mode).
1246            // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
1247            //           everything in this case. If it turns out that this isn't true, we could
1248            //           consider other options, such as per-tile opacity (natively supported
1249            //           on CoreAnimation, and supported if backed by non-virtual surfaces in
1250            //           DirectComposition).
1251            if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface {
1252                if let Some(id) = id.take() {
1253                    state.resource_cache.destroy_compositor_tile(id);
1254                }
1255            }
1256
1257            // Invalidate the entire tile to force a redraw.
1258            self.invalidate(None, InvalidationReason::SurfaceOpacityChanged);
1259            self.is_opaque = is_opaque;
1260        }
1261
1262        // Check if the selected composite mode supports dirty rect updates. For Draw composite
1263        // mode, we can always update the content with smaller dirty rects, unless there is a
1264        // driver bug to workaround. For native composite mode, we can only use dirty rects if
1265        // the compositor supports partial surface updates.
1266        let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
1267            CompositorKind::Draw { .. } => {
1268                (frame_context.config.gpu_supports_render_target_partial_update, true)
1269            }
1270            CompositorKind::Native { capabilities, .. } => {
1271                (capabilities.max_update_rects > 0, false)
1272            }
1273            CompositorKind::Layer { .. } => {
1274                // TODO(gwc): Support partial updates here
1275                (false, true)
1276            }
1277        };
1278
1279        // TODO(gw): Consider using smaller tiles and/or tile splits for
1280        //           native compositors that don't support dirty rects.
1281        if supports_dirty_rects {
1282            // Only allow splitting for normal content sized tiles
1283            if ctx.current_tile_size == state.resource_cache.picture_textures.default_tile_size() {
1284                let max_split_level = 3;
1285
1286                // Consider splitting / merging dirty regions
1287                self.root.maybe_merge_or_split(
1288                    0,
1289                    &self.current_descriptor.prims,
1290                    max_split_level,
1291                );
1292            }
1293        }
1294
1295        // The dirty rect will be set correctly by now. If the underlying platform
1296        // doesn't support partial updates, and this tile isn't valid, force the dirty
1297        // rect to be the size of the entire tile.
1298        if !self.is_valid && !supports_dirty_rects {
1299            self.local_dirty_rect = self.local_tile_rect;
1300        }
1301
1302        // See if this tile is a simple color, in which case we can just draw
1303        // it as a rect, and avoid allocating a texture surface and drawing it.
1304        // TODO(gw): Initial native compositor interface doesn't support simple
1305        //           color tiles. We can definitely support this in DC, so this
1306        //           should be added as a follow up.
1307        let is_simple_prim =
1308            ctx.backdrop.map_or(false, |b| b.kind.is_some()) &&
1309            self.current_descriptor.prims.len() == 1 &&
1310            self.is_opaque &&
1311            supports_simple_prims;
1312
1313        // Set up the backing surface for this tile.
1314        let surface = if is_simple_prim {
1315            // If we determine the tile can be represented by a color, set the
1316            // surface unconditionally (this will drop any previously used
1317            // texture cache backing surface).
1318            match ctx.backdrop.unwrap().kind {
1319                Some(BackdropKind::Color { color }) => {
1320                    TileSurface::Color {
1321                        color,
1322                    }
1323                }
1324                Some(BackdropKind::Clear) => {
1325                    TileSurface::Clear
1326                }
1327                None => {
1328                    // This should be prevented by the is_simple_prim check above.
1329                    unreachable!();
1330                }
1331            }
1332        } else {
1333            // If this tile will be backed by a surface, we want to retain
1334            // the texture handle from the previous frame, if possible. If
1335            // the tile was previously a color, or not set, then just set
1336            // up a new texture cache handle.
1337            match self.surface.take() {
1338                Some(TileSurface::Texture { descriptor }) => {
1339                    // Reuse the existing descriptor and vis mask
1340                    TileSurface::Texture {
1341                        descriptor,
1342                    }
1343                }
1344                Some(TileSurface::Color { .. }) | Some(TileSurface::Clear) | None => {
1345                    // This is the case where we are constructing a tile surface that
1346                    // involves drawing to a texture. Create the correct surface
1347                    // descriptor depending on the compositing mode that will read
1348                    // the output.
1349                    let descriptor = match state.composite_state.compositor_kind {
1350                        CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
1351                            // For a texture cache entry, create an invalid handle that
1352                            // will be allocated when update_picture_cache is called.
1353                            SurfaceTextureDescriptor::TextureCache {
1354                                handle: None,
1355                            }
1356                        }
1357                        CompositorKind::Native { .. } => {
1358                            // Create a native surface surface descriptor, but don't allocate
1359                            // a surface yet. The surface is allocated *after* occlusion
1360                            // culling occurs, so that only visible tiles allocate GPU memory.
1361                            SurfaceTextureDescriptor::Native {
1362                                id: None,
1363                            }
1364                        }
1365                    };
1366
1367                    TileSurface::Texture {
1368                        descriptor,
1369                    }
1370                }
1371            }
1372        };
1373
1374        // Store the current surface backing info for use during batching.
1375        self.surface = Some(surface);
1376    }
1377}
1378
1379/// Defines a key that uniquely identifies a primitive instance.
1380#[derive(Debug, Clone)]
1381#[cfg_attr(feature = "capture", derive(Serialize))]
1382#[cfg_attr(feature = "replay", derive(Deserialize))]
1383pub struct PrimitiveDescriptor {
1384    pub prim_uid: ItemUid,
1385    pub prim_clip_box: PictureBox2D,
1386    // TODO(gw): These two fields could be packed as a u24/u8
1387    pub dep_offset: u32,
1388    pub dep_count: u32,
1389}
1390
1391impl PartialEq for PrimitiveDescriptor {
1392    fn eq(&self, other: &Self) -> bool {
1393        const EPSILON: f32 = 0.001;
1394
1395        if self.prim_uid != other.prim_uid {
1396            return false;
1397        }
1398
1399        if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) {
1400            return false;
1401        }
1402        if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) {
1403            return false;
1404        }
1405        if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) {
1406            return false;
1407        }
1408        if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) {
1409            return false;
1410        }
1411
1412        if self.dep_count != other.dep_count {
1413            return false;
1414        }
1415
1416        true
1417    }
1418}
1419
1420/// Uniquely describes the content of this tile, in a way that can be
1421/// (reasonably) efficiently hashed and compared.
1422#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
1423#[cfg_attr(feature = "capture", derive(Serialize))]
1424#[cfg_attr(feature = "replay", derive(Deserialize))]
1425pub struct TileDescriptor {
1426    /// List of primitive instance unique identifiers. The uid is guaranteed
1427    /// to uniquely describe the content of the primitive template, while
1428    /// the other parameters describe the clip chain and instance params.
1429    prims: Vec<PrimitiveDescriptor>,
1430
1431    /// Picture space rect that contains valid pixels region of this tile.
1432    pub local_valid_rect: PictureRect,
1433
1434    /// The last frame this tile had its dependencies updated (dependency updating is
1435    /// skipped if a tile is off-screen).
1436    last_updated_frame_id: FrameId,
1437
1438    /// Packed per-prim dependency information
1439    dep_data: Vec<u8>,
1440}
1441
1442impl TileDescriptor {
1443    fn new() -> Self {
1444        TileDescriptor {
1445            local_valid_rect: PictureRect::zero(),
1446            dep_data: Vec::new(),
1447            prims: Vec::new(),
1448            last_updated_frame_id: FrameId::INVALID,
1449        }
1450    }
1451
1452    /// Print debug information about this tile descriptor to a tree printer.
1453    fn print(&self, pt: &mut dyn PrintTreePrinter) {
1454        pt.new_level("current_descriptor".to_string());
1455
1456        pt.new_level("prims".to_string());
1457        for prim in &self.prims {
1458            pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
1459            pt.add_item(format!("clip: p0={},{} p1={},{}",
1460                prim.prim_clip_box.min.x,
1461                prim.prim_clip_box.min.y,
1462                prim.prim_clip_box.max.x,
1463                prim.prim_clip_box.max.y,
1464            ));
1465            pt.end_level();
1466        }
1467        pt.end_level();
1468
1469        pt.end_level();
1470    }
1471
1472    /// Clear the dependency information for a tile, when the dependencies
1473    /// are being rebuilt.
1474    fn clear(&mut self) {
1475        self.local_valid_rect = PictureRect::zero();
1476        self.prims.clear();
1477        self.dep_data.clear();
1478    }
1479}
1480
1481/// Represents the dirty region of a tile cache picture, relative to a
1482/// "visibility" spatial node. At the moment the visibility node is
1483/// world space, but the plan is to switch to raster space.
1484///
1485/// The plan is to move away from these world space representation and
1486/// compute dirty regions in raster space instead.
1487#[derive(Clone)]
1488pub struct DirtyRegion {
1489    /// The overall dirty rect, a combination of dirty_rects
1490    pub combined: VisRect,
1491
1492    /// The corrdinate space used to do clipping, visibility, and
1493    /// dirty rect calculations.
1494    pub visibility_spatial_node: SpatialNodeIndex,
1495    /// Spatial node of the picture this region represents.
1496    local_spatial_node: SpatialNodeIndex,
1497}
1498
1499impl DirtyRegion {
1500    /// Construct a new dirty region tracker.
1501    pub fn new(
1502        visibility_spatial_node: SpatialNodeIndex,
1503        local_spatial_node: SpatialNodeIndex,
1504    ) -> Self {
1505        DirtyRegion {
1506            combined: VisRect::zero(),
1507            visibility_spatial_node,
1508            local_spatial_node,
1509        }
1510    }
1511
1512    /// Reset the dirty regions back to empty
1513    pub fn reset(
1514        &mut self,
1515        visibility_spatial_node: SpatialNodeIndex,
1516        local_spatial_node: SpatialNodeIndex,
1517    ) {
1518        self.combined = VisRect::zero();
1519        self.visibility_spatial_node = visibility_spatial_node;
1520        self.local_spatial_node = local_spatial_node;
1521    }
1522
1523    /// Add a dirty region to the tracker. Returns the visibility mask that corresponds to
1524    /// this region in the tracker.
1525    pub fn add_dirty_region(
1526        &mut self,
1527        rect_in_pic_space: PictureRect,
1528        spatial_tree: &SpatialTree,
1529    ) {
1530        let map_pic_to_raster = SpaceMapper::new_with_target(
1531            self.visibility_spatial_node,
1532            self.local_spatial_node,
1533            VisRect::max_rect(),
1534            spatial_tree,
1535        );
1536
1537        let raster_rect = map_pic_to_raster
1538            .map(&rect_in_pic_space)
1539            .expect("bug");
1540
1541        // Include this in the overall dirty rect
1542        self.combined = self.combined.union(&raster_rect);
1543    }
1544}
1545
1546// TODO(gw): Tidy this up by:
1547//      - Rename Clear variant to something more appropriate to what it does
1548//      - Add an Other variant for things like opaque gradient backdrops
1549#[derive(Debug, Copy, Clone)]
1550pub enum BackdropKind {
1551    Color {
1552        color: ColorF,
1553    },
1554    Clear,
1555}
1556
1557/// Stores information about the calculated opaque backdrop of this slice.
1558#[derive(Debug, Copy, Clone)]
1559pub struct BackdropInfo {
1560    /// The picture space rectangle that is known to be opaque. This is used
1561    /// to determine where subpixel AA can be used, and where alpha blending
1562    /// can be disabled.
1563    pub opaque_rect: PictureRect,
1564    /// If the backdrop covers the entire slice with an opaque color, this
1565    /// will be set and can be used as a clear color for the slice's tiles.
1566    pub spanning_opaque_color: Option<ColorF>,
1567    /// Kind of the backdrop
1568    pub kind: Option<BackdropKind>,
1569    /// The picture space rectangle of the backdrop, if kind is set.
1570    pub backdrop_rect: PictureRect,
1571}
1572
1573impl BackdropInfo {
1574    fn empty() -> Self {
1575        BackdropInfo {
1576            opaque_rect: PictureRect::zero(),
1577            spanning_opaque_color: None,
1578            kind: None,
1579            backdrop_rect: PictureRect::zero(),
1580        }
1581    }
1582}
1583
1584/// Represents the native surfaces created for a picture cache, if using
1585/// a native compositor. An opaque and alpha surface is always created,
1586/// but tiles are added to a surface based on current opacity. If the
1587/// calculated opacity of a tile changes, the tile is invalidated and
1588/// attached to a different native surface. This means that we don't
1589/// need to invalidate the entire surface if only some tiles are changing
1590/// opacity. It also means we can take advantage of opaque tiles on cache
1591/// slices where only some of the tiles are opaque. There is an assumption
1592/// that creating a native surface is cheap, and only when a tile is added
1593/// to a surface is there a significant cost. This assumption holds true
1594/// for the current native compositor implementations on Windows and Mac.
1595pub struct NativeSurface {
1596    /// Native surface for opaque tiles
1597    pub opaque: NativeSurfaceId,
1598    /// Native surface for alpha tiles
1599    pub alpha: NativeSurfaceId,
1600}
1601
1602/// Hash key for an external native compositor surface
1603#[derive(PartialEq, Eq, Hash)]
1604pub struct ExternalNativeSurfaceKey {
1605    /// The YUV/RGB image keys that are used to draw this surface.
1606    pub image_keys: [ImageKey; 3],
1607    /// If this is not an 'external' compositor surface created via
1608    /// Compositor::create_external_surface, this is set to the
1609    /// current device size of the surface.
1610    pub size: Option<DeviceIntSize>,
1611}
1612
1613/// Information about a native compositor surface cached between frames.
1614pub struct ExternalNativeSurface {
1615    /// If true, the surface was used this frame. Used for a simple form
1616    /// of GC to remove old surfaces.
1617    pub used_this_frame: bool,
1618    /// The native compositor surface handle
1619    pub native_surface_id: NativeSurfaceId,
1620    /// List of image keys, and current image generations, that are drawn in this surface.
1621    /// The image generations are used to check if the compositor surface is dirty and
1622    /// needs to be updated.
1623    pub image_dependencies: [ImageDependency; 3],
1624}
1625
1626/// The key that identifies a tile cache instance. For now, it's simple the index of
1627/// the slice as it was created during scene building.
1628#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1629#[cfg_attr(feature = "capture", derive(Serialize))]
1630#[cfg_attr(feature = "replay", derive(Deserialize))]
1631pub struct SliceId(usize);
1632
1633impl SliceId {
1634    pub fn new(index: usize) -> Self {
1635        SliceId(index)
1636    }
1637}
1638
1639/// Information that is required to reuse or create a new tile cache. Created
1640/// during scene building and passed to the render backend / frame builder.
1641pub struct TileCacheParams {
1642    // The current debug flags for the system.
1643    pub debug_flags: DebugFlags,
1644    // Index of the slice (also effectively the key of the tile cache, though we use SliceId where that matters)
1645    pub slice: usize,
1646    // Flags describing content of this cache (e.g. scrollbars)
1647    pub slice_flags: SliceFlags,
1648    // The anchoring spatial node / scroll root
1649    pub spatial_node_index: SpatialNodeIndex,
1650    // The space in which visibility/invalidation/clipping computations are done.
1651    pub visibility_node_index: SpatialNodeIndex,
1652    // Optional background color of this tilecache. If present, can be used as an optimization
1653    // to enable opaque blending and/or subpixel AA in more places.
1654    pub background_color: Option<ColorF>,
1655    // Node in the clip-tree that defines where we exclude clips from child prims
1656    pub shared_clip_node_id: ClipNodeId,
1657    // Clip leaf that is used to build the clip-chain for this tile cache.
1658    pub shared_clip_leaf_id: Option<ClipLeafId>,
1659    // Virtual surface sizes are always square, so this represents both the width and height
1660    pub virtual_surface_size: i32,
1661    // The number of Image surfaces that are being requested for this tile cache.
1662    // This is only a suggestion - the tile cache will clamp this as a reasonable number
1663    // and only promote a limited number of surfaces.
1664    pub image_surface_count: usize,
1665    // The number of YuvImage surfaces that are being requested for this tile cache.
1666    // This is only a suggestion - the tile cache will clamp this as a reasonable number
1667    // and only promote a limited number of surfaces.
1668    pub yuv_image_surface_count: usize,
1669}
1670
1671/// Defines which sub-slice (effectively a z-index) a primitive exists on within
1672/// a picture cache instance.
1673#[cfg_attr(feature = "capture", derive(Serialize))]
1674#[cfg_attr(feature = "replay", derive(Deserialize))]
1675#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1676pub struct SubSliceIndex(u8);
1677
1678impl SubSliceIndex {
1679    pub const DEFAULT: SubSliceIndex = SubSliceIndex(0);
1680
1681    pub fn new(index: usize) -> Self {
1682        SubSliceIndex(index as u8)
1683    }
1684
1685    /// Return true if this sub-slice is the primary sub-slice (for now, we assume
1686    /// that only the primary sub-slice may be opaque and support subpixel AA, for example).
1687    pub fn is_primary(&self) -> bool {
1688        self.0 == 0
1689    }
1690
1691    /// Get an array index for this sub-slice
1692    pub fn as_usize(&self) -> usize {
1693        self.0 as usize
1694    }
1695}
1696
1697/// Wrapper struct around an external surface descriptor with a little more information
1698/// that the picture caching code needs.
1699pub struct CompositorSurface {
1700    // External surface descriptor used by compositing logic
1701    pub descriptor: ExternalSurfaceDescriptor,
1702    // The compositor surface rect + any intersecting prims. Later prims that intersect
1703    // with this must be added to the next sub-slice.
1704    prohibited_rect: PictureRect,
1705    // If the compositor surface content is opaque.
1706    pub is_opaque: bool,
1707}
1708
1709/// A SubSlice represents a potentially overlapping set of tiles within a picture cache. Most
1710/// picture cache instances will have only a single sub-slice. The exception to this is when
1711/// a picture cache has compositor surfaces, in which case sub slices are used to interleave
1712/// content under or order the compositor surface(s).
1713pub struct SubSlice {
1714    /// Hash of tiles present in this picture.
1715    pub tiles: FastHashMap<TileOffset, Box<Tile>>,
1716    /// The allocated compositor surfaces for this picture cache. May be None if
1717    /// not using native compositor, or if the surface was destroyed and needs
1718    /// to be reallocated next time this surface contains valid tiles.
1719    pub native_surface: Option<NativeSurface>,
1720    /// List of compositor surfaces that have been promoted from primitives
1721    /// in this tile cache.
1722    pub compositor_surfaces: Vec<CompositorSurface>,
1723    /// List of visible tiles to be composited for this subslice
1724    pub composite_tiles: Vec<CompositeTile>,
1725    /// Compositor descriptors of visible, opaque tiles (used by composite_state.push_surface)
1726    pub opaque_tile_descriptors: Vec<CompositeTileDescriptor>,
1727    /// Compositor descriptors of visible, alpha tiles (used by composite_state.push_surface)
1728    pub alpha_tile_descriptors: Vec<CompositeTileDescriptor>,
1729}
1730
1731impl SubSlice {
1732    /// Construct a new sub-slice
1733    fn new() -> Self {
1734        SubSlice {
1735            tiles: FastHashMap::default(),
1736            native_surface: None,
1737            compositor_surfaces: Vec::new(),
1738            composite_tiles: Vec::new(),
1739            opaque_tile_descriptors: Vec::new(),
1740            alpha_tile_descriptors: Vec::new(),
1741        }
1742    }
1743
1744    /// Reset the list of compositor surfaces that follow this sub-slice.
1745    /// Built per-frame, since APZ may change whether an image is suitable to be a compositor surface.
1746    fn reset(&mut self) {
1747        self.compositor_surfaces.clear();
1748        self.composite_tiles.clear();
1749        self.opaque_tile_descriptors.clear();
1750        self.alpha_tile_descriptors.clear();
1751    }
1752
1753    /// Resize the tile grid to match a new tile bounds
1754    fn resize(&mut self, new_tile_rect: TileRect) -> FastHashMap<TileOffset, Box<Tile>> {
1755        let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
1756        self.tiles.reserve(new_tile_rect.area() as usize);
1757
1758        for y in new_tile_rect.min.y .. new_tile_rect.max.y {
1759            for x in new_tile_rect.min.x .. new_tile_rect.max.x {
1760                let key = TileOffset::new(x, y);
1761                let tile = old_tiles
1762                    .remove(&key)
1763                    .unwrap_or_else(|| {
1764                        Box::new(Tile::new(key))
1765                    });
1766                self.tiles.insert(key, tile);
1767            }
1768        }
1769
1770        old_tiles
1771    }
1772}
1773
1774pub struct BackdropSurface {
1775    pub id: NativeSurfaceId,
1776    color: ColorF,
1777    pub device_rect: DeviceRect,
1778}
1779
1780/// Represents a cache of tiles that make up a picture primitives.
1781pub struct TileCacheInstance {
1782    // The current debug flags for the system.
1783    pub debug_flags: DebugFlags,
1784    /// Index of the tile cache / slice for this frame builder. It's determined
1785    /// by the setup_picture_caching method during flattening, which splits the
1786    /// picture tree into multiple slices. It's used as a simple input to the tile
1787    /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed
1788    /// between display lists - this seems very unlikely to occur on most pages, but
1789    /// can be revisited if we ever notice that.
1790    pub slice: usize,
1791    /// Propagated information about the slice
1792    pub slice_flags: SliceFlags,
1793    /// The currently selected tile size to use for this cache
1794    pub current_tile_size: DeviceIntSize,
1795    /// The list of sub-slices in this tile cache
1796    pub sub_slices: Vec<SubSlice>,
1797    /// The positioning node for this tile cache.
1798    pub spatial_node_index: SpatialNodeIndex,
1799    /// The coordinate space to do visibility/clipping/invalidation in.
1800    pub visibility_node_index: SpatialNodeIndex,
1801    /// List of opacity bindings, with some extra information
1802    /// about whether they changed since last frame.
1803    opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
1804    /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
1805    old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
1806    /// A helper to compare transforms between previous and current frame.
1807    spatial_node_comparer: SpatialNodeComparer,
1808    /// List of color bindings, with some extra information
1809    /// about whether they changed since last frame.
1810    color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
1811    /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
1812    old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
1813    /// The current dirty region tracker for this picture.
1814    pub dirty_region: DirtyRegion,
1815    /// Current size of tiles in picture units.
1816    tile_size: PictureSize,
1817    /// Tile coords of the currently allocated grid.
1818    tile_rect: TileRect,
1819    /// Pre-calculated versions of the tile_rect above, used to speed up the
1820    /// calculations in get_tile_coords_for_rect.
1821    tile_bounds_p0: TileOffset,
1822    tile_bounds_p1: TileOffset,
1823    /// Local rect (unclipped) of the picture this cache covers.
1824    pub local_rect: PictureRect,
1825    /// The local clip rect, from the shared clips of this picture.
1826    pub local_clip_rect: PictureRect,
1827    /// Registered clip in CompositeState for this picture cache
1828    pub compositor_clip: Option<CompositorClipIndex>,
1829    /// The screen rect, transformed to local picture space.
1830    pub screen_rect_in_pic_space: PictureRect,
1831    /// The surface index that this tile cache will be drawn into.
1832    surface_index: SurfaceIndex,
1833    /// The background color from the renderer. If this is set opaque, we know it's
1834    /// fine to clear the tiles to this and allow subpixel text on the first slice.
1835    pub background_color: Option<ColorF>,
1836    /// Information about the calculated backdrop content of this cache.
1837    pub backdrop: BackdropInfo,
1838    /// The allowed subpixel mode for this surface, which depends on the detected
1839    /// opacity of the background.
1840    pub subpixel_mode: SubpixelMode,
1841    // Node in the clip-tree that defines where we exclude clips from child prims
1842    pub shared_clip_node_id: ClipNodeId,
1843    // Clip leaf that is used to build the clip-chain for this tile cache.
1844    pub shared_clip_leaf_id: Option<ClipLeafId>,
1845    /// The number of frames until this cache next evaluates what tile size to use.
1846    /// If a picture rect size is regularly changing just around a size threshold,
1847    /// we don't want to constantly invalidate and reallocate different tile size
1848    /// configuration each frame.
1849    frames_until_size_eval: usize,
1850    /// For DirectComposition, virtual surfaces don't support negative coordinates. However,
1851    /// picture cache tile coordinates can be negative. To handle this, we apply an offset
1852    /// to each tile in DirectComposition. We want to change this as little as possible,
1853    /// to avoid invalidating tiles. However, if we have a picture cache tile coordinate
1854    /// which is outside the virtual surface bounds, we must change this to allow
1855    /// correct remapping of the coordinates passed to BeginDraw in DC.
1856    virtual_offset: DeviceIntPoint,
1857    /// keep around the hash map used as compare_cache to avoid reallocating it each
1858    /// frame.
1859    compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
1860    /// The currently considered tile size override. Used to check if we should
1861    /// re-evaluate tile size, even if the frame timer hasn't expired.
1862    tile_size_override: Option<DeviceIntSize>,
1863    /// A cache of compositor surfaces that are retained between frames
1864    pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
1865    /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting
1866    frame_id: FrameId,
1867    /// Registered transform in CompositeState for this picture cache
1868    pub transform_index: CompositorTransformIndex,
1869    /// Current transform mapping local picture space to compositor surface raster space
1870    local_to_raster: ScaleOffset,
1871    /// Current transform mapping compositor surface raster space to final device space
1872    raster_to_device: ScaleOffset,
1873    /// If true, we need to invalidate all tiles during `post_update`
1874    invalidate_all_tiles: bool,
1875    /// The current raster scale for tiles in this cache
1876    current_raster_scale: f32,
1877    /// Depth of off-screen surfaces that are currently pushed during dependency updates
1878    current_surface_traversal_depth: usize,
1879    /// A list of extra dirty invalidation tests that can only be checked once we
1880    /// know the dirty rect of all tiles
1881    deferred_dirty_tests: Vec<DeferredDirtyTest>,
1882    /// Is there a backdrop associated with this cache
1883    found_prims_after_backdrop: bool,
1884    pub backdrop_surface: Option<BackdropSurface>,
1885    /// List of underlay compositor surfaces that exist in this picture cache
1886    pub underlays: Vec<ExternalSurfaceDescriptor>,
1887    /// "Region" (actually a spanning rect) containing all overlay promoted surfaces
1888    pub overlay_region: PictureRect,
1889    /// The number YuvImage prims in this cache, provided in our TileCacheParams.
1890    pub yuv_images_count: usize,
1891    /// The remaining number of YuvImage prims we will see this frame. We prioritize
1892    /// promoting these before promoting any Image prims.
1893    pub yuv_images_remaining: usize,
1894}
1895
1896#[derive(Clone, Copy)]
1897enum SurfacePromotionFailure {
1898    ImageWaitingOnYuvImage,
1899    NotPremultipliedAlpha,
1900    OverlaySurfaceLimit,
1901    OverlayNeedsMask,
1902    UnderlayAlphaBackdrop,
1903    UnderlaySurfaceLimit,
1904    UnderlayIntersectsOverlay,
1905    UnderlayLowQualityZoom,
1906    NotRootTileCache,
1907    ComplexTransform,
1908    SliceAtomic,
1909    SizeTooLarge,
1910}
1911
1912impl Display for SurfacePromotionFailure {
1913    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
1914        write!(
1915            f,
1916            "{}",
1917            match *self {
1918                SurfacePromotionFailure::ImageWaitingOnYuvImage => "Image prim waiting for all YuvImage prims to be considered for promotion",
1919                SurfacePromotionFailure::NotPremultipliedAlpha => "does not use premultiplied alpha",
1920                SurfacePromotionFailure::OverlaySurfaceLimit => "hit the overlay surface limit",
1921                SurfacePromotionFailure::OverlayNeedsMask => "overlay not allowed for prim with mask",
1922                SurfacePromotionFailure::UnderlayAlphaBackdrop => "underlay requires an opaque backdrop",
1923                SurfacePromotionFailure::UnderlaySurfaceLimit => "hit the underlay surface limit",
1924                SurfacePromotionFailure::UnderlayIntersectsOverlay => "underlay intersects already-promoted overlay",
1925                SurfacePromotionFailure::UnderlayLowQualityZoom => "underlay not allowed during low-quality pinch zoom",
1926                SurfacePromotionFailure::NotRootTileCache => "is not on a root tile cache",
1927                SurfacePromotionFailure::ComplexTransform => "has a complex transform",
1928                SurfacePromotionFailure::SliceAtomic => "slice is atomic",
1929                SurfacePromotionFailure::SizeTooLarge => "surface is too large for compositor",
1930            }.to_owned()
1931        )
1932    }
1933}
1934
1935impl TileCacheInstance {
1936    pub fn new(params: TileCacheParams) -> Self {
1937        // Determine how many sub-slices we need. Clamp to an arbitrary limit to ensure
1938        // we don't create a huge number of OS compositor tiles and sub-slices.
1939        let sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1;
1940
1941        let mut sub_slices = Vec::with_capacity(sub_slice_count);
1942        for _ in 0 .. sub_slice_count {
1943            sub_slices.push(SubSlice::new());
1944        }
1945
1946        TileCacheInstance {
1947            debug_flags: params.debug_flags,
1948            slice: params.slice,
1949            slice_flags: params.slice_flags,
1950            spatial_node_index: params.spatial_node_index,
1951            visibility_node_index: params.visibility_node_index,
1952            sub_slices,
1953            opacity_bindings: FastHashMap::default(),
1954            old_opacity_bindings: FastHashMap::default(),
1955            spatial_node_comparer: SpatialNodeComparer::new(),
1956            color_bindings: FastHashMap::default(),
1957            old_color_bindings: FastHashMap::default(),
1958            dirty_region: DirtyRegion::new(params.visibility_node_index, params.spatial_node_index),
1959            tile_size: PictureSize::zero(),
1960            tile_rect: TileRect::zero(),
1961            tile_bounds_p0: TileOffset::zero(),
1962            tile_bounds_p1: TileOffset::zero(),
1963            local_rect: PictureRect::zero(),
1964            local_clip_rect: PictureRect::zero(),
1965            compositor_clip: None,
1966            screen_rect_in_pic_space: PictureRect::zero(),
1967            surface_index: SurfaceIndex(0),
1968            background_color: params.background_color,
1969            backdrop: BackdropInfo::empty(),
1970            subpixel_mode: SubpixelMode::Allow,
1971            shared_clip_node_id: params.shared_clip_node_id,
1972            shared_clip_leaf_id: params.shared_clip_leaf_id,
1973            current_tile_size: DeviceIntSize::zero(),
1974            frames_until_size_eval: 0,
1975            // Default to centering the virtual offset in the middle of the DC virtual surface
1976            virtual_offset: DeviceIntPoint::new(
1977                params.virtual_surface_size / 2,
1978                params.virtual_surface_size / 2,
1979            ),
1980            compare_cache: FastHashMap::default(),
1981            tile_size_override: None,
1982            external_native_surface_cache: FastHashMap::default(),
1983            frame_id: FrameId::INVALID,
1984            transform_index: CompositorTransformIndex::INVALID,
1985            raster_to_device: ScaleOffset::identity(),
1986            local_to_raster: ScaleOffset::identity(),
1987            invalidate_all_tiles: true,
1988            current_raster_scale: 1.0,
1989            current_surface_traversal_depth: 0,
1990            deferred_dirty_tests: Vec::new(),
1991            found_prims_after_backdrop: false,
1992            backdrop_surface: None,
1993            underlays: Vec::new(),
1994            overlay_region: PictureRect::zero(),
1995            yuv_images_count: params.yuv_image_surface_count,
1996            yuv_images_remaining: 0,
1997        }
1998    }
1999
2000    /// Return the total number of tiles allocated by this tile cache
2001    pub fn tile_count(&self) -> usize {
2002        self.tile_rect.area() as usize * self.sub_slices.len()
2003    }
2004
2005    /// Trims memory held by the tile cache, such as native surfaces.
2006    pub fn memory_pressure(&mut self, resource_cache: &mut ResourceCache) {
2007        for sub_slice in &mut self.sub_slices {
2008            for tile in sub_slice.tiles.values_mut() {
2009                if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2010                    // Reseting the id to None with take() ensures that a new
2011                    // tile will be allocated during the next frame build.
2012                    if let Some(id) = id.take() {
2013                        resource_cache.destroy_compositor_tile(id);
2014                    }
2015                }
2016            }
2017            if let Some(native_surface) = sub_slice.native_surface.take() {
2018                resource_cache.destroy_compositor_surface(native_surface.opaque);
2019                resource_cache.destroy_compositor_surface(native_surface.alpha);
2020            }
2021        }
2022    }
2023
2024    /// Reset this tile cache with the updated parameters from a new scene
2025    /// that has arrived. This allows the tile cache to be retained across
2026    /// new scenes.
2027    pub fn prepare_for_new_scene(
2028        &mut self,
2029        params: TileCacheParams,
2030        resource_cache: &mut ResourceCache,
2031    ) {
2032        // We should only receive updated state for matching slice key
2033        assert_eq!(self.slice, params.slice);
2034
2035        // Determine how many sub-slices we need, based on how many compositor surface prims are
2036        // in the supplied primitive list.
2037        let required_sub_slice_count = (params.image_surface_count + params.yuv_image_surface_count).min(MAX_COMPOSITOR_SURFACES) + 1;
2038
2039        if self.sub_slices.len() != required_sub_slice_count {
2040            self.tile_rect = TileRect::zero();
2041
2042            if self.sub_slices.len() > required_sub_slice_count {
2043                let old_sub_slices = self.sub_slices.split_off(required_sub_slice_count);
2044
2045                for mut sub_slice in old_sub_slices {
2046                    for tile in sub_slice.tiles.values_mut() {
2047                        if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2048                            if let Some(id) = id.take() {
2049                                resource_cache.destroy_compositor_tile(id);
2050                            }
2051                        }
2052                    }
2053
2054                    if let Some(native_surface) = sub_slice.native_surface {
2055                        resource_cache.destroy_compositor_surface(native_surface.opaque);
2056                        resource_cache.destroy_compositor_surface(native_surface.alpha);
2057                    }
2058                }
2059            } else {
2060                while self.sub_slices.len() < required_sub_slice_count {
2061                    self.sub_slices.push(SubSlice::new());
2062                }
2063            }
2064        }
2065
2066        // Store the parameters from the scene builder for this slice. Other
2067        // params in the tile cache are retained and reused, or are always
2068        // updated during pre/post_update.
2069        self.slice_flags = params.slice_flags;
2070        self.spatial_node_index = params.spatial_node_index;
2071        self.background_color = params.background_color;
2072        self.shared_clip_leaf_id = params.shared_clip_leaf_id;
2073        self.shared_clip_node_id = params.shared_clip_node_id;
2074
2075        // Since the slice flags may have changed, ensure we re-evaluate the
2076        // appropriate tile size for this cache next update.
2077        self.frames_until_size_eval = 0;
2078
2079        // Update the number of YuvImage prims we have in the scene.
2080        self.yuv_images_count = params.yuv_image_surface_count;
2081    }
2082
2083    /// Destroy any manually managed resources before this picture cache is
2084    /// destroyed, such as native compositor surfaces.
2085    pub fn destroy(
2086        self,
2087        resource_cache: &mut ResourceCache,
2088    ) {
2089        for sub_slice in self.sub_slices {
2090            if let Some(native_surface) = sub_slice.native_surface {
2091                resource_cache.destroy_compositor_surface(native_surface.opaque);
2092                resource_cache.destroy_compositor_surface(native_surface.alpha);
2093            }
2094        }
2095
2096        for (_, external_surface) in self.external_native_surface_cache {
2097            resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
2098        }
2099
2100        if let Some(backdrop_surface) = &self.backdrop_surface {
2101            resource_cache.destroy_compositor_surface(backdrop_surface.id);
2102        }
2103    }
2104
2105    /// Get the tile coordinates for a given rectangle.
2106    fn get_tile_coords_for_rect(
2107        &self,
2108        rect: &PictureRect,
2109    ) -> (TileOffset, TileOffset) {
2110        // Get the tile coordinates in the picture space.
2111        let mut p0 = TileOffset::new(
2112            (rect.min.x / self.tile_size.width).floor() as i32,
2113            (rect.min.y / self.tile_size.height).floor() as i32,
2114        );
2115
2116        let mut p1 = TileOffset::new(
2117            (rect.max.x / self.tile_size.width).ceil() as i32,
2118            (rect.max.y / self.tile_size.height).ceil() as i32,
2119        );
2120
2121        // Clamp the tile coordinates here to avoid looping over irrelevant tiles later on.
2122        p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
2123        p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
2124        p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
2125        p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
2126
2127        (p0, p1)
2128    }
2129
2130    /// Update transforms, opacity, color bindings and tile rects.
2131    pub fn pre_update(
2132        &mut self,
2133        surface_index: SurfaceIndex,
2134        frame_context: &FrameVisibilityContext,
2135        frame_state: &mut FrameVisibilityState,
2136    ) -> WorldRect {
2137        let surface = &frame_state.surfaces[surface_index.0];
2138        let pic_rect = surface.unclipped_local_rect;
2139
2140        self.surface_index = surface_index;
2141        self.local_rect = pic_rect;
2142        self.local_clip_rect = PictureRect::max_rect();
2143        self.deferred_dirty_tests.clear();
2144        self.underlays.clear();
2145        self.overlay_region = PictureRect::zero();
2146        self.yuv_images_remaining = self.yuv_images_count;
2147
2148        for sub_slice in &mut self.sub_slices {
2149            sub_slice.reset();
2150        }
2151
2152        // Reset the opaque rect + subpixel mode, as they are calculated
2153        // during the prim dependency checks.
2154        self.backdrop = BackdropInfo::empty();
2155
2156        // Calculate the screen rect in picture space, for later comparison against
2157        // backdrops, and prims potentially covering backdrops.
2158        let pic_to_world_mapper = SpaceMapper::new_with_target(
2159            frame_context.root_spatial_node_index,
2160            self.spatial_node_index,
2161            frame_context.global_screen_world_rect,
2162            frame_context.spatial_tree,
2163        );
2164        self.screen_rect_in_pic_space = pic_to_world_mapper
2165            .unmap(&frame_context.global_screen_world_rect)
2166            .expect("unable to unmap screen rect");
2167
2168        let pic_to_vis_mapper = SpaceMapper::new_with_target(
2169            // TODO: use the raster node instead of the root node.
2170            frame_context.root_spatial_node_index,
2171            self.spatial_node_index,
2172            surface.culling_rect,
2173            frame_context.spatial_tree,
2174        );
2175
2176        // If there is a valid set of shared clips, build a clip chain instance for this,
2177        // which will provide a local clip rect. This is useful for establishing things
2178        // like whether the backdrop rect supplied by Gecko can be considered opaque.
2179        if let Some(shared_clip_leaf_id) = self.shared_clip_leaf_id {
2180            let map_local_to_picture = SpaceMapper::new(
2181                self.spatial_node_index,
2182                pic_rect,
2183            );
2184
2185            frame_state.clip_store.set_active_clips(
2186                self.spatial_node_index,
2187                map_local_to_picture.ref_spatial_node_index,
2188                surface.visibility_spatial_node_index,
2189                shared_clip_leaf_id,
2190                frame_context.spatial_tree,
2191                &mut frame_state.data_stores.clip,
2192                &frame_state.clip_tree,
2193            );
2194
2195            let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance(
2196                pic_rect.cast_unit(),
2197                &map_local_to_picture,
2198                &pic_to_vis_mapper,
2199                frame_context.spatial_tree,
2200                frame_state.gpu_cache,
2201                frame_state.resource_cache,
2202                frame_context.global_device_pixel_scale,
2203                &surface.culling_rect,
2204                &mut frame_state.data_stores.clip,
2205                frame_state.rg_builder,
2206                true,
2207            );
2208
2209            // Ensure that if the entire picture cache is clipped out, the local
2210            // clip rect is zero. This makes sure we don't register any occluders
2211            // that are actually off-screen.
2212            self.local_clip_rect = PictureRect::zero();
2213            self.compositor_clip = None;
2214
2215            if let Some(clip_chain) = clip_chain_instance {
2216                self.local_clip_rect = clip_chain.pic_coverage_rect;
2217
2218                self.compositor_clip = if clip_chain.needs_mask {
2219                    let clip_instance = frame_state
2220                        .clip_store
2221                        .get_instance_from_range(&clip_chain.clips_range, 0);
2222                    let clip_node = &frame_state.data_stores.clip[clip_instance.handle];
2223
2224                    let index = match clip_node.item.kind {
2225                        ClipItemKind::RoundedRectangle { rect, radius, mode } => {
2226                            assert_eq!(mode, ClipMode::Clip);
2227
2228                            // Map the clip in to device space. We know from the shared
2229                            // clip creation logic it's in root coord system, so only a
2230                            // 2d axis-aligned transform can apply. For example, in the
2231                            // case of a pinch-zoom effect.
2232                            let map = ClipSpaceConversion::new(
2233                                frame_context.root_spatial_node_index,
2234                                clip_node.item.spatial_node_index,
2235                                frame_context.root_spatial_node_index,                   
2236                                frame_context.spatial_tree,
2237                            );
2238
2239                            let (rect, radius) = match map {
2240                                ClipSpaceConversion::Local => {
2241                                    (rect.cast_unit(), radius)
2242                                }
2243                                ClipSpaceConversion::ScaleOffset(scale_offset) => {
2244                                    (
2245                                        scale_offset.map_rect(&rect),
2246                                        BorderRadius {
2247                                            top_left: scale_offset.map_size(&radius.top_left),
2248                                            top_right: scale_offset.map_size(&radius.top_right),
2249                                            bottom_left: scale_offset.map_size(&radius.bottom_left),
2250                                            bottom_right: scale_offset.map_size(&radius.bottom_right),
2251                                        },
2252                                    )
2253                                }
2254                                ClipSpaceConversion::Transform(..) => {
2255                                    unreachable!();                                    
2256                                }
2257                            };
2258
2259                            frame_state.composite_state.register_clip(
2260                                rect,
2261                                radius,
2262                            )
2263                        }
2264                        _ => {
2265                            // The logic to check for shared clips excludes other mask
2266                            // clip types (box-shadow, image-mask) and ensures that the
2267                            // clip is in the root coord system (so rect clips can't
2268                            // produce a mask).
2269                            unreachable!();
2270                        }
2271                    };
2272
2273                    Some(index)
2274                } else {
2275                    None
2276                };
2277            }
2278        }
2279
2280        // Advance the current frame ID counter for this picture cache (must be done
2281        // after any retained prev state is taken above).
2282        self.frame_id.advance();
2283
2284        // Notify the spatial node comparer that a new frame has started, and the
2285        // current reference spatial node for this tile cache.
2286        self.spatial_node_comparer.next_frame(self.spatial_node_index);
2287
2288        // At the start of the frame, step through each current compositor surface
2289        // and mark it as unused. Later, this is used to free old compositor surfaces.
2290        // TODO(gw): In future, we might make this more sophisticated - for example,
2291        //           retaining them for >1 frame if unused, or retaining them in some
2292        //           kind of pool to reduce future allocations.
2293        for external_native_surface in self.external_native_surface_cache.values_mut() {
2294            external_native_surface.used_this_frame = false;
2295        }
2296
2297        // Only evaluate what tile size to use fairly infrequently, so that we don't end
2298        // up constantly invalidating and reallocating tiles if the picture rect size is
2299        // changing near a threshold value.
2300        if self.frames_until_size_eval == 0 ||
2301           self.tile_size_override != frame_context.config.tile_size_override {
2302
2303            // Work out what size tile is appropriate for this picture cache.
2304            let desired_tile_size = match frame_context.config.tile_size_override {
2305                Some(tile_size_override) => {
2306                    tile_size_override
2307                }
2308                None => {
2309                    if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) {
2310                        if pic_rect.width() <= pic_rect.height() {
2311                            TILE_SIZE_SCROLLBAR_VERTICAL
2312                        } else {
2313                            TILE_SIZE_SCROLLBAR_HORIZONTAL
2314                        }
2315                    } else {
2316                        frame_state.resource_cache.picture_textures.default_tile_size()
2317                    }
2318                }
2319            };
2320
2321            // If the desired tile size has changed, then invalidate and drop any
2322            // existing tiles.
2323            if desired_tile_size != self.current_tile_size {
2324                for sub_slice in &mut self.sub_slices {
2325                    // Destroy any native surfaces on the tiles that will be dropped due
2326                    // to resizing.
2327                    if let Some(native_surface) = sub_slice.native_surface.take() {
2328                        frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
2329                        frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
2330                    }
2331                    sub_slice.tiles.clear();
2332                }
2333                self.tile_rect = TileRect::zero();
2334                self.current_tile_size = desired_tile_size;
2335            }
2336
2337            // Reset counter until next evaluating the desired tile size. This is an
2338            // arbitrary value.
2339            self.frames_until_size_eval = 120;
2340            self.tile_size_override = frame_context.config.tile_size_override;
2341        }
2342
2343        // Get the complete scale-offset from local space to device space
2344        let local_to_device = get_relative_scale_offset(
2345            self.spatial_node_index,
2346            frame_context.root_spatial_node_index,
2347            frame_context.spatial_tree,
2348        );
2349
2350        // Get the compositor transform, which depends on pinch-zoom mode
2351        let mut raster_to_device = local_to_device;
2352
2353        if frame_context.config.low_quality_pinch_zoom {
2354            raster_to_device.scale.x /= self.current_raster_scale;
2355            raster_to_device.scale.y /= self.current_raster_scale;
2356        } else {
2357            raster_to_device.scale.x = 1.0;
2358            raster_to_device.scale.y = 1.0;
2359        }
2360
2361        // Use that compositor transform to calculate a relative local to surface
2362        let local_to_raster = local_to_device.then(&raster_to_device.inverse());
2363
2364        const EPSILON: f32 = 0.001;
2365        let compositor_translation_changed =
2366            !raster_to_device.offset.x.approx_eq_eps(&self.raster_to_device.offset.x, &EPSILON) ||
2367            !raster_to_device.offset.y.approx_eq_eps(&self.raster_to_device.offset.y, &EPSILON);
2368        let compositor_scale_changed =
2369            !raster_to_device.scale.x.approx_eq_eps(&self.raster_to_device.scale.x, &EPSILON) ||
2370            !raster_to_device.scale.y.approx_eq_eps(&self.raster_to_device.scale.y, &EPSILON);
2371        let surface_scale_changed =
2372            !local_to_raster.scale.x.approx_eq_eps(&self.local_to_raster.scale.x, &EPSILON) ||
2373            !local_to_raster.scale.y.approx_eq_eps(&self.local_to_raster.scale.y, &EPSILON);
2374
2375        if compositor_translation_changed ||
2376           compositor_scale_changed ||
2377           surface_scale_changed ||
2378           frame_context.config.force_invalidation {
2379            frame_state.composite_state.dirty_rects_are_valid = false;
2380        }
2381
2382        self.raster_to_device = raster_to_device;
2383        self.local_to_raster = local_to_raster;
2384        self.invalidate_all_tiles = surface_scale_changed || frame_context.config.force_invalidation;
2385
2386        // Do a hacky diff of opacity binding values from the last frame. This is
2387        // used later on during tile invalidation tests.
2388        let current_properties = frame_context.scene_properties.float_properties();
2389        mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings);
2390
2391        self.opacity_bindings.clear();
2392        for (id, value) in current_properties {
2393            let changed = match self.old_opacity_bindings.get(id) {
2394                Some(old_property) => !old_property.value.approx_eq(value),
2395                None => true,
2396            };
2397            self.opacity_bindings.insert(*id, OpacityBindingInfo {
2398                value: *value,
2399                changed,
2400            });
2401        }
2402
2403        // Do a hacky diff of color binding values from the last frame. This is
2404        // used later on during tile invalidation tests.
2405        let current_properties = frame_context.scene_properties.color_properties();
2406        mem::swap(&mut self.color_bindings, &mut self.old_color_bindings);
2407
2408        self.color_bindings.clear();
2409        for (id, value) in current_properties {
2410            let changed = match self.old_color_bindings.get(id) {
2411                Some(old_property) => old_property.value != (*value).into(),
2412                None => true,
2413            };
2414            self.color_bindings.insert(*id, ColorBindingInfo {
2415                value: (*value).into(),
2416                changed,
2417            });
2418        }
2419
2420        let world_tile_size = WorldSize::new(
2421            self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
2422            self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
2423        );
2424
2425        self.tile_size = PictureSize::new(
2426            world_tile_size.width / self.local_to_raster.scale.x,
2427            world_tile_size.height / self.local_to_raster.scale.y,
2428        );
2429
2430        // Inflate the needed rect a bit, so that we retain tiles that we have drawn
2431        // but have just recently gone off-screen. This means that we avoid re-drawing
2432        // tiles if the user is scrolling up and down small amounts, at the cost of
2433        // a bit of extra texture memory.
2434        let desired_rect_in_pic_space = self.screen_rect_in_pic_space
2435            .inflate(0.0, 1.0 * self.tile_size.height);
2436
2437        let needed_rect_in_pic_space = desired_rect_in_pic_space
2438            .intersection(&pic_rect)
2439            .unwrap_or_else(Box2D::zero);
2440
2441        let p0 = needed_rect_in_pic_space.min;
2442        let p1 = needed_rect_in_pic_space.max;
2443
2444        let x0 = (p0.x / self.tile_size.width).floor() as i32;
2445        let x1 = (p1.x / self.tile_size.width).ceil() as i32;
2446
2447        let y0 = (p0.y / self.tile_size.height).floor() as i32;
2448        let y1 = (p1.y / self.tile_size.height).ceil() as i32;
2449
2450        let new_tile_rect = TileRect {
2451            min: TileOffset::new(x0, y0),
2452            max: TileOffset::new(x1, y1),
2453        };
2454
2455        // Determine whether the current bounds of the tile grid will exceed the
2456        // bounds of the DC virtual surface, taking into account the current
2457        // virtual offset. If so, we need to invalidate all tiles, and set up
2458        // a new virtual offset, centered around the current tile grid.
2459
2460        let virtual_surface_size = frame_context.config.compositor_kind.get_virtual_surface_size();
2461        // We only need to invalidate in this case if the underlying platform
2462        // uses virtual surfaces.
2463        if virtual_surface_size > 0 {
2464            // Get the extremities of the tile grid after virtual offset is applied
2465            let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width;
2466            let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height;
2467            let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width;
2468            let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height;
2469
2470            let need_new_virtual_offset = tx0 < 0 ||
2471                                          ty0 < 0 ||
2472                                          tx1 >= virtual_surface_size ||
2473                                          ty1 >= virtual_surface_size;
2474
2475            if need_new_virtual_offset {
2476                // Calculate a new virtual offset, centered around the middle of the
2477                // current tile grid. This means we won't need to invalidate and get
2478                // a new offset for a long time!
2479                self.virtual_offset = DeviceIntPoint::new(
2480                    (virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width,
2481                    (virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height,
2482                );
2483
2484                // Invalidate all native tile surfaces. They will be re-allocated next time
2485                // they are scheduled to be rasterized.
2486                for sub_slice in &mut self.sub_slices {
2487                    for tile in sub_slice.tiles.values_mut() {
2488                        if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2489                            if let Some(id) = id.take() {
2490                                frame_state.resource_cache.destroy_compositor_tile(id);
2491                                tile.surface = None;
2492                                // Invalidate the entire tile to force a redraw.
2493                                // TODO(gw): Add a new invalidation reason for virtual offset changing
2494                                tile.invalidate(None, InvalidationReason::CompositorKindChanged);
2495                            }
2496                        }
2497                    }
2498
2499                    // Destroy the native virtual surfaces. They will be re-allocated next time a tile
2500                    // that references them is scheduled to draw.
2501                    if let Some(native_surface) = sub_slice.native_surface.take() {
2502                        frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
2503                        frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
2504                    }
2505                }
2506            }
2507        }
2508
2509        // Rebuild the tile grid if the picture cache rect has changed.
2510        if new_tile_rect != self.tile_rect {
2511            for sub_slice in &mut self.sub_slices {
2512                let mut old_tiles = sub_slice.resize(new_tile_rect);
2513
2514                // When old tiles that remain after the loop, dirty rects are not valid.
2515                if !old_tiles.is_empty() {
2516                    frame_state.composite_state.dirty_rects_are_valid = false;
2517                }
2518
2519                // Any old tiles that remain after the loop above are going to be dropped. For
2520                // simple composite mode, the texture cache handle will expire and be collected
2521                // by the texture cache. For native compositor mode, we need to explicitly
2522                // invoke a callback to the client to destroy that surface.
2523                frame_state.composite_state.destroy_native_tiles(
2524                    old_tiles.values_mut(),
2525                    frame_state.resource_cache,
2526                );
2527            }
2528        }
2529
2530        // This is duplicated information from tile_rect, but cached here to avoid
2531        // redundant calculations during get_tile_coords_for_rect
2532        self.tile_bounds_p0 = TileOffset::new(x0, y0);
2533        self.tile_bounds_p1 = TileOffset::new(x1, y1);
2534        self.tile_rect = new_tile_rect;
2535
2536        let mut world_culling_rect = WorldRect::zero();
2537
2538        let mut ctx = TilePreUpdateContext {
2539            pic_to_world_mapper,
2540            background_color: self.background_color,
2541            global_screen_world_rect: frame_context.global_screen_world_rect,
2542            tile_size: self.tile_size,
2543            frame_id: self.frame_id,
2544        };
2545
2546        // Pre-update each tile
2547        for sub_slice in &mut self.sub_slices {
2548            for tile in sub_slice.tiles.values_mut() {
2549                tile.pre_update(&ctx);
2550
2551                // Only include the tiles that are currently in view into the world culling
2552                // rect. This is a very important optimization for a couple of reasons:
2553                // (1) Primitives that intersect with tiles in the grid that are not currently
2554                //     visible can be skipped from primitive preparation, clip chain building
2555                //     and tile dependency updates.
2556                // (2) When we need to allocate an off-screen surface for a child picture (for
2557                //     example a CSS filter) we clip the size of the GPU surface to the world
2558                //     culling rect below (to ensure we draw enough of it to be sampled by any
2559                //     tiles that reference it). Making the world culling rect only affected
2560                //     by visible tiles (rather than the entire virtual tile display port) can
2561                //     result in allocating _much_ smaller GPU surfaces for cases where the
2562                //     true off-screen surface size is very large.
2563                if tile.is_visible {
2564                    world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
2565                }
2566            }
2567
2568            // The background color can only be applied to the first sub-slice.
2569            ctx.background_color = None;
2570        }
2571
2572        // If compositor mode is changed, need to drop all incompatible tiles.
2573        match frame_context.config.compositor_kind {
2574            CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
2575                for sub_slice in &mut self.sub_slices {
2576                    for tile in sub_slice.tiles.values_mut() {
2577                        if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2578                            if let Some(id) = id.take() {
2579                                frame_state.resource_cache.destroy_compositor_tile(id);
2580                            }
2581                            tile.surface = None;
2582                            // Invalidate the entire tile to force a redraw.
2583                            tile.invalidate(None, InvalidationReason::CompositorKindChanged);
2584                        }
2585                    }
2586
2587                    if let Some(native_surface) = sub_slice.native_surface.take() {
2588                        frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
2589                        frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
2590                    }
2591                }
2592
2593                for (_, external_surface) in self.external_native_surface_cache.drain() {
2594                    frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
2595                }
2596            }
2597            CompositorKind::Native { .. } => {
2598                // This could hit even when compositor mode is not changed,
2599                // then we need to check if there are incompatible tiles.
2600                for sub_slice in &mut self.sub_slices {
2601                    for tile in sub_slice.tiles.values_mut() {
2602                        if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface {
2603                            tile.surface = None;
2604                            // Invalidate the entire tile to force a redraw.
2605                            tile.invalidate(None, InvalidationReason::CompositorKindChanged);
2606                        }
2607                    }
2608                }
2609            }
2610        }
2611
2612        world_culling_rect
2613    }
2614
2615    fn can_promote_to_surface(
2616        &mut self,
2617        prim_clip_chain: &ClipChainInstance,
2618        prim_spatial_node_index: SpatialNodeIndex,
2619        is_root_tile_cache: bool,
2620        sub_slice_index: usize,
2621        surface_kind: CompositorSurfaceKind,
2622        pic_coverage_rect: PictureRect,
2623        frame_context: &FrameVisibilityContext,
2624    ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
2625        use crate::picture::SurfacePromotionFailure::*;
2626
2627        // Each strategy has different restrictions on whether we can promote
2628        match surface_kind {
2629            CompositorSurfaceKind::Overlay => {
2630                // For now, only support a small (arbitrary) number of compositor surfaces.
2631                // Non-opaque compositor surfaces require sub-slices, as they are drawn
2632                // as overlays.
2633                if sub_slice_index == self.sub_slices.len() - 1 {
2634                    return Err(OverlaySurfaceLimit);
2635                }
2636
2637                // If a complex clip is being applied to this primitive, it can't be
2638                // promoted directly to a compositor surface.
2639                if prim_clip_chain.needs_mask {
2640                    return Err(OverlayNeedsMask);
2641                }
2642            }
2643            CompositorSurfaceKind::Underlay => {
2644                // If a mask is needed, there are some restrictions.
2645                if prim_clip_chain.needs_mask {
2646                    // Need an opaque region behind this prim. The opaque region doesn't
2647                    // need to span the entire visible region of the TileCacheInstance,
2648                    // which would set self.backdrop.kind, but that also qualifies.
2649                    if !self.backdrop.opaque_rect.contains_box(&pic_coverage_rect) {
2650                        return Err(UnderlayAlphaBackdrop);
2651                    }
2652
2653                    // Only one masked underlay allowed.
2654                    if !self.underlays.is_empty() {
2655                        return Err(UnderlaySurfaceLimit);
2656                    }
2657                }
2658
2659                // Underlays can't appear on top of overlays, because they can't punch
2660                // through the existing overlay.
2661                if self.overlay_region.intersects(&pic_coverage_rect) {
2662                    return Err(UnderlayIntersectsOverlay);
2663                }
2664
2665                // Underlay cutouts are difficult to align with compositor surfaces when
2666                // compositing during low-quality zoom, and the required invalidation
2667                // whilst zooming would prevent low-quality zoom from working efficiently.
2668                if frame_context.config.low_quality_pinch_zoom &&
2669                    frame_context.spatial_tree.get_spatial_node(prim_spatial_node_index).is_ancestor_or_self_zooming
2670                {
2671                    return Err(UnderlayLowQualityZoom);
2672                }
2673            }
2674            CompositorSurfaceKind::Blit => unreachable!(),
2675        }
2676
2677        // If not on the root picture cache, it has some kind of
2678        // complex effect (such as a filter, mix-blend-mode or 3d transform).
2679        if !is_root_tile_cache {
2680            return Err(NotRootTileCache);
2681        }
2682
2683        let mapper : SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
2684            frame_context.root_spatial_node_index,
2685            prim_spatial_node_index,
2686            frame_context.global_screen_world_rect,
2687            &frame_context.spatial_tree);
2688        let transform = mapper.get_transform();
2689        if !transform.is_2d_scale_translation() {
2690            return Err(ComplexTransform);
2691        }
2692
2693        if self.slice_flags.contains(SliceFlags::IS_ATOMIC) {
2694            return Err(SliceAtomic);
2695        }
2696
2697        Ok(surface_kind)
2698    }
2699
2700    fn setup_compositor_surfaces_yuv(
2701        &mut self,
2702        sub_slice_index: usize,
2703        prim_info: &mut PrimitiveDependencyInfo,
2704        flags: PrimitiveFlags,
2705        local_prim_rect: LayoutRect,
2706        prim_spatial_node_index: SpatialNodeIndex,
2707        pic_coverage_rect: PictureRect,
2708        frame_context: &FrameVisibilityContext,
2709        image_dependencies: &[ImageDependency;3],
2710        api_keys: &[ImageKey; 3],
2711        resource_cache: &mut ResourceCache,
2712        composite_state: &mut CompositeState,
2713        gpu_cache: &mut GpuCache,
2714        image_rendering: ImageRendering,
2715        color_depth: ColorDepth,
2716        color_space: YuvRangedColorSpace,
2717        format: YuvFormat,
2718        surface_kind: CompositorSurfaceKind,
2719    ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
2720        for &key in api_keys {
2721            if key != ImageKey::DUMMY {
2722                // TODO: See comment in setup_compositor_surfaces_rgb.
2723                resource_cache.request_image(ImageRequest {
2724                        key,
2725                        rendering: image_rendering,
2726                        tile: None,
2727                    },
2728                    gpu_cache,
2729                );
2730            }
2731        }
2732
2733        self.setup_compositor_surfaces_impl(
2734            sub_slice_index,
2735            prim_info,
2736            flags,
2737            local_prim_rect,
2738            prim_spatial_node_index,
2739            pic_coverage_rect,
2740            frame_context,
2741            ExternalSurfaceDependency::Yuv {
2742                image_dependencies: *image_dependencies,
2743                color_space,
2744                format,
2745                channel_bit_depth: color_depth.bit_depth(),
2746            },
2747            api_keys,
2748            resource_cache,
2749            composite_state,
2750            image_rendering,
2751            true,
2752            surface_kind,
2753        )
2754    }
2755
2756    fn setup_compositor_surfaces_rgb(
2757        &mut self,
2758        sub_slice_index: usize,
2759        prim_info: &mut PrimitiveDependencyInfo,
2760        flags: PrimitiveFlags,
2761        local_prim_rect: LayoutRect,
2762        prim_spatial_node_index: SpatialNodeIndex,
2763        pic_coverage_rect: PictureRect,
2764        frame_context: &FrameVisibilityContext,
2765        image_dependency: ImageDependency,
2766        api_key: ImageKey,
2767        resource_cache: &mut ResourceCache,
2768        composite_state: &mut CompositeState,
2769        gpu_cache: &mut GpuCache,
2770        image_rendering: ImageRendering,
2771        is_opaque: bool,
2772        surface_kind: CompositorSurfaceKind,
2773    ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
2774        let mut api_keys = [ImageKey::DUMMY; 3];
2775        api_keys[0] = api_key;
2776
2777        // TODO: The picture compositing code requires images promoted
2778        // into their own picture cache slices to be requested every
2779        // frame even if they are not visible. However the image updates
2780        // are only reached on the prepare pass for visible primitives.
2781        // So we make sure to trigger an image request when promoting
2782        // the image here.
2783        resource_cache.request_image(ImageRequest {
2784                key: api_key,
2785                rendering: image_rendering,
2786                tile: None,
2787            },
2788            gpu_cache,
2789        );
2790
2791        self.setup_compositor_surfaces_impl(
2792            sub_slice_index,
2793            prim_info,
2794            flags,
2795            local_prim_rect,
2796            prim_spatial_node_index,
2797            pic_coverage_rect,
2798            frame_context,
2799            ExternalSurfaceDependency::Rgb {
2800                image_dependency,
2801            },
2802            &api_keys,
2803            resource_cache,
2804            composite_state,
2805            image_rendering,
2806            is_opaque,
2807            surface_kind,
2808        )
2809    }
2810
2811    // returns false if composition is not available for this surface,
2812    // and the non-compositor path should be used to draw it instead.
2813    fn setup_compositor_surfaces_impl(
2814        &mut self,
2815        sub_slice_index: usize,
2816        prim_info: &mut PrimitiveDependencyInfo,
2817        flags: PrimitiveFlags,
2818        local_prim_rect: LayoutRect,
2819        prim_spatial_node_index: SpatialNodeIndex,
2820        pic_coverage_rect: PictureRect,
2821        frame_context: &FrameVisibilityContext,
2822        dependency: ExternalSurfaceDependency,
2823        api_keys: &[ImageKey; 3],
2824        resource_cache: &mut ResourceCache,
2825        composite_state: &mut CompositeState,
2826        image_rendering: ImageRendering,
2827        is_opaque: bool,
2828        surface_kind: CompositorSurfaceKind,
2829    ) -> Result<CompositorSurfaceKind, SurfacePromotionFailure> {
2830        use crate::picture::SurfacePromotionFailure::*;
2831
2832        let map_local_to_picture = SpaceMapper::new_with_target(
2833            self.spatial_node_index,
2834            prim_spatial_node_index,
2835            self.local_rect,
2836            frame_context.spatial_tree,
2837        );
2838
2839        // Map the primitive local rect into picture space.
2840        let prim_rect = match map_local_to_picture.map(&local_prim_rect) {
2841            Some(rect) => rect,
2842            None => return Ok(surface_kind),
2843        };
2844
2845        // If the rect is invalid, no need to create dependencies.
2846        if prim_rect.is_empty() {
2847            return Ok(surface_kind);
2848        }
2849
2850        let pic_to_world_mapper = SpaceMapper::new_with_target(
2851            frame_context.root_spatial_node_index,
2852            self.spatial_node_index,
2853            frame_context.global_screen_world_rect,
2854            frame_context.spatial_tree,
2855        );
2856
2857        let world_clip_rect = pic_to_world_mapper
2858            .map(&prim_info.prim_clip_box)
2859            .expect("bug: unable to map clip to world space");
2860
2861        let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
2862        if !is_visible {
2863            return Ok(surface_kind);
2864        }
2865
2866        let prim_offset = ScaleOffset::from_offset(local_prim_rect.min.to_vector().cast_unit());
2867
2868        let local_prim_to_device = get_relative_scale_offset(
2869            prim_spatial_node_index,
2870            frame_context.root_spatial_node_index,
2871            frame_context.spatial_tree,
2872        );
2873
2874        let normalized_prim_to_device = prim_offset.then(&local_prim_to_device);
2875
2876        let local_to_raster = ScaleOffset::identity();
2877        let raster_to_device = normalized_prim_to_device;
2878
2879        // If this primitive is an external image, and supports being used
2880        // directly by a native compositor, then lookup the external image id
2881        // so we can pass that through.
2882        let mut external_image_id = if flags.contains(PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE)
2883            && image_rendering == ImageRendering::Auto {
2884            resource_cache.get_image_properties(api_keys[0])
2885                .and_then(|properties| properties.external_image)
2886                .and_then(|image| Some(image.id))
2887        } else {
2888            None
2889        };
2890
2891
2892        if let CompositorKind::Native { capabilities, .. } = composite_state.compositor_kind {
2893            if external_image_id.is_some() &&
2894               !capabilities.supports_external_compositor_surface_negative_scaling &&
2895               (raster_to_device.scale.x < 0.0 || raster_to_device.scale.y < 0.0) {
2896                external_image_id = None;
2897            }
2898        }
2899
2900        let compositor_transform_index = composite_state.register_transform(
2901            local_to_raster,
2902            raster_to_device,
2903        );
2904
2905        let surface_size = composite_state.get_surface_rect(
2906            &local_prim_rect,
2907            &local_prim_rect,
2908            compositor_transform_index,
2909        ).size();
2910
2911        let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
2912
2913        if surface_size.width >= MAX_COMPOSITOR_SURFACES_SIZE ||
2914           surface_size.height >= MAX_COMPOSITOR_SURFACES_SIZE {
2915           return Err(SizeTooLarge);
2916        }
2917
2918        // When using native compositing, we need to find an existing native surface
2919        // handle to use, or allocate a new one. For existing native surfaces, we can
2920        // also determine whether this needs to be updated, depending on whether the
2921        // image generation(s) of the planes have changed since last composite.
2922        let (native_surface_id, update_params) = match composite_state.compositor_kind {
2923            CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
2924                (None, None)
2925            }
2926            CompositorKind::Native { .. } => {
2927                let native_surface_size = surface_size.to_i32();
2928
2929                let key = ExternalNativeSurfaceKey {
2930                    image_keys: *api_keys,
2931                    size: if external_image_id.is_some() { None } else { Some(native_surface_size) },
2932                };
2933
2934                let native_surface = self.external_native_surface_cache
2935                    .entry(key)
2936                    .or_insert_with(|| {
2937                        // No existing surface, so allocate a new compositor surface.
2938                        let native_surface_id = match external_image_id {
2939                            Some(_external_image) => {
2940                                // If we have a suitable external image, then create an external
2941                                // surface to attach to.
2942                                resource_cache.create_compositor_external_surface(is_opaque)
2943                            }
2944                            None => {
2945                                // Otherwise create a normal compositor surface and a single
2946                                // compositor tile that covers the entire surface.
2947                                let native_surface_id =
2948                                resource_cache.create_compositor_surface(
2949                                    DeviceIntPoint::zero(),
2950                                    native_surface_size,
2951                                    is_opaque,
2952                                );
2953
2954                                let tile_id = NativeTileId {
2955                                    surface_id: native_surface_id,
2956                                    x: 0,
2957                                    y: 0,
2958                                };
2959                                resource_cache.create_compositor_tile(tile_id);
2960
2961                                native_surface_id
2962                            }
2963                        };
2964
2965                        ExternalNativeSurface {
2966                            used_this_frame: true,
2967                            native_surface_id,
2968                            image_dependencies: [ImageDependency::INVALID; 3],
2969                        }
2970                    });
2971
2972                // Mark that the surface is referenced this frame so that the
2973                // backing native surface handle isn't freed.
2974                native_surface.used_this_frame = true;
2975
2976                let update_params = match external_image_id {
2977                    Some(external_image) => {
2978                        // If this is an external image surface, then there's no update
2979                        // to be done. Just attach the current external image to the surface
2980                        // and we're done.
2981                        resource_cache.attach_compositor_external_image(
2982                            native_surface.native_surface_id,
2983                            external_image,
2984                        );
2985                        None
2986                    }
2987                    None => {
2988                        // If the image dependencies match, there is no need to update
2989                        // the backing native surface.
2990                        match dependency {
2991                            ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => {
2992                                if image_dependencies == native_surface.image_dependencies {
2993                                    None
2994                                } else {
2995                                    Some(native_surface_size)
2996                                }
2997                            },
2998                            ExternalSurfaceDependency::Rgb{ image_dependency, .. } => {
2999                                if image_dependency == native_surface.image_dependencies[0] {
3000                                    None
3001                                } else {
3002                                    Some(native_surface_size)
3003                                }
3004                            },
3005                        }
3006                    }
3007                };
3008
3009                (Some(native_surface.native_surface_id), update_params)
3010            }
3011        };
3012
3013        let descriptor = ExternalSurfaceDescriptor {
3014            local_surface_size: local_prim_rect.size(),
3015            local_rect: prim_rect,
3016            local_clip_rect: prim_info.prim_clip_box,
3017            dependency,
3018            image_rendering,
3019            clip_rect,
3020            transform_index: compositor_transform_index,
3021            z_id: ZBufferId::invalid(),
3022            native_surface_id,
3023            update_params,
3024            external_image_id,
3025        };
3026
3027        // If the surface is opaque, we can draw it an an underlay (which avoids
3028        // additional sub-slice surfaces, and supports clip masks)
3029        match surface_kind {
3030            CompositorSurfaceKind::Underlay => {
3031                self.underlays.push(descriptor);
3032            }
3033            CompositorSurfaceKind::Overlay => {
3034                // For compositor surfaces, if we didn't find an earlier sub-slice to add to,
3035                // we know we can append to the current slice.
3036                assert!(sub_slice_index < self.sub_slices.len() - 1);
3037                let sub_slice = &mut self.sub_slices[sub_slice_index];
3038
3039                // Each compositor surface allocates a unique z-id
3040                sub_slice.compositor_surfaces.push(CompositorSurface {
3041                    prohibited_rect: pic_coverage_rect,
3042                    is_opaque,
3043                    descriptor,
3044                });
3045
3046                // Add the pic_coverage_rect to the overlay region. This prevents
3047                // future promoted surfaces from becoming underlays if they would
3048                // intersect with the overlay region.
3049                self.overlay_region = self.overlay_region.union(&pic_coverage_rect);
3050            }
3051            CompositorSurfaceKind::Blit => unreachable!(),
3052        }
3053
3054        Ok(surface_kind)
3055    }
3056
3057    /// Push an estimated rect for an off-screen surface during dependency updates. This is
3058    /// a workaround / hack that allows the picture cache code to know when it should be
3059    /// processing primitive dependencies as a single atomic unit. In future, we aim to remove
3060    /// this hack by having the primitive dependencies stored _within_ each owning picture.
3061    /// This is part of the work required to support child picture caching anyway!
3062    pub fn push_surface(
3063        &mut self,
3064        estimated_local_rect: LayoutRect,
3065        surface_spatial_node_index: SpatialNodeIndex,
3066        spatial_tree: &SpatialTree,
3067    ) {
3068        // Only need to evaluate sub-slice regions if we have compositor surfaces present
3069        if self.current_surface_traversal_depth == 0 && self.sub_slices.len() > 1 {
3070            let map_local_to_picture = SpaceMapper::new_with_target(
3071                self.spatial_node_index,
3072                surface_spatial_node_index,
3073                self.local_rect,
3074                spatial_tree,
3075            );
3076
3077            if let Some(pic_rect) = map_local_to_picture.map(&estimated_local_rect) {
3078                // Find the first sub-slice we can add this primitive to (we want to add
3079                // prims to the primary surface if possible, so they get subpixel AA).
3080                for sub_slice in &mut self.sub_slices {
3081                    let mut intersects_prohibited_region = false;
3082
3083                    for surface in &mut sub_slice.compositor_surfaces {
3084                        if pic_rect.intersects(&surface.prohibited_rect) {
3085                            surface.prohibited_rect = surface.prohibited_rect.union(&pic_rect);
3086
3087                            intersects_prohibited_region = true;
3088                        }
3089                    }
3090
3091                    if !intersects_prohibited_region {
3092                        break;
3093                    }
3094                }
3095            }
3096        }
3097
3098        self.current_surface_traversal_depth += 1;
3099    }
3100
3101    /// Pop an off-screen surface off the stack during dependency updates
3102    pub fn pop_surface(&mut self) {
3103        self.current_surface_traversal_depth -= 1;
3104    }
3105
3106    fn maybe_report_promotion_failure(&self,
3107                                  result: Result<CompositorSurfaceKind, SurfacePromotionFailure>,
3108                                  rect: PictureRect,
3109                                  reported: &mut bool) {
3110        if !self.debug_flags.contains(DebugFlags::SURFACE_PROMOTION_LOGGING) || result.is_ok() || *reported {
3111            return;
3112        }
3113
3114        // Report this as a warning.
3115        // TODO: Find a way to expose this to web authors.
3116        warn!("Surface promotion of prim at {:?} failed with: {}.", rect, result.unwrap_err());
3117        *reported = true;
3118    }
3119
3120    /// Update the dependencies for each tile for a given primitive instance.
3121    pub fn update_prim_dependencies(
3122        &mut self,
3123        prim_instance: &mut PrimitiveInstance,
3124        prim_spatial_node_index: SpatialNodeIndex,
3125        local_prim_rect: LayoutRect,
3126        frame_context: &FrameVisibilityContext,
3127        data_stores: &DataStores,
3128        clip_store: &ClipStore,
3129        pictures: &[PicturePrimitive],
3130        resource_cache: &mut ResourceCache,
3131        color_bindings: &ColorBindingStorage,
3132        surface_stack: &[(PictureIndex, SurfaceIndex)],
3133        composite_state: &mut CompositeState,
3134        gpu_cache: &mut GpuCache,
3135        scratch: &mut PrimitiveScratchBuffer,
3136        is_root_tile_cache: bool,
3137        surfaces: &mut [SurfaceInfo],
3138        profile: &mut TransactionProfile,
3139    ) -> VisibilityState {
3140        use crate::picture::SurfacePromotionFailure::*;
3141
3142        // This primitive exists on the last element on the current surface stack.
3143        profile_scope!("update_prim_dependencies");
3144        let prim_surface_index = surface_stack.last().unwrap().1;
3145        let prim_clip_chain = &prim_instance.vis.clip_chain;
3146
3147        // If the primitive is directly drawn onto this picture cache surface, then
3148        // the pic_coverage_rect is in the same space. If not, we need to map it from
3149        // the intermediate picture space into the picture cache space.
3150        let on_picture_surface = prim_surface_index == self.surface_index;
3151        let pic_coverage_rect = if on_picture_surface {
3152            prim_clip_chain.pic_coverage_rect
3153        } else {
3154            // We want to get the rect in the tile cache picture space that this primitive
3155            // occupies, in order to enable correct invalidation regions. Each surface
3156            // that exists in the chain between this primitive and the tile cache surface
3157            // may have an arbitrary inflation factor (for example, in the case of a series
3158            // of nested blur elements). To account for this, step through the current
3159            // surface stack, mapping the primitive rect into each picture space, including
3160            // the inflation factor from each intermediate surface.
3161            let mut current_pic_coverage_rect = prim_clip_chain.pic_coverage_rect;
3162            let mut current_spatial_node_index = surfaces[prim_surface_index.0]
3163                .surface_spatial_node_index;
3164
3165            for (pic_index, surface_index) in surface_stack.iter().rev() {
3166                let surface = &surfaces[surface_index.0];
3167                let pic = &pictures[pic_index.0];
3168
3169                let map_local_to_parent = SpaceMapper::new_with_target(
3170                    surface.surface_spatial_node_index,
3171                    current_spatial_node_index,
3172                    surface.unclipped_local_rect,
3173                    frame_context.spatial_tree,
3174                );
3175
3176                // Map the rect into the parent surface, and inflate if this surface requires
3177                // it. If the rect can't be mapping (e.g. due to an invalid transform) then
3178                // just bail out from the dependencies and cull this primitive.
3179                current_pic_coverage_rect = match map_local_to_parent.map(&current_pic_coverage_rect) {
3180                    Some(rect) => {
3181                        // TODO(gw): The casts here are a hack. We have some interface inconsistencies
3182                        //           between layout/picture rects which don't really work with the
3183                        //           current unit system, since sometimes the local rect of a picture
3184                        //           is a LayoutRect, and sometimes it's a PictureRect. Consider how
3185                        //           we can improve this?
3186                        pic.composite_mode.as_ref().unwrap().get_coverage(
3187                            surface,
3188                            Some(rect.cast_unit()),
3189                        ).cast_unit()
3190                    }
3191                    None => {
3192                        return VisibilityState::Culled;
3193                    }
3194                };
3195
3196                current_spatial_node_index = surface.surface_spatial_node_index;
3197            }
3198
3199            current_pic_coverage_rect
3200        };
3201
3202        // Get the tile coordinates in the picture space.
3203        let (p0, p1) = self.get_tile_coords_for_rect(&pic_coverage_rect);
3204
3205        // If the primitive is outside the tiling rects, it's known to not
3206        // be visible.
3207        if p0.x == p1.x || p0.y == p1.y {
3208            return VisibilityState::Culled;
3209        }
3210
3211        // Build the list of resources that this primitive has dependencies on.
3212        let mut prim_info = PrimitiveDependencyInfo::new(
3213            prim_instance.uid(),
3214            pic_coverage_rect,
3215        );
3216
3217        let mut sub_slice_index = self.sub_slices.len() - 1;
3218
3219        // Only need to evaluate sub-slice regions if we have compositor surfaces present
3220        if sub_slice_index > 0 {
3221            // Find the first sub-slice we can add this primitive to (we want to add
3222            // prims to the primary surface if possible, so they get subpixel AA).
3223            for (i, sub_slice) in self.sub_slices.iter_mut().enumerate() {
3224                let mut intersects_prohibited_region = false;
3225
3226                for surface in &mut sub_slice.compositor_surfaces {
3227                    if pic_coverage_rect.intersects(&surface.prohibited_rect) {
3228                        surface.prohibited_rect = surface.prohibited_rect.union(&pic_coverage_rect);
3229
3230                        intersects_prohibited_region = true;
3231                    }
3232                }
3233
3234                if !intersects_prohibited_region {
3235                    sub_slice_index = i;
3236                    break;
3237                }
3238            }
3239        }
3240
3241        // Include the prim spatial node, if differs relative to cache root.
3242        if prim_spatial_node_index != self.spatial_node_index {
3243            prim_info.spatial_nodes.push(prim_spatial_node_index);
3244        }
3245
3246        // If there was a clip chain, add any clip dependencies to the list for this tile.
3247        let clip_instances = &clip_store
3248            .clip_node_instances[prim_clip_chain.clips_range.to_range()];
3249        for clip_instance in clip_instances {
3250            let clip = &data_stores.clip[clip_instance.handle];
3251
3252            prim_info.clips.push(clip_instance.handle.uid());
3253
3254            // If the clip has the same spatial node, the relative transform
3255            // will always be the same, so there's no need to depend on it.
3256            if clip.item.spatial_node_index != self.spatial_node_index
3257                && !prim_info.spatial_nodes.contains(&clip.item.spatial_node_index) {
3258                prim_info.spatial_nodes.push(clip.item.spatial_node_index);
3259            }
3260        }
3261
3262        // Certain primitives may select themselves to be a backdrop candidate, which is
3263        // then applied below.
3264        let mut backdrop_candidate = None;
3265
3266        // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
3267        // use it to calculate the local bounding rect for the tiles. If we include them
3268        // then we may calculate a bounding rect that is too large, since it won't include
3269        // the clip bounds of the picture. Excluding them from the bounding rect here
3270        // fixes any correctness issues (the clips themselves are considered when we
3271        // consider the bounds of the primitives that are *children* of the picture),
3272        // however it does potentially result in some un-necessary invalidations of a
3273        // tile (in cases where the picture local rect affects the tile, but the clip
3274        // rect eventually means it doesn't affect that tile).
3275        // TODO(gw): Get picture clips earlier (during the initial picture traversal
3276        //           pass) so that we can calculate these correctly.
3277        let mut promotion_result: Result<CompositorSurfaceKind, SurfacePromotionFailure> = Ok(CompositorSurfaceKind::Blit);
3278        let mut promotion_failure_reported = false;
3279        match prim_instance.kind {
3280            PrimitiveInstanceKind::Picture { pic_index,.. } => {
3281                // Pictures can depend on animated opacity bindings.
3282                let pic = &pictures[pic_index.0];
3283                if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.composite_mode {
3284                    prim_info.opacity_bindings.push(binding.into());
3285                }
3286            }
3287            PrimitiveInstanceKind::Rectangle { data_handle, color_binding_index, .. } => {
3288                // Rectangles can only form a backdrop candidate if they are known opaque.
3289                // TODO(gw): We could resolve the opacity binding here, but the common
3290                //           case for background rects is that they don't have animated opacity.
3291                let color = match data_stores.prim[data_handle].kind {
3292                    PrimitiveTemplateKind::Rectangle { color, .. } => {
3293                        frame_context.scene_properties.resolve_color(&color)
3294                    }
3295                    _ => unreachable!(),
3296                };
3297                if color.a >= 1.0 {
3298                    backdrop_candidate = Some(BackdropInfo {
3299                        opaque_rect: pic_coverage_rect,
3300                        spanning_opaque_color: None,
3301                        kind: Some(BackdropKind::Color { color }),
3302                        backdrop_rect: pic_coverage_rect,
3303                    });
3304                }
3305
3306                if color_binding_index != ColorBindingIndex::INVALID {
3307                    prim_info.color_binding = Some(color_bindings[color_binding_index].into());
3308                }
3309            }
3310            PrimitiveInstanceKind::Image { data_handle, ref mut compositor_surface_kind, .. } => {
3311                let image_key = &data_stores.image[data_handle];
3312                let image_data = &image_key.kind;
3313
3314                // For now, assume that for compositor surface purposes, any RGBA image may be
3315                // translucent. See the comment in `add_prim` in this source file for more
3316                // details. We'll leave the `is_opaque` code branches here, but disabled, as
3317                // in future we will want to support this case correctly.
3318                let mut is_opaque = false;
3319
3320                if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
3321                    // For an image to be a possible opaque backdrop, it must:
3322                    // - Have a valid, opaque image descriptor
3323                    // - Not use tiling (since they can fail to draw)
3324                    // - Not having any spacing / padding
3325                    // - Have opaque alpha in the instance (flattened) color
3326                    if image_properties.descriptor.is_opaque() &&
3327                       image_properties.tiling.is_none() &&
3328                       image_data.tile_spacing == LayoutSize::zero() &&
3329                       image_data.color.a >= 1.0 {
3330                        backdrop_candidate = Some(BackdropInfo {
3331                            opaque_rect: pic_coverage_rect,
3332                            spanning_opaque_color: None,
3333                            kind: None,
3334                            backdrop_rect: PictureRect::zero(),
3335                        });
3336                    }
3337
3338                    is_opaque = image_properties.descriptor.is_opaque();
3339                }
3340
3341                if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
3342                    // Only consider promoting Images if all of our YuvImages have been
3343                    // processed (whether they were promoted or not).
3344                    if self.yuv_images_remaining > 0 {
3345                        promotion_result = Err(ImageWaitingOnYuvImage);
3346                    } else {
3347                        promotion_result = self.can_promote_to_surface(prim_clip_chain,
3348                                                          prim_spatial_node_index,
3349                                                          is_root_tile_cache,
3350                                                          sub_slice_index,
3351                                                          CompositorSurfaceKind::Overlay,
3352                                                          pic_coverage_rect,
3353                                                          frame_context);
3354                    }
3355
3356                    // Native OS compositors (DC and CA, at least) support premultiplied alpha
3357                    // only. If we have an image that's not pre-multiplied alpha, we can't promote it.
3358                    if image_data.alpha_type == AlphaType::Alpha {
3359                        promotion_result = Err(NotPremultipliedAlpha);
3360                    }
3361
3362                    if let Ok(kind) = promotion_result {
3363                        promotion_result = self.setup_compositor_surfaces_rgb(
3364                            sub_slice_index,
3365                            &mut prim_info,
3366                            image_key.common.flags,
3367                            local_prim_rect,
3368                            prim_spatial_node_index,
3369                            pic_coverage_rect,
3370                            frame_context,
3371                            ImageDependency {
3372                                key: image_data.key,
3373                                generation: resource_cache.get_image_generation(image_data.key),
3374                            },
3375                            image_data.key,
3376                            resource_cache,
3377                            composite_state,
3378                            gpu_cache,
3379                            image_data.image_rendering,
3380                            is_opaque,
3381                            kind,
3382                        );
3383                    }
3384                }
3385
3386                if let Ok(kind) = promotion_result {
3387                    *compositor_surface_kind = kind;
3388
3389                    if kind == CompositorSurfaceKind::Overlay {
3390                        profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS);
3391                        return VisibilityState::Culled;
3392                    }
3393
3394                    assert!(kind == CompositorSurfaceKind::Blit, "Image prims should either be overlays or blits.");
3395                } else {
3396                    // In Err case, we handle as a blit, and proceed.
3397                    *compositor_surface_kind = CompositorSurfaceKind::Blit;
3398                }
3399
3400                if image_key.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
3401                    profile.inc(profiler::COMPOSITOR_SURFACE_BLITS);
3402                }
3403
3404                prim_info.images.push(ImageDependency {
3405                    key: image_data.key,
3406                    generation: resource_cache.get_image_generation(image_data.key),
3407                });
3408            }
3409            PrimitiveInstanceKind::YuvImage { data_handle, ref mut compositor_surface_kind, .. } => {
3410                let prim_data = &data_stores.yuv_image[data_handle];
3411
3412                if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
3413                    // Note if this is one of the YuvImages we were considering for
3414                    // surface promotion. We only care for primitives that were added
3415                    // to us, indicated by is_root_tile_cache. Those are the only ones
3416                    // that were added to the TileCacheParams that configured the
3417                    // current scene.
3418                    if is_root_tile_cache {
3419                        self.yuv_images_remaining -= 1;
3420                    }
3421
3422                    let clip_on_top = prim_clip_chain.needs_mask;
3423                    let prefer_underlay = clip_on_top || !cfg!(target_os = "macos");
3424                    let promotion_attempts = if prefer_underlay {
3425                        [CompositorSurfaceKind::Underlay, CompositorSurfaceKind::Overlay]
3426                    } else {
3427                        [CompositorSurfaceKind::Overlay, CompositorSurfaceKind::Underlay]
3428                    };
3429
3430                    for kind in promotion_attempts {
3431                        // Since this might be an attempt after an earlier error, clear the flag
3432                        // so that we are allowed to report another error.
3433                        promotion_failure_reported = false;
3434                        promotion_result = self.can_promote_to_surface(
3435                                                    prim_clip_chain,
3436                                                    prim_spatial_node_index,
3437                                                    is_root_tile_cache,
3438                                                    sub_slice_index,
3439                                                    kind,
3440                                                    pic_coverage_rect,
3441                                                    frame_context);
3442                        if promotion_result.is_ok() {
3443                            break;
3444                        }
3445
3446                        // We couldn't promote, but did we give up because the slice is marked
3447                        // atomic? If that was the reason, and the YuvImage is wide color,
3448                        // failing to promote will flatten the colors and look terrible. Let's
3449                        // ignore the atomic slice restriction in such a case.
3450                        if let Err(SliceAtomic) = promotion_result {
3451                            if prim_data.kind. color_depth != ColorDepth::Color8 {
3452                                // Let's promote with the attempted kind.
3453                                promotion_result = Ok(kind);
3454                                break;
3455                            }
3456                        }
3457
3458                        self.maybe_report_promotion_failure(promotion_result, pic_coverage_rect, &mut promotion_failure_reported);
3459                    }
3460
3461                    // TODO(gw): When we support RGBA images for external surfaces, we also
3462                    //           need to check if opaque (YUV images are implicitly opaque).
3463
3464                    // If this primitive is being promoted to a surface, construct an external
3465                    // surface descriptor for use later during batching and compositing. We only
3466                    // add the image keys for this primitive as a dependency if this is _not_
3467                    // a promoted surface, since we don't want the tiles to invalidate when the
3468                    // video content changes, if it's a compositor surface!
3469                    if let Ok(kind) = promotion_result {
3470                        // Build dependency for each YUV plane, with current image generation for
3471                        // later detection of when the composited surface has changed.
3472                        let mut image_dependencies = [ImageDependency::INVALID; 3];
3473                        for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
3474                            *dep = ImageDependency {
3475                                key,
3476                                generation: resource_cache.get_image_generation(key),
3477                            }
3478                        }
3479
3480                        promotion_result = self.setup_compositor_surfaces_yuv(
3481                            sub_slice_index,
3482                            &mut prim_info,
3483                            prim_data.common.flags,
3484                            local_prim_rect,
3485                            prim_spatial_node_index,
3486                            pic_coverage_rect,
3487                            frame_context,
3488                            &image_dependencies,
3489                            &prim_data.kind.yuv_key,
3490                            resource_cache,
3491                            composite_state,
3492                            gpu_cache,
3493                            prim_data.kind.image_rendering,
3494                            prim_data.kind.color_depth,
3495                            prim_data.kind.color_space.with_range(prim_data.kind.color_range),
3496                            prim_data.kind.format,
3497                            kind,
3498                        );
3499                    }
3500                }
3501
3502                // Store on the YUV primitive instance whether this is a promoted surface.
3503                // This is used by the batching code to determine whether to draw the
3504                // image to the content tiles, or just a transparent z-write.
3505                if let Ok(kind) = promotion_result {
3506                    *compositor_surface_kind = kind;
3507                    if kind == CompositorSurfaceKind::Overlay {
3508                        profile.inc(profiler::COMPOSITOR_SURFACE_OVERLAYS);
3509                        return VisibilityState::Culled;
3510                    }
3511
3512                    profile.inc(profiler::COMPOSITOR_SURFACE_UNDERLAYS);
3513                } else {
3514                    // In Err case, we handle as a blit, and proceed.
3515                    *compositor_surface_kind = CompositorSurfaceKind::Blit;
3516                    if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
3517                        profile.inc(profiler::COMPOSITOR_SURFACE_BLITS);
3518                    }
3519                }
3520
3521                if *compositor_surface_kind == CompositorSurfaceKind::Blit {
3522                    prim_info.images.extend(
3523                        prim_data.kind.yuv_key.iter().map(|key| {
3524                            ImageDependency {
3525                                key: *key,
3526                                generation: resource_cache.get_image_generation(*key),
3527                            }
3528                        })
3529                    );
3530                }
3531            }
3532            PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
3533                let border_data = &data_stores.image_border[data_handle].kind;
3534                prim_info.images.push(ImageDependency {
3535                    key: border_data.request.key,
3536                    generation: resource_cache.get_image_generation(border_data.request.key),
3537                });
3538            }
3539            PrimitiveInstanceKind::Clear { .. } => {
3540                backdrop_candidate = Some(BackdropInfo {
3541                    opaque_rect: pic_coverage_rect,
3542                    spanning_opaque_color: None,
3543                    kind: Some(BackdropKind::Clear),
3544                    backdrop_rect: pic_coverage_rect,
3545                });
3546            }
3547            PrimitiveInstanceKind::LinearGradient { data_handle, .. }
3548            | PrimitiveInstanceKind::CachedLinearGradient { data_handle, .. } => {
3549                let gradient_data = &data_stores.linear_grad[data_handle];
3550                if gradient_data.stops_opacity.is_opaque
3551                    && gradient_data.tile_spacing == LayoutSize::zero()
3552                {
3553                    backdrop_candidate = Some(BackdropInfo {
3554                        opaque_rect: pic_coverage_rect,
3555                        spanning_opaque_color: None,
3556                        kind: None,
3557                        backdrop_rect: PictureRect::zero(),
3558                    });
3559                }
3560            }
3561            PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
3562                let gradient_data = &data_stores.conic_grad[data_handle];
3563                if gradient_data.stops_opacity.is_opaque
3564                    && gradient_data.tile_spacing == LayoutSize::zero()
3565                {
3566                    backdrop_candidate = Some(BackdropInfo {
3567                        opaque_rect: pic_coverage_rect,
3568                        spanning_opaque_color: None,
3569                        kind: None,
3570                        backdrop_rect: PictureRect::zero(),
3571                    });
3572                }
3573            }
3574            PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
3575                let gradient_data = &data_stores.radial_grad[data_handle];
3576                if gradient_data.stops_opacity.is_opaque
3577                    && gradient_data.tile_spacing == LayoutSize::zero()
3578                {
3579                    backdrop_candidate = Some(BackdropInfo {
3580                        opaque_rect: pic_coverage_rect,
3581                        spanning_opaque_color: None,
3582                        kind: None,
3583                        backdrop_rect: PictureRect::zero(),
3584                    });
3585                }
3586            }
3587            PrimitiveInstanceKind::BackdropCapture { .. } => {}
3588            PrimitiveInstanceKind::BackdropRender { pic_index, .. } => {
3589                // If the area that the backdrop covers in the space of the surface it draws on
3590                // is empty, skip any sub-graph processing. This is not just a performance win,
3591                // it also ensures that we don't do a deferred dirty test that invalidates a tile
3592                // even if the tile isn't actually dirty, which can cause panics later in the
3593                // WR pipeline.
3594                if !pic_coverage_rect.is_empty() {
3595                    // Mark that we need the sub-graph this render depends on so that
3596                    // we don't skip it during the prepare pass
3597                    scratch.required_sub_graphs.insert(pic_index);
3598
3599                    // If this is a sub-graph, register the bounds on any affected tiles
3600                    // so we know how much to expand the content tile by.
3601                    let sub_slice = &mut self.sub_slices[sub_slice_index];
3602
3603                    let mut surface_info = Vec::new();
3604                    for (pic_index, surface_index) in surface_stack.iter().rev() {
3605                        let pic = &pictures[pic_index.0];
3606                        surface_info.push((pic.composite_mode.as_ref().unwrap().clone(), *surface_index));
3607                    }
3608
3609                    for y in p0.y .. p1.y {
3610                        for x in p0.x .. p1.x {
3611                            let key = TileOffset::new(x, y);
3612                            let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
3613                            tile.sub_graphs.push((pic_coverage_rect, surface_info.clone()));
3614                        }
3615                    }
3616
3617                    // For backdrop-filter, we need to check if any of the dirty rects
3618                    // in tiles that are affected by the filter primitive are dirty.
3619                    self.deferred_dirty_tests.push(DeferredDirtyTest {
3620                        tile_rect: TileRect::new(p0, p1),
3621                        prim_rect: pic_coverage_rect,
3622                    });
3623                }
3624            }
3625            PrimitiveInstanceKind::LineDecoration { .. } |
3626            PrimitiveInstanceKind::NormalBorder { .. } |
3627            PrimitiveInstanceKind::BoxShadow { .. } |
3628            PrimitiveInstanceKind::TextRun { .. } => {
3629                // These don't contribute dependencies
3630            }
3631        };
3632
3633        self.maybe_report_promotion_failure(promotion_result, pic_coverage_rect, &mut promotion_failure_reported);
3634
3635        // Calculate the screen rect in local space. When we calculate backdrops, we
3636        // care only that they cover the visible rect (based off the local clip), and
3637        // don't have any overlapping prims in the visible rect.
3638        let visible_local_clip_rect = self.local_clip_rect.intersection(&self.screen_rect_in_pic_space).unwrap_or_default();
3639        if pic_coverage_rect.intersects(&visible_local_clip_rect) {
3640            self.found_prims_after_backdrop = true;
3641        }
3642
3643        // If this primitive considers itself a backdrop candidate, apply further
3644        // checks to see if it matches all conditions to be a backdrop.
3645        let mut vis_flags = PrimitiveVisibilityFlags::empty();
3646        let sub_slice = &mut self.sub_slices[sub_slice_index];
3647        if let Some(mut backdrop_candidate) = backdrop_candidate {
3648            // Update whether the surface that this primitive exists on
3649            // can be considered opaque. Any backdrop kind other than
3650            // a clear primitive (e.g. color, gradient, image) can be
3651            // considered.
3652            match backdrop_candidate.kind {
3653                Some(BackdropKind::Color { .. }) | None => {
3654                    let surface = &mut surfaces[prim_surface_index.0];
3655
3656                    let is_same_coord_system = frame_context.spatial_tree.is_matching_coord_system(
3657                        prim_spatial_node_index,
3658                        surface.surface_spatial_node_index,
3659                    );
3660
3661                    // To be an opaque backdrop, it must:
3662                    // - Be the same coordinate system (axis-aligned)
3663                    // - Have no clip mask
3664                    // - Have a rect that covers the surface local rect
3665                    if is_same_coord_system &&
3666                       !prim_clip_chain.needs_mask &&
3667                       prim_clip_chain.pic_coverage_rect.contains_box(&surface.unclipped_local_rect)
3668                    {
3669                        // Note that we use `prim_clip_chain.pic_clip_rect` here rather
3670                        // than `backdrop_candidate.opaque_rect`. The former is in the
3671                        // local space of the surface, the latter is in the local space
3672                        // of the top level tile-cache.
3673                        surface.is_opaque = true;
3674                    }
3675                }
3676                Some(BackdropKind::Clear) => {}
3677            }
3678
3679            let is_suitable_backdrop = match backdrop_candidate.kind {
3680                Some(BackdropKind::Clear) => {
3681                    // Clear prims are special - they always end up in their own slice,
3682                    // and always set the backdrop. In future, we hope to completely
3683                    // remove clear prims, since they don't integrate with the compositing
3684                    // system cleanly.
3685                    true
3686                }
3687                Some(BackdropKind::Color { .. }) | None => {
3688                    // Check a number of conditions to see if we can consider this
3689                    // primitive as an opaque backdrop rect. Several of these are conservative
3690                    // checks and could be relaxed in future. However, these checks
3691                    // are quick and capture the common cases of background rects and images.
3692                    // Specifically, we currently require:
3693                    //  - The primitive is on the main picture cache surface.
3694                    //  - Same coord system as picture cache (ensures rects are axis-aligned).
3695                    //  - No clip masks exist.
3696                    let same_coord_system = frame_context.spatial_tree.is_matching_coord_system(
3697                        prim_spatial_node_index,
3698                        self.spatial_node_index,
3699                    );
3700
3701                    same_coord_system && on_picture_surface
3702                }
3703            };
3704
3705            if sub_slice_index == 0 &&
3706               is_suitable_backdrop &&
3707               sub_slice.compositor_surfaces.is_empty() {
3708
3709                // If the backdrop candidate has a clip-mask, try to extract an opaque inner
3710                // rect that is safe to use for subpixel rendering
3711                if prim_clip_chain.needs_mask {
3712                    backdrop_candidate.opaque_rect = clip_store
3713                        .get_inner_rect_for_clip_chain(
3714                            prim_clip_chain,
3715                            &data_stores.clip,
3716                            frame_context.spatial_tree,
3717                        )
3718                        .unwrap_or(PictureRect::zero());
3719                }
3720
3721                // We set the backdrop opaque_rect here, indicating the coverage area, which
3722                // is useful for calculate_subpixel_mode. We will only set the backdrop kind
3723                // if it covers the visible rect.
3724                if backdrop_candidate.opaque_rect.contains_box(&self.backdrop.opaque_rect) {
3725                    self.backdrop.opaque_rect = backdrop_candidate.opaque_rect;
3726                }
3727
3728                if let Some(kind) = backdrop_candidate.kind {
3729                    if backdrop_candidate.opaque_rect.contains_box(&visible_local_clip_rect) {
3730                        self.found_prims_after_backdrop = false;
3731                        self.backdrop.kind = Some(kind);
3732                        self.backdrop.backdrop_rect = backdrop_candidate.opaque_rect;
3733
3734                        // If we have a color backdrop that spans the entire local rect, mark
3735                        // the visibility flags of the primitive so it is skipped during batching
3736                        // (and also clears any previous primitives). Additionally, update our
3737                        // background color to match the backdrop color, which will ensure that
3738                        // our tiles are cleared to this color.
3739                        if let BackdropKind::Color { color } = kind {
3740                            if backdrop_candidate.opaque_rect.contains_box(&self.local_rect) {
3741                                vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP;
3742                                self.backdrop.spanning_opaque_color = Some(color);
3743                            }
3744                        }
3745                    }
3746                }
3747            }
3748        }
3749
3750        // Record any new spatial nodes in the used list.
3751        for spatial_node_index in &prim_info.spatial_nodes {
3752            self.spatial_node_comparer.register_used_transform(
3753                *spatial_node_index,
3754                self.frame_id,
3755                frame_context.spatial_tree,
3756            );
3757        }
3758
3759        // Normalize the tile coordinates before adding to tile dependencies.
3760        // For each affected tile, mark any of the primitive dependencies.
3761        for y in p0.y .. p1.y {
3762            for x in p0.x .. p1.x {
3763                // TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile?
3764                let key = TileOffset::new(x, y);
3765                let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
3766
3767                tile.add_prim_dependency(&prim_info);
3768            }
3769        }
3770
3771        VisibilityState::Visible {
3772            vis_flags,
3773            sub_slice_index: SubSliceIndex::new(sub_slice_index),
3774        }
3775    }
3776
3777    /// Print debug information about this picture cache to a tree printer.
3778    fn print(&self) {
3779        // TODO(gw): This initial implementation is very basic - just printing
3780        //           the picture cache state to stdout. In future, we can
3781        //           make this dump each frame to a file, and produce a report
3782        //           stating which frames had invalidations. This will allow
3783        //           diff'ing the invalidation states in a visual tool.
3784        let mut pt = PrintTree::new("Picture Cache");
3785
3786        pt.new_level(format!("Slice {:?}", self.slice));
3787
3788        pt.add_item(format!("background_color: {:?}", self.background_color));
3789
3790        for (sub_slice_index, sub_slice) in self.sub_slices.iter().enumerate() {
3791            pt.new_level(format!("SubSlice {:?}", sub_slice_index));
3792
3793            for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y {
3794                for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x {
3795                    let key = TileOffset::new(x, y);
3796                    let tile = &sub_slice.tiles[&key];
3797                    tile.print(&mut pt);
3798                }
3799            }
3800
3801            pt.end_level();
3802        }
3803
3804        pt.end_level();
3805    }
3806
3807    fn calculate_subpixel_mode(&self) -> SubpixelMode {
3808        // We can only consider the full opaque cases if there's no underlays
3809        if self.underlays.is_empty() {
3810            let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
3811
3812            // If the overall tile cache is known opaque, subpixel AA is allowed everywhere
3813            if has_opaque_bg_color {
3814                return SubpixelMode::Allow;
3815            }
3816
3817            // If the opaque backdrop rect covers the entire tile cache surface,
3818            // we can allow subpixel AA anywhere, skipping the per-text-run tests
3819            // later on during primitive preparation.
3820            if self.backdrop.opaque_rect.contains_box(&self.local_rect) {
3821                return SubpixelMode::Allow;
3822            }
3823        }
3824
3825        // If we didn't find any valid opaque backdrop, no subpixel AA allowed
3826        if self.backdrop.opaque_rect.is_empty() {
3827            return SubpixelMode::Deny;
3828        }
3829
3830        // Calculate a prohibited rect where we won't allow subpixel AA.
3831        // TODO(gw): This is conservative - it will disallow subpixel AA if there
3832        // are two underlay surfaces with text placed in between them. That's
3833        // probably unlikely to be an issue in practice, but maybe we should support
3834        // an array of prohibted rects?
3835        let prohibited_rect = self
3836            .underlays
3837            .iter()
3838            .fold(
3839                PictureRect::zero(),
3840                |acc, underlay| {
3841                    acc.union(&underlay.local_rect)
3842                }
3843            );
3844
3845        // If none of the simple cases above match, we need test where we can support subpixel AA.
3846        // TODO(gw): In future, it may make sense to have > 1 inclusion rect,
3847        //           but this handles the common cases.
3848        // TODO(gw): If a text run gets animated such that it's moving in a way that is
3849        //           sometimes intersecting with the video rect, this can result in subpixel
3850        //           AA flicking on/off for that text run. It's probably very rare, but
3851        //           something we should handle in future.
3852        SubpixelMode::Conditional {
3853            allowed_rect: self.backdrop.opaque_rect,
3854            prohibited_rect,
3855        }
3856    }
3857
3858    /// Apply any updates after prim dependency updates. This applies
3859    /// any late tile invalidations, and sets up the dirty rect and
3860    /// set of tile blits.
3861    pub fn post_update(
3862        &mut self,
3863        frame_context: &FrameVisibilityContext,
3864        composite_state: &mut CompositeState,
3865        resource_cache: &mut ResourceCache,
3866    ) {
3867        assert!(self.current_surface_traversal_depth == 0);
3868
3869        // TODO: Switch from the root node ot raster space.
3870        let visibility_node = frame_context.spatial_tree.root_reference_frame_index();
3871
3872        self.dirty_region.reset(visibility_node, self.spatial_node_index);
3873        self.subpixel_mode = self.calculate_subpixel_mode();
3874
3875        self.transform_index = composite_state.register_transform(
3876            self.local_to_raster,
3877            // TODO(gw): Once we support scaling of picture cache tiles during compositing,
3878            //           that transform gets plugged in here!
3879            self.raster_to_device,
3880        );
3881
3882        let map_pic_to_world = SpaceMapper::new_with_target(
3883            frame_context.root_spatial_node_index,
3884            self.spatial_node_index,
3885            frame_context.global_screen_world_rect,
3886            frame_context.spatial_tree,
3887        );
3888
3889        // A simple GC of the native external surface cache, to remove and free any
3890        // surfaces that were not referenced during the update_prim_dependencies pass.
3891        self.external_native_surface_cache.retain(|_, surface| {
3892            if !surface.used_this_frame {
3893                // If we removed an external surface, we need to mark the dirty rects as
3894                // invalid so a full composite occurs on the next frame.
3895                composite_state.dirty_rects_are_valid = false;
3896
3897                resource_cache.destroy_compositor_surface(surface.native_surface_id);
3898            }
3899
3900            surface.used_this_frame
3901        });
3902
3903        let pic_to_world_mapper = SpaceMapper::new_with_target(
3904            frame_context.root_spatial_node_index,
3905            self.spatial_node_index,
3906            frame_context.global_screen_world_rect,
3907            frame_context.spatial_tree,
3908        );
3909
3910        let ctx = TileUpdateDirtyContext {
3911            pic_to_world_mapper,
3912            global_device_pixel_scale: frame_context.global_device_pixel_scale,
3913            opacity_bindings: &self.opacity_bindings,
3914            color_bindings: &self.color_bindings,
3915            local_rect: self.local_rect,
3916            invalidate_all: self.invalidate_all_tiles,
3917        };
3918
3919        let mut state = TileUpdateDirtyState {
3920            resource_cache,
3921            composite_state,
3922            compare_cache: &mut self.compare_cache,
3923            spatial_node_comparer: &mut self.spatial_node_comparer,
3924        };
3925
3926        // Step through each tile and invalidate if the dependencies have changed. Determine
3927        // the current opacity setting and whether it's changed.
3928        for sub_slice in &mut self.sub_slices {
3929            for tile in sub_slice.tiles.values_mut() {
3930                tile.update_dirty_and_valid_rects(&ctx, &mut state, frame_context);
3931            }
3932        }
3933
3934        // Process any deferred dirty checks
3935        for sub_slice in &mut self.sub_slices {
3936            for dirty_test in self.deferred_dirty_tests.drain(..) {
3937                // Calculate the total dirty rect from all tiles that this primitive affects
3938                let mut total_dirty_rect = PictureRect::zero();
3939
3940                for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
3941                    for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
3942                        let key = TileOffset::new(x, y);
3943                        let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
3944                        total_dirty_rect = total_dirty_rect.union(&tile.local_dirty_rect);
3945                    }
3946                }
3947
3948                // If that dirty rect intersects with the local rect of the primitive
3949                // being checked, invalidate that region in all of the affected tiles.
3950                // TODO(gw): This is somewhat conservative, we could be more clever
3951                //           here and avoid invalidating every tile when this changes.
3952                //           We could also store the dirty rect only when the prim
3953                //           is encountered, so that we don't invalidate if something
3954                //           *after* the query in the rendering order affects invalidation.
3955                if total_dirty_rect.intersects(&dirty_test.prim_rect) {
3956                    for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
3957                        for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
3958                            let key = TileOffset::new(x, y);
3959                            let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
3960                            tile.invalidate(
3961                                Some(dirty_test.prim_rect),
3962                                InvalidationReason::SurfaceContentChanged,
3963                            );
3964                        }
3965                    }
3966                }
3967            }
3968        }
3969
3970        let mut ctx = TilePostUpdateContext {
3971            local_clip_rect: self.local_clip_rect,
3972            backdrop: None,
3973            current_tile_size: self.current_tile_size,
3974            z_id: ZBufferId::invalid(),
3975            underlays: &self.underlays,
3976        };
3977
3978        let mut state = TilePostUpdateState {
3979            resource_cache,
3980            composite_state,
3981        };
3982
3983        for (i, sub_slice) in self.sub_slices.iter_mut().enumerate().rev() {
3984            // The backdrop is only relevant for the first sub-slice
3985            if i == 0 {
3986                ctx.backdrop = Some(self.backdrop);
3987            }
3988
3989            for compositor_surface in sub_slice.compositor_surfaces.iter_mut().rev() {
3990                compositor_surface.descriptor.z_id = state.composite_state.z_generator.next();
3991            }
3992
3993            ctx.z_id = state.composite_state.z_generator.next();
3994
3995            for tile in sub_slice.tiles.values_mut() {
3996                tile.post_update(&ctx, &mut state, frame_context);
3997            }
3998        }
3999
4000        // Assign z-order for each underlay
4001        for underlay in self.underlays.iter_mut().rev() {
4002            underlay.z_id = state.composite_state.z_generator.next();
4003        }
4004
4005        // Register any opaque external compositor surfaces as potential occluders. This
4006        // is especially useful when viewing video in full-screen mode, as it is
4007        // able to occlude every background tile (avoiding allocation, rasterizion
4008        // and compositing).
4009
4010        // Register any underlays as occluders where possible
4011        for underlay in &self.underlays {
4012            if let Some(world_surface_rect) = underlay.get_occluder_rect(
4013                &self.local_clip_rect,
4014                &map_pic_to_world,
4015            ) {
4016                composite_state.register_occluder(
4017                    underlay.z_id,
4018                    world_surface_rect,
4019                    self.compositor_clip,
4020                );
4021            }
4022        }
4023
4024        for sub_slice in &self.sub_slices {
4025            for compositor_surface in &sub_slice.compositor_surfaces {
4026                if compositor_surface.is_opaque {
4027                    if let Some(world_surface_rect) = compositor_surface.descriptor.get_occluder_rect(
4028                        &self.local_clip_rect,
4029                        &map_pic_to_world,
4030                    ) {
4031                        composite_state.register_occluder(
4032                            compositor_surface.descriptor.z_id,
4033                            world_surface_rect,
4034                            self.compositor_clip,
4035                        );
4036                    }
4037                }
4038            }
4039        }
4040
4041        // Register the opaque region of this tile cache as an occluder, which
4042        // is used later in the frame to occlude other tiles.
4043        if !self.backdrop.opaque_rect.is_empty() {
4044            let z_id_backdrop = composite_state.z_generator.next();
4045
4046            let backdrop_rect = self.backdrop.opaque_rect
4047                .intersection(&self.local_rect)
4048                .and_then(|r| {
4049                    r.intersection(&self.local_clip_rect)
4050                });
4051
4052            if let Some(backdrop_rect) = backdrop_rect {
4053                let world_backdrop_rect = map_pic_to_world
4054                    .map(&backdrop_rect)
4055                    .expect("bug: unable to map backdrop to world space");
4056
4057                // Since we register the entire backdrop rect, use the opaque z-id for the
4058                // picture cache slice.
4059                composite_state.register_occluder(
4060                    z_id_backdrop,
4061                    world_backdrop_rect,
4062                    self.compositor_clip,
4063                );
4064            }
4065        }
4066    }
4067}
4068
4069pub struct PictureScratchBuffer {
4070    surface_stack: Vec<SurfaceIndex>,
4071}
4072
4073impl Default for PictureScratchBuffer {
4074    fn default() -> Self {
4075        PictureScratchBuffer {
4076            surface_stack: Vec::new(),
4077        }
4078    }
4079}
4080
4081impl PictureScratchBuffer {
4082    pub fn begin_frame(&mut self) {
4083        self.surface_stack.clear();
4084    }
4085
4086    pub fn recycle(&mut self, recycler: &mut Recycler) {
4087        recycler.recycle_vec(&mut self.surface_stack);
4088    }
4089}
4090
4091#[derive(Debug, Copy, Clone, PartialEq)]
4092#[cfg_attr(feature = "capture", derive(Serialize))]
4093#[cfg_attr(feature = "replay", derive(Deserialize))]
4094pub struct SurfaceIndex(pub usize);
4095
4096/// Information about an offscreen surface. For now,
4097/// it contains information about the size and coordinate
4098/// system of the surface. In the future, it will contain
4099/// information about the contents of the surface, which
4100/// will allow surfaces to be cached / retained between
4101/// frames and display lists.
4102pub struct SurfaceInfo {
4103    /// A local rect defining the size of this surface, in the
4104    /// coordinate system of the parent surface. This contains
4105    /// the unclipped bounding rect of child primitives.
4106    pub unclipped_local_rect: PictureRect,
4107    /// The local space coverage of child primitives after they are
4108    /// are clipped to their owning clip-chain.
4109    pub clipped_local_rect: PictureRect,
4110    /// The (conservative) valid part of this surface rect. Used
4111    /// to reduce the size of render target allocation.
4112    pub clipping_rect: PictureRect,
4113    /// The rectangle to use for culling and clipping.
4114    pub culling_rect: VisRect,
4115    /// Helper structs for mapping local rects in different
4116    /// coordinate systems into the picture coordinates.
4117    pub map_local_to_picture: SpaceMapper<LayoutPixel, PicturePixel>,
4118    /// The positioning node for the surface itself,
4119    pub surface_spatial_node_index: SpatialNodeIndex,
4120    /// The rasterization root for this surface.
4121    pub raster_spatial_node_index: SpatialNodeIndex,
4122    /// The spatial node for culling and clipping (anything using VisPixel).
4123    /// TODO: Replace with the raster spatial node.
4124    pub visibility_spatial_node_index: SpatialNodeIndex,
4125    /// The device pixel ratio specific to this surface.
4126    pub device_pixel_scale: DevicePixelScale,
4127    /// The scale factors of the surface to world transform.
4128    pub world_scale_factors: (f32, f32),
4129    /// Local scale factors surface to raster transform
4130    pub local_scale: (f32, f32),
4131    /// If true, we know this surface is completely opaque.
4132    pub is_opaque: bool,
4133    /// If true, allow snapping on this and child surfaces
4134    pub allow_snapping: bool,
4135    /// If true, the scissor rect must be set when drawing this surface
4136    pub force_scissor_rect: bool,
4137}
4138
4139impl SurfaceInfo {
4140    pub fn new(
4141        surface_spatial_node_index: SpatialNodeIndex,
4142        raster_spatial_node_index: SpatialNodeIndex,
4143        world_rect: WorldRect,
4144        spatial_tree: &SpatialTree,
4145        device_pixel_scale: DevicePixelScale,
4146        world_scale_factors: (f32, f32),
4147        local_scale: (f32, f32),
4148        allow_snapping: bool,
4149        force_scissor_rect: bool,
4150    ) -> Self {
4151        let map_surface_to_world = SpaceMapper::new_with_target(
4152            spatial_tree.root_reference_frame_index(),
4153            surface_spatial_node_index,
4154            world_rect,
4155            spatial_tree,
4156        );
4157
4158        let pic_bounds = map_surface_to_world
4159            .unmap(&map_surface_to_world.bounds)
4160            .unwrap_or_else(PictureRect::max_rect);
4161
4162        let map_local_to_picture = SpaceMapper::new(
4163            surface_spatial_node_index,
4164            pic_bounds,
4165        );
4166
4167        // TODO: replace the root with raster space.
4168        let visibility_spatial_node_index = spatial_tree.root_reference_frame_index();
4169
4170        SurfaceInfo {
4171            unclipped_local_rect: PictureRect::zero(),
4172            clipped_local_rect: PictureRect::zero(),
4173            is_opaque: false,
4174            clipping_rect: PictureRect::zero(),
4175            map_local_to_picture,
4176            raster_spatial_node_index,
4177            surface_spatial_node_index,
4178            visibility_spatial_node_index,
4179            device_pixel_scale,
4180            world_scale_factors,
4181            local_scale,
4182            allow_snapping,
4183            force_scissor_rect,
4184            // TODO: At the moment all culling is done in world space but
4185            // but the plan is to move it to raster space.
4186            culling_rect: world_rect.cast_unit(),
4187        }
4188    }
4189
4190    /// Clamps the blur radius depending on scale factors.
4191    pub fn clamp_blur_radius(
4192        &self,
4193        x_blur_radius: f32,
4194        y_blur_radius: f32,
4195    ) -> (f32, f32) {
4196        // Clamping must occur after scale factors are applied, but scale factors are not applied
4197        // until later on. To clamp the blur radius, we first apply the scale factors and then clamp
4198        // and finally revert the scale factors.
4199
4200        let sx_blur_radius = x_blur_radius * self.local_scale.0;
4201        let sy_blur_radius = y_blur_radius * self.local_scale.1;
4202
4203        let largest_scaled_blur_radius = f32::max(
4204            sx_blur_radius * self.world_scale_factors.0,
4205            sy_blur_radius * self.world_scale_factors.1,
4206        );
4207
4208        if largest_scaled_blur_radius > MAX_BLUR_RADIUS {
4209            let sf = MAX_BLUR_RADIUS / largest_scaled_blur_radius;
4210            (x_blur_radius * sf, y_blur_radius * sf)
4211        } else {
4212            // Return the original blur radius to avoid any rounding errors
4213            (x_blur_radius, y_blur_radius)
4214        }
4215    }
4216
4217    pub fn map_to_device_rect(
4218        &self,
4219        picture_rect: &PictureRect,
4220        spatial_tree: &SpatialTree,
4221    ) -> DeviceRect {
4222        let raster_rect = if self.raster_spatial_node_index != self.surface_spatial_node_index {
4223            // Currently, the surface's spatial node can be different from its raster node only
4224            // for surfaces in the root coordinate system for snapping reasons.
4225            // See `PicturePrimitive::assign_surface`.
4226            assert_eq!(self.device_pixel_scale.0, 1.0);
4227            assert_eq!(self.raster_spatial_node_index, spatial_tree.root_reference_frame_index());
4228
4229            let pic_to_raster = SpaceMapper::new_with_target(
4230                self.raster_spatial_node_index,
4231                self.surface_spatial_node_index,
4232                WorldRect::max_rect(),
4233                spatial_tree,
4234            );
4235
4236            pic_to_raster.map(&picture_rect).unwrap()
4237        } else {
4238            picture_rect.cast_unit()
4239        };
4240
4241        raster_rect * self.device_pixel_scale
4242    }
4243
4244    /// Clip and transform a local rect to a device rect suitable for allocating
4245    /// a child off-screen surface of this surface (e.g. for clip-masks)
4246    pub fn get_surface_rect(
4247        &self,
4248        local_rect: &PictureRect,
4249        spatial_tree: &SpatialTree,
4250    ) -> Option<DeviceIntRect> {
4251        let local_rect = match local_rect.intersection(&self.clipping_rect) {
4252            Some(rect) => rect,
4253            None => return None,
4254        };
4255
4256        let raster_rect = if self.raster_spatial_node_index != self.surface_spatial_node_index {
4257            assert_eq!(self.device_pixel_scale.0, 1.0);
4258
4259            let local_to_world = SpaceMapper::new_with_target(
4260                spatial_tree.root_reference_frame_index(),
4261                self.surface_spatial_node_index,
4262                WorldRect::max_rect(),
4263                spatial_tree,
4264            );
4265
4266            local_to_world.map(&local_rect).unwrap()
4267        } else {
4268            // The content should have been culled out earlier.
4269            assert!(self.device_pixel_scale.0 > 0.0);
4270
4271            local_rect.cast_unit()
4272        };
4273
4274        let surface_rect = (raster_rect * self.device_pixel_scale).round_out().to_i32();
4275        if surface_rect.is_empty() {
4276            // The local_rect computed above may have non-empty size that is very
4277            // close to zero. Due to limited arithmetic precision, the SpaceMapper
4278            // might transform the near-zero-sized rect into a zero-sized one.
4279            return None;
4280        }
4281
4282        Some(surface_rect)
4283    }
4284}
4285
4286/// Information from `get_surface_rects` about the allocated size, UV sampling
4287/// parameters etc for an off-screen surface
4288#[derive(Debug)]
4289struct SurfaceAllocInfo {
4290    task_size: DeviceIntSize,
4291    needs_scissor_rect: bool,
4292    clipped: DeviceRect,
4293    unclipped: DeviceRect,
4294    // Only used for SVGFEGraph currently, this is the source pixels needed to
4295    // render the pixels in clipped.
4296    source: DeviceRect,
4297    // Only used for SVGFEGraph, this is the same as clipped before rounding.
4298    clipped_notsnapped: DeviceRect,
4299    clipped_local: PictureRect,
4300    uv_rect_kind: UvRectKind,
4301}
4302
4303#[derive(Debug)]
4304#[cfg_attr(feature = "capture", derive(Serialize))]
4305pub struct RasterConfig {
4306    /// How this picture should be composited into
4307    /// the parent surface.
4308    // TODO(gw): We should remove this and just use what is in PicturePrimitive
4309    pub composite_mode: PictureCompositeMode,
4310    /// Index to the surface descriptor for this
4311    /// picture.
4312    pub surface_index: SurfaceIndex,
4313}
4314
4315bitflags! {
4316    /// A set of flags describing why a picture may need a backing surface.
4317    #[cfg_attr(feature = "capture", derive(Serialize))]
4318    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
4319    pub struct BlitReason: u32 {
4320        /// Mix-blend-mode on a child that requires isolation.
4321        const ISOLATE = 1;
4322        /// Clip node that _might_ require a surface.
4323        const CLIP = 2;
4324        /// Preserve-3D requires a surface for plane-splitting.
4325        const PRESERVE3D = 4;
4326        /// A backdrop that is reused which requires a surface.
4327        const BACKDROP = 8;
4328        /// We may need to render the picture into an image and cache it.
4329        const SNAPSHOT = 16;
4330    }
4331}
4332
4333/// Specifies how this Picture should be composited
4334/// onto the target it belongs to.
4335#[allow(dead_code)]
4336#[derive(Debug, Clone)]
4337#[cfg_attr(feature = "capture", derive(Serialize))]
4338pub enum PictureCompositeMode {
4339    /// Apply CSS mix-blend-mode effect.
4340    MixBlend(MixBlendMode),
4341    /// Apply a CSS filter (except component transfer).
4342    Filter(Filter),
4343    /// Apply a component transfer filter.
4344    ComponentTransferFilter(FilterDataHandle),
4345    /// Draw to intermediate surface, copy straight across. This
4346    /// is used for CSS isolation, and plane splitting.
4347    Blit(BlitReason),
4348    /// Used to cache a picture as a series of tiles.
4349    TileCache {
4350        slice_id: SliceId,
4351    },
4352    /// Apply an SVG filter
4353    SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
4354    /// Apply an SVG filter graph
4355    SVGFEGraph(Vec<(FilterGraphNode, FilterGraphOp)>),
4356    /// A surface that is used as an input to another primitive
4357    IntermediateSurface,
4358}
4359
4360impl PictureCompositeMode {
4361    pub fn get_rect(
4362        &self,
4363        surface: &SurfaceInfo,
4364        sub_rect: Option<LayoutRect>,
4365    ) -> LayoutRect {
4366        let surface_rect = match sub_rect {
4367            Some(sub_rect) => sub_rect,
4368            None => surface.clipped_local_rect.cast_unit(),
4369        };
4370
4371        match self {
4372            PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate }) => {
4373                if *should_inflate {
4374                    let (width_factor, height_factor) = surface.clamp_blur_radius(*width, *height);
4375
4376                    surface_rect.inflate(
4377                        width_factor.ceil() * BLUR_SAMPLE_SCALE,
4378                        height_factor.ceil() * BLUR_SAMPLE_SCALE,
4379                    )
4380                } else {
4381                    surface_rect
4382                }
4383            }
4384            PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
4385                let mut max_blur_radius = 0.0;
4386                for shadow in shadows {
4387                    max_blur_radius = f32::max(max_blur_radius, shadow.blur_radius);
4388                }
4389
4390                let (max_blur_radius_x, max_blur_radius_y) = surface.clamp_blur_radius(
4391                    max_blur_radius,
4392                    max_blur_radius,
4393                );
4394                let blur_inflation_x = max_blur_radius_x * BLUR_SAMPLE_SCALE;
4395                let blur_inflation_y = max_blur_radius_y * BLUR_SAMPLE_SCALE;
4396
4397                surface_rect.inflate(blur_inflation_x, blur_inflation_y)
4398            }
4399            PictureCompositeMode::SvgFilter(primitives, _) => {
4400                let mut result_rect = surface_rect;
4401                let mut output_rects = Vec::with_capacity(primitives.len());
4402
4403                for (cur_index, primitive) in primitives.iter().enumerate() {
4404                    let output_rect = match primitive.kind {
4405                        FilterPrimitiveKind::Blur(ref primitive) => {
4406                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect);
4407                            let width_factor = primitive.width.round() * BLUR_SAMPLE_SCALE;
4408                            let height_factor = primitive.height.round() * BLUR_SAMPLE_SCALE;
4409                            input.inflate(width_factor, height_factor)
4410                        }
4411                        FilterPrimitiveKind::DropShadow(ref primitive) => {
4412                            let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
4413                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect);
4414                            let shadow_rect = input.inflate(inflation_factor, inflation_factor);
4415                            input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0)))
4416                        }
4417                        FilterPrimitiveKind::Blend(ref primitive) => {
4418                            primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)
4419                                .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect))
4420                        }
4421                        FilterPrimitiveKind::Composite(ref primitive) => {
4422                            primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)
4423                                .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect))
4424                        }
4425                        FilterPrimitiveKind::Identity(ref primitive) =>
4426                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4427                        FilterPrimitiveKind::Opacity(ref primitive) =>
4428                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4429                        FilterPrimitiveKind::ColorMatrix(ref primitive) =>
4430                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4431                        FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
4432                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4433                        FilterPrimitiveKind::Offset(ref primitive) => {
4434                            let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect);
4435                            input_rect.translate(primitive.offset * Scale::new(1.0))
4436                        },
4437
4438                        FilterPrimitiveKind::Flood(..) => surface_rect,
4439                    };
4440                    output_rects.push(output_rect);
4441                    result_rect = result_rect.union(&output_rect);
4442                }
4443                result_rect
4444            }
4445            PictureCompositeMode::SVGFEGraph(ref filters) => {
4446                // Return prim_subregion for use in get_local_prim_rect, which
4447                // is the polygon size.
4448                // This must match surface_rects.unclipped_local.
4449                self.get_coverage_target_svgfe(filters, surface_rect.cast_unit())
4450            }
4451            _ => {
4452                surface_rect
4453            }
4454        }
4455    }
4456
4457    pub fn get_coverage(
4458        &self,
4459        surface: &SurfaceInfo,
4460        sub_rect: Option<LayoutRect>,
4461    ) -> LayoutRect {
4462        let surface_rect = match sub_rect {
4463            Some(sub_rect) => sub_rect,
4464            None => surface.clipped_local_rect.cast_unit(),
4465        };
4466
4467        match self {
4468            PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate }) => {
4469                if *should_inflate {
4470                    let (width_factor, height_factor) = surface.clamp_blur_radius(*width, *height);
4471
4472                    surface_rect.inflate(
4473                        width_factor.ceil() * BLUR_SAMPLE_SCALE,
4474                        height_factor.ceil() * BLUR_SAMPLE_SCALE,
4475                    )
4476                } else {
4477                    surface_rect
4478                }
4479            }
4480            PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
4481                let mut rect = surface_rect;
4482
4483                for shadow in shadows {
4484                    let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
4485                        shadow.blur_radius,
4486                        shadow.blur_radius,
4487                    );
4488                    let blur_inflation_x = blur_radius_x * BLUR_SAMPLE_SCALE;
4489                    let blur_inflation_y = blur_radius_y * BLUR_SAMPLE_SCALE;
4490
4491                    let shadow_rect = surface_rect
4492                        .translate(shadow.offset)
4493                        .inflate(blur_inflation_x, blur_inflation_y);
4494                    rect = rect.union(&shadow_rect);
4495                }
4496
4497                rect
4498            }
4499            PictureCompositeMode::SvgFilter(primitives, _) => {
4500                let mut result_rect = surface_rect;
4501                let mut output_rects = Vec::with_capacity(primitives.len());
4502
4503                for (cur_index, primitive) in primitives.iter().enumerate() {
4504                    let output_rect = match primitive.kind {
4505                        FilterPrimitiveKind::Blur(ref primitive) => {
4506                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect);
4507                            let width_factor = primitive.width.round() * BLUR_SAMPLE_SCALE;
4508                            let height_factor = primitive.height.round() * BLUR_SAMPLE_SCALE;
4509
4510                            input.inflate(width_factor, height_factor)
4511                        }
4512                        FilterPrimitiveKind::DropShadow(ref primitive) => {
4513                            let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
4514                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect);
4515                            let shadow_rect = input.inflate(inflation_factor, inflation_factor);
4516                            input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0)))
4517                        }
4518                        FilterPrimitiveKind::Blend(ref primitive) => {
4519                            primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)
4520                                .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect))
4521                        }
4522                        FilterPrimitiveKind::Composite(ref primitive) => {
4523                            primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)
4524                                .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect))
4525                        }
4526                        FilterPrimitiveKind::Identity(ref primitive) =>
4527                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4528                        FilterPrimitiveKind::Opacity(ref primitive) =>
4529                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4530                        FilterPrimitiveKind::ColorMatrix(ref primitive) =>
4531                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4532                        FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
4533                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect),
4534                        FilterPrimitiveKind::Offset(ref primitive) => {
4535                            let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect);
4536                            input_rect.translate(primitive.offset * Scale::new(1.0))
4537                        },
4538
4539                        FilterPrimitiveKind::Flood(..) => surface_rect,
4540                    };
4541                    output_rects.push(output_rect);
4542                    result_rect = result_rect.union(&output_rect);
4543                }
4544                result_rect
4545            }
4546            PictureCompositeMode::SVGFEGraph(ref filters) => {
4547                // surface_rect may be for source or target, so invalidate based
4548                // on both interpretations
4549                let target_subregion = self.get_coverage_source_svgfe(filters, surface_rect.cast());
4550                let source_subregion = self.get_coverage_target_svgfe(filters, surface_rect.cast());
4551                target_subregion.union(&source_subregion)
4552            }
4553            _ => {
4554                surface_rect
4555            }
4556        }
4557    }
4558
4559    /// Returns a static str describing the type of PictureCompositeMode (and
4560    /// filter type if applicable)
4561    pub fn kind(&self) -> &'static str {
4562        match *self {
4563            PictureCompositeMode::Blit(..) => "Blit",
4564            PictureCompositeMode::ComponentTransferFilter(..) => "ComponentTransferFilter",
4565            PictureCompositeMode::IntermediateSurface => "IntermediateSurface",
4566            PictureCompositeMode::MixBlend(..) => "MixBlend",
4567            PictureCompositeMode::SVGFEGraph(..) => "SVGFEGraph",
4568            PictureCompositeMode::SvgFilter(..) => "SvgFilter",
4569            PictureCompositeMode::TileCache{..} => "TileCache",
4570            PictureCompositeMode::Filter(Filter::Blur{..}) => "Filter::Blur",
4571            PictureCompositeMode::Filter(Filter::Brightness(..)) => "Filter::Brightness",
4572            PictureCompositeMode::Filter(Filter::ColorMatrix(..)) => "Filter::ColorMatrix",
4573            PictureCompositeMode::Filter(Filter::ComponentTransfer) => "Filter::ComponentTransfer",
4574            PictureCompositeMode::Filter(Filter::Contrast(..)) => "Filter::Contrast",
4575            PictureCompositeMode::Filter(Filter::DropShadows(..)) => "Filter::DropShadows",
4576            PictureCompositeMode::Filter(Filter::Flood(..)) => "Filter::Flood",
4577            PictureCompositeMode::Filter(Filter::Grayscale(..)) => "Filter::Grayscale",
4578            PictureCompositeMode::Filter(Filter::HueRotate(..)) => "Filter::HueRotate",
4579            PictureCompositeMode::Filter(Filter::Identity) => "Filter::Identity",
4580            PictureCompositeMode::Filter(Filter::Invert(..)) => "Filter::Invert",
4581            PictureCompositeMode::Filter(Filter::LinearToSrgb) => "Filter::LinearToSrgb",
4582            PictureCompositeMode::Filter(Filter::Opacity(..)) => "Filter::Opacity",
4583            PictureCompositeMode::Filter(Filter::Saturate(..)) => "Filter::Saturate",
4584            PictureCompositeMode::Filter(Filter::Sepia(..)) => "Filter::Sepia",
4585            PictureCompositeMode::Filter(Filter::SrgbToLinear) => "Filter::SrgbToLinear",
4586            PictureCompositeMode::Filter(Filter::SVGGraphNode(..)) => "Filter::SVGGraphNode",
4587        }
4588    }
4589
4590    /// Here we transform source rect to target rect for SVGFEGraph by walking
4591    /// the whole graph and propagating subregions based on the provided
4592    /// invalidation rect, and we want it to be a tight fit so we don't waste
4593    /// time applying multiple filters to pixels that do not contribute to the
4594    /// invalidated rect.
4595    ///
4596    /// The interesting parts of the handling of SVG filters are:
4597    /// * scene_building.rs : wrap_prim_with_filters
4598    /// * picture.rs : get_coverage_target_svgfe (you are here)
4599    /// * picture.rs : get_coverage_source_svgfe
4600    /// * render_task.rs : new_svg_filter_graph
4601    /// * render_target.rs : add_svg_filter_node_instances
4602    pub fn get_coverage_target_svgfe(
4603        &self,
4604        filters: &[(FilterGraphNode, FilterGraphOp)],
4605        surface_rect: LayoutRect,
4606    ) -> LayoutRect {
4607
4608        // The value of BUFFER_LIMIT here must be the same as in
4609        // scene_building.rs, or we'll hit asserts here.
4610        const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
4611
4612        // We need to evaluate the subregions based on the proposed
4613        // SourceGraphic rect as it isn't known at scene build time.
4614        let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT];
4615        for (id, (node, op)) in filters.iter().enumerate() {
4616            let full_subregion = node.subregion;
4617            let mut used_subregion = LayoutRect::zero();
4618            for input in &node.inputs {
4619                match input.buffer_id {
4620                    FilterOpGraphPictureBufferId::BufferId(id) => {
4621                        assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
4622                        // This id lookup should always succeed.
4623                        let input_subregion = subregion_by_buffer_id[id as usize];
4624                        // Now add the padding that transforms from
4625                        // source to target, this was determined during
4626                        // scene build based on the operation.
4627                        let input_subregion =
4628                            LayoutRect::new(
4629                                LayoutPoint::new(
4630                                    input_subregion.min.x + input.target_padding.min.x,
4631                                    input_subregion.min.y + input.target_padding.min.y,
4632                                ),
4633                                LayoutPoint::new(
4634                                    input_subregion.max.x + input.target_padding.max.x,
4635                                    input_subregion.max.y + input.target_padding.max.y,
4636                                ),
4637                            );
4638                        used_subregion = used_subregion
4639                            .union(&input_subregion);
4640                    }
4641                    FilterOpGraphPictureBufferId::None => {
4642                        panic!("Unsupported BufferId type");
4643                    }
4644                }
4645            }
4646            // We can clip the used subregion to the node subregion
4647            used_subregion = used_subregion
4648                .intersection(&full_subregion)
4649                .unwrap_or(LayoutRect::zero());
4650            match op {
4651                FilterGraphOp::SVGFEBlendColor => {}
4652                FilterGraphOp::SVGFEBlendColorBurn => {}
4653                FilterGraphOp::SVGFEBlendColorDodge => {}
4654                FilterGraphOp::SVGFEBlendDarken => {}
4655                FilterGraphOp::SVGFEBlendDifference => {}
4656                FilterGraphOp::SVGFEBlendExclusion => {}
4657                FilterGraphOp::SVGFEBlendHardLight => {}
4658                FilterGraphOp::SVGFEBlendHue => {}
4659                FilterGraphOp::SVGFEBlendLighten => {}
4660                FilterGraphOp::SVGFEBlendLuminosity => {}
4661                FilterGraphOp::SVGFEBlendMultiply => {}
4662                FilterGraphOp::SVGFEBlendNormal => {}
4663                FilterGraphOp::SVGFEBlendOverlay => {}
4664                FilterGraphOp::SVGFEBlendSaturation => {}
4665                FilterGraphOp::SVGFEBlendScreen => {}
4666                FilterGraphOp::SVGFEBlendSoftLight => {}
4667                FilterGraphOp::SVGFEColorMatrix { values } => {
4668                    if values[3] != 0.0 ||
4669                        values[7] != 0.0 ||
4670                        values[11] != 0.0 ||
4671                        values[19] != 0.0 {
4672                        // Manipulating alpha can easily create new
4673                        // pixels outside of input subregions
4674                        used_subregion = full_subregion;
4675                    }
4676                }
4677                FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
4678                FilterGraphOp::SVGFEComponentTransferInterned{handle: _, creates_pixels} => {
4679                    // Check if the value of alpha[0] is modified, if so
4680                    // the whole subregion is used because it will be
4681                    // creating new pixels outside of input subregions
4682                    if *creates_pixels {
4683                        used_subregion = full_subregion;
4684                    }
4685                }
4686                FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => {
4687                    // Optimization opportunity - some inputs may be
4688                    // smaller subregions due to the way the math works,
4689                    // k1 is the intersection of the two inputs, k2 is
4690                    // the first input only, k3 is the second input
4691                    // only, and k4 changes the whole subregion.
4692                    //
4693                    // See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC
4694                    // in FilterSupport.cpp
4695                    //
4696                    // We can at least ignore the entire node if
4697                    // everything is zero.
4698                    if *k1 <= 0.0 &&
4699                        *k2 <= 0.0 &&
4700                        *k3 <= 0.0 {
4701                        used_subregion = LayoutRect::zero();
4702                    }
4703                    // Check if alpha is added to pixels as it means it
4704                    // can fill pixels outside input subregions
4705                    if *k4 > 0.0 {
4706                        used_subregion = full_subregion;
4707                    }
4708                }
4709                FilterGraphOp::SVGFECompositeATop => {}
4710                FilterGraphOp::SVGFECompositeIn => {}
4711                FilterGraphOp::SVGFECompositeLighter => {}
4712                FilterGraphOp::SVGFECompositeOut => {}
4713                FilterGraphOp::SVGFECompositeOver => {}
4714                FilterGraphOp::SVGFECompositeXOR => {}
4715                FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {}
4716                FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {}
4717                FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {}
4718                FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {}
4719                FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {}
4720                FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {}
4721                FilterGraphOp::SVGFEDisplacementMap{..} => {}
4722                FilterGraphOp::SVGFEDropShadow{..} => {}
4723                FilterGraphOp::SVGFEFlood { color } => {
4724                    // Subregion needs to be set to the full node
4725                    // subregion for fills (unless the fill is a no-op)
4726                    if color.a > 0.0 {
4727                        used_subregion = full_subregion;
4728                    }
4729                }
4730                FilterGraphOp::SVGFEGaussianBlur{..} => {}
4731                FilterGraphOp::SVGFEIdentity => {}
4732                FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => {
4733                    // TODO: calculate the actual subregion
4734                    used_subregion = full_subregion;
4735                }
4736                FilterGraphOp::SVGFEMorphologyDilate{..} => {}
4737                FilterGraphOp::SVGFEMorphologyErode{..} => {}
4738                FilterGraphOp::SVGFEOpacity { valuebinding: _valuebinding, value } => {
4739                    // If fully transparent, we can ignore this node
4740                    if *value <= 0.0 {
4741                        used_subregion = LayoutRect::zero();
4742                    }
4743                }
4744                FilterGraphOp::SVGFESourceAlpha |
4745                FilterGraphOp::SVGFESourceGraphic => {
4746                    used_subregion = surface_rect;
4747                }
4748                FilterGraphOp::SVGFESpecularLightingDistant{..} => {}
4749                FilterGraphOp::SVGFESpecularLightingPoint{..} => {}
4750                FilterGraphOp::SVGFESpecularLightingSpot{..} => {}
4751                FilterGraphOp::SVGFETile => {
4752                    // feTile fills the entire output with
4753                    // source pixels, so it's effectively a flood.
4754                    used_subregion = full_subregion;
4755                }
4756                FilterGraphOp::SVGFEToAlpha => {}
4757                FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
4758                FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
4759                FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
4760                FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
4761                    // Turbulence produces pixel values throughout the
4762                    // node subregion.
4763                    used_subregion = full_subregion;
4764                }
4765            }
4766            // Store the subregion so later nodes can refer back
4767            // to this and propagate rects properly
4768            assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
4769            subregion_by_buffer_id[id] = used_subregion;
4770        }
4771        subregion_by_buffer_id[filters.len() - 1]
4772    }
4773
4774    /// Here we transform target rect to source rect for SVGFEGraph by walking
4775    /// the whole graph and propagating subregions based on the provided
4776    /// invalidation rect, and we want it to be a tight fit so we don't waste
4777    /// time applying multiple filters to pixels that do not contribute to the
4778    /// invalidated rect.
4779    ///
4780    /// The interesting parts of the handling of SVG filters are:
4781    /// * scene_building.rs : wrap_prim_with_filters
4782    /// * picture.rs : get_coverage_target_svgfe
4783    /// * picture.rs : get_coverage_source_svgfe (you are here)
4784    /// * render_task.rs : new_svg_filter_graph
4785    /// * render_target.rs : add_svg_filter_node_instances
4786    pub fn get_coverage_source_svgfe(
4787        &self,
4788        filters: &[(FilterGraphNode, FilterGraphOp)],
4789        surface_rect: LayoutRect,
4790    ) -> LayoutRect {
4791
4792        // The value of BUFFER_LIMIT here must be the same as in
4793        // scene_building.rs, or we'll hit asserts here.
4794        const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
4795
4796        // We're solving the source rect from target rect (e.g. due
4797        // to invalidation of a region, we need to know how much of
4798        // SourceGraphic is needed to draw that region accurately),
4799        // so we need to walk the DAG in reverse and accumulate the source
4800        // subregion for each input onto the referenced node, which can then
4801        // propagate that to its inputs when it is iterated.
4802        let mut source_subregion = LayoutRect::zero();
4803        let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] =
4804        [LayoutRect::zero(); BUFFER_LIMIT];
4805        let final_buffer_id = filters.len() - 1;
4806        assert!(final_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
4807        subregion_by_buffer_id[final_buffer_id] = surface_rect;
4808        for (node_buffer_id, (node, op)) in filters.iter().enumerate().rev() {
4809            // This is the subregion this node outputs, we can clip
4810            // the inputs based on source_padding relative to this,
4811            // and accumulate a new subregion for them.
4812            assert!(node_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
4813            let full_subregion = node.subregion;
4814            let mut used_subregion =
4815                subregion_by_buffer_id[node_buffer_id];
4816            // We can clip the propagated subregion to the node subregion before
4817            // we add source_padding for each input and propogate to them
4818            used_subregion = used_subregion
4819                .intersection(&full_subregion)
4820                .unwrap_or(LayoutRect::zero());
4821            if !used_subregion.is_empty() {
4822                for input in &node.inputs {
4823                    let input_subregion = LayoutRect::new(
4824                        LayoutPoint::new(
4825                            used_subregion.min.x + input.source_padding.min.x,
4826                            used_subregion.min.y + input.source_padding.min.y,
4827                        ),
4828                        LayoutPoint::new(
4829                            used_subregion.max.x + input.source_padding.max.x,
4830                            used_subregion.max.y + input.source_padding.max.y,
4831                        ),
4832                    );
4833                    match input.buffer_id {
4834                        FilterOpGraphPictureBufferId::BufferId(id) => {
4835                            // Add the used area to the input, later when
4836                            // the referneced node is iterated as a node it
4837                            // will propagate the used bounds.
4838                            subregion_by_buffer_id[id as usize] =
4839                                subregion_by_buffer_id[id as usize]
4840                                .union(&input_subregion);
4841                        }
4842                        FilterOpGraphPictureBufferId::None => {}
4843                    }
4844                }
4845            }
4846            // If this is the SourceGraphic or SourceAlpha, we now have the
4847            // source subregion we're looking for.  If both exist in the
4848            // same graph, we need to combine them, so don't merely replace.
4849            match op {
4850                FilterGraphOp::SVGFESourceAlpha |
4851                FilterGraphOp::SVGFESourceGraphic => {
4852                    source_subregion = source_subregion.union(&used_subregion);
4853                }
4854                _ => {}
4855            }
4856        }
4857
4858        // Note that this can be zero if SourceGraphic/SourceAlpha is not used
4859        // in this graph.
4860        source_subregion
4861    }
4862}
4863
4864/// Enum value describing the place of a picture in a 3D context.
4865#[derive(Clone, Debug)]
4866#[cfg_attr(feature = "capture", derive(Serialize))]
4867pub enum Picture3DContext<C> {
4868    /// The picture is not a part of 3D context sub-hierarchy.
4869    Out,
4870    /// The picture is a part of 3D context.
4871    In {
4872        /// Additional data per child for the case of this a root of 3D hierarchy.
4873        root_data: Option<Vec<C>>,
4874        /// The spatial node index of an "ancestor" element, i.e. one
4875        /// that establishes the transformed element's containing block.
4876        ///
4877        /// See CSS spec draft for more details:
4878        /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
4879        ancestor_index: SpatialNodeIndex,
4880        /// Index in the built scene's array of plane splitters.
4881        plane_splitter_index: PlaneSplitterIndex,
4882    },
4883}
4884
4885/// Information about a preserve-3D hierarchy child that has been plane-split
4886/// and ordered according to the view direction.
4887#[derive(Clone, Debug)]
4888#[cfg_attr(feature = "capture", derive(Serialize))]
4889pub struct OrderedPictureChild {
4890    pub anchor: PlaneSplitAnchor,
4891    pub gpu_address: GpuCacheAddress,
4892}
4893
4894bitflags! {
4895    /// A set of flags describing why a picture may need a backing surface.
4896    #[cfg_attr(feature = "capture", derive(Serialize))]
4897    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
4898    pub struct ClusterFlags: u32 {
4899        /// Whether this cluster is visible when the position node is a backface.
4900        const IS_BACKFACE_VISIBLE = 1;
4901        /// This flag is set during the first pass picture traversal, depending on whether
4902        /// the cluster is visible or not. It's read during the second pass when primitives
4903        /// consult their owning clusters to see if the primitive itself is visible.
4904        const IS_VISIBLE = 2;
4905    }
4906}
4907
4908/// Descriptor for a cluster of primitives. For now, this is quite basic but will be
4909/// extended to handle more spatial clustering of primitives.
4910#[cfg_attr(feature = "capture", derive(Serialize))]
4911pub struct PrimitiveCluster {
4912    /// The positioning node for this cluster.
4913    pub spatial_node_index: SpatialNodeIndex,
4914    /// The bounding rect of the cluster, in the local space of the spatial node.
4915    /// This is used to quickly determine the overall bounding rect for a picture
4916    /// during the first picture traversal, which is needed for local scale
4917    /// determination, and render task size calculations.
4918    bounding_rect: LayoutRect,
4919    /// a part of the cluster that we know to be opaque if any. Does not always
4920    /// describe the entire opaque region, but all content within that rect must
4921    /// be opaque.
4922    pub opaque_rect: LayoutRect,
4923    /// The range of primitive instance indices associated with this cluster.
4924    pub prim_range: Range<usize>,
4925    /// Various flags / state for this cluster.
4926    pub flags: ClusterFlags,
4927}
4928
4929impl PrimitiveCluster {
4930    /// Construct a new primitive cluster for a given positioning node.
4931    fn new(
4932        spatial_node_index: SpatialNodeIndex,
4933        flags: ClusterFlags,
4934        first_instance_index: usize,
4935    ) -> Self {
4936        PrimitiveCluster {
4937            bounding_rect: LayoutRect::zero(),
4938            opaque_rect: LayoutRect::zero(),
4939            spatial_node_index,
4940            flags,
4941            prim_range: first_instance_index..first_instance_index
4942        }
4943    }
4944
4945    /// Return true if this cluster is compatible with the given params
4946    pub fn is_compatible(
4947        &self,
4948        spatial_node_index: SpatialNodeIndex,
4949        flags: ClusterFlags,
4950        instance_index: usize,
4951    ) -> bool {
4952        self.flags == flags &&
4953        self.spatial_node_index == spatial_node_index &&
4954        instance_index == self.prim_range.end
4955    }
4956
4957    pub fn prim_range(&self) -> Range<usize> {
4958        self.prim_range.clone()
4959    }
4960
4961    /// Add a primitive instance to this cluster, at the start or end
4962    fn add_instance(
4963        &mut self,
4964        culling_rect: &LayoutRect,
4965        instance_index: usize,
4966    ) {
4967        debug_assert_eq!(instance_index, self.prim_range.end);
4968        self.bounding_rect = self.bounding_rect.union(culling_rect);
4969        self.prim_range.end += 1;
4970    }
4971}
4972
4973/// A list of primitive instances that are added to a picture
4974/// This ensures we can keep a list of primitives that
4975/// are pictures, for a fast initial traversal of the picture
4976/// tree without walking the instance list.
4977#[cfg_attr(feature = "capture", derive(Serialize))]
4978pub struct PrimitiveList {
4979    /// List of primitives grouped into clusters.
4980    pub clusters: Vec<PrimitiveCluster>,
4981    pub child_pictures: Vec<PictureIndex>,
4982    /// The number of Image compositor surfaces that were found when
4983    /// adding prims to this list, which might be rendered as overlays.
4984    pub image_surface_count: usize,
4985    /// The number of YuvImage compositor surfaces that were found when
4986    /// adding prims to this list, which might be rendered as overlays.
4987    pub yuv_image_surface_count: usize,
4988    pub needs_scissor_rect: bool,
4989}
4990
4991impl PrimitiveList {
4992    /// Construct an empty primitive list. This is
4993    /// just used during the take_context / restore_context
4994    /// borrow check dance, which will be removed as the
4995    /// picture traversal pass is completed.
4996    pub fn empty() -> Self {
4997        PrimitiveList {
4998            clusters: Vec::new(),
4999            child_pictures: Vec::new(),
5000            image_surface_count: 0,
5001            yuv_image_surface_count: 0,
5002            needs_scissor_rect: false,
5003        }
5004    }
5005
5006    pub fn merge(&mut self, other: PrimitiveList) {
5007        self.clusters.extend(other.clusters);
5008        self.child_pictures.extend(other.child_pictures);
5009        self.image_surface_count += other.image_surface_count;
5010        self.yuv_image_surface_count += other.yuv_image_surface_count;
5011        self.needs_scissor_rect |= other.needs_scissor_rect;
5012    }
5013
5014    /// Add a primitive instance to the end of the list
5015    pub fn add_prim(
5016        &mut self,
5017        prim_instance: PrimitiveInstance,
5018        prim_rect: LayoutRect,
5019        spatial_node_index: SpatialNodeIndex,
5020        prim_flags: PrimitiveFlags,
5021        prim_instances: &mut Vec<PrimitiveInstance>,
5022        clip_tree_builder: &ClipTreeBuilder,
5023    ) {
5024        let mut flags = ClusterFlags::empty();
5025
5026        // Pictures are always put into a new cluster, to make it faster to
5027        // iterate all pictures in a given primitive list.
5028        match prim_instance.kind {
5029            PrimitiveInstanceKind::Picture { pic_index, .. } => {
5030                self.child_pictures.push(pic_index);
5031            }
5032            PrimitiveInstanceKind::TextRun { .. } => {
5033                self.needs_scissor_rect = true;
5034            }
5035            PrimitiveInstanceKind::YuvImage { .. } => {
5036                // Any YUV image that requests a compositor surface is implicitly
5037                // opaque. Though we might treat this prim as an underlay, which
5038                // doesn't require an overlay surface, we add to the count anyway
5039                // in case we opt to present it as an overlay. This means we may
5040                // be allocating more subslices than we actually need, but it
5041                // gives us maximum flexibility.
5042                if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
5043                    self.yuv_image_surface_count += 1;
5044                }
5045            }
5046            PrimitiveInstanceKind::Image { .. } => {
5047                // For now, we assume that any image that wants a compositor surface
5048                // is transparent, and uses the existing overlay compositor surface
5049                // infrastructure. In future, we could detect opaque images, however
5050                // it's a little bit of work, as scene building doesn't have access
5051                // to the opacity state of an image key at this point.
5052                if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
5053                    self.image_surface_count += 1;
5054                }
5055            }
5056            _ => {}
5057        }
5058
5059        if prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
5060            flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE);
5061        }
5062
5063        let clip_leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
5064        let culling_rect = clip_leaf.local_clip_rect
5065            .intersection(&prim_rect)
5066            .unwrap_or_else(LayoutRect::zero);
5067
5068        let instance_index = prim_instances.len();
5069        prim_instances.push(prim_instance);
5070
5071        if let Some(cluster) = self.clusters.last_mut() {
5072            if cluster.is_compatible(spatial_node_index, flags, instance_index) {
5073                cluster.add_instance(&culling_rect, instance_index);
5074                return;
5075            }
5076        }
5077
5078        // Same idea with clusters, using a different distribution.
5079        let clusters_len = self.clusters.len();
5080        if clusters_len == self.clusters.capacity() {
5081            let next_alloc = match clusters_len {
5082                1 ..= 15 => 16 - clusters_len,
5083                16 ..= 127 => 128 - clusters_len,
5084                _ => clusters_len * 2,
5085            };
5086
5087            self.clusters.reserve(next_alloc);
5088        }
5089
5090        let mut cluster = PrimitiveCluster::new(
5091            spatial_node_index,
5092            flags,
5093            instance_index,
5094        );
5095
5096        cluster.add_instance(&culling_rect, instance_index);
5097        self.clusters.push(cluster);
5098    }
5099
5100    /// Returns true if there are no clusters (and thus primitives)
5101    pub fn is_empty(&self) -> bool {
5102        self.clusters.is_empty()
5103    }
5104}
5105
5106bitflags! {
5107    #[cfg_attr(feature = "capture", derive(Serialize))]
5108    /// Flags describing properties for a given PicturePrimitive
5109    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
5110    pub struct PictureFlags : u8 {
5111        /// This picture is a resolve target (doesn't actually render content itself,
5112        /// will have content copied in to it)
5113        const IS_RESOLVE_TARGET = 1 << 0;
5114        /// This picture establishes a sub-graph, which affects how SurfaceBuilder will
5115        /// set up dependencies in the render task graph
5116        const IS_SUB_GRAPH = 1 << 1;
5117        /// If set, this picture should not apply snapping via changing the raster root
5118        const DISABLE_SNAPPING = 1 << 2;
5119    }
5120}
5121
5122#[cfg_attr(feature = "capture", derive(Serialize))]
5123pub struct PicturePrimitive {
5124    /// List of primitives, and associated info for this picture.
5125    pub prim_list: PrimitiveList,
5126
5127    /// If false and transform ends up showing the back of the picture,
5128    /// it will be considered invisible.
5129    pub is_backface_visible: bool,
5130
5131    /// All render tasks have 0-2 input tasks.
5132    pub primary_render_task_id: Option<RenderTaskId>,
5133    /// If a mix-blend-mode, contains the render task for
5134    /// the readback of the framebuffer that we use to sample
5135    /// from in the mix-blend-mode shader.
5136    /// For drop-shadow filter, this will store the original
5137    /// picture task which would be rendered on screen after
5138    /// blur pass.
5139    /// This is also used by SVGFEBlend, SVGFEComposite and
5140    /// SVGFEDisplacementMap filters.
5141    pub secondary_render_task_id: Option<RenderTaskId>,
5142    /// How this picture should be composited.
5143    /// If None, don't composite - just draw directly on parent surface.
5144    pub composite_mode: Option<PictureCompositeMode>,
5145
5146    pub raster_config: Option<RasterConfig>,
5147    pub context_3d: Picture3DContext<OrderedPictureChild>,
5148
5149    // Optional cache handles for storing extra data
5150    // in the GPU cache, depending on the type of
5151    // picture.
5152    pub extra_gpu_data_handles: SmallVec<[GpuCacheHandle; 1]>,
5153
5154    /// The spatial node index of this picture when it is
5155    /// composited into the parent picture.
5156    pub spatial_node_index: SpatialNodeIndex,
5157
5158    /// Store the state of the previous local rect
5159    /// for this picture. We need this in order to know when
5160    /// to invalidate segments / drop-shadow gpu cache handles.
5161    pub prev_local_rect: LayoutRect,
5162
5163    /// If false, this picture needs to (re)build segments
5164    /// if it supports segment rendering. This can occur
5165    /// if the local rect of the picture changes due to
5166    /// transform animation and/or scrolling.
5167    pub segments_are_valid: bool,
5168
5169    /// Set to true if we know for sure the picture is fully opaque.
5170    pub is_opaque: bool,
5171
5172    /// Requested raster space for this picture
5173    pub raster_space: RasterSpace,
5174
5175    /// Flags for this picture primitive
5176    pub flags: PictureFlags,
5177
5178    /// The lowest common ancestor clip of all of the primitives in this
5179    /// picture, to be ignored when clipping those primitives and applied
5180    /// later when compositing the picture.
5181    pub clip_root: Option<ClipNodeId>,
5182
5183    /// If provided, cache the content of this picture into an image
5184    /// associated with the image key.
5185    pub snapshot: Option<SnapshotInfo>,
5186}
5187
5188impl PicturePrimitive {
5189    pub fn print<T: PrintTreePrinter>(
5190        &self,
5191        pictures: &[Self],
5192        self_index: PictureIndex,
5193        pt: &mut T,
5194    ) {
5195        pt.new_level(format!("{:?}", self_index));
5196        pt.add_item(format!("cluster_count: {:?}", self.prim_list.clusters.len()));
5197        pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
5198        pt.add_item(format!("raster_config: {:?}", self.raster_config));
5199        pt.add_item(format!("composite_mode: {:?}", self.composite_mode));
5200        pt.add_item(format!("flags: {:?}", self.flags));
5201
5202        for child_pic_index in &self.prim_list.child_pictures {
5203            pictures[child_pic_index.0].print(pictures, *child_pic_index, pt);
5204        }
5205
5206        pt.end_level();
5207    }
5208
5209    fn resolve_scene_properties(&mut self, properties: &SceneProperties) {
5210        match self.composite_mode {
5211            Some(PictureCompositeMode::Filter(ref mut filter)) => {
5212                match *filter {
5213                    Filter::Opacity(ref binding, ref mut value) => {
5214                        *value = properties.resolve_float(binding);
5215                    }
5216                    _ => {}
5217                }
5218            }
5219            _ => {}
5220        }
5221    }
5222
5223    pub fn is_visible(
5224        &self,
5225        spatial_tree: &SpatialTree,
5226    ) -> bool {
5227        if let Some(PictureCompositeMode::Filter(ref filter)) = self.composite_mode {
5228            if !filter.is_visible() {
5229                return false;
5230            }
5231        }
5232
5233        // For out-of-preserve-3d pictures, the backface visibility is determined by
5234        // the local transform only.
5235        // Note: we aren't taking the transform relative to the parent picture,
5236        // since picture tree can be more dense than the corresponding spatial tree.
5237        if !self.is_backface_visible {
5238            if let Picture3DContext::Out = self.context_3d {
5239                match spatial_tree.get_local_visible_face(self.spatial_node_index) {
5240                    VisibleFace::Front => {}
5241                    VisibleFace::Back => return false,
5242                }
5243            }
5244        }
5245
5246        true
5247    }
5248
5249    pub fn new_image(
5250        composite_mode: Option<PictureCompositeMode>,
5251        context_3d: Picture3DContext<OrderedPictureChild>,
5252        prim_flags: PrimitiveFlags,
5253        prim_list: PrimitiveList,
5254        spatial_node_index: SpatialNodeIndex,
5255        raster_space: RasterSpace,
5256        flags: PictureFlags,
5257        snapshot: Option<SnapshotInfo>,
5258    ) -> Self {
5259        PicturePrimitive {
5260            prim_list,
5261            primary_render_task_id: None,
5262            secondary_render_task_id: None,
5263            composite_mode,
5264            raster_config: None,
5265            context_3d,
5266            extra_gpu_data_handles: SmallVec::new(),
5267            is_backface_visible: prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE),
5268            spatial_node_index,
5269            prev_local_rect: LayoutRect::zero(),
5270            segments_are_valid: false,
5271            is_opaque: false,
5272            raster_space,
5273            flags,
5274            clip_root: None,
5275            snapshot,
5276        }
5277    }
5278
5279    pub fn take_context(
5280        &mut self,
5281        pic_index: PictureIndex,
5282        parent_surface_index: Option<SurfaceIndex>,
5283        parent_subpixel_mode: SubpixelMode,
5284        frame_state: &mut FrameBuildingState,
5285        frame_context: &FrameBuildingContext,
5286        data_stores: &mut DataStores,
5287        scratch: &mut PrimitiveScratchBuffer,
5288        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
5289    ) -> Option<(PictureContext, PictureState, PrimitiveList)> {
5290        frame_state.visited_pictures[pic_index.0] = true;
5291        self.primary_render_task_id = None;
5292        self.secondary_render_task_id = None;
5293
5294        let dbg_flags = DebugFlags::PICTURE_CACHING_DBG | DebugFlags::PICTURE_BORDERS;
5295        if frame_context.debug_flags.intersects(dbg_flags) {
5296            self.draw_debug_overlay(
5297                parent_surface_index,
5298                frame_state,
5299                frame_context,
5300                tile_caches,
5301                scratch,
5302            );
5303        }
5304
5305        if !self.is_visible(frame_context.spatial_tree) {
5306            return None;
5307        }
5308
5309        profile_scope!("take_context");
5310
5311        let surface_index = match self.raster_config {
5312            Some(ref raster_config) => raster_config.surface_index,
5313            None => parent_surface_index.expect("bug: no parent"),
5314        };
5315        let surface = &frame_state.surfaces[surface_index.0];
5316        let surface_spatial_node_index = surface.surface_spatial_node_index;
5317
5318        let map_pic_to_world = SpaceMapper::new_with_target(
5319            frame_context.root_spatial_node_index,
5320            surface_spatial_node_index,
5321            frame_context.global_screen_world_rect,
5322            frame_context.spatial_tree,
5323        );
5324
5325        let map_pic_to_vis = SpaceMapper::new_with_target(
5326            // TODO: switch from root to raster space.
5327            frame_context.root_spatial_node_index,
5328            surface_spatial_node_index,
5329            surface.culling_rect,
5330            frame_context.spatial_tree,
5331        );
5332
5333        // TODO: When moving VisRect to raster space, compute the picture
5334        // bounds by projecting the parent surface's culling rect into the
5335        // current surface's raster space.
5336        let pic_bounds = map_pic_to_world
5337            .unmap(&map_pic_to_world.bounds)
5338            .unwrap_or_else(PictureRect::max_rect);
5339
5340        let map_local_to_pic = SpaceMapper::new(
5341            surface_spatial_node_index,
5342            pic_bounds,
5343        );
5344
5345        match self.raster_config {
5346            Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => {
5347                let tile_cache = tile_caches.get_mut(&slice_id).unwrap();
5348                let mut debug_info = SliceDebugInfo::new();
5349                let mut surface_render_tasks = FastHashMap::default();
5350                let mut surface_local_dirty_rect = PictureRect::zero();
5351                let device_pixel_scale = frame_state
5352                    .surfaces[surface_index.0]
5353                    .device_pixel_scale;
5354                let mut at_least_one_tile_visible = false;
5355
5356                // Get the overall world space rect of the picture cache. Used to clip
5357                // the tile rects below for occlusion testing to the relevant area.
5358                let world_clip_rect = map_pic_to_world
5359                    .map(&tile_cache.local_clip_rect)
5360                    .expect("bug: unable to map clip rect")
5361                    .round();
5362                let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
5363
5364                for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter_mut().enumerate() {
5365                    for tile in sub_slice.tiles.values_mut() {
5366                        // Ensure that the dirty rect doesn't extend outside the local valid rect.
5367                        tile.local_dirty_rect = tile.local_dirty_rect
5368                            .intersection(&tile.current_descriptor.local_valid_rect)
5369                            .unwrap_or_else(|| { tile.is_valid = true; PictureRect::zero() });
5370
5371                        let valid_rect = frame_state.composite_state.get_surface_rect(
5372                            &tile.current_descriptor.local_valid_rect,
5373                            &tile.local_tile_rect,
5374                            tile_cache.transform_index,
5375                        ).to_i32();
5376
5377                        let scissor_rect = frame_state.composite_state.get_surface_rect(
5378                            &tile.local_dirty_rect,
5379                            &tile.local_tile_rect,
5380                            tile_cache.transform_index,
5381                        ).to_i32().intersection(&valid_rect).unwrap_or_else(|| { Box2D::zero() });
5382
5383                        if tile.is_visible {
5384                            // Get the world space rect that this tile will actually occupy on screen
5385                            let world_draw_rect = world_clip_rect.intersection(&tile.world_valid_rect);
5386
5387                            // If that draw rect is occluded by some set of tiles in front of it,
5388                            // then mark it as not visible and skip drawing. When it's not occluded
5389                            // it will fail this test, and get rasterized by the render task setup
5390                            // code below.
5391                            match world_draw_rect {
5392                                Some(world_draw_rect) => {
5393                                    // Only check for occlusion on visible tiles that are fixed position.
5394                                    if tile_cache.spatial_node_index == frame_context.root_spatial_node_index &&
5395                                       frame_state.composite_state.occluders.is_tile_occluded(tile.z_id, world_draw_rect) {
5396                                        // If this tile has an allocated native surface, free it, since it's completely
5397                                        // occluded. We will need to re-allocate this surface if it becomes visible,
5398                                        // but that's likely to be rare (e.g. when there is no content display list
5399                                        // for a frame or two during a tab switch).
5400                                        let surface = tile.surface.as_mut().expect("no tile surface set!");
5401
5402                                        if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface {
5403                                            if let Some(id) = id.take() {
5404                                                frame_state.resource_cache.destroy_compositor_tile(id);
5405                                            }
5406                                        }
5407
5408                                        tile.is_visible = false;
5409
5410                                        if frame_context.fb_config.testing {
5411                                            debug_info.tiles.insert(
5412                                                tile.tile_offset,
5413                                                TileDebugInfo::Occluded,
5414                                            );
5415                                        }
5416
5417                                        continue;
5418                                    }
5419                                }
5420                                None => {
5421                                    tile.is_visible = false;
5422                                }
5423                            }
5424
5425                            // In extreme zoom/offset cases, we may end up with a local scissor/valid rect
5426                            // that becomes empty after transformation to device space (e.g. if the local
5427                            // rect height is 0.00001 and the compositor transform has large scale + offset).
5428                            // DirectComposition panics if we try to BeginDraw with an empty rect, so catch
5429                            // that here and mark the tile non-visible. This is a bit of a hack - we should
5430                            // ideally handle these in a more accurate way so we don't end up with an empty
5431                            // rect here.
5432                            if !tile.is_valid && (scissor_rect.is_empty() || valid_rect.is_empty()) {
5433                                tile.is_visible = false;
5434                            }
5435                        }
5436
5437                        // If we get here, we want to ensure that the surface remains valid in the texture
5438                        // cache, _even if_ it's not visible due to clipping or being scrolled off-screen.
5439                        // This ensures that we retain valid tiles that are off-screen, but still in the
5440                        // display port of this tile cache instance.
5441                        if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() {
5442                            if let SurfaceTextureDescriptor::TextureCache { handle: Some(handle), .. } = descriptor {
5443                                frame_state.resource_cache
5444                                    .picture_textures.request(handle, frame_state.gpu_cache);
5445                            }
5446                        }
5447
5448                        // If the tile has been found to be off-screen / clipped, skip any further processing.
5449                        if !tile.is_visible {
5450                            if frame_context.fb_config.testing {
5451                                debug_info.tiles.insert(
5452                                    tile.tile_offset,
5453                                    TileDebugInfo::Culled,
5454                                );
5455                            }
5456
5457                            continue;
5458                        }
5459
5460                        at_least_one_tile_visible = true;
5461
5462                        if let TileSurface::Texture { descriptor, .. } = tile.surface.as_mut().unwrap() {
5463                            match descriptor {
5464                                SurfaceTextureDescriptor::TextureCache { ref handle, .. } => {
5465                                    let exists = handle.as_ref().map_or(false,
5466                                        |handle| frame_state.resource_cache.picture_textures.entry_exists(handle)
5467                                    );
5468                                    // Invalidate if the backing texture was evicted.
5469                                    if exists {
5470                                        // Request the backing texture so it won't get evicted this frame.
5471                                        // We specifically want to mark the tile texture as used, even
5472                                        // if it's detected not visible below and skipped. This is because
5473                                        // we maintain the set of tiles we care about based on visibility
5474                                        // during pre_update. If a tile still exists after that, we are
5475                                        // assuming that it's either visible or we want to retain it for
5476                                        // a while in case it gets scrolled back onto screen soon.
5477                                        // TODO(gw): Consider switching to manual eviction policy?
5478                                        frame_state.resource_cache
5479                                            .picture_textures
5480                                            .request(handle.as_ref().unwrap(), frame_state.gpu_cache);
5481                                    } else {
5482                                        // If the texture was evicted on a previous frame, we need to assume
5483                                        // that the entire tile rect is dirty.
5484                                        tile.invalidate(None, InvalidationReason::NoTexture);
5485                                    }
5486                                }
5487                                SurfaceTextureDescriptor::Native { id, .. } => {
5488                                    if id.is_none() {
5489                                        // There is no current surface allocation, so ensure the entire tile is invalidated
5490                                        tile.invalidate(None, InvalidationReason::NoSurface);
5491                                    }
5492                                }
5493                            }
5494                        }
5495
5496                        // Ensure - again - that the dirty rect doesn't extend outside the local valid rect,
5497                        // as the tile could have been invalidated since the first computation.
5498                        tile.local_dirty_rect = tile.local_dirty_rect
5499                            .intersection(&tile.current_descriptor.local_valid_rect)
5500                            .unwrap_or_else(|| { tile.is_valid = true; PictureRect::zero() });
5501
5502                        surface_local_dirty_rect = surface_local_dirty_rect.union(&tile.local_dirty_rect);
5503
5504                        // Update the world/device dirty rect
5505                        let world_dirty_rect = map_pic_to_world.map(&tile.local_dirty_rect).expect("bug");
5506
5507                        let device_rect = (tile.world_tile_rect * frame_context.global_device_pixel_scale).round();
5508                        tile.device_dirty_rect = (world_dirty_rect * frame_context.global_device_pixel_scale)
5509                            .round_out()
5510                            .intersection(&device_rect)
5511                            .unwrap_or_else(DeviceRect::zero);
5512
5513                        if tile.is_valid {
5514                            if frame_context.fb_config.testing {
5515                                debug_info.tiles.insert(
5516                                    tile.tile_offset,
5517                                    TileDebugInfo::Valid,
5518                                );
5519                            }
5520                        } else {
5521                            // Add this dirty rect to the dirty region tracker. This must be done outside the if statement below,
5522                            // so that we include in the dirty region tiles that are handled by a background color only (no
5523                            // surface allocation).
5524                            tile_cache.dirty_region.add_dirty_region(
5525                                tile.local_dirty_rect,
5526                                frame_context.spatial_tree,
5527                            );
5528
5529                            // Ensure that this texture is allocated.
5530                            if let TileSurface::Texture { ref mut descriptor } = tile.surface.as_mut().unwrap() {
5531                                match descriptor {
5532                                    SurfaceTextureDescriptor::TextureCache { ref mut handle } => {
5533
5534                                        frame_state.resource_cache.picture_textures.update(
5535                                            tile_cache.current_tile_size,
5536                                            handle,
5537                                            frame_state.gpu_cache,
5538                                            &mut frame_state.resource_cache.texture_cache.next_id,
5539                                            &mut frame_state.resource_cache.texture_cache.pending_updates,
5540                                        );
5541                                    }
5542                                    SurfaceTextureDescriptor::Native { id } => {
5543                                        if id.is_none() {
5544                                            // Allocate a native surface id if we're in native compositing mode,
5545                                            // and we don't have a surface yet (due to first frame, or destruction
5546                                            // due to tile size changing etc).
5547                                            if sub_slice.native_surface.is_none() {
5548                                                let opaque = frame_state
5549                                                    .resource_cache
5550                                                    .create_compositor_surface(
5551                                                        tile_cache.virtual_offset,
5552                                                        tile_cache.current_tile_size,
5553                                                        true,
5554                                                    );
5555
5556                                                let alpha = frame_state
5557                                                    .resource_cache
5558                                                    .create_compositor_surface(
5559                                                        tile_cache.virtual_offset,
5560                                                        tile_cache.current_tile_size,
5561                                                        false,
5562                                                    );
5563
5564                                                sub_slice.native_surface = Some(NativeSurface {
5565                                                    opaque,
5566                                                    alpha,
5567                                                });
5568                                            }
5569
5570                                            // Create the tile identifier and allocate it.
5571                                            let surface_id = if tile.is_opaque {
5572                                                sub_slice.native_surface.as_ref().unwrap().opaque
5573                                            } else {
5574                                                sub_slice.native_surface.as_ref().unwrap().alpha
5575                                            };
5576
5577                                            let tile_id = NativeTileId {
5578                                                surface_id,
5579                                                x: tile.tile_offset.x,
5580                                                y: tile.tile_offset.y,
5581                                            };
5582
5583                                            frame_state.resource_cache.create_compositor_tile(tile_id);
5584
5585                                            *id = Some(tile_id);
5586                                        }
5587                                    }
5588                                }
5589
5590                                // The cast_unit() here is because the `content_origin` is expected to be in
5591                                // device pixels, however we're establishing raster roots for picture cache
5592                                // tiles meaning the `content_origin` needs to be in the local space of that root.
5593                                // TODO(gw): `content_origin` should actually be in RasterPixels to be consistent
5594                                //           with both local / screen raster modes, but this involves a lot of
5595                                //           changes to render task and picture code.
5596                                let content_origin_f = tile.local_tile_rect.min.cast_unit() * device_pixel_scale;
5597                                let content_origin = content_origin_f.round();
5598                                // TODO: these asserts used to have a threshold of 0.01 but failed intermittently the
5599                                // gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html test on android.
5600                                // moving the rectangles in space mapping conversion code to the Box2D representaton
5601                                // made the failure happen more often.
5602                                debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.15);
5603                                debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.15);
5604
5605                                let surface = descriptor.resolve(
5606                                    frame_state.resource_cache,
5607                                    tile_cache.current_tile_size,
5608                                );
5609
5610                                // Recompute the scissor rect as the tile could have been invalidated since the first computation.
5611                                let scissor_rect = frame_state.composite_state.get_surface_rect(
5612                                    &tile.local_dirty_rect,
5613                                    &tile.local_tile_rect,
5614                                    tile_cache.transform_index,
5615                                ).to_i32();
5616
5617                                let composite_task_size = tile_cache.current_tile_size;
5618
5619                                let tile_key = TileKey {
5620                                    sub_slice_index: SubSliceIndex::new(sub_slice_index),
5621                                    tile_offset: tile.tile_offset,
5622                                };
5623
5624                                let mut clear_color = ColorF::TRANSPARENT;
5625
5626                                if SubSliceIndex::new(sub_slice_index).is_primary() {
5627                                    if let Some(background_color) = tile_cache.background_color {
5628                                        clear_color = background_color;
5629                                    }
5630
5631                                    // If this picture cache has a spanning_opaque_color, we will use
5632                                    // that as the clear color. The primitive that was detected as a
5633                                    // spanning primitive will have been set with IS_BACKDROP, causing
5634                                    // it to be skipped and removing everything added prior to it
5635                                    // during batching.
5636                                    if let Some(color) = tile_cache.backdrop.spanning_opaque_color {
5637                                        clear_color = color;
5638                                    }
5639                                }
5640
5641                                let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
5642
5643                                // TODO(gw): As a performance optimization, we could skip the resolve picture
5644                                //           if the dirty rect is the same as the resolve rect (probably quite
5645                                //           common for effects that scroll underneath a backdrop-filter, for example).
5646                                let use_tile_composite = !tile.sub_graphs.is_empty();
5647
5648                                if use_tile_composite {
5649                                    let mut local_content_rect = tile.local_dirty_rect;
5650
5651                                    for (sub_graph_rect, surface_stack) in &tile.sub_graphs {
5652                                        if let Some(dirty_sub_graph_rect) = sub_graph_rect.intersection(&tile.local_dirty_rect) {
5653                                            for (composite_mode, surface_index) in surface_stack {
5654                                                let surface = &frame_state.surfaces[surface_index.0];
5655
5656                                                let rect = composite_mode.get_coverage(
5657                                                    surface,
5658                                                    Some(dirty_sub_graph_rect.cast_unit()),
5659                                                ).cast_unit();
5660
5661                                                local_content_rect = local_content_rect.union(&rect);
5662                                            }
5663                                        }
5664                                    }
5665
5666                                    // We know that we'll never need to sample > 300 device pixels outside the tile
5667                                    // for blurring, so clamp the content rect here so that we don't try to allocate
5668                                    // a really large surface in the case of a drop-shadow with large offset.
5669                                    let max_content_rect = (tile.local_dirty_rect.cast_unit() * device_pixel_scale)
5670                                        .inflate(
5671                                            MAX_BLUR_RADIUS * BLUR_SAMPLE_SCALE,
5672                                            MAX_BLUR_RADIUS * BLUR_SAMPLE_SCALE,
5673                                        )
5674                                        .round_out()
5675                                        .to_i32();
5676
5677                                    let content_device_rect = (local_content_rect.cast_unit() * device_pixel_scale)
5678                                        .round_out()
5679                                        .to_i32();
5680
5681                                    let content_device_rect = content_device_rect
5682                                        .intersection(&max_content_rect)
5683                                        .expect("bug: no intersection with tile dirty rect: {content_device_rect:?} / {max_content_rect:?}");
5684
5685                                    let content_task_size = content_device_rect.size();
5686                                    let normalized_content_rect = content_task_size.into();
5687
5688                                    let inner_offset = content_origin + scissor_rect.min.to_vector().to_f32();
5689                                    let outer_offset = content_device_rect.min.to_f32();
5690                                    let sub_rect_offset = (inner_offset - outer_offset).round().to_i32();
5691
5692                                    let render_task_id = frame_state.rg_builder.add().init(
5693                                        RenderTask::new_dynamic(
5694                                            content_task_size,
5695                                            RenderTaskKind::new_picture(
5696                                                content_task_size,
5697                                                true,
5698                                                content_device_rect.min.to_f32(),
5699                                                surface_spatial_node_index,
5700                                                // raster == surface implicitly for picture cache tiles
5701                                                surface_spatial_node_index,
5702                                                device_pixel_scale,
5703                                                Some(normalized_content_rect),
5704                                                None,
5705                                                Some(clear_color),
5706                                                cmd_buffer_index,
5707                                                false,
5708                                            )
5709                                        ),
5710                                    );
5711
5712                                    let composite_task_id = frame_state.rg_builder.add().init(
5713                                        RenderTask::new(
5714                                            RenderTaskLocation::Static {
5715                                                surface: StaticRenderTaskSurface::PictureCache {
5716                                                    surface,
5717                                                },
5718                                                rect: composite_task_size.into(),
5719                                            },
5720                                            RenderTaskKind::new_tile_composite(
5721                                                sub_rect_offset,
5722                                                scissor_rect,
5723                                                valid_rect,
5724                                                clear_color,
5725                                            ),
5726                                        ),
5727                                    );
5728
5729                                    surface_render_tasks.insert(
5730                                        tile_key,
5731                                        SurfaceTileDescriptor {
5732                                            current_task_id: render_task_id,
5733                                            composite_task_id: Some(composite_task_id),
5734                                            dirty_rect: tile.local_dirty_rect,
5735                                        },
5736                                    );
5737                                } else {
5738                                    let render_task_id = frame_state.rg_builder.add().init(
5739                                        RenderTask::new(
5740                                            RenderTaskLocation::Static {
5741                                                surface: StaticRenderTaskSurface::PictureCache {
5742                                                    surface,
5743                                                },
5744                                                rect: composite_task_size.into(),
5745                                            },
5746                                            RenderTaskKind::new_picture(
5747                                                composite_task_size,
5748                                                true,
5749                                                content_origin,
5750                                                surface_spatial_node_index,
5751                                                // raster == surface implicitly for picture cache tiles
5752                                                surface_spatial_node_index,
5753                                                device_pixel_scale,
5754                                                Some(scissor_rect),
5755                                                Some(valid_rect),
5756                                                Some(clear_color),
5757                                                cmd_buffer_index,
5758                                                false,
5759                                            )
5760                                        ),
5761                                    );
5762
5763                                    surface_render_tasks.insert(
5764                                        tile_key,
5765                                        SurfaceTileDescriptor {
5766                                            current_task_id: render_task_id,
5767                                            composite_task_id: None,
5768                                            dirty_rect: tile.local_dirty_rect,
5769                                        },
5770                                    );
5771                                }
5772                            }
5773
5774                            if frame_context.fb_config.testing {
5775                                debug_info.tiles.insert(
5776                                    tile.tile_offset,
5777                                    TileDebugInfo::Dirty(DirtyTileDebugInfo {
5778                                        local_valid_rect: tile.current_descriptor.local_valid_rect,
5779                                        local_dirty_rect: tile.local_dirty_rect,
5780                                    }),
5781                                );
5782                            }
5783                        }
5784
5785                        let surface = tile.surface.as_ref().expect("no tile surface set!");
5786
5787                        let descriptor = CompositeTileDescriptor {
5788                            surface_kind: surface.into(),
5789                            tile_id: tile.id,
5790                        };
5791
5792                        let (surface, is_opaque) = match surface {
5793                            TileSurface::Color { color } => {
5794                                (CompositeTileSurface::Color { color: *color }, true)
5795                            }
5796                            TileSurface::Clear => {
5797                                // Clear tiles are rendered with blend mode pre-multiply-dest-out.
5798                                (CompositeTileSurface::Clear, false)
5799                            }
5800                            TileSurface::Texture { descriptor, .. } => {
5801                                let surface = descriptor.resolve(frame_state.resource_cache, tile_cache.current_tile_size);
5802                                (
5803                                    CompositeTileSurface::Texture { surface },
5804                                    tile.is_opaque
5805                                )
5806                            }
5807                        };
5808
5809                        if is_opaque {
5810                            sub_slice.opaque_tile_descriptors.push(descriptor);
5811                        } else {
5812                            sub_slice.alpha_tile_descriptors.push(descriptor);
5813                        }
5814
5815                        let composite_tile = CompositeTile {
5816                            kind: tile_kind(&surface, is_opaque),
5817                            surface,
5818                            local_rect: tile.local_tile_rect,
5819                            local_valid_rect: tile.current_descriptor.local_valid_rect,
5820                            local_dirty_rect: tile.local_dirty_rect,
5821                            device_clip_rect,
5822                            z_id: tile.z_id,
5823                            transform_index: tile_cache.transform_index,
5824                            clip_index: tile_cache.compositor_clip,
5825                        };
5826
5827                        sub_slice.composite_tiles.push(composite_tile);
5828
5829                        // Now that the tile is valid, reset the dirty rect.
5830                        tile.local_dirty_rect = PictureRect::zero();
5831                        tile.is_valid = true;
5832                    }
5833
5834                    // Sort the tile descriptor lists, since iterating values in the tile_cache.tiles
5835                    // hashmap doesn't provide any ordering guarantees, but we want to detect the
5836                    // composite descriptor as equal if the tiles list is the same, regardless of
5837                    // ordering.
5838                    sub_slice.opaque_tile_descriptors.sort_by_key(|desc| desc.tile_id);
5839                    sub_slice.alpha_tile_descriptors.sort_by_key(|desc| desc.tile_id);
5840                }
5841
5842                // Check to see if we should add backdrops as native surfaces.
5843                let backdrop_rect = tile_cache.backdrop.backdrop_rect
5844                    .intersection(&tile_cache.local_rect)
5845                    .and_then(|r| {
5846                        r.intersection(&tile_cache.local_clip_rect)
5847                });
5848
5849                let mut backdrop_in_use_and_visible = false;
5850                if let Some(backdrop_rect) = backdrop_rect {
5851                    let supports_surface_for_backdrop = match frame_state.composite_state.compositor_kind {
5852                        CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
5853                            false
5854                        }
5855                        CompositorKind::Native { capabilities, .. } => {
5856                            capabilities.supports_surface_for_backdrop
5857                        }
5858                    };
5859                    if supports_surface_for_backdrop && !tile_cache.found_prims_after_backdrop && at_least_one_tile_visible {
5860                        if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind {
5861                            backdrop_in_use_and_visible = true;
5862
5863                            // We're going to let the compositor handle the backdrop as a native surface.
5864                            // Hide all of our sub_slice tiles so they aren't also trying to draw it.
5865                            for sub_slice in &mut tile_cache.sub_slices {
5866                                for tile in sub_slice.tiles.values_mut() {
5867                                    tile.is_visible = false;
5868                                }
5869                            }
5870
5871                            // Destroy our backdrop surface if it doesn't match the new color.
5872                            // TODO: This is a performance hit for animated color backdrops.
5873                            if let Some(backdrop_surface) = &tile_cache.backdrop_surface {
5874                                if backdrop_surface.color != color {
5875                                    frame_state.resource_cache.destroy_compositor_surface(backdrop_surface.id);
5876                                    tile_cache.backdrop_surface = None;
5877                                }
5878                            }
5879
5880                            // Calculate the device_rect for the backdrop, which is just the backdrop_rect
5881                            // converted into world space and scaled to device pixels.
5882                            let world_backdrop_rect = map_pic_to_world.map(&backdrop_rect).expect("bug: unable to map backdrop rect");
5883                            let device_rect = (world_backdrop_rect * frame_context.global_device_pixel_scale).round();
5884
5885                            // If we already have a backdrop surface, update the device rect. Otherwise, create
5886                            // a backdrop surface.
5887                            if let Some(backdrop_surface) = &mut tile_cache.backdrop_surface {
5888                                backdrop_surface.device_rect = device_rect;
5889                            } else {
5890                                // Create native compositor surface with color for the backdrop and store the id.
5891                                tile_cache.backdrop_surface = Some(BackdropSurface {
5892                                    id: frame_state.resource_cache.create_compositor_backdrop_surface(color),
5893                                    color,
5894                                    device_rect,
5895                                });
5896                            }
5897                        }
5898                    }
5899                }
5900
5901                if !backdrop_in_use_and_visible {
5902                    if let Some(backdrop_surface) = &tile_cache.backdrop_surface {
5903                        // We've already allocated a backdrop surface, but we're not using it.
5904                        // Tell the compositor to get rid of it.
5905                        frame_state.resource_cache.destroy_compositor_surface(backdrop_surface.id);
5906                        tile_cache.backdrop_surface = None;
5907                    }
5908                }
5909
5910                // If invalidation debugging is enabled, dump the picture cache state to a tree printer.
5911                if frame_context.debug_flags.contains(DebugFlags::INVALIDATION_DBG) {
5912                    tile_cache.print();
5913                }
5914
5915                // If testing mode is enabled, write some information about the current state
5916                // of this picture cache (made available in RenderResults).
5917                if frame_context.fb_config.testing {
5918                    frame_state.composite_state
5919                        .picture_cache_debug
5920                        .slices
5921                        .insert(
5922                            tile_cache.slice,
5923                            debug_info,
5924                        );
5925                }
5926
5927                let descriptor = SurfaceDescriptor::new_tiled(surface_render_tasks);
5928
5929                frame_state.surface_builder.push_surface(
5930                    surface_index,
5931                    false,
5932                    surface_local_dirty_rect,
5933                    Some(descriptor),
5934                    frame_state.surfaces,
5935                    frame_state.rg_builder,
5936                );
5937            }
5938            Some(ref mut raster_config) => {
5939                let (pic_rect, force_scissor_rect) = {
5940                    let surface = &frame_state.surfaces[raster_config.surface_index.0];
5941                    (surface.clipped_local_rect, surface.force_scissor_rect)
5942                };
5943
5944                let parent_surface_index = parent_surface_index.expect("bug: no parent for child surface");
5945
5946                // Layout space for the picture is picture space from the
5947                // perspective of its child primitives.
5948                let local_rect = pic_rect * Scale::new(1.0);
5949
5950                // If the precise rect changed since last frame, we need to invalidate
5951                // any segments and gpu cache handles for drop-shadows.
5952                // TODO(gw): Requiring storage of the `prev_precise_local_rect` here
5953                //           is a total hack. It's required because `prev_precise_local_rect`
5954                //           gets written to twice (during initial vis pass and also during
5955                //           prepare pass). The proper longer term fix for this is to make
5956                //           use of the conservative picture rect for segmenting (which should
5957                //           be done during scene building).
5958                if local_rect != self.prev_local_rect {
5959                    match raster_config.composite_mode {
5960                        PictureCompositeMode::Filter(Filter::DropShadows(..)) => {
5961                            for handle in &self.extra_gpu_data_handles {
5962                                frame_state.gpu_cache.invalidate(handle);
5963                            }
5964                        }
5965                        _ => {}
5966                    }
5967                    // Invalidate any segments built for this picture, since the local
5968                    // rect has changed.
5969                    self.segments_are_valid = false;
5970                    self.prev_local_rect = local_rect;
5971                }
5972
5973                let max_surface_size = frame_context
5974                    .fb_config
5975                    .max_surface_override
5976                    .unwrap_or(MAX_SURFACE_SIZE) as f32;
5977
5978                let surface_rects = match get_surface_rects(
5979                    raster_config.surface_index,
5980                    &raster_config.composite_mode,
5981                    parent_surface_index,
5982                    &mut frame_state.surfaces,
5983                    frame_context.spatial_tree,
5984                    max_surface_size,
5985                    force_scissor_rect,
5986                ) {
5987                    Some(rects) => rects,
5988                    None => return None,
5989                };
5990
5991                let (raster_spatial_node_index, device_pixel_scale) = {
5992                    let surface = &frame_state.surfaces[surface_index.0];
5993                    (surface.raster_spatial_node_index, surface.device_pixel_scale)
5994                };
5995                let can_use_shared_surface = !self.flags.contains(PictureFlags::IS_RESOLVE_TARGET);
5996
5997                let primary_render_task_id;
5998                let surface_descriptor;
5999                match raster_config.composite_mode {
6000                    PictureCompositeMode::TileCache { .. } => {
6001                        unreachable!("handled above");
6002                    }
6003                    PictureCompositeMode::Filter(Filter::Blur { width, height, .. }) => {
6004                        let surface = &frame_state.surfaces[raster_config.surface_index.0];
6005                        let (width, height) = surface.clamp_blur_radius(width, height);
6006
6007                        let width_std_deviation = width * surface.local_scale.0 * device_pixel_scale.0;
6008                        let height_std_deviation = height * surface.local_scale.1 * device_pixel_scale.0;
6009                        let blur_std_deviation = DeviceSize::new(
6010                            width_std_deviation,
6011                            height_std_deviation,
6012                        );
6013
6014                        let original_size = surface_rects.clipped.size();
6015
6016                        // Adjust the size to avoid introducing sampling errors during the down-scaling passes.
6017                        // what would be even better is to rasterize the picture at the down-scaled size
6018                        // directly.
6019                        let adjusted_size = BlurTask::adjusted_blur_source_size(
6020                            original_size,
6021                            blur_std_deviation,
6022                        );
6023
6024                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6025
6026                        // Since we (may have) adjusted the render task size for downscaling accuracy
6027                        // above, recalculate the uv rect for tasks that may sample from this blur output
6028                        let uv_rect_kind = calculate_uv_rect_kind(
6029                            DeviceRect::from_origin_and_size(surface_rects.clipped.min, adjusted_size.to_f32()),
6030                            surface_rects.unclipped,
6031                        );
6032
6033                        let picture_task_id = frame_state.rg_builder.add().init(
6034                            RenderTask::new_dynamic(
6035                                adjusted_size,
6036                                RenderTaskKind::new_picture(
6037                                    adjusted_size,
6038                                    surface_rects.needs_scissor_rect,
6039                                    surface_rects.clipped.min,
6040                                    surface_spatial_node_index,
6041                                    raster_spatial_node_index,
6042                                    device_pixel_scale,
6043                                    None,
6044                                    None,
6045                                    None,
6046                                    cmd_buffer_index,
6047                                    can_use_shared_surface,
6048                                )
6049                            ).with_uv_rect_kind(uv_rect_kind)
6050                        );
6051
6052
6053                        let blur_render_task_id = request_render_task(
6054                            frame_state,
6055                            &self.snapshot,
6056                            &surface_rects,
6057                            false,
6058                            &mut|rg_builder, _, _| {
6059                                RenderTask::new_blur(
6060                                    blur_std_deviation,
6061                                    picture_task_id,
6062                                    rg_builder,
6063                                    RenderTargetKind::Color,
6064                                    None,
6065                                    original_size.to_i32(),
6066                                )
6067                            }
6068                        );
6069                        primary_render_task_id = blur_render_task_id;
6070
6071                        surface_descriptor = SurfaceDescriptor::new_chained(
6072                            picture_task_id,
6073                            blur_render_task_id,
6074                            surface_rects.clipped_local,
6075                        );
6076                    }
6077                    PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
6078                        let surface = &frame_state.surfaces[raster_config.surface_index.0];
6079
6080                        let device_rect = surface_rects.clipped;
6081
6082                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6083
6084                        let picture_task_id = frame_state.rg_builder.add().init(
6085                            RenderTask::new_dynamic(
6086                                surface_rects.task_size,
6087                                RenderTaskKind::new_picture(
6088                                    surface_rects.task_size,
6089                                    surface_rects.needs_scissor_rect,
6090                                    device_rect.min,
6091                                    surface_spatial_node_index,
6092                                    raster_spatial_node_index,
6093                                    device_pixel_scale,
6094                                    None,
6095                                    None,
6096                                    None,
6097                                    cmd_buffer_index,
6098                                    can_use_shared_surface,
6099                                ),
6100                            ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6101                        );
6102
6103                        let mut blur_tasks = BlurTaskCache::default();
6104
6105                        self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
6106
6107                        let mut blur_render_task_id = picture_task_id;
6108                        for shadow in shadows {
6109                            let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
6110                                shadow.blur_radius,
6111                                shadow.blur_radius,
6112                            );
6113
6114                            blur_render_task_id = RenderTask::new_blur(
6115                                DeviceSize::new(
6116                                    blur_radius_x * surface.local_scale.0 * device_pixel_scale.0,
6117                                    blur_radius_y * surface.local_scale.1 * device_pixel_scale.0,
6118                                ),
6119                                picture_task_id,
6120                                frame_state.rg_builder,
6121                                RenderTargetKind::Color,
6122                                Some(&mut blur_tasks),
6123                                device_rect.size().to_i32(),
6124                            );
6125                        }
6126
6127                        // TODO: Ensure that snapshots bake their shadow.
6128
6129                        // Add this content picture as a dependency of the parent surface, to
6130                        // ensure it isn't free'd after the shadow uses it as an input.
6131                        frame_state.surface_builder.add_picture_render_task(picture_task_id);
6132
6133                        primary_render_task_id = blur_render_task_id;
6134                        self.secondary_render_task_id = Some(picture_task_id);
6135
6136                        surface_descriptor = SurfaceDescriptor::new_chained(
6137                            picture_task_id,
6138                            blur_render_task_id,
6139                            surface_rects.clipped_local,
6140                        );
6141                    }
6142                    PictureCompositeMode::MixBlend(mode) if BlendMode::from_mix_blend_mode(
6143                        mode,
6144                        frame_context.fb_config.gpu_supports_advanced_blend,
6145                        frame_context.fb_config.advanced_blend_is_coherent,
6146                        frame_context.fb_config.dual_source_blending_is_supported,
6147                    ).is_none() => {
6148                        let parent_surface = &frame_state.surfaces[parent_surface_index.0];
6149
6150                        // Create a space mapper that will allow mapping from the local rect
6151                        // of the mix-blend primitive into the space of the surface that we
6152                        // need to read back from. Note that we use the parent's raster spatial
6153                        // node here, so that we are in the correct device space of the parent
6154                        // surface, whether it establishes a raster root or not.
6155                        let map_pic_to_parent = SpaceMapper::new_with_target(
6156                            parent_surface.surface_spatial_node_index,
6157                            surface_spatial_node_index,
6158                            parent_surface.clipping_rect,
6159                            frame_context.spatial_tree,
6160                        );
6161                        let pic_in_raster_space = map_pic_to_parent
6162                            .map(&pic_rect)
6163                            .expect("bug: unable to map mix-blend content into parent");
6164
6165                        // Apply device pixel ratio for parent surface to get into device
6166                        // pixels for that surface.
6167                        let backdrop_rect = pic_in_raster_space;
6168                        let parent_surface_rect = parent_surface.clipping_rect;
6169
6170                        // If there is no available parent surface to read back from (for example, if
6171                        // the parent surface is affected by a clip that doesn't affect the child
6172                        // surface), then create a dummy 16x16 readback. In future, we could alter
6173                        // the composite mode of this primitive to skip the mix-blend, but for simplicity
6174                        // we just create a dummy readback for now.
6175
6176                        let readback_task_id = match backdrop_rect.intersection(&parent_surface_rect) {
6177                            Some(available_rect) => {
6178                                // Calculate the UV coords necessary for the shader to sampler
6179                                // from the primitive rect within the readback region. This is
6180                                // 0..1 for aligned surfaces, but doing it this way allows
6181                                // accurate sampling if the primitive bounds have fractional values.
6182
6183                                let backdrop_rect = parent_surface.map_to_device_rect(
6184                                    &backdrop_rect,
6185                                    frame_context.spatial_tree,
6186                                );
6187
6188                                let available_rect = parent_surface.map_to_device_rect(
6189                                    &available_rect,
6190                                    frame_context.spatial_tree,
6191                                ).round_out();
6192
6193                                let backdrop_uv = calculate_uv_rect_kind(
6194                                    available_rect,
6195                                    backdrop_rect,
6196                                );
6197
6198                                frame_state.rg_builder.add().init(
6199                                    RenderTask::new_dynamic(
6200                                        available_rect.size().to_i32(),
6201                                        RenderTaskKind::new_readback(Some(available_rect.min)),
6202                                    ).with_uv_rect_kind(backdrop_uv)
6203                                )
6204                            }
6205                            None => {
6206                                frame_state.rg_builder.add().init(
6207                                    RenderTask::new_dynamic(
6208                                        DeviceIntSize::new(16, 16),
6209                                        RenderTaskKind::new_readback(None),
6210                                    )
6211                                )
6212                            }
6213                        };
6214
6215                        frame_state.surface_builder.add_child_render_task(
6216                            readback_task_id,
6217                            frame_state.rg_builder,
6218                        );
6219
6220                        self.secondary_render_task_id = Some(readback_task_id);
6221
6222                        let task_size = surface_rects.clipped.size().to_i32();
6223
6224                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6225
6226                        let is_opaque = false; // TODO
6227                        let render_task_id = request_render_task(
6228                            frame_state,
6229                            &self.snapshot,
6230                            &surface_rects,
6231                            is_opaque,
6232                            &mut|rg_builder, _, _| {
6233                                rg_builder.add().init(
6234                                    RenderTask::new_dynamic(
6235                                        task_size,
6236                                        RenderTaskKind::new_picture(
6237                                            task_size,
6238                                            surface_rects.needs_scissor_rect,
6239                                            surface_rects.clipped.min,
6240                                            surface_spatial_node_index,
6241                                            raster_spatial_node_index,
6242                                            device_pixel_scale,
6243                                            None,
6244                                            None,
6245                                            None,
6246                                            cmd_buffer_index,
6247                                            can_use_shared_surface,
6248                                        )
6249                                    ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6250                                )
6251                            }
6252                        );
6253
6254                        primary_render_task_id = render_task_id;
6255
6256                        surface_descriptor = SurfaceDescriptor::new_simple(
6257                            render_task_id,
6258                            surface_rects.clipped_local,
6259                        );
6260                    }
6261                    PictureCompositeMode::Filter(..) => {
6262                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6263
6264                        let is_opaque = false; // TODO
6265                        let render_task_id = request_render_task(
6266                            frame_state,
6267                            &self.snapshot,
6268                            &surface_rects,
6269                            is_opaque,
6270                            &mut|rg_builder, _, _| {
6271                                rg_builder.add().init(
6272                                    RenderTask::new_dynamic(
6273                                        surface_rects.task_size,
6274                                        RenderTaskKind::new_picture(
6275                                            surface_rects.task_size,
6276                                            surface_rects.needs_scissor_rect,
6277                                            surface_rects.clipped.min,
6278                                            surface_spatial_node_index,
6279                                            raster_spatial_node_index,
6280                                            device_pixel_scale,
6281                                            None,
6282                                            None,
6283                                            None,
6284                                            cmd_buffer_index,
6285                                            can_use_shared_surface,
6286                                        )
6287                                    ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6288                                )
6289                            },
6290                        );
6291
6292                        primary_render_task_id = render_task_id;
6293
6294                        surface_descriptor = SurfaceDescriptor::new_simple(
6295                            render_task_id,
6296                            surface_rects.clipped_local,
6297                        );
6298                    }
6299                    PictureCompositeMode::ComponentTransferFilter(..) => {
6300                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6301
6302                        let is_opaque = false; // TODO
6303                        let render_task_id = request_render_task(
6304                            frame_state,
6305                            &self.snapshot,
6306                            &surface_rects,
6307                            is_opaque,
6308                            &mut|rg_builder, _, _| {
6309                                rg_builder.add().init(
6310                                    RenderTask::new_dynamic(
6311                                        surface_rects.task_size,
6312                                        RenderTaskKind::new_picture(
6313                                            surface_rects.task_size,
6314                                            surface_rects.needs_scissor_rect,
6315                                            surface_rects.clipped.min,
6316                                            surface_spatial_node_index,
6317                                            raster_spatial_node_index,
6318                                            device_pixel_scale,
6319                                            None,
6320                                            None,
6321                                            None,
6322                                            cmd_buffer_index,
6323                                            can_use_shared_surface,
6324                                        )
6325                                    ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6326                                )
6327                            }
6328                        );
6329
6330                        primary_render_task_id = render_task_id;
6331
6332                        surface_descriptor = SurfaceDescriptor::new_simple(
6333                            render_task_id,
6334                            surface_rects.clipped_local,
6335                        );
6336                    }
6337                    PictureCompositeMode::MixBlend(..) |
6338                    PictureCompositeMode::Blit(_) => {
6339                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6340
6341                        let is_opaque = false; // TODO
6342                        let render_task_id = request_render_task(
6343                            frame_state,
6344                            &self.snapshot,
6345                            &surface_rects,
6346                            is_opaque,
6347                            &mut|rg_builder, _, _| {
6348                                rg_builder.add().init(
6349                                    RenderTask::new_dynamic(
6350                                        surface_rects.task_size,
6351                                        RenderTaskKind::new_picture(
6352                                            surface_rects.task_size,
6353                                            surface_rects.needs_scissor_rect,
6354                                            surface_rects.clipped.min,
6355                                            surface_spatial_node_index,
6356                                            raster_spatial_node_index,
6357                                            device_pixel_scale,
6358                                            None,
6359                                            None,
6360                                            None,
6361                                            cmd_buffer_index,
6362                                            can_use_shared_surface,
6363                                        )
6364                                    ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6365                                )
6366                            }
6367                        );
6368
6369                        primary_render_task_id = render_task_id;
6370
6371                        surface_descriptor = SurfaceDescriptor::new_simple(
6372                            render_task_id,
6373                            surface_rects.clipped_local,
6374                        );
6375                    }
6376                    PictureCompositeMode::IntermediateSurface => {
6377                        if !scratch.required_sub_graphs.contains(&pic_index) {
6378                            return None;
6379                        }
6380
6381                        // TODO(gw): Remove all the mostly duplicated code in each of these
6382                        //           match cases (they used to be quite different).
6383                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6384
6385                        let is_opaque = false; // TODO
6386                        let render_task_id = request_render_task(
6387                            frame_state,
6388                            &self.snapshot,
6389                            &surface_rects,
6390                            is_opaque,
6391                            &mut|rg_builder, _, _| {
6392                                rg_builder.add().init(
6393                                    RenderTask::new_dynamic(
6394                                        surface_rects.task_size,
6395                                        RenderTaskKind::new_picture(
6396                                            surface_rects.task_size,
6397                                            surface_rects.needs_scissor_rect,
6398                                            surface_rects.clipped.min,
6399                                            surface_spatial_node_index,
6400                                            raster_spatial_node_index,
6401                                            device_pixel_scale,
6402                                            None,
6403                                            None,
6404                                            None,
6405                                            cmd_buffer_index,
6406                                            can_use_shared_surface,
6407                                        )
6408                                    ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6409                                )
6410                            }
6411                        );
6412
6413                        primary_render_task_id = render_task_id;
6414
6415                        surface_descriptor = SurfaceDescriptor::new_simple(
6416                            render_task_id,
6417                            surface_rects.clipped_local,
6418                        );
6419                    }
6420                    PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
6421                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6422
6423                        let picture_task_id = frame_state.rg_builder.add().init(
6424                            RenderTask::new_dynamic(
6425                                surface_rects.task_size,
6426                                RenderTaskKind::new_picture(
6427                                    surface_rects.task_size,
6428                                    surface_rects.needs_scissor_rect,
6429                                    surface_rects.clipped.min,
6430                                    surface_spatial_node_index,
6431                                    raster_spatial_node_index,
6432                                    device_pixel_scale,
6433                                    None,
6434                                    None,
6435                                    None,
6436                                    cmd_buffer_index,
6437                                    can_use_shared_surface,
6438                                )
6439                            ).with_uv_rect_kind(surface_rects.uv_rect_kind)
6440                        );
6441
6442                        let is_opaque = false; // TODO
6443                        let filter_task_id = request_render_task(
6444                            frame_state,
6445                            &self.snapshot,
6446                            &surface_rects,
6447                            is_opaque,
6448                            &mut|rg_builder, _, _| {
6449                                RenderTask::new_svg_filter(
6450                                    primitives,
6451                                    filter_datas,
6452                                    rg_builder,
6453                                    surface_rects.clipped.size().to_i32(),
6454                                    surface_rects.uv_rect_kind,
6455                                    picture_task_id,
6456                                    device_pixel_scale,
6457                                )
6458                            }
6459                        );
6460
6461                        primary_render_task_id = filter_task_id;
6462
6463                        surface_descriptor = SurfaceDescriptor::new_chained(
6464                            picture_task_id,
6465                            filter_task_id,
6466                            surface_rects.clipped_local,
6467                        );
6468                    }
6469                    PictureCompositeMode::SVGFEGraph(ref filters) => {
6470                        let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
6471
6472                        // Whole target without regard to clipping.
6473                        let prim_subregion = surface_rects.unclipped;
6474                        // Visible (clipped) subregion within prim_subregion.
6475                        let target_subregion = surface_rects.clipped;
6476                        // Subregion of the SourceGraphic that we need to render
6477                        // all pixels within target_subregion.
6478                        let source_subregion = surface_rects.source;
6479
6480                        // Produce the source pixels, this task will be consumed
6481                        // by the RenderTask graph we build
6482                        let source_task_size = source_subregion.round_out().size().to_i32();
6483                        let source_task_size = if source_task_size.width > 0 && source_task_size.height > 0 {
6484                            source_task_size
6485                        } else {
6486                            DeviceIntSize::new(1,1)
6487                        };
6488                        let picture_task_id = frame_state.rg_builder.add().init(
6489                            RenderTask::new_dynamic(
6490                                source_task_size,
6491                                RenderTaskKind::new_picture(
6492                                    source_task_size,
6493                                    surface_rects.needs_scissor_rect,
6494                                    source_subregion.min,
6495                                    surface_spatial_node_index,
6496                                    raster_spatial_node_index,
6497                                    device_pixel_scale,
6498                                    None,
6499                                    None,
6500                                    None,
6501                                    cmd_buffer_index,
6502                                    can_use_shared_surface,
6503                                )
6504                            )
6505                        );
6506
6507                        // Determine the local space to device pixel scaling in the most robust
6508                        // way, this accounts for local to device transform and
6509                        // device_pixel_scale (if the task is shrunk in get_surface_rects).
6510                        let subregion_to_device_scale_x = surface_rects.clipped_notsnapped.width() / surface_rects.clipped_local.width();
6511                        let subregion_to_device_scale_y = surface_rects.clipped_notsnapped.height() / surface_rects.clipped_local.height();
6512                        let subregion_to_device_offset_x = surface_rects.clipped_notsnapped.min.x - (surface_rects.clipped_local.min.x * subregion_to_device_scale_x).floor();
6513                        let subregion_to_device_offset_y = surface_rects.clipped_notsnapped.min.y - (surface_rects.clipped_local.min.y * subregion_to_device_scale_y).floor();
6514
6515                        // Produce the target pixels, this is the result of the
6516                        // composite op
6517                        let filter_task_id = request_render_task(
6518                            frame_state,
6519                            &self.snapshot,
6520                            &surface_rects,
6521                            false,
6522                            &mut|rg_builder, _, gpu_cache| {
6523                                RenderTask::new_svg_filter_graph(
6524                                    filters,
6525                                    rg_builder,
6526                                    gpu_cache,
6527                                    data_stores,
6528                                    surface_rects.uv_rect_kind,
6529                                    picture_task_id,
6530                                    source_subregion.cast_unit(),
6531                                    target_subregion.cast_unit(),
6532                                    prim_subregion.cast_unit(),
6533                                    subregion_to_device_scale_x,
6534                                    subregion_to_device_scale_y,
6535                                    subregion_to_device_offset_x,
6536                                    subregion_to_device_offset_y,
6537                                )
6538                            }
6539                        );
6540
6541                        primary_render_task_id = filter_task_id;
6542
6543                        surface_descriptor = SurfaceDescriptor::new_chained(
6544                            picture_task_id,
6545                            filter_task_id,
6546                            surface_rects.clipped_local,
6547                        );
6548                    }
6549                }
6550
6551                let is_sub_graph = self.flags.contains(PictureFlags::IS_SUB_GRAPH);
6552
6553                frame_state.surface_builder.push_surface(
6554                    raster_config.surface_index,
6555                    is_sub_graph,
6556                    surface_rects.clipped_local,
6557                    Some(surface_descriptor),
6558                    frame_state.surfaces,
6559                    frame_state.rg_builder,
6560                );
6561
6562                self.primary_render_task_id = Some(primary_render_task_id);
6563            }
6564            None => {}
6565        };
6566
6567        let state = PictureState {
6568            map_local_to_pic,
6569            map_pic_to_vis,
6570        };
6571
6572        let mut dirty_region_count = 0;
6573
6574        // If this is a picture cache, push the dirty region to ensure any
6575        // child primitives are culled and clipped to the dirty rect(s).
6576        if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) = self.raster_config {
6577            let dirty_region = tile_caches[&slice_id].dirty_region.clone();
6578            frame_state.push_dirty_region(dirty_region);
6579
6580            dirty_region_count += 1;
6581        }
6582
6583        // Disallow subpixel AA if an intermediate surface is needed.
6584        // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
6585        let subpixel_mode = match self.raster_config {
6586            Some(RasterConfig { ref composite_mode, .. }) => {
6587                let subpixel_mode = match composite_mode {
6588                    PictureCompositeMode::TileCache { slice_id } => {
6589                        tile_caches[&slice_id].subpixel_mode
6590                    }
6591                    PictureCompositeMode::Blit(..) |
6592                    PictureCompositeMode::ComponentTransferFilter(..) |
6593                    PictureCompositeMode::Filter(..) |
6594                    PictureCompositeMode::MixBlend(..) |
6595                    PictureCompositeMode::IntermediateSurface |
6596                    PictureCompositeMode::SvgFilter(..) |
6597                    PictureCompositeMode::SVGFEGraph(..) => {
6598                        // TODO(gw): We can take advantage of the same logic that
6599                        //           exists in the opaque rect detection for tile
6600                        //           caches, to allow subpixel text on other surfaces
6601                        //           that can be detected as opaque.
6602                        SubpixelMode::Deny
6603                    }
6604                };
6605
6606                subpixel_mode
6607            }
6608            None => {
6609                SubpixelMode::Allow
6610            }
6611        };
6612
6613        // Still disable subpixel AA if parent forbids it
6614        let subpixel_mode = match (parent_subpixel_mode, subpixel_mode) {
6615            (SubpixelMode::Allow, SubpixelMode::Allow) => {
6616                // Both parent and this surface unconditionally allow subpixel AA
6617                SubpixelMode::Allow
6618            }
6619            (SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect, prohibited_rect }) => {
6620                // Parent allows, but we are conditional subpixel AA
6621                SubpixelMode::Conditional {
6622                    allowed_rect,
6623                    prohibited_rect,
6624                }
6625            }
6626            (SubpixelMode::Conditional { allowed_rect, prohibited_rect }, SubpixelMode::Allow) => {
6627                // Propagate conditional subpixel mode to child pictures that allow subpixel AA
6628                SubpixelMode::Conditional {
6629                    allowed_rect,
6630                    prohibited_rect,
6631                }
6632            }
6633            (SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => {
6634                unreachable!("bug: only top level picture caches have conditional subpixel");
6635            }
6636            (SubpixelMode::Deny, _) | (_, SubpixelMode::Deny) => {
6637                // Either parent or this surface explicitly deny subpixel, these take precedence
6638                SubpixelMode::Deny
6639            }
6640        };
6641
6642        let context = PictureContext {
6643            pic_index,
6644            raster_spatial_node_index: frame_state.surfaces[surface_index.0].raster_spatial_node_index,
6645            // TODO: switch the visibility spatial node from the root to raster space.
6646            visibility_spatial_node_index: frame_context.root_spatial_node_index,
6647            surface_spatial_node_index,
6648            surface_index,
6649            dirty_region_count,
6650            subpixel_mode,
6651        };
6652
6653        let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
6654
6655        Some((context, state, prim_list))
6656    }
6657
6658    pub fn restore_context(
6659        &mut self,
6660        pic_index: PictureIndex,
6661        prim_list: PrimitiveList,
6662        context: PictureContext,
6663        prim_instances: &[PrimitiveInstance],
6664        frame_context: &FrameBuildingContext,
6665        frame_state: &mut FrameBuildingState,
6666    ) {
6667        // Pop any dirty regions this picture set
6668        for _ in 0 .. context.dirty_region_count {
6669            frame_state.pop_dirty_region();
6670        }
6671
6672        if self.raster_config.is_some() {
6673            frame_state.surface_builder.pop_surface(
6674                pic_index,
6675                frame_state.rg_builder,
6676                frame_state.cmd_buffers,
6677            );
6678        }
6679
6680        if let Picture3DContext::In { root_data: Some(ref mut list), plane_splitter_index, .. } = self.context_3d {
6681            let splitter = &mut frame_state.plane_splitters[plane_splitter_index.0];
6682
6683            // Resolve split planes via BSP
6684            PicturePrimitive::resolve_split_planes(
6685                splitter,
6686                list,
6687                &mut frame_state.gpu_cache,
6688                &frame_context.spatial_tree,
6689            );
6690
6691            // Add the child prims to the relevant command buffers
6692            let mut cmd_buffer_targets = Vec::new();
6693            for child in list {
6694                let child_prim_instance = &prim_instances[child.anchor.instance_index.0 as usize];
6695
6696                if frame_state.surface_builder.get_cmd_buffer_targets_for_prim(
6697                    &child_prim_instance.vis,
6698                    &mut cmd_buffer_targets,
6699                ) {
6700                    let prim_cmd = PrimitiveCommand::complex(
6701                        child.anchor.instance_index,
6702                        child.gpu_address
6703                    );
6704
6705                    frame_state.push_prim(
6706                        &prim_cmd,
6707                        child.anchor.spatial_node_index,
6708                        &cmd_buffer_targets,
6709                    );
6710                }
6711            }
6712        }
6713
6714        self.prim_list = prim_list;
6715    }
6716
6717    /// Add a primitive instance to the plane splitter. The function would generate
6718    /// an appropriate polygon, clip it against the frustum, and register with the
6719    /// given plane splitter.
6720    pub fn add_split_plane(
6721        splitter: &mut PlaneSplitter,
6722        spatial_tree: &SpatialTree,
6723        prim_spatial_node_index: SpatialNodeIndex,
6724        // TODO: this is called "visibility" while transitioning from world to raster
6725        // space.
6726        visibility_spatial_node_index: SpatialNodeIndex,
6727        original_local_rect: LayoutRect,
6728        combined_local_clip_rect: &LayoutRect,
6729        dirty_rect: VisRect,
6730        plane_split_anchor: PlaneSplitAnchor,
6731    ) -> bool {
6732        let transform = spatial_tree.get_relative_transform(
6733            prim_spatial_node_index,
6734            visibility_spatial_node_index
6735        );
6736
6737        let matrix = transform.clone().into_transform().cast().to_untyped();
6738
6739        // Apply the local clip rect here, before splitting. This is
6740        // because the local clip rect can't be applied in the vertex
6741        // shader for split composites, since we are drawing polygons
6742        // rather that rectangles. The interpolation still works correctly
6743        // since we determine the UVs by doing a bilerp with a factor
6744        // from the original local rect.
6745        let local_rect = match original_local_rect
6746            .intersection(combined_local_clip_rect)
6747        {
6748            Some(rect) => rect.cast(),
6749            None => return false,
6750        };
6751        let dirty_rect = dirty_rect.cast();
6752
6753        match transform {
6754            CoordinateSpaceMapping::Local => {
6755                let polygon = Polygon::from_rect(
6756                    local_rect.to_rect() * Scale::new(1.0),
6757                    plane_split_anchor,
6758                );
6759                splitter.add(polygon);
6760            }
6761            CoordinateSpaceMapping::ScaleOffset(scale_offset) if scale_offset.scale == Vector2D::new(1.0, 1.0) => {
6762                let inv_matrix = scale_offset.inverse().to_transform().cast();
6763                let polygon = Polygon::from_transformed_rect_with_inverse(
6764                    local_rect.to_rect().to_untyped(),
6765                    &matrix,
6766                    &inv_matrix,
6767                    plane_split_anchor,
6768                ).unwrap();
6769                splitter.add(polygon);
6770            }
6771            CoordinateSpaceMapping::ScaleOffset(_) |
6772            CoordinateSpaceMapping::Transform(_) => {
6773                let mut clipper = Clipper::new();
6774                let results = clipper.clip_transformed(
6775                    Polygon::from_rect(
6776                        local_rect.to_rect().to_untyped(),
6777                        plane_split_anchor,
6778                    ),
6779                    &matrix,
6780                    Some(dirty_rect.to_rect().to_untyped()),
6781                );
6782                if let Ok(results) = results {
6783                    for poly in results {
6784                        splitter.add(poly);
6785                    }
6786                }
6787            }
6788        }
6789
6790        true
6791    }
6792
6793    fn resolve_split_planes(
6794        splitter: &mut PlaneSplitter,
6795        ordered: &mut Vec<OrderedPictureChild>,
6796        gpu_cache: &mut GpuCache,
6797        spatial_tree: &SpatialTree,
6798    ) {
6799        ordered.clear();
6800
6801        // Process the accumulated split planes and order them for rendering.
6802        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
6803        let sorted = splitter.sort(vec3(0.0, 0.0, 1.0));
6804        ordered.reserve(sorted.len());
6805        for poly in sorted {
6806            let transform = match spatial_tree
6807                .get_world_transform(poly.anchor.spatial_node_index)
6808                .inverse()
6809            {
6810                Some(transform) => transform.into_transform(),
6811                // logging this would be a bit too verbose
6812                None => continue,
6813            };
6814
6815            let local_points = [
6816                transform.transform_point3d(poly.points[0].cast_unit().to_f32()),
6817                transform.transform_point3d(poly.points[1].cast_unit().to_f32()),
6818                transform.transform_point3d(poly.points[2].cast_unit().to_f32()),
6819                transform.transform_point3d(poly.points[3].cast_unit().to_f32()),
6820            ];
6821
6822            // If any of the points are un-transformable, just drop this
6823            // plane from drawing.
6824            if local_points.iter().any(|p| p.is_none()) {
6825                continue;
6826            }
6827
6828            let p0 = local_points[0].unwrap();
6829            let p1 = local_points[1].unwrap();
6830            let p2 = local_points[2].unwrap();
6831            let p3 = local_points[3].unwrap();
6832            let gpu_blocks = [
6833                [p0.x, p0.y, p1.x, p1.y].into(),
6834                [p2.x, p2.y, p3.x, p3.y].into(),
6835            ];
6836            let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
6837            let gpu_address = gpu_cache.get_address(&gpu_handle);
6838
6839            ordered.push(OrderedPictureChild {
6840                anchor: poly.anchor,
6841                gpu_address,
6842            });
6843        }
6844    }
6845
6846    /// Do initial checks to determine whether this picture should be drawn as part of the
6847    /// frame build.
6848    pub fn pre_update(
6849        &mut self,
6850        frame_context: &FrameBuildingContext,
6851    ) {
6852        // Resolve animation properties
6853        self.resolve_scene_properties(frame_context.scene_properties);
6854    }
6855
6856    /// Called during initial picture traversal, before we know the
6857    /// bounding rect of children. It is possible to determine the
6858    /// surface / raster config now though.
6859    pub fn assign_surface(
6860        &mut self,
6861        frame_context: &FrameBuildingContext,
6862        parent_surface_index: Option<SurfaceIndex>,
6863        tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
6864        surfaces: &mut Vec<SurfaceInfo>,
6865    ) -> Option<SurfaceIndex> {
6866        // Reset raster config in case we early out below.
6867        self.raster_config = None;
6868
6869        match self.composite_mode {
6870            Some(ref composite_mode) => {
6871                let surface_spatial_node_index = self.spatial_node_index;
6872
6873                // Currently, we ensure that the scaling factor is >= 1.0 as a smaller scale factor can result in blurry output.
6874                let mut min_scale;
6875                let mut max_scale = 1.0e32;
6876
6877                // If a raster root is established, this surface should be scaled based on the scale factors of the surface raster to parent raster transform.
6878                // This scaling helps ensure that the content in this surface does not become blurry or pixelated when composited in the parent surface.
6879
6880                let world_scale_factors = match parent_surface_index {
6881                    Some(parent_surface_index) => {
6882                        let parent_surface = &surfaces[parent_surface_index.0];
6883
6884                        let local_to_surface = frame_context
6885                            .spatial_tree
6886                            .get_relative_transform(
6887                                surface_spatial_node_index,
6888                                parent_surface.surface_spatial_node_index,
6889                            );
6890
6891                        // Since we can't determine reasonable scale factors for transforms
6892                        // with perspective, just use a scale of (1,1) for now, which is
6893                        // what Gecko does when it choosed to supplies a scale factor anyway.
6894                        // In future, we might be able to improve the quality here by taking
6895                        // into account the screen rect after clipping, but for now this gives
6896                        // better results than just taking the matrix scale factors.
6897                        let scale_factors = if local_to_surface.is_perspective() {
6898                            (1.0, 1.0)
6899                        } else {
6900                            local_to_surface.scale_factors()
6901                        };
6902
6903                        let scale_factors = (
6904                            scale_factors.0 * parent_surface.world_scale_factors.0,
6905                            scale_factors.1 * parent_surface.world_scale_factors.1,
6906                        );
6907
6908                        scale_factors
6909                    }
6910                    None => {
6911                        let local_to_surface_scale_factors = frame_context
6912                            .spatial_tree
6913                            .get_relative_transform(
6914                                surface_spatial_node_index,
6915                                frame_context.spatial_tree.root_reference_frame_index(),
6916                            )
6917                            .scale_factors();
6918
6919                        let scale_factors = (
6920                            local_to_surface_scale_factors.0,
6921                            local_to_surface_scale_factors.1,
6922                        );
6923
6924                        scale_factors
6925                    }
6926                };
6927
6928                // TODO(gw): For now, we disable snapping on any sub-graph, as that implies
6929                //           that the spatial / raster node must be the same as the parent
6930                //           surface. In future, we may be able to support snapping in these
6931                //           cases (if it's even useful?) or perhaps add a ENABLE_SNAPPING
6932                //           picture flag, if the IS_SUB_GRAPH is ever useful in a different
6933                //           context.
6934                let allow_snapping = !self.flags.contains(PictureFlags::DISABLE_SNAPPING);
6935
6936                // For some primitives (e.g. text runs) we can't rely on the bounding rect being
6937                // exactly correct. For these cases, ensure we set a scissor rect when drawing
6938                // this picture to a surface.
6939                // TODO(gw) In future, we may be able to improve how the text run bounding rect is
6940                // calculated so that we don't need to do this. We could either fix Gecko up to
6941                // provide an exact bounds, or we could calculate the bounding rect internally in
6942                // WR, which would be easier to do efficiently once we have retained text runs
6943                // as part of the planned frame-tree interface changes.
6944                let force_scissor_rect = self.prim_list.needs_scissor_rect;
6945
6946                // Check if there is perspective or if an SVG filter is applied, and thus whether a new
6947                // rasterization root should be established.
6948                let (device_pixel_scale, raster_spatial_node_index, local_scale, world_scale_factors) = match composite_mode {
6949                    PictureCompositeMode::TileCache { slice_id } => {
6950                        let tile_cache = tile_caches.get_mut(&slice_id).unwrap();
6951
6952                        // Get the complete scale-offset from local space to device space
6953                        let local_to_device = get_relative_scale_offset(
6954                            tile_cache.spatial_node_index,
6955                            frame_context.root_spatial_node_index,
6956                            frame_context.spatial_tree,
6957                        );
6958                        let local_to_cur_raster_scale = local_to_device.scale.x / tile_cache.current_raster_scale;
6959
6960                        // We only update the raster scale if we're in high quality zoom mode, or there is no
6961                        // pinch-zoom active, or the zoom has doubled or halved since the raster scale was
6962                        // last updated. During a low-quality zoom we therefore typically retain the previous
6963                        // scale factor, which avoids expensive re-rasterizations, except for when the zoom
6964                        // has become too large or too small when we re-rasterize to avoid bluriness or a
6965                        // proliferation of picture cache tiles. When the zoom ends we select a high quality
6966                        // scale factor for the next frame to be drawn.
6967                        if !frame_context.fb_config.low_quality_pinch_zoom
6968                            || !frame_context
6969                                .spatial_tree.get_spatial_node(tile_cache.spatial_node_index)
6970                                .is_ancestor_or_self_zooming
6971                            || local_to_cur_raster_scale <= 0.5
6972                            || local_to_cur_raster_scale >= 2.0
6973                        {
6974                            tile_cache.current_raster_scale = local_to_device.scale.x;
6975                        }
6976
6977                        // We may need to minify when zooming out picture cache tiles
6978                        min_scale = 0.0;
6979
6980                        if frame_context.fb_config.low_quality_pinch_zoom {
6981                            // Force the scale for this tile cache to be the currently selected
6982                            // local raster scale, so we don't need to rasterize tiles during
6983                            // the pinch-zoom.
6984                            min_scale = tile_cache.current_raster_scale;
6985                            max_scale = tile_cache.current_raster_scale;
6986                        }
6987
6988                        // Pick the largest scale factor of the transform for the scaling factor.
6989                        let scaling_factor = world_scale_factors.0.max(world_scale_factors.1).max(min_scale).min(max_scale);
6990
6991                        let device_pixel_scale = Scale::new(scaling_factor);
6992
6993                        (device_pixel_scale, surface_spatial_node_index, (1.0, 1.0), world_scale_factors)
6994                    }
6995                    _ => {
6996                        let surface_spatial_node = frame_context.spatial_tree.get_spatial_node(surface_spatial_node_index);
6997
6998                        let enable_snapping =
6999                            allow_snapping &&
7000                            surface_spatial_node.coordinate_system_id == CoordinateSystemId::root() &&
7001                            surface_spatial_node.snapping_transform.is_some();
7002
7003                        if enable_snapping {
7004                            let raster_spatial_node_index = frame_context.spatial_tree.root_reference_frame_index();
7005
7006                            let local_to_raster_transform = frame_context
7007                                .spatial_tree
7008                                .get_relative_transform(
7009                                    self.spatial_node_index,
7010                                    raster_spatial_node_index,
7011                                );
7012
7013                            let local_scale = local_to_raster_transform.scale_factors();
7014
7015                            (Scale::new(1.0), raster_spatial_node_index, local_scale, (1.0, 1.0))
7016                        } else {
7017                            // If client supplied a specific local scale, use that instead of
7018                            // estimating from parent transform
7019                            let world_scale_factors = match self.raster_space {
7020                                RasterSpace::Screen => world_scale_factors,
7021                                RasterSpace::Local(scale) => (scale, scale),
7022                            };
7023
7024                            let device_pixel_scale = Scale::new(
7025                                world_scale_factors.0.max(world_scale_factors.1).min(max_scale)
7026                            );
7027
7028                            (device_pixel_scale, surface_spatial_node_index, (1.0, 1.0), world_scale_factors)
7029                        }
7030                    }
7031                };
7032
7033                let surface = SurfaceInfo::new(
7034                    surface_spatial_node_index,
7035                    raster_spatial_node_index,
7036                    frame_context.global_screen_world_rect,
7037                    &frame_context.spatial_tree,
7038                    device_pixel_scale,
7039                    world_scale_factors,
7040                    local_scale,
7041                    allow_snapping,
7042                    force_scissor_rect,
7043                );
7044
7045                let surface_index = SurfaceIndex(surfaces.len());
7046
7047                surfaces.push(surface);
7048
7049                self.raster_config = Some(RasterConfig {
7050                    composite_mode: composite_mode.clone(),
7051                    surface_index,
7052                });
7053
7054                Some(surface_index)
7055            }
7056            None => {
7057                None
7058            }
7059        }
7060    }
7061
7062    /// Called after updating child pictures during the initial
7063    /// picture traversal. Bounding rects are propagated from
7064    /// child pictures up to parent picture surfaces, so that the
7065    /// parent bounding rect includes any dynamic picture bounds.
7066    pub fn propagate_bounding_rect(
7067        &mut self,
7068        surface_index: SurfaceIndex,
7069        parent_surface_index: Option<SurfaceIndex>,
7070        surfaces: &mut [SurfaceInfo],
7071        frame_context: &FrameBuildingContext,
7072    ) {
7073        let surface = &mut surfaces[surface_index.0];
7074
7075        for cluster in &mut self.prim_list.clusters {
7076            cluster.flags.remove(ClusterFlags::IS_VISIBLE);
7077
7078            // Skip the cluster if backface culled.
7079            if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) {
7080                // For in-preserve-3d primitives and pictures, the backface visibility is
7081                // evaluated relative to the containing block.
7082                if let Picture3DContext::In { ancestor_index, .. } = self.context_3d {
7083                    let mut face = VisibleFace::Front;
7084                    frame_context.spatial_tree.get_relative_transform_with_face(
7085                        cluster.spatial_node_index,
7086                        ancestor_index,
7087                        Some(&mut face),
7088                    );
7089                    if face == VisibleFace::Back {
7090                        continue
7091                    }
7092                }
7093            }
7094
7095            // No point including this cluster if it can't be transformed
7096            let spatial_node = &frame_context
7097                .spatial_tree
7098                .get_spatial_node(cluster.spatial_node_index);
7099            if !spatial_node.invertible {
7100                continue;
7101            }
7102
7103            // Map the cluster bounding rect into the space of the surface, and
7104            // include it in the surface bounding rect.
7105            surface.map_local_to_picture.set_target_spatial_node(
7106                cluster.spatial_node_index,
7107                frame_context.spatial_tree,
7108            );
7109
7110            // Mark the cluster visible, since it passed the invertible and
7111            // backface checks.
7112            cluster.flags.insert(ClusterFlags::IS_VISIBLE);
7113            if let Some(cluster_rect) = surface.map_local_to_picture.map(&cluster.bounding_rect) {
7114                surface.unclipped_local_rect = surface.unclipped_local_rect.union(&cluster_rect);
7115            }
7116        }
7117
7118        // If this picture establishes a surface, then map the surface bounding
7119        // rect into the parent surface coordinate space, and propagate that up
7120        // to the parent.
7121        if let Some(ref mut raster_config) = self.raster_config {
7122            // Propagate up to parent surface, now that we know this surface's static rect
7123            if let Some(parent_surface_index) = parent_surface_index {
7124                let surface_rect = raster_config.composite_mode.get_coverage(
7125                    surface,
7126                    Some(surface.unclipped_local_rect.cast_unit()),
7127                );
7128
7129                let parent_surface = &mut surfaces[parent_surface_index.0];
7130                parent_surface.map_local_to_picture.set_target_spatial_node(
7131                    self.spatial_node_index,
7132                    frame_context.spatial_tree,
7133                );
7134
7135                // Drop shadows draw both a content and shadow rect, so need to expand the local
7136                // rect of any surfaces to be composited in parent surfaces correctly.
7137
7138                if let Some(parent_surface_rect) = parent_surface
7139                    .map_local_to_picture
7140                    .map(&surface_rect)
7141                {
7142                    parent_surface.unclipped_local_rect =
7143                        parent_surface.unclipped_local_rect.union(&parent_surface_rect);
7144                }
7145            }
7146        }
7147    }
7148
7149    pub fn prepare_for_render(
7150        &mut self,
7151        frame_state: &mut FrameBuildingState,
7152        data_stores: &mut DataStores,
7153    ) -> bool {
7154        let raster_config = match self.raster_config {
7155            Some(ref mut raster_config) => raster_config,
7156            None => {
7157                return true
7158            }
7159        };
7160
7161        // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
7162        //           to store the same type of data. The exception is the filter
7163        //           with a ColorMatrix, which stores the color matrix here. It's
7164        //           probably worth tidying this code up to be a bit more consistent.
7165        //           Perhaps store the color matrix after the common data, even though
7166        //           it's not used by that shader.
7167
7168        match raster_config.composite_mode {
7169            PictureCompositeMode::TileCache { .. } => {}
7170            PictureCompositeMode::Filter(Filter::Blur { .. }) => {}
7171            PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
7172                self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
7173                for (shadow, extra_handle) in shadows.iter().zip(self.extra_gpu_data_handles.iter_mut()) {
7174                    if let Some(mut request) = frame_state.gpu_cache.request(extra_handle) {
7175                        let surface = &frame_state.surfaces[raster_config.surface_index.0];
7176                        let prim_rect = surface.clipped_local_rect.cast_unit();
7177
7178                        // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
7179                        //  [brush specific data]
7180                        //  [segment_rect, segment data]
7181                        let (blur_inflation_x, blur_inflation_y) = surface.clamp_blur_radius(
7182                            shadow.blur_radius,
7183                            shadow.blur_radius,
7184                        );
7185
7186                        let shadow_rect = prim_rect.inflate(
7187                            blur_inflation_x * BLUR_SAMPLE_SCALE,
7188                            blur_inflation_y * BLUR_SAMPLE_SCALE,
7189                        ).translate(shadow.offset);
7190
7191                        // ImageBrush colors
7192                        request.push(shadow.color.premultiplied());
7193                        request.push(PremultipliedColorF::WHITE);
7194                        request.push([
7195                            shadow_rect.width(),
7196                            shadow_rect.height(),
7197                            0.0,
7198                            0.0,
7199                        ]);
7200
7201                        // segment rect / extra data
7202                        request.push(shadow_rect);
7203                        request.push([0.0, 0.0, 0.0, 0.0]);
7204                    }
7205                }
7206            }
7207            PictureCompositeMode::Filter(ref filter) => {
7208                match *filter {
7209                    Filter::ColorMatrix(ref m) => {
7210                        if self.extra_gpu_data_handles.is_empty() {
7211                            self.extra_gpu_data_handles.push(GpuCacheHandle::new());
7212                        }
7213                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
7214                            for i in 0..5 {
7215                                request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
7216                            }
7217                        }
7218                    }
7219                    Filter::Flood(ref color) => {
7220                        if self.extra_gpu_data_handles.is_empty() {
7221                            self.extra_gpu_data_handles.push(GpuCacheHandle::new());
7222                        }
7223                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
7224                            request.push(color.to_array());
7225                        }
7226                    }
7227                    _ => {}
7228                }
7229            }
7230            PictureCompositeMode::ComponentTransferFilter(handle) => {
7231                let filter_data = &mut data_stores.filter_data[handle];
7232                filter_data.update(&mut frame_state.gpu_cache);
7233            }
7234            PictureCompositeMode::MixBlend(..) |
7235            PictureCompositeMode::Blit(_) |
7236            PictureCompositeMode::IntermediateSurface |
7237            PictureCompositeMode::SvgFilter(..) => {}
7238            PictureCompositeMode::SVGFEGraph(ref filters) => {
7239                // Update interned filter data
7240                for (_node, op) in filters {
7241                    match op {
7242                        FilterGraphOp::SVGFEComponentTransferInterned { handle, creates_pixels: _ } => {
7243                            let filter_data = &mut data_stores.filter_data[*handle];
7244                            filter_data.update(&mut frame_state.gpu_cache);
7245                        }
7246                        _ => {}
7247                    }
7248                }
7249            }
7250        }
7251
7252        true
7253    }
7254
7255    #[cold]
7256    fn draw_debug_overlay(
7257        &self,
7258        parent_surface_index: Option<SurfaceIndex>,
7259        frame_state: &FrameBuildingState,
7260        frame_context: &FrameBuildingContext,
7261        tile_caches: &FastHashMap<SliceId, Box<TileCacheInstance>>,
7262        scratch: &mut PrimitiveScratchBuffer,
7263    ) {
7264        fn draw_debug_border(
7265            local_rect: &PictureRect,
7266            thickness: i32,
7267            pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
7268            global_device_pixel_scale: DevicePixelScale,
7269            color: ColorF,
7270            scratch: &mut PrimitiveScratchBuffer,
7271        ) {
7272            if let Some(world_rect) = pic_to_world_mapper.map(&local_rect) {
7273                let device_rect = world_rect * global_device_pixel_scale;
7274                scratch.push_debug_rect(
7275                    device_rect,
7276                    thickness,
7277                    color,
7278                    ColorF::TRANSPARENT,
7279                );
7280            }
7281        }
7282
7283        let flags = frame_context.debug_flags;
7284        let draw_borders = flags.contains(DebugFlags::PICTURE_BORDERS);
7285        let draw_tile_dbg = flags.contains(DebugFlags::PICTURE_CACHING_DBG);
7286
7287        let surface_index = match &self.raster_config {
7288            Some(raster_config) => raster_config.surface_index,
7289            None => parent_surface_index.expect("bug: no parent"),
7290        };
7291        let surface_spatial_node_index = frame_state
7292            .surfaces[surface_index.0]
7293            .surface_spatial_node_index;
7294
7295        let map_pic_to_world = SpaceMapper::new_with_target(
7296            frame_context.root_spatial_node_index,
7297            surface_spatial_node_index,
7298            frame_context.global_screen_world_rect,
7299            frame_context.spatial_tree,
7300        );
7301
7302        let Some(raster_config) = &self.raster_config else {
7303            return;
7304        };
7305
7306        if draw_borders {
7307            let layer_color;
7308            if let PictureCompositeMode::TileCache { slice_id } = &raster_config.composite_mode {
7309                // Tiled picture;
7310                layer_color = ColorF::new(0.0, 1.0, 0.0, 0.8);
7311
7312                let Some(tile_cache) = tile_caches.get(&slice_id) else {
7313                    return;
7314                };
7315
7316                // Draw a rectangle for each tile.
7317                for (_, sub_slice) in tile_cache.sub_slices.iter().enumerate() {
7318                    for tile in sub_slice.tiles.values() {
7319                        if !tile.is_visible {
7320                            continue;
7321                        }
7322                        let rect = tile.local_tile_rect.intersection(&tile_cache.local_rect);
7323                        if let Some(rect) = rect {
7324                            draw_debug_border(
7325                                &rect,
7326                                1,
7327                                &map_pic_to_world,
7328                                frame_context.global_device_pixel_scale,
7329                                ColorF::new(0.0, 1.0, 0.0, 0.2),
7330                                scratch,
7331                            );
7332                        }
7333                    }
7334                }
7335            } else {
7336                // Non-tiled picture
7337                layer_color = ColorF::new(1.0, 0.0, 0.0, 0.5);
7338            }
7339
7340            // Draw a rectangle for the whole picture.
7341            let pic_rect = frame_state
7342                .surfaces[raster_config.surface_index.0]
7343                .unclipped_local_rect;
7344
7345            draw_debug_border(
7346                &pic_rect,
7347                3,
7348                &map_pic_to_world,
7349                frame_context.global_device_pixel_scale,
7350                layer_color,
7351                scratch,
7352            );
7353        }
7354
7355        if draw_tile_dbg && self.is_visible(frame_context.spatial_tree) {
7356            if let PictureCompositeMode::TileCache { slice_id } = &raster_config.composite_mode {
7357                let Some(tile_cache) = tile_caches.get(&slice_id) else {
7358                    return;
7359                };
7360                for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter().enumerate() {
7361                    for tile in sub_slice.tiles.values() {
7362                        if !tile.is_visible {
7363                            continue;
7364                        }
7365                        tile.root.draw_debug_rects(
7366                            &map_pic_to_world,
7367                            tile.is_opaque,
7368                            tile.current_descriptor.local_valid_rect,
7369                            scratch,
7370                            frame_context.global_device_pixel_scale,
7371                        );
7372
7373                        let label_offset = DeviceVector2D::new(
7374                            20.0 + sub_slice_index as f32 * 20.0,
7375                            30.0 + sub_slice_index as f32 * 20.0,
7376                        );
7377                        let tile_device_rect = tile.world_tile_rect
7378                            * frame_context.global_device_pixel_scale;
7379
7380                        if tile_device_rect.height() >= label_offset.y {
7381                            let surface = tile.surface.as_ref().expect("no tile surface set!");
7382
7383                            scratch.push_debug_string(
7384                                tile_device_rect.min + label_offset,
7385                                debug_colors::RED,
7386                                format!("{:?}: s={} is_opaque={} surface={} sub={}",
7387                                        tile.id,
7388                                        tile_cache.slice,
7389                                        tile.is_opaque,
7390                                        surface.kind(),
7391                                        sub_slice_index,
7392                                ),
7393                            );
7394                        }
7395                    }
7396                }
7397            }
7398        }
7399    }
7400}
7401
7402fn get_transform_key(
7403    spatial_node_index: SpatialNodeIndex,
7404    cache_spatial_node_index: SpatialNodeIndex,
7405    spatial_tree: &SpatialTree,
7406) -> TransformKey {
7407    spatial_tree.get_relative_transform(
7408        spatial_node_index,
7409        cache_spatial_node_index,
7410    ).into()
7411}
7412
7413/// A key for storing primitive comparison results during tile dependency tests.
7414#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
7415struct PrimitiveComparisonKey {
7416    prev_index: PrimitiveDependencyIndex,
7417    curr_index: PrimitiveDependencyIndex,
7418}
7419
7420/// Information stored an image dependency
7421#[derive(Debug, Copy, Clone, PartialEq, PeekPoke, Default)]
7422#[cfg_attr(feature = "capture", derive(Serialize))]
7423#[cfg_attr(feature = "replay", derive(Deserialize))]
7424pub struct ImageDependency {
7425    pub key: ImageKey,
7426    pub generation: ImageGeneration,
7427}
7428
7429impl ImageDependency {
7430    pub const INVALID: ImageDependency = ImageDependency {
7431        key: ImageKey::DUMMY,
7432        generation: ImageGeneration::INVALID,
7433    };
7434}
7435
7436/// In some cases, we need to know the dirty rect of all tiles in order
7437/// to correctly invalidate a primitive.
7438#[derive(Debug)]
7439struct DeferredDirtyTest {
7440    /// The tile rect that the primitive being checked affects
7441    tile_rect: TileRect,
7442    /// The picture-cache local rect of the primitive being checked
7443    prim_rect: PictureRect,
7444}
7445
7446/// A helper struct to compare a primitive and all its sub-dependencies.
7447struct PrimitiveComparer<'a> {
7448    prev_data: &'a [u8],
7449    curr_data: &'a [u8],
7450    prev_frame_id: FrameId,
7451    curr_frame_id: FrameId,
7452    resource_cache: &'a ResourceCache,
7453    spatial_node_comparer: &'a mut SpatialNodeComparer,
7454    opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
7455    color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
7456}
7457
7458impl<'a> PrimitiveComparer<'a> {
7459    fn new(
7460        prev: &'a TileDescriptor,
7461        curr: &'a TileDescriptor,
7462        resource_cache: &'a ResourceCache,
7463        spatial_node_comparer: &'a mut SpatialNodeComparer,
7464        opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
7465        color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
7466    ) -> Self {
7467        PrimitiveComparer {
7468            prev_data: &prev.dep_data,
7469            curr_data: &curr.dep_data,
7470            prev_frame_id: prev.last_updated_frame_id,
7471            curr_frame_id: curr.last_updated_frame_id,
7472            resource_cache,
7473            spatial_node_comparer,
7474            opacity_bindings,
7475            color_bindings,
7476        }
7477    }
7478
7479    /// Check if two primitive descriptors are the same.
7480    fn compare_prim(
7481        &mut self,
7482        prev_desc: &PrimitiveDescriptor,
7483        curr_desc: &PrimitiveDescriptor,
7484    ) -> PrimitiveCompareResult {
7485        let resource_cache = self.resource_cache;
7486        let spatial_node_comparer = &mut self.spatial_node_comparer;
7487        let opacity_bindings = self.opacity_bindings;
7488        let color_bindings = self.color_bindings;
7489
7490        // Check equality of the PrimitiveDescriptor
7491        if prev_desc != curr_desc {
7492            return PrimitiveCompareResult::Descriptor;
7493        }
7494
7495        let mut prev_dep_data = &self.prev_data[prev_desc.dep_offset as usize ..];
7496        let mut curr_dep_data = &self.curr_data[curr_desc.dep_offset as usize ..];
7497
7498        let mut prev_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID };
7499        let mut curr_dep = PrimitiveDependency::SpatialNode { index: SpatialNodeIndex::INVALID };
7500
7501        debug_assert_eq!(prev_desc.dep_count, curr_desc.dep_count);
7502
7503        for _ in 0 .. prev_desc.dep_count {
7504            prev_dep_data = peek_from_slice(prev_dep_data, &mut prev_dep);
7505            curr_dep_data = peek_from_slice(curr_dep_data, &mut curr_dep);
7506
7507            match (&prev_dep, &curr_dep) {
7508                (PrimitiveDependency::Clip { clip: prev }, PrimitiveDependency::Clip { clip: curr }) => {
7509                    if prev != curr {
7510                        return PrimitiveCompareResult::Clip;
7511                    }
7512                }
7513                (PrimitiveDependency::SpatialNode { index: prev }, PrimitiveDependency::SpatialNode { index: curr }) => {
7514                    let prev_key = SpatialNodeKey {
7515                        spatial_node_index: *prev,
7516                        frame_id: self.prev_frame_id,
7517                    };
7518                    let curr_key = SpatialNodeKey {
7519                        spatial_node_index: *curr,
7520                        frame_id: self.curr_frame_id,
7521                    };
7522                    if !spatial_node_comparer.are_transforms_equivalent(&prev_key, &curr_key) {
7523                        return PrimitiveCompareResult::Transform;
7524                    }
7525                }
7526                (PrimitiveDependency::OpacityBinding { binding: prev }, PrimitiveDependency::OpacityBinding { binding: curr }) => {
7527                    if prev != curr {
7528                        return PrimitiveCompareResult::OpacityBinding;
7529                    }
7530
7531                    if let OpacityBinding::Binding(id) = curr {
7532                        if opacity_bindings
7533                            .get(id)
7534                            .map_or(true, |info| info.changed) {
7535                            return PrimitiveCompareResult::OpacityBinding;
7536                        }
7537                    }
7538                }
7539                (PrimitiveDependency::ColorBinding { binding: prev }, PrimitiveDependency::ColorBinding { binding: curr }) => {
7540                    if prev != curr {
7541                        return PrimitiveCompareResult::ColorBinding;
7542                    }
7543
7544                    if let ColorBinding::Binding(id) = curr {
7545                        if color_bindings
7546                            .get(id)
7547                            .map_or(true, |info| info.changed) {
7548                            return PrimitiveCompareResult::ColorBinding;
7549                        }
7550                    }
7551                }
7552                (PrimitiveDependency::Image { image: prev }, PrimitiveDependency::Image { image: curr }) => {
7553                    if prev != curr {
7554                        return PrimitiveCompareResult::Image;
7555                    }
7556
7557                    if resource_cache.get_image_generation(curr.key) != curr.generation {
7558                        return PrimitiveCompareResult::Image;
7559                    }
7560                }
7561                _ => {
7562                    // There was a mismatch between types of dependencies, so something changed
7563                    return PrimitiveCompareResult::Descriptor;
7564                }
7565            }
7566        }
7567
7568        PrimitiveCompareResult::Equal
7569    }
7570}
7571
7572/// Details for a node in a quadtree that tracks dirty rects for a tile.
7573#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
7574#[cfg_attr(feature = "capture", derive(Serialize))]
7575#[cfg_attr(feature = "replay", derive(Deserialize))]
7576pub enum TileNodeKind {
7577    Leaf {
7578        /// The index buffer of primitives that affected this tile previous frame
7579        #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
7580        prev_indices: Vec<PrimitiveDependencyIndex>,
7581        /// The index buffer of primitives that affect this tile on this frame
7582        #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
7583        curr_indices: Vec<PrimitiveDependencyIndex>,
7584        /// A bitset of which of the last 64 frames have been dirty for this leaf.
7585        #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
7586        dirty_tracker: u64,
7587        /// The number of frames since this node split or merged.
7588        #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
7589        frames_since_modified: usize,
7590    },
7591    Node {
7592        /// The four children of this node
7593        children: Vec<TileNode>,
7594    },
7595}
7596
7597/// The kind of modification that a tile wants to do
7598#[derive(Copy, Clone, PartialEq, Debug)]
7599enum TileModification {
7600    Split,
7601    Merge,
7602}
7603
7604/// A node in the dirty rect tracking quadtree.
7605#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
7606#[cfg_attr(feature = "capture", derive(Serialize))]
7607#[cfg_attr(feature = "replay", derive(Deserialize))]
7608pub struct TileNode {
7609    /// Leaf or internal node
7610    pub kind: TileNodeKind,
7611    /// Rect of this node in the same space as the tile cache picture
7612    pub rect: PictureBox2D,
7613}
7614
7615impl TileNode {
7616    /// Construct a new leaf node, with the given primitive dependency index buffer
7617    fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self {
7618        TileNode {
7619            kind: TileNodeKind::Leaf {
7620                prev_indices: Vec::new(),
7621                curr_indices,
7622                dirty_tracker: 0,
7623                frames_since_modified: 0,
7624            },
7625            rect: PictureBox2D::zero(),
7626        }
7627    }
7628
7629    /// Draw debug information about this tile node
7630    fn draw_debug_rects(
7631        &self,
7632        pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
7633        is_opaque: bool,
7634        local_valid_rect: PictureRect,
7635        scratch: &mut PrimitiveScratchBuffer,
7636        global_device_pixel_scale: DevicePixelScale,
7637    ) {
7638        match self.kind {
7639            TileNodeKind::Leaf { dirty_tracker, .. } => {
7640                let color = if (dirty_tracker & 1) != 0 {
7641                    debug_colors::RED
7642                } else if is_opaque {
7643                    debug_colors::GREEN
7644                } else {
7645                    debug_colors::YELLOW
7646                };
7647
7648                if let Some(local_rect) = local_valid_rect.intersection(&self.rect) {
7649                    let world_rect = pic_to_world_mapper
7650                        .map(&local_rect)
7651                        .unwrap();
7652                    let device_rect = world_rect * global_device_pixel_scale;
7653
7654                    let outer_color = color.scale_alpha(0.3);
7655                    let inner_color = outer_color.scale_alpha(0.5);
7656                    scratch.push_debug_rect(
7657                        device_rect.inflate(-3.0, -3.0),
7658                        1,
7659                        outer_color,
7660                        inner_color
7661                    );
7662                }
7663            }
7664            TileNodeKind::Node { ref children, .. } => {
7665                for child in children.iter() {
7666                    child.draw_debug_rects(
7667                        pic_to_world_mapper,
7668                        is_opaque,
7669                        local_valid_rect,
7670                        scratch,
7671                        global_device_pixel_scale,
7672                    );
7673                }
7674            }
7675        }
7676    }
7677
7678    /// Calculate the four child rects for a given node
7679    fn get_child_rects(
7680        rect: &PictureBox2D,
7681        result: &mut [PictureBox2D; 4],
7682    ) {
7683        let p0 = rect.min;
7684        let p1 = rect.max;
7685        let pc = p0 + rect.size() * 0.5;
7686
7687        *result = [
7688            PictureBox2D::new(
7689                p0,
7690                pc,
7691            ),
7692            PictureBox2D::new(
7693                PicturePoint::new(pc.x, p0.y),
7694                PicturePoint::new(p1.x, pc.y),
7695            ),
7696            PictureBox2D::new(
7697                PicturePoint::new(p0.x, pc.y),
7698                PicturePoint::new(pc.x, p1.y),
7699            ),
7700            PictureBox2D::new(
7701                pc,
7702                p1,
7703            ),
7704        ];
7705    }
7706
7707    /// Called during pre_update, to clear the current dependencies
7708    fn clear(
7709        &mut self,
7710        rect: PictureBox2D,
7711    ) {
7712        self.rect = rect;
7713
7714        match self.kind {
7715            TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => {
7716                // Swap current dependencies to be the previous frame
7717                mem::swap(prev_indices, curr_indices);
7718                curr_indices.clear();
7719                // Note that another frame has passed in the dirty bit trackers
7720                *dirty_tracker = *dirty_tracker << 1;
7721                *frames_since_modified += 1;
7722            }
7723            TileNodeKind::Node { ref mut children, .. } => {
7724                let mut child_rects = [PictureBox2D::zero(); 4];
7725                TileNode::get_child_rects(&rect, &mut child_rects);
7726                assert_eq!(child_rects.len(), children.len());
7727
7728                for (child, rect) in children.iter_mut().zip(child_rects.iter()) {
7729                    child.clear(*rect);
7730                }
7731            }
7732        }
7733    }
7734
7735    /// Add a primitive dependency to this node
7736    fn add_prim(
7737        &mut self,
7738        index: PrimitiveDependencyIndex,
7739        prim_rect: &PictureBox2D,
7740    ) {
7741        match self.kind {
7742            TileNodeKind::Leaf { ref mut curr_indices, .. } => {
7743                curr_indices.push(index);
7744            }
7745            TileNodeKind::Node { ref mut children, .. } => {
7746                for child in children.iter_mut() {
7747                    if child.rect.intersects(prim_rect) {
7748                        child.add_prim(index, prim_rect);
7749                    }
7750                }
7751            }
7752        }
7753    }
7754
7755    /// Apply a merge or split operation to this tile, if desired
7756    fn maybe_merge_or_split(
7757        &mut self,
7758        level: i32,
7759        curr_prims: &[PrimitiveDescriptor],
7760        max_split_levels: i32,
7761    ) {
7762        // Determine if this tile wants to split or merge
7763        let mut tile_mod = None;
7764
7765        fn get_dirty_frames(
7766            dirty_tracker: u64,
7767            frames_since_modified: usize,
7768        ) -> Option<u32> {
7769            // Only consider splitting or merging at least 64 frames since we last changed
7770            if frames_since_modified > 64 {
7771                // Each bit in the tracker is a frame that was recently invalidated
7772                Some(dirty_tracker.count_ones())
7773            } else {
7774                None
7775            }
7776        }
7777
7778        match self.kind {
7779            TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => {
7780                // Only consider splitting if the tree isn't too deep.
7781                if level < max_split_levels {
7782                    if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
7783                        // If the tile has invalidated > 50% of the recent number of frames, split.
7784                        if dirty_frames > 32 {
7785                            tile_mod = Some(TileModification::Split);
7786                        }
7787                    }
7788                }
7789            }
7790            TileNodeKind::Node { ref children, .. } => {
7791                // There's two conditions that cause a node to merge its children:
7792                // (1) If _all_ the child nodes are constantly invalidating, then we are wasting
7793                //     CPU time tracking dependencies for each child, so merge them.
7794                // (2) If _none_ of the child nodes are recently invalid, then the page content
7795                //     has probably changed, and we no longer need to track fine grained dependencies here.
7796
7797                let mut static_count = 0;
7798                let mut changing_count = 0;
7799
7800                for child in children {
7801                    // Only consider merging nodes at the edge of the tree.
7802                    if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind {
7803                        if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
7804                            if dirty_frames == 0 {
7805                                // Hasn't been invalidated for some time
7806                                static_count += 1;
7807                            } else if dirty_frames == 64 {
7808                                // Is constantly being invalidated
7809                                changing_count += 1;
7810                            }
7811                        }
7812                    }
7813
7814                    // Only merge if all the child tiles are in agreement. Otherwise, we have some
7815                    // that are invalidating / static, and it's worthwhile tracking dependencies for
7816                    // them individually.
7817                    if static_count == 4 || changing_count == 4 {
7818                        tile_mod = Some(TileModification::Merge);
7819                    }
7820                }
7821            }
7822        }
7823
7824        match tile_mod {
7825            Some(TileModification::Split) => {
7826                // To split a node, take the current dependency index buffer for this node, and
7827                // split it into child index buffers.
7828                let curr_indices = match self.kind {
7829                    TileNodeKind::Node { .. } => {
7830                        unreachable!("bug - only leaves can split");
7831                    }
7832                    TileNodeKind::Leaf { ref mut curr_indices, .. } => {
7833                        curr_indices.take()
7834                    }
7835                };
7836
7837                let mut child_rects = [PictureBox2D::zero(); 4];
7838                TileNode::get_child_rects(&self.rect, &mut child_rects);
7839
7840                let mut child_indices = [
7841                    Vec::new(),
7842                    Vec::new(),
7843                    Vec::new(),
7844                    Vec::new(),
7845                ];
7846
7847                // Step through the index buffer, and add primitives to each of the children
7848                // that they intersect.
7849                for index in curr_indices {
7850                    let prim = &curr_prims[index.0 as usize];
7851                    for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) {
7852                        if prim.prim_clip_box.intersects(child_rect) {
7853                            indices.push(index);
7854                        }
7855                    }
7856                }
7857
7858                // Create the child nodes and switch from leaf -> node.
7859                let children = child_indices
7860                    .iter_mut()
7861                    .map(|i| TileNode::new_leaf(mem::replace(i, Vec::new())))
7862                    .collect();
7863
7864                self.kind = TileNodeKind::Node {
7865                    children,
7866                };
7867            }
7868            Some(TileModification::Merge) => {
7869                // Construct a merged index buffer by collecting the dependency index buffers
7870                // from each child, and merging them into a de-duplicated index buffer.
7871                let merged_indices = match self.kind {
7872                    TileNodeKind::Node { ref mut children, .. } => {
7873                        let mut merged_indices = Vec::new();
7874
7875                        for child in children.iter() {
7876                            let child_indices = match child.kind {
7877                                TileNodeKind::Leaf { ref curr_indices, .. } => {
7878                                    curr_indices
7879                                }
7880                                TileNodeKind::Node { .. } => {
7881                                    unreachable!("bug: child is not a leaf");
7882                                }
7883                            };
7884                            merged_indices.extend_from_slice(child_indices);
7885                        }
7886
7887                        merged_indices.sort();
7888                        merged_indices.dedup();
7889
7890                        merged_indices
7891                    }
7892                    TileNodeKind::Leaf { .. } => {
7893                        unreachable!("bug - trying to merge a leaf");
7894                    }
7895                };
7896
7897                // Switch from a node to a leaf, with the combined index buffer
7898                self.kind = TileNodeKind::Leaf {
7899                    prev_indices: Vec::new(),
7900                    curr_indices: merged_indices,
7901                    dirty_tracker: 0,
7902                    frames_since_modified: 0,
7903                };
7904            }
7905            None => {
7906                // If this node didn't merge / split, then recurse into children
7907                // to see if they want to split / merge.
7908                if let TileNodeKind::Node { ref mut children, .. } = self.kind {
7909                    for child in children.iter_mut() {
7910                        child.maybe_merge_or_split(
7911                            level+1,
7912                            curr_prims,
7913                            max_split_levels,
7914                        );
7915                    }
7916                }
7917            }
7918        }
7919    }
7920
7921    /// Update the dirty state of this node, building the overall dirty rect
7922    fn update_dirty_rects(
7923        &mut self,
7924        prev_prims: &[PrimitiveDescriptor],
7925        curr_prims: &[PrimitiveDescriptor],
7926        prim_comparer: &mut PrimitiveComparer,
7927        dirty_rect: &mut PictureBox2D,
7928        compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
7929        invalidation_reason: &mut Option<InvalidationReason>,
7930        frame_context: &FrameVisibilityContext,
7931    ) {
7932        match self.kind {
7933            TileNodeKind::Node { ref mut children, .. } => {
7934                for child in children.iter_mut() {
7935                    child.update_dirty_rects(
7936                        prev_prims,
7937                        curr_prims,
7938                        prim_comparer,
7939                        dirty_rect,
7940                        compare_cache,
7941                        invalidation_reason,
7942                        frame_context,
7943                    );
7944                }
7945            }
7946            TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => {
7947                // If the index buffers are of different length, they must be different
7948                if prev_indices.len() == curr_indices.len() {
7949                    // Walk each index buffer, comparing primitives
7950                    for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) {
7951                        let i0 = prev_index.0 as usize;
7952                        let i1 = curr_index.0 as usize;
7953
7954                        // Compare the primitives, caching the result in a hash map
7955                        // to save comparisons in other tree nodes.
7956                        let key = PrimitiveComparisonKey {
7957                            prev_index: *prev_index,
7958                            curr_index: *curr_index,
7959                        };
7960
7961                        let prim_compare_result = *compare_cache
7962                            .entry(key)
7963                            .or_insert_with(|| {
7964                                let prev = &prev_prims[i0];
7965                                let curr = &curr_prims[i1];
7966                                prim_comparer.compare_prim(prev, curr)
7967                            });
7968
7969                        // If not the same, mark this node as dirty and update the dirty rect
7970                        if prim_compare_result != PrimitiveCompareResult::Equal {
7971                            if invalidation_reason.is_none() {
7972                                *invalidation_reason = Some(InvalidationReason::Content);
7973                            }
7974                            *dirty_rect = self.rect.union(dirty_rect);
7975                            *dirty_tracker = *dirty_tracker | 1;
7976                            break;
7977                        }
7978                    }
7979                } else {
7980                    if invalidation_reason.is_none() {
7981                        *invalidation_reason = Some(InvalidationReason::PrimCount);
7982                    }
7983                    *dirty_rect = self.rect.union(dirty_rect);
7984                    *dirty_tracker = *dirty_tracker | 1;
7985                }
7986            }
7987        }
7988    }
7989}
7990
7991impl CompositeState {
7992    // A helper function to destroy all native surfaces for a given list of tiles
7993    pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Box<Tile>>>(
7994        &mut self,
7995        tiles_iter: I,
7996        resource_cache: &mut ResourceCache,
7997    ) {
7998        // Any old tiles that remain after the loop above are going to be dropped. For
7999        // simple composite mode, the texture cache handle will expire and be collected
8000        // by the texture cache. For native compositor mode, we need to explicitly
8001        // invoke a callback to the client to destroy that surface.
8002        if let CompositorKind::Native { .. } = self.compositor_kind {
8003            for tile in tiles_iter {
8004                // Only destroy native surfaces that have been allocated. It's
8005                // possible for display port tiles to be created that never
8006                // come on screen, and thus never get a native surface allocated.
8007                if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
8008                    if let Some(id) = id.take() {
8009                        resource_cache.destroy_compositor_tile(id);
8010                    }
8011                }
8012            }
8013        }
8014    }
8015}
8016
8017fn get_relative_scale_offset(
8018    child_spatial_node_index: SpatialNodeIndex,
8019    parent_spatial_node_index: SpatialNodeIndex,
8020    spatial_tree: &SpatialTree,
8021) -> ScaleOffset {
8022    let transform = spatial_tree.get_relative_transform(
8023        child_spatial_node_index,
8024        parent_spatial_node_index,
8025    );
8026    let mut scale_offset = match transform {
8027        CoordinateSpaceMapping::Local => ScaleOffset::identity(),
8028        CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset,
8029        CoordinateSpaceMapping::Transform(m) => {
8030            ScaleOffset::from_transform(&m).expect("bug: pictures caches don't support complex transforms")
8031        }
8032    };
8033
8034    // Compositors expect things to be aligned on device pixels. Logic at a higher level ensures that is
8035    // true, but floating point inaccuracy can sometimes result in small differences, so remove
8036    // them here.
8037    scale_offset.offset = scale_offset.offset.round();
8038
8039    scale_offset
8040}
8041
8042pub fn calculate_screen_uv(
8043    p: DevicePoint,
8044    clipped: DeviceRect,
8045) -> DeviceHomogeneousVector {
8046    // TODO(gw): Switch to a simple mix, no bilerp / homogeneous vec needed anymore
8047    DeviceHomogeneousVector::new(
8048        (p.x - clipped.min.x) / (clipped.max.x - clipped.min.x),
8049        (p.y - clipped.min.y) / (clipped.max.y - clipped.min.y),
8050        0.0,
8051        1.0,
8052    )
8053}
8054
8055fn get_surface_rects(
8056    surface_index: SurfaceIndex,
8057    composite_mode: &PictureCompositeMode,
8058    parent_surface_index: SurfaceIndex,
8059    surfaces: &mut [SurfaceInfo],
8060    spatial_tree: &SpatialTree,
8061    max_surface_size: f32,
8062    force_scissor_rect: bool,
8063) -> Option<SurfaceAllocInfo> {
8064    let parent_surface = &surfaces[parent_surface_index.0];
8065
8066    let local_to_parent = SpaceMapper::new_with_target(
8067        parent_surface.surface_spatial_node_index,
8068        surfaces[surface_index.0].surface_spatial_node_index,
8069        parent_surface.clipping_rect,
8070        spatial_tree,
8071    );
8072
8073    let local_clip_rect = local_to_parent
8074        .unmap(&parent_surface.clipping_rect)
8075        .unwrap_or(PictureRect::max_rect())
8076        .cast_unit();
8077
8078    let surface = &mut surfaces[surface_index.0];
8079
8080    let (clipped_local, unclipped_local, source_local) = match composite_mode {
8081        PictureCompositeMode::SVGFEGraph(ref filters) => {
8082            // We need to get the primitive rect, and get_coverage_target_svgfe
8083            // requires the provided rect is in user space (defined in SVG spec)
8084            // for subregion calculations to work properly
8085            //
8086            // Calculate the target rect from source rect, note that this can
8087            // produce a valid target rect even with an empty source rect in the
8088            // case of filters like feFlood, feComponentTransfer, feColorMatrix,
8089            // feImage and feTurbulence which can fill their whole subregion
8090            // even if given empty SourceGraphic.  It can also produce a smaller
8091            // rect than source if subregions or filter region apply clipping to
8092            // the intermediate pictures or the final picture.
8093            let prim_subregion = composite_mode.get_rect(surface, None);
8094
8095            // Clip the prim_subregion by the clip_rect, this will be put into
8096            // surface_rects.clipped.
8097            let visible_subregion: LayoutRect =
8098                prim_subregion.cast_unit()
8099                .intersection(&local_clip_rect)
8100                .unwrap_or(PictureRect::zero())
8101                .cast_unit();
8102
8103            // If the visible_subregion was empty to begin with, or clipped away
8104            // entirely, then there is nothing to do here, this is the hot path
8105            // for culling of composited pictures.
8106            if visible_subregion.is_empty() {
8107                return None;
8108            }
8109
8110            // Calculate the subregion for how much of SourceGraphic we may need
8111            // to produce to satisfy the invalidation rect, then clip it by the
8112            // original primitive rect because we have no reason to produce any
8113            // out of bounds pixels; they would just be blank anyway.
8114            let source_potential_subregion = composite_mode.get_coverage_source_svgfe(
8115                filters, visible_subregion.cast_unit());
8116            let source_subregion =
8117                source_potential_subregion
8118                .intersection(&surface.unclipped_local_rect.cast_unit())
8119                .unwrap_or(LayoutRect::zero());
8120
8121            // For some reason, code assumes that the clipped_local rect we make
8122            // here will enclose the source_subregion, and also be a valid
8123            // prim_subregion, so we have to union the two rects to meet those
8124            // expectations.  This is an optimization opportunity - figure out
8125            // how to make just the visible_subregion work here.
8126            let coverage_subregion = source_subregion.union(&visible_subregion);
8127
8128            (coverage_subregion.cast_unit(), prim_subregion.cast_unit(), source_subregion.cast_unit())
8129        }
8130        PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
8131            let local_prim_rect = surface.clipped_local_rect;
8132
8133            let mut required_local_rect = local_prim_rect
8134                .intersection(&local_clip_rect)
8135                .unwrap_or(PictureRect::zero());
8136
8137            for shadow in shadows {
8138                let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
8139                    shadow.blur_radius,
8140                    shadow.blur_radius,
8141                );
8142                let blur_inflation_x = blur_radius_x * BLUR_SAMPLE_SCALE;
8143                let blur_inflation_y = blur_radius_y * BLUR_SAMPLE_SCALE;
8144
8145                let local_shadow_rect = local_prim_rect
8146                    .translate(shadow.offset.cast_unit())
8147                    .inflate(blur_inflation_x, blur_inflation_y);
8148
8149                if let Some(clipped_shadow_rect) = local_clip_rect.intersection(&local_shadow_rect) {
8150                    let required_shadow_rect = clipped_shadow_rect.inflate(blur_inflation_x, blur_inflation_y);
8151
8152                    let local_clipped_shadow_rect = required_shadow_rect.translate(-shadow.offset.cast_unit());
8153
8154                    required_local_rect = required_local_rect.union(&local_clipped_shadow_rect);
8155                }
8156            }
8157
8158            let unclipped = composite_mode.get_rect(surface, None);
8159            let clipped = required_local_rect;
8160
8161            let clipped = match clipped.intersection(&unclipped.cast_unit()) {
8162                Some(rect) => rect,
8163                None => return None,
8164            };
8165
8166            (clipped, unclipped, clipped)
8167        }
8168        _ => {
8169            let surface_origin = surface.clipped_local_rect.min.to_vector().cast_unit();
8170
8171            let normalized_prim_rect = composite_mode
8172                .get_rect(surface, None)
8173                .translate(-surface_origin);
8174
8175            let normalized_clip_rect = local_clip_rect
8176                .cast_unit()
8177                .translate(-surface_origin);
8178
8179            let norm_clipped_rect = match normalized_prim_rect.intersection(&normalized_clip_rect) {
8180                Some(rect) => rect,
8181                None => return None,
8182            };
8183
8184            let norm_clipped_rect = composite_mode.get_rect(surface, Some(norm_clipped_rect));
8185
8186            let norm_clipped_rect = match norm_clipped_rect.intersection(&normalized_prim_rect) {
8187                Some(rect) => rect,
8188                None => return None,
8189            };
8190
8191            let unclipped = normalized_prim_rect.translate(surface_origin);
8192            let clipped = norm_clipped_rect.translate(surface_origin);
8193
8194            (clipped.cast_unit(), unclipped.cast_unit(), clipped.cast_unit())
8195        }
8196    };
8197
8198    // We need to put the clipped, unclipped and source rects in the chosen
8199    // raster spatial node if possible, so that it will be rendered at the
8200    // proper pixel scale with antialiasing, otherwise it would be blurry.
8201    let (mut clipped, mut unclipped, mut source) = if surface.raster_spatial_node_index != surface.surface_spatial_node_index {
8202        // Transform surface into the chosen raster spatial node
8203        assert_eq!(surface.device_pixel_scale.0, 1.0);
8204
8205        let local_to_world = SpaceMapper::new_with_target(
8206            spatial_tree.root_reference_frame_index(),
8207            surface.surface_spatial_node_index,
8208            WorldRect::max_rect(),
8209            spatial_tree,
8210        );
8211
8212        let clipped = local_to_world.map(&clipped_local.cast_unit()).unwrap() * surface.device_pixel_scale;
8213        let unclipped = local_to_world.map(&unclipped_local).unwrap() * surface.device_pixel_scale;
8214        let source = local_to_world.map(&source_local.cast_unit()).unwrap() * surface.device_pixel_scale;
8215
8216        (clipped, unclipped, source)
8217    } else {
8218        // Surface is already in the chosen raster spatial node
8219        let clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
8220        let unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
8221        let source = source_local.cast_unit() * surface.device_pixel_scale;
8222
8223        (clipped, unclipped, source)
8224    };
8225    let mut clipped_snapped = clipped.round_out();
8226    let mut source_snapped = source.round_out();
8227
8228    // We need to make sure the surface size does not exceed max_surface_size,
8229    // if it would exceed it we actually want to keep the surface in its local
8230    // space and stop worrying about it being a little blurry.
8231    //
8232    // Since both clipped and source are subject to the same limit, we can just
8233    // pick the largest axis from all rects involved.
8234    //
8235    // Importantly, surfaces that are exactly at max_surface_size are relatively
8236    // common for some reason, so we don't want to use a conservative limit.
8237    //
8238    // If you change this, test with:
8239    // ./mach crashtest layout/svg/crashtests/387290-1.svg
8240    let max_dimension =
8241        clipped_snapped.width().max(
8242            clipped_snapped.height().max(
8243                source_snapped.width().max(
8244                    source_snapped.height()
8245                ))).ceil();
8246    if max_dimension > max_surface_size {
8247        // We have to recalculate max_dimension for the local space we'll be
8248        // using as we're no longer rasterizing in the parent space
8249        let max_dimension =
8250            clipped_local.width().max(
8251                clipped_local.height().max(
8252                    source_local.width().max(
8253                        source_local.height()
8254                    ))).ceil();
8255        surface.raster_spatial_node_index = surface.surface_spatial_node_index;
8256        surface.device_pixel_scale = Scale::new(max_surface_size / max_dimension);
8257        surface.local_scale = (1.0, 1.0);
8258
8259        let add_markers = profiler::thread_is_being_profiled();
8260        if add_markers {
8261            let new_clipped = (clipped_local.cast_unit() * surface.device_pixel_scale).round();
8262            let new_source = (source_local.cast_unit() * surface.device_pixel_scale).round();
8263            profiler::add_text_marker("SurfaceSizeLimited",
8264                format!("Surface for {:?} reduced from raster {:?} (source {:?}) to local {:?} (source {:?})",
8265                    composite_mode.kind(),
8266                    clipped.size(), source.size(),
8267                    new_clipped, new_source).as_str(),
8268                Duration::from_secs_f32(new_clipped.width() * new_clipped.height() / 1000000000.0));
8269        }
8270
8271        clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
8272        unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
8273        source = source_local.cast_unit() * surface.device_pixel_scale;
8274        clipped_snapped = clipped.round();
8275        source_snapped = source.round();
8276    }
8277
8278    let task_size = clipped_snapped.size().to_i32();
8279    // We must avoid hitting the assert here at all costs because panics here
8280    // will repeatedly crash the GPU Process, making the whole app unusable,
8281    // so make sure task_size <= max_surface_size, it's possible that we lose a
8282    // pixel here if the max_dimension threshold was not optimal.
8283    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1948939 for more info.
8284    let task_size = task_size.min(DeviceIntSize::new(max_surface_size as i32, max_surface_size as i32));
8285    debug_assert!(
8286        task_size.width <= max_surface_size as i32 &&
8287        task_size.height <= max_surface_size as i32,
8288        "task_size {:?} for {:?} must be within max_surface_size {}",
8289        task_size,
8290        composite_mode.kind(),
8291        max_surface_size);
8292
8293    let uv_rect_kind = calculate_uv_rect_kind(
8294        clipped_snapped,
8295        unclipped,
8296    );
8297
8298    // If the task size is zero sized, skip creation and drawing of it
8299    if task_size.width == 0 || task_size.height == 0 {
8300        return None;
8301    }
8302
8303    // If the final clipped surface rect is not the same or larger as the unclipped
8304    // local rect of the surface, we need to enable scissor rect (which disables
8305    // merging batches between this and other render tasks allocated to the same
8306    // render target). This is conservative - we could do better in future by
8307    // distinguishing between clips that affect the surface itself vs. clips on
8308    // child primitives that don't affect this.
8309    let needs_scissor_rect = force_scissor_rect || !clipped_local.contains_box(&surface.unclipped_local_rect);
8310
8311    Some(SurfaceAllocInfo {
8312        task_size,
8313        needs_scissor_rect,
8314        clipped: clipped_snapped,
8315        unclipped,
8316        source: source_snapped,
8317        clipped_notsnapped: clipped,
8318        clipped_local,
8319        uv_rect_kind,
8320    })
8321}
8322
8323pub fn calculate_uv_rect_kind(
8324    clipped: DeviceRect,
8325    unclipped: DeviceRect,
8326) -> UvRectKind {
8327    let top_left = calculate_screen_uv(
8328        unclipped.top_left().cast_unit(),
8329        clipped,
8330    );
8331
8332    let top_right = calculate_screen_uv(
8333        unclipped.top_right().cast_unit(),
8334        clipped,
8335    );
8336
8337    let bottom_left = calculate_screen_uv(
8338        unclipped.bottom_left().cast_unit(),
8339        clipped,
8340    );
8341
8342    let bottom_right = calculate_screen_uv(
8343        unclipped.bottom_right().cast_unit(),
8344        clipped,
8345    );
8346
8347    UvRectKind::Quad {
8348        top_left,
8349        top_right,
8350        bottom_left,
8351        bottom_right,
8352    }
8353}
8354
8355#[test]
8356fn test_large_surface_scale_1() {
8357    use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
8358
8359    let mut cst = SceneSpatialTree::new();
8360    let root_reference_frame_index = cst.root_reference_frame_index();
8361
8362    let mut spatial_tree = SpatialTree::new();
8363    spatial_tree.apply_updates(cst.end_frame_and_get_pending_updates());
8364    spatial_tree.update_tree(&SceneProperties::new());
8365
8366    let map_local_to_picture = SpaceMapper::new_with_target(
8367        root_reference_frame_index,
8368        root_reference_frame_index,
8369        PictureRect::max_rect(),
8370        &spatial_tree,
8371    );
8372
8373    let mut surfaces = vec![
8374        SurfaceInfo {
8375            unclipped_local_rect: PictureRect::max_rect(),
8376            clipped_local_rect: PictureRect::max_rect(),
8377            is_opaque: true,
8378            clipping_rect: PictureRect::max_rect(),
8379            culling_rect: VisRect::max_rect(),
8380            map_local_to_picture: map_local_to_picture.clone(),
8381            raster_spatial_node_index: root_reference_frame_index,
8382            surface_spatial_node_index: root_reference_frame_index,
8383            visibility_spatial_node_index: root_reference_frame_index,
8384            device_pixel_scale: DevicePixelScale::new(1.0),
8385            world_scale_factors: (1.0, 1.0),
8386            local_scale: (1.0, 1.0),
8387            allow_snapping: true,
8388            force_scissor_rect: false,
8389        },
8390        SurfaceInfo {
8391            unclipped_local_rect: PictureRect::new(
8392                PicturePoint::new(52.76350021362305, 0.0),
8393                PicturePoint::new(159.6738739013672, 35.0),
8394            ),
8395            clipped_local_rect: PictureRect::max_rect(),
8396            is_opaque: true,
8397            clipping_rect: PictureRect::max_rect(),
8398            culling_rect: VisRect::max_rect(),
8399            map_local_to_picture,
8400            raster_spatial_node_index: root_reference_frame_index,
8401            surface_spatial_node_index: root_reference_frame_index,
8402            visibility_spatial_node_index: root_reference_frame_index,
8403            device_pixel_scale: DevicePixelScale::new(43.82798767089844),
8404            world_scale_factors: (1.0, 1.0),
8405            local_scale: (1.0, 1.0),
8406            allow_snapping: true,
8407            force_scissor_rect: false,
8408        },
8409    ];
8410
8411    get_surface_rects(
8412        SurfaceIndex(1),
8413        &PictureCompositeMode::Blit(BlitReason::ISOLATE),
8414        SurfaceIndex(0),
8415        &mut surfaces,
8416        &spatial_tree,
8417        MAX_SURFACE_SIZE as f32,
8418        false,
8419    );
8420}
8421
8422#[test]
8423fn test_drop_filter_dirty_region_outside_prim() {
8424    // Ensure that if we have a drop-filter where the content of the
8425    // shadow is outside the dirty rect, but blurred pixels from that
8426    // content will affect the dirty rect, that we correctly calculate
8427    // the required region of the drop-filter input
8428
8429    use api::Shadow;
8430    use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
8431
8432    let mut cst = SceneSpatialTree::new();
8433    let root_reference_frame_index = cst.root_reference_frame_index();
8434
8435    let mut spatial_tree = SpatialTree::new();
8436    spatial_tree.apply_updates(cst.end_frame_and_get_pending_updates());
8437    spatial_tree.update_tree(&SceneProperties::new());
8438
8439    let map_local_to_picture = SpaceMapper::new_with_target(
8440        root_reference_frame_index,
8441        root_reference_frame_index,
8442        PictureRect::max_rect(),
8443        &spatial_tree,
8444    );
8445
8446    let mut surfaces = vec![
8447        SurfaceInfo {
8448            unclipped_local_rect: PictureRect::max_rect(),
8449            clipped_local_rect: PictureRect::max_rect(),
8450            is_opaque: true,
8451            clipping_rect: PictureRect::max_rect(),
8452            map_local_to_picture: map_local_to_picture.clone(),
8453            raster_spatial_node_index: root_reference_frame_index,
8454            surface_spatial_node_index: root_reference_frame_index,
8455            visibility_spatial_node_index: root_reference_frame_index,
8456            device_pixel_scale: DevicePixelScale::new(1.0),
8457            world_scale_factors: (1.0, 1.0),
8458            local_scale: (1.0, 1.0),
8459            allow_snapping: true,
8460            force_scissor_rect: false,
8461            culling_rect: VisRect::max_rect(),
8462        },
8463        SurfaceInfo {
8464            unclipped_local_rect: PictureRect::new(
8465                PicturePoint::new(0.0, 0.0),
8466                PicturePoint::new(750.0, 450.0),
8467            ),
8468            clipped_local_rect: PictureRect::new(
8469                PicturePoint::new(0.0, 0.0),
8470                PicturePoint::new(750.0, 450.0),
8471            ),
8472            is_opaque: true,
8473            clipping_rect: PictureRect::max_rect(),
8474            map_local_to_picture,
8475            raster_spatial_node_index: root_reference_frame_index,
8476            surface_spatial_node_index: root_reference_frame_index,
8477            visibility_spatial_node_index: root_reference_frame_index,
8478            device_pixel_scale: DevicePixelScale::new(1.0),
8479            world_scale_factors: (1.0, 1.0),
8480            local_scale: (1.0, 1.0),
8481            allow_snapping: true,
8482            force_scissor_rect: false,
8483            culling_rect: VisRect::max_rect(),
8484        },
8485    ];
8486
8487    let shadows = smallvec![
8488        Shadow {
8489            offset: LayoutVector2D::zero(),
8490            color: ColorF::BLACK,
8491            blur_radius: 75.0,
8492        },
8493    ];
8494
8495    let composite_mode = PictureCompositeMode::Filter(Filter::DropShadows(shadows));
8496
8497    // Ensure we get a valid and correct render task size when dirty region covers entire screen
8498    let info = get_surface_rects(
8499        SurfaceIndex(1),
8500        &composite_mode,
8501        SurfaceIndex(0),
8502        &mut surfaces,
8503        &spatial_tree,
8504        MAX_SURFACE_SIZE as f32,
8505        false,
8506    ).expect("No surface rect");
8507    assert_eq!(info.task_size, DeviceIntSize::new(1200, 900));
8508
8509    // Ensure we get a valid and correct render task size when dirty region is outside filter content
8510    surfaces[0].clipping_rect = PictureRect::new(
8511        PicturePoint::new(768.0, 128.0),
8512        PicturePoint::new(1024.0, 256.0),
8513    );
8514    let info = get_surface_rects(
8515        SurfaceIndex(1),
8516        &composite_mode,
8517        SurfaceIndex(0),
8518        &mut surfaces,
8519        &spatial_tree,
8520        MAX_SURFACE_SIZE as f32,
8521        false,
8522    ).expect("No surface rect");
8523    assert_eq!(info.task_size, DeviceIntSize::new(432, 578));
8524}
8525
8526fn request_render_task(
8527    frame_state: &mut FrameBuildingState,
8528    snapshot: &Option<SnapshotInfo>,
8529    surface_rects: &SurfaceAllocInfo,
8530    is_opaque: bool,
8531    f: &mut dyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF, &mut GpuCache) -> RenderTaskId,
8532) -> RenderTaskId {
8533
8534    let task_id = match snapshot {
8535        Some(info) => {
8536            let adjustment = AdjustedImageSource::from_rects(
8537                &info.area,
8538                &surface_rects.clipped_local.cast_unit()
8539            );
8540            let task_id = frame_state.resource_cache.render_as_image(
8541                info.key.as_image(),
8542                surface_rects.task_size,
8543                frame_state.rg_builder,
8544                &mut frame_state.frame_gpu_data.f32,
8545                frame_state.gpu_cache,
8546                is_opaque,
8547                &adjustment,
8548                f
8549            );
8550
8551            // TODO(bug 1929809): adding the dependency in the other branch causes
8552            // a panic in reftests/blend/backdrop-filter-blend-container.yaml.
8553            // Presumably if we use backdrop filters with snapshotting it will
8554            // trigger the panic as well.
8555            frame_state.surface_builder.add_child_render_task(
8556                task_id,
8557                frame_state.rg_builder,
8558            );
8559
8560            frame_state.image_dependencies.insert(info.key.as_image(), task_id);
8561
8562            task_id
8563        }
8564        None => {
8565            f(
8566                frame_state.rg_builder,
8567                &mut frame_state.frame_gpu_data.f32,
8568                frame_state.gpu_cache
8569            )
8570        }
8571    };
8572
8573    task_id
8574}