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(¤t_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