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