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