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