Skip to main content

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