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