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