tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

scene_building.rs (198957B)


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