webrender/
scene_building.rs

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//! # Scene building
6//!
7//! Scene building is the phase during which display lists, a representation built for
8//! serialization, are turned into a scene, webrender's internal representation that is
9//! suited for rendering frames.
10//!
11//! This phase is happening asynchronously on the scene builder thread.
12//!
13//! # General algorithm
14//!
15//! The important aspects of scene building are:
16//! - Building up primitive lists (much of the cost of scene building goes here).
17//! - Creating pictures for content that needs to be rendered into a surface, be it so that
18//!   filters can be applied or for caching purposes.
19//! - Maintaining a temporary stack of stacking contexts to keep track of some of the
20//!   drawing states.
21//! - Stitching multiple display lists which reference each other (without cycles) into
22//!   a single scene (see build_reference_frame).
23//! - Interning, which detects when some of the retained state stays the same between display
24//!   lists.
25//!
26//! The scene builder linearly traverses the serialized display list which is naturally
27//! ordered back-to-front, accumulating primitives in the top-most stacking context's
28//! primitive list.
29//! At the end of each stacking context (see pop_stacking_context), its primitive list is
30//! either handed over to a picture if one is created, or it is concatenated into the parent
31//! stacking context's primitive list.
32//!
33//! The flow of the algorithm is mostly linear except when handling:
34//!  - shadow stacks (see push_shadow and pop_all_shadows),
35//!  - backdrop filters (see add_backdrop_filter)
36//!
37
38use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, BuiltDisplayListIter, PrimitiveFlags, SnapshotInfo};
39use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
40use api::{DebugFlags, DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData};
41use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop};
42use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
43use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags};
44use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDescriptor};
45use api::{APZScrollGeneration, HasScrollLinkedEffect, Shadow, SpatialId, StickyFrameDescriptor, ImageMask, ItemTag};
46use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
47use api::{ReferenceTransformBinding, Rotation, FillRule, SpatialTreeItem, ReferenceFrameDescriptor};
48use api::{FilterOpGraphPictureBufferId, SVGFE_GRAPH_MAX};
49use api::channel::{unbounded_channel, Receiver, Sender};
50use api::units::*;
51use crate::image_tiling::simplify_repeated_primitive;
52use crate::box_shadow::BLUR_SAMPLE_SCALE;
53use crate::clip::{ClipIntern, ClipItemKey, ClipItemKeyKind, ClipStore};
54use crate::clip::{ClipInternData, ClipNodeId, ClipLeafId};
55use crate::clip::{PolygonDataHandle, ClipTreeBuilder};
56use crate::segment::EdgeAaSegmentMask;
57use crate::spatial_tree::{SceneSpatialTree, SpatialNodeContainer, SpatialNodeIndex, get_external_scroll_offset};
58use crate::frame_builder::FrameBuilderConfig;
59use glyph_rasterizer::{FontInstance, SharedFontResources};
60use crate::hit_test::HitTestingScene;
61use crate::intern::Interner;
62use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, PlaneSplitterIndex, PipelineInstanceId};
63use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
64use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, SurfaceInfo, PictureFlags};
65use crate::picture_graph::PictureGraph;
66use crate::prim_store::{PrimitiveInstance, PrimitiveStoreStats};
67use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
68use crate::prim_store::{InternablePrimitive, PictureIndex};
69use crate::prim_store::PolygonKey;
70use crate::prim_store::backdrop::{BackdropCapture, BackdropRender};
71use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
72use crate::prim_store::gradient::{
73    GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient,
74    ConicGradientParams, optimize_radial_gradient, apply_gradient_local_clip,
75    optimize_linear_gradient, self,
76};
77use crate::prim_store::image::{Image, YuvImage};
78use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey, get_line_decoration_size};
79use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
80use crate::prim_store::text_run::TextRun;
81use crate::render_backend::SceneView;
82use crate::resource_cache::ImageRequest;
83use crate::scene::{BuiltScene, Scene, ScenePipeline, SceneStats, StackingContextHelpers};
84use crate::scene_builder_thread::Interners;
85use crate::space::SpaceSnapper;
86use crate::spatial_node::{
87    ReferenceFrameInfo, StickyFrameInfo, ScrollFrameKind, SpatialNodeUid, SpatialNodeType
88};
89use crate::tile_cache::TileCacheBuilder;
90use euclid::approxeq::ApproxEq;
91use std::{f32, mem, usize};
92use std::collections::vec_deque::VecDeque;
93use std::sync::Arc;
94use crate::util::{VecHelper, MaxRect};
95use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
96use log::Level;
97
98/// Offsets primitives (and clips) by the external scroll offset
99/// supplied to scroll nodes.
100pub struct ScrollOffsetMapper {
101    pub current_spatial_node: SpatialNodeIndex,
102    pub current_offset: LayoutVector2D,
103}
104
105impl ScrollOffsetMapper {
106    fn new() -> Self {
107        ScrollOffsetMapper {
108            current_spatial_node: SpatialNodeIndex::INVALID,
109            current_offset: LayoutVector2D::zero(),
110        }
111    }
112
113    /// Return the accumulated external scroll offset for a spatial
114    /// node. This caches the last result, which is the common case,
115    /// or defers to the spatial tree to build the value.
116    fn external_scroll_offset(
117        &mut self,
118        spatial_node_index: SpatialNodeIndex,
119        spatial_tree: &SceneSpatialTree,
120    ) -> LayoutVector2D {
121        if spatial_node_index != self.current_spatial_node {
122            self.current_spatial_node = spatial_node_index;
123            self.current_offset = get_external_scroll_offset(spatial_tree, spatial_node_index);
124        }
125
126        self.current_offset
127    }
128}
129
130/// A data structure that keeps track of mapping between API Ids for spatials and the indices
131/// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives
132/// and clips during frame building.
133#[derive(Default)]
134pub struct NodeIdToIndexMapper {
135    spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
136}
137
138impl NodeIdToIndexMapper {
139    fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
140        let _old_value = self.spatial_node_map.insert(id, index);
141        assert!(_old_value.is_none());
142    }
143
144    fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
145        self.spatial_node_map[&id]
146    }
147}
148
149#[derive(Debug, Clone, Default)]
150pub struct CompositeOps {
151    // Requires only a single texture as input (e.g. most filters)
152    pub filters: Vec<Filter>,
153    pub filter_datas: Vec<FilterData>,
154    pub filter_primitives: Vec<FilterPrimitive>,
155    pub snapshot: Option<SnapshotInfo>,
156
157    // Requires two source textures (e.g. mix-blend-mode)
158    pub mix_blend_mode: Option<MixBlendMode>,
159}
160
161impl CompositeOps {
162    pub fn new(
163        filters: Vec<Filter>,
164        filter_datas: Vec<FilterData>,
165        filter_primitives: Vec<FilterPrimitive>,
166        mix_blend_mode: Option<MixBlendMode>,
167        snapshot: Option<SnapshotInfo>,
168    ) -> Self {
169        CompositeOps {
170            filters,
171            filter_datas,
172            filter_primitives,
173            mix_blend_mode,
174            snapshot,
175        }
176    }
177
178    pub fn is_empty(&self) -> bool {
179        self.filters.is_empty() &&
180            self.filter_primitives.is_empty() &&
181            self.mix_blend_mode.is_none() &&
182            self.snapshot.is_none()
183    }
184
185    /// Returns true if this CompositeOps contains any filters that affect
186    /// the content (false if no filters, or filters are all no-ops).
187    fn has_valid_filters(&self) -> bool {
188        // For each filter, create a new image with that composite mode.
189        let mut current_filter_data_index = 0;
190        for filter in &self.filters {
191            match filter {
192                Filter::ComponentTransfer => {
193                    let filter_data =
194                        &self.filter_datas[current_filter_data_index];
195                    let filter_data = filter_data.sanitize();
196                    current_filter_data_index = current_filter_data_index + 1;
197                    if filter_data.is_identity() {
198                        continue
199                    } else {
200                        return true;
201                    }
202                }
203                Filter::SVGGraphNode(..) => {return true;}
204                _ => {
205                    if filter.is_noop() {
206                        continue;
207                    } else {
208                        return true;
209                    }
210                }
211            }
212        }
213
214        if !self.filter_primitives.is_empty() {
215            return true;
216        }
217
218        false
219    }
220}
221
222/// Represents the current input for a picture chain builder (either a
223/// prim list from the stacking context, or a wrapped picture instance).
224enum PictureSource {
225    PrimitiveList {
226        prim_list: PrimitiveList,
227    },
228    WrappedPicture {
229        instance: PrimitiveInstance,
230    },
231}
232
233/// Helper struct to build picture chains during scene building from
234/// a flattened stacking context struct.
235struct PictureChainBuilder {
236    /// The current input source for the next picture
237    current: PictureSource,
238
239    /// Positioning node for this picture chain
240    spatial_node_index: SpatialNodeIndex,
241    /// Prim flags for any pictures in this chain
242    flags: PrimitiveFlags,
243    /// Requested raster space for enclosing stacking context
244    raster_space: RasterSpace,
245    /// If true, set first picture as a resolve target
246    set_resolve_target: bool,
247    /// If true, mark the last picture as a sub-graph
248    establishes_sub_graph: bool,
249}
250
251impl PictureChainBuilder {
252    /// Create a new picture chain builder, from a primitive list
253    fn from_prim_list(
254        prim_list: PrimitiveList,
255        flags: PrimitiveFlags,
256        spatial_node_index: SpatialNodeIndex,
257        raster_space: RasterSpace,
258        is_sub_graph: bool,
259    ) -> Self {
260        PictureChainBuilder {
261            current: PictureSource::PrimitiveList {
262                prim_list,
263            },
264            spatial_node_index,
265            flags,
266            raster_space,
267            establishes_sub_graph: is_sub_graph,
268            set_resolve_target: is_sub_graph,
269        }
270    }
271
272    /// Create a new picture chain builder, from a picture wrapper instance
273    fn from_instance(
274        instance: PrimitiveInstance,
275        flags: PrimitiveFlags,
276        spatial_node_index: SpatialNodeIndex,
277        raster_space: RasterSpace,
278    ) -> Self {
279        PictureChainBuilder {
280            current: PictureSource::WrappedPicture {
281                instance,
282            },
283            flags,
284            spatial_node_index,
285            raster_space,
286            establishes_sub_graph: false,
287            set_resolve_target: false,
288        }
289    }
290
291    /// Wrap the existing content with a new picture with the given parameters
292    #[must_use]
293    fn add_picture(
294        self,
295        composite_mode: PictureCompositeMode,
296        clip_node_id: ClipNodeId,
297        context_3d: Picture3DContext<OrderedPictureChild>,
298        interners: &mut Interners,
299        prim_store: &mut PrimitiveStore,
300        prim_instances: &mut Vec<PrimitiveInstance>,
301        clip_tree_builder: &mut ClipTreeBuilder,
302    ) -> PictureChainBuilder {
303        let prim_list = match self.current {
304            PictureSource::PrimitiveList { prim_list } => {
305                prim_list
306            }
307            PictureSource::WrappedPicture { instance } => {
308                let mut prim_list = PrimitiveList::empty();
309
310                prim_list.add_prim(
311                    instance,
312                    LayoutRect::zero(),
313                    self.spatial_node_index,
314                    self.flags,
315                    prim_instances,
316                    clip_tree_builder,
317                );
318
319                prim_list
320            }
321        };
322
323        let flags = if self.set_resolve_target {
324            PictureFlags::IS_RESOLVE_TARGET
325        } else {
326            PictureFlags::empty()
327        };
328
329        let pic_index = PictureIndex(prim_store.pictures
330            .alloc()
331            .init(PicturePrimitive::new_image(
332                Some(composite_mode.clone()),
333                context_3d,
334                self.flags,
335                prim_list,
336                self.spatial_node_index,
337                self.raster_space,
338                flags,
339                None,
340            ))
341        );
342
343        let instance = create_prim_instance(
344            pic_index,
345            Some(composite_mode).into(),
346            self.raster_space,
347            clip_node_id,
348            interners,
349            clip_tree_builder,
350        );
351
352        PictureChainBuilder {
353            current: PictureSource::WrappedPicture {
354                instance,
355            },
356            spatial_node_index: self.spatial_node_index,
357            flags: self.flags,
358            raster_space: self.raster_space,
359            // We are now on a subsequent picture, so set_resolve_target has been handled
360            set_resolve_target: false,
361            establishes_sub_graph: self.establishes_sub_graph,
362        }
363    }
364
365    /// Finish building this picture chain. Set the clip chain on the outermost picture
366    fn finalize(
367        self,
368        clip_node_id: ClipNodeId,
369        interners: &mut Interners,
370        prim_store: &mut PrimitiveStore,
371        clip_tree_builder: &mut ClipTreeBuilder,
372        snapshot: Option<SnapshotInfo>,
373    ) -> PrimitiveInstance {
374        let mut flags = PictureFlags::empty();
375        if self.establishes_sub_graph {
376            flags |= PictureFlags::IS_SUB_GRAPH;
377        }
378
379        match self.current {
380            PictureSource::WrappedPicture { instance } => {
381                let pic_index = instance.kind.as_pic();
382                let picture = &mut prim_store.pictures[pic_index.0];
383                picture.flags |= flags;
384                picture.snapshot = snapshot;
385
386                instance
387            }
388            PictureSource::PrimitiveList { prim_list } => {
389                if self.set_resolve_target {
390                    flags |= PictureFlags::IS_RESOLVE_TARGET;
391                }
392
393                // If no picture was created for this stacking context, create a
394                // pass-through wrapper now. This is only needed in 1-2 edge cases
395                // now, and will be removed as a follow up.
396
397                // If the picture is snapshotted, it needs to have a surface rather
398                // than being pass-through.
399                let composite_mode = snapshot.map(|_| PictureCompositeMode::Blit(BlitReason::SNAPSHOT));
400
401                let pic_index = PictureIndex(prim_store.pictures
402                    .alloc()
403                    .init(PicturePrimitive::new_image(
404                        composite_mode,
405                        Picture3DContext::Out,
406                        self.flags,
407                        prim_list,
408                        self.spatial_node_index,
409                        self.raster_space,
410                        flags,
411                        snapshot,
412                    ))
413                );
414
415                create_prim_instance(
416                    pic_index,
417                    None.into(),
418                    self.raster_space,
419                    clip_node_id,
420                    interners,
421                    clip_tree_builder,
422                )
423            }
424        }
425    }
426
427    /// Returns true if this builder wraps a picture
428    #[allow(dead_code)]
429    fn has_picture(&self) -> bool {
430        match self.current {
431            PictureSource::WrappedPicture { .. } => true,
432            PictureSource::PrimitiveList { .. } => false,
433        }
434    }
435}
436
437bitflags! {
438    /// Slice flags
439    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
440    pub struct SliceFlags : u8 {
441        /// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER
442        const IS_SCROLLBAR = 1;
443        /// Represents an atomic container (can't split out compositor surfaces in this slice)
444        const IS_ATOMIC = 2;
445    }
446}
447
448/// A structure that converts a serialized display list into a form that WebRender
449/// can use to later build a frame. This structure produces a BuiltScene. Public
450/// members are typically those that are destructured into the BuiltScene.
451pub struct SceneBuilder<'a> {
452    /// The scene that we are currently building.
453    scene: &'a Scene,
454
455    /// The map of all font instances.
456    fonts: SharedFontResources,
457
458    /// The data structure that converts between ClipId/SpatialId and the various
459    /// index types that the SpatialTree uses.
460    id_to_index_mapper_stack: Vec<NodeIdToIndexMapper>,
461
462    /// A stack of stacking context properties.
463    sc_stack: Vec<FlattenedStackingContext>,
464
465    /// Stack of spatial node indices forming containing block for 3d contexts
466    containing_block_stack: Vec<SpatialNodeIndex>,
467
468    /// Stack of requested raster spaces for stacking contexts
469    raster_space_stack: Vec<RasterSpace>,
470
471    /// Maintains state for any currently active shadows
472    pending_shadow_items: VecDeque<ShadowItem>,
473
474    /// The SpatialTree that we are currently building during building.
475    pub spatial_tree: &'a mut SceneSpatialTree,
476
477    /// The store of primitives.
478    pub prim_store: PrimitiveStore,
479
480    /// Information about all primitives involved in hit testing.
481    pub hit_testing_scene: HitTestingScene,
482
483    /// The store which holds all complex clipping information.
484    pub clip_store: ClipStore,
485
486    /// The configuration to use for the FrameBuilder. We consult this in
487    /// order to determine the default font.
488    pub config: FrameBuilderConfig,
489
490    /// Reference to the set of data that is interned across display lists.
491    pub interners: &'a mut Interners,
492
493    /// Helper struct to map spatial nodes to external scroll offsets.
494    external_scroll_mapper: ScrollOffsetMapper,
495
496    /// The current recursion depth of iframes encountered. Used to restrict picture
497    /// caching slices to only the top-level content frame.
498    iframe_size: Vec<LayoutSize>,
499
500    /// Clip-chain for root iframes applied to any tile caches created within this iframe
501    root_iframe_clip: Option<ClipId>,
502
503    /// The current quality / performance settings for this scene.
504    quality_settings: QualitySettings,
505
506    /// Maintains state about the list of tile caches being built for this scene.
507    tile_cache_builder: TileCacheBuilder,
508
509    /// A helper struct to snap local rects in device space. During frame
510    /// building we may establish new raster roots, however typically that is in
511    /// cases where we won't be applying snapping (e.g. has perspective), or in
512    /// edge cases (e.g. SVG filter) where we can accept slightly incorrect
513    /// behaviour in favour of getting the common case right.
514    snap_to_device: SpaceSnapper,
515
516    /// A DAG that represents dependencies between picture primitives. This builds
517    /// a set of passes to run various picture processing passes in during frame
518    /// building, in a way that pictures are processed before (or after) their
519    /// dependencies, without relying on recursion for those passes.
520    picture_graph: PictureGraph,
521
522    /// Keep track of snapshot pictures to ensure that they are rendered even if they
523    /// are off-screen and the visibility traversal does not reach them.
524    snapshot_pictures: Vec<PictureIndex>,
525
526    /// Keep track of allocated plane splitters for this scene. A plane
527    /// splitter is allocated whenever we encounter a new 3d rendering context.
528    /// They are stored outside the picture since it makes it easier for them
529    /// to be referenced by both the owning 3d rendering context and the child
530    /// pictures that contribute to the splitter.
531    /// During scene building "allocating" a splitter is just incrementing an index.
532    /// Splitter objects themselves are allocated and recycled in the frame builder.
533    next_plane_splitter_index: usize,
534
535    /// A list of all primitive instances in the scene. We store them as a single
536    /// array so that multiple different systems (e.g. tile-cache, visibility, property
537    /// animation bindings) can store index buffers to prim instances.
538    prim_instances: Vec<PrimitiveInstance>,
539
540    /// A map of pipeline ids encountered during scene build - used to create unique
541    /// pipeline instance ids as they are encountered.
542    pipeline_instance_ids: FastHashMap<PipelineId, u32>,
543
544    /// A list of surfaces (backing textures) that are relevant for this scene.
545    /// Every picture is assigned to a surface (either a new surface if the picture
546    /// has a composite mode, or the parent surface if it's a pass-through).
547    surfaces: Vec<SurfaceInfo>,
548
549    /// Used to build a ClipTree from the clip-chains, clips and state during scene building.
550    clip_tree_builder: ClipTreeBuilder,
551
552    /// Some primitives need to nest two stacking contexts instead of one
553    /// (see push_stacking_context). We keep track of the extra stacking context info
554    /// here and set a boolean on the inner stacking context info to remember to
555    /// pop from this stack (see StackingContextInfo::needs_extra_stacking_context)
556    extra_stacking_context_stack: Vec<StackingContextInfo>,
557}
558
559impl<'a> SceneBuilder<'a> {
560    pub fn build(
561        scene: &Scene,
562        fonts: SharedFontResources,
563        view: &SceneView,
564        frame_builder_config: &FrameBuilderConfig,
565        interners: &mut Interners,
566        spatial_tree: &mut SceneSpatialTree,
567        recycler: &mut SceneRecycler,
568        stats: &SceneStats,
569        debug_flags: DebugFlags,
570    ) -> BuiltScene {
571        profile_scope!("build_scene");
572
573        // We checked that the root pipeline is available on the render backend.
574        let root_pipeline_id = scene.root_pipeline_id.unwrap();
575        let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
576        let root_reference_frame_index = spatial_tree.root_reference_frame_index();
577
578        // During scene building, we assume a 1:1 picture -> raster pixel scale
579        let snap_to_device = SpaceSnapper::new(
580            root_reference_frame_index,
581            RasterPixelScale::new(1.0),
582        );
583
584        let mut builder = SceneBuilder {
585            scene,
586            spatial_tree,
587            fonts,
588            config: *frame_builder_config,
589            id_to_index_mapper_stack: mem::take(&mut recycler.id_to_index_mapper_stack),
590            hit_testing_scene: recycler.hit_testing_scene.take().unwrap_or_else(|| HitTestingScene::new(&stats.hit_test_stats)),
591            pending_shadow_items: mem::take(&mut recycler.pending_shadow_items),
592            sc_stack: mem::take(&mut recycler.sc_stack),
593            containing_block_stack: mem::take(&mut recycler.containing_block_stack),
594            raster_space_stack: mem::take(&mut recycler.raster_space_stack),
595            prim_store: mem::take(&mut recycler.prim_store),
596            clip_store: mem::take(&mut recycler.clip_store),
597            interners,
598            external_scroll_mapper: ScrollOffsetMapper::new(),
599            iframe_size: mem::take(&mut recycler.iframe_size),
600            root_iframe_clip: None,
601            quality_settings: view.quality_settings,
602            tile_cache_builder: TileCacheBuilder::new(
603                root_reference_frame_index,
604                frame_builder_config.background_color,
605                debug_flags,
606            ),
607            snap_to_device,
608            picture_graph: mem::take(&mut recycler.picture_graph),
609            // This vector is empty most of the time, don't bother with recycling it for now.
610            snapshot_pictures: Vec::new(),
611            next_plane_splitter_index: 0,
612            prim_instances: mem::take(&mut recycler.prim_instances),
613            pipeline_instance_ids: FastHashMap::default(),
614            surfaces: mem::take(&mut recycler.surfaces),
615            clip_tree_builder: recycler.clip_tree_builder.take().unwrap_or_else(|| ClipTreeBuilder::new()),
616            extra_stacking_context_stack: Vec::new(),
617        };
618
619        // Reset
620        builder.hit_testing_scene.reset();
621        builder.prim_store.reset();
622        builder.clip_store.reset();
623        builder.picture_graph.reset();
624        builder.prim_instances.clear();
625        builder.surfaces.clear();
626        builder.sc_stack.clear();
627        builder.containing_block_stack.clear();
628        builder.id_to_index_mapper_stack.clear();
629        builder.pending_shadow_items.clear();
630        builder.iframe_size.clear();
631
632        builder.raster_space_stack.clear();
633        builder.raster_space_stack.push(RasterSpace::Screen);
634
635        builder.clip_tree_builder.begin();
636
637        builder.build_all(
638            root_pipeline_id,
639            &root_pipeline,
640        );
641
642        // Construct the picture cache primitive instance(s) from the tile cache builder
643        let (tile_cache_config, tile_cache_pictures) = builder.tile_cache_builder.build(
644            &builder.config,
645            &mut builder.prim_store,
646            &builder.spatial_tree,
647            &builder.prim_instances,
648            &mut builder.clip_tree_builder,
649            &builder.interners,
650        );
651
652        for pic_index in &builder.snapshot_pictures {
653            builder.picture_graph.add_root(*pic_index);
654        }
655
656        // Add all the tile cache pictures as roots of the picture graph
657        for pic_index in &tile_cache_pictures {
658            builder.picture_graph.add_root(*pic_index);
659            SceneBuilder::finalize_picture(
660                *pic_index,
661                None,
662                &mut builder.prim_store.pictures,
663                None,
664                &builder.clip_tree_builder,
665                &builder.prim_instances,
666                &builder.interners.clip,
667            );
668        }
669
670        let clip_tree = builder.clip_tree_builder.finalize();
671
672        recycler.clip_tree_builder = Some(builder.clip_tree_builder);
673        recycler.sc_stack = builder.sc_stack;
674        recycler.id_to_index_mapper_stack = builder.id_to_index_mapper_stack;
675        recycler.containing_block_stack = builder.containing_block_stack;
676        recycler.raster_space_stack = builder.raster_space_stack;
677        recycler.pending_shadow_items = builder.pending_shadow_items;
678        recycler.iframe_size = builder.iframe_size;
679
680        BuiltScene {
681            has_root_pipeline: scene.has_root_pipeline(),
682            pipeline_epochs: scene.pipeline_epochs.clone(),
683            output_rect: view.device_rect.size().into(),
684            hit_testing_scene: Arc::new(builder.hit_testing_scene),
685            prim_store: builder.prim_store,
686            clip_store: builder.clip_store,
687            config: builder.config,
688            tile_cache_config,
689            snapshot_pictures: builder.snapshot_pictures,
690            tile_cache_pictures,
691            picture_graph: builder.picture_graph,
692            num_plane_splitters: builder.next_plane_splitter_index,
693            prim_instances: builder.prim_instances,
694            surfaces: builder.surfaces,
695            clip_tree,
696            recycler_tx: Some(recycler.tx.clone()),
697        }
698    }
699
700    /// Traverse the picture prim list and update any late-set spatial nodes.
701    /// Also, for each picture primitive, store the lowest-common-ancestor
702    /// of all of the contained primitives' clips.
703    // TODO(gw): This is somewhat hacky - it's unfortunate we need to do this, but it's
704    //           because we can't determine the scroll root until we have checked all the
705    //           primitives in the slice. Perhaps we could simplify this by doing some
706    //           work earlier in the DL builder, so we know what scroll root will be picked?
707    fn finalize_picture(
708        pic_index: PictureIndex,
709        prim_index: Option<usize>,
710        pictures: &mut [PicturePrimitive],
711        parent_spatial_node_index: Option<SpatialNodeIndex>,
712        clip_tree_builder: &ClipTreeBuilder,
713        prim_instances: &[PrimitiveInstance],
714        clip_interner: &Interner<ClipIntern>,
715    ) {
716        // Extract the prim_list (borrow check) and select the spatial node to
717        // assign to unknown clusters
718        let (mut prim_list, spatial_node_index) = {
719            let pic = &mut pictures[pic_index.0];
720            assert_ne!(pic.spatial_node_index, SpatialNodeIndex::UNKNOWN);
721
722            if pic.flags.contains(PictureFlags::IS_RESOLVE_TARGET) {
723                pic.flags |= PictureFlags::DISABLE_SNAPPING;
724            }
725
726            // If we're a surface, use that spatial node, otherwise the parent
727            let spatial_node_index = match pic.composite_mode {
728                Some(_) => pic.spatial_node_index,
729                None => parent_spatial_node_index.expect("bug: no parent"),
730            };
731
732            (
733                mem::replace(&mut pic.prim_list, PrimitiveList::empty()),
734                spatial_node_index,
735            )
736        };
737
738        // Update the spatial node of any unknown clusters
739        for cluster in &mut prim_list.clusters {
740            if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
741                cluster.spatial_node_index = spatial_node_index;
742            }
743        }
744
745        // Work out the lowest common clip which is shared by all the
746        // primitives in this picture.  If it is the same as the picture clip
747        // then store it as the clip tree root for the picture so that it is
748        // applied later as part of picture compositing.  Gecko gives every
749        // primitive a viewport clip which, if applied within the picture,
750        // will mess up tile caching and mean we have to redraw on every
751        // scroll event (for tile caching to work usefully we specifically
752        // want to draw things even if they are outside the viewport).
753        let mut shared_clip_node_id = None;
754
755        // Snapshot picture are special. All clips belonging to parents
756        // *must* be extracted from the snapshot, so we rely on this optimization
757        // taking out parent clips and it overrides other conditions.
758        // In addition we need to ensure that only parent clips are extracted.
759        let is_snapshot = pictures[pic_index.0].snapshot.is_some();
760
761        if is_snapshot {
762            // In the general case, if all of the children of a picture share the
763            // same clips, then these clips are hoisted up in the parent picture,
764            // however we rely on child clips of snapshotted pictures to be baked
765            // into the snapshot.
766            // Snapshotted pictures use the parent of their clip node (if any)
767            // as the clip root, to ensure that the parent clip hierarchy is
768            // extracted from clip chains inside the snapshot, and to make sure
769            // that child clips of the snapshots are not hoisted out of the
770            // snapshot even when all children of the snapshotted picture share
771            // a clip.
772            if let Some(idx) = prim_index {
773                let clip_node = clip_tree_builder.get_leaf(prim_instances[idx].clip_leaf_id).node_id;
774                shared_clip_node_id = clip_tree_builder.get_parent(clip_node);
775            }
776        } else {
777            for cluster in &prim_list.clusters {
778                for prim_instance in &prim_instances[cluster.prim_range()] {
779                    let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
780
781                    shared_clip_node_id = match shared_clip_node_id {
782                        Some(current) => {
783                            Some(clip_tree_builder.find_lowest_common_ancestor(
784                                current,
785                                leaf.node_id,
786                            ))
787                        }
788                        None => Some(leaf.node_id)
789                    };
790                }
791            }
792        }
793
794        let lca_tree_node = shared_clip_node_id
795            .and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id))
796            .map(|node_id| clip_tree_builder.get_node(node_id));
797        let lca_node = lca_tree_node
798            .map(|tree_node| &clip_interner[tree_node.handle]);
799        let pic_node_id = prim_index
800            .map(|prim_index| clip_tree_builder.get_leaf(prim_instances[prim_index].clip_leaf_id).node_id)
801            .and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id));
802        let pic_node = pic_node_id
803            .map(|node_id| clip_tree_builder.get_node(node_id))
804            .map(|tree_node| &clip_interner[tree_node.handle]);
805
806        // The logic behind this optimisation is that there's no need to clip
807        // the contents of a picture when the crop will be applied anyway as
808        // part of compositing the picture.  However, this is not true if the
809        // picture includes a blur filter as the blur result depends on the
810        // offscreen pixels which may or may not be cropped away.
811        let has_blur = match &pictures[pic_index.0].composite_mode {
812            Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true,
813            Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true,
814            Some(PictureCompositeMode::SvgFilter( .. )) => true,
815            Some(PictureCompositeMode::SVGFEGraph( .. )) => true,
816            _ => false,
817        };
818
819        // It is only safe to apply this optimisation if the old pic clip node
820        // is the direct parent of the new LCA node.  If this is not the case
821        // then there could be other more restrictive clips in between the two
822        // which we would ignore by changing the clip root.  See Bug 1854062
823        // for an example of this.
824        let direct_parent = lca_tree_node
825            .zip(pic_node_id)
826            .map(|(lca_tree_node, pic_node_id)| lca_tree_node.parent == pic_node_id)
827            .unwrap_or(false);
828
829        let should_set_clip_root = is_snapshot || lca_node.zip(pic_node).map_or(false, |(lca_node, pic_node)| {
830            // It is only safe to ignore the LCA clip (by making it the clip
831            // root) if it is equal to or larger than the picture clip. But
832            // this comparison also needs to take into account spatial nodes
833            // as the two clips may in general be on different spatial nodes.
834            // For this specific Gecko optimisation we expect the the two
835            // clips to be identical and have the same spatial node so it's
836            // simplest to just test for ClipItemKey equality (which includes
837            // both spatial node and the actual clip).
838            lca_node.key == pic_node.key && !has_blur && direct_parent
839        });
840
841        if should_set_clip_root {
842            pictures[pic_index.0].clip_root = shared_clip_node_id;
843        }
844
845        // Update the spatial node of any child pictures
846        for cluster in &prim_list.clusters {
847            for prim_instance_index in cluster.prim_range() {
848                if let PrimitiveInstanceKind::Picture { pic_index: child_pic_index, .. } = prim_instances[prim_instance_index].kind {
849                    let child_pic = &mut pictures[child_pic_index.0];
850
851                    if child_pic.spatial_node_index == SpatialNodeIndex::UNKNOWN {
852                        child_pic.spatial_node_index = spatial_node_index;
853                    }
854
855                    // Recurse into child pictures which may also have unknown spatial nodes
856                    SceneBuilder::finalize_picture(
857                        child_pic_index,
858                        Some(prim_instance_index),
859                        pictures,
860                        Some(spatial_node_index),
861                        clip_tree_builder,
862                        prim_instances,
863                        clip_interner,
864                    );
865
866                    if pictures[child_pic_index.0].flags.contains(PictureFlags::DISABLE_SNAPPING) {
867                        pictures[pic_index.0].flags |= PictureFlags::DISABLE_SNAPPING;
868                    }
869                }
870            }
871        }
872
873        // Restore the prim_list
874        pictures[pic_index.0].prim_list = prim_list;
875    }
876
877    /// Retrieve the current external scroll offset on the provided spatial node.
878    fn current_external_scroll_offset(
879        &mut self,
880        spatial_node_index: SpatialNodeIndex,
881    ) -> LayoutVector2D {
882        // Get the external scroll offset, if applicable.
883        self.external_scroll_mapper
884            .external_scroll_offset(
885                spatial_node_index,
886                self.spatial_tree,
887            )
888    }
889
890    fn build_spatial_tree_for_display_list(
891        &mut self,
892        dl: &BuiltDisplayList,
893        pipeline_id: PipelineId,
894        instance_id: PipelineInstanceId,
895    ) {
896        dl.iter_spatial_tree(|item| {
897            match item {
898                SpatialTreeItem::ScrollFrame(descriptor) => {
899                    let parent_space = self.get_space(descriptor.parent_space);
900                    self.build_scroll_frame(
901                        descriptor,
902                        parent_space,
903                        pipeline_id,
904                        instance_id,
905                    );
906                }
907                SpatialTreeItem::ReferenceFrame(descriptor) => {
908                    let parent_space = self.get_space(descriptor.parent_spatial_id);
909                    self.build_reference_frame(
910                        descriptor,
911                        parent_space,
912                        pipeline_id,
913                        instance_id,
914                    );
915                }
916                SpatialTreeItem::StickyFrame(descriptor) => {
917                    let parent_space = self.get_space(descriptor.parent_spatial_id);
918                    self.build_sticky_frame(
919                        descriptor,
920                        parent_space,
921                        instance_id,
922                    );
923                }
924                SpatialTreeItem::Invalid => {
925                    unreachable!();
926                }
927            }
928        });
929    }
930
931    fn build_all(
932        &mut self,
933        root_pipeline_id: PipelineId,
934        root_pipeline: &ScenePipeline,
935    ) {
936        enum ContextKind<'a> {
937            Root,
938            StackingContext {
939                sc_info: StackingContextInfo,
940            },
941            ReferenceFrame,
942            Iframe {
943                parent_traversal: BuiltDisplayListIter<'a>,
944            }
945        }
946        struct BuildContext<'a> {
947            pipeline_id: PipelineId,
948            kind: ContextKind<'a>,
949        }
950
951        self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default());
952
953        let instance_id = self.get_next_instance_id_for_pipeline(root_pipeline_id);
954
955        self.push_root(
956            root_pipeline_id,
957            instance_id,
958        );
959        self.build_spatial_tree_for_display_list(
960            &root_pipeline.display_list.display_list,
961            root_pipeline_id,
962            instance_id,
963        );
964
965        let mut stack = vec![BuildContext {
966            pipeline_id: root_pipeline_id,
967            kind: ContextKind::Root,
968        }];
969        let mut traversal = root_pipeline.display_list.iter();
970
971        'outer: while let Some(bc) = stack.pop() {
972            loop {
973                let item = match traversal.next() {
974                    Some(item) => item,
975                    None => break,
976                };
977
978                match item.item() {
979                    DisplayItem::PushStackingContext(ref info) => {
980                        profile_scope!("build_stacking_context");
981                        let spatial_node_index = self.get_space(info.spatial_id);
982                        let mut subtraversal = item.sub_iter();
983                        // Avoid doing unnecessary work for empty stacking contexts.
984                        // We still have to process it if it has filters, they
985                        // may be things like SVGFEFlood or various specific
986                        // ways to use ComponentTransfer, ColorMatrix, Composite
987                        // which are still visible on an empty stacking context
988                        if subtraversal.current_stacking_context_empty() && item.filters().is_empty() {
989                            subtraversal.skip_current_stacking_context();
990                            traversal = subtraversal;
991                            continue;
992                        }
993
994                        let snapshot = info.snapshot.map(|snapshot| {
995                            // Offset the snapshot area by the stacking context origin
996                            // so that the area is expressed in the same coordinate space
997                            // as the items in the stacking context.
998                            SnapshotInfo {
999                                area: snapshot.area.translate(info.origin.to_vector()),
1000                                .. snapshot
1001                            }
1002                        });
1003
1004                        let composition_operations = CompositeOps::new(
1005                            filter_ops_for_compositing(item.filters()),
1006                            filter_datas_for_compositing(item.filter_datas()),
1007                            filter_primitives_for_compositing(item.filter_primitives()),
1008                            info.stacking_context.mix_blend_mode_for_compositing(),
1009                            snapshot,
1010                        );
1011
1012                        let sc_info = self.push_stacking_context(
1013                            composition_operations,
1014                            info.stacking_context.transform_style,
1015                            info.prim_flags,
1016                            spatial_node_index,
1017                            info.stacking_context.clip_chain_id,
1018                            info.stacking_context.raster_space,
1019                            info.stacking_context.flags,
1020                            info.ref_frame_offset + info.origin.to_vector(),
1021                        );
1022
1023                        let new_context = BuildContext {
1024                            pipeline_id: bc.pipeline_id,
1025                            kind: ContextKind::StackingContext {
1026                                sc_info,
1027                            },
1028                        };
1029                        stack.push(bc);
1030                        stack.push(new_context);
1031
1032                        subtraversal.merge_debug_stats_from(&mut traversal);
1033                        traversal = subtraversal;
1034                        continue 'outer;
1035                    }
1036                    DisplayItem::PushReferenceFrame(..) => {
1037                        profile_scope!("build_reference_frame");
1038                        let mut subtraversal = item.sub_iter();
1039
1040                        let new_context = BuildContext {
1041                            pipeline_id: bc.pipeline_id,
1042                            kind: ContextKind::ReferenceFrame,
1043                        };
1044                        stack.push(bc);
1045                        stack.push(new_context);
1046
1047                        subtraversal.merge_debug_stats_from(&mut traversal);
1048                        traversal = subtraversal;
1049                        continue 'outer;
1050                    }
1051                    DisplayItem::PopReferenceFrame |
1052                    DisplayItem::PopStackingContext => break,
1053                    DisplayItem::Iframe(ref info) => {
1054                        profile_scope!("iframe");
1055
1056                        let space = self.get_space(info.space_and_clip.spatial_id);
1057                        let subtraversal = match self.push_iframe(info, space) {
1058                            Some(pair) => pair,
1059                            None => continue,
1060                        };
1061
1062                        let new_context = BuildContext {
1063                            pipeline_id: info.pipeline_id,
1064                            kind: ContextKind::Iframe {
1065                                parent_traversal: mem::replace(&mut traversal, subtraversal),
1066                            },
1067                        };
1068                        stack.push(bc);
1069                        stack.push(new_context);
1070                        continue 'outer;
1071                    }
1072                    _ => {
1073                        self.build_item(item);
1074                    }
1075                };
1076            }
1077
1078            match bc.kind {
1079                ContextKind::Root => {}
1080                ContextKind::StackingContext { sc_info } => {
1081                    self.pop_stacking_context(sc_info);
1082                }
1083                ContextKind::ReferenceFrame => {
1084                }
1085                ContextKind::Iframe { parent_traversal } => {
1086                    self.iframe_size.pop();
1087                    self.clip_tree_builder.pop_clip();
1088                    self.clip_tree_builder.pop_clip();
1089
1090                    if self.iframe_size.is_empty() {
1091                        assert!(self.root_iframe_clip.is_some());
1092                        self.root_iframe_clip = None;
1093                        self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
1094                    }
1095
1096                    self.id_to_index_mapper_stack.pop().unwrap();
1097
1098                    traversal = parent_traversal;
1099                }
1100            }
1101
1102            // TODO: factor this out to be part of capture
1103            if cfg!(feature = "display_list_stats") {
1104                let stats = traversal.debug_stats();
1105                let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
1106                debug!("item, total count, total bytes, % of DL bytes, bytes per item");
1107                for (label, stats) in stats {
1108                    debug!("{}, {}, {}kb, {}%, {}",
1109                        label,
1110                        stats.total_count,
1111                        stats.num_bytes / 1000,
1112                        ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
1113                        stats.num_bytes / stats.total_count.max(1));
1114                }
1115                debug!("");
1116            }
1117        }
1118
1119        debug_assert!(self.sc_stack.is_empty());
1120
1121        self.id_to_index_mapper_stack.pop().unwrap();
1122        assert!(self.id_to_index_mapper_stack.is_empty());
1123    }
1124
1125    fn build_sticky_frame(
1126        &mut self,
1127        info: &StickyFrameDescriptor,
1128        parent_node_index: SpatialNodeIndex,
1129        instance_id: PipelineInstanceId,
1130    ) {
1131        let external_scroll_offset = self.current_external_scroll_offset(parent_node_index);
1132
1133        let sticky_frame_info = StickyFrameInfo::new(
1134            info.bounds.translate(external_scroll_offset),
1135            info.margins,
1136            info.vertical_offset_bounds,
1137            info.horizontal_offset_bounds,
1138            info.previously_applied_offset,
1139            info.transform,
1140        );
1141
1142        let index = self.spatial_tree.add_sticky_frame(
1143            parent_node_index,
1144            sticky_frame_info,
1145            info.id.pipeline_id(),
1146            info.key,
1147            instance_id,
1148        );
1149        self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(info.id, index);
1150    }
1151
1152    fn build_reference_frame(
1153        &mut self,
1154        info: &ReferenceFrameDescriptor,
1155        parent_space: SpatialNodeIndex,
1156        pipeline_id: PipelineId,
1157        instance_id: PipelineInstanceId,
1158    ) {
1159        let transform = match info.reference_frame.transform {
1160            ReferenceTransformBinding::Static { binding } => binding,
1161            ReferenceTransformBinding::Computed { scale_from, vertical_flip, rotation } => {
1162                let content_size = &self.iframe_size.last().unwrap();
1163
1164                let mut transform = if let Some(scale_from) = scale_from {
1165                    // If we have a 90/270 degree rotation, then scale_from
1166                    // and content_size are in different coordinate spaces and
1167                    // we need to swap width/height for them to be correct.
1168                    match rotation {
1169                        Rotation::Degree0 |
1170                        Rotation::Degree180 => {
1171                            LayoutTransform::scale(
1172                                content_size.width / scale_from.width,
1173                                content_size.height / scale_from.height,
1174                                1.0
1175                            )
1176                        },
1177                        Rotation::Degree90 |
1178                        Rotation::Degree270 => {
1179                            LayoutTransform::scale(
1180                                content_size.height / scale_from.width,
1181                                content_size.width / scale_from.height,
1182                                1.0
1183                            )
1184
1185                        }
1186                    }
1187                } else {
1188                    LayoutTransform::identity()
1189                };
1190
1191                if vertical_flip {
1192                    let content_size = &self.iframe_size.last().unwrap();
1193                    let content_height = match rotation {
1194                        Rotation::Degree0 | Rotation::Degree180 => content_size.height,
1195                        Rotation::Degree90 | Rotation::Degree270 => content_size.width,
1196                    };
1197                    transform = transform
1198                        .then_translate(LayoutVector3D::new(0.0, content_height, 0.0))
1199                        .pre_scale(1.0, -1.0, 1.0);
1200                }
1201
1202                let rotate = rotation.to_matrix(**content_size);
1203                let transform = transform.then(&rotate);
1204
1205                PropertyBinding::Value(transform)
1206            },
1207        };
1208
1209        let external_scroll_offset = self.current_external_scroll_offset(parent_space);
1210
1211        self.push_reference_frame(
1212            info.reference_frame.id,
1213            parent_space,
1214            pipeline_id,
1215            info.reference_frame.transform_style,
1216            transform,
1217            info.reference_frame.kind,
1218            (info.origin + external_scroll_offset).to_vector(),
1219            SpatialNodeUid::external(info.reference_frame.key, pipeline_id, instance_id),
1220        );
1221    }
1222
1223    fn build_scroll_frame(
1224        &mut self,
1225        info: &ScrollFrameDescriptor,
1226        parent_node_index: SpatialNodeIndex,
1227        pipeline_id: PipelineId,
1228        instance_id: PipelineInstanceId,
1229    ) {
1230        // This is useful when calculating scroll extents for the
1231        // SpatialNode::scroll(..) API as well as for properly setting sticky
1232        // positioning offsets.
1233        let content_size = info.content_rect.size();
1234        let external_scroll_offset = self.current_external_scroll_offset(parent_node_index);
1235
1236        self.add_scroll_frame(
1237            info.scroll_frame_id,
1238            parent_node_index,
1239            info.external_id,
1240            pipeline_id,
1241            &info.frame_rect.translate(external_scroll_offset),
1242            &content_size,
1243            ScrollFrameKind::Explicit,
1244            info.external_scroll_offset,
1245            info.scroll_offset_generation,
1246            info.has_scroll_linked_effect,
1247            SpatialNodeUid::external(info.key, pipeline_id, instance_id),
1248        );
1249    }
1250
1251    /// Advance and return the next instance id for a given pipeline id
1252    fn get_next_instance_id_for_pipeline(
1253        &mut self,
1254        pipeline_id: PipelineId,
1255    ) -> PipelineInstanceId {
1256        let next_instance = self.pipeline_instance_ids
1257            .entry(pipeline_id)
1258            .or_insert(0);
1259
1260        let instance_id = PipelineInstanceId::new(*next_instance);
1261        *next_instance += 1;
1262
1263        instance_id
1264    }
1265
1266    fn push_iframe(
1267        &mut self,
1268        info: &IframeDisplayItem,
1269        spatial_node_index: SpatialNodeIndex,
1270    ) -> Option<BuiltDisplayListIter<'a>> {
1271        let iframe_pipeline_id = info.pipeline_id;
1272        let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
1273            Some(pipeline) => pipeline,
1274            None => {
1275                debug_assert!(info.ignore_missing_pipeline);
1276                return None
1277            },
1278        };
1279
1280        self.clip_tree_builder.push_clip_chain(Some(info.space_and_clip.clip_chain_id), false);
1281
1282        // TODO(gw): This is the only remaining call site that relies on ClipId parenting, remove me!
1283        self.add_rect_clip_node(
1284            ClipId::root(iframe_pipeline_id),
1285            info.space_and_clip.spatial_id,
1286            &info.clip_rect,
1287        );
1288
1289        self.clip_tree_builder.push_clip_id(ClipId::root(iframe_pipeline_id));
1290
1291        let instance_id = self.get_next_instance_id_for_pipeline(iframe_pipeline_id);
1292
1293        self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default());
1294
1295        let bounds = self.normalize_scroll_offset_and_snap_rect(
1296            &info.bounds,
1297            spatial_node_index,
1298        );
1299
1300        let spatial_node_index = self.push_reference_frame(
1301            SpatialId::root_reference_frame(iframe_pipeline_id),
1302            spatial_node_index,
1303            iframe_pipeline_id,
1304            TransformStyle::Flat,
1305            PropertyBinding::Value(LayoutTransform::identity()),
1306            ReferenceFrameKind::Transform {
1307                is_2d_scale_translation: true,
1308                should_snap: true,
1309                paired_with_perspective: false,
1310            },
1311            bounds.min.to_vector(),
1312            SpatialNodeUid::root_reference_frame(iframe_pipeline_id, instance_id),
1313        );
1314
1315        let iframe_rect = LayoutRect::from_size(bounds.size());
1316        let is_root_pipeline = self.iframe_size.is_empty();
1317
1318        self.add_scroll_frame(
1319            SpatialId::root_scroll_node(iframe_pipeline_id),
1320            spatial_node_index,
1321            ExternalScrollId(0, iframe_pipeline_id),
1322            iframe_pipeline_id,
1323            &iframe_rect,
1324            &bounds.size(),
1325            ScrollFrameKind::PipelineRoot {
1326                is_root_pipeline,
1327            },
1328            LayoutVector2D::zero(),
1329            APZScrollGeneration::default(),
1330            HasScrollLinkedEffect::No,
1331            SpatialNodeUid::root_scroll_frame(iframe_pipeline_id, instance_id),
1332        );
1333
1334        // If this is a root iframe, force a new tile cache both before and after
1335        // adding primitives for this iframe.
1336        if self.iframe_size.is_empty() {
1337            assert!(self.root_iframe_clip.is_none());
1338            self.root_iframe_clip = Some(ClipId::root(iframe_pipeline_id));
1339            self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
1340        }
1341        self.iframe_size.push(bounds.size());
1342
1343        self.build_spatial_tree_for_display_list(
1344            &pipeline.display_list.display_list,
1345            iframe_pipeline_id,
1346            instance_id,
1347        );
1348
1349        Some(pipeline.display_list.iter())
1350    }
1351
1352    fn get_space(
1353        &self,
1354        spatial_id: SpatialId,
1355    ) -> SpatialNodeIndex {
1356        self.id_to_index_mapper_stack.last().unwrap().get_spatial_node_index(spatial_id)
1357    }
1358
1359    fn get_clip_node(
1360        &mut self,
1361        clip_chain_id: api::ClipChainId,
1362    ) -> ClipNodeId {
1363        self.clip_tree_builder.build_clip_set(
1364            clip_chain_id,
1365        )
1366    }
1367
1368    fn process_common_properties(
1369        &mut self,
1370        common: &CommonItemProperties,
1371        bounds: Option<LayoutRect>,
1372    ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) {
1373        let spatial_node_index = self.get_space(common.spatial_id);
1374
1375        // If no bounds rect is given, default to clip rect.
1376        let mut clip_rect = common.clip_rect;
1377        let mut prim_rect = bounds.unwrap_or(clip_rect);
1378        let unsnapped_rect = self.normalize_rect_scroll_offset(&prim_rect, spatial_node_index);
1379
1380        // If antialiased, no need to snap but we still need to remove the
1381        // external scroll offset (it's applied later during frame building,
1382        // so that we don't intern to a different hash and invalidate content
1383        // in picture caches unnecessarily).
1384        if common.flags.contains(PrimitiveFlags::ANTIALISED) {
1385            prim_rect = self.normalize_rect_scroll_offset(&prim_rect, spatial_node_index);
1386            clip_rect = self.normalize_rect_scroll_offset(&clip_rect, spatial_node_index);
1387        } else {
1388            clip_rect = self.normalize_scroll_offset_and_snap_rect(
1389                &clip_rect,
1390                spatial_node_index,
1391            );
1392
1393            prim_rect = self.normalize_scroll_offset_and_snap_rect(
1394                &prim_rect,
1395                spatial_node_index,
1396            );
1397        }
1398
1399        let clip_node_id = self.get_clip_node(
1400            common.clip_chain_id,
1401        );
1402
1403        let layout = LayoutPrimitiveInfo {
1404            rect: prim_rect,
1405            clip_rect,
1406            flags: common.flags,
1407        };
1408
1409        (layout, unsnapped_rect, spatial_node_index, clip_node_id)
1410    }
1411
1412    fn process_common_properties_with_bounds(
1413        &mut self,
1414        common: &CommonItemProperties,
1415        bounds: LayoutRect,
1416    ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) {
1417        self.process_common_properties(
1418            common,
1419            Some(bounds),
1420        )
1421    }
1422
1423    // Remove the effect of the external scroll offset embedded in the display list
1424    // coordinates by Gecko. This ensures that we don't necessarily invalidate picture
1425    // cache tiles due to the embedded scroll offsets.
1426    fn normalize_rect_scroll_offset(
1427        &mut self,
1428        rect: &LayoutRect,
1429        spatial_node_index: SpatialNodeIndex,
1430    ) -> LayoutRect {
1431        let current_offset = self.current_external_scroll_offset(spatial_node_index);
1432
1433        rect.translate(current_offset)
1434    }
1435
1436    // Remove external scroll offset and snap a rect. The external scroll offset must
1437    // be removed first, as it may be fractional (which we don't want to affect the
1438    // snapping behavior during scene building).
1439    fn normalize_scroll_offset_and_snap_rect(
1440        &mut self,
1441        rect: &LayoutRect,
1442        target_spatial_node: SpatialNodeIndex,
1443    ) -> LayoutRect {
1444        let rect = self.normalize_rect_scroll_offset(rect, target_spatial_node);
1445
1446        self.snap_to_device.set_target_spatial_node(
1447            target_spatial_node,
1448            self.spatial_tree,
1449        );
1450        self.snap_to_device.snap_rect(&rect)
1451    }
1452
1453    fn build_item<'b>(
1454        &'b mut self,
1455        item: DisplayItemRef,
1456    ) {
1457        match *item.item() {
1458            DisplayItem::Image(ref info) => {
1459                profile_scope!("image");
1460
1461                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1462                    &info.common,
1463                    info.bounds,
1464                );
1465
1466                self.add_image(
1467                    spatial_node_index,
1468                    clip_node_id,
1469                    &layout,
1470                    layout.rect.size(),
1471                    LayoutSize::zero(),
1472                    info.image_key,
1473                    info.image_rendering,
1474                    info.alpha_type,
1475                    info.color,
1476                );
1477            }
1478            DisplayItem::RepeatingImage(ref info) => {
1479                profile_scope!("repeating_image");
1480
1481                let (layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1482                    &info.common,
1483                    info.bounds,
1484                );
1485
1486                let stretch_size = process_repeat_size(
1487                    &layout.rect,
1488                    &unsnapped_rect,
1489                    info.stretch_size,
1490                );
1491
1492                self.add_image(
1493                    spatial_node_index,
1494                    clip_node_id,
1495                    &layout,
1496                    stretch_size,
1497                    info.tile_spacing,
1498                    info.image_key,
1499                    info.image_rendering,
1500                    info.alpha_type,
1501                    info.color,
1502                );
1503            }
1504            DisplayItem::YuvImage(ref info) => {
1505                profile_scope!("yuv_image");
1506
1507                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1508                    &info.common,
1509                    info.bounds,
1510                );
1511
1512                self.add_yuv_image(
1513                    spatial_node_index,
1514                    clip_node_id,
1515                    &layout,
1516                    info.yuv_data,
1517                    info.color_depth,
1518                    info.color_space,
1519                    info.color_range,
1520                    info.image_rendering,
1521                );
1522            }
1523            DisplayItem::Text(ref info) => {
1524                profile_scope!("text");
1525
1526                // TODO(aosmond): Snapping text primitives does not make much sense, given the
1527                // primitive bounds and clip are supposed to be conservative, not definitive.
1528                // E.g. they should be able to grow and not impact the output. However there
1529                // are subtle interactions between the primitive origin and the glyph offset
1530                // which appear to be significant (presumably due to some sort of accumulated
1531                // error throughout the layers). We should fix this at some point.
1532                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1533                    &info.common,
1534                    info.bounds,
1535                );
1536
1537                self.add_text(
1538                    spatial_node_index,
1539                    clip_node_id,
1540                    &layout,
1541                    &info.font_key,
1542                    &info.color,
1543                    item.glyphs(),
1544                    info.glyph_options,
1545                    info.ref_frame_offset,
1546                );
1547            }
1548            DisplayItem::Rectangle(ref info) => {
1549                profile_scope!("rect");
1550
1551                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1552                    &info.common,
1553                    info.bounds,
1554                );
1555
1556                self.add_primitive(
1557                    spatial_node_index,
1558                    clip_node_id,
1559                    &layout,
1560                    Vec::new(),
1561                    PrimitiveKeyKind::Rectangle {
1562                        color: info.color.into(),
1563                    },
1564                );
1565
1566                if info.common.flags.contains(PrimitiveFlags::CHECKERBOARD_BACKGROUND) {
1567                    self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
1568                }
1569            }
1570            DisplayItem::HitTest(ref info) => {
1571                profile_scope!("hit_test");
1572
1573                let spatial_node_index = self.get_space(info.spatial_id);
1574
1575                let rect = self.normalize_scroll_offset_and_snap_rect(
1576                    &info.rect,
1577                    spatial_node_index,
1578                );
1579
1580                let layout = LayoutPrimitiveInfo {
1581                    rect,
1582                    clip_rect: rect,
1583                    flags: info.flags,
1584                };
1585
1586                let spatial_node = self.spatial_tree.get_node_info(spatial_node_index);
1587                let anim_id: u64 =  match spatial_node.node_type {
1588                    SpatialNodeType::ReferenceFrame(ReferenceFrameInfo {
1589                        source_transform: PropertyBinding::Binding(key, _),
1590                        ..
1591                    }) => key.clone().into(),
1592                    SpatialNodeType::StickyFrame(StickyFrameInfo {
1593                        transform: Some(PropertyBinding::Binding(key, _)),
1594                        ..
1595                    }) => key.clone().into(),
1596                    _ => 0,
1597                };
1598
1599                let clip_node_id = self.get_clip_node(info.clip_chain_id);
1600
1601                self.add_primitive_to_hit_testing_list(
1602                    &layout,
1603                    spatial_node_index,
1604                    clip_node_id,
1605                    info.tag,
1606                    anim_id,
1607                );
1608            }
1609            DisplayItem::ClearRectangle(ref info) => {
1610                profile_scope!("clear");
1611
1612                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1613                    &info.common,
1614                    info.bounds,
1615                );
1616
1617                self.add_clear_rectangle(
1618                    spatial_node_index,
1619                    clip_node_id,
1620                    &layout,
1621                );
1622            }
1623            DisplayItem::Line(ref info) => {
1624                profile_scope!("line");
1625
1626                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1627                    &info.common,
1628                    info.area,
1629                );
1630
1631                self.add_line(
1632                    spatial_node_index,
1633                    clip_node_id,
1634                    &layout,
1635                    info.wavy_line_thickness,
1636                    info.orientation,
1637                    info.color,
1638                    info.style,
1639                );
1640            }
1641            DisplayItem::Gradient(ref info) => {
1642                profile_scope!("gradient");
1643
1644                if !info.gradient.is_valid() {
1645                    return;
1646                }
1647
1648                let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1649                    &info.common,
1650                    info.bounds,
1651                );
1652
1653                let mut tile_size = process_repeat_size(
1654                    &layout.rect,
1655                    &unsnapped_rect,
1656                    info.tile_size,
1657                );
1658
1659                let mut stops = read_gradient_stops(item.gradient_stops());
1660                let mut start = info.gradient.start_point;
1661                let mut end = info.gradient.end_point;
1662                let flags = layout.flags;
1663
1664                let optimized = optimize_linear_gradient(
1665                    &mut layout.rect,
1666                    &mut tile_size,
1667                    info.tile_spacing,
1668                    &layout.clip_rect,
1669                    &mut start,
1670                    &mut end,
1671                    info.gradient.extend_mode,
1672                    &mut stops,
1673                    &mut |rect, start, end, stops, edge_aa_mask| {
1674                        let layout = LayoutPrimitiveInfo { rect: *rect, clip_rect: *rect, flags };
1675                        if let Some(prim_key_kind) = self.create_linear_gradient_prim(
1676                            &layout,
1677                            start,
1678                            end,
1679                            stops.to_vec(),
1680                            ExtendMode::Clamp,
1681                            rect.size(),
1682                            LayoutSize::zero(),
1683                            None,
1684                            edge_aa_mask,
1685                        ) {
1686                            self.add_nonshadowable_primitive(
1687                                spatial_node_index,
1688                                clip_node_id,
1689                                &layout,
1690                                Vec::new(),
1691                                prim_key_kind,
1692                            );
1693                        }
1694                    }
1695                );
1696
1697                if !optimized && !tile_size.ceil().is_empty() {
1698                    if let Some(prim_key_kind) = self.create_linear_gradient_prim(
1699                        &layout,
1700                        start,
1701                        end,
1702                        stops,
1703                        info.gradient.extend_mode,
1704                        tile_size,
1705                        info.tile_spacing,
1706                        None,
1707                        EdgeAaSegmentMask::all(),
1708                    ) {
1709                        self.add_nonshadowable_primitive(
1710                            spatial_node_index,
1711                            clip_node_id,
1712                            &layout,
1713                            Vec::new(),
1714                            prim_key_kind,
1715                        );
1716                    }
1717                }
1718            }
1719            DisplayItem::RadialGradient(ref info) => {
1720                profile_scope!("radial");
1721
1722                if !info.gradient.is_valid() {
1723                    return;
1724                }
1725
1726                let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1727                    &info.common,
1728                    info.bounds,
1729                );
1730
1731                let mut center = info.gradient.center;
1732
1733                let stops = read_gradient_stops(item.gradient_stops());
1734
1735                let mut tile_size = process_repeat_size(
1736                    &layout.rect,
1737                    &unsnapped_rect,
1738                    info.tile_size,
1739                );
1740
1741                let mut prim_rect = layout.rect;
1742                let mut tile_spacing = info.tile_spacing;
1743                optimize_radial_gradient(
1744                    &mut prim_rect,
1745                    &mut tile_size,
1746                    &mut center,
1747                    &mut tile_spacing,
1748                    &layout.clip_rect,
1749                    info.gradient.radius,
1750                    info.gradient.end_offset,
1751                    info.gradient.extend_mode,
1752                    &stops,
1753                    &mut |solid_rect, color| {
1754                        self.add_nonshadowable_primitive(
1755                            spatial_node_index,
1756                            clip_node_id,
1757                            &LayoutPrimitiveInfo {
1758                                rect: *solid_rect,
1759                                .. layout
1760                            },
1761                            Vec::new(),
1762                            PrimitiveKeyKind::Rectangle { color: PropertyBinding::Value(color) },
1763                        );
1764                    }
1765                );
1766
1767                // TODO: create_radial_gradient_prim already calls
1768                // this, but it leaves the info variable that is
1769                // passed to add_nonshadowable_primitive unmodified
1770                // which can cause issues.
1771                simplify_repeated_primitive(&tile_size, &mut tile_spacing, &mut prim_rect);
1772
1773                if !tile_size.ceil().is_empty() {
1774                    layout.rect = prim_rect;
1775                    let prim_key_kind = self.create_radial_gradient_prim(
1776                        &layout,
1777                        center,
1778                        info.gradient.start_offset * info.gradient.radius.width,
1779                        info.gradient.end_offset * info.gradient.radius.width,
1780                        info.gradient.radius.width / info.gradient.radius.height,
1781                        stops,
1782                        info.gradient.extend_mode,
1783                        tile_size,
1784                        tile_spacing,
1785                        None,
1786                    );
1787
1788                    self.add_nonshadowable_primitive(
1789                        spatial_node_index,
1790                        clip_node_id,
1791                        &layout,
1792                        Vec::new(),
1793                        prim_key_kind,
1794                    );
1795                }
1796            }
1797            DisplayItem::ConicGradient(ref info) => {
1798                profile_scope!("conic");
1799
1800                if !info.gradient.is_valid() {
1801                    return;
1802                }
1803
1804                let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1805                    &info.common,
1806                    info.bounds,
1807                );
1808
1809                let tile_size = process_repeat_size(
1810                    &layout.rect,
1811                    &unsnapped_rect,
1812                    info.tile_size,
1813                );
1814
1815                let offset = apply_gradient_local_clip(
1816                    &mut layout.rect,
1817                    &tile_size,
1818                    &info.tile_spacing,
1819                    &layout.clip_rect,
1820                );
1821                let center = info.gradient.center + offset;
1822
1823                if !tile_size.ceil().is_empty() {
1824                    let prim_key_kind = self.create_conic_gradient_prim(
1825                        &layout,
1826                        center,
1827                        info.gradient.angle,
1828                        info.gradient.start_offset,
1829                        info.gradient.end_offset,
1830                        item.gradient_stops(),
1831                        info.gradient.extend_mode,
1832                        tile_size,
1833                        info.tile_spacing,
1834                        None,
1835                    );
1836
1837                    self.add_nonshadowable_primitive(
1838                        spatial_node_index,
1839                        clip_node_id,
1840                        &layout,
1841                        Vec::new(),
1842                        prim_key_kind,
1843                    );
1844                }
1845            }
1846            DisplayItem::BoxShadow(ref info) => {
1847                profile_scope!("box_shadow");
1848
1849                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1850                    &info.common,
1851                    info.box_bounds,
1852                );
1853
1854                self.add_box_shadow(
1855                    spatial_node_index,
1856                    clip_node_id,
1857                    &layout,
1858                    &info.offset,
1859                    info.color,
1860                    info.blur_radius,
1861                    info.spread_radius,
1862                    info.border_radius,
1863                    info.clip_mode,
1864                    self.spatial_tree.is_root_coord_system(spatial_node_index),
1865                );
1866            }
1867            DisplayItem::Border(ref info) => {
1868                profile_scope!("border");
1869
1870                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
1871                    &info.common,
1872                    info.bounds,
1873                );
1874
1875                self.add_border(
1876                    spatial_node_index,
1877                    clip_node_id,
1878                    &layout,
1879                    info,
1880                    item.gradient_stops(),
1881                );
1882            }
1883            DisplayItem::ImageMaskClip(ref info) => {
1884                profile_scope!("image_clip");
1885
1886                self.add_image_mask_clip_node(
1887                    info.id,
1888                    info.spatial_id,
1889                    &info.image_mask,
1890                    info.fill_rule,
1891                    item.points(),
1892                );
1893            }
1894            DisplayItem::RoundedRectClip(ref info) => {
1895                profile_scope!("rounded_clip");
1896
1897                self.add_rounded_rect_clip_node(
1898                    info.id,
1899                    info.spatial_id,
1900                    &info.clip,
1901                );
1902            }
1903            DisplayItem::RectClip(ref info) => {
1904                profile_scope!("rect_clip");
1905
1906                self.add_rect_clip_node(
1907                    info.id,
1908                    info.spatial_id,
1909                    &info.clip_rect,
1910                );
1911            }
1912            DisplayItem::ClipChain(ref info) => {
1913                profile_scope!("clip_chain");
1914
1915                self.clip_tree_builder.define_clip_chain(
1916                    info.id,
1917                    info.parent,
1918                    item.clip_chain_items().into_iter(),
1919                );
1920            },
1921            DisplayItem::BackdropFilter(ref info) => {
1922                profile_scope!("backdrop");
1923
1924                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties(
1925                    &info.common,
1926                    None,
1927                );
1928
1929                let filters = filter_ops_for_compositing(item.filters());
1930                let filter_datas = filter_datas_for_compositing(item.filter_datas());
1931                let filter_primitives = filter_primitives_for_compositing(item.filter_primitives());
1932
1933                self.add_backdrop_filter(
1934                    spatial_node_index,
1935                    clip_node_id,
1936                    &layout,
1937                    filters,
1938                    filter_datas,
1939                    filter_primitives,
1940                );
1941            }
1942
1943            // Do nothing; these are dummy items for the display list parser
1944            DisplayItem::SetGradientStops |
1945            DisplayItem::SetFilterOps |
1946            DisplayItem::SetFilterData |
1947            DisplayItem::SetFilterPrimitives |
1948            DisplayItem::SetPoints => {}
1949
1950            // Special items that are handled in the parent method
1951            DisplayItem::PushStackingContext(..) |
1952            DisplayItem::PushReferenceFrame(..) |
1953            DisplayItem::PopReferenceFrame |
1954            DisplayItem::PopStackingContext |
1955            DisplayItem::Iframe(_) => {
1956                unreachable!("Handled in `build_all`")
1957            }
1958
1959            DisplayItem::ReuseItems(key) |
1960            DisplayItem::RetainedItems(key) => {
1961                unreachable!("Iterator logic error: {:?}", key);
1962            }
1963
1964            DisplayItem::PushShadow(info) => {
1965                profile_scope!("push_shadow");
1966
1967                let spatial_node_index = self.get_space(info.space_and_clip.spatial_id);
1968
1969                self.push_shadow(
1970                    info.shadow,
1971                    spatial_node_index,
1972                    info.space_and_clip.clip_chain_id,
1973                    info.should_inflate,
1974                );
1975            }
1976            DisplayItem::PopAllShadows => {
1977                profile_scope!("pop_all_shadows");
1978
1979                self.pop_all_shadows();
1980            }
1981            DisplayItem::DebugMarker(..) => {}
1982        }
1983    }
1984
1985    /// Create a primitive and add it to the prim store. This method doesn't
1986    /// add the primitive to the draw list, so can be used for creating
1987    /// sub-primitives.
1988    ///
1989    /// TODO(djg): Can this inline into `add_interned_prim_to_draw_list`
1990    fn create_primitive<P>(
1991        &mut self,
1992        info: &LayoutPrimitiveInfo,
1993        clip_leaf_id: ClipLeafId,
1994        prim: P,
1995    ) -> PrimitiveInstance
1996    where
1997        P: InternablePrimitive,
1998        Interners: AsMut<Interner<P>>,
1999    {
2000        // Build a primitive key.
2001        let prim_key = prim.into_key(info);
2002
2003        let interner = self.interners.as_mut();
2004        let prim_data_handle = interner
2005            .intern(&prim_key, || ());
2006
2007        let instance_kind = P::make_instance_kind(
2008            prim_key,
2009            prim_data_handle,
2010            &mut self.prim_store,
2011        );
2012
2013        PrimitiveInstance::new(
2014            instance_kind,
2015            clip_leaf_id,
2016        )
2017    }
2018
2019    fn add_primitive_to_hit_testing_list(
2020        &mut self,
2021        info: &LayoutPrimitiveInfo,
2022        spatial_node_index: SpatialNodeIndex,
2023        clip_node_id: ClipNodeId,
2024        tag: ItemTag,
2025        anim_id: u64,
2026    ) {
2027        self.hit_testing_scene.add_item(
2028            tag,
2029            anim_id,
2030            info,
2031            spatial_node_index,
2032            clip_node_id,
2033            &self.clip_tree_builder,
2034            self.interners,
2035        );
2036    }
2037
2038    /// Add an already created primitive to the draw lists.
2039    pub fn add_primitive_to_draw_list(
2040        &mut self,
2041        prim_instance: PrimitiveInstance,
2042        prim_rect: LayoutRect,
2043        spatial_node_index: SpatialNodeIndex,
2044        flags: PrimitiveFlags,
2045    ) {
2046        // Add primitive to the top-most stacking context on the stack.
2047
2048        // If we have a valid stacking context, the primitive gets added to that.
2049        // Otherwise, it gets added to a top-level picture cache slice.
2050
2051        match self.sc_stack.last_mut() {
2052            Some(stacking_context) => {
2053                stacking_context.prim_list.add_prim(
2054                    prim_instance,
2055                    prim_rect,
2056                    spatial_node_index,
2057                    flags,
2058                    &mut self.prim_instances,
2059                    &self.clip_tree_builder,
2060                );
2061            }
2062            None => {
2063                self.tile_cache_builder.add_prim(
2064                    prim_instance,
2065                    prim_rect,
2066                    spatial_node_index,
2067                    flags,
2068                    self.spatial_tree,
2069                    self.interners,
2070                    &self.quality_settings,
2071                    &mut self.prim_instances,
2072                    &self.clip_tree_builder,
2073                );
2074            }
2075        }
2076    }
2077
2078    /// Convenience interface that creates a primitive entry and adds it
2079    /// to the draw list.
2080    pub fn add_nonshadowable_primitive<P>(
2081        &mut self,
2082        spatial_node_index: SpatialNodeIndex,
2083        clip_node_id: ClipNodeId,
2084        info: &LayoutPrimitiveInfo,
2085        clip_items: Vec<ClipItemKey>,
2086        prim: P,
2087    )
2088    where
2089        P: InternablePrimitive + IsVisible,
2090        Interners: AsMut<Interner<P>>,
2091    {
2092        if prim.is_visible() {
2093            let clip_leaf_id = self.clip_tree_builder.build_for_prim(
2094                clip_node_id,
2095                info,
2096                &clip_items,
2097                &mut self.interners,
2098            );
2099
2100            self.add_prim_to_draw_list(
2101                info,
2102                spatial_node_index,
2103                clip_leaf_id,
2104                prim,
2105            );
2106        }
2107    }
2108
2109    pub fn add_primitive<P>(
2110        &mut self,
2111        spatial_node_index: SpatialNodeIndex,
2112        clip_node_id: ClipNodeId,
2113        info: &LayoutPrimitiveInfo,
2114        clip_items: Vec<ClipItemKey>,
2115        prim: P,
2116    )
2117    where
2118        P: InternablePrimitive + IsVisible,
2119        Interners: AsMut<Interner<P>>,
2120        ShadowItem: From<PendingPrimitive<P>>
2121    {
2122        // If a shadow context is not active, then add the primitive
2123        // directly to the parent picture.
2124        if self.pending_shadow_items.is_empty() {
2125            self.add_nonshadowable_primitive(
2126                spatial_node_index,
2127                clip_node_id,
2128                info,
2129                clip_items,
2130                prim,
2131            );
2132        } else {
2133            debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives");
2134
2135            // There is an active shadow context. Store as a pending primitive
2136            // for processing during pop_all_shadows.
2137            self.pending_shadow_items.push_back(PendingPrimitive {
2138                spatial_node_index,
2139                clip_node_id,
2140                info: *info,
2141                prim,
2142            }.into());
2143        }
2144    }
2145
2146    fn add_prim_to_draw_list<P>(
2147        &mut self,
2148        info: &LayoutPrimitiveInfo,
2149        spatial_node_index: SpatialNodeIndex,
2150        clip_leaf_id: ClipLeafId,
2151        prim: P,
2152    )
2153    where
2154        P: InternablePrimitive,
2155        Interners: AsMut<Interner<P>>,
2156    {
2157        let prim_instance = self.create_primitive(
2158            info,
2159            clip_leaf_id,
2160            prim,
2161        );
2162        self.add_primitive_to_draw_list(
2163            prim_instance,
2164            info.rect,
2165            spatial_node_index,
2166            info.flags,
2167        );
2168    }
2169
2170    fn make_current_slice_atomic_if_required(&mut self) {
2171        let has_non_wrapping_sc = self.sc_stack
2172            .iter()
2173            .position(|sc| {
2174                !sc.flags.contains(StackingContextFlags::WRAPS_BACKDROP_FILTER)
2175            })
2176            .is_some();
2177
2178        if has_non_wrapping_sc {
2179            return;
2180        }
2181
2182        // Shadows can only exist within a stacking context
2183        assert!(self.pending_shadow_items.is_empty());
2184        self.tile_cache_builder.make_current_slice_atomic();
2185    }
2186
2187    /// If no stacking contexts are present (i.e. we are adding prims to a tile
2188    /// cache), set a barrier to force creation of a slice before the next prim
2189    fn add_tile_cache_barrier_if_needed(
2190        &mut self,
2191        slice_flags: SliceFlags,
2192    ) {
2193        if self.sc_stack.is_empty() {
2194            // Shadows can only exist within a stacking context
2195            assert!(self.pending_shadow_items.is_empty());
2196
2197            self.tile_cache_builder.add_tile_cache_barrier(
2198                slice_flags,
2199                self.root_iframe_clip,
2200            );
2201        }
2202    }
2203
2204    /// Push a new stacking context. Returns context that must be passed to pop_stacking_context().
2205    fn push_stacking_context(
2206        &mut self,
2207        mut composite_ops: CompositeOps,
2208        transform_style: TransformStyle,
2209        prim_flags: PrimitiveFlags,
2210        spatial_node_index: SpatialNodeIndex,
2211        clip_chain_id: Option<api::ClipChainId>,
2212        requested_raster_space: RasterSpace,
2213        flags: StackingContextFlags,
2214        subregion_offset: LayoutVector2D,
2215    ) -> StackingContextInfo {
2216        profile_scope!("push_stacking_context");
2217
2218        // Filters have to be baked into the snapshot. Most filters are applied
2219        // when rendering the picture into its parent, so if the stacking context
2220        // needs to be snapshotted, we nest it into an extra stacking context and
2221        // capture the outer stacking context into which the filter is drawn.
2222        // Note: blur filters don't actually need an extra stacking context
2223        // since the blur is baked into a render task instead of being applied
2224        // when compositing the picture into its parent. This case is fairly rare
2225        // so we pay the cost of the extra render pass for now.
2226        let needs_extra_stacking_context = composite_ops.snapshot.is_some()
2227            && composite_ops.has_valid_filters();
2228
2229        if needs_extra_stacking_context {
2230            let snapshot = mem::take(&mut composite_ops.snapshot);
2231            let mut info = self.push_stacking_context(
2232                CompositeOps {
2233                    filters: Vec::new(),
2234                    filter_datas: Vec::new(),
2235                    filter_primitives: Vec::new(),
2236                    mix_blend_mode: None,
2237                    snapshot,
2238                },
2239                TransformStyle::Flat,
2240                prim_flags,
2241                spatial_node_index,
2242                clip_chain_id,
2243                requested_raster_space,
2244                flags,
2245                LayoutVector2D::zero(),
2246            );
2247            info.pop_stacking_context = true;
2248            self.extra_stacking_context_stack.push(info);
2249        }
2250
2251        let clip_node_id = match clip_chain_id {
2252            Some(id) => {
2253                self.clip_tree_builder.build_clip_set(id)
2254            }
2255            None => {
2256                self.clip_tree_builder.build_clip_set(api::ClipChainId::INVALID)
2257            }
2258        };
2259
2260        self.clip_tree_builder.push_clip_chain(
2261            clip_chain_id,
2262            !composite_ops.is_empty(),
2263        );
2264
2265        let new_space = match (self.raster_space_stack.last(), requested_raster_space) {
2266            // If no parent space, just use the requested space
2267            (None, _) => requested_raster_space,
2268            // If screen, use the parent
2269            (Some(parent_space), RasterSpace::Screen) => *parent_space,
2270            // If currently screen, select the requested
2271            (Some(RasterSpace::Screen), space) => space,
2272            // If both local, take the maximum scale
2273            (Some(RasterSpace::Local(parent_scale)), RasterSpace::Local(scale)) => RasterSpace::Local(parent_scale.max(scale)),
2274        };
2275        self.raster_space_stack.push(new_space);
2276
2277        // Get the transform-style of the parent stacking context,
2278        // which determines if we *might* need to draw this on
2279        // an intermediate surface for plane splitting purposes.
2280        let (parent_is_3d, extra_3d_instance, plane_splitter_index) = match self.sc_stack.last_mut() {
2281            Some(ref mut sc) if sc.is_3d() => {
2282                let (flat_items_context_3d, plane_splitter_index) = match sc.context_3d {
2283                    Picture3DContext::In { ancestor_index, plane_splitter_index, .. } => {
2284                        (
2285                            Picture3DContext::In {
2286                                root_data: None,
2287                                ancestor_index,
2288                                plane_splitter_index,
2289                            },
2290                            plane_splitter_index,
2291                        )
2292                    }
2293                    Picture3DContext::Out => panic!("Unexpected out of 3D context"),
2294                };
2295                // Cut the sequence of flat children before starting a child stacking context,
2296                // so that the relative order between them and our current SC is preserved.
2297                let extra_instance = sc.cut_item_sequence(
2298                    &mut self.prim_store,
2299                    &mut self.interners,
2300                    Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)),
2301                    flat_items_context_3d,
2302                    &mut self.clip_tree_builder,
2303                );
2304                let extra_instance = extra_instance.map(|(_, instance)| {
2305                    ExtendedPrimitiveInstance {
2306                        instance,
2307                        spatial_node_index: sc.spatial_node_index,
2308                        flags: sc.prim_flags,
2309                    }
2310                });
2311                (true, extra_instance, Some(plane_splitter_index))
2312            },
2313            _ => (false, None, None),
2314        };
2315
2316        if let Some(instance) = extra_3d_instance {
2317            self.add_primitive_instance_to_3d_root(instance);
2318        }
2319
2320        // If this is preserve-3d *or* the parent is, then this stacking
2321        // context is participating in the 3d rendering context. In that
2322        // case, hoist the picture up to the 3d rendering context
2323        // container, so that it's rendered as a sibling with other
2324        // elements in this context.
2325        let participating_in_3d_context =
2326            composite_ops.is_empty() &&
2327            (parent_is_3d || transform_style == TransformStyle::Preserve3D);
2328
2329        let context_3d = if participating_in_3d_context {
2330            // Get the spatial node index of the containing block, which
2331            // defines the context of backface-visibility.
2332            let ancestor_index = self.containing_block_stack
2333                .last()
2334                .cloned()
2335                .unwrap_or(self.spatial_tree.root_reference_frame_index());
2336
2337            let plane_splitter_index = plane_splitter_index.unwrap_or_else(|| {
2338                let index = self.next_plane_splitter_index;
2339                self.next_plane_splitter_index += 1;
2340                PlaneSplitterIndex(index)
2341            });
2342
2343            Picture3DContext::In {
2344                root_data: if parent_is_3d {
2345                    None
2346                } else {
2347                    Some(Vec::new())
2348                },
2349                plane_splitter_index,
2350                ancestor_index,
2351            }
2352        } else {
2353            Picture3DContext::Out
2354        };
2355
2356        // Force an intermediate surface if the stacking context has a
2357        // complex clip node. In the future, we may decide during
2358        // prepare step to skip the intermediate surface if the
2359        // clip node doesn't affect the stacking context rect.
2360        let mut blit_reason = BlitReason::empty();
2361
2362        // Stacking context snapshots are offscreen syrfaces.
2363        if composite_ops.snapshot.is_some() {
2364            blit_reason = BlitReason::SNAPSHOT;
2365        }
2366
2367        // If this stacking context has any complex clips, we need to draw it
2368        // to an off-screen surface.
2369        if let Some(clip_chain_id) = clip_chain_id {
2370            if self.clip_tree_builder.clip_chain_has_complex_clips(clip_chain_id, &self.interners) {
2371                blit_reason |= BlitReason::CLIP;
2372            }
2373        }
2374
2375        // Check if we know this stacking context is redundant (doesn't need a surface)
2376        // The check for blend-container redundancy is more involved so it's handled below.
2377        let mut is_redundant = FlattenedStackingContext::is_redundant(
2378            &context_3d,
2379            &composite_ops,
2380            blit_reason,
2381            self.sc_stack.last(),
2382            prim_flags,
2383        );
2384
2385        // If the stacking context is a blend container, and if we're at the top level
2386        // of the stacking context tree, we may be able to make this blend container into a tile
2387        // cache. This means that we get caching and correct scrolling invalidation for
2388        // root level blend containers. For these cases, the readbacks of the backdrop
2389        // are handled by doing partial reads of the picture cache tiles during rendering.
2390        if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) {
2391            // Check if we're inside a stacking context hierarchy with an existing surface
2392            match self.sc_stack.last() {
2393                Some(_) => {
2394                    // If we are already inside a stacking context hierarchy with a surface, then we
2395                    // need to do the normal isolate of this blend container as a regular surface
2396                    blit_reason |= BlitReason::ISOLATE;
2397                    is_redundant = false;
2398                }
2399                None => {
2400                    // If the current slice is empty, then we can just mark the slice as
2401                    // atomic (so that compositor surfaces don't get promoted within it)
2402                    // and use that slice as the backing surface for the blend container
2403                    if self.tile_cache_builder.is_current_slice_empty() &&
2404                       self.spatial_tree.is_root_coord_system(spatial_node_index) &&
2405                       !self.clip_tree_builder.clip_node_has_complex_clips(clip_node_id, &self.interners)
2406                    {
2407                        self.add_tile_cache_barrier_if_needed(SliceFlags::IS_ATOMIC);
2408                        self.tile_cache_builder.make_current_slice_atomic();
2409                    } else {
2410                        // If the slice wasn't empty, we need to isolate a separate surface
2411                        // to ensure that the content already in the slice is not used as
2412                        // an input to the mix-blend composite
2413                        blit_reason |= BlitReason::ISOLATE;
2414                        is_redundant = false;
2415                    }
2416                }
2417            }
2418        }
2419
2420        // If stacking context is a scrollbar, force a new slice for the primitives
2421        // within. The stacking context will be redundant and removed by above check.
2422        let set_tile_cache_barrier = prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER);
2423
2424        if set_tile_cache_barrier {
2425            self.add_tile_cache_barrier_if_needed(SliceFlags::IS_SCROLLBAR);
2426        }
2427
2428        let mut sc_info = StackingContextInfo {
2429            pop_stacking_context: false,
2430            pop_containing_block: false,
2431            set_tile_cache_barrier,
2432            needs_extra_stacking_context,
2433        };
2434
2435        // If this is not 3d, then it establishes an ancestor root for child 3d contexts.
2436        if !participating_in_3d_context {
2437            sc_info.pop_containing_block = true;
2438            self.containing_block_stack.push(spatial_node_index);
2439        }
2440
2441        // If not redundant, create a stacking context to hold primitive clusters
2442        if !is_redundant {
2443            sc_info.pop_stacking_context = true;
2444
2445            // Push the SC onto the stack, so we know how to handle things in
2446            // pop_stacking_context.
2447            self.sc_stack.push(FlattenedStackingContext {
2448                prim_list: PrimitiveList::empty(),
2449                prim_flags,
2450                spatial_node_index,
2451                clip_node_id,
2452                composite_ops,
2453                blit_reason,
2454                transform_style,
2455                context_3d,
2456                flags,
2457                raster_space: new_space,
2458                subregion_offset,
2459            });
2460        }
2461
2462        sc_info
2463    }
2464
2465    fn pop_stacking_context(
2466        &mut self,
2467        info: StackingContextInfo,
2468    ) {
2469        profile_scope!("pop_stacking_context");
2470
2471        self.clip_tree_builder.pop_clip();
2472
2473        // Pop off current raster space (pushed unconditionally in push_stacking_context)
2474        self.raster_space_stack.pop().unwrap();
2475
2476        // If the stacking context formed a containing block, pop off the stack
2477        if info.pop_containing_block {
2478            self.containing_block_stack.pop().unwrap();
2479        }
2480
2481        if info.set_tile_cache_barrier {
2482            self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
2483        }
2484
2485        // If the stacking context was otherwise redundant, early exit
2486        if !info.pop_stacking_context {
2487            return;
2488        }
2489
2490        let stacking_context = self.sc_stack.pop().unwrap();
2491
2492        let mut source = match stacking_context.context_3d {
2493            // TODO(gw): For now, as soon as this picture is in
2494            //           a 3D context, we draw it to an intermediate
2495            //           surface and apply plane splitting. However,
2496            //           there is a large optimization opportunity here.
2497            //           During culling, we can check if there is actually
2498            //           perspective present, and skip the plane splitting
2499            //           completely when that is not the case.
2500            Picture3DContext::In { ancestor_index, plane_splitter_index, .. } => {
2501                let composite_mode = Some(
2502                    PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason)
2503                );
2504
2505                // Add picture for this actual stacking context contents to render into.
2506                let pic_index = PictureIndex(self.prim_store.pictures
2507                    .alloc()
2508                    .init(PicturePrimitive::new_image(
2509                        composite_mode.clone(),
2510                        Picture3DContext::In { root_data: None, ancestor_index, plane_splitter_index },
2511                        stacking_context.prim_flags,
2512                        stacking_context.prim_list,
2513                        stacking_context.spatial_node_index,
2514                        stacking_context.raster_space,
2515                        PictureFlags::empty(),
2516                        None,
2517                    ))
2518                );
2519
2520                let instance = create_prim_instance(
2521                    pic_index,
2522                    composite_mode.into(),
2523                    stacking_context.raster_space,
2524                    stacking_context.clip_node_id,
2525                    &mut self.interners,
2526                    &mut self.clip_tree_builder,
2527                );
2528
2529                PictureChainBuilder::from_instance(
2530                    instance,
2531                    stacking_context.prim_flags,
2532                    stacking_context.spatial_node_index,
2533                    stacking_context.raster_space,
2534                )
2535            }
2536            Picture3DContext::Out => {
2537                if stacking_context.blit_reason.is_empty() {
2538                    PictureChainBuilder::from_prim_list(
2539                        stacking_context.prim_list,
2540                        stacking_context.prim_flags,
2541                        stacking_context.spatial_node_index,
2542                        stacking_context.raster_space,
2543                        false,
2544                    )
2545                } else {
2546                    let composite_mode = Some(
2547                        PictureCompositeMode::Blit(stacking_context.blit_reason)
2548                    );
2549
2550                    // Add picture for this actual stacking context contents to render into.
2551                    let pic_index = PictureIndex(self.prim_store.pictures
2552                        .alloc()
2553                        .init(PicturePrimitive::new_image(
2554                            composite_mode.clone(),
2555                            Picture3DContext::Out,
2556                            stacking_context.prim_flags,
2557                            stacking_context.prim_list,
2558                            stacking_context.spatial_node_index,
2559                            stacking_context.raster_space,
2560                            PictureFlags::empty(),
2561                            None,
2562                        ))
2563                    );
2564
2565                    let instance = create_prim_instance(
2566                        pic_index,
2567                        composite_mode.into(),
2568                        stacking_context.raster_space,
2569                        stacking_context.clip_node_id,
2570                        &mut self.interners,
2571                        &mut self.clip_tree_builder,
2572                    );
2573
2574                    PictureChainBuilder::from_instance(
2575                        instance,
2576                        stacking_context.prim_flags,
2577                        stacking_context.spatial_node_index,
2578                        stacking_context.raster_space,
2579                    )
2580                }
2581            }
2582        };
2583
2584        // If establishing a 3d context, the `cur_instance` represents
2585        // a picture with all the *trailing* immediate children elements.
2586        // We append this to the preserve-3D picture set and make a container picture of them.
2587        if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index, plane_splitter_index } = stacking_context.context_3d {
2588            let instance = source.finalize(
2589                ClipNodeId::NONE,
2590                &mut self.interners,
2591                &mut self.prim_store,
2592                &mut self.clip_tree_builder,
2593                None,
2594            );
2595
2596            prims.push(ExtendedPrimitiveInstance {
2597                instance,
2598                spatial_node_index: stacking_context.spatial_node_index,
2599                flags: stacking_context.prim_flags,
2600            });
2601
2602            let mut prim_list = PrimitiveList::empty();
2603
2604            // Web content often specifies `preserve-3d` on pages that don't actually need
2605            // a 3d rendering context (as a hint / hack to convince other browsers to
2606            // layerize these elements to an off-screen surface). Detect cases where the
2607            // preserve-3d has no effect on correctness and convert them to pass-through
2608            // pictures instead. This has two benefits for WR:
2609            //
2610            // (1) We get correct subpixel-snapping behavior between preserve-3d elements
2611            //     that don't have complex transforms without additional complexity of
2612            //     handling subpixel-snapping across different surfaces.
2613            // (2) We can draw this content directly in to the parent surface / tile cache,
2614            //     which is a performance win by avoiding allocating, drawing,
2615            //     plane-splitting and blitting an off-screen surface.
2616            let mut needs_3d_context = false;
2617
2618            for ext_prim in prims.drain(..) {
2619                // If all the preserve-3d elements are in the root coordinate system, we
2620                // know that there is no need for a true 3d rendering context / plane-split.
2621                // TODO(gw): We can expand this in future to handle this in more cases
2622                //           (e.g. a non-root coord system that is 2d within the 3d context).
2623                if !self.spatial_tree.is_root_coord_system(ext_prim.spatial_node_index) {
2624                    needs_3d_context = true;
2625                }
2626
2627                prim_list.add_prim(
2628                    ext_prim.instance,
2629                    LayoutRect::zero(),
2630                    ext_prim.spatial_node_index,
2631                    ext_prim.flags,
2632                    &mut self.prim_instances,
2633                    &self.clip_tree_builder,
2634                );
2635            }
2636
2637            let context_3d = if needs_3d_context {
2638                Picture3DContext::In {
2639                    root_data: Some(Vec::new()),
2640                    ancestor_index,
2641                    plane_splitter_index,
2642                }
2643            } else {
2644                // If we didn't need a 3d rendering context, walk the child pictures
2645                // that make up this context and disable the off-screen surface and
2646                // 3d render context.
2647                for child_pic_index in &prim_list.child_pictures {
2648                    let child_pic = &mut self.prim_store.pictures[child_pic_index.0];
2649                    let needs_surface = child_pic.snapshot.is_some();
2650                    if !needs_surface {
2651                        child_pic.composite_mode = None;
2652                    }
2653                    child_pic.context_3d = Picture3DContext::Out;
2654                }
2655
2656                Picture3DContext::Out
2657            };
2658
2659            // This is the acttual picture representing our 3D hierarchy root.
2660            let pic_index = PictureIndex(self.prim_store.pictures
2661                .alloc()
2662                .init(PicturePrimitive::new_image(
2663                    None,
2664                    context_3d,
2665                    stacking_context.prim_flags,
2666                    prim_list,
2667                    stacking_context.spatial_node_index,
2668                    stacking_context.raster_space,
2669                    PictureFlags::empty(),
2670                    None,
2671                ))
2672            );
2673
2674            let instance = create_prim_instance(
2675                pic_index,
2676                PictureCompositeKey::Identity,
2677                stacking_context.raster_space,
2678                stacking_context.clip_node_id,
2679                &mut self.interners,
2680                &mut self.clip_tree_builder,
2681            );
2682
2683            source = PictureChainBuilder::from_instance(
2684                instance,
2685                stacking_context.prim_flags,
2686                stacking_context.spatial_node_index,
2687                stacking_context.raster_space,
2688            );
2689        }
2690
2691        let has_filters = stacking_context.composite_ops.has_valid_filters();
2692
2693        let spatial_node_context_offset =
2694            stacking_context.subregion_offset +
2695            self.current_external_scroll_offset(stacking_context.spatial_node_index);
2696        source = self.wrap_prim_with_filters(
2697            source,
2698            stacking_context.clip_node_id,
2699            stacking_context.composite_ops.filters,
2700            stacking_context.composite_ops.filter_primitives,
2701            stacking_context.composite_ops.filter_datas,
2702            None,
2703            spatial_node_context_offset,
2704        );
2705
2706        // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent
2707        // stacking context.
2708        // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is:
2709        // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs)
2710        // where
2711        // Cs = Source color
2712        // ab = Backdrop alpha
2713        // Cb = Backdrop color
2714        //
2715        // If we're the first primitive within a stacking context, then we can guarantee that the
2716        // backdrop alpha will be 0, and then the blend equation collapses to just
2717        // Cs = Cs, and the blend mode isn't taken into account at all.
2718        if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
2719            let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode);
2720
2721            source = source.add_picture(
2722                composite_mode,
2723                stacking_context.clip_node_id,
2724                Picture3DContext::Out,
2725                &mut self.interners,
2726                &mut self.prim_store,
2727                &mut self.prim_instances,
2728                &mut self.clip_tree_builder,
2729            );
2730        }
2731
2732        // Set the stacking context clip on the outermost picture in the chain,
2733        // unless we already set it on the leaf picture.
2734        let cur_instance = source.finalize(
2735            stacking_context.clip_node_id,
2736            &mut self.interners,
2737            &mut self.prim_store,
2738            &mut self.clip_tree_builder,
2739            stacking_context.composite_ops.snapshot,
2740        );
2741
2742        if stacking_context.composite_ops.snapshot.is_some() {
2743            let pic_index = cur_instance.kind.as_pic();
2744            self.snapshot_pictures.push(pic_index);
2745        }
2746
2747        // The primitive instance for the remainder of flat children of this SC
2748        // if it's a part of 3D hierarchy but not the root of it.
2749        let trailing_children_instance = match self.sc_stack.last_mut() {
2750            // Preserve3D path (only relevant if there are no filters/mix-blend modes)
2751            Some(ref parent_sc) if !has_filters && parent_sc.is_3d() => {
2752                Some(cur_instance)
2753            }
2754            // Regular parenting path
2755            Some(ref mut parent_sc) => {
2756                parent_sc.prim_list.add_prim(
2757                    cur_instance,
2758                    LayoutRect::zero(),
2759                    stacking_context.spatial_node_index,
2760                    stacking_context.prim_flags,
2761                    &mut self.prim_instances,
2762                    &self.clip_tree_builder,
2763                );
2764                None
2765            }
2766            // This must be the root stacking context
2767            None => {
2768                self.add_primitive_to_draw_list(
2769                    cur_instance,
2770                    LayoutRect::zero(),
2771                    stacking_context.spatial_node_index,
2772                    stacking_context.prim_flags,
2773                );
2774
2775                None
2776            }
2777        };
2778
2779        // finally, if there any outstanding 3D primitive instances,
2780        // find the 3D hierarchy root and add them there.
2781        if let Some(instance) = trailing_children_instance {
2782            self.add_primitive_instance_to_3d_root(ExtendedPrimitiveInstance {
2783                instance,
2784                spatial_node_index: stacking_context.spatial_node_index,
2785                flags: stacking_context.prim_flags,
2786            });
2787        }
2788
2789        assert!(
2790            self.pending_shadow_items.is_empty(),
2791            "Found unpopped shadows when popping stacking context!"
2792        );
2793
2794        if info.needs_extra_stacking_context {
2795            let inner_info = self.extra_stacking_context_stack.pop().unwrap();
2796            self.pop_stacking_context(inner_info);
2797        }
2798    }
2799
2800    pub fn push_reference_frame(
2801        &mut self,
2802        reference_frame_id: SpatialId,
2803        parent_index: SpatialNodeIndex,
2804        pipeline_id: PipelineId,
2805        transform_style: TransformStyle,
2806        source_transform: PropertyBinding<LayoutTransform>,
2807        kind: ReferenceFrameKind,
2808        origin_in_parent_reference_frame: LayoutVector2D,
2809        uid: SpatialNodeUid,
2810    ) -> SpatialNodeIndex {
2811        let index = self.spatial_tree.add_reference_frame(
2812            parent_index,
2813            transform_style,
2814            source_transform,
2815            kind,
2816            origin_in_parent_reference_frame,
2817            pipeline_id,
2818            uid,
2819        );
2820        self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(reference_frame_id, index);
2821
2822        index
2823    }
2824
2825    fn push_root(
2826        &mut self,
2827        pipeline_id: PipelineId,
2828        instance: PipelineInstanceId,
2829    ) {
2830        let spatial_node_index = self.push_reference_frame(
2831            SpatialId::root_reference_frame(pipeline_id),
2832            self.spatial_tree.root_reference_frame_index(),
2833            pipeline_id,
2834            TransformStyle::Flat,
2835            PropertyBinding::Value(LayoutTransform::identity()),
2836            ReferenceFrameKind::Transform {
2837                is_2d_scale_translation: true,
2838                should_snap: true,
2839                paired_with_perspective: false,
2840            },
2841            LayoutVector2D::zero(),
2842            SpatialNodeUid::root_reference_frame(pipeline_id, instance),
2843        );
2844
2845        let viewport_rect = LayoutRect::max_rect();
2846
2847        self.add_scroll_frame(
2848            SpatialId::root_scroll_node(pipeline_id),
2849            spatial_node_index,
2850            ExternalScrollId(0, pipeline_id),
2851            pipeline_id,
2852            &viewport_rect,
2853            &viewport_rect.size(),
2854            ScrollFrameKind::PipelineRoot {
2855                is_root_pipeline: true,
2856            },
2857            LayoutVector2D::zero(),
2858            APZScrollGeneration::default(),
2859            HasScrollLinkedEffect::No,
2860            SpatialNodeUid::root_scroll_frame(pipeline_id, instance),
2861        );
2862    }
2863
2864    fn add_image_mask_clip_node(
2865        &mut self,
2866        new_node_id: ClipId,
2867        spatial_id: SpatialId,
2868        image_mask: &ImageMask,
2869        fill_rule: FillRule,
2870        points_range: ItemRange<LayoutPoint>,
2871    ) {
2872        let spatial_node_index = self.get_space(spatial_id);
2873
2874        let snapped_mask_rect = self.normalize_scroll_offset_and_snap_rect(
2875            &image_mask.rect,
2876            spatial_node_index,
2877        );
2878
2879        let points: Vec<LayoutPoint> = points_range.iter().collect();
2880
2881        // If any points are provided, then intern a polygon with the points and fill rule.
2882        let mut polygon_handle: Option<PolygonDataHandle> = None;
2883        if points.len() > 0 {
2884            let item = PolygonKey::new(&points, fill_rule);
2885
2886            let handle = self
2887                .interners
2888                .polygon
2889                .intern(&item, || item);
2890            polygon_handle = Some(handle);
2891        }
2892
2893        let item = ClipItemKey {
2894            kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect, polygon_handle),
2895            spatial_node_index,
2896        };
2897
2898        let handle = self
2899            .interners
2900            .clip
2901            .intern(&item, || {
2902                ClipInternData {
2903                    key: item,
2904                }
2905            });
2906
2907        self.clip_tree_builder.define_image_mask_clip(
2908            new_node_id,
2909            handle,
2910        );
2911    }
2912
2913    /// Add a new rectangle clip, positioned by the spatial node in the `space_and_clip`.
2914    fn add_rect_clip_node(
2915        &mut self,
2916        new_node_id: ClipId,
2917        spatial_id: SpatialId,
2918        clip_rect: &LayoutRect,
2919    ) {
2920        let spatial_node_index = self.get_space(spatial_id);
2921
2922        let snapped_clip_rect = self.normalize_scroll_offset_and_snap_rect(
2923            clip_rect,
2924            spatial_node_index,
2925        );
2926
2927        let item = ClipItemKey {
2928            kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
2929            spatial_node_index,
2930        };
2931        let handle = self
2932            .interners
2933            .clip
2934            .intern(&item, || {
2935                ClipInternData {
2936                    key: item,
2937                }
2938            });
2939
2940        self.clip_tree_builder.define_rect_clip(
2941            new_node_id,
2942            handle,
2943        );
2944    }
2945
2946    fn add_rounded_rect_clip_node(
2947        &mut self,
2948        new_node_id: ClipId,
2949        spatial_id: SpatialId,
2950        clip: &ComplexClipRegion,
2951    ) {
2952        let spatial_node_index = self.get_space(spatial_id);
2953
2954        let snapped_region_rect = self.normalize_scroll_offset_and_snap_rect(
2955            &clip.rect,
2956            spatial_node_index,
2957        );
2958
2959        let item = ClipItemKey {
2960            kind: ClipItemKeyKind::rounded_rect(
2961                snapped_region_rect,
2962                clip.radii,
2963                clip.mode,
2964            ),
2965            spatial_node_index,
2966        };
2967
2968        let handle = self
2969            .interners
2970            .clip
2971            .intern(&item, || {
2972                ClipInternData {
2973                    key: item,
2974                }
2975            });
2976
2977        self.clip_tree_builder.define_rounded_rect_clip(
2978            new_node_id,
2979            handle,
2980        );
2981    }
2982
2983    pub fn add_scroll_frame(
2984        &mut self,
2985        new_node_id: SpatialId,
2986        parent_node_index: SpatialNodeIndex,
2987        external_id: ExternalScrollId,
2988        pipeline_id: PipelineId,
2989        frame_rect: &LayoutRect,
2990        content_size: &LayoutSize,
2991        frame_kind: ScrollFrameKind,
2992        external_scroll_offset: LayoutVector2D,
2993        scroll_offset_generation: APZScrollGeneration,
2994        has_scroll_linked_effect: HasScrollLinkedEffect,
2995        uid: SpatialNodeUid,
2996    ) -> SpatialNodeIndex {
2997        let node_index = self.spatial_tree.add_scroll_frame(
2998            parent_node_index,
2999            external_id,
3000            pipeline_id,
3001            frame_rect,
3002            content_size,
3003            frame_kind,
3004            external_scroll_offset,
3005            scroll_offset_generation,
3006            has_scroll_linked_effect,
3007            uid,
3008        );
3009        self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(new_node_id, node_index);
3010        node_index
3011    }
3012
3013    pub fn push_shadow(
3014        &mut self,
3015        shadow: Shadow,
3016        spatial_node_index: SpatialNodeIndex,
3017        clip_chain_id: api::ClipChainId,
3018        should_inflate: bool,
3019    ) {
3020        self.clip_tree_builder.push_clip_chain(Some(clip_chain_id), false);
3021
3022        // Store this shadow in the pending list, for processing
3023        // during pop_all_shadows.
3024        self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
3025            shadow,
3026            spatial_node_index,
3027            should_inflate,
3028        }));
3029    }
3030
3031    pub fn pop_all_shadows(
3032        &mut self,
3033    ) {
3034        assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
3035
3036        let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
3037
3038        //
3039        // The pending_shadow_items queue contains a list of shadows and primitives
3040        // that were pushed during the active shadow context. To process these, we:
3041        //
3042        // Iterate the list, popping an item from the front each iteration.
3043        //
3044        // If the item is a shadow:
3045        //      - Create a shadow picture primitive.
3046        //      - Add *any* primitives that remain in the item list to this shadow.
3047        // If the item is a primitive:
3048        //      - Add that primitive as a normal item (if alpha > 0)
3049        //
3050
3051        while let Some(item) = items.pop_front() {
3052            match item {
3053                ShadowItem::Shadow(pending_shadow) => {
3054                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
3055                    // "the image that would be generated by applying to the shadow a
3056                    // Gaussian blur with a standard deviation equal to half the blur radius."
3057                    let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
3058
3059                    // Add any primitives that come after this shadow in the item
3060                    // list to this shadow.
3061                    let mut prim_list = PrimitiveList::empty();
3062                    let blur_filter = Filter::Blur {
3063                        width: std_deviation,
3064                        height: std_deviation,
3065                        should_inflate: pending_shadow.should_inflate,
3066                    };
3067                    let blur_is_noop = blur_filter.is_noop();
3068
3069                    for item in &items {
3070                        let (instance, info, spatial_node_index) = match item {
3071                            ShadowItem::Image(ref pending_image) => {
3072                                self.create_shadow_prim(
3073                                    &pending_shadow,
3074                                    pending_image,
3075                                    blur_is_noop,
3076                                )
3077                            }
3078                            ShadowItem::LineDecoration(ref pending_line_dec) => {
3079                                self.create_shadow_prim(
3080                                    &pending_shadow,
3081                                    pending_line_dec,
3082                                    blur_is_noop,
3083                                )
3084                            }
3085                            ShadowItem::NormalBorder(ref pending_border) => {
3086                                self.create_shadow_prim(
3087                                    &pending_shadow,
3088                                    pending_border,
3089                                    blur_is_noop,
3090                                )
3091                            }
3092                            ShadowItem::Primitive(ref pending_primitive) => {
3093                                self.create_shadow_prim(
3094                                    &pending_shadow,
3095                                    pending_primitive,
3096                                    blur_is_noop,
3097                                )
3098                            }
3099                            ShadowItem::TextRun(ref pending_text_run) => {
3100                                self.create_shadow_prim(
3101                                    &pending_shadow,
3102                                    pending_text_run,
3103                                    blur_is_noop,
3104                                )
3105                            }
3106                            _ => {
3107                                continue;
3108                            }
3109                        };
3110
3111                        if blur_is_noop {
3112                            self.add_primitive_to_draw_list(
3113                                instance,
3114                                info.rect,
3115                                spatial_node_index,
3116                                info.flags,
3117                            );
3118                        } else {
3119                            prim_list.add_prim(
3120                                instance,
3121                                info.rect,
3122                                spatial_node_index,
3123                                info.flags,
3124                                &mut self.prim_instances,
3125                                &self.clip_tree_builder,
3126                            );
3127                        }
3128                    }
3129
3130                    // No point in adding a shadow here if there were no primitives
3131                    // added to the shadow.
3132                    if !prim_list.is_empty() {
3133                        // Create a picture that the shadow primitives will be added to. If the
3134                        // blur radius is 0, the code in Picture::prepare_for_render will
3135                        // detect this and mark the picture to be drawn directly into the
3136                        // parent picture, which avoids an intermediate surface and blur.
3137                        assert!(!blur_filter.is_noop());
3138                        let composite_mode = Some(PictureCompositeMode::Filter(blur_filter));
3139                        let composite_mode_key = composite_mode.clone().into();
3140                        let raster_space = RasterSpace::Screen;
3141
3142                        // Create the primitive to draw the shadow picture into the scene.
3143                        let shadow_pic_index = PictureIndex(self.prim_store.pictures
3144                            .alloc()
3145                            .init(PicturePrimitive::new_image(
3146                                composite_mode,
3147                                Picture3DContext::Out,
3148                                PrimitiveFlags::IS_BACKFACE_VISIBLE,
3149                                prim_list,
3150                                pending_shadow.spatial_node_index,
3151                                raster_space,
3152                                PictureFlags::empty(),
3153                                None,
3154                            ))
3155                        );
3156
3157                        let shadow_pic_key = PictureKey::new(
3158                            Picture { composite_mode_key, raster_space },
3159                        );
3160
3161                        let shadow_prim_data_handle = self.interners
3162                            .picture
3163                            .intern(&shadow_pic_key, || ());
3164
3165                        let clip_node_id = self.clip_tree_builder.build_clip_set(api::ClipChainId::INVALID);
3166
3167                        let shadow_prim_instance = PrimitiveInstance::new(
3168                            PrimitiveInstanceKind::Picture {
3169                                data_handle: shadow_prim_data_handle,
3170                                pic_index: shadow_pic_index,
3171                            },
3172                            self.clip_tree_builder.build_for_picture(clip_node_id),
3173                        );
3174
3175                        // Add the shadow primitive. This must be done before pushing this
3176                        // picture on to the shadow stack, to avoid infinite recursion!
3177                        self.add_primitive_to_draw_list(
3178                            shadow_prim_instance,
3179                            LayoutRect::zero(),
3180                            pending_shadow.spatial_node_index,
3181                            PrimitiveFlags::IS_BACKFACE_VISIBLE,
3182                        );
3183                    }
3184
3185                    self.clip_tree_builder.pop_clip();
3186                }
3187                ShadowItem::Image(pending_image) => {
3188                    self.add_shadow_prim_to_draw_list(
3189                        pending_image,
3190                    )
3191                },
3192                ShadowItem::LineDecoration(pending_line_dec) => {
3193                    self.add_shadow_prim_to_draw_list(
3194                        pending_line_dec,
3195                    )
3196                },
3197                ShadowItem::NormalBorder(pending_border) => {
3198                    self.add_shadow_prim_to_draw_list(
3199                        pending_border,
3200                    )
3201                },
3202                ShadowItem::Primitive(pending_primitive) => {
3203                    self.add_shadow_prim_to_draw_list(
3204                        pending_primitive,
3205                    )
3206                },
3207                ShadowItem::TextRun(pending_text_run) => {
3208                    self.add_shadow_prim_to_draw_list(
3209                        pending_text_run,
3210                    )
3211                },
3212            }
3213        }
3214
3215        debug_assert!(items.is_empty());
3216        self.pending_shadow_items = items;
3217    }
3218
3219    fn create_shadow_prim<P>(
3220        &mut self,
3221        pending_shadow: &PendingShadow,
3222        pending_primitive: &PendingPrimitive<P>,
3223        blur_is_noop: bool,
3224    ) -> (PrimitiveInstance, LayoutPrimitiveInfo, SpatialNodeIndex)
3225    where
3226        P: InternablePrimitive + CreateShadow,
3227        Interners: AsMut<Interner<P>>,
3228    {
3229        // Offset the local rect and clip rect by the shadow offset. The pending
3230        // primitive has already been snapped, but we will need to snap the
3231        // shadow after translation. We don't need to worry about the size
3232        // changing because the shadow has the same raster space as the
3233        // primitive, and thus we know the size is already rounded.
3234        let mut info = pending_primitive.info.clone();
3235        info.rect = info.rect.translate(pending_shadow.shadow.offset);
3236        info.clip_rect = info.clip_rect.translate(pending_shadow.shadow.offset);
3237
3238        let clip_set = self.clip_tree_builder.build_for_prim(
3239            pending_primitive.clip_node_id,
3240            &info,
3241            &[],
3242            &mut self.interners,
3243        );
3244
3245        // Construct and add a primitive for the given shadow.
3246        let shadow_prim_instance = self.create_primitive(
3247            &info,
3248            clip_set,
3249            pending_primitive.prim.create_shadow(
3250                &pending_shadow.shadow,
3251                blur_is_noop,
3252                self.raster_space_stack.last().cloned().unwrap(),
3253            ),
3254        );
3255
3256        (shadow_prim_instance, info, pending_primitive.spatial_node_index)
3257    }
3258
3259    fn add_shadow_prim_to_draw_list<P>(
3260        &mut self,
3261        pending_primitive: PendingPrimitive<P>,
3262    ) where
3263        P: InternablePrimitive + IsVisible,
3264        Interners: AsMut<Interner<P>>,
3265    {
3266        // For a normal primitive, if it has alpha > 0, then we add this
3267        // as a normal primitive to the parent picture.
3268        if pending_primitive.prim.is_visible() {
3269            let clip_set = self.clip_tree_builder.build_for_prim(
3270                pending_primitive.clip_node_id,
3271                &pending_primitive.info,
3272                &[],
3273                &mut self.interners,
3274            );
3275
3276            self.add_prim_to_draw_list(
3277                &pending_primitive.info,
3278                pending_primitive.spatial_node_index,
3279                clip_set,
3280                pending_primitive.prim,
3281            );
3282        }
3283    }
3284
3285    pub fn add_clear_rectangle(
3286        &mut self,
3287        spatial_node_index: SpatialNodeIndex,
3288        clip_node_id: ClipNodeId,
3289        info: &LayoutPrimitiveInfo,
3290    ) {
3291        // Clear prims must be in their own picture cache slice to
3292        // be composited correctly.
3293        self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
3294
3295        self.add_primitive(
3296            spatial_node_index,
3297            clip_node_id,
3298            info,
3299            Vec::new(),
3300            PrimitiveKeyKind::Clear,
3301        );
3302
3303        self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
3304    }
3305
3306    pub fn add_line(
3307        &mut self,
3308        spatial_node_index: SpatialNodeIndex,
3309        clip_node_id: ClipNodeId,
3310        info: &LayoutPrimitiveInfo,
3311        wavy_line_thickness: f32,
3312        orientation: LineOrientation,
3313        color: ColorF,
3314        style: LineStyle,
3315    ) {
3316        // For line decorations, we can construct the render task cache key
3317        // here during scene building, since it doesn't depend on device
3318        // pixel ratio or transform.
3319        let size = get_line_decoration_size(
3320            &info.rect.size(),
3321            orientation,
3322            style,
3323            wavy_line_thickness,
3324        );
3325
3326        let cache_key = size.map(|size| {
3327            LineDecorationCacheKey {
3328                style,
3329                orientation,
3330                wavy_line_thickness: Au::from_f32_px(wavy_line_thickness),
3331                size: size.to_au(),
3332            }
3333        });
3334
3335        self.add_primitive(
3336            spatial_node_index,
3337            clip_node_id,
3338            &info,
3339            Vec::new(),
3340            LineDecoration {
3341                cache_key,
3342                color: color.into(),
3343            },
3344        );
3345    }
3346
3347    pub fn add_border(
3348        &mut self,
3349        spatial_node_index: SpatialNodeIndex,
3350        clip_node_id: ClipNodeId,
3351        info: &LayoutPrimitiveInfo,
3352        border_item: &BorderDisplayItem,
3353        gradient_stops: ItemRange<GradientStop>,
3354    ) {
3355        match border_item.details {
3356            BorderDetails::NinePatch(ref border) => {
3357                let nine_patch = NinePatchDescriptor {
3358                    width: border.width,
3359                    height: border.height,
3360                    slice: border.slice,
3361                    fill: border.fill,
3362                    repeat_horizontal: border.repeat_horizontal,
3363                    repeat_vertical: border.repeat_vertical,
3364                    widths: border_item.widths.into(),
3365                };
3366
3367                match border.source {
3368                    NinePatchBorderSource::Image(key, rendering) => {
3369                        let prim = ImageBorder {
3370                            request: ImageRequest {
3371                                key,
3372                                rendering,
3373                                tile: None,
3374                            },
3375                            nine_patch,
3376                        };
3377
3378                        self.add_nonshadowable_primitive(
3379                            spatial_node_index,
3380                            clip_node_id,
3381                            info,
3382                            Vec::new(),
3383                            prim,
3384                        );
3385                    }
3386                    NinePatchBorderSource::Gradient(gradient) => {
3387                        let prim = match self.create_linear_gradient_prim(
3388                            &info,
3389                            gradient.start_point,
3390                            gradient.end_point,
3391                            read_gradient_stops(gradient_stops),
3392                            gradient.extend_mode,
3393                            LayoutSize::new(border.height as f32, border.width as f32),
3394                            LayoutSize::zero(),
3395                            Some(Box::new(nine_patch)),
3396                            EdgeAaSegmentMask::all(),
3397                        ) {
3398                            Some(prim) => prim,
3399                            None => return,
3400                        };
3401
3402                        self.add_nonshadowable_primitive(
3403                            spatial_node_index,
3404                            clip_node_id,
3405                            info,
3406                            Vec::new(),
3407                            prim,
3408                        );
3409                    }
3410                    NinePatchBorderSource::RadialGradient(gradient) => {
3411                        let prim = self.create_radial_gradient_prim(
3412                            &info,
3413                            gradient.center,
3414                            gradient.start_offset * gradient.radius.width,
3415                            gradient.end_offset * gradient.radius.width,
3416                            gradient.radius.width / gradient.radius.height,
3417                            read_gradient_stops(gradient_stops),
3418                            gradient.extend_mode,
3419                            LayoutSize::new(border.height as f32, border.width as f32),
3420                            LayoutSize::zero(),
3421                            Some(Box::new(nine_patch)),
3422                        );
3423
3424                        self.add_nonshadowable_primitive(
3425                            spatial_node_index,
3426                            clip_node_id,
3427                            info,
3428                            Vec::new(),
3429                            prim,
3430                        );
3431                    }
3432                    NinePatchBorderSource::ConicGradient(gradient) => {
3433                        let prim = self.create_conic_gradient_prim(
3434                            &info,
3435                            gradient.center,
3436                            gradient.angle,
3437                            gradient.start_offset,
3438                            gradient.end_offset,
3439                            gradient_stops,
3440                            gradient.extend_mode,
3441                            LayoutSize::new(border.height as f32, border.width as f32),
3442                            LayoutSize::zero(),
3443                            Some(Box::new(nine_patch)),
3444                        );
3445
3446                        self.add_nonshadowable_primitive(
3447                            spatial_node_index,
3448                            clip_node_id,
3449                            info,
3450                            Vec::new(),
3451                            prim,
3452                        );
3453                    }
3454                };
3455            }
3456            BorderDetails::Normal(ref border) => {
3457                self.add_normal_border(
3458                    info,
3459                    border,
3460                    border_item.widths,
3461                    spatial_node_index,
3462                    clip_node_id,
3463                );
3464            }
3465        }
3466    }
3467
3468    pub fn create_linear_gradient_prim(
3469        &mut self,
3470        info: &LayoutPrimitiveInfo,
3471        start_point: LayoutPoint,
3472        end_point: LayoutPoint,
3473        stops: Vec<GradientStopKey>,
3474        extend_mode: ExtendMode,
3475        stretch_size: LayoutSize,
3476        mut tile_spacing: LayoutSize,
3477        nine_patch: Option<Box<NinePatchDescriptor>>,
3478        edge_aa_mask: EdgeAaSegmentMask,
3479    ) -> Option<LinearGradient> {
3480        let mut prim_rect = info.rect;
3481        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3482
3483        let mut has_hard_stops = false;
3484        let mut is_entirely_transparent = true;
3485        let mut prev_stop = None;
3486        for stop in &stops {
3487            if Some(stop.offset) == prev_stop {
3488                has_hard_stops = true;
3489            }
3490            prev_stop = Some(stop.offset);
3491            if stop.color.a > 0 {
3492                is_entirely_transparent = false;
3493            }
3494        }
3495
3496        // If all the stops have no alpha, then this
3497        // gradient can't contribute to the scene.
3498        if is_entirely_transparent {
3499            return None;
3500        }
3501
3502        // Try to ensure that if the gradient is specified in reverse, then so long as the stops
3503        // are also supplied in reverse that the rendered result will be equivalent. To do this,
3504        // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
3505        // just designate the reference orientation as start < end. Aligned gradient rendering
3506        // manages to produce the same result regardless of orientation, so don't worry about
3507        // reversing in that case.
3508        let reverse_stops = start_point.x > end_point.x ||
3509            (start_point.x == end_point.x && start_point.y > end_point.y);
3510
3511        // To get reftests exactly matching with reverse start/end
3512        // points, it's necessary to reverse the gradient
3513        // line in some cases.
3514        let (sp, ep) = if reverse_stops {
3515            (end_point, start_point)
3516        } else {
3517            (start_point, end_point)
3518        };
3519
3520        // We set a limit to the resolution at which cached gradients are rendered.
3521        // For most gradients this is fine but when there are hard stops this causes
3522        // noticeable artifacts. If so, fall back to non-cached gradients.
3523        let max = gradient::LINEAR_MAX_CACHED_SIZE;
3524        let caching_causes_artifacts = has_hard_stops && (stretch_size.width > max || stretch_size.height > max);
3525
3526        let is_tiled = prim_rect.width() > stretch_size.width
3527         || prim_rect.height() > stretch_size.height;
3528        // SWGL has a fast-path that can render gradients faster than it can sample from the
3529        // texture cache so we disable caching in this configuration. Cached gradients are
3530        // faster on hardware.
3531        let cached = (!self.config.is_software || is_tiled) && !caching_causes_artifacts;
3532
3533        Some(LinearGradient {
3534            extend_mode,
3535            start_point: sp.into(),
3536            end_point: ep.into(),
3537            stretch_size: stretch_size.into(),
3538            tile_spacing: tile_spacing.into(),
3539            stops,
3540            reverse_stops,
3541            nine_patch,
3542            cached,
3543            edge_aa_mask,
3544        })
3545    }
3546
3547    pub fn create_radial_gradient_prim(
3548        &mut self,
3549        info: &LayoutPrimitiveInfo,
3550        center: LayoutPoint,
3551        start_radius: f32,
3552        end_radius: f32,
3553        ratio_xy: f32,
3554        stops: Vec<GradientStopKey>,
3555        extend_mode: ExtendMode,
3556        stretch_size: LayoutSize,
3557        mut tile_spacing: LayoutSize,
3558        nine_patch: Option<Box<NinePatchDescriptor>>,
3559    ) -> RadialGradient {
3560        let mut prim_rect = info.rect;
3561        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3562
3563        let params = RadialGradientParams {
3564            start_radius,
3565            end_radius,
3566            ratio_xy,
3567        };
3568
3569        RadialGradient {
3570            extend_mode,
3571            center: center.into(),
3572            params,
3573            stretch_size: stretch_size.into(),
3574            tile_spacing: tile_spacing.into(),
3575            nine_patch,
3576            stops,
3577        }
3578    }
3579
3580    pub fn create_conic_gradient_prim(
3581        &mut self,
3582        info: &LayoutPrimitiveInfo,
3583        center: LayoutPoint,
3584        angle: f32,
3585        start_offset: f32,
3586        end_offset: f32,
3587        stops: ItemRange<GradientStop>,
3588        extend_mode: ExtendMode,
3589        stretch_size: LayoutSize,
3590        mut tile_spacing: LayoutSize,
3591        nine_patch: Option<Box<NinePatchDescriptor>>,
3592    ) -> ConicGradient {
3593        let mut prim_rect = info.rect;
3594        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3595
3596        let stops = stops.iter().map(|stop| {
3597            GradientStopKey {
3598                offset: stop.offset,
3599                color: stop.color.into(),
3600            }
3601        }).collect();
3602
3603        ConicGradient {
3604            extend_mode,
3605            center: center.into(),
3606            params: ConicGradientParams { angle, start_offset, end_offset },
3607            stretch_size: stretch_size.into(),
3608            tile_spacing: tile_spacing.into(),
3609            nine_patch,
3610            stops,
3611        }
3612    }
3613
3614    pub fn add_text(
3615        &mut self,
3616        spatial_node_index: SpatialNodeIndex,
3617        clip_node_id: ClipNodeId,
3618        prim_info: &LayoutPrimitiveInfo,
3619        font_instance_key: &FontInstanceKey,
3620        text_color: &ColorF,
3621        glyph_range: ItemRange<GlyphInstance>,
3622        glyph_options: Option<GlyphOptions>,
3623        ref_frame_offset: LayoutVector2D,
3624    ) {
3625        let offset = self.current_external_scroll_offset(spatial_node_index) + ref_frame_offset;
3626
3627        let text_run = {
3628            let shared_key = self.fonts.instance_keys.map_key(font_instance_key);
3629            let font_instance = match self.fonts.instances.get_font_instance(shared_key) {
3630                Some(instance) => instance,
3631                None => {
3632                    warn!("Unknown font instance key");
3633                    debug!("key={:?} shared={:?}", font_instance_key, shared_key);
3634                    return;
3635                }
3636            };
3637
3638            // Trivial early out checks
3639            if font_instance.size <= FontSize::zero() {
3640                return;
3641            }
3642
3643            // TODO(gw): Use a proper algorithm to select
3644            // whether this item should be rendered with
3645            // subpixel AA!
3646            let mut render_mode = self.config
3647                .default_font_render_mode
3648                .limit_by(font_instance.render_mode);
3649            let mut flags = font_instance.flags;
3650            if let Some(options) = glyph_options {
3651                render_mode = render_mode.limit_by(options.render_mode);
3652                flags |= options.flags;
3653            }
3654
3655            let font = FontInstance::new(
3656                font_instance,
3657                (*text_color).into(),
3658                render_mode,
3659                flags,
3660            );
3661
3662            // TODO(gw): It'd be nice not to have to allocate here for creating
3663            //           the primitive key, when the common case is that the
3664            //           hash will match and we won't end up creating a new
3665            //           primitive template.
3666            let prim_offset = prim_info.rect.min.to_vector() - offset;
3667            let glyphs = glyph_range
3668                .iter()
3669                .map(|glyph| {
3670                    GlyphInstance {
3671                        index: glyph.index,
3672                        point: glyph.point - prim_offset,
3673                    }
3674                })
3675                .collect();
3676
3677            // Query the current requested raster space (stack handled by push/pop
3678            // stacking context).
3679            let requested_raster_space = self.raster_space_stack
3680                .last()
3681                .cloned()
3682                .unwrap();
3683
3684            TextRun {
3685                glyphs: Arc::new(glyphs),
3686                font,
3687                shadow: false,
3688                requested_raster_space,
3689                reference_frame_offset: ref_frame_offset,
3690            }
3691        };
3692
3693        self.add_primitive(
3694            spatial_node_index,
3695            clip_node_id,
3696            prim_info,
3697            Vec::new(),
3698            text_run,
3699        );
3700    }
3701
3702    pub fn add_image(
3703        &mut self,
3704        spatial_node_index: SpatialNodeIndex,
3705        clip_node_id: ClipNodeId,
3706        info: &LayoutPrimitiveInfo,
3707        stretch_size: LayoutSize,
3708        mut tile_spacing: LayoutSize,
3709        image_key: ImageKey,
3710        image_rendering: ImageRendering,
3711        alpha_type: AlphaType,
3712        color: ColorF,
3713    ) {
3714        let mut prim_rect = info.rect;
3715        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3716        let info = LayoutPrimitiveInfo {
3717            rect: prim_rect,
3718            .. *info
3719        };
3720
3721        self.add_primitive(
3722            spatial_node_index,
3723            clip_node_id,
3724            &info,
3725            Vec::new(),
3726            Image {
3727                key: image_key,
3728                tile_spacing: tile_spacing.into(),
3729                stretch_size: stretch_size.into(),
3730                color: color.into(),
3731                image_rendering,
3732                alpha_type,
3733            },
3734        );
3735    }
3736
3737    pub fn add_yuv_image(
3738        &mut self,
3739        spatial_node_index: SpatialNodeIndex,
3740        clip_node_id: ClipNodeId,
3741        info: &LayoutPrimitiveInfo,
3742        yuv_data: YuvData,
3743        color_depth: ColorDepth,
3744        color_space: YuvColorSpace,
3745        color_range: ColorRange,
3746        image_rendering: ImageRendering,
3747    ) {
3748        let format = yuv_data.get_format();
3749        let yuv_key = match yuv_data {
3750            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
3751            YuvData::P010(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
3752            YuvData::NV16(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
3753            YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
3754            YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
3755        };
3756
3757        self.add_nonshadowable_primitive(
3758            spatial_node_index,
3759            clip_node_id,
3760            info,
3761            Vec::new(),
3762            YuvImage {
3763                color_depth,
3764                yuv_key,
3765                format,
3766                color_space,
3767                color_range,
3768                image_rendering,
3769            },
3770        );
3771    }
3772
3773    fn add_primitive_instance_to_3d_root(
3774        &mut self,
3775        prim: ExtendedPrimitiveInstance,
3776    ) {
3777        // find the 3D root and append to the children list
3778        for sc in self.sc_stack.iter_mut().rev() {
3779            match sc.context_3d {
3780                Picture3DContext::In { root_data: Some(ref mut prims), .. } => {
3781                    prims.push(prim);
3782                    break;
3783                }
3784                Picture3DContext::In { .. } => {}
3785                Picture3DContext::Out => panic!("Unable to find 3D root"),
3786            }
3787        }
3788    }
3789
3790    #[allow(dead_code)]
3791    pub fn add_backdrop_filter(
3792        &mut self,
3793        spatial_node_index: SpatialNodeIndex,
3794        clip_node_id: ClipNodeId,
3795        info: &LayoutPrimitiveInfo,
3796        filters: Vec<Filter>,
3797        filter_datas: Vec<FilterData>,
3798        filter_primitives: Vec<FilterPrimitive>,
3799    ) {
3800        // We don't know the spatial node for a backdrop filter, as it's whatever is the
3801        // backdrop root, but we can't know this if the root is a picture cache slice
3802        // (which is the common case). It will get resolved later during `finalize_picture`.
3803        let filter_spatial_node_index = SpatialNodeIndex::UNKNOWN;
3804
3805        self.make_current_slice_atomic_if_required();
3806
3807        // Ensure we create a clip-chain for the capture primitive that matches
3808        // the render primitive, otherwise one might get culled while the other
3809        // is considered visible.
3810        let clip_leaf_id = self.clip_tree_builder.build_for_prim(
3811            clip_node_id,
3812            info,
3813            &[],
3814            &mut self.interners,
3815        );
3816
3817        // Create the backdrop prim - this is a placeholder which sets the size of resolve
3818        // picture that reads from the backdrop root
3819        let backdrop_capture_instance = self.create_primitive(
3820            info,
3821            clip_leaf_id,
3822            BackdropCapture {
3823            },
3824        );
3825
3826        // Create a prim_list for this backdrop prim and add to a picture chain builder, which
3827        // is needed for the call to `wrap_prim_with_filters` below
3828        let mut prim_list = PrimitiveList::empty();
3829        prim_list.add_prim(
3830            backdrop_capture_instance,
3831            info.rect,
3832            spatial_node_index,
3833            info.flags,
3834            &mut self.prim_instances,
3835            &self.clip_tree_builder,
3836        );
3837
3838        let mut source = PictureChainBuilder::from_prim_list(
3839            prim_list,
3840            info.flags,
3841            filter_spatial_node_index,
3842            RasterSpace::Screen,
3843            true,
3844        );
3845
3846        // Wrap the backdrop primitive picture with the filters that were specified. This
3847        // produces a picture chain with 1+ pictures with the filter composite modes set.
3848        source = self.wrap_prim_with_filters(
3849            source,
3850            clip_node_id,
3851            filters,
3852            filter_primitives,
3853            filter_datas,
3854            Some(false),
3855            LayoutVector2D::zero(),
3856        );
3857
3858        // If all the filters were no-ops (e.g. opacity(0)) then we don't get a picture here
3859        // and we can skip adding the backdrop-filter.
3860        if source.has_picture() {
3861            source = source.add_picture(
3862                PictureCompositeMode::IntermediateSurface,
3863                clip_node_id,
3864                Picture3DContext::Out,
3865                &mut self.interners,
3866                &mut self.prim_store,
3867                &mut self.prim_instances,
3868                &mut self.clip_tree_builder,
3869            );
3870
3871            let filtered_instance = source.finalize(
3872                clip_node_id,
3873                &mut self.interners,
3874                &mut self.prim_store,
3875                &mut self.clip_tree_builder,
3876                None,
3877            );
3878
3879            // Extract the pic index for the intermediate surface. We need to
3880            // supply this to the capture prim below.
3881            let output_pic_index = match filtered_instance.kind {
3882                PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
3883                _ => panic!("bug: not a picture"),
3884            };
3885
3886            // Find which stacking context (or root tile cache) to add the
3887            // backdrop-filter chain to
3888            let sc_index = self.sc_stack.iter().rposition(|sc| {
3889                !sc.flags.contains(StackingContextFlags::WRAPS_BACKDROP_FILTER)
3890            });
3891
3892            match sc_index {
3893                Some(sc_index) => {
3894                    self.sc_stack[sc_index].prim_list.add_prim(
3895                        filtered_instance,
3896                        info.rect,
3897                        filter_spatial_node_index,
3898                        info.flags,
3899                        &mut self.prim_instances,
3900                        &self.clip_tree_builder,
3901                    );
3902                }
3903                None => {
3904                    self.tile_cache_builder.add_prim(
3905                        filtered_instance,
3906                        info.rect,
3907                        filter_spatial_node_index,
3908                        info.flags,
3909                        self.spatial_tree,
3910                        self.interners,
3911                        &self.quality_settings,
3912                        &mut self.prim_instances,
3913                        &self.clip_tree_builder,
3914                    );
3915                }
3916            }
3917
3918            // Add the prim that renders the result of the backdrop filter chain
3919            let mut backdrop_render_instance = self.create_primitive(
3920                info,
3921                clip_leaf_id,
3922                BackdropRender {
3923                },
3924            );
3925
3926            // Set up the picture index for the backdrop-filter output in the prim
3927            // that will draw it
3928            match backdrop_render_instance.kind {
3929                PrimitiveInstanceKind::BackdropRender { ref mut pic_index, .. } => {
3930                    assert_eq!(*pic_index, PictureIndex::INVALID);
3931                    *pic_index = output_pic_index;
3932                }
3933                _ => panic!("bug: unexpected prim kind"),
3934            }
3935
3936            self.add_primitive_to_draw_list(
3937                backdrop_render_instance,
3938                info.rect,
3939                spatial_node_index,
3940                info.flags,
3941            );
3942        }
3943    }
3944
3945    #[must_use]
3946    fn wrap_prim_with_filters(
3947        &mut self,
3948        mut source: PictureChainBuilder,
3949        clip_node_id: ClipNodeId,
3950        mut filter_ops: Vec<Filter>,
3951        mut filter_primitives: Vec<FilterPrimitive>,
3952        filter_datas: Vec<FilterData>,
3953        should_inflate_override: Option<bool>,
3954        context_offset: LayoutVector2D,
3955    ) -> PictureChainBuilder {
3956        // TODO(cbrewster): Currently CSS and SVG filters live side by side in WebRender, but unexpected results will
3957        // happen if they are used simulataneously. Gecko only provides either filter ops or filter primitives.
3958        // At some point, these two should be combined and CSS filters should be expressed in terms of SVG filters.
3959        assert!(filter_ops.is_empty() || filter_primitives.is_empty(),
3960            "Filter ops and filter primitives are not allowed on the same stacking context.");
3961
3962        // For each filter, create a new image with that composite mode.
3963        let mut current_filter_data_index = 0;
3964        // Check if the filter chain is actually an SVGFE filter graph DAG
3965        //
3966        // TODO: We technically could translate all CSS filters to SVGFE here if
3967        // we want to reduce redundant code.
3968        if let Some(Filter::SVGGraphNode(..)) = filter_ops.first() {
3969            // The interesting parts of the handling of SVG filters are:
3970            // * scene_building.rs : wrap_prim_with_filters (you are here)
3971            // * picture.rs : get_coverage_svgfe
3972            // * render_task.rs : new_svg_filter_graph
3973            // * render_target.rs : add_svg_filter_node_instances
3974
3975            // The SVG spec allows us to drop the entire filter graph if it is
3976            // unreasonable, so we limit the number of filters in a graph
3977            const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
3978            // Easily tunable for debugging proper handling of inflated rects,
3979            // this should normally be 1
3980            const SVGFE_INFLATE: i16 = 1;
3981
3982            // Validate inputs to all filters.
3983            //
3984            // Several assumptions can be made about the DAG:
3985            // * All filters take a specific number of inputs (feMerge is not
3986            //   supported, the code that built the display items had to convert
3987            //   any feMerge ops to SVGFECompositeOver already).
3988            // * All input buffer ids are < the output buffer id of the node.
3989            // * If SourceGraphic or SourceAlpha are used, they are standalone
3990            //   nodes with no inputs.
3991            // * Whenever subregion of a node is smaller than the subregion
3992            //   of the inputs, it is a deliberate clip of those inputs to the
3993            //   new rect, this can occur before/after blur and dropshadow for
3994            //   example, so we must explicitly handle subregion correctly, but
3995            //   we do not have to allocate the unused pixels as the transparent
3996            //   black has no efect on any of the filters, only certain filters
3997            //   like feFlood can generate something from nothing.
3998            // * Coordinate basis of the graph has to be adjusted by
3999            //   context_offset to put the subregions in the same space that the
4000            //   primitives are in, as they do that offset as well.
4001            let mut reference_for_buffer_id: [FilterGraphPictureReference; BUFFER_LIMIT] = [
4002                FilterGraphPictureReference{
4003                    // This value is deliberately invalid, but not a magic
4004                    // number, it's just this way to guarantee an assertion
4005                    // failure if something goes wrong.
4006                    buffer_id: FilterOpGraphPictureBufferId::BufferId(-1),
4007                    subregion: LayoutRect::zero(), // Always overridden
4008                    offset: LayoutVector2D::zero(),
4009                    inflate: 0,
4010                    source_padding: LayoutRect::zero(),
4011                    target_padding: LayoutRect::zero(),
4012                }; BUFFER_LIMIT];
4013            let mut filters: Vec<(FilterGraphNode, FilterGraphOp)> = Vec::new();
4014            filters.reserve(BUFFER_LIMIT);
4015            for (original_id, parsefilter) in filter_ops.iter().enumerate() {
4016                if filters.len() >= BUFFER_LIMIT {
4017                    // If the DAG is too large to process, the spec requires
4018                    // that we drop all filters and display source image as-is.
4019                    return source;
4020                }
4021
4022                let newfilter = match parsefilter {
4023                    Filter::SVGGraphNode(parsenode, op) => {
4024                        // We need to offset the subregion by the stacking context
4025                        // offset or we'd be in the wrong coordinate system, prims
4026                        // are already offset by this same amount.
4027                        let clip_region = parsenode.subregion
4028                            .translate(context_offset);
4029
4030                        let mut newnode = FilterGraphNode {
4031                            kept_by_optimizer: false,
4032                            linear: parsenode.linear,
4033                            inflate: SVGFE_INFLATE,
4034                            inputs: Vec::new(),
4035                            subregion: clip_region,
4036                        };
4037
4038                        // Initialize remapped versions of the inputs, this is
4039                        // done here to share code between the enum variants.
4040                        let mut remapped_inputs: Vec<FilterGraphPictureReference> = Vec::new();
4041                        remapped_inputs.reserve_exact(parsenode.inputs.len());
4042                        for input in &parsenode.inputs {
4043                            match input.buffer_id {
4044                                FilterOpGraphPictureBufferId::BufferId(buffer_id) => {
4045                                    // Reference to earlier node output, if this
4046                                    // is None, it's a bug
4047                                    let pic = *reference_for_buffer_id
4048                                        .get(buffer_id as usize)
4049                                        .expect("BufferId not valid?");
4050                                    // We have to adjust the subregion and
4051                                    // padding based on the input offset for
4052                                    // feOffset ops, the padding may be inflated
4053                                    // further by other ops such as blurs below.
4054                                    let offset = input.offset;
4055                                    let subregion = pic.subregion
4056                                        .translate(offset);
4057                                    let source_padding = LayoutRect::zero()
4058                                        .translate(-offset);
4059                                    let target_padding = LayoutRect::zero()
4060                                        .translate(offset);
4061                                    remapped_inputs.push(
4062                                        FilterGraphPictureReference {
4063                                            buffer_id: pic.buffer_id,
4064                                            subregion,
4065                                            offset,
4066                                            inflate: pic.inflate,
4067                                            source_padding,
4068                                            target_padding,
4069                                        });
4070                                }
4071                                FilterOpGraphPictureBufferId::None => panic!("Unsupported FilterOpGraphPictureBufferId"),
4072                            }
4073                        }
4074
4075                        fn union_unchecked(a: LayoutRect, b: LayoutRect) -> LayoutRect {
4076                            let mut r = a;
4077                            if r.min.x > b.min.x {r.min.x = b.min.x}
4078                            if r.min.y > b.min.y {r.min.y = b.min.y}
4079                            if r.max.x < b.max.x {r.max.x = b.max.x}
4080                            if r.max.y < b.max.y {r.max.y = b.max.y}
4081                            r
4082                        }
4083
4084                        match op {
4085                            FilterGraphOp::SVGFEFlood{..} |
4086                            FilterGraphOp::SVGFESourceAlpha |
4087                            FilterGraphOp::SVGFESourceGraphic |
4088                            FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
4089                            FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
4090                            FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
4091                            FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
4092                                assert!(remapped_inputs.len() == 0);
4093                                (newnode.clone(), op.clone())
4094                            }
4095                            FilterGraphOp::SVGFEColorMatrix{..} |
4096                            FilterGraphOp::SVGFEIdentity |
4097                            FilterGraphOp::SVGFEImage{..} |
4098                            FilterGraphOp::SVGFEOpacity{..} |
4099                            FilterGraphOp::SVGFEToAlpha => {
4100                                assert!(remapped_inputs.len() == 1);
4101                                newnode.inputs = remapped_inputs;
4102                                (newnode.clone(), op.clone())
4103                            }
4104                            FilterGraphOp::SVGFEComponentTransfer => {
4105                                assert!(remapped_inputs.len() == 1);
4106                                // Convert to SVGFEComponentTransferInterned
4107                                let filter_data =
4108                                    &filter_datas[current_filter_data_index];
4109                                let filter_data = filter_data.sanitize();
4110                                current_filter_data_index = current_filter_data_index + 1;
4111
4112                                // filter data is 4KiB of gamma ramps used
4113                                // only by SVGFEComponentTransferWithHandle.
4114                                //
4115                                // The gamma ramps are interleaved as RGBA32F
4116                                // pixels (unlike in regular ComponentTransfer,
4117                                // where the values are not interleaved), so
4118                                // r_values[3] is the alpha of the first color,
4119                                // not the 4th red value.  This layout makes the
4120                                // shader more compatible with buggy compilers that
4121                                // do not like indexing components on a vec4.
4122                                let creates_pixels =
4123                                    if let Some(a) = filter_data.r_values.get(3) {
4124                                        *a != 0.0
4125                                    } else {
4126                                        false
4127                                    };
4128                                let filter_data_key = SFilterDataKey {
4129                                    data:
4130                                        SFilterData {
4131                                            r_func: SFilterDataComponent::from_functype_values(
4132                                                filter_data.func_r_type, &filter_data.r_values),
4133                                            g_func: SFilterDataComponent::from_functype_values(
4134                                                filter_data.func_g_type, &filter_data.g_values),
4135                                            b_func: SFilterDataComponent::from_functype_values(
4136                                                filter_data.func_b_type, &filter_data.b_values),
4137                                            a_func: SFilterDataComponent::from_functype_values(
4138                                                filter_data.func_a_type, &filter_data.a_values),
4139                                        },
4140                                };
4141
4142                                let handle = self.interners
4143                                    .filter_data
4144                                    .intern(&filter_data_key, || ());
4145
4146                                newnode.inputs = remapped_inputs;
4147                                (newnode.clone(), FilterGraphOp::SVGFEComponentTransferInterned{handle, creates_pixels})
4148                            }
4149                            FilterGraphOp::SVGFEComponentTransferInterned{..} => unreachable!(),
4150                            FilterGraphOp::SVGFETile => {
4151                                assert!(remapped_inputs.len() == 1);
4152                                // feTile usually uses every pixel of input
4153                                remapped_inputs[0].source_padding =
4154                                    LayoutRect::max_rect();
4155                                remapped_inputs[0].target_padding =
4156                                    LayoutRect::max_rect();
4157                                newnode.inputs = remapped_inputs;
4158                                (newnode.clone(), op.clone())
4159                            }
4160                            FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{kernel_unit_length_x, kernel_unit_length_y, ..} |
4161                            FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{kernel_unit_length_x, kernel_unit_length_y, ..} |
4162                            FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{kernel_unit_length_x, kernel_unit_length_y, ..} |
4163                            FilterGraphOp::SVGFEMorphologyDilate{radius_x: kernel_unit_length_x, radius_y: kernel_unit_length_y} => {
4164                                assert!(remapped_inputs.len() == 1);
4165                                let padding = LayoutSize::new(
4166                                    kernel_unit_length_x.ceil(),
4167                                    kernel_unit_length_y.ceil(),
4168                                );
4169                                // Add source padding to represent the kernel pixels
4170                                // needed relative to target pixels
4171                                remapped_inputs[0].source_padding =
4172                                    remapped_inputs[0].source_padding
4173                                    .inflate(padding.width, padding.height);
4174                                // Add target padding to represent the area affected
4175                                // by a source pixel
4176                                remapped_inputs[0].target_padding =
4177                                    remapped_inputs[0].target_padding
4178                                    .inflate(padding.width, padding.height);
4179                                newnode.inputs = remapped_inputs;
4180                                (newnode.clone(), op.clone())
4181                            },
4182                            FilterGraphOp::SVGFEDiffuseLightingDistant{kernel_unit_length_x, kernel_unit_length_y, ..} |
4183                            FilterGraphOp::SVGFEDiffuseLightingPoint{kernel_unit_length_x, kernel_unit_length_y, ..} |
4184                            FilterGraphOp::SVGFEDiffuseLightingSpot{kernel_unit_length_x, kernel_unit_length_y, ..} |
4185                            FilterGraphOp::SVGFESpecularLightingDistant{kernel_unit_length_x, kernel_unit_length_y, ..} |
4186                            FilterGraphOp::SVGFESpecularLightingPoint{kernel_unit_length_x, kernel_unit_length_y, ..} |
4187                            FilterGraphOp::SVGFESpecularLightingSpot{kernel_unit_length_x, kernel_unit_length_y, ..} |
4188                            FilterGraphOp::SVGFEMorphologyErode{radius_x: kernel_unit_length_x, radius_y: kernel_unit_length_y} => {
4189                                assert!(remapped_inputs.len() == 1);
4190                                let padding = LayoutSize::new(
4191                                    kernel_unit_length_x.ceil(),
4192                                    kernel_unit_length_y.ceil(),
4193                                );
4194                                // Add source padding to represent the kernel pixels
4195                                // needed relative to target pixels
4196                                remapped_inputs[0].source_padding =
4197                                    remapped_inputs[0].source_padding
4198                                    .inflate(padding.width, padding.height);
4199                                // Add target padding to represent the area affected
4200                                // by a source pixel
4201                                remapped_inputs[0].target_padding =
4202                                    remapped_inputs[0].target_padding
4203                                    .inflate(padding.width, padding.height);
4204                                newnode.inputs = remapped_inputs;
4205                                (newnode.clone(), op.clone())
4206                            },
4207                            FilterGraphOp::SVGFEDisplacementMap { scale, .. } => {
4208                                assert!(remapped_inputs.len() == 2);
4209                                let padding = LayoutSize::new(
4210                                    scale.ceil(),
4211                                    scale.ceil(),
4212                                );
4213                                // Add padding to both inputs for source and target
4214                                // rects, we might be able to skip some of these,
4215                                // but it's not that important to optimize here, a
4216                                // loose fit is fine.
4217                                remapped_inputs[0].source_padding =
4218                                    remapped_inputs[0].source_padding
4219                                    .inflate(padding.width, padding.height);
4220                                remapped_inputs[1].source_padding =
4221                                    remapped_inputs[1].source_padding
4222                                    .inflate(padding.width, padding.height);
4223                                remapped_inputs[0].target_padding =
4224                                    remapped_inputs[0].target_padding
4225                                    .inflate(padding.width, padding.height);
4226                                remapped_inputs[1].target_padding =
4227                                    remapped_inputs[1].target_padding
4228                                    .inflate(padding.width, padding.height);
4229                                newnode.inputs = remapped_inputs;
4230                                (newnode.clone(), op.clone())
4231                            },
4232                            FilterGraphOp::SVGFEDropShadow{ dx, dy, std_deviation_x, std_deviation_y, .. } => {
4233                                assert!(remapped_inputs.len() == 1);
4234                                let padding = LayoutSize::new(
4235                                    std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
4236                                    std_deviation_y.ceil() * BLUR_SAMPLE_SCALE,
4237                                );
4238                                // Add source padding to represent the shadow
4239                                remapped_inputs[0].source_padding =
4240                                    union_unchecked(
4241                                        remapped_inputs[0].source_padding,
4242                                        remapped_inputs[0].source_padding
4243                                            .inflate(padding.width, padding.height)
4244                                            .translate(
4245                                                LayoutVector2D::new(-dx, -dy)
4246                                            )
4247                                    );
4248                                // Add target padding to represent the area needed
4249                                // to calculate pixels of the shadow
4250                                remapped_inputs[0].target_padding =
4251                                    union_unchecked(
4252                                        remapped_inputs[0].target_padding,
4253                                        remapped_inputs[0].target_padding
4254                                            .inflate(padding.width, padding.height)
4255                                            .translate(
4256                                                LayoutVector2D::new(*dx, *dy)
4257                                            )
4258                                    );
4259                                newnode.inputs = remapped_inputs;
4260                                (newnode.clone(), op.clone())
4261                            },
4262                            FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y} => {
4263                                assert!(remapped_inputs.len() == 1);
4264                                let padding = LayoutSize::new(
4265                                    std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
4266                                    std_deviation_y.ceil() * BLUR_SAMPLE_SCALE,
4267                                );
4268                                // Add source padding to represent the blur
4269                                remapped_inputs[0].source_padding =
4270                                    remapped_inputs[0].source_padding
4271                                    .inflate(padding.width, padding.height);
4272                                // Add target padding to represent the blur
4273                                remapped_inputs[0].target_padding =
4274                                    remapped_inputs[0].target_padding
4275                                    .inflate(padding.width, padding.height);
4276                                newnode.inputs = remapped_inputs;
4277                                (newnode.clone(), op.clone())
4278                            }
4279                            FilterGraphOp::SVGFEBlendColor |
4280                            FilterGraphOp::SVGFEBlendColorBurn |
4281                            FilterGraphOp::SVGFEBlendColorDodge |
4282                            FilterGraphOp::SVGFEBlendDarken |
4283                            FilterGraphOp::SVGFEBlendDifference |
4284                            FilterGraphOp::SVGFEBlendExclusion |
4285                            FilterGraphOp::SVGFEBlendHardLight |
4286                            FilterGraphOp::SVGFEBlendHue |
4287                            FilterGraphOp::SVGFEBlendLighten |
4288                            FilterGraphOp::SVGFEBlendLuminosity|
4289                            FilterGraphOp::SVGFEBlendMultiply |
4290                            FilterGraphOp::SVGFEBlendNormal |
4291                            FilterGraphOp::SVGFEBlendOverlay |
4292                            FilterGraphOp::SVGFEBlendSaturation |
4293                            FilterGraphOp::SVGFEBlendScreen |
4294                            FilterGraphOp::SVGFEBlendSoftLight |
4295                            FilterGraphOp::SVGFECompositeArithmetic{..} |
4296                            FilterGraphOp::SVGFECompositeATop |
4297                            FilterGraphOp::SVGFECompositeIn |
4298                            FilterGraphOp::SVGFECompositeLighter |
4299                            FilterGraphOp::SVGFECompositeOut |
4300                            FilterGraphOp::SVGFECompositeOver |
4301                            FilterGraphOp::SVGFECompositeXOR => {
4302                                assert!(remapped_inputs.len() == 2);
4303                                newnode.inputs = remapped_inputs;
4304                                (newnode, op.clone())
4305                            }
4306                        }
4307                    }
4308                    Filter::Opacity(valuebinding, value) => {
4309                        // Opacity filter is sometimes appended by
4310                        // wr_dp_push_stacking_context before we get here,
4311                        // convert to SVGFEOpacity in the graph.  Note that
4312                        // linear is set to false because it has no meaning for
4313                        // opacity (which scales all of the RGBA uniformly).
4314                        let pic = reference_for_buffer_id[original_id as usize - 1];
4315                        (
4316                            FilterGraphNode {
4317                                kept_by_optimizer: false,
4318                                linear: false,
4319                                inflate: SVGFE_INFLATE,
4320                                inputs: [pic].to_vec(),
4321                                subregion: pic.subregion,
4322                            },
4323                            FilterGraphOp::SVGFEOpacity{
4324                                valuebinding: *valuebinding,
4325                                value: *value,
4326                            },
4327                        )
4328                    }
4329                    _ => {
4330                        log!(Level::Warn, "wrap_prim_with_filters: unexpected filter after SVG filters filter[{:?}]={:?}", original_id, parsefilter);
4331                        // If we can't figure out how to process the graph, spec
4332                        // requires that we drop all filters and display source
4333                        // image as-is.
4334                        return source;
4335                    }
4336                };
4337                let id = filters.len();
4338                filters.push(newfilter);
4339
4340                // Set the reference remapping for the last (or only) node
4341                // that we just pushed
4342                reference_for_buffer_id[original_id] = FilterGraphPictureReference {
4343                    buffer_id: FilterOpGraphPictureBufferId::BufferId(id as i16),
4344                    subregion: filters[id].0.subregion,
4345                    offset: LayoutVector2D::zero(),
4346                    inflate: filters[id].0.inflate,
4347                    source_padding: LayoutRect::zero(),
4348                    target_padding: LayoutRect::zero(),
4349                };
4350            }
4351
4352            if filters.len() >= BUFFER_LIMIT {
4353                // If the DAG is too large to process, the spec requires
4354                // that we drop all filters and display source image as-is.
4355                return source;
4356            }
4357
4358            // Mark used graph nodes, starting at the last graph node, since
4359            // this is a DAG in sorted order we can just iterate backwards and
4360            // know we will find children before parents in order.
4361            //
4362            // Per SVG spec the last node (which is the first we encounter this
4363            // way) is the final output, so its dependencies are what we want to
4364            // mark as kept_by_optimizer
4365            let mut kept_node_by_buffer_id = [false; BUFFER_LIMIT];
4366            kept_node_by_buffer_id[filters.len() - 1] = true;
4367            for (index, (node, _op)) in filters.iter_mut().enumerate().rev() {
4368                let mut keep = false;
4369                // Check if this node's output was marked to be kept
4370                if let Some(k) = kept_node_by_buffer_id.get(index) {
4371                    if *k {
4372                        keep = true;
4373                    }
4374                }
4375                if keep {
4376                    // If this node contributes to the final output we need
4377                    // to mark its inputs as also contributing when they are
4378                    // encountered later
4379                    node.kept_by_optimizer = true;
4380                    for input in &node.inputs {
4381                        if let FilterOpGraphPictureBufferId::BufferId(id) = input.buffer_id {
4382                            if let Some(k) = kept_node_by_buffer_id.get_mut(id as usize) {
4383                                *k = true;
4384                            }
4385                        }
4386                    }
4387                }
4388            }
4389
4390            // Validate the DAG nature of the graph - if we find anything wrong
4391            // here it means the above code is bugged.
4392            let mut invalid_dag = false;
4393            for (id, (node, _op)) in filters.iter().enumerate() {
4394                for input in &node.inputs {
4395                    if let FilterOpGraphPictureBufferId::BufferId(buffer_id) = input.buffer_id {
4396                        if buffer_id < 0 || buffer_id as usize >= id {
4397                            invalid_dag = true;
4398                        }
4399                    }
4400                }
4401            }
4402
4403            if invalid_dag {
4404                log!(Level::Warn, "List of FilterOp::SVGGraphNode filter primitives appears to be invalid!");
4405                for (id, (node, op)) in filters.iter().enumerate() {
4406                    log!(Level::Warn, " node:     buffer=BufferId({}) op={} inflate={} subregion {:?} linear={} kept={}",
4407                         id, op.kind(), node.inflate,
4408                         node.subregion,
4409                         node.linear,
4410                         node.kept_by_optimizer,
4411                    );
4412                    for input in &node.inputs {
4413                        log!(Level::Warn, "input: buffer={} inflate={} subregion {:?} offset {:?} target_padding={:?} source_padding={:?}",
4414                            match input.buffer_id {
4415                                FilterOpGraphPictureBufferId::BufferId(id) => format!("BufferId({})", id),
4416                                FilterOpGraphPictureBufferId::None => "None".into(),
4417                            },
4418                            input.inflate,
4419                            input.subregion,
4420                            input.offset,
4421                            input.target_padding,
4422                            input.source_padding,
4423                        );
4424                    }
4425                }
4426            }
4427            if invalid_dag {
4428                // if the DAG is invalid, we can't render it
4429                return source;
4430            }
4431
4432            let composite_mode = PictureCompositeMode::SVGFEGraph(
4433                filters,
4434            );
4435
4436            source = source.add_picture(
4437                composite_mode,
4438                clip_node_id,
4439                Picture3DContext::Out,
4440                &mut self.interners,
4441                &mut self.prim_store,
4442                &mut self.prim_instances,
4443                &mut self.clip_tree_builder,
4444            );
4445
4446            return source;
4447        }
4448
4449        // Handle regular CSS filter chains
4450        for filter in &mut filter_ops {
4451            let composite_mode = match filter {
4452                Filter::ComponentTransfer => {
4453                    let filter_data =
4454                        &filter_datas[current_filter_data_index];
4455                    let filter_data = filter_data.sanitize();
4456                    current_filter_data_index = current_filter_data_index + 1;
4457                    if filter_data.is_identity() {
4458                        continue
4459                    } else {
4460                        let filter_data_key = SFilterDataKey {
4461                            data:
4462                                SFilterData {
4463                                    r_func: SFilterDataComponent::from_functype_values(
4464                                        filter_data.func_r_type, &filter_data.r_values),
4465                                    g_func: SFilterDataComponent::from_functype_values(
4466                                        filter_data.func_g_type, &filter_data.g_values),
4467                                    b_func: SFilterDataComponent::from_functype_values(
4468                                        filter_data.func_b_type, &filter_data.b_values),
4469                                    a_func: SFilterDataComponent::from_functype_values(
4470                                        filter_data.func_a_type, &filter_data.a_values),
4471                                },
4472                        };
4473
4474                        let handle = self.interners
4475                            .filter_data
4476                            .intern(&filter_data_key, || ());
4477                        PictureCompositeMode::ComponentTransferFilter(handle)
4478                    }
4479                }
4480                Filter::SVGGraphNode(_, _) => {
4481                    // SVG filter graphs were handled above
4482                    panic!("SVGGraphNode encountered in regular CSS filter chain?");
4483                }
4484                _ => {
4485                    if filter.is_noop() {
4486                        continue;
4487                    } else {
4488                        let mut filter = filter.clone();
4489
4490                        // backdrop-filter spec says that blurs should assume edgeMode=Duplicate
4491                        // We can do this by not inflating the bounds, which means the blur
4492                        // shader will duplicate pixels outside the sample rect
4493                        if let Some(should_inflate_override) = should_inflate_override {
4494                            if let Filter::Blur { ref mut should_inflate, .. } = filter {
4495                                *should_inflate = should_inflate_override;
4496                            }
4497                        }
4498
4499                        PictureCompositeMode::Filter(filter)
4500                    }
4501                }
4502            };
4503
4504            source = source.add_picture(
4505                composite_mode,
4506                clip_node_id,
4507                Picture3DContext::Out,
4508                &mut self.interners,
4509                &mut self.prim_store,
4510                &mut self.prim_instances,
4511                &mut self.clip_tree_builder,
4512            );
4513        }
4514
4515        if !filter_primitives.is_empty() {
4516            let filter_datas = filter_datas.iter()
4517                .map(|filter_data| filter_data.sanitize())
4518                .map(|filter_data| {
4519                    SFilterData {
4520                        r_func: SFilterDataComponent::from_functype_values(
4521                            filter_data.func_r_type, &filter_data.r_values),
4522                        g_func: SFilterDataComponent::from_functype_values(
4523                            filter_data.func_g_type, &filter_data.g_values),
4524                        b_func: SFilterDataComponent::from_functype_values(
4525                            filter_data.func_b_type, &filter_data.b_values),
4526                        a_func: SFilterDataComponent::from_functype_values(
4527                            filter_data.func_a_type, &filter_data.a_values),
4528                    }
4529                })
4530                .collect();
4531
4532            // Sanitize filter inputs
4533            for primitive in &mut filter_primitives {
4534                primitive.sanitize();
4535            }
4536
4537            let composite_mode = PictureCompositeMode::SvgFilter(
4538                filter_primitives,
4539                filter_datas,
4540            );
4541
4542            source = source.add_picture(
4543                composite_mode,
4544                clip_node_id,
4545                Picture3DContext::Out,
4546                &mut self.interners,
4547                &mut self.prim_store,
4548                &mut self.prim_instances,
4549                &mut self.clip_tree_builder,
4550            );
4551        }
4552
4553        source
4554    }
4555}
4556
4557
4558pub trait CreateShadow {
4559    fn create_shadow(
4560        &self,
4561        shadow: &Shadow,
4562        blur_is_noop: bool,
4563        current_raster_space: RasterSpace,
4564    ) -> Self;
4565}
4566
4567pub trait IsVisible {
4568    fn is_visible(&self) -> bool;
4569}
4570
4571/// A primitive instance + some extra information about the primitive. This is
4572/// stored when constructing 3d rendering contexts, which involve cutting
4573/// primitive lists.
4574struct ExtendedPrimitiveInstance {
4575    instance: PrimitiveInstance,
4576    spatial_node_index: SpatialNodeIndex,
4577    flags: PrimitiveFlags,
4578}
4579
4580/// Internal tracking information about the currently pushed stacking context.
4581/// Used to track what operations need to happen when a stacking context is popped.
4582struct StackingContextInfo {
4583    /// If true, pop and entry from the containing block stack.
4584    pop_containing_block: bool,
4585    /// If true, pop an entry from the flattened stacking context stack.
4586    pop_stacking_context: bool,
4587    /// If true, set a tile cache barrier when popping the stacking context.
4588    set_tile_cache_barrier: bool,
4589    /// If true, this stacking context was nested into two pushes instead of
4590    /// one, and requires an extra pop to compensate. The info to pop is stored
4591    /// at the top of `extra_stacking_context_stack`.
4592    needs_extra_stacking_context: bool,
4593}
4594
4595/// Properties of a stacking context that are maintained
4596/// during creation of the scene. These structures are
4597/// not persisted after the initial scene build.
4598struct FlattenedStackingContext {
4599    /// The list of primitive instances added to this stacking context.
4600    prim_list: PrimitiveList,
4601
4602    /// Primitive instance flags for compositing this stacking context
4603    prim_flags: PrimitiveFlags,
4604
4605    /// The positioning node for this stacking context
4606    spatial_node_index: SpatialNodeIndex,
4607
4608    /// The clip chain for this stacking context
4609    clip_node_id: ClipNodeId,
4610
4611    /// The list of filters / mix-blend-mode for this
4612    /// stacking context.
4613    composite_ops: CompositeOps,
4614
4615    /// Bitfield of reasons this stacking context needs to
4616    /// be an offscreen surface.
4617    blit_reason: BlitReason,
4618
4619    /// CSS transform-style property.
4620    transform_style: TransformStyle,
4621
4622    /// Defines the relationship to a preserve-3D hiearachy.
4623    context_3d: Picture3DContext<ExtendedPrimitiveInstance>,
4624
4625    /// Flags identifying the type of container (among other things) this stacking context is
4626    flags: StackingContextFlags,
4627
4628    /// Requested raster space for this stacking context
4629    raster_space: RasterSpace,
4630
4631    /// Offset to be applied to any filter sub-regions
4632    subregion_offset: LayoutVector2D,
4633}
4634
4635impl FlattenedStackingContext {
4636    /// Return true if the stacking context has a valid preserve-3d property
4637    pub fn is_3d(&self) -> bool {
4638        self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty()
4639    }
4640
4641    /// Return true if the stacking context isn't needed.
4642    pub fn is_redundant(
4643        context_3d: &Picture3DContext<ExtendedPrimitiveInstance>,
4644        composite_ops: &CompositeOps,
4645        blit_reason: BlitReason,
4646        parent: Option<&FlattenedStackingContext>,
4647        prim_flags: PrimitiveFlags,
4648    ) -> bool {
4649        // Any 3d context is required
4650        if let Picture3DContext::In { .. } = context_3d {
4651            return false;
4652        }
4653
4654        // If any filters are present that affect the output
4655        if composite_ops.has_valid_filters() {
4656            return false;
4657        }
4658
4659        // If a mix-blend is active, we'll need to apply it in most cases
4660        if composite_ops.mix_blend_mode.is_some() {
4661            match parent {
4662                Some(ref parent) => {
4663                    // However, if the parent stacking context is empty, then the mix-blend
4664                    // is a no-op, and we can skip it
4665                    if !parent.prim_list.is_empty() {
4666                        return false;
4667                    }
4668                }
4669                None => {
4670                    // TODO(gw): For now, we apply mix-blend ops that may be no-ops on a root
4671                    //           level picture cache slice. We could apply a similar optimization
4672                    //           to above with a few extra checks here, but it's probably quite rare.
4673                    return false;
4674                }
4675            }
4676        }
4677
4678        // If need to isolate in surface due to clipping / mix-blend-mode
4679        if !blit_reason.is_empty() {
4680            return false;
4681        }
4682
4683        // If backface visibility is explicitly set.
4684        if !prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
4685            return false;
4686        }
4687
4688        // It is redundant!
4689        true
4690    }
4691
4692    /// Cut the sequence of the immediate children recorded so far and generate a picture from them.
4693    pub fn cut_item_sequence(
4694        &mut self,
4695        prim_store: &mut PrimitiveStore,
4696        interners: &mut Interners,
4697        composite_mode: Option<PictureCompositeMode>,
4698        flat_items_context_3d: Picture3DContext<OrderedPictureChild>,
4699        clip_tree_builder: &mut ClipTreeBuilder,
4700    ) -> Option<(PictureIndex, PrimitiveInstance)> {
4701        if self.prim_list.is_empty() {
4702            return None
4703        }
4704
4705        let pic_index = PictureIndex(prim_store.pictures
4706            .alloc()
4707            .init(PicturePrimitive::new_image(
4708                composite_mode.clone(),
4709                flat_items_context_3d,
4710                self.prim_flags,
4711                mem::replace(&mut self.prim_list, PrimitiveList::empty()),
4712                self.spatial_node_index,
4713                self.raster_space,
4714                PictureFlags::empty(),
4715                None
4716            ))
4717        );
4718
4719        let prim_instance = create_prim_instance(
4720            pic_index,
4721            composite_mode.into(),
4722            self.raster_space,
4723            self.clip_node_id,
4724            interners,
4725            clip_tree_builder,
4726        );
4727
4728        Some((pic_index, prim_instance))
4729    }
4730}
4731
4732/// A primitive that is added while a shadow context is
4733/// active is stored as a pending primitive and only
4734/// added to pictures during pop_all_shadows.
4735pub struct PendingPrimitive<T> {
4736    spatial_node_index: SpatialNodeIndex,
4737    clip_node_id: ClipNodeId,
4738    info: LayoutPrimitiveInfo,
4739    prim: T,
4740}
4741
4742/// As shadows are pushed, they are stored as pending
4743/// shadows, and handled at once during pop_all_shadows.
4744pub struct PendingShadow {
4745    shadow: Shadow,
4746    should_inflate: bool,
4747    spatial_node_index: SpatialNodeIndex,
4748}
4749
4750pub enum ShadowItem {
4751    Shadow(PendingShadow),
4752    Image(PendingPrimitive<Image>),
4753    LineDecoration(PendingPrimitive<LineDecoration>),
4754    NormalBorder(PendingPrimitive<NormalBorderPrim>),
4755    Primitive(PendingPrimitive<PrimitiveKeyKind>),
4756    TextRun(PendingPrimitive<TextRun>),
4757}
4758
4759impl From<PendingPrimitive<Image>> for ShadowItem {
4760    fn from(image: PendingPrimitive<Image>) -> Self {
4761        ShadowItem::Image(image)
4762    }
4763}
4764
4765impl From<PendingPrimitive<LineDecoration>> for ShadowItem {
4766    fn from(line_dec: PendingPrimitive<LineDecoration>) -> Self {
4767        ShadowItem::LineDecoration(line_dec)
4768    }
4769}
4770
4771impl From<PendingPrimitive<NormalBorderPrim>> for ShadowItem {
4772    fn from(border: PendingPrimitive<NormalBorderPrim>) -> Self {
4773        ShadowItem::NormalBorder(border)
4774    }
4775}
4776
4777impl From<PendingPrimitive<PrimitiveKeyKind>> for ShadowItem {
4778    fn from(container: PendingPrimitive<PrimitiveKeyKind>) -> Self {
4779        ShadowItem::Primitive(container)
4780    }
4781}
4782
4783impl From<PendingPrimitive<TextRun>> for ShadowItem {
4784    fn from(text_run: PendingPrimitive<TextRun>) -> Self {
4785        ShadowItem::TextRun(text_run)
4786    }
4787}
4788
4789fn create_prim_instance(
4790    pic_index: PictureIndex,
4791    composite_mode_key: PictureCompositeKey,
4792    raster_space: RasterSpace,
4793    clip_node_id: ClipNodeId,
4794    interners: &mut Interners,
4795    clip_tree_builder: &mut ClipTreeBuilder,
4796) -> PrimitiveInstance {
4797    let pic_key = PictureKey::new(
4798        Picture {
4799            composite_mode_key,
4800            raster_space,
4801        },
4802    );
4803
4804    let data_handle = interners
4805        .picture
4806        .intern(&pic_key, || ());
4807
4808    PrimitiveInstance::new(
4809        PrimitiveInstanceKind::Picture {
4810            data_handle,
4811            pic_index,
4812        },
4813        clip_tree_builder.build_for_picture(
4814            clip_node_id,
4815        ),
4816    )
4817}
4818
4819fn filter_ops_for_compositing(
4820    input_filters: ItemRange<FilterOp>,
4821) -> Vec<Filter> {
4822    // TODO(gw): Now that we resolve these later on,
4823    //           we could probably make it a bit
4824    //           more efficient than cloning these here.
4825    input_filters.iter().map(|filter| filter.into()).collect()
4826}
4827
4828fn filter_datas_for_compositing(
4829    input_filter_datas: &[TempFilterData],
4830) -> Vec<FilterData> {
4831    // TODO(gw): Now that we resolve these later on,
4832    //           we could probably make it a bit
4833    //           more efficient than cloning these here.
4834    let mut filter_datas = vec![];
4835    for temp_filter_data in input_filter_datas {
4836        let func_types : Vec<ComponentTransferFuncType> = temp_filter_data.func_types.iter().collect();
4837        debug_assert!(func_types.len() == 4);
4838        filter_datas.push( FilterData {
4839            func_r_type: func_types[0],
4840            r_values: temp_filter_data.r_values.iter().collect(),
4841            func_g_type: func_types[1],
4842            g_values: temp_filter_data.g_values.iter().collect(),
4843            func_b_type: func_types[2],
4844            b_values: temp_filter_data.b_values.iter().collect(),
4845            func_a_type: func_types[3],
4846            a_values: temp_filter_data.a_values.iter().collect(),
4847        });
4848    }
4849    filter_datas
4850}
4851
4852fn filter_primitives_for_compositing(
4853    input_filter_primitives: ItemRange<FilterPrimitive>,
4854) -> Vec<FilterPrimitive> {
4855    // Resolve these in the flattener?
4856    // TODO(gw): Now that we resolve these later on,
4857    //           we could probably make it a bit
4858    //           more efficient than cloning these here.
4859    input_filter_primitives.iter().map(|primitive| primitive).collect()
4860}
4861
4862fn process_repeat_size(
4863    snapped_rect: &LayoutRect,
4864    unsnapped_rect: &LayoutRect,
4865    repeat_size: LayoutSize,
4866) -> LayoutSize {
4867    // FIXME(aosmond): The tile size is calculated based on several parameters
4868    // during display list building. It may produce a slightly different result
4869    // than the bounds due to floating point error accumulation, even though in
4870    // theory they should be the same. We do a fuzzy check here to paper over
4871    // that. It may make more sense to push the original parameters into scene
4872    // building and let it do a saner calculation with more information (e.g.
4873    // the snapped values).
4874    const EPSILON: f32 = 0.001;
4875    LayoutSize::new(
4876        if repeat_size.width.approx_eq_eps(&unsnapped_rect.width(), &EPSILON) {
4877            snapped_rect.width()
4878        } else {
4879            repeat_size.width
4880        },
4881        if repeat_size.height.approx_eq_eps(&unsnapped_rect.height(), &EPSILON) {
4882            snapped_rect.height()
4883        } else {
4884            repeat_size.height
4885        },
4886    )
4887}
4888
4889fn read_gradient_stops(stops: ItemRange<GradientStop>) -> Vec<GradientStopKey> {
4890    stops.iter().map(|stop| {
4891        GradientStopKey {
4892            offset: stop.offset,
4893            color: stop.color.into(),
4894        }
4895    }).collect()
4896}
4897
4898/// A helper for reusing the scene builder's memory allocations and dropping
4899/// scene allocations on the scene builder thread to avoid lock contention in
4900/// jemalloc.
4901pub struct SceneRecycler {
4902    pub tx: Sender<BuiltScene>,
4903    rx: Receiver<BuiltScene>,
4904
4905    // Allocations recycled from BuiltScene:
4906
4907    pub prim_store: PrimitiveStore,
4908    pub clip_store: ClipStore,
4909    pub picture_graph: PictureGraph,
4910    pub prim_instances: Vec<PrimitiveInstance>,
4911    pub surfaces: Vec<SurfaceInfo>,
4912    pub hit_testing_scene: Option<HitTestingScene>,
4913    pub clip_tree_builder: Option<ClipTreeBuilder>,
4914    //Could also attempt to recycle the following:
4915    //pub tile_cache_config: TileCacheConfig,
4916    //pub pipeline_epochs: FastHashMap<PipelineId, Epoch>,
4917    //pub tile_cache_pictures: Vec<PictureIndex>,
4918
4919
4920    // Allocations recycled from SceneBuilder
4921
4922    id_to_index_mapper_stack: Vec<NodeIdToIndexMapper>,
4923    sc_stack: Vec<FlattenedStackingContext>,
4924    containing_block_stack: Vec<SpatialNodeIndex>,
4925    raster_space_stack: Vec<RasterSpace>,
4926    pending_shadow_items: VecDeque<ShadowItem>,
4927    iframe_size: Vec<LayoutSize>,
4928}
4929
4930impl SceneRecycler {
4931    pub fn new() -> Self {
4932        let (tx, rx) = unbounded_channel();
4933        SceneRecycler {
4934            tx,
4935            rx,
4936
4937            prim_instances: Vec::new(),
4938            surfaces: Vec::new(),
4939            prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
4940            clip_store: ClipStore::new(),
4941            picture_graph: PictureGraph::new(),
4942            hit_testing_scene: None,
4943            clip_tree_builder: None,
4944
4945            id_to_index_mapper_stack: Vec::new(),
4946            sc_stack: Vec::new(),
4947            containing_block_stack: Vec::new(),
4948            raster_space_stack: Vec::new(),
4949            pending_shadow_items: VecDeque::new(),
4950            iframe_size: Vec::new(),
4951        }
4952    }
4953
4954    /// Do some bookkeeping of past memory allocations, retaining some of them for
4955    /// reuse and dropping the rest.
4956    ///
4957    /// Should be called once between scene builds, ideally outside of the critical
4958    /// path since deallocations can take some time.
4959    #[inline(never)]
4960    pub fn recycle_built_scene(&mut self) {
4961        let Ok(scene) = self.rx.try_recv() else {
4962            return;
4963        };
4964
4965        self.prim_store = scene.prim_store;
4966        self.clip_store = scene.clip_store;
4967        // We currently retain top-level allocations but don't attempt to retain leaf
4968        // allocations in the prim store and clip store. We don't have to reset it here
4969        // but doing so avoids dropping the leaf allocations in the
4970        self.prim_store.reset();
4971        self.clip_store.reset();
4972        self.hit_testing_scene = Arc::try_unwrap(scene.hit_testing_scene).ok();
4973        self.picture_graph = scene.picture_graph;
4974        self.prim_instances = scene.prim_instances;
4975        self.surfaces = scene.surfaces;
4976        if let Some(clip_tree_builder) = &mut self.clip_tree_builder {
4977            clip_tree_builder.recycle_tree(scene.clip_tree);
4978        }
4979
4980        while let Ok(_) = self.rx.try_recv() {
4981            // If for some reason more than one scene accumulated in the queue, drop
4982            // the rest.
4983        }
4984
4985        // Note: fields of the scene we don't recycle get dropped here.
4986    }
4987}