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
use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, PrimitiveFlags};
6
use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
7
use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData};
8
use api::{FilterOp, FilterPrimitive, FontInstanceKey, GlyphInstance, GlyphOptions, GradientStop};
9
use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
10
use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode};
11
use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
12
use api::{Shadow, SpaceAndClipInfo, SpatialId, StackingContext, StickyFrameDisplayItem};
13
use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
14
use api::units::*;
15
use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind};
16
use crate::clip::{ClipInternData, ClipDataHandle, ClipNodeKind};
17
use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex};
18
use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
19
use crate::glyph_rasterizer::FontInstance;
20
use crate::hit_test::{HitTestingItem, HitTestingScene};
21
use crate::image::simplify_repeated_primitive;
22
use crate::intern::Interner;
23
use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo, Filter};
24
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions};
25
use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, TileCacheInstance, ClusterFlags};
26
use crate::prim_store::{PrimitiveInstance, PrimitiveSceneData};
27
use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
28
use crate::prim_store::{ScrollNodeAndClipChain, PictureIndex};
29
use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex};
30
use crate::prim_store::{register_prim_chase_id, get_line_decoration_size};
31
use crate::prim_store::{SpaceSnapper};
32
use crate::prim_store::backdrop::Backdrop;
33
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
34
use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientParams};
35
use crate::prim_store::image::{Image, YuvImage};
36
use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey};
37
use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
38
use crate::prim_store::text_run::TextRun;
39
use crate::render_backend::{DocumentView};
40
use crate::resource_cache::{FontInstanceMap, ImageRequest};
41
use crate::scene::{Scene, BuiltScene, SceneStats, StackingContextHelpers};
42
use crate::scene_builder_thread::Interners;
43
use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind};
44
use euclid::approxeq::ApproxEq;
45
use std::{f32, mem, usize, ops};
46
use std::collections::vec_deque::VecDeque;
47
use std::sync::Arc;
48
use crate::util::{MaxRect, VecHelper};
49
use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
50
51
#[derive(Debug, Copy, Clone)]
52
struct ClipNode {
53
id: ClipChainId,
54
count: usize,
55
}
56
57
58
59
impl ClipNode {
60
fn new(id: ClipChainId, count: usize) -> Self {
61
ClipNode {
62
id,
63
count,
64
}
65
}
66
}
67
68
/// The offset stack for a given reference frame.
69
struct ReferenceFrameState {
70
/// A stack of current offsets from the current reference frame scope.
71
offsets: Vec<LayoutVector2D>,
72
}
73
74
/// Maps from stacking context layout coordinates into reference frame
75
/// relative coordinates.
76
struct ReferenceFrameMapper {
77
/// A stack of reference frame scopes.
78
frames: Vec<ReferenceFrameState>,
79
}
80
81
impl ReferenceFrameMapper {
82
fn new() -> Self {
83
ReferenceFrameMapper {
84
frames: vec![
85
ReferenceFrameState {
86
offsets: vec![
87
LayoutVector2D::zero(),
88
],
89
}
90
],
91
}
92
}
93
94
/// Push a new scope. This resets the current offset to zero, and is
95
/// used when a new reference frame or iframe is pushed.
96
fn push_scope(&mut self) {
97
self.frames.push(ReferenceFrameState {
98
offsets: vec![
99
LayoutVector2D::zero(),
100
],
101
});
102
}
103
104
/// Pop a reference frame scope off the stack.
105
fn pop_scope(&mut self) {
106
self.frames.pop().unwrap();
107
}
108
109
/// Push a new offset for the current scope. This is used when
110
/// a new stacking context is pushed.
111
fn push_offset(&mut self, offset: LayoutVector2D) {
112
let frame = self.frames.last_mut().unwrap();
113
let current_offset = *frame.offsets.last().unwrap();
114
frame.offsets.push(current_offset + offset);
115
}
116
117
/// Pop a local stacking context offset from the current scope.
118
fn pop_offset(&mut self) {
119
let frame = self.frames.last_mut().unwrap();
120
frame.offsets.pop().unwrap();
121
}
122
123
/// Retrieve the current offset to allow converting a stacking context
124
/// relative coordinate to be relative to the owing reference frame.
125
/// TODO(gw): We could perhaps have separate coordinate spaces for this,
126
/// however that's going to either mean a lot of changes to
127
/// public API code, or a lot of changes to internal code.
128
/// Before doing that, we should revisit how Gecko would
129
/// prefer to provide coordinates.
130
/// TODO(gw): For now, this includes only the reference frame relative
131
/// offset. Soon, we will expand this to include the initial
132
/// scroll offsets that are now available on scroll nodes. This
133
/// will allow normalizing the coordinates even between display
134
/// lists where APZ has scrolled the content.
135
fn current_offset(&self) -> LayoutVector2D {
136
*self.frames.last().unwrap().offsets.last().unwrap()
137
}
138
}
139
140
/// Offsets primitives (and clips) by the external scroll offset
141
/// supplied to scroll nodes.
142
pub struct ScrollOffsetMapper {
143
pub current_spatial_node: SpatialNodeIndex,
144
pub current_offset: LayoutVector2D,
145
}
146
147
impl ScrollOffsetMapper {
148
fn new() -> Self {
149
ScrollOffsetMapper {
150
current_spatial_node: SpatialNodeIndex::INVALID,
151
current_offset: LayoutVector2D::zero(),
152
}
153
}
154
155
/// Return the accumulated external scroll offset for a spatial
156
/// node. This caches the last result, which is the common case,
157
/// or defers to the spatial tree to build the value.
158
fn external_scroll_offset(
159
&mut self,
160
spatial_node_index: SpatialNodeIndex,
161
spatial_tree: &SpatialTree,
162
) -> LayoutVector2D {
163
if spatial_node_index != self.current_spatial_node {
164
self.current_spatial_node = spatial_node_index;
165
self.current_offset = spatial_tree.external_scroll_offset(spatial_node_index);
166
}
167
168
self.current_offset
169
}
170
}
171
172
/// A data structure that keeps track of mapping between API Ids for clips/spatials and the indices
173
/// used internally in the SpatialTree to avoid having to do HashMap lookups. NodeIdToIndexMapper
174
/// is responsible for mapping both ClipId to ClipChainIndex and SpatialId to SpatialNodeIndex.
175
#[derive(Default)]
176
pub struct NodeIdToIndexMapper {
177
clip_node_map: FastHashMap<ClipId, ClipNode>,
178
spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
179
}
180
181
impl NodeIdToIndexMapper {
182
pub fn add_clip_chain(
183
&mut self,
184
id: ClipId,
185
index: ClipChainId,
186
count: usize,
187
) {
188
let _old_value = self.clip_node_map.insert(id, ClipNode::new(index, count));
189
debug_assert!(_old_value.is_none());
190
}
191
192
pub fn map_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
193
let _old_value = self.spatial_node_map.insert(id, index);
194
debug_assert!(_old_value.is_none());
195
}
196
197
fn get_clip_node(&self, id: &ClipId) -> ClipNode {
198
self.clip_node_map[id]
199
}
200
201
pub fn get_clip_chain_id(&self, id: ClipId) -> ClipChainId {
202
self.clip_node_map[&id].id
203
}
204
205
pub fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
206
self.spatial_node_map[&id]
207
}
208
}
209
210
#[derive(Debug, Clone, Default)]
211
pub struct CompositeOps {
212
// Requires only a single texture as input (e.g. most filters)
213
pub filters: Vec<Filter>,
214
pub filter_datas: Vec<FilterData>,
215
pub filter_primitives: Vec<FilterPrimitive>,
216
217
// Requires two source textures (e.g. mix-blend-mode)
218
pub mix_blend_mode: Option<MixBlendMode>,
219
}
220
221
impl CompositeOps {
222
pub fn new(
223
filters: Vec<Filter>,
224
filter_datas: Vec<FilterData>,
225
filter_primitives: Vec<FilterPrimitive>,
226
mix_blend_mode: Option<MixBlendMode>
227
) -> Self {
228
CompositeOps {
229
filters,
230
filter_datas,
231
filter_primitives,
232
mix_blend_mode,
233
}
234
}
235
236
pub fn is_empty(&self) -> bool {
237
self.filters.is_empty() &&
238
self.filter_primitives.is_empty() &&
239
self.mix_blend_mode.is_none()
240
}
241
}
242
243
/// Information about unpaired Push/Pop clip chain instances that need to be fixed up.
244
struct ClipChainPairInfo {
245
spatial_node_index: SpatialNodeIndex,
246
clip_chain_id: ClipChainId,
247
}
248
249
bitflags! {
250
/// Slice flags
251
pub struct SliceFlags : u8 {
252
/// Slice created by a cluster that has ClusterFlags::SCROLLBAR_CONTAINER
253
const IS_SCROLLBAR = 1;
254
}
255
}
256
257
/// Information about a set of primitive clusters that will form a picture cache slice.
258
struct Slice {
259
/// The spatial node root of the picture cache. If this is None, the slice
260
/// will not be cached and instead drawn directly to the parent surface. This
261
/// is a temporary measure until we enable caching all slices.
262
cache_scroll_root: Option<SpatialNodeIndex>,
263
/// List of primitive clusters that make up this slice
264
prim_list: PrimitiveList,
265
/// A list of clips that are shared by all primitives in the slice. These can be
266
/// filtered out and applied when the tile cache is composited rather than per-item.
267
shared_clips: Option<Vec<ClipDataHandle>>,
268
/// Various flags describing properties of this slice
269
pub flags: SliceFlags,
270
}
271
272
impl Slice {
273
// Open clip chain instances at the start of a slice
274
fn push_clip_instances(
275
&mut self,
276
stack: &[ClipChainPairInfo],
277
) {
278
for clip_chain_instance in stack.iter().rev() {
279
self.prim_list.add_prim_to_start(
280
create_clip_prim_instance(
281
clip_chain_instance.clip_chain_id,
282
PrimitiveInstanceKind::PushClipChain,
283
),
284
LayoutSize::zero(),
285
clip_chain_instance.spatial_node_index,
286
PrimitiveFlags::IS_BACKFACE_VISIBLE,
287
);
288
}
289
}
290
291
// Close clip chain instances at the end of a slice
292
fn pop_clip_instances(
293
&mut self,
294
stack: &[ClipChainPairInfo],
295
) {
296
for clip_chain_instance in stack {
297
self.prim_list.add_prim(
298
create_clip_prim_instance(
299
clip_chain_instance.clip_chain_id,
300
PrimitiveInstanceKind::PopClipChain,
301
),
302
LayoutSize::zero(),
303
clip_chain_instance.spatial_node_index,
304
PrimitiveFlags::IS_BACKFACE_VISIBLE,
305
);
306
}
307
}
308
}
309
310
/// A structure that converts a serialized display list into a form that WebRender
311
/// can use to later build a frame. This structure produces a BuiltScene. Public
312
/// members are typically those that are destructured into the BuiltScene.
313
pub struct SceneBuilder<'a> {
314
/// The scene that we are currently building.
315
scene: &'a Scene,
316
317
/// The map of all font instances.
318
font_instances: FontInstanceMap,
319
320
/// A set of pipelines that the caller has requested be made available as
321
/// output textures.
322
output_pipelines: &'a FastHashSet<PipelineId>,
323
324
/// The data structure that converts between ClipId/SpatialId and the various
325
/// index types that the SpatialTree uses.
326
id_to_index_mapper: NodeIdToIndexMapper,
327
328
/// A stack of stacking context properties.
329
sc_stack: Vec<FlattenedStackingContext>,
330
331
/// Maintains state for any currently active shadows
332
pending_shadow_items: VecDeque<ShadowItem>,
333
334
/// The stack keeping track of the root clip chains associated with pipelines.
335
pipeline_clip_chain_stack: Vec<ClipChainId>,
336
337
/// The SpatialTree that we are currently building during building.
338
pub spatial_tree: SpatialTree,
339
340
/// The store of primitives.
341
pub prim_store: PrimitiveStore,
342
343
/// Information about all primitives involved in hit testing.
344
pub hit_testing_scene: HitTestingScene,
345
346
/// The store which holds all complex clipping information.
347
pub clip_store: ClipStore,
348
349
/// The configuration to use for the FrameBuilder. We consult this in
350
/// order to determine the default font.
351
pub config: FrameBuilderConfig,
352
353
/// Reference to the set of data that is interned across display lists.
354
interners: &'a mut Interners,
355
356
/// The root picture index for this builder. This is the picture
357
/// to start the culling phase from.
358
pub root_pic_index: PictureIndex,
359
360
/// Helper struct to map stacking context coords <-> reference frame coords.
361
rf_mapper: ReferenceFrameMapper,
362
363
/// Helper struct to map spatial nodes to external scroll offsets.
364
external_scroll_mapper: ScrollOffsetMapper,
365
366
/// If true, picture caching setup has already been completed.
367
picture_caching_initialized: bool,
368
369
/// The current recursion depth of iframes encountered. Used to restrict picture
370
/// caching slices to only the top-level content frame.
371
iframe_depth: usize,
372
373
/// The number of picture cache slices that were created for content.
374
content_slice_count: usize,
375
376
/// A set of any spatial nodes that are attached to either a picture cache
377
/// root, or a clip node on the picture cache primitive. These are used
378
/// to detect cases where picture caching must be disabled. This is mostly
379
/// a temporary workaround for some existing wrench tests. I don't think
380
/// Gecko ever produces picture cache slices with complex transforms, so
381
/// in future we should prevent this in the public API and remove this hack.
382
picture_cache_spatial_nodes: FastHashSet<SpatialNodeIndex>,
383
384
/// The current quality / performance settings for this scene.
385
quality_settings: QualitySettings,
386
}
387
388
impl<'a> SceneBuilder<'a> {
389
pub fn build(
390
scene: &Scene,
391
font_instances: FontInstanceMap,
392
view: &DocumentView,
393
output_pipelines: &FastHashSet<PipelineId>,
394
frame_builder_config: &FrameBuilderConfig,
395
interners: &mut Interners,
396
stats: &SceneStats,
397
) -> BuiltScene {
398
// We checked that the root pipeline is available on the render backend.
399
let root_pipeline_id = scene.root_pipeline_id.unwrap();
400
let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
401
402
let background_color = root_pipeline
403
.background_color
404
.and_then(|color| if color.a > 0.0 { Some(color) } else { None });
405
406
let mut builder = SceneBuilder {
407
scene,
408
spatial_tree: SpatialTree::new(),
409
font_instances,
410
config: *frame_builder_config,
411
output_pipelines,
412
id_to_index_mapper: NodeIdToIndexMapper::default(),
413
hit_testing_scene: HitTestingScene::new(&stats.hit_test_stats),
414
pending_shadow_items: VecDeque::new(),
415
sc_stack: Vec::new(),
416
pipeline_clip_chain_stack: vec![ClipChainId::NONE],
417
prim_store: PrimitiveStore::new(&stats.prim_store_stats),
418
clip_store: ClipStore::new(),
419
interners,
420
root_pic_index: PictureIndex(0),
421
rf_mapper: ReferenceFrameMapper::new(),
422
external_scroll_mapper: ScrollOffsetMapper::new(),
423
picture_caching_initialized: false,
424
iframe_depth: 0,
425
content_slice_count: 0,
426
picture_cache_spatial_nodes: FastHashSet::default(),
427
quality_settings: view.quality_settings,
428
};
429
430
let device_pixel_scale = view.accumulated_scale_factor_for_snapping();
431
432
builder.push_root(
433
root_pipeline_id,
434
&root_pipeline.viewport_size,
435
&root_pipeline.content_size,
436
device_pixel_scale,
437
);
438
439
// In order to ensure we have a single root stacking context for the
440
// entire display list, we push one here. Gecko _almost_ wraps its
441
// entire display list within a single stacking context, but sometimes
442
// appends a few extra items in AddWindowOverlayWebRenderCommands. We
443
// could fix it there, but it's easier and more robust for WebRender
444
// to just ensure there's a context on the stack whenever we append
445
// primitives (since otherwise we'd panic).
446
//
447
// Note that we don't do this for iframes, even if they're pipeline
448
// roots, because they should be entirely contained within a stacking
449
// context, and we probably wouldn't crash if they weren't.
450
builder.push_stacking_context(
451
root_pipeline.pipeline_id,
452
CompositeOps::default(),
453
TransformStyle::Flat,
454
/* prim_flags = */ PrimitiveFlags::IS_BACKFACE_VISIBLE,
455
/* create_tile_cache = */ false,
456
ROOT_SPATIAL_NODE_INDEX,
457
ClipChainId::NONE,
458
RasterSpace::Screen,
459
/* is_backdrop_root = */ true,
460
device_pixel_scale,
461
);
462
463
builder.build_items(
464
&mut root_pipeline.display_list.iter(),
465
root_pipeline.pipeline_id,
466
true,
467
);
468
469
builder.pop_stacking_context();
470
471
debug_assert!(builder.sc_stack.is_empty());
472
473
BuiltScene {
474
has_root_pipeline: scene.has_root_pipeline(),
475
pipeline_epochs: scene.pipeline_epochs.clone(),
476
output_rect: view.device_rect.size.into(),
477
background_color,
478
hit_testing_scene: Arc::new(builder.hit_testing_scene),
479
spatial_tree: builder.spatial_tree,
480
prim_store: builder.prim_store,
481
clip_store: builder.clip_store,
482
root_pic_index: builder.root_pic_index,
483
config: builder.config,
484
content_slice_count: builder.content_slice_count,
485
picture_cache_spatial_nodes: builder.picture_cache_spatial_nodes,
486
}
487
}
488
489
/// Retrieve the current offset to allow converting a stacking context
490
/// relative coordinate to be relative to the owing reference frame,
491
/// also considering any external scroll offset on the provided
492
/// spatial node.
493
fn current_offset(
494
&mut self,
495
spatial_node_index: SpatialNodeIndex,
496
) -> LayoutVector2D {
497
// Get the current offset from stacking context <-> reference frame space.
498
let rf_offset = self.rf_mapper.current_offset();
499
500
// Get the external scroll offset, if applicable.
501
let scroll_offset = self
502
.external_scroll_mapper
503
.external_scroll_offset(
504
spatial_node_index,
505
&self.spatial_tree,
506
);
507
508
rf_offset + scroll_offset
509
}
510
511
/// Figure out the shape of the display list, and wrap various primitive clusters
512
/// into tile cache primitive instances.
513
fn setup_picture_caching(
514
&mut self,
515
main_prim_list: &mut PrimitiveList,
516
) {
517
if !self.config.global_enable_picture_caching {
518
return;
519
}
520
521
// Ensure that setup_picture_caching has executed
522
debug_assert!(self.picture_caching_initialized);
523
524
// Unconditionally insert a marker to create a picture cache slice on the
525
// first cluster. This handles implicit picture caches, and also the common
526
// case, by allowing the root / background primitives to be cached in a slice.
527
if let Some(cluster) = main_prim_list.clusters.first_mut() {
528
cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE);
529
}
530
531
// List of slices that have been found
532
let mut slices: Vec<Slice> = Vec::new();
533
// Current stack of open clip chain instances that need to be fixed up
534
let mut clip_chain_instance_stack = Vec::new();
535
// Tracker for whether a new slice should be created
536
let mut create_slice = true;
537
// The clips found the last time we traversed a set of clip chains. Stored and cleared
538
// here to avoid constant allocations.
539
let mut prim_clips = Vec::new();
540
// If true, the cache is out of date and needs to be rebuilt.
541
let mut update_shared_clips = true;
542
// The last prim clip chain we build prim_clips for.
543
let mut last_prim_clip_chain_id = ClipChainId::NONE;
544
545
// Walk the supplied top level of clusters, slicing into slices as appropriate
546
for cluster in main_prim_list.clusters.drain(..) {
547
// Check if this cluster requires a new slice
548
create_slice |= cluster.flags.intersects(
549
ClusterFlags::CREATE_PICTURE_CACHE_PRE | ClusterFlags::IS_CLEAR_PRIMITIVE
550
);
551
552
if create_slice {
553
// When creating a slice, close off any open clip chains on prev slice.
554
if let Some(prev_slice) = slices.last_mut() {
555
prev_slice.pop_clip_instances(&clip_chain_instance_stack);
556
}
557
558
let slice_flags = if cluster.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) {
559
SliceFlags::IS_SCROLLBAR
560
} else {
561
SliceFlags::empty()
562
};
563
let mut slice = Slice {
564
cache_scroll_root: cluster.cache_scroll_root,
565
prim_list: PrimitiveList::empty(),
566
shared_clips: None,
567
flags: slice_flags
568
};
569
570
// Open up clip chains on the stack on the new slice
571
slice.push_clip_instances(&clip_chain_instance_stack);
572
slices.push(slice);
573
create_slice = false;
574
}
575
576
// Step through each prim instance, in order to collect shared clips for the slice.
577
for instance in &cluster.prim_instances {
578
// If a Push/Pop clip chain, record that in the clip stack stack.
579
match instance.kind {
580
PrimitiveInstanceKind::PushClipChain => {
581
clip_chain_instance_stack.push(ClipChainPairInfo {
582
spatial_node_index: cluster.spatial_node_index,
583
clip_chain_id: instance.clip_chain_id,
584
});
585
// Invalidate the prim_clips cache - a clip chain was removed.
586
update_shared_clips = true;
587
continue;
588
}
589
PrimitiveInstanceKind::PopClipChain => {
590
let clip_chain_instance = clip_chain_instance_stack.pop().unwrap();
591
debug_assert_eq!(
592
clip_chain_instance.clip_chain_id,
593
instance.clip_chain_id,
594
);
595
debug_assert_eq!(
596
clip_chain_instance.spatial_node_index,
597
cluster.spatial_node_index,
598
);
599
// Invalidate the prim_clips cache - a clip chain was removed.
600
update_shared_clips = true;
601
continue;
602
}
603
_ => {}
604
}
605
606
// If the primitive clip chain is different, then we need to rebuild prim_clips.
607
update_shared_clips |= last_prim_clip_chain_id != instance.clip_chain_id;
608
last_prim_clip_chain_id = instance.clip_chain_id;
609
610
if update_shared_clips {
611
prim_clips.clear();
612
// Update the list of clips that apply to this primitive instance
613
for clip_instance in &clip_chain_instance_stack {
614
add_clips(
615
clip_instance.clip_chain_id,
616
&mut prim_clips,
617
&self.clip_store,
618
&self.interners,
619
);
620
}
621
add_clips(
622
instance.clip_chain_id,
623
&mut prim_clips,
624
&self.clip_store,
625
&self.interners,
626
);
627
}
628
629
// If there are no shared clips set for this slice, the shared clips are just
630
// the current clips set. Otherwise, the shared clips are those that are
631
// in both the current shared list and the clips list for this primitive.
632
match slices.last_mut().unwrap().shared_clips {
633
Some(ref mut shared_clips) => {
634
if update_shared_clips {
635
shared_clips.retain(|h1: &ClipDataHandle| {
636
let uid = h1.uid();
637
prim_clips.iter().any(|h2| {
638
uid == h2.uid()
639
})
640
});
641
}
642
}
643
ref mut shared_clips @ None => {
644
*shared_clips = Some(prim_clips.clone());
645
}
646
}
647
648
update_shared_clips = false;
649
}
650
651
// If this cluster creates a slice after, then note that for next cluster
652
create_slice |= cluster.flags.intersects(
653
ClusterFlags::CREATE_PICTURE_CACHE_POST | ClusterFlags::IS_CLEAR_PRIMITIVE
654
);
655
656
// Finally, add this cluster to the current slice
657
slices.last_mut().unwrap().prim_list.add_cluster(cluster);
658
}
659
660
// Close off any open clip chains on prev slice.
661
if let Some(prev_slice) = slices.last_mut() {
662
prev_slice.pop_clip_instances(&clip_chain_instance_stack);
663
}
664
665
// Step through the slices, creating picture cache wrapper instances.
666
for (slice_index, slice) in slices.drain(..).enumerate() {
667
let background_color = if slice_index == 0 {
668
self.config.background_color
669
} else {
670
None
671
};
672
673
// If the cluster specifies a scroll root, use it. Otherwise,
674
// just cache assuming no scrolling takes place. Even if that's
675
// not true, we still get caching benefits for any changes that
676
// occur while not scrolling (such as animation, video etc);
677
let scroll_root = slice.cache_scroll_root.unwrap_or(ROOT_SPATIAL_NODE_INDEX);
678
679
let instance = create_tile_cache(
680
slice_index,
681
slice.flags,
682
scroll_root,
683
slice.prim_list,
684
background_color,
685
slice.shared_clips.unwrap_or_else(Vec::new),
686
&mut self.interners,
687
&mut self.prim_store,
688
&mut self.clip_store,
689
&mut self.picture_cache_spatial_nodes,
690
&self.config,
691
);
692
693
main_prim_list.add_prim(
694
instance,
695
LayoutSize::zero(),
696
scroll_root,
697
PrimitiveFlags::IS_BACKFACE_VISIBLE,
698
);
699
}
700
}
701
702
fn build_items(
703
&mut self,
704
traversal: &mut BuiltDisplayListIter<'a>,
705
pipeline_id: PipelineId,
706
apply_pipeline_clip: bool,
707
) {
708
loop {
709
let item = match traversal.next() {
710
Some(item) => item,
711
None => break,
712
};
713
714
let subtraversal = match item.item() {
715
DisplayItem::PushStackingContext(ref info) => {
716
let space = self.get_space(&info.spatial_id);
717
let mut subtraversal = item.sub_iter();
718
self.build_stacking_context(
719
&mut subtraversal,
720
pipeline_id,
721
&info.stacking_context,
722
space,
723
info.origin,
724
item.filters(),
725
&item.filter_datas(),
726
item.filter_primitives(),
727
info.prim_flags,
728
apply_pipeline_clip,
729
);
730
Some(subtraversal)
731
}
732
DisplayItem::PushReferenceFrame(ref info) => {
733
let parent_space = self.get_space(&info.parent_spatial_id);
734
let mut subtraversal = item.sub_iter();
735
self.build_reference_frame(
736
&mut subtraversal,
737
pipeline_id,
738
parent_space,
739
info.origin,
740
&info.reference_frame,
741
apply_pipeline_clip,
742
);
743
Some(subtraversal)
744
}
745
DisplayItem::PopReferenceFrame |
746
DisplayItem::PopStackingContext => return,
747
_ => None,
748
};
749
750
// If build_item created a sub-traversal, we need `traversal` to have the
751
// same state as the completed subtraversal, so we reinitialize it here.
752
if let Some(mut subtraversal) = subtraversal {
753
subtraversal.merge_debug_stats_from(traversal);
754
*traversal = subtraversal;
755
} else {
756
self.build_item(item, pipeline_id, apply_pipeline_clip);
757
}
758
}
759
760
// TODO: factor this out to be part of capture
761
if cfg!(feature = "display_list_stats") {
762
let stats = traversal.debug_stats();
763
let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
764
println!("item, total count, total bytes, % of DL bytes, bytes per item");
765
for (label, stats) in stats {
766
println!("{}, {}, {}kb, {}%, {}",
767
label,
768
stats.total_count,
769
stats.num_bytes / 1000,
770
((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
771
stats.num_bytes / stats.total_count.max(1));
772
}
773
println!();
774
}
775
}
776
777
fn build_sticky_frame(
778
&mut self,
779
info: &StickyFrameDisplayItem,
780
parent_node_index: SpatialNodeIndex,
781
) {
782
let current_offset = self.current_offset(parent_node_index);
783
let frame_rect = info.bounds.translate(current_offset);
784
let sticky_frame_info = StickyFrameInfo::new(
785
frame_rect,
786
info.margins,
787
info.vertical_offset_bounds,
788
info.horizontal_offset_bounds,
789
info.previously_applied_offset,
790
);
791
792
let index = self.spatial_tree.add_sticky_frame(
793
parent_node_index,
794
sticky_frame_info,
795
info.id.pipeline_id(),
796
);
797
self.id_to_index_mapper.map_spatial_node(info.id, index);
798
}
799
800
fn build_scroll_frame(
801
&mut self,
802
item: &DisplayItemRef,
803
info: &ScrollFrameDisplayItem,
804
parent_node_index: SpatialNodeIndex,
805
pipeline_id: PipelineId,
806
) {
807
let current_offset = self.current_offset(parent_node_index);
808
let clip_region = ClipRegion::create_for_clip_node(
809
info.clip_rect,
810
item.complex_clip().iter(),
811
info.image_mask,
812
&current_offset,
813
);
814
// Just use clip rectangle as the frame rect for this scroll frame.
815
// This is useful when calculating scroll extents for the
816
// SpatialNode::scroll(..) API as well as for properly setting sticky
817
// positioning offsets.
818
let frame_rect = clip_region.main;
819
let content_size = info.content_rect.size;
820
821
self.add_clip_node(info.clip_id, &info.parent_space_and_clip, clip_region);
822
823
self.add_scroll_frame(
824
info.scroll_frame_id,
825
parent_node_index,
826
info.external_id,
827
pipeline_id,
828
&frame_rect,
829
&content_size,
830
info.scroll_sensitivity,
831
ScrollFrameKind::Explicit,
832
info.external_scroll_offset,
833
);
834
}
835
836
fn build_reference_frame(
837
&mut self,
838
traversal: &mut BuiltDisplayListIter<'a>,
839
pipeline_id: PipelineId,
840
parent_spatial_node: SpatialNodeIndex,
841
origin: LayoutPoint,
842
reference_frame: &ReferenceFrame,
843
apply_pipeline_clip: bool,
844
) {
845
let current_offset = self.current_offset(parent_spatial_node);
846
self.push_reference_frame(
847
reference_frame.id,
848
Some(parent_spatial_node),
849
pipeline_id,
850
reference_frame.transform_style,
851
reference_frame.transform,
852
reference_frame.kind,
853
current_offset + origin.to_vector(),
854
);
855
856
self.rf_mapper.push_scope();
857
self.build_items(
858
traversal,
859
pipeline_id,
860
apply_pipeline_clip,
861
);
862
self.rf_mapper.pop_scope();
863
}
864
865
866
fn build_stacking_context(
867
&mut self,
868
traversal: &mut BuiltDisplayListIter<'a>,
869
pipeline_id: PipelineId,
870
stacking_context: &StackingContext,
871
spatial_node_index: SpatialNodeIndex,
872
origin: LayoutPoint,
873
filters: ItemRange<FilterOp>,
874
filter_datas: &[TempFilterData],
875
filter_primitives: ItemRange<FilterPrimitive>,
876
prim_flags: PrimitiveFlags,
877
apply_pipeline_clip: bool,
878
) {
879
// Avoid doing unnecessary work for empty stacking contexts.
880
if traversal.current_stacking_context_empty() {
881
traversal.skip_current_stacking_context();
882
return;
883
}
884
885
let composition_operations = {
886
CompositeOps::new(
887
filter_ops_for_compositing(filters),
888
filter_datas_for_compositing(filter_datas),
889
filter_primitives_for_compositing(filter_primitives),
890
stacking_context.mix_blend_mode_for_compositing(),
891
)
892
};
893
894
let clip_chain_id = match stacking_context.clip_id {
895
Some(clip_id) => self.id_to_index_mapper.get_clip_chain_id(clip_id),
896
None => ClipChainId::NONE,
897
};
898
899
self.push_stacking_context(
900
pipeline_id,
901
composition_operations,
902
stacking_context.transform_style,
903
prim_flags,
904
stacking_context.cache_tiles,
905
spatial_node_index,
906
clip_chain_id,
907
stacking_context.raster_space,
908
stacking_context.is_backdrop_root,
909
self.sc_stack.last().unwrap().snap_to_device.device_pixel_scale,
910
);
911
912
if cfg!(debug_assertions) && apply_pipeline_clip && clip_chain_id != ClipChainId::NONE {
913
// This is the rootmost stacking context in this pipeline that has
914
// a clip set. Check that the clip chain includes the pipeline clip
915
// as well, because this where we recurse with `apply_pipeline_clip`
916
// set to false and stop explicitly adding the pipeline clip to
917
// individual items.
918
let pipeline_clip = self.pipeline_clip_chain_stack.last().unwrap();
919
let mut found_root = *pipeline_clip == ClipChainId::NONE;
920
let mut cur_clip = clip_chain_id.clone();
921
while cur_clip != ClipChainId::NONE {
922
if cur_clip == *pipeline_clip {
923
found_root = true;
924
break;
925
}
926
cur_clip = self.clip_store.get_clip_chain(cur_clip).parent_clip_chain_id;
927
}
928
debug_assert!(found_root);
929
}
930
931
self.rf_mapper.push_offset(origin.to_vector());
932
self.build_items(
933
traversal,
934
pipeline_id,
935
apply_pipeline_clip && clip_chain_id == ClipChainId::NONE,
936
);
937
self.rf_mapper.pop_offset();
938
939
self.pop_stacking_context();
940
}
941
942
fn build_iframe(
943
&mut self,
944
info: &IframeDisplayItem,
945
spatial_node_index: SpatialNodeIndex,
946
) {
947
let iframe_pipeline_id = info.pipeline_id;
948
let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
949
Some(pipeline) => pipeline,
950
None => {
951
debug_assert!(info.ignore_missing_pipeline);
952
return
953
},
954
};
955
956
let current_offset = self.current_offset(spatial_node_index);
957
let clip_chain_index = self.add_clip_node(
958
ClipId::root(iframe_pipeline_id),
959
&info.space_and_clip,
960
ClipRegion::create_for_clip_node_with_local_clip(
961
&info.clip_rect,
962
&current_offset,
963
),
964
);
965
self.pipeline_clip_chain_stack.push(clip_chain_index);
966
967
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
968
snap_to_device.set_target_spatial_node(
969
spatial_node_index,
970
&self.spatial_tree,
971
);
972
973
let bounds = snap_to_device.snap_rect(
974
&info.bounds.translate(current_offset),
975
);
976
977
let content_size = snap_to_device.snap_size(&pipeline.content_size);
978
979
let spatial_node_index = self.push_reference_frame(
980
SpatialId::root_reference_frame(iframe_pipeline_id),
981
Some(spatial_node_index),
982
iframe_pipeline_id,
983
TransformStyle::Flat,
984
PropertyBinding::Value(LayoutTransform::identity()),
985
ReferenceFrameKind::Transform,
986
bounds.origin.to_vector(),
987
);
988
989
let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
990
self.add_scroll_frame(
991
SpatialId::root_scroll_node(iframe_pipeline_id),
992
spatial_node_index,
993
Some(ExternalScrollId(0, iframe_pipeline_id)),
994
iframe_pipeline_id,
995
&iframe_rect,
996
&content_size,
997
ScrollSensitivity::ScriptAndInputEvents,
998
ScrollFrameKind::PipelineRoot,
999
LayoutVector2D::zero(),
1000
);
1001
1002
self.rf_mapper.push_scope();
1003
self.iframe_depth += 1;
1004
1005
self.build_items(
1006
&mut pipeline.display_list.iter(),
1007
pipeline.pipeline_id,
1008
true,
1009
);
1010
self.iframe_depth -= 1;
1011
self.rf_mapper.pop_scope();
1012
1013
self.pipeline_clip_chain_stack.pop();
1014
}
1015
1016
fn get_space(&mut self, spatial_id: &SpatialId) -> SpatialNodeIndex {
1017
self.id_to_index_mapper.get_spatial_node_index(*spatial_id)
1018
}
1019
1020
fn get_clip_and_scroll(
1021
&mut self,
1022
clip_id: &ClipId,
1023
spatial_id: &SpatialId,
1024
apply_pipeline_clip: bool
1025
) -> ScrollNodeAndClipChain {
1026
ScrollNodeAndClipChain::new(
1027
self.id_to_index_mapper.get_spatial_node_index(*spatial_id),
1028
if !apply_pipeline_clip && clip_id.is_root() {
1029
ClipChainId::NONE
1030
} else if clip_id.is_valid() {
1031
self.id_to_index_mapper.get_clip_chain_id(*clip_id)
1032
} else {
1033
ClipChainId::INVALID
1034
},
1035
)
1036
}
1037
1038
fn process_common_properties(
1039
&mut self,
1040
common: &CommonItemProperties,
1041
bounds: Option<&LayoutRect>,
1042
apply_pipeline_clip: bool,
1043
) -> (LayoutPrimitiveInfo, LayoutRect, ScrollNodeAndClipChain) {
1044
let clip_and_scroll = self.get_clip_and_scroll(
1045
&common.clip_id,
1046
&common.spatial_id,
1047
apply_pipeline_clip
1048
);
1049
1050
let current_offset = self.current_offset(clip_and_scroll.spatial_node_index);
1051
1052
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
1053
snap_to_device.set_target_spatial_node(
1054
clip_and_scroll.spatial_node_index,
1055
&self.spatial_tree
1056
);
1057
1058
let unsnapped_clip_rect = common.clip_rect.translate(current_offset);
1059
let clip_rect = snap_to_device.snap_rect(&unsnapped_clip_rect);
1060
1061
let unsnapped_rect = bounds.map(|bounds| {
1062
bounds.translate(current_offset)
1063
});
1064
1065
// If no bounds rect is given, default to clip rect.
1066
let rect = unsnapped_rect.map_or(clip_rect, |bounds| {
1067
snap_to_device.snap_rect(&bounds)
1068
});
1069
1070
let layout = LayoutPrimitiveInfo {
1071
rect,
1072
clip_rect,
1073
flags: common.flags,
1074
hit_info: common.hit_info,
1075
};
1076
1077
(layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), clip_and_scroll)
1078
}
1079
1080
fn process_common_properties_with_bounds(
1081
&mut self,
1082
common: &CommonItemProperties,
1083
bounds: &LayoutRect,
1084
apply_pipeline_clip: bool,
1085
) -> (LayoutPrimitiveInfo, LayoutRect, ScrollNodeAndClipChain) {
1086
self.process_common_properties(
1087
common,
1088
Some(bounds),
1089
apply_pipeline_clip,
1090
)
1091
}
1092
1093
pub fn snap_rect(
1094
&mut self,
1095
rect: &LayoutRect,
1096
target_spatial_node: SpatialNodeIndex,
1097
) -> LayoutRect {
1098
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
1099
snap_to_device.set_target_spatial_node(
1100
target_spatial_node,
1101
&self.spatial_tree
1102
);
1103
snap_to_device.snap_rect(rect)
1104
}
1105
1106
fn build_item<'b>(
1107
&'b mut self,
1108
item: DisplayItemRef,
1109
pipeline_id: PipelineId,
1110
apply_pipeline_clip: bool,
1111
) {
1112
match *item.item() {
1113
DisplayItem::Image(ref info) => {
1114
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1115
&info.common,
1116
&info.bounds,
1117
apply_pipeline_clip,
1118
);
1119
1120
self.add_image(
1121
clip_and_scroll,
1122
&layout,
1123
layout.rect.size,
1124
LayoutSize::zero(),
1125
None,
1126
info.image_key,
1127
info.image_rendering,
1128
info.alpha_type,
1129
info.color,
1130
);
1131
}
1132
DisplayItem::RepeatingImage(ref info) => {
1133
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
1134
&info.common,
1135
&info.bounds,
1136
apply_pipeline_clip,
1137
);
1138
1139
let stretch_size = process_repeat_size(
1140
&layout.rect,
1141
&unsnapped_rect,
1142
info.stretch_size,
1143
);
1144
1145
self.add_image(
1146
clip_and_scroll,
1147
&layout,
1148
stretch_size,
1149
info.tile_spacing,
1150
None,
1151
info.image_key,
1152
info.image_rendering,
1153
info.alpha_type,
1154
info.color,
1155
);
1156
}
1157
DisplayItem::YuvImage(ref info) => {
1158
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1159
&info.common,
1160
&info.bounds,
1161
apply_pipeline_clip,
1162
);
1163
1164
self.add_yuv_image(
1165
clip_and_scroll,
1166
&layout,
1167
info.yuv_data,
1168
info.color_depth,
1169
info.color_space,
1170
info.color_range,
1171
info.image_rendering,
1172
);
1173
}
1174
DisplayItem::Text(ref info) => {
1175
// TODO(aosmond): Snapping text primitives does not make much sense, given the
1176
// primitive bounds and clip are supposed to be conservative, not definitive.
1177
// E.g. they should be able to grow and not impact the output. However there
1178
// are subtle interactions between the primitive origin and the glyph offset
1179
// which appear to be significant (presumably due to some sort of accumulated
1180
// error throughout the layers). We should fix this at some point.
1181
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1182
&info.common,
1183
&info.bounds,
1184
apply_pipeline_clip,
1185
);
1186
1187
self.add_text(
1188
clip_and_scroll,
1189
&layout,
1190
&info.font_key,
1191
&info.color,
1192
item.glyphs(),
1193
info.glyph_options,
1194
);
1195
}
1196
DisplayItem::Rectangle(ref info) => {
1197
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1198
&info.common,
1199
&info.bounds,
1200
apply_pipeline_clip,
1201
);
1202
1203
self.add_solid_rectangle(
1204
clip_and_scroll,
1205
&layout,
1206
info.color,
1207
);
1208
}
1209
DisplayItem::HitTest(ref info) => {
1210
let (layout, _, clip_and_scroll) = self.process_common_properties(
1211
&info.common,
1212
None,
1213
apply_pipeline_clip,
1214
);
1215
1216
self.add_solid_rectangle(
1217
clip_and_scroll,
1218
&layout,
1219
PropertyBinding::Value(ColorF::TRANSPARENT),
1220
);
1221
}
1222
DisplayItem::ClearRectangle(ref info) => {
1223
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1224
&info.common,
1225
&info.bounds,
1226
apply_pipeline_clip,
1227
);
1228
1229
self.add_clear_rectangle(
1230
clip_and_scroll,
1231
&layout,
1232
);
1233
}
1234
DisplayItem::Line(ref info) => {
1235
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1236
&info.common,
1237
&info.area,
1238
apply_pipeline_clip,
1239
);
1240
1241
self.add_line(
1242
clip_and_scroll,
1243
&layout,
1244
info.wavy_line_thickness,
1245
info.orientation,
1246
info.color,
1247
info.style,
1248
);
1249
}
1250
DisplayItem::Gradient(ref info) => {
1251
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
1252
&info.common,
1253
&info.bounds,
1254
apply_pipeline_clip,
1255
);
1256
1257
let tile_size = process_repeat_size(
1258
&layout.rect,
1259
&unsnapped_rect,
1260
info.tile_size,
1261
);
1262
1263
if let Some(prim_key_kind) = self.create_linear_gradient_prim(
1264
&layout,
1265
info.gradient.start_point,
1266
info.gradient.end_point,
1267
item.gradient_stops(),
1268
info.gradient.extend_mode,
1269
tile_size,
1270
info.tile_spacing,
1271
None,
1272
) {
1273
self.add_nonshadowable_primitive(
1274
clip_and_scroll,
1275
&layout,
1276
Vec::new(),
1277
prim_key_kind,
1278
);
1279
}
1280
}
1281
DisplayItem::RadialGradient(ref info) => {
1282
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
1283
&info.common,
1284
&info.bounds,
1285
apply_pipeline_clip,
1286
);
1287
1288
let tile_size = process_repeat_size(
1289
&layout.rect,
1290
&unsnapped_rect,
1291
info.tile_size,
1292
);
1293
1294
let prim_key_kind = self.create_radial_gradient_prim(
1295
&layout,
1296
info.gradient.center,
1297
info.gradient.start_offset * info.gradient.radius.width,
1298
info.gradient.end_offset * info.gradient.radius.width,
1299
info.gradient.radius.width / info.gradient.radius.height,
1300
item.gradient_stops(),
1301
info.gradient.extend_mode,
1302
tile_size,
1303
info.tile_spacing,
1304
None,
1305
);
1306
1307
self.add_nonshadowable_primitive(
1308
clip_and_scroll,
1309
&layout,
1310
Vec::new(),
1311
prim_key_kind,
1312
);
1313
}
1314
DisplayItem::ConicGradient(ref info) => {
1315
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
1316
&info.common,
1317
&info.bounds,
1318
apply_pipeline_clip,
1319
);
1320
1321
let tile_size = process_repeat_size(
1322
&layout.rect,
1323
&unsnapped_rect,
1324
info.tile_size,
1325
);
1326
1327
let prim_key_kind = self.create_conic_gradient_prim(
1328
&layout,
1329
info.gradient.center,
1330
info.gradient.angle,
1331
info.gradient.start_offset,
1332
info.gradient.end_offset,
1333
item.gradient_stops(),
1334
info.gradient.extend_mode,
1335
tile_size,
1336
info.tile_spacing,
1337
None,
1338
);
1339
1340
self.add_nonshadowable_primitive(
1341
clip_and_scroll,
1342
&layout,
1343
Vec::new(),
1344
prim_key_kind,
1345
);
1346
}
1347
DisplayItem::BoxShadow(ref info) => {
1348
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1349
&info.common,
1350
&info.box_bounds,
1351
apply_pipeline_clip,
1352
);
1353
1354
self.add_box_shadow(
1355
clip_and_scroll,
1356
&layout,
1357
&info.offset,
1358
info.color,
1359
info.blur_radius,
1360
info.spread_radius,
1361
info.border_radius,
1362
info.clip_mode,
1363
);
1364
}
1365
DisplayItem::Border(ref info) => {
1366
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
1367
&info.common,
1368
&info.bounds,
1369
apply_pipeline_clip,
1370
);
1371
1372
self.add_border(
1373
clip_and_scroll,
1374
&layout,
1375
info,
1376
item.gradient_stops(),
1377
);
1378
}
1379
DisplayItem::Iframe(ref info) => {
1380
let space = self.get_space(&info.space_and_clip.spatial_id);
1381
self.build_iframe(
1382
info,
1383
space,
1384
);
1385
}
1386
DisplayItem::Clip(ref info) => {
1387
let parent_space = self.get_space(&info.parent_space_and_clip.spatial_id);
1388
let current_offset = self.current_offset(parent_space);
1389
let clip_region = ClipRegion::create_for_clip_node(
1390
info.clip_rect,
1391
item.complex_clip().iter(),
1392
info.image_mask,
1393
&current_offset,
1394
);
1395
self.add_clip_node(info.id, &info.parent_space_and_clip, clip_region);
1396
}
1397
DisplayItem::ClipChain(ref info) => {
1398
// For a user defined clip-chain the parent (if specified) must
1399
// refer to another user defined clip-chain. If none is specified,
1400
// the parent is the root clip-chain for the given pipeline. This
1401
// is used to provide a root clip chain for iframes.
1402
let parent_clip_chain_id = match info.parent {
1403
Some(id) => {
1404
self.id_to_index_mapper.get_clip_chain_id(ClipId::ClipChain(id))
1405
}
1406
None => {
1407
self.pipeline_clip_chain_stack.last().cloned().unwrap()
1408
}
1409
};
1410
1411
// Create a linked list of clip chain nodes. To do this, we will
1412
// create a clip chain node + clip source for each listed clip id,
1413
// and link these together, with the root of this list parented to
1414
// the parent clip chain node found above. For this API, the clip
1415
// id that is specified for an existing clip chain node is used to
1416
// get the index of the clip sources that define that clip node.
1417
let mut clip_chain_id = parent_clip_chain_id;
1418
1419
// For each specified clip id
1420
for clip_item in item.clip_chain_items() {
1421
// Map the ClipId to an existing clip chain node.
1422
let item_clip_node = self
1423
.id_to_index_mapper
1424
.get_clip_node(&clip_item);
1425
1426
let mut clip_node_clip_chain_id = item_clip_node.id;
1427
1428
// Each 'clip node' (as defined by the WR API) can contain one or
1429
// more clip items (e.g. rects, image masks, rounded rects). When
1430
// each of these clip nodes is stored internally, they are stored
1431
// as a clip chain (one clip item per node), eventually parented
1432
// to the parent clip node. For a user defined clip chain, we will
1433
// need to walk the linked list of clip chain nodes for each clip
1434
// node, accumulating them into one clip chain that is then
1435
// parented to the clip chain parent.
1436
1437
for _ in 0 .. item_clip_node.count {
1438
// Get the id of the clip sources entry for that clip chain node.
1439
let handle = {
1440
let clip_chain = self
1441
.clip_store
1442
.get_clip_chain(clip_node_clip_chain_id);
1443
1444
clip_node_clip_chain_id = clip_chain.parent_clip_chain_id;
1445
1446
clip_chain.handle
1447
};
1448
1449
// Add a new clip chain node, which references the same clip sources, and
1450
// parent it to the current parent.
1451
clip_chain_id = self
1452
.clip_store
1453
.add_clip_chain_node(
1454
handle,
1455
clip_chain_id,
1456
);
1457
}
1458
}
1459
1460
// Map the last entry in the clip chain to the supplied ClipId. This makes
1461
// this ClipId available as a source to other user defined clip chains.
1462
self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_id, 0);
1463
},
1464
DisplayItem::ScrollFrame(ref info) => {
1465
let parent_space = self.get_space(&info.parent_space_and_clip.spatial_id);
1466
self.build_scroll_frame(
1467
&item,
1468
info,
1469
parent_space,
1470
pipeline_id,
1471
);
1472
}
1473
DisplayItem::StickyFrame(ref info) => {
1474
let parent_space = self.get_space(&info.parent_spatial_id);
1475
self.build_sticky_frame(
1476
info,
1477
parent_space,
1478
);
1479
}
1480
DisplayItem::BackdropFilter(ref info) => {
1481
let (layout, _, clip_and_scroll) = self.process_common_properties(
1482
&info.common,
1483
None,
1484
apply_pipeline_clip,
1485
);
1486
1487
let filters = filter_ops_for_compositing(item.filters());
1488
let filter_datas = filter_datas_for_compositing(item.filter_datas());
1489
let filter_primitives = filter_primitives_for_compositing(item.filter_primitives());
1490
1491
self.add_backdrop_filter(
1492
clip_and_scroll,
1493
&layout,
1494
filters,
1495
filter_datas,
1496
filter_primitives,
1497
);
1498
}
1499
1500
// Do nothing; these are dummy items for the display list parser
1501
DisplayItem::SetGradientStops |
1502
DisplayItem::SetFilterOps |
1503
DisplayItem::SetFilterData |
1504
DisplayItem::SetFilterPrimitives => {}
1505
1506
// Special items that are handled in the parent method
1507
DisplayItem::PushStackingContext(..) |
1508
DisplayItem::PushReferenceFrame(..) |
1509
DisplayItem::PopReferenceFrame |
1510
DisplayItem::PopStackingContext => {
1511
unreachable!("Should have returned in parent method.")
1512
}
1513
1514
DisplayItem::ReuseItems(key) |
1515
DisplayItem::RetainedItems(key) => {
1516
unreachable!("Iterator logic error: {:?}", key);
1517
}
1518
1519
DisplayItem::PushShadow(info) => {
1520
let clip_and_scroll = self.get_clip_and_scroll(
1521
&info.space_and_clip.clip_id,
1522
&info.space_and_clip.spatial_id,
1523
apply_pipeline_clip
1524
);
1525
1526
self.push_shadow(info.shadow, clip_and_scroll, info.should_inflate);
1527
}
1528
DisplayItem::PopAllShadows => {
1529
self.pop_all_shadows();
1530
}
1531
}
1532
}
1533
1534
// Given a list of clip sources, a positioning node and
1535
// a parent clip chain, return a new clip chain entry.
1536
// If the supplied list of clip sources is empty, then
1537
// just return the parent clip chain id directly.
1538
fn build_clip_chain(
1539
&mut self,
1540
clip_items: Vec<ClipItemKey>,
1541
parent_clip_chain_id: ClipChainId,
1542
) -> ClipChainId {
1543
if clip_items.is_empty() {
1544
parent_clip_chain_id
1545
} else {
1546
let mut clip_chain_id = parent_clip_chain_id;
1547
1548
for item in clip_items {
1549
// Intern this clip item, and store the handle
1550
// in the clip chain node.
1551
let handle = self.interners
1552
.clip
1553
.intern(&item, || {
1554
ClipInternData {
1555
clip_node_kind: item.kind.node_kind(),
1556
spatial_node_index: item.spatial_node_index,
1557
}
1558
});
1559
1560
clip_chain_id = self.clip_store.add_clip_chain_node(
1561
handle,
1562
clip_chain_id,
1563
);
1564
}
1565
1566
clip_chain_id
1567
}
1568
}
1569
1570
/// Create a primitive and add it to the prim store. This method doesn't
1571
/// add the primitive to the draw list, so can be used for creating
1572
/// sub-primitives.
1573
///
1574
/// TODO(djg): Can this inline into `add_interned_prim_to_draw_list`
1575
fn create_primitive<P>(
1576
&mut self,
1577
info: &LayoutPrimitiveInfo,
1578
clip_chain_id: ClipChainId,
1579
spatial_node_index: SpatialNodeIndex,
1580
prim: P,
1581
) -> PrimitiveInstance
1582
where
1583
P: InternablePrimitive,
1584
Interners: AsMut<Interner<P>>,
1585
{
1586
// Build a primitive key.
1587
let prim_key = prim.into_key(info);
1588
1589
let current_offset = self.current_offset(spatial_node_index);
1590
let interner = self.interners.as_mut();
1591
let prim_data_handle = interner
1592
.intern(&prim_key, || {
1593
PrimitiveSceneData {
1594
prim_size: info.rect.size,
1595
flags: info.flags,
1596
}
1597
});
1598
1599
let instance_kind = P::make_instance_kind(
1600
prim_key,
1601
prim_data_handle,
1602
&mut self.prim_store,
1603
current_offset,
1604
);
1605
1606
PrimitiveInstance::new(
1607
info.rect.origin,
1608
info.clip_rect,
1609
instance_kind,
1610
clip_chain_id,
1611
)
1612
}
1613
1614
pub fn add_primitive_to_hit_testing_list(
1615
&mut self,
1616
info: &LayoutPrimitiveInfo,
1617
clip_and_scroll: ScrollNodeAndClipChain
1618
) {
1619
let tag = match info.hit_info {
1620
Some(tag) => tag,
1621
None => return,
1622
};
1623
1624
// We want to get a range of clip chain roots that apply to this
1625
// hit testing primitive.
1626
1627
// Get the start index for the clip chain root range for this primitive.
1628
let start = self.hit_testing_scene.next_clip_chain_index();
1629
1630
// Add the clip chain root for the primitive itself.
1631
self.hit_testing_scene.add_clip_chain(clip_and_scroll.clip_chain_id);
1632
1633
// Append any clip chain roots from enclosing stacking contexts.
1634
for sc in &self.sc_stack {
1635
self.hit_testing_scene.add_clip_chain(sc.clip_chain_id);
1636
}
1637
1638
// Construct a clip chain roots range to be stored with the item.
1639
let clip_chain_range = ops::Range {
1640
start,
1641
end: self.hit_testing_scene.next_clip_chain_index(),
1642
};
1643
1644
// Create and store the hit testing primitive itself.
1645
let new_item = HitTestingItem::new(
1646
tag,
1647
info,
1648
clip_and_scroll.spatial_node_index,
1649
clip_chain_range,
1650
);
1651
self.hit_testing_scene.add_item(new_item);
1652
}
1653
1654
/// Add an already created primitive to the draw lists.
1655
pub fn add_primitive_to_draw_list(
1656
&mut self,
1657
prim_instance: PrimitiveInstance,
1658
prim_size: LayoutSize,
1659
spatial_node_index: SpatialNodeIndex,
1660
flags: PrimitiveFlags,
1661
) {
1662
// Add primitive to the top-most stacking context on the stack.
1663
if prim_instance.is_chased() {
1664
println!("\tadded to stacking context at {}", self.sc_stack.len());
1665
}
1666
1667
let stacking_context = self.sc_stack.last_mut().unwrap();
1668
stacking_context.prim_list.add_prim(
1669
prim_instance,
1670
prim_size,
1671
spatial_node_index,
1672
flags,
1673
);
1674
}
1675
1676
/// Convenience interface that creates a primitive entry and adds it
1677
/// to the draw list.
1678
fn add_nonshadowable_primitive<P>(
1679
&mut self,
1680
clip_and_scroll: ScrollNodeAndClipChain,
1681
info: &LayoutPrimitiveInfo,
1682
clip_items: Vec<ClipItemKey>,
1683
prim: P,
1684
)
1685
where
1686
P: InternablePrimitive + IsVisible,
1687
Interners: AsMut<Interner<P>>,
1688
{
1689
if prim.is_visible() {
1690
let clip_chain_id = self.build_clip_chain(
1691
clip_items,
1692
clip_and_scroll.clip_chain_id,
1693
);
1694
self.add_prim_to_draw_list(
1695
info,
1696
clip_chain_id,
1697
clip_and_scroll,
1698
prim,
1699
);
1700
}
1701
}
1702
1703
pub fn add_primitive<P>(
1704
&mut self,
1705
clip_and_scroll: ScrollNodeAndClipChain,
1706
info: &LayoutPrimitiveInfo,
1707
clip_items: Vec<ClipItemKey>,
1708
prim: P,
1709
)
1710
where
1711
P: InternablePrimitive + IsVisible,
1712
Interners: AsMut<Interner<P>>,
1713
ShadowItem: From<PendingPrimitive<P>>
1714
{
1715
// If a shadow context is not active, then add the primitive
1716
// directly to the parent picture.
1717
if self.pending_shadow_items.is_empty() {
1718
self.add_nonshadowable_primitive(
1719
clip_and_scroll,
1720
info,
1721
clip_items,
1722
prim,
1723
);
1724
} else {
1725
debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives");
1726
1727
// There is an active shadow context. Store as a pending primitive
1728
// for processing during pop_all_shadows.
1729
self.pending_shadow_items.push_back(PendingPrimitive {