Source code

Revision control

Other Tools

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
use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
73
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
74
use api::{DebugFlags, RasterSpace, ImageKey, ColorF, PrimitiveFlags};
75
use api::units::*;
76
use crate::box_shadow::{BLUR_SAMPLE_SCALE};
77
use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
78
use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
79
ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
80
};
81
use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId};
82
use crate::debug_colors;
83
use euclid::{vec3, Point2D, Scale, Size2D, Vector2D, Rect};
84
use euclid::approxeq::ApproxEq;
85
use crate::filterdata::SFilterData;
86
use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
87
use crate::intern::ItemUid;
88
use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource};
89
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
90
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
91
use crate::gpu_types::UvRectKind;
92
use plane_split::{Clipper, Polygon, Splitter};
93
use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PointKey, PrimitiveTemplateKind};
94
use crate::prim_store::{SpaceSnapper, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
95
use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey};
96
use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex};
97
use crate::print_tree::{PrintTree, PrintTreePrinter};
98
use crate::render_backend::DataStores;
99
use crate::render_task_graph::RenderTaskId;
100
use crate::render_target::RenderTargetKind;
101
use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode};
102
use crate::resource_cache::ResourceCache;
103
use crate::scene::SceneProperties;
104
use smallvec::SmallVec;
105
use std::{mem, u8, marker, u32};
106
use std::sync::atomic::{AtomicUsize, Ordering};
107
use crate::texture_cache::TextureCacheHandle;
108
use crate::util::{TransformedRectKind, MatrixHelpers, MaxRect, scale_factors, VecHelper, RectHelpers};
109
use crate::filterdata::{FilterDataHandle};
110
111
/// Specify whether a surface allows subpixel AA text rendering.
112
#[derive(Debug, Copy, Clone, PartialEq)]
113
pub enum SubpixelMode {
114
/// This surface allows subpixel AA text
115
Allow,
116
/// Subpixel AA text cannot be drawn on this surface
117
Deny,
118
}
119
120
/// A comparable transform matrix, that compares with epsilon checks.
121
#[derive(Debug, Clone)]
122
struct MatrixKey {
123
m: [f32; 16],
124
}
125
126
impl PartialEq for MatrixKey {
127
fn eq(&self, other: &Self) -> bool {
128
const EPSILON: f32 = 0.001;
129
130
// TODO(gw): It's possible that we may need to adjust the epsilon
131
// to be tighter on most of the matrix, except the
132
// translation parts?
133
for (i, j) in self.m.iter().zip(other.m.iter()) {
134
if !i.approx_eq_eps(j, &EPSILON) {
135
return false;
136
}
137
}
138
139
true
140
}
141
}
142
143
/// A comparable / hashable version of a coordinate space mapping. Used to determine
144
/// if a transform dependency for a tile has changed.
145
#[derive(Debug, PartialEq, Clone)]
146
enum TransformKey {
147
Local,
148
ScaleOffset {
149
scale_x: f32,
150
scale_y: f32,
151
offset_x: f32,
152
offset_y: f32,
153
},
154
Transform {
155
m: MatrixKey,
156
}
157
}
158
159
impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
160
fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey {
161
match transform {
162
CoordinateSpaceMapping::Local => {
163
TransformKey::Local
164
}
165
CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
166
TransformKey::ScaleOffset {
167
scale_x: scale_offset.scale.x,
168
scale_y: scale_offset.scale.y,
169
offset_x: scale_offset.offset.x,
170
offset_y: scale_offset.offset.y,
171
}
172
}
173
CoordinateSpaceMapping::Transform(ref m) => {
174
TransformKey::Transform {
175
m: MatrixKey {
176
m: m.to_row_major_array(),
177
},
178
}
179
}
180
}
181
}
182
}
183
184
/// Information about a picture that is pushed / popped on the
185
/// PictureUpdateState during picture traversal pass.
186
struct PictureInfo {
187
/// The spatial node for this picture.
188
_spatial_node_index: SpatialNodeIndex,
189
}
190
191
pub struct PictureCacheState {
192
/// The tiles retained by this picture cache.
193
pub tiles: FastHashMap<TileOffset, Tile>,
194
/// State of the spatial nodes from previous frame
195
spatial_nodes: FastHashMap<SpatialNodeIndex, SpatialNodeDependency>,
196
/// State of opacity bindings from previous frame
197
opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
198
/// The current transform of the picture cache root spatial node
199
root_transform: TransformKey,
200
/// The current tile size in device pixels
201
current_tile_size: DeviceIntSize,
202
}
203
204
/// Stores a list of cached picture tiles that are retained
205
/// between new scenes.
206
#[cfg_attr(feature = "capture", derive(Serialize))]
207
pub struct RetainedTiles {
208
/// The tiles retained between display lists.
209
#[cfg_attr(feature = "capture", serde(skip))] //TODO
210
pub caches: FastHashMap<usize, PictureCacheState>,
211
}
212
213
impl RetainedTiles {
214
pub fn new() -> Self {
215
RetainedTiles {
216
caches: FastHashMap::default(),
217
}
218
}
219
220
/// Merge items from one retained tiles into another.
221
pub fn merge(&mut self, other: RetainedTiles) {
222
assert!(self.caches.is_empty() || other.caches.is_empty());
223
if self.caches.is_empty() {
224
self.caches = other.caches;
225
}
226
}
227
}
228
229
/// Unit for tile coordinates.
230
#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
231
pub struct TileCoordinate;
232
233
// Geometry types for tile coordinates.
234
pub type TileOffset = Point2D<i32, TileCoordinate>;
235
pub type TileSize = Size2D<i32, TileCoordinate>;
236
pub type TileRect = Rect<i32, TileCoordinate>;
237
238
/// The size in device pixels of a normal cached tile.
239
pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize {
240
width: 1024,
241
height: 512,
242
_unit: marker::PhantomData,
243
};
244
245
/// The size in device pixels of a tile for horizontal scroll bars
246
pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize {
247
width: 512,
248
height: 16,
249
_unit: marker::PhantomData,
250
};
251
252
/// The size in device pixels of a tile for vertical scroll bars
253
pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
254
width: 16,
255
height: 512,
256
_unit: marker::PhantomData,
257
};
258
259
// Return the list of tile sizes for the renderer to allocate texture arrays for.
260
pub fn tile_cache_sizes() -> &'static [DeviceIntSize] {
261
&[
262
TILE_SIZE_DEFAULT,
263
TILE_SIZE_SCROLLBAR_HORIZONTAL,
264
TILE_SIZE_SCROLLBAR_VERTICAL,
265
]
266
}
267
268
/// The maximum size per axis of a surface,
269
/// in WorldPixel coordinates.
270
const MAX_SURFACE_SIZE: f32 = 4096.0;
271
272
/// The maximum number of sub-dependencies (e.g. clips, transforms) we can handle
273
/// per-primitive. If a primitive has more than this, it will invalidate every frame.
274
const MAX_PRIM_SUB_DEPS: usize = u8::MAX as usize;
275
276
/// Used to get unique tile IDs, even when the tile cache is
277
/// destroyed between display lists / scenes.
278
static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
279
280
fn clamp(value: i32, low: i32, high: i32) -> i32 {
281
value.max(low).min(high)
282
}
283
284
fn clampf(value: f32, low: f32, high: f32) -> f32 {
285
value.max(low).min(high)
286
}
287
288
/// An index into the prims array in a TileDescriptor.
289
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
290
struct PrimitiveDependencyIndex(u32);
291
292
/// Information about the state of an opacity binding.
293
#[derive(Debug)]
294
pub struct OpacityBindingInfo {
295
/// The current value retrieved from dynamic scene properties.
296
value: f32,
297
/// True if it was changed (or is new) since the last frame build.
298
changed: bool,
299
}
300
301
/// Information stored in a tile descriptor for an opacity binding.
302
#[derive(Debug, PartialEq, Clone)]
303
pub enum OpacityBinding {
304
Value(f32),
305
Binding(PropertyBindingId),
306
}
307
308
impl From<PropertyBinding<f32>> for OpacityBinding {
309
fn from(binding: PropertyBinding<f32>) -> OpacityBinding {
310
match binding {
311
PropertyBinding::Binding(key, _) => OpacityBinding::Binding(key.id),
312
PropertyBinding::Value(value) => OpacityBinding::Value(value),
313
}
314
}
315
}
316
317
/// Information about the state of a spatial node value
318
#[derive(Debug)]
319
pub struct SpatialNodeDependency {
320
/// The current value retrieved from the clip-scroll tree.
321
value: TransformKey,
322
/// True if it was changed (or is new) since the last frame build.
323
changed: bool,
324
}
325
326
// Immutable context passed to picture cache tiles during pre_update
327
struct TilePreUpdateContext {
328
/// The local rect of the overall picture cache
329
local_rect: PictureRect,
330
331
/// The local clip rect (in picture space) of the entire picture cache
332
local_clip_rect: PictureRect,
333
334
/// Maps from picture cache coords -> world space coords.
335
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
336
337
/// The fractional position of the picture cache, which may
338
/// require invalidation of all tiles.
339
fract_offset: PictureVector2D,
340
341
/// The optional background color of the picture cache instance
342
background_color: Option<ColorF>,
343
344
/// The visible part of the screen in world coords.
345
global_screen_world_rect: WorldRect,
346
}
347
348
// Immutable context passed to picture cache tiles during post_update
349
struct TilePostUpdateContext<'a> {
350
/// The calculated backdrop information for this cache instance.
351
backdrop: BackdropInfo,
352
353
/// Information about transform node differences from last frame.
354
spatial_nodes: &'a FastHashMap<SpatialNodeIndex, SpatialNodeDependency>,
355
356
/// Information about opacity bindings from the picture cache.
357
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
358
359
/// Current size in device pixels of tiles for this cache
360
current_tile_size: DeviceIntSize,
361
}
362
363
// Mutable state passed to picture cache tiles during post_update
364
struct TilePostUpdateState<'a> {
365
/// Allow access to the texture cache for requesting tiles
366
resource_cache: &'a ResourceCache,
367
368
/// Current configuration and setup for compositing all the picture cache tiles in renderer.
369
composite_state: &'a mut CompositeState,
370
}
371
372
/// Information about the dependencies of a single primitive instance.
373
struct PrimitiveDependencyInfo {
374
/// If true, this instance can be cached.
375
is_cacheable: bool,
376
377
/// If true, we should clip the prim rect to the tile boundaries.
378
clip_by_tile: bool,
379
380
/// Unique content identifier of the primitive.
381
prim_uid: ItemUid,
382
383
/// The picture space origin of this primitive.
384
prim_origin: PicturePoint,
385
386
/// The (conservative) clipped area in picture space this primitive occupies.
387
prim_clip_rect: PictureRect,
388
389
/// Image keys this primitive depends on.
390
image_keys: SmallVec<[ImageKey; 8]>,
391
392
/// Opacity bindings this primitive depends on.
393
opacity_bindings: SmallVec<[OpacityBinding; 4]>,
394
395
/// Clips that this primitive depends on.
396
clips: SmallVec<[ItemUid; 8]>,
397
398
/// Spatial nodes references by the clip dependencies of this primitive.
399
spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
400
}
401
402
impl PrimitiveDependencyInfo {
403
/// Construct dependency info for a new primitive.
404
fn new(
405
prim_uid: ItemUid,
406
prim_origin: PicturePoint,
407
prim_clip_rect: PictureRect,
408
is_cacheable: bool,
409
) -> Self {
410
PrimitiveDependencyInfo {
411
prim_uid,
412
prim_origin,
413
is_cacheable,
414
image_keys: SmallVec::new(),
415
opacity_bindings: SmallVec::new(),
416
clip_by_tile: false,
417
prim_clip_rect,
418
clips: SmallVec::new(),
419
spatial_nodes: SmallVec::new(),
420
}
421
}
422
}
423
424
/// A stable ID for a given tile, to help debugging. These are also used
425
/// as unique identfiers for tile surfaces when using a native compositor.
426
#[derive(Debug, Copy, Clone, PartialEq)]
427
#[cfg_attr(feature = "capture", derive(Serialize))]
428
#[cfg_attr(feature = "replay", derive(Deserialize))]
429
pub struct TileId(pub usize);
430
431
/// A descriptor for the kind of texture that a picture cache tile will
432
/// be drawn into.
433
#[derive(Debug)]
434
pub enum SurfaceTextureDescriptor {
435
/// When using the WR compositor, the tile is drawn into an entry
436
/// in the WR texture cache.
437
TextureCache {
438
handle: TextureCacheHandle
439
},
440
/// When using an OS compositor, the tile is drawn into a native
441
/// surface identified by arbitrary id.
442
NativeSurface {
443
/// The arbitrary id of this surface.
444
id: Option<NativeSurfaceId>,
445
/// Size in device pixels of the native surface.
446
size: DeviceIntSize,
447
},
448
}
449
450
/// This is the same as a `SurfaceTextureDescriptor` but has been resolved
451
/// into a texture cache handle (if appropriate) that can be used by the
452
/// batching and compositing code in the renderer.
453
#[derive(Clone, Debug)]
454
#[cfg_attr(feature = "capture", derive(Serialize))]
455
#[cfg_attr(feature = "replay", derive(Deserialize))]
456
pub enum ResolvedSurfaceTexture {
457
TextureCache {
458
/// The texture ID to draw to.
459
texture: TextureSource,
460
/// Slice index in the texture array to draw to.
461
layer: i32,
462
},
463
NativeSurface {
464
/// The arbitrary id of this surface.
465
id: NativeSurfaceId,
466
/// Size in device pixels of the native surface.
467
size: DeviceIntSize,
468
}
469
}
470
471
impl SurfaceTextureDescriptor {
472
/// Create a resolved surface texture for this descriptor
473
pub fn resolve(
474
&self,
475
resource_cache: &ResourceCache,
476
) -> ResolvedSurfaceTexture {
477
match self {
478
SurfaceTextureDescriptor::TextureCache { handle } => {
479
let cache_item = resource_cache.texture_cache.get(handle);
480
481
ResolvedSurfaceTexture::TextureCache {
482
texture: cache_item.texture_id,
483
layer: cache_item.texture_layer,
484
}
485
}
486
SurfaceTextureDescriptor::NativeSurface { id, size } => {
487
ResolvedSurfaceTexture::NativeSurface {
488
id: id.expect("bug: native surface not allocated"),
489
size: *size,
490
}
491
}
492
}
493
}
494
}
495
496
/// The backing surface for this tile.
497
#[derive(Debug)]
498
pub enum TileSurface {
499
Texture {
500
/// Descriptor for the surface that this tile draws into.
501
descriptor: SurfaceTextureDescriptor,
502
/// Bitfield specifying the dirty region(s) that are relevant to this tile.
503
visibility_mask: PrimitiveVisibilityMask,
504
},
505
Color {
506
color: ColorF,
507
},
508
Clear,
509
}
510
511
impl TileSurface {
512
fn kind(&self) -> &'static str {
513
match *self {
514
TileSurface::Color { .. } => "Color",
515
TileSurface::Texture { .. } => "Texture",
516
TileSurface::Clear => "Clear",
517
}
518
}
519
}
520
521
/// The result of a primitive dependency comparison. Size is a u8
522
/// since this is a hot path in the code, and keeping the data small
523
/// is a performance win.
524
#[derive(Debug, Copy, Clone, PartialEq)]
525
#[repr(u8)]
526
enum PrimitiveCompareResult {
527
/// Primitives match
528
Equal,
529
/// Something in the PrimitiveDescriptor was different
530
Descriptor,
531
/// The clip node content or spatial node changed
532
Clip,
533
/// The value of the transform changed
534
Transform,
535
/// An image dependency was dirty
536
Image,
537
/// The value of an opacity binding changed
538
OpacityBinding,
539
}
540
541
/// Debugging information about why a tile was invalidated
542
#[derive(Debug)]
543
enum InvalidationReason {
544
/// The fractional offset changed
545
FractionalOffset,
546
/// The background color changed
547
BackgroundColor,
548
/// Tile was not cacheable (e.g. video element)
549
NonCacheable,
550
/// The opaque state of the backing native surface changed
551
SurfaceOpacityChanged,
552
/// There was no backing texture (evicted or never rendered)
553
NoTexture,
554
/// There was no backing native surface (never rendered, or recreated)
555
NoSurface,
556
/// The primitive count in the dependency list was different
557
PrimCount,
558
/// The content of one of the primitives was different
559
Content {
560
/// What changed in the primitive that was different
561
prim_compare_result: PrimitiveCompareResult,
562
},
563
}
564
565
/// Information about a cached tile.
566
pub struct Tile {
567
/// The current world rect of this tile.
568
pub world_rect: WorldRect,
569
/// The current local rect of this tile.
570
pub rect: PictureRect,
571
/// The local rect of the tile clipped to the overall picture local rect.
572
clipped_rect: PictureRect,
573
/// Uniquely describes the content of this tile, in a way that can be
574
/// (reasonably) efficiently hashed and compared.
575
pub current_descriptor: TileDescriptor,
576
/// The content descriptor for this tile from the previous frame.
577
pub prev_descriptor: TileDescriptor,
578
/// Handle to the backing surface for this tile.
579
pub surface: Option<TileSurface>,
580
/// If true, this tile is marked valid, and the existing texture
581
/// cache handle can be used. Tiles are invalidated during the
582
/// build_dirty_regions method.
583
pub is_valid: bool,
584
/// If true, this tile intersects with the currently visible screen
585
/// rect, and will be drawn.
586
pub is_visible: bool,
587
/// The current fractional offset of the cache transform root. If this changes,
588
/// all tiles need to be invalidated and redrawn, since snapping differences are
589
/// likely to occur.
590
fract_offset: PictureVector2D,
591
/// The tile id is stable between display lists and / or frames,
592
/// if the tile is retained. Useful for debugging tile evictions.
593
pub id: TileId,
594
/// If true, the tile was determined to be opaque, which means blending
595
/// can be disabled when drawing it.
596
pub is_opaque: bool,
597
/// Root node of the quadtree dirty rect tracker.
598
root: TileNode,
599
/// The picture space dirty rect for this tile.
600
dirty_rect: PictureRect,
601
/// The world space dirty rect for this tile.
602
/// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
603
/// expose these as multiple dirty rects, which will help in some cases.
604
pub world_dirty_rect: WorldRect,
605
/// The last rendered background color on this tile.
606
background_color: Option<ColorF>,
607
/// The first reason the tile was invalidated this frame.
608
invalidation_reason: Option<InvalidationReason>,
609
}
610
611
impl Tile {
612
/// Construct a new, invalid tile.
613
fn new(
614
id: TileId,
615
) -> Self {
616
Tile {
617
rect: PictureRect::zero(),
618
clipped_rect: PictureRect::zero(),
619
world_rect: WorldRect::zero(),
620
surface: None,
621
current_descriptor: TileDescriptor::new(),
622
prev_descriptor: TileDescriptor::new(),
623
is_valid: false,
624
is_visible: false,
625
fract_offset: PictureVector2D::zero(),
626
id,
627
is_opaque: false,
628
root: TileNode::new_leaf(Vec::new()),
629
dirty_rect: PictureRect::zero(),
630
world_dirty_rect: WorldRect::zero(),
631
background_color: None,
632
invalidation_reason: None,
633
}
634
}
635
636
/// Print debug information about this tile to a tree printer.
637
fn print(&self, pt: &mut dyn PrintTreePrinter) {
638
pt.new_level(format!("Tile {:?}", self.id));
639
pt.add_item(format!("rect: {}", self.rect));
640
pt.add_item(format!("fract_offset: {:?}", self.fract_offset));
641
pt.add_item(format!("background_color: {:?}", self.background_color));
642
pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
643
self.current_descriptor.print(pt);
644
pt.end_level();
645
}
646
647
/// Check if the content of the previous and current tile descriptors match
648
fn update_dirty_rects(
649
&mut self,
650
ctx: &TilePostUpdateContext,
651
state: &TilePostUpdateState,
652
compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
653
invalidation_reason: &mut Option<InvalidationReason>,
654
) -> PictureRect {
655
let mut prim_comparer = PrimitiveComparer::new(
656
&self.prev_descriptor,
657
&self.current_descriptor,
658
state.resource_cache,
659
ctx.spatial_nodes,
660
ctx.opacity_bindings,
661
);
662
663
let mut dirty_rect = PictureRect::zero();
664
665
self.root.update_dirty_rects(
666
&self.prev_descriptor.prims,
667
&self.current_descriptor.prims,
668
&mut prim_comparer,
669
&mut dirty_rect,
670
compare_cache,
671
invalidation_reason,
672
);
673
674
dirty_rect
675
}
676
677
/// Invalidate a tile based on change in content. This
678
/// must be called even if the tile is not currently
679
/// visible on screen. We might be able to improve this
680
/// later by changing how ComparableVec is used.
681
fn update_content_validity(
682
&mut self,
683
ctx: &TilePostUpdateContext,
684
state: &TilePostUpdateState,
685
) {
686
// Check if the contents of the primitives, clips, and
687
// other dependencies are the same.
688
let mut compare_cache = FastHashMap::default();
689
let mut invalidation_reason = None;
690
let dirty_rect = self.update_dirty_rects(
691
ctx,
692
state,
693
&mut compare_cache,
694
&mut invalidation_reason,
695
);
696
if !dirty_rect.is_empty() {
697
self.invalidate(
698
Some(dirty_rect),
699
invalidation_reason.expect("bug: no invalidation_reason"),
700
);
701
}
702
}
703
704
/// Invalidate this tile. If `invalidation_rect` is None, the entire
705
/// tile is invalidated.
706
fn invalidate(
707
&mut self,
708
invalidation_rect: Option<PictureRect>,
709
reason: InvalidationReason,
710
) {
711
self.is_valid = false;
712
713
match invalidation_rect {
714
Some(rect) => {
715
self.dirty_rect = self.dirty_rect.union(&rect);
716
}
717
None => {
718
self.dirty_rect = self.rect;
719
}
720
}
721
722
if self.invalidation_reason.is_none() {
723
self.invalidation_reason = Some(reason);
724
}
725
}
726
727
/// Called during pre_update of a tile cache instance. Allows the
728
/// tile to setup state before primitive dependency calculations.
729
fn pre_update(
730
&mut self,
731
rect: PictureRect,
732
ctx: &TilePreUpdateContext,
733
) {
734
self.rect = rect;
735
self.invalidation_reason = None;
736
737
self.clipped_rect = self.rect
738
.intersection(&ctx.local_rect)
739
.and_then(|r| r.intersection(&ctx.local_clip_rect))
740
.unwrap_or(PictureRect::zero());
741
742
self.world_rect = ctx.pic_to_world_mapper
743
.map(&self.rect)
744
.expect("bug: map local tile rect");
745
746
// Check if this tile is currently on screen.
747
self.is_visible = self.world_rect.intersects(&ctx.global_screen_world_rect);
748
749
// If the tile isn't visible, early exit, skipping the normal set up to
750
// validate dependencies. Instead, we will only compare the current tile
751
// dependencies the next time it comes into view.
752
if !self.is_visible {
753
return;
754
}
755
756
// Determine if the fractional offset of the transform is different this frame
757
// from the currently cached tile set.
758
let fract_changed = (self.fract_offset.x - ctx.fract_offset.x).abs() > 0.001 ||
759
(self.fract_offset.y - ctx.fract_offset.y).abs() > 0.001;
760
if fract_changed {
761
self.invalidate(None, InvalidationReason::FractionalOffset);
762
self.fract_offset = ctx.fract_offset;
763
}
764
765
if ctx.background_color != self.background_color {
766
self.invalidate(None, InvalidationReason::BackgroundColor);
767
self.background_color = ctx.background_color;
768
}
769
770
// Clear any dependencies so that when we rebuild them we
771
// can compare if the tile has the same content.
772
mem::swap(
773
&mut self.current_descriptor,
774
&mut self.prev_descriptor,
775
);
776
self.current_descriptor.clear();
777
self.root.clear(rect);
778
}
779
780
/// Add dependencies for a given primitive to this tile.
781
fn add_prim_dependency(
782
&mut self,
783
info: &PrimitiveDependencyInfo,
784
) {
785
// If this tile isn't currently visible, we don't want to update the dependencies
786
// for this tile, as an optimization, since it won't be drawn anyway.
787
if !self.is_visible {
788
return;
789
}
790
791
// Mark if the tile is cacheable at all.
792
if !info.is_cacheable {
793
self.invalidate(Some(info.prim_clip_rect), InvalidationReason::NonCacheable);
794
}
795
796
// Include any image keys this tile depends on.
797
self.current_descriptor.image_keys.extend_from_slice(&info.image_keys);
798
799
// Include any opacity bindings this primitive depends on.
800
self.current_descriptor.opacity_bindings.extend_from_slice(&info.opacity_bindings);
801
802
// Include any clip nodes that this primitive depends on.
803
self.current_descriptor.clips.extend_from_slice(&info.clips);
804
805
// Include any transforms that this primitive depends on.
806
self.current_descriptor.transforms.extend_from_slice(&info.spatial_nodes);
807
808
// TODO(gw): The origin of background rects produced by APZ changes
809
// in Gecko during scrolling. Consider investigating this so the
810
// hack / workaround below is not required.
811
let (prim_origin, prim_clip_rect) = if info.clip_by_tile {
812
let tile_p0 = self.rect.origin;
813
let tile_p1 = self.rect.bottom_right();
814
815
let clip_p0 = PicturePoint::new(
816
clampf(info.prim_clip_rect.origin.x, tile_p0.x, tile_p1.x),
817
clampf(info.prim_clip_rect.origin.y, tile_p0.y, tile_p1.y),
818
);
819
820
let clip_p1 = PicturePoint::new(
821
clampf(info.prim_clip_rect.origin.x + info.prim_clip_rect.size.width, tile_p0.x, tile_p1.x),
822
clampf(info.prim_clip_rect.origin.y + info.prim_clip_rect.size.height, tile_p0.y, tile_p1.y),
823
);
824
825
(
826
PicturePoint::new(
827
clampf(info.prim_origin.x, tile_p0.x, tile_p1.x),
828
clampf(info.prim_origin.y, tile_p0.y, tile_p1.y),
829
),
830
PictureRect::new(
831
clip_p0,
832
PictureSize::new(
833
clip_p1.x - clip_p0.x,
834
clip_p1.y - clip_p0.y,
835
),
836
),
837
)
838
} else {
839
(info.prim_origin, info.prim_clip_rect)
840
};
841
842
// Update the tile descriptor, used for tile comparison during scene swaps.
843
let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
844
845
// We know that the casts below will never overflow because the array lengths are
846
// truncated to MAX_PRIM_SUB_DEPS during update_prim_dependencies.
847
debug_assert!(info.spatial_nodes.len() <= MAX_PRIM_SUB_DEPS);
848
debug_assert!(info.clips.len() <= MAX_PRIM_SUB_DEPS);
849
debug_assert!(info.image_keys.len() <= MAX_PRIM_SUB_DEPS);
850
debug_assert!(info.opacity_bindings.len() <= MAX_PRIM_SUB_DEPS);
851
852
self.current_descriptor.prims.push(PrimitiveDescriptor {
853
prim_uid: info.prim_uid,
854
origin: prim_origin.into(),
855
prim_clip_rect: prim_clip_rect.into(),
856
transform_dep_count: info.spatial_nodes.len() as u8,
857
clip_dep_count: info.clips.len() as u8,
858
image_dep_count: info.image_keys.len() as u8,
859
opacity_binding_dep_count: info.opacity_bindings.len() as u8,
860
});
861
862
// Add this primitive to the dirty rect quadtree.
863
self.root.add_prim(prim_index, &info.prim_clip_rect);
864
}
865
866
/// Called during tile cache instance post_update. Allows invalidation and dirty
867
/// rect calculation after primitive dependencies have been updated.
868
fn post_update(
869
&mut self,
870
ctx: &TilePostUpdateContext,
871
state: &mut TilePostUpdateState,
872
) -> bool {
873
// If tile is not visible, just early out from here - we don't update dependencies
874
// so don't want to invalidate, merge, split etc. The tile won't need to be drawn
875
// (and thus updated / invalidated) until it is on screen again.
876
if !self.is_visible {
877
return false;
878
}
879
880
// Check if this tile can be considered opaque.
881
let tile_is_opaque = ctx.backdrop.rect.contains_rect(&self.clipped_rect);
882
let opacity_changed = tile_is_opaque != self.is_opaque;
883
self.is_opaque = tile_is_opaque;
884
885
// Invalidate the tile based on the content changing.
886
self.update_content_validity(ctx, state);
887
888
// If there are no primitives there is no need to draw or cache it.
889
if self.current_descriptor.prims.is_empty() {
890
return false;
891
}
892
893
// Check if the selected composite mode supports dirty rect updates. For Draw composite
894
// mode, we can always update the content with smaller dirty rects. For native composite
895
// mode, we can only use dirty rects if the compositor supports partial surface updates.
896
let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
897
CompositorKind::Draw { .. } => {
898
(true, true)
899
}
900
CompositorKind::Native { max_update_rects, .. } => {
901
(max_update_rects > 0, false)
902
}
903
};
904
905
// TODO(gw): Consider using smaller tiles and/or tile splits for
906
// native compositors that don't support dirty rects.
907
if supports_dirty_rects {
908
// Only allow splitting for normal content sized tiles
909
if ctx.current_tile_size == TILE_SIZE_DEFAULT {
910
let max_split_level = 3;
911
912
// Consider splitting / merging dirty regions
913
self.root.maybe_merge_or_split(
914
0,
915
&self.current_descriptor.prims,
916
max_split_level,
917
);
918
}
919
}
920
921
// The dirty rect will be set correctly by now. If the underlying platform
922
// doesn't support partial updates, and this tile isn't valid, force the dirty
923
// rect to be the size of the entire tile.
924
if !self.is_valid && !supports_dirty_rects {
925
self.dirty_rect = self.rect;
926
}
927
928
// Ensure that the dirty rect doesn't extend outside the local tile rect.
929
self.dirty_rect = self.dirty_rect
930
.intersection(&self.rect)
931
.unwrap_or(PictureRect::zero());
932
933
// See if this tile is a simple color, in which case we can just draw
934
// it as a rect, and avoid allocating a texture surface and drawing it.
935
// TODO(gw): Initial native compositor interface doesn't support simple
936
// color tiles. We can definitely support this in DC, so this
937
// should be added as a follow up.
938
let is_simple_prim =
939
ctx.backdrop.kind.can_be_promoted_to_compositor_surface() &&
940
self.current_descriptor.prims.len() == 1 &&
941
self.is_opaque &&
942
supports_simple_prims;
943
944
// Set up the backing surface for this tile.
945
let surface = if is_simple_prim {
946
// If we determine the tile can be represented by a color, set the
947
// surface unconditionally (this will drop any previously used
948
// texture cache backing surface).
949
match ctx.backdrop.kind {
950
BackdropKind::Color { color } => {
951
TileSurface::Color {
952
color,
953
}
954
}
955
BackdropKind::Clear => {
956
TileSurface::Clear
957
}
958
BackdropKind::Image => {
959
// This should be prevented by the is_simple_prim check above.
960
unreachable!();
961
}
962
}
963
} else {
964
// If this tile will be backed by a surface, we want to retain
965
// the texture handle from the previous frame, if possible. If
966
// the tile was previously a color, or not set, then just set
967
// up a new texture cache handle.
968
match self.surface.take() {
969
Some(TileSurface::Texture { mut descriptor, visibility_mask }) => {
970
// If opacity changed, and this is a native OS compositor surface,
971
// it needs to be recreated.
972
// TODO(gw): This is a limitation of the DirectComposite APIs. It might
973
// make sense on other platforms to be able to change this as
974
// a property on a surface, if we ever see pages where this
975
// is changing frequently.
976
if opacity_changed {
977
if let SurfaceTextureDescriptor::NativeSurface { ref mut id, .. } = descriptor {
978
// Reset the dirty rect and tile validity in this case, to
979
// force the new tile to be completely redrawn.
980
self.invalidate(None, InvalidationReason::SurfaceOpacityChanged);
981
982
// If this tile has a currently allocated native surface, destroy it. It
983
// will be re-allocated next time it's determined to be visible.
984
if let Some(id) = id.take() {
985
state.composite_state.destroy_surface(id);
986
}
987
}
988
}
989
990
// Reuse the existing descriptor and vis mask
991
TileSurface::Texture {
992
descriptor,
993
visibility_mask,
994
}
995
}
996
Some(TileSurface::Color { .. }) | Some(TileSurface::Clear) | None => {
997
// This is the case where we are constructing a tile surface that
998
// involves drawing to a texture. Create the correct surface
999
// descriptor depending on the compositing mode that will read
1000
// the output.
1001
let descriptor = match state.composite_state.compositor_kind {
1002
CompositorKind::Draw { .. } => {
1003
// For a texture cache entry, create an invalid handle that
1004
// will be allocated when update_picture_cache is called.
1005
SurfaceTextureDescriptor::TextureCache {
1006
handle: TextureCacheHandle::invalid(),
1007
}
1008
}
1009
CompositorKind::Native { .. } => {
1010
// Create a native surface surface descriptor, but don't allocate
1011
// a surface yet. The surface is allocated *after* occlusion
1012
// culling occurs, so that only visible tiles allocate GPU memory.
1013
SurfaceTextureDescriptor::NativeSurface {
1014
id: None,
1015
size: ctx.current_tile_size,
1016
}
1017
}
1018
};
1019
1020
TileSurface::Texture {
1021
descriptor,
1022
visibility_mask: PrimitiveVisibilityMask::empty(),
1023
}
1024
}
1025
}
1026
};
1027
1028
// Store the current surface backing info for use during batching.
1029
self.surface = Some(surface);
1030
1031
true
1032
}
1033
}
1034
1035
/// Defines a key that uniquely identifies a primitive instance.
1036
#[derive(Debug, Clone)]
1037
pub struct PrimitiveDescriptor {
1038
/// Uniquely identifies the content of the primitive template.
1039
prim_uid: ItemUid,
1040
/// The origin in world space of this primitive.
1041
origin: PointKey,
1042
/// The clip rect for this primitive. Included here in
1043
/// dependencies since there is no entry in the clip chain
1044
/// dependencies for the local clip rect.
1045
prim_clip_rect: RectangleKey,
1046
/// The number of extra dependencies that this primitive has.
1047
transform_dep_count: u8,
1048
image_dep_count: u8,
1049
opacity_binding_dep_count: u8,
1050
clip_dep_count: u8,
1051
}
1052
1053
impl PartialEq for PrimitiveDescriptor {
1054
fn eq(&self, other: &Self) -> bool {
1055
const EPSILON: f32 = 0.001;
1056
1057
if self.prim_uid != other.prim_uid {
1058
return false;
1059
}
1060
1061
if !self.origin.x.approx_eq_eps(&other.origin.x, &EPSILON) {
1062
return false;
1063
}
1064
if !self.origin.y.approx_eq_eps(&other.origin.y, &EPSILON) {
1065
return false;
1066
}
1067
1068
if !self.prim_clip_rect.x.approx_eq_eps(&other.prim_clip_rect.x, &EPSILON) {
1069
return false;
1070
}
1071
if !self.prim_clip_rect.y.approx_eq_eps(&other.prim_clip_rect.y, &EPSILON) {
1072
return false;
1073
}
1074
if !self.prim_clip_rect.w.approx_eq_eps(&other.prim_clip_rect.w, &EPSILON) {
1075
return false;
1076
}
1077
if !self.prim_clip_rect.h.approx_eq_eps(&other.prim_clip_rect.h, &EPSILON) {
1078
return false;
1079
}
1080
1081
true
1082
}
1083
}
1084
1085
/// A small helper to compare two arrays of primitive dependencies.
1086
struct CompareHelper<'a, T> {
1087
offset_curr: usize,
1088
offset_prev: usize,
1089
curr_items: &'a [T],
1090
prev_items: &'a [T],
1091
}
1092
1093
impl<'a, T> CompareHelper<'a, T> where T: PartialEq {
1094
/// Construct a new compare helper for a current / previous set of dependency information.
1095
fn new(
1096
prev_items: &'a [T],
1097
curr_items: &'a [T],
1098
) -> Self {
1099
CompareHelper {
1100
offset_curr: 0,
1101
offset_prev: 0,
1102
curr_items,
1103
prev_items,
1104
}
1105
}
1106
1107
/// Reset the current position in the dependency array to the start
1108
fn reset(&mut self) {
1109
self.offset_prev = 0;
1110
self.offset_curr = 0;
1111
}
1112
1113
/// Test if two sections of the dependency arrays are the same, by checking both
1114
/// item equality, and a user closure to see if the content of the item changed.
1115
fn is_same<F>(
1116
&self,
1117
prev_count: u8,
1118
curr_count: u8,
1119
f: F,
1120
) -> bool where F: Fn(&T) -> bool {
1121
// If the number of items is different, trivial reject.
1122
if prev_count != curr_count {
1123
return false;
1124
}
1125
// If both counts are 0, then no need to check these dependencies.
1126
if curr_count == 0 {
1127
return true;
1128
}
1129
// If both counts are u8::MAX, this is a sentinel that we can't compare these
1130
// deps, so just trivial reject.
1131
if curr_count as usize == MAX_PRIM_SUB_DEPS {
1132
return false;
1133
}
1134
1135
let end_prev = self.offset_prev + prev_count as usize;
1136
let end_curr = self.offset_curr + curr_count as usize;
1137
1138
let curr_items = &self.curr_items[self.offset_curr .. end_curr];
1139
let prev_items = &self.prev_items[self.offset_prev .. end_prev];
1140
1141
for (curr, prev) in curr_items.iter().zip(prev_items.iter()) {
1142
if prev != curr {
1143
return false;
1144
}
1145
1146
if f(curr) {
1147
return false;
1148
}
1149
}
1150
1151
true
1152
}
1153
1154
// Advance the prev dependency array by a given amount
1155
fn advance_prev(&mut self, count: u8) {
1156
self.offset_prev += count as usize;
1157
}
1158
1159
// Advance the current dependency array by a given amount
1160
fn advance_curr(&mut self, count: u8) {
1161
self.offset_curr += count as usize;
1162
}
1163
}
1164
1165
/// Uniquely describes the content of this tile, in a way that can be
1166
/// (reasonably) efficiently hashed and compared.
1167
pub struct TileDescriptor {
1168
/// List of primitive instance unique identifiers. The uid is guaranteed
1169
/// to uniquely describe the content of the primitive template, while
1170
/// the other parameters describe the clip chain and instance params.
1171
pub prims: Vec<PrimitiveDescriptor>,
1172
1173
/// List of clip node descriptors.
1174
clips: Vec<ItemUid>,
1175
1176
/// List of image keys that this tile depends on.
1177
image_keys: Vec<ImageKey>,
1178
1179
/// The set of opacity bindings that this tile depends on.
1180
// TODO(gw): Ugh, get rid of all opacity binding support!
1181
opacity_bindings: Vec<OpacityBinding>,
1182
1183
/// List of the effects of transforms that we care about
1184
/// tracking for this tile.
1185
transforms: Vec<SpatialNodeIndex>,
1186
}
1187
1188
impl TileDescriptor {
1189
fn new() -> Self {
1190
TileDescriptor {
1191
prims: Vec::new(),
1192
clips: Vec::new(),
1193
opacity_bindings: Vec::new(),
1194
image_keys: Vec::new(),
1195
transforms: Vec::new(),
1196
}
1197
}
1198
1199
/// Print debug information about this tile descriptor to a tree printer.
1200
fn print(&self, pt: &mut dyn PrintTreePrinter) {
1201
pt.new_level("current_descriptor".to_string());
1202
1203
pt.new_level("prims".to_string());
1204
for prim in &self.prims {
1205
pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
1206
pt.add_item(format!("origin: {},{}", prim.origin.x, prim.origin.y));
1207
pt.add_item(format!("clip: origin={},{} size={}x{}",
1208
prim.prim_clip_rect.x,
1209
prim.prim_clip_rect.y,
1210
prim.prim_clip_rect.w,
1211
prim.prim_clip_rect.h,
1212
));
1213
pt.add_item(format!("deps: t={} i={} o={} c={}",
1214
prim.transform_dep_count,
1215
prim.image_dep_count,
1216
prim.opacity_binding_dep_count,
1217
prim.clip_dep_count,
1218
));
1219
pt.end_level();
1220
}
1221
pt.end_level();
1222
1223
if !self.clips.is_empty() {
1224
pt.new_level("clips".to_string());
1225
for clip in &self.clips {
1226
pt.new_level(format!("clip uid={}", clip.get_uid()));
1227
pt.end_level();
1228
}
1229
pt.end_level();
1230
}
1231
1232
if !self.image_keys.is_empty() {
1233
pt.new_level("image_keys".to_string());
1234
for key in &self.image_keys {
1235
pt.new_level(format!("key={:?}", key));
1236
pt.end_level();
1237
}
1238
pt.end_level();
1239
}
1240
1241
if !self.opacity_bindings.is_empty() {
1242
pt.new_level("opacity_bindings".to_string());
1243
for opacity_binding in &self.opacity_bindings {
1244
pt.new_level(format!("binding={:?}", opacity_binding));
1245
pt.end_level();
1246
}
1247
pt.end_level();
1248
}
1249
1250
if !self.transforms.is_empty() {
1251
pt.new_level("transforms".to_string());
1252
for transform in &self.transforms {
1253
pt.new_level(format!("spatial_node={:?}", transform));
1254
pt.end_level();
1255
}
1256
pt.end_level();
1257
}
1258
1259
pt.end_level();
1260
}
1261
1262
/// Clear the dependency information for a tile, when the dependencies
1263
/// are being rebuilt.
1264
fn clear(&mut self) {
1265
self.prims.clear();
1266
self.clips.clear();
1267
self.opacity_bindings.clear();
1268
self.image_keys.clear();
1269
self.transforms.clear();
1270
}
1271
}
1272
1273
/// Stores both the world and devices rects for a single dirty rect.
1274
#[derive(Debug, Clone)]
1275
pub struct DirtyRegionRect {
1276
/// World rect of this dirty region
1277
pub world_rect: WorldRect,
1278
/// Bitfield for picture render tasks that draw this dirty region.
1279
pub visibility_mask: PrimitiveVisibilityMask,
1280
}
1281
1282
/// Represents the dirty region of a tile cache picture.
1283
#[derive(Debug, Clone)]
1284
pub struct DirtyRegion {
1285
/// The individual dirty rects of this region.
1286
pub dirty_rects: Vec<DirtyRegionRect>,
1287
1288
/// The overall dirty rect, a combination of dirty_rects
1289
pub combined: WorldRect,
1290
}
1291
1292
impl DirtyRegion {
1293
/// Construct a new dirty region tracker.
1294
pub fn new(
1295
) -> Self {
1296
DirtyRegion {
1297
dirty_rects: Vec::with_capacity(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS),
1298
combined: WorldRect::zero(),
1299
}
1300
}
1301
1302
/// Reset the dirty regions back to empty
1303
pub fn clear(&mut self) {
1304
self.dirty_rects.clear();
1305
self.combined = WorldRect::zero();
1306
}
1307
1308
/// Push a dirty rect into this region
1309
pub fn push(
1310
&mut self,
1311
rect: WorldRect,
1312
visibility_mask: PrimitiveVisibilityMask,
1313
) {
1314
// Include this in the overall dirty rect
1315
self.combined = self.combined.union(&rect);
1316
1317
// Store the individual dirty rect.
1318
self.dirty_rects.push(DirtyRegionRect {
1319
world_rect: rect,
1320
visibility_mask,
1321
});
1322
}
1323
1324
/// Include another rect into an existing dirty region.
1325
pub fn include_rect(
1326
&mut self,
1327
region_index: usize,
1328
rect: WorldRect,
1329
) {
1330
self.combined = self.combined.union(&rect);
1331
1332
let region = &mut self.dirty_rects[region_index];
1333
region.world_rect = region.world_rect.union(&rect);
1334
}
1335
1336
// TODO(gw): This returns a heap allocated object. Perhaps we can simplify this
1337
// logic? Although - it's only used very rarely so it may not be an issue.
1338
pub fn inflate(
1339
&self,
1340
inflate_amount: f32,
1341
) -> DirtyRegion {
1342
let mut dirty_rects = Vec::with_capacity(self.dirty_rects.len());
1343
let mut combined = WorldRect::zero();
1344
1345
for rect in &self.dirty_rects {
1346
let world_rect = rect.world_rect.inflate(inflate_amount, inflate_amount);
1347
combined = combined.union(&world_rect);
1348
dirty_rects.push(DirtyRegionRect {
1349
world_rect,
1350
visibility_mask: rect.visibility_mask,
1351
});
1352
}
1353
1354
DirtyRegion {
1355
dirty_rects,
1356
combined,
1357
}
1358
}
1359
1360
/// Creates a record of this dirty region for exporting to test infrastructure.
1361
pub fn record(&self) -> RecordedDirtyRegion {
1362
let mut rects: Vec<WorldRect> =
1363
self.dirty_rects.iter().map(|r| r.world_rect.clone()).collect();
1364
rects.sort_unstable_by_key(|r| (r.origin.y as usize, r.origin.x as usize));
1365
RecordedDirtyRegion { rects }
1366
}
1367
}
1368
1369
/// A recorded copy of the dirty region for exporting to test infrastructure.
1370
#[cfg_attr(feature = "capture", derive(Serialize))]
1371
pub struct RecordedDirtyRegion {
1372
pub rects: Vec<WorldRect>,
1373
}
1374
1375
impl ::std::fmt::Display for RecordedDirtyRegion {
1376
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
1377
for r in self.rects.iter() {
1378
let (x, y, w, h) = (r.origin.x, r.origin.y, r.size.width, r.size.height);
1379
write!(f, "[({},{}):{}x{}]", x, y, w, h)?;
1380
}
1381
Ok(())
1382
}
1383
}
1384
1385
impl ::std::fmt::Debug for RecordedDirtyRegion {
1386
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
1387
::std::fmt::Display::fmt(self, f)
1388
}
1389
}
1390
1391
#[derive(Debug, Copy, Clone)]
1392
enum BackdropKind {
1393
Color {
1394
color: ColorF,
1395
},
1396
Clear,
1397
Image,
1398
}
1399
1400
impl BackdropKind {
1401
/// Returns true if the compositor can directly draw this backdrop.
1402
fn can_be_promoted_to_compositor_surface(&self) -> bool {
1403
match self {
1404
BackdropKind::Color { .. } | BackdropKind::Clear => true,
1405
BackdropKind::Image => false,
1406
}
1407
}
1408
}
1409
1410
/// Stores information about the calculated opaque backdrop of this slice.
1411
#[derive(Debug, Copy, Clone)]
1412
struct BackdropInfo {
1413
/// The picture space rectangle that is known to be opaque. This is used
1414
/// to determine where subpixel AA can be used, and where alpha blending
1415
/// can be disabled.
1416
rect: PictureRect,
1417
/// Kind of the backdrop
1418
kind: BackdropKind,
1419
}
1420
1421
impl BackdropInfo {
1422
fn empty() -> Self {
1423
BackdropInfo {
1424
rect: PictureRect::zero(),
1425
kind: BackdropKind::Color {
1426
color: ColorF::BLACK,
1427
},
1428
}
1429
}
1430
}
1431
1432
/// Represents a cache of tiles that make up a picture primitives.
1433
pub struct TileCacheInstance {
1434
/// Index of the tile cache / slice for this frame builder. It's determined
1435
/// by the setup_picture_caching method during flattening, which splits the
1436
/// picture tree into multiple slices. It's used as a simple input to the tile
1437
/// keys. It does mean we invalidate tiles if a new layer gets inserted / removed
1438
/// between display lists - this seems very unlikely to occur on most pages, but
1439
/// can be revisited if we ever notice that.
1440
pub slice: usize,
1441
/// The currently selected tile size to use for this cache
1442
pub current_tile_size: DeviceIntSize,
1443
/// The positioning node for this tile cache.
1444
pub spatial_node_index: SpatialNodeIndex,
1445
/// Hash of tiles present in this picture.
1446
pub tiles: FastHashMap<TileOffset, Tile>,
1447
/// A helper struct to map local rects into surface coords.
1448
map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
1449
/// A helper struct to map child picture rects into picture cache surface coords.
1450
map_child_pic_to_surface: SpaceMapper<PicturePixel, PicturePixel>,
1451
/// List of opacity bindings, with some extra information
1452
/// about whether they changed since last frame.
1453
opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
1454
/// List of spatial nodes, with some extra information
1455
/// about whether they changed since last frame.
1456
spatial_nodes: FastHashMap<SpatialNodeIndex, SpatialNodeDependency>,
1457
/// A set of spatial nodes that primitives / clips depend on found
1458
/// during dependency creation. This is used to avoid trying to
1459
/// calculate invalid relative transforms when building the spatial
1460
/// nodes hash above.
1461
used_spatial_nodes: FastHashSet<SpatialNodeIndex>,
1462
/// The current dirty region tracker for this picture.
1463
pub dirty_region: DirtyRegion,
1464
/// Current size of tiles in picture units.
1465
tile_size: PictureSize,
1466
/// Tile coords of the currently allocated grid.
1467
tile_rect: TileRect,
1468
/// Pre-calculated versions of the tile_rect above, used to speed up the
1469
/// calculations in get_tile_coords_for_rect.
1470
tile_bounds_p0: TileOffset,
1471
tile_bounds_p1: TileOffset,
1472
/// Local rect (unclipped) of the picture this cache covers.
1473
pub local_rect: PictureRect,
1474
/// The local clip rect, from the shared clips of this picture.
1475
local_clip_rect: PictureRect,
1476
/// A list of tiles that are valid and visible, which should be drawn to the main scene.
1477
pub tiles_to_draw: Vec<TileOffset>,
1478
/// The surface index that this tile cache will be drawn into.
1479
surface_index: SurfaceIndex,
1480
/// The background color from the renderer. If this is set opaque, we know it's
1481
/// fine to clear the tiles to this and allow subpixel text on the first slice.
1482
pub background_color: Option<ColorF>,
1483
/// Information about the calculated backdrop content of this cache.
1484
backdrop: BackdropInfo,
1485
/// The allowed subpixel mode for this surface, which depends on the detected
1486
/// opacity of the background.
1487
pub subpixel_mode: SubpixelMode,
1488
/// A list of clip handles that exist on every (top-level) primitive in this picture.
1489
/// It's often the case that these are root / fixed position clips. By handling them
1490
/// here, we can avoid applying them to the items, which reduces work, but more importantly
1491
/// reduces invalidations.
1492
pub shared_clips: Vec<ClipDataHandle>,
1493
/// The clip chain that represents the shared_clips above. Used to build the local
1494
/// clip rect for this tile cache.
1495
shared_clip_chain: ClipChainId,
1496
/// The current transform of the picture cache root spatial node
1497
root_transform: TransformKey,
1498
/// The number of frames until this cache next evaluates what tile size to use.
1499
/// If a picture rect size is regularly changing just around a size threshold,
1500
/// we don't want to constantly invalidate and reallocate different tile size
1501
/// configuration each frame.
1502
frames_until_size_eval: usize,
1503
/// The current fractional offset of the cached picture
1504
fract_offset: PictureVector2D,
1505
}
1506
1507
impl TileCacheInstance {
1508
pub fn new(
1509
slice: usize,
1510
spatial_node_index: SpatialNodeIndex,
1511
background_color: Option<ColorF>,
1512
shared_clips: Vec<ClipDataHandle>,
1513
shared_clip_chain: ClipChainId,
1514
) -> Self {
1515
TileCacheInstance {
1516
slice,
1517
spatial_node_index,
1518
tiles: FastHashMap::default(),
1519
map_local_to_surface: SpaceMapper::new(
1520
ROOT_SPATIAL_NODE_INDEX,
1521
PictureRect::zero(),
1522
),
1523
map_child_pic_to_surface: SpaceMapper::new(
1524
ROOT_SPATIAL_NODE_INDEX,
1525
PictureRect::zero(),
1526
),
1527
opacity_bindings: FastHashMap::default(),
1528
spatial_nodes: FastHashMap::default(),
1529
used_spatial_nodes: FastHashSet::default(),
1530
dirty_region: DirtyRegion::new(),
1531
tile_size: PictureSize::zero(),
1532
tile_rect: TileRect::zero(),
1533
tile_bounds_p0: TileOffset::zero(),
1534
tile_bounds_p1: TileOffset::zero(),
1535
local_rect: PictureRect::zero(),
1536
local_clip_rect: PictureRect::zero(),
1537
tiles_to_draw: Vec::new(),
1538
surface_index: SurfaceIndex(0),
1539
background_color,
1540
backdrop: BackdropInfo::empty(),
1541
subpixel_mode: SubpixelMode::Allow,
1542
root_transform: TransformKey::Local,
1543
shared_clips,
1544
shared_clip_chain,
1545
current_tile_size: DeviceIntSize::zero(),
1546
frames_until_size_eval: 0,
1547
fract_offset: PictureVector2D::zero(),
1548
}
1549
}
1550
1551
/// Returns true if this tile cache is considered opaque.
1552
pub fn is_opaque(&self) -> bool {
1553
// If known opaque due to background clear color and being the first slice.
1554
// The background_color will only be Some(..) if this is the first slice.
1555
match self.background_color {
1556
Some(color) => color.a >= 1.0,
1557
None => false
1558
}
1559
}
1560
1561
/// Get the tile coordinates for a given rectangle.
1562
fn get_tile_coords_for_rect(
1563
&self,
1564
rect: &PictureRect,
1565
) -> (TileOffset, TileOffset) {
1566
// Get the tile coordinates in the picture space.
1567
let mut p0 = TileOffset::new(
1568
(rect.origin.x / self.tile_size.width).floor() as i32,
1569
(rect.origin.y / self.tile_size.height).floor() as i32,
1570
);
1571
1572
let mut p1 = TileOffset::new(
1573
((rect.origin.x + rect.size.width) / self.tile_size.width).ceil() as i32,
1574
((rect.origin.y + rect.size.height) / self.tile_size.height).ceil() as i32,
1575
);
1576
1577
// Clamp the tile coordinates here to avoid looping over irrelevant tiles later on.
1578
p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
1579
p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
1580
p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
1581
p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
1582
1583
(p0, p1)
1584
}
1585
1586
/// Update transforms, opacity bindings and tile rects.
1587
pub fn pre_update(
1588
&mut self,
1589
pic_rect: PictureRect,
1590
surface_index: SurfaceIndex,
1591
frame_context: &FrameVisibilityContext,
1592
frame_state: &mut FrameVisibilityState,
1593
) -> WorldRect {
1594
self.surface_index = surface_index;
1595
self.local_rect = pic_rect;
1596
self.local_clip_rect = PictureRect::max_rect();
1597
1598
// Reset the opaque rect + subpixel mode, as they are calculated
1599
// during the prim dependency checks.
1600
self.backdrop = BackdropInfo::empty();
1601
self.subpixel_mode = SubpixelMode::Allow;
1602
1603
self.map_local_to_surface = SpaceMapper::new(
1604
self.spatial_node_index,
1605
PictureRect::from_untyped(&pic_rect.to_untyped()),
1606
);
1607
self.map_child_pic_to_surface = SpaceMapper::new(
1608
self.spatial_node_index,
1609
PictureRect::from_untyped(&pic_rect.to_untyped()),
1610
);
1611
1612
let pic_to_world_mapper = SpaceMapper::new_with_target(
1613
ROOT_SPATIAL_NODE_INDEX,
1614
self.spatial_node_index,
1615
frame_context.global_screen_world_rect,
1616
frame_context.clip_scroll_tree,
1617
);
1618
1619
// If there is a valid set of shared clips, build a clip chain instance for this,
1620
// which will provide a local clip rect. This is useful for establishing things
1621
// like whether the backdrop rect supplied by Gecko can be considered opaque.
1622
if self.shared_clip_chain != ClipChainId::NONE {
1623
let mut shared_clips = Vec::new();
1624
let mut current_clip_chain_id = self.shared_clip_chain;
1625
while current_clip_chain_id != ClipChainId::NONE {
1626
shared_clips.push(current_clip_chain_id);
1627
let clip_chain_node = &frame_state.clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
1628
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
1629
}
1630
1631
frame_state.clip_store.set_active_clips(
1632
LayoutRect::max_rect(),
1633
self.spatial_node_index,
1634
&shared_clips,
1635
frame_context.clip_scroll_tree,
1636
&mut frame_state.data_stores.clip,
1637
);
1638
1639
let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance(
1640
LayoutRect::from_untyped(&pic_rect.to_untyped()),
1641
&self.map_local_to_surface,
1642
&pic_to_world_mapper,
1643
frame_context.clip_scroll_tree,
1644
frame_state.gpu_cache,
1645
frame_state.resource_cache,
1646
frame_context.global_device_pixel_scale,
1647
&frame_context.global_screen_world_rect,
1648
&mut frame_state.data_stores.clip,
1649
true,
1650
false,
1651
);
1652
1653
// Ensure that if the entire picture cache is clipped out, the local
1654
// clip rect is zero. This makes sure we don't register any occluders
1655
// that are actually off-screen.
1656
self.local_clip_rect = clip_chain_instance.map_or(PictureRect::zero(), |clip_chain_instance| {
1657
clip_chain_instance.pic_clip_rect
1658
});
1659
}
1660
1661
// If there are pending retained state, retrieve it.
1662
if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) {
1663
self.tiles.extend(prev_state.tiles);
1664
self.root_transform = prev_state.root_transform;
1665
self.spatial_nodes = prev_state.spatial_nodes;
1666
self.opacity_bindings = prev_state.opacity_bindings;
1667
self.current_tile_size = prev_state.current_tile_size;
1668
}
1669
1670
// Only evaluate what tile size to use fairly infrequently, so that we don't end
1671
// up constantly invalidating and reallocating tiles if the picture rect size is
1672
// changing near a threshold value.
1673
if self.frames_until_size_eval == 0 {
1674
const TILE_SIZE_TINY: f32 = 32.0;
1675
const TILE_SIZE_LARGE: f32 = 512.0;
1676
1677
// Work out what size tile is appropriate for this picture cache.
1678
let desired_tile_size;
1679
1680
if pic_rect.size.width <= TILE_SIZE_TINY && pic_rect.size.height > TILE_SIZE_LARGE {
1681
desired_tile_size = TILE_SIZE_SCROLLBAR_VERTICAL;
1682
} else if pic_rect.size.width > TILE_SIZE_LARGE && pic_rect.size.height <= TILE_SIZE_TINY {
1683
desired_tile_size = TILE_SIZE_SCROLLBAR_HORIZONTAL;
1684
} else {
1685
desired_tile_size = TILE_SIZE_DEFAULT;
1686
}
1687
1688
// If the desired tile size has changed, then invalidate and drop any
1689
// existing tiles.
1690
if desired_tile_size != self.current_tile_size {
1691
// Destroy any native surfaces on the tiles that will be dropped due
1692
// to resizing.
1693
frame_state.composite_state.destroy_native_surfaces(
1694
self.tiles.values(),
1695
);
1696
self.tiles.clear();
1697
self.current_tile_size = desired_tile_size;
1698
}
1699
1700
// Reset counter until next evaluating the desired tile size. This is an
1701
// arbitrary value.
1702
self.frames_until_size_eval = 120;
1703
}
1704
1705
// Map an arbitrary point in picture space to world space, to work out
1706
// what the fractional translation is that's applied by this scroll root.
1707
// TODO(gw): I'm not 100% sure this is right. At least, in future, we should
1708
// make a specific API for this, and/or enforce that the picture
1709
// cache transform only includes scale and/or translation (we
1710
// already ensure it doesn't have perspective).
1711
let world_origin = pic_to_world_mapper
1712
.map(&PictureRect::new(PicturePoint::zero(), PictureSize::new(1.0, 1.0)))
1713
.expect("bug: unable to map origin to world space")
1714
.origin;
1715
1716
// Get the desired integer device coordinate
1717
let device_origin = world_origin * frame_context.global_device_pixel_scale;
1718
let desired_device_origin = device_origin.round();
1719
1720
// Unmap from device space to world space rect
1721
let ref_world_rect = WorldRect::new(
1722
desired_device_origin / frame_context.global_device_pixel_scale,
1723
WorldSize::new(1.0, 1.0),
1724
);
1725
1726
// Unmap from world space to picture space
1727
let ref_point = pic_to_world_mapper
1728
.unmap(&ref_world_rect)
1729
.expect("bug: unable to unmap ref world rect")
1730
.origin;
1731
1732
// Extract the fractional offset required in picture space to align in device space
1733
self.fract_offset = PictureVector2D::new(
1734
ref_point.x.fract(),
1735
ref_point.y.fract(),
1736
);
1737
1738
// Do a hacky diff of opacity binding values from the last frame. This is
1739
// used later on during tile invalidation tests.
1740
let current_properties = frame_context.scene_properties.float_properties();
1741
let old_properties = mem::replace(&mut self.opacity_bindings, FastHashMap::default());
1742
1743
for (id, value) in current_properties {
1744
let changed = match old_properties.get(id) {
1745
Some(old_property) => !old_property.value.approx_eq(value),
1746
None => true,
1747
};
1748
self.opacity_bindings.insert(*id, OpacityBindingInfo {
1749
value: *value,
1750
changed,
1751
});
1752
}
1753
1754
let world_tile_size = WorldSize::new(
1755
self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
1756
self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
1757
);
1758
1759
// We know that this is an exact rectangle, since we (for now) only support tile
1760
// caches where the scroll root is in the root coordinate system.
1761
let local_tile_rect = pic_to_world_mapper
1762
.unmap(&WorldRect::new(WorldPoint::zero(), world_tile_size))
1763
.expect("bug: unable to get local tile rect");
1764
1765
self.tile_size = local_tile_rect.size;
1766
1767
let screen_rect_in_pic_space = pic_to_world_mapper
1768
.unmap(&frame_context.global_screen_world_rect)
1769
.expect("unable to unmap screen rect");
1770
1771
// Inflate the needed rect a bit, so that we retain tiles that we have drawn
1772
// but have just recently gone off-screen. This means that we avoid re-drawing
1773
// tiles if the user is scrolling up and down small amounts, at the cost of
1774
// a bit of extra texture memory.
1775
let desired_rect_in_pic_space = screen_rect_in_pic_space
1776
.inflate(0.0, 3.0 * self.tile_size.height);
1777
1778
let needed_rect_in_pic_space = desired_rect_in_pic_space
1779
.intersection(&pic_rect)
1780
.unwrap_or(PictureRect::zero());
1781
1782
let p0 = needed_rect_in_pic_space.origin;
1783
let p1 = needed_rect_in_pic_space.bottom_right();
1784
1785
let x0 = (p0.x / local_tile_rect.size.width).floor() as i32;
1786
let x1 = (p1.x / local_tile_rect.size.width).ceil() as i32;
1787
1788
let y0 = (p0.y / local_tile_rect.size.height).floor() as i32;
1789
let y1 = (p1.y / local_tile_rect.size.height).ceil() as i32;
1790
1791
let x_tiles = x1 - x0;
1792
let y_tiles = y1 - y0;
1793
self.tile_rect = TileRect::new(
1794
TileOffset::new(x0, y0),
1795
TileSize::new(x_tiles, y_tiles),
1796
);
1797
// This is duplicated information from tile_rect, but cached here to avoid
1798
// redundant calculations during get_tile_coords_for_rect
1799
self.tile_bounds_p0 = TileOffset::new(x0, y0);
1800
self.tile_bounds_p1 = TileOffset::new(x1, y1);
1801
1802
let mut world_culling_rect = WorldRect::zero();
1803
1804
let mut old_tiles = mem::replace(
1805
&mut self.tiles,
1806
FastHashMap::default(),
1807
);
1808
1809
let ctx = TilePreUpdateContext {
1810
local_rect: self.local_rect,
1811
local_clip_rect: self.local_clip_rect,
1812
pic_to_world_mapper,
1813
fract_offset: self.fract_offset,
1814
background_color: self.background_color,
1815
global_screen_world_rect: frame_context.global_screen_world_rect,
1816
};
1817
1818
for y in y0 .. y1 {
1819
for x in x0 .. x1 {
1820
let key = TileOffset::new(x, y);
1821
1822
let mut tile = old_tiles
1823
.remove(&key)
1824
.unwrap_or_else(|| {
1825
let next_id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
1826
Tile::new(next_id)
1827
});
1828
1829
// Ensure each tile is offset by the appropriate amount from the
1830
// origin, such that the content origin will be a whole number and
1831
// the snapping will be consistent.
1832
let rect = PictureRect::new(
1833
PicturePoint::new(
1834
x as f32 * self.tile_size.width + self.fract_offset.x,
1835
y as f32 * self.tile_size.height + self.fract_offset.y,
1836
),
1837
self.tile_size,
1838
);
1839
1840
tile.pre_update(
1841
rect,
1842
&ctx,
1843
);
1844
1845
// Only include the tiles that are currently in view into the world culling
1846
// rect. This is a very important optimization for a couple of reasons:
1847
// (1) Primitives that intersect with tiles in the grid that are not currently
1848
// visible can be skipped from primitive preparation, clip chain building
1849
// and tile dependency updates.
1850
// (2) When we need to allocate an off-screen surface for a child picture (for
1851
// example a CSS filter) we clip the size of the GPU surface to the world
1852
// culling rect below (to ensure we draw enough of it to be sampled by any
1853
// tiles that reference it). Making the world culling rect only affected
1854
// by visible tiles (rather than the entire virtual tile display port) can
1855
// result in allocating _much_ smaller GPU surfaces for cases where the
1856
// true off-screen surface size is very large.
1857
if tile.is_visible {
1858
world_culling_rect = world_culling_rect.union(&tile.world_rect);
1859
}
1860
1861
self.tiles.insert(key, tile);
1862
}
1863
}
1864
1865
// Any old tiles that remain after the loop above are going to be dropped. For
1866
// simple composite mode, the texture cache handle will expire and be collected
1867
// by the texture cache. For native compositor mode, we need to explicitly
1868
// invoke a callback to the client to destroy that surface.
1869
frame_state.composite_state.destroy_native_surfaces(
1870
old_tiles.values(),
1871
);
1872
1873
world_culling_rect
1874
}
1875
1876
/// Update the dependencies for each tile for a given primitive instance.
1877
pub fn update_prim_dependencies(
1878
&mut self,
1879
prim_instance: &PrimitiveInstance,
1880
prim_spatial_node_index: SpatialNodeIndex,
1881
prim_clip_chain: Option<&ClipChainInstance>,
1882
local_prim_rect: LayoutRect,
1883
clip_scroll_tree: &ClipScrollTree,
1884
data_stores: &DataStores,
1885
clip_store: &ClipStore,
1886
pictures: &[PicturePrimitive],
1887
resource_cache: &ResourceCache,
1888
opacity_binding_store: &OpacityBindingStorage,
1889
image_instances: &ImageInstanceStorage,
1890
surface_index: SurfaceIndex,
1891
surface_spatial_node_index: SpatialNodeIndex,
1892
) -> bool {
1893
// If the primitive is completely clipped out by the clip chain, there
1894
// is no need to add it to any primitive dependencies.
1895
let prim_clip_chain = match prim_clip_chain {
1896
Some(prim_clip_chain) => prim_clip_chain,
1897
None => return false,
1898
};
1899
1900
self.map_local_to_surface.set_target_spatial_node(
1901
prim_spatial_node_index,
1902
clip_scroll_tree,
1903
);
1904
1905
// Map the primitive local rect into picture space.
1906
let prim_rect = match self.map_local_to_surface.map(&local_prim_rect) {
1907
Some(rect) => rect,
1908
None => return false,
1909
};
1910
1911
// If the rect is invalid, no need to create dependencies.
1912
if prim_rect.size.is_empty_or_negative() {
1913
return false;
1914
}
1915
1916
// If the primitive is directly drawn onto this picture cache surface, then
1917
// the pic_clip_rect is in the same space. If not, we need to map it from