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