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