Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  scene_building.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! # Scene building
//!
//! Scene building is the phase during which display lists, a representation built for
//! serialization, are turned into a scene, webrender's internal representation that is
//! suited for rendering frames.
//!
//! This phase is happening asynchronously on the scene builder thread.
//!
//! # General algorithm
//!
//! The important aspects of scene building are:
//! - Building up primitive lists (much of the cost of scene building goes here).
//! - Creating pictures for content that needs to be rendered into a surface, be it so that
//!   filters can be applied or for caching purposes.
//! - Maintaining a temporary stack of stacking contexts to keep track of some of the
//!   drawing states.
//! - Stitching multiple display lists which reference each other (without cycles) into
//!   a single scene (see build_reference_frame).
//! - Interning, which detects when some of the retained state stays the same between display
//!   lists.
//!
//! The scene builder linearly traverses the serialized display list which is naturally
//! ordered back-to-front, accumulating primitives in the top-most stacking context's
//! primitive list.
//! At the end of each stacking context (see pop_stacking_context), its primitive list is
//! either handed over to a picture if one is created, or it is concatenated into the parent
//! stacking context's primitive list.
//!
//! The flow of the algorithm is mostly linear except when handling:
//!  - shadow stacks (see push_shadow and pop_all_shadows),
//!  - backdrop filters (see add_backdrop_filter)
//!

use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, BuiltDisplayListIter, PrimitiveFlags, SnapshotInfo};
use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
use api::{DebugFlags, DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData};
use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop};
use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags};
use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDescriptor};
use api::{APZScrollGeneration, HasScrollLinkedEffect, Shadow, SpatialId, StickyFrameDescriptor, ImageMask, ItemTag};
use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
use api::{ReferenceTransformBinding, Rotation, FillRule, SpatialTreeItem, ReferenceFrameDescriptor};
use api::{FilterOpGraphPictureBufferId, SVGFE_GRAPH_MAX};
use api::channel::{unbounded_channel, Receiver, Sender};
use api::units::*;
use crate::image_tiling::simplify_repeated_primitive;
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipIntern, ClipItemKey, ClipItemKeyKind, ClipStore};
use crate::clip::{ClipInternData, ClipNodeId, ClipLeafId};
use crate::clip::{PolygonDataHandle, ClipTreeBuilder};
use crate::segment::EdgeAaSegmentMask;
use crate::spatial_tree::{SceneSpatialTree, SpatialNodeContainer, SpatialNodeIndex, get_external_scroll_offset};
use crate::frame_builder::FrameBuilderConfig;
use glyph_rasterizer::{FontInstance, SharedFontResources};
use crate::hit_test::HitTestingScene;
use crate::intern::Interner;
use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, PlaneSplitterIndex, PipelineInstanceId};
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, SurfaceInfo, PictureFlags};
use crate::picture_graph::PictureGraph;
use crate::prim_store::{PrimitiveInstance, PrimitiveStoreStats};
use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
use crate::prim_store::{InternablePrimitive, PictureIndex};
use crate::prim_store::PolygonKey;
use crate::prim_store::backdrop::{BackdropCapture, BackdropRender};
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
use crate::prim_store::gradient::{
    GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient,
    ConicGradientParams, optimize_radial_gradient, apply_gradient_local_clip,
    optimize_linear_gradient, self,
};
use crate::prim_store::image::{Image, YuvImage};
use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey, get_line_decoration_size};
use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
use crate::prim_store::text_run::TextRun;
use crate::render_backend::SceneView;
use crate::resource_cache::ImageRequest;
use crate::scene::{BuiltScene, Scene, ScenePipeline, SceneStats, StackingContextHelpers};
use crate::scene_builder_thread::Interners;
use crate::space::SpaceSnapper;
use crate::spatial_node::{
    ReferenceFrameInfo, StickyFrameInfo, ScrollFrameKind, SpatialNodeUid, SpatialNodeType
};
use crate::tile_cache::TileCacheBuilder;
use euclid::approxeq::ApproxEq;
use std::{f32, mem, usize};
use std::collections::vec_deque::VecDeque;
use std::sync::Arc;
use crate::util::{VecHelper, MaxRect};
use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
use log::Level;

/// Offsets primitives (and clips) by the external scroll offset
/// supplied to scroll nodes.
pub struct ScrollOffsetMapper {
    pub current_spatial_node: SpatialNodeIndex,
    pub current_offset: LayoutVector2D,
}

impl ScrollOffsetMapper {
    fn new() -> Self {
        ScrollOffsetMapper {
            current_spatial_node: SpatialNodeIndex::INVALID,
            current_offset: LayoutVector2D::zero(),
        }
    }

    /// Return the accumulated external scroll offset for a spatial
    /// node. This caches the last result, which is the common case,
    /// or defers to the spatial tree to build the value.
    fn external_scroll_offset(
        &mut self,
        spatial_node_index: SpatialNodeIndex,
        spatial_tree: &SceneSpatialTree,
    ) -> LayoutVector2D {
        if spatial_node_index != self.current_spatial_node {
            self.current_spatial_node = spatial_node_index;
            self.current_offset = get_external_scroll_offset(spatial_tree, spatial_node_index);
        }

        self.current_offset
    }
}

/// A data structure that keeps track of mapping between API Ids for spatials and the indices
/// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives
/// and clips during frame building.
#[derive(Default)]
pub struct NodeIdToIndexMapper {
    spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
}

impl NodeIdToIndexMapper {
    fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
        let _old_value = self.spatial_node_map.insert(id, index);
        assert!(_old_value.is_none());
    }

    fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
        self.spatial_node_map[&id]
    }
}

#[derive(Debug, Clone, Default)]
pub struct CompositeOps {
    // Requires only a single texture as input (e.g. most filters)
    pub filters: Vec<Filter>,
    pub filter_datas: Vec<FilterData>,
    pub filter_primitives: Vec<FilterPrimitive>,
    pub snapshot: Option<SnapshotInfo>,

    // Requires two source textures (e.g. mix-blend-mode)
    pub mix_blend_mode: Option<MixBlendMode>,
}

impl CompositeOps {
    pub fn new(
        filters: Vec<Filter>,
        filter_datas: Vec<FilterData>,
        filter_primitives: Vec<FilterPrimitive>,
        mix_blend_mode: Option<MixBlendMode>,
        snapshot: Option<SnapshotInfo>,
    ) -> Self {
        CompositeOps {
            filters,
            filter_datas,
            filter_primitives,
            mix_blend_mode,
            snapshot,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.filters.is_empty() &&
            self.filter_primitives.is_empty() &&
            self.mix_blend_mode.is_none() &&
            self.snapshot.is_none()
    }

    /// Returns true if this CompositeOps contains any filters that affect
    /// the content (false if no filters, or filters are all no-ops).
    fn has_valid_filters(&self) -> bool {
        // For each filter, create a new image with that composite mode.
        let mut current_filter_data_index = 0;
        for filter in &self.filters {
            match filter {
                Filter::ComponentTransfer => {
                    let filter_data =
                        &self.filter_datas[current_filter_data_index];
                    let filter_data = filter_data.sanitize();
                    current_filter_data_index = current_filter_data_index + 1;
                    if filter_data.is_identity() {
                        continue
                    } else {
                        return true;
                    }
                }
                Filter::SVGGraphNode(..) => {return true;}
                _ => {
                    if filter.is_noop() {
                        continue;
                    } else {
                        return true;
                    }
                }
            }
        }

        if !self.filter_primitives.is_empty() {
            return true;
        }

        false
    }
}

/// Represents the current input for a picture chain builder (either a
/// prim list from the stacking context, or a wrapped picture instance).
enum PictureSource {
    PrimitiveList {
        prim_list: PrimitiveList,
    },
    WrappedPicture {
        instance: PrimitiveInstance,
    },
}

/// Helper struct to build picture chains during scene building from
/// a flattened stacking context struct.
struct PictureChainBuilder {
    /// The current input source for the next picture
    current: PictureSource,

    /// Positioning node for this picture chain
    spatial_node_index: SpatialNodeIndex,
    /// Prim flags for any pictures in this chain
    flags: PrimitiveFlags,
    /// Requested raster space for enclosing stacking context
    raster_space: RasterSpace,
    /// If true, set first picture as a resolve target
    set_resolve_target: bool,
    /// If true, mark the last picture as a sub-graph
    establishes_sub_graph: bool,
}

impl PictureChainBuilder {
    /// Create a new picture chain builder, from a primitive list
    fn from_prim_list(
        prim_list: PrimitiveList,
        flags: PrimitiveFlags,
        spatial_node_index: SpatialNodeIndex,
        raster_space: RasterSpace,
        is_sub_graph: bool,
    ) -> Self {
        PictureChainBuilder {
            current: PictureSource::PrimitiveList {
                prim_list,
            },
            spatial_node_index,
            flags,
            raster_space,
            establishes_sub_graph: is_sub_graph,
            set_resolve_target: is_sub_graph,
        }
    }

    /// Create a new picture chain builder, from a picture wrapper instance
    fn from_instance(
        instance: PrimitiveInstance,
        flags: PrimitiveFlags,
        spatial_node_index: SpatialNodeIndex,
        raster_space: RasterSpace,
    ) -> Self {
        PictureChainBuilder {
            current: PictureSource::WrappedPicture {
                instance,
            },
            flags,
            spatial_node_index,
            raster_space,
            establishes_sub_graph: false,
            set_resolve_target: false,
        }
    }

    /// Wrap the existing content with a new picture with the given parameters
    #[must_use]
    fn add_picture(
        self,
        composite_mode: PictureCompositeMode,
        clip_node_id: ClipNodeId,
        context_3d: Picture3DContext<OrderedPictureChild>,
        interners: &mut Interners,
        prim_store: &mut PrimitiveStore,
        prim_instances: &mut Vec<PrimitiveInstance>,
        clip_tree_builder: &mut ClipTreeBuilder,
    ) -> PictureChainBuilder {
        let prim_list = match self.current {
            PictureSource::PrimitiveList { prim_list } => {
                prim_list
            }
            PictureSource::WrappedPicture { instance } => {
                let mut prim_list = PrimitiveList::empty();

                prim_list.add_prim(
                    instance,
                    LayoutRect::zero(),
                    self.spatial_node_index,
                    self.flags,
                    prim_instances,
                    clip_tree_builder,
                );

                prim_list
            }
        };

        let flags = if self.set_resolve_target {
            PictureFlags::IS_RESOLVE_TARGET
        } else {
            PictureFlags::empty()
        };

        let pic_index = PictureIndex(prim_store.pictures
            .alloc()
            .init(PicturePrimitive::new_image(
                Some(composite_mode.clone()),
                context_3d,
                self.flags,
                prim_list,
                self.spatial_node_index,
                self.raster_space,
                flags,
                None,
            ))
        );

        let instance = create_prim_instance(
            pic_index,
            Some(composite_mode).into(),
            self.raster_space,
            clip_node_id,
            interners,
            clip_tree_builder,
        );

        PictureChainBuilder {
            current: PictureSource::WrappedPicture {
                instance,
            },
            spatial_node_index: self.spatial_node_index,
            flags: self.flags,
            raster_space: self.raster_space,
            // We are now on a subsequent picture, so set_resolve_target has been handled
            set_resolve_target: false,
            establishes_sub_graph: self.establishes_sub_graph,
        }
    }

    /// Finish building this picture chain. Set the clip chain on the outermost picture
    fn finalize(
        self,
        clip_node_id: ClipNodeId,
        interners: &mut Interners,
        prim_store: &mut PrimitiveStore,
        clip_tree_builder: &mut ClipTreeBuilder,
        snapshot: Option<SnapshotInfo>,
    ) -> PrimitiveInstance {
        let mut flags = PictureFlags::empty();
        if self.establishes_sub_graph {
            flags |= PictureFlags::IS_SUB_GRAPH;
        }

        match self.current {
            PictureSource::WrappedPicture { instance } => {
                let pic_index = instance.kind.as_pic();
                let picture = &mut prim_store.pictures[pic_index.0];
                picture.flags |= flags;
                picture.snapshot = snapshot;

                instance
            }
            PictureSource::PrimitiveList { prim_list } => {
                if self.set_resolve_target {
                    flags |= PictureFlags::IS_RESOLVE_TARGET;
                }

                // If no picture was created for this stacking context, create a
                // pass-through wrapper now. This is only needed in 1-2 edge cases
                // now, and will be removed as a follow up.
                let pic_index = PictureIndex(prim_store.pictures
                    .alloc()
                    .init(PicturePrimitive::new_image(
                        None,
                        Picture3DContext::Out,
                        self.flags,
                        prim_list,
                        self.spatial_node_index,
                        self.raster_space,
                        flags,
                        snapshot,
                    ))
                );

                create_prim_instance(
                    pic_index,
                    None.into(),
                    self.raster_space,
                    clip_node_id,
                    interners,
                    clip_tree_builder,
                )
            }
        }
    }

    /// Returns true if this builder wraps a picture
    #[allow(dead_code)]
    fn has_picture(&self) -> bool {
        match self.current {
            PictureSource::WrappedPicture { .. } => true,
            PictureSource::PrimitiveList { .. } => false,
        }
    }
}

bitflags! {
    /// Slice flags
    #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
    pub struct SliceFlags : u8 {
        /// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER
        const IS_SCROLLBAR = 1;
        /// Represents an atomic container (can't split out compositor surfaces in this slice)
        const IS_ATOMIC = 2;
    }
}

/// A structure that converts a serialized display list into a form that WebRender
/// can use to later build a frame. This structure produces a BuiltScene. Public
/// members are typically those that are destructured into the BuiltScene.
pub struct SceneBuilder<'a> {
    /// The scene that we are currently building.
    scene: &'a Scene,

    /// The map of all font instances.
    fonts: SharedFontResources,

    /// The data structure that converts between ClipId/SpatialId and the various
    /// index types that the SpatialTree uses.
    id_to_index_mapper_stack: Vec<NodeIdToIndexMapper>,

    /// A stack of stacking context properties.
    sc_stack: Vec<FlattenedStackingContext>,

    /// Stack of spatial node indices forming containing block for 3d contexts
    containing_block_stack: Vec<SpatialNodeIndex>,

    /// Stack of requested raster spaces for stacking contexts
    raster_space_stack: Vec<RasterSpace>,

    /// Maintains state for any currently active shadows
    pending_shadow_items: VecDeque<ShadowItem>,

    /// The SpatialTree that we are currently building during building.
    pub spatial_tree: &'a mut SceneSpatialTree,

    /// The store of primitives.
    pub prim_store: PrimitiveStore,

    /// Information about all primitives involved in hit testing.
    pub hit_testing_scene: HitTestingScene,

    /// The store which holds all complex clipping information.
    pub clip_store: ClipStore,

    /// The configuration to use for the FrameBuilder. We consult this in
    /// order to determine the default font.
    pub config: FrameBuilderConfig,

    /// Reference to the set of data that is interned across display lists.
    pub interners: &'a mut Interners,

    /// Helper struct to map spatial nodes to external scroll offsets.
    external_scroll_mapper: ScrollOffsetMapper,

    /// The current recursion depth of iframes encountered. Used to restrict picture
    /// caching slices to only the top-level content frame.
    iframe_size: Vec<LayoutSize>,

    /// Clip-chain for root iframes applied to any tile caches created within this iframe
    root_iframe_clip: Option<ClipId>,

    /// The current quality / performance settings for this scene.
    quality_settings: QualitySettings,

    /// Maintains state about the list of tile caches being built for this scene.
    tile_cache_builder: TileCacheBuilder,

    /// A helper struct to snap local rects in device space. During frame
    /// building we may establish new raster roots, however typically that is in
    /// cases where we won't be applying snapping (e.g. has perspective), or in
    /// edge cases (e.g. SVG filter) where we can accept slightly incorrect
    /// behaviour in favour of getting the common case right.
    snap_to_device: SpaceSnapper,

    /// A DAG that represents dependencies between picture primitives. This builds
    /// a set of passes to run various picture processing passes in during frame
    /// building, in a way that pictures are processed before (or after) their
    /// dependencies, without relying on recursion for those passes.
    picture_graph: PictureGraph,

    /// Keep track of snapshot pictures to ensure that they are rendered even if they
    /// are off-screen and the visibility traversal does not reach them.
    snapshot_pictures: Vec<PictureIndex>,

    /// Keep track of allocated plane splitters for this scene. A plane
    /// splitter is allocated whenever we encounter a new 3d rendering context.
    /// They are stored outside the picture since it makes it easier for them
    /// to be referenced by both the owning 3d rendering context and the child
    /// pictures that contribute to the splitter.
    /// During scene building "allocating" a splitter is just incrementing an index.
    /// Splitter objects themselves are allocated and recycled in the frame builder.
    next_plane_splitter_index: usize,

    /// A list of all primitive instances in the scene. We store them as a single
    /// array so that multiple different systems (e.g. tile-cache, visibility, property
    /// animation bindings) can store index buffers to prim instances.
    prim_instances: Vec<PrimitiveInstance>,

    /// A map of pipeline ids encountered during scene build - used to create unique
    /// pipeline instance ids as they are encountered.
    pipeline_instance_ids: FastHashMap<PipelineId, u32>,

    /// A list of surfaces (backing textures) that are relevant for this scene.
    /// Every picture is assigned to a surface (either a new surface if the picture
    /// has a composite mode, or the parent surface if it's a pass-through).
    surfaces: Vec<SurfaceInfo>,

    /// Used to build a ClipTree from the clip-chains, clips and state during scene building.
    clip_tree_builder: ClipTreeBuilder,

    /// Some primitives need to nest two stacking contexts instead of one
    /// (see push_stacking_context). We keep track of the extra stacking context info
    /// here and set a boolean on the inner stacking context info to remember to
    /// pop from this stack (see StackingContextInfo::needs_extra_stacking_context)
    extra_stacking_context_stack: Vec<StackingContextInfo>,
}

impl<'a> SceneBuilder<'a> {
    pub fn build(
        scene: &Scene,
        fonts: SharedFontResources,
        view: &SceneView,
        frame_builder_config: &FrameBuilderConfig,
        interners: &mut Interners,
        spatial_tree: &mut SceneSpatialTree,
        recycler: &mut SceneRecycler,
        stats: &SceneStats,
        debug_flags: DebugFlags,
    ) -> BuiltScene {
        profile_scope!("build_scene");

        // We checked that the root pipeline is available on the render backend.
        let root_pipeline_id = scene.root_pipeline_id.unwrap();
        let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
        let root_reference_frame_index = spatial_tree.root_reference_frame_index();

        // During scene building, we assume a 1:1 picture -> raster pixel scale
        let snap_to_device = SpaceSnapper::new(
            root_reference_frame_index,
            RasterPixelScale::new(1.0),
        );

        let mut builder = SceneBuilder {
            scene,
            spatial_tree,
            fonts,
            config: *frame_builder_config,
            id_to_index_mapper_stack: mem::take(&mut recycler.id_to_index_mapper_stack),
            hit_testing_scene: recycler.hit_testing_scene.take().unwrap_or_else(|| HitTestingScene::new(&stats.hit_test_stats)),
            pending_shadow_items: mem::take(&mut recycler.pending_shadow_items),
            sc_stack: mem::take(&mut recycler.sc_stack),
            containing_block_stack: mem::take(&mut recycler.containing_block_stack),
            raster_space_stack: mem::take(&mut recycler.raster_space_stack),
            prim_store: mem::take(&mut recycler.prim_store),
            clip_store: mem::take(&mut recycler.clip_store),
            interners,
            external_scroll_mapper: ScrollOffsetMapper::new(),
            iframe_size: mem::take(&mut recycler.iframe_size),
            root_iframe_clip: None,
            quality_settings: view.quality_settings,
            tile_cache_builder: TileCacheBuilder::new(
                root_reference_frame_index,
                frame_builder_config.background_color,
                debug_flags,
            ),
            snap_to_device,
            picture_graph: mem::take(&mut recycler.picture_graph),
            // This vector is empty most of the time, don't bother with recycling it for now.
            snapshot_pictures: Vec::new(),
            next_plane_splitter_index: 0,
            prim_instances: mem::take(&mut recycler.prim_instances),
            pipeline_instance_ids: FastHashMap::default(),
            surfaces: mem::take(&mut recycler.surfaces),
            clip_tree_builder: recycler.clip_tree_builder.take().unwrap_or_else(|| ClipTreeBuilder::new()),
            extra_stacking_context_stack: Vec::new(),
        };

        // Reset
        builder.hit_testing_scene.reset();
        builder.prim_store.reset();
        builder.clip_store.reset();
        builder.picture_graph.reset();
        builder.prim_instances.clear();
        builder.surfaces.clear();
        builder.sc_stack.clear();
        builder.containing_block_stack.clear();
        builder.id_to_index_mapper_stack.clear();
        builder.pending_shadow_items.clear();
        builder.iframe_size.clear();

        builder.raster_space_stack.clear();
        builder.raster_space_stack.push(RasterSpace::Screen);

        builder.clip_tree_builder.begin();

        builder.build_all(
            root_pipeline_id,
            &root_pipeline,
        );

        // Construct the picture cache primitive instance(s) from the tile cache builder
        let (tile_cache_config, tile_cache_pictures) = builder.tile_cache_builder.build(
            &builder.config,
            &mut builder.prim_store,
            &builder.spatial_tree,
            &builder.prim_instances,
            &mut builder.clip_tree_builder,
        );

        // Add all the tile cache pictures as roots of the picture graph
        for pic_index in &tile_cache_pictures {
            builder.picture_graph.add_root(*pic_index);
            SceneBuilder::finalize_picture(
                *pic_index,
                None,
                &mut builder.prim_store.pictures,
                None,
                &builder.clip_tree_builder,
                &builder.prim_instances,
                &builder.interners.clip,
            );
        }

        let clip_tree = builder.clip_tree_builder.finalize();

        recycler.clip_tree_builder = Some(builder.clip_tree_builder);
        recycler.sc_stack = builder.sc_stack;
        recycler.id_to_index_mapper_stack = builder.id_to_index_mapper_stack;
        recycler.containing_block_stack = builder.containing_block_stack;
        recycler.raster_space_stack = builder.raster_space_stack;
        recycler.pending_shadow_items = builder.pending_shadow_items;
        recycler.iframe_size = builder.iframe_size;

        BuiltScene {
            has_root_pipeline: scene.has_root_pipeline(),
            pipeline_epochs: scene.pipeline_epochs.clone(),
            output_rect: view.device_rect.size().into(),
            hit_testing_scene: Arc::new(builder.hit_testing_scene),
            prim_store: builder.prim_store,
            clip_store: builder.clip_store,
            config: builder.config,
            tile_cache_config,
            snapshot_pictures: builder.snapshot_pictures,
            tile_cache_pictures,
            picture_graph: builder.picture_graph,
            num_plane_splitters: builder.next_plane_splitter_index,
            prim_instances: builder.prim_instances,
            surfaces: builder.surfaces,
            clip_tree,
            recycler_tx: Some(recycler.tx.clone()),
        }
    }

    /// Traverse the picture prim list and update any late-set spatial nodes.
    /// Also, for each picture primitive, store the lowest-common-ancestor
    /// of all of the contained primitives' clips.
    // TODO(gw): This is somewhat hacky - it's unfortunate we need to do this, but it's
    //           because we can't determine the scroll root until we have checked all the
    //           primitives in the slice. Perhaps we could simplify this by doing some
    //           work earlier in the DL builder, so we know what scroll root will be picked?
    fn finalize_picture(
        pic_index: PictureIndex,
        prim_index: Option<usize>,
        pictures: &mut [PicturePrimitive],
        parent_spatial_node_index: Option<SpatialNodeIndex>,
        clip_tree_builder: &ClipTreeBuilder,
        prim_instances: &[PrimitiveInstance],
        clip_interner: &Interner<ClipIntern>,
    ) {
        // Extract the prim_list (borrow check) and select the spatial node to
        // assign to unknown clusters
        let (mut prim_list, spatial_node_index) = {
            let pic = &mut pictures[pic_index.0];
            assert_ne!(pic.spatial_node_index, SpatialNodeIndex::UNKNOWN);

            if pic.flags.contains(PictureFlags::IS_RESOLVE_TARGET) {
                pic.flags |= PictureFlags::DISABLE_SNAPPING;
            }

            // If we're a surface, use that spatial node, otherwise the parent
            let spatial_node_index = match pic.composite_mode {
                Some(_) => pic.spatial_node_index,
                None => parent_spatial_node_index.expect("bug: no parent"),
            };

            (
                mem::replace(&mut pic.prim_list, PrimitiveList::empty()),
                spatial_node_index,
            )
        };

        // Update the spatial node of any unknown clusters
        for cluster in &mut prim_list.clusters {
            if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
                cluster.spatial_node_index = spatial_node_index;
            }
        }

        // Work out the lowest common clip which is shared by all the
        // primitives in this picture.  If it is the same as the picture clip
        // then store it as the clip tree root for the picture so that it is
        // applied later as part of picture compositing.  Gecko gives every
        // primitive a viewport clip which, if applied within the picture,
        // will mess up tile caching and mean we have to redraw on every
        // scroll event (for tile caching to work usefully we specifically
        // want to draw things even if they are outside the viewport).
        let mut shared_clip_node_id = None;
        for cluster in &prim_list.clusters {
            for prim_instance in &prim_instances[cluster.prim_range()] {
                let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);

                shared_clip_node_id = match shared_clip_node_id {
                    Some(current) => {
                        Some(clip_tree_builder.find_lowest_common_ancestor(
                            current,
                            leaf.node_id,
                        ))
                    }
                    None => Some(leaf.node_id)
                };
            }
        }

        let lca_tree_node = shared_clip_node_id
            .and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id))
            .map(|node_id| clip_tree_builder.get_node(node_id));
        let lca_node = lca_tree_node
            .map(|tree_node| &clip_interner[tree_node.handle]);
        let pic_node_id = prim_index
            .map(|prim_index| clip_tree_builder.get_leaf(prim_instances[prim_index].clip_leaf_id).node_id)
            .and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id));
        let pic_node = pic_node_id
            .map(|node_id| clip_tree_builder.get_node(node_id))
            .map(|tree_node| &clip_interner[tree_node.handle]);

        // The logic behind this optimisation is that there's no need to clip
        // the contents of a picture when the crop will be applied anyway as
        // part of compositing the picture.  However, this is not true if the
        // picture includes a blur filter as the blur result depends on the
        // offscreen pixels which may or may not be cropped away.
        let has_blur = match &pictures[pic_index.0].composite_mode {
            Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true,
            Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true,
            Some(PictureCompositeMode::SvgFilter( .. )) => true,
            Some(PictureCompositeMode::SVGFEGraph( .. )) => true,
            _ => false,
        };

        // It is only safe to apply this optimisation if the old pic clip node
        // is the direct parent of the new LCA node.  If this is not the case
        // then there could be other more restrictive clips in between the two
        // which we would ignore by changing the clip root.  See Bug 1854062
        // for an example of this.
        let direct_parent = lca_tree_node
            .zip(pic_node_id)
            .map(|(lca_tree_node, pic_node_id)| lca_tree_node.parent == pic_node_id)
            .unwrap_or(false);

        if let Some((lca_node, pic_node)) = lca_node.zip(pic_node) {
            // It is only safe to ignore the LCA clip (by making it the clip
            // root) if it is equal to or larger than the picture clip. But
            // this comparison also needs to take into account spatial nodes
            // as the two clips may in general be on different spatial nodes.
            // For this specific Gecko optimisation we expect the the two
            // clips to be identical and have the same spatial node so it's
            // simplest to just test for ClipItemKey equality (which includes
            // both spatial node and the actual clip).
            if lca_node.key == pic_node.key && !has_blur && direct_parent {
                pictures[pic_index.0].clip_root = shared_clip_node_id;
            }
        }

        // Update the spatial node of any child pictures
        for cluster in &prim_list.clusters {
            for prim_instance_index in cluster.prim_range() {
                if let PrimitiveInstanceKind::Picture { pic_index: child_pic_index, .. } = prim_instances[prim_instance_index].kind {
                    let child_pic = &mut pictures[child_pic_index.0];

                    if child_pic.spatial_node_index == SpatialNodeIndex::UNKNOWN {
                        child_pic.spatial_node_index = spatial_node_index;
                    }

                    // Recurse into child pictures which may also have unknown spatial nodes
                    SceneBuilder::finalize_picture(
                        child_pic_index,
                        Some(prim_instance_index),
                        pictures,
                        Some(spatial_node_index),
                        clip_tree_builder,
                        prim_instances,
                        clip_interner,
                    );

                    if pictures[child_pic_index.0].flags.contains(PictureFlags::DISABLE_SNAPPING) {
                        pictures[pic_index.0].flags |= PictureFlags::DISABLE_SNAPPING;
                    }
                }
            }
        }

        // Restore the prim_list
        pictures[pic_index.0].prim_list = prim_list;
    }

    /// Retrieve the current external scroll offset on the provided spatial node.
    fn current_external_scroll_offset(
        &mut self,
        spatial_node_index: SpatialNodeIndex,
    ) -> LayoutVector2D {
        // Get the external scroll offset, if applicable.
        self.external_scroll_mapper
            .external_scroll_offset(
                spatial_node_index,
                self.spatial_tree,
            )
    }

    fn build_spatial_tree_for_display_list(
        &mut self,
        dl: &BuiltDisplayList,
        pipeline_id: PipelineId,
        instance_id: PipelineInstanceId,
    ) {
        dl.iter_spatial_tree(|item| {
            match item {
                SpatialTreeItem::ScrollFrame(descriptor) => {
                    let parent_space = self.get_space(descriptor.parent_space);
                    self.build_scroll_frame(
                        descriptor,
                        parent_space,
                        pipeline_id,
                        instance_id,
                    );
                }
                SpatialTreeItem::ReferenceFrame(descriptor) => {
                    let parent_space = self.get_space(descriptor.parent_spatial_id);
                    self.build_reference_frame(
                        descriptor,
                        parent_space,
                        pipeline_id,
                        instance_id,
                    );
                }
                SpatialTreeItem::StickyFrame(descriptor) => {
                    let parent_space = self.get_space(descriptor.parent_spatial_id);
                    self.build_sticky_frame(
                        descriptor,
                        parent_space,
                        instance_id,
                    );
                }
                SpatialTreeItem::Invalid => {
                    unreachable!();
                }
            }
        });
    }

    fn build_all(
        &mut self,
        root_pipeline_id: PipelineId,
        root_pipeline: &ScenePipeline,
    ) {
        enum ContextKind<'a> {
            Root,
            StackingContext {
                sc_info: StackingContextInfo,
            },
            ReferenceFrame,
            Iframe {
                parent_traversal: BuiltDisplayListIter<'a>,
            }
        }
        struct BuildContext<'a> {
            pipeline_id: PipelineId,
            kind: ContextKind<'a>,
        }

        self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default());

        let instance_id = self.get_next_instance_id_for_pipeline(root_pipeline_id);

        self.push_root(
            root_pipeline_id,
            instance_id,
        );
        self.build_spatial_tree_for_display_list(
            &root_pipeline.display_list.display_list,
            root_pipeline_id,
            instance_id,
        );

        let mut stack = vec![BuildContext {
            pipeline_id: root_pipeline_id,
            kind: ContextKind::Root,
        }];
        let mut traversal = root_pipeline.display_list.iter();

        'outer: while let Some(bc) = stack.pop() {
            loop {
                let item = match traversal.next() {
                    Some(item) => item,
                    None => break,
                };

                match item.item() {
                    DisplayItem::PushStackingContext(ref info) => {
                        profile_scope!("build_stacking_context");
                        let spatial_node_index = self.get_space(info.spatial_id);
                        let mut subtraversal = item.sub_iter();
                        // Avoid doing unnecessary work for empty stacking contexts.
                        // We still have to process it if it has filters, they
                        // may be things like SVGFEFlood or various specific
                        // ways to use ComponentTransfer, ColorMatrix, Composite
                        // which are still visible on an empty stacking context
                        if subtraversal.current_stacking_context_empty() && item.filters().is_empty() {
                            subtraversal.skip_current_stacking_context();
                            traversal = subtraversal;
                            continue;
                        }

                        let snapshot = info.snapshot.map(|snapshot| {
                            // Offset the snapshot area by the stacking context origin
                            // so that the area is expressed in the same coordinate space
                            // as the items in the stacking context.
                            SnapshotInfo {
                                area: snapshot.area.translate(info.origin.to_vector()),
                                .. snapshot
                            }
                        });

                        let composition_operations = CompositeOps::new(
                            filter_ops_for_compositing(item.filters()),
                            filter_datas_for_compositing(item.filter_datas()),
                            filter_primitives_for_compositing(item.filter_primitives()),
                            info.stacking_context.mix_blend_mode_for_compositing(),
                            snapshot,
                        );

                        let sc_info = self.push_stacking_context(
                            composition_operations,
                            info.stacking_context.transform_style,
                            info.prim_flags,
                            spatial_node_index,
                            info.stacking_context.clip_chain_id,
                            info.stacking_context.raster_space,
                            info.stacking_context.flags,
                            info.ref_frame_offset + info.origin.to_vector(),
                        );

                        let new_context = BuildContext {
                            pipeline_id: bc.pipeline_id,
                            kind: ContextKind::StackingContext {
                                sc_info,
                            },
                        };
                        stack.push(bc);
                        stack.push(new_context);

                        subtraversal.merge_debug_stats_from(&mut traversal);
                        traversal = subtraversal;
                        continue 'outer;
                    }
                    DisplayItem::PushReferenceFrame(..) => {
                        profile_scope!("build_reference_frame");
                        let mut subtraversal = item.sub_iter();

                        let new_context = BuildContext {
                            pipeline_id: bc.pipeline_id,
                            kind: ContextKind::ReferenceFrame,
                        };
                        stack.push(bc);
                        stack.push(new_context);

                        subtraversal.merge_debug_stats_from(&mut traversal);
                        traversal = subtraversal;
                        continue 'outer;
                    }
                    DisplayItem::PopReferenceFrame |
                    DisplayItem::PopStackingContext => break,
                    DisplayItem::Iframe(ref info) => {
                        profile_scope!("iframe");

                        let space = self.get_space(info.space_and_clip.spatial_id);
                        let subtraversal = match self.push_iframe(info, space) {
                            Some(pair) => pair,
                            None => continue,
                        };

                        let new_context = BuildContext {
                            pipeline_id: info.pipeline_id,
                            kind: ContextKind::Iframe {
                                parent_traversal: mem::replace(&mut traversal, subtraversal),
                            },
                        };
                        stack.push(bc);
                        stack.push(new_context);
                        continue 'outer;
                    }
                    _ => {
                        self.build_item(item);
                    }
                };
            }

            match bc.kind {
                ContextKind::Root => {}
                ContextKind::StackingContext { sc_info } => {
                    self.pop_stacking_context(sc_info);
                }
                ContextKind::ReferenceFrame => {
                }
                ContextKind::Iframe { parent_traversal } => {
                    self.iframe_size.pop();
                    self.clip_tree_builder.pop_clip();
                    self.clip_tree_builder.pop_clip();

                    if self.iframe_size.is_empty() {
                        assert!(self.root_iframe_clip.is_some());
                        self.root_iframe_clip = None;
                        self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
                    }

                    self.id_to_index_mapper_stack.pop().unwrap();

                    traversal = parent_traversal;
                }
            }

            // TODO: factor this out to be part of capture
            if cfg!(feature = "display_list_stats") {
                let stats = traversal.debug_stats();
                let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
                debug!("item, total count, total bytes, % of DL bytes, bytes per item");
                for (label, stats) in stats {
                    debug!("{}, {}, {}kb, {}%, {}",
                        label,
                        stats.total_count,
                        stats.num_bytes / 1000,
                        ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
                        stats.num_bytes / stats.total_count.max(1));
                }
                debug!("");
            }
        }

        debug_assert!(self.sc_stack.is_empty());

        self.id_to_index_mapper_stack.pop().unwrap();
        assert!(self.id_to_index_mapper_stack.is_empty());
    }

    fn build_sticky_frame(
        &mut self,
        info: &StickyFrameDescriptor,
        parent_node_index: SpatialNodeIndex,
        instance_id: PipelineInstanceId,
    ) {
        let external_scroll_offset = self.current_external_scroll_offset(parent_node_index);

        let sticky_frame_info = StickyFrameInfo::new(
            info.bounds.translate(external_scroll_offset),
            info.margins,
            info.vertical_offset_bounds,
            info.horizontal_offset_bounds,
            info.previously_applied_offset,
            info.transform,
        );

        let index = self.spatial_tree.add_sticky_frame(
            parent_node_index,
            sticky_frame_info,
            info.id.pipeline_id(),
            info.key,
            instance_id,
        );
        self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(info.id, index);
    }

    fn build_reference_frame(
        &mut self,
        info: &ReferenceFrameDescriptor,
        parent_space: SpatialNodeIndex,
        pipeline_id: PipelineId,
        instance_id: PipelineInstanceId,
    ) {
        let transform = match info.reference_frame.transform {
            ReferenceTransformBinding::Static { binding } => binding,
            ReferenceTransformBinding::Computed { scale_from, vertical_flip, rotation } => {
                let content_size = &self.iframe_size.last().unwrap();

                let mut transform = if let Some(scale_from) = scale_from {
                    // If we have a 90/270 degree rotation, then scale_from
                    // and content_size are in different coordinate spaces and
                    // we need to swap width/height for them to be correct.
                    match rotation {
                        Rotation::Degree0 |
                        Rotation::Degree180 => {
                            LayoutTransform::scale(
                                content_size.width / scale_from.width,
                                content_size.height / scale_from.height,
                                1.0
                            )
                        },
                        Rotation::Degree90 |
                        Rotation::Degree270 => {
                            LayoutTransform::scale(
                                content_size.height / scale_from.width,
                                content_size.width / scale_from.height,
                                1.0
                            )

                        }
                    }
                } else {
                    LayoutTransform::identity()
                };

                if vertical_flip {
                    let content_size = &self.iframe_size.last().unwrap();
                    let content_height = match rotation {
                        Rotation::Degree0 | Rotation::Degree180 => content_size.height,
                        Rotation::Degree90 | Rotation::Degree270 => content_size.width,
                    };
                    transform = transform
                        .then_translate(LayoutVector3D::new(0.0, content_height, 0.0))
                        .pre_scale(1.0, -1.0, 1.0);
                }

                let rotate = rotation.to_matrix(**content_size);
                let transform = transform.then(&rotate);

                PropertyBinding::Value(transform)
            },
        };

        let external_scroll_offset = self.current_external_scroll_offset(parent_space);

        self.push_reference_frame(
            info.reference_frame.id,
            parent_space,
            pipeline_id,
            info.reference_frame.transform_style,
            transform,
            info.reference_frame.kind,
            (info.origin + external_scroll_offset).to_vector(),
            SpatialNodeUid::external(info.reference_frame.key, pipeline_id, instance_id),
        );
    }

    fn build_scroll_frame(
        &mut self,
        info: &ScrollFrameDescriptor,
        parent_node_index: SpatialNodeIndex,
        pipeline_id: PipelineId,
        instance_id: PipelineInstanceId,
    ) {
        // This is useful when calculating scroll extents for the
        // SpatialNode::scroll(..) API as well as for properly setting sticky
        // positioning offsets.
        let content_size = info.content_rect.size();
        let external_scroll_offset = self.current_external_scroll_offset(parent_node_index);

        self.add_scroll_frame(
            info.scroll_frame_id,
            parent_node_index,
            info.external_id,
            pipeline_id,
            &info.frame_rect.translate(external_scroll_offset),
            &content_size,
            ScrollFrameKind::Explicit,
            info.external_scroll_offset,
            info.scroll_offset_generation,
            info.has_scroll_linked_effect,
            SpatialNodeUid::external(info.key, pipeline_id, instance_id),
        );
    }

    /// Advance and return the next instance id for a given pipeline id
    fn get_next_instance_id_for_pipeline(
        &mut self,
        pipeline_id: PipelineId,
    ) -> PipelineInstanceId {
        let next_instance = self.pipeline_instance_ids
            .entry(pipeline_id)
            .or_insert(0);

        let instance_id = PipelineInstanceId::new(*next_instance);
        *next_instance += 1;

        instance_id
    }

    fn push_iframe(
        &mut self,
        info: &IframeDisplayItem,
        spatial_node_index: SpatialNodeIndex,
    ) -> Option<BuiltDisplayListIter<'a>> {
        let iframe_pipeline_id = info.pipeline_id;
        let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
            Some(pipeline) => pipeline,
            None => {
                debug_assert!(info.ignore_missing_pipeline);
                return None
            },
        };

        self.clip_tree_builder.push_clip_chain(Some(info.space_and_clip.clip_chain_id), false);

        let external_scroll_offset = self.current_external_scroll_offset(spatial_node_index);

        // TODO(gw): This is the only remaining call site that relies on ClipId parenting, remove me!
        self.add_rect_clip_node(
            ClipId::root(iframe_pipeline_id),
            info.space_and_clip.spatial_id,
            &info.clip_rect,
        );

        self.clip_tree_builder.push_clip_id(ClipId::root(iframe_pipeline_id));

        let instance_id = self.get_next_instance_id_for_pipeline(iframe_pipeline_id);

        self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default());

        let mut bounds = self.snap_rect(
            &info.bounds,
            spatial_node_index,
        );

        bounds = bounds.translate(external_scroll_offset);

        let spatial_node_index = self.push_reference_frame(
            SpatialId::root_reference_frame(iframe_pipeline_id),
            spatial_node_index,
            iframe_pipeline_id,
            TransformStyle::Flat,
            PropertyBinding::Value(LayoutTransform::identity()),
            ReferenceFrameKind::Transform {
                is_2d_scale_translation: true,
                should_snap: true,
                paired_with_perspective: false,
            },
            bounds.min.to_vector(),
            SpatialNodeUid::root_reference_frame(iframe_pipeline_id, instance_id),
        );

        let iframe_rect = LayoutRect::from_size(bounds.size());
        let is_root_pipeline = self.iframe_size.is_empty();

        self.add_scroll_frame(
            SpatialId::root_scroll_node(iframe_pipeline_id),
            spatial_node_index,
            ExternalScrollId(0, iframe_pipeline_id),
            iframe_pipeline_id,
            &iframe_rect,
            &bounds.size(),
            ScrollFrameKind::PipelineRoot {
                is_root_pipeline,
            },
            LayoutVector2D::zero(),
            APZScrollGeneration::default(),
            HasScrollLinkedEffect::No,
            SpatialNodeUid::root_scroll_frame(iframe_pipeline_id, instance_id),
        );

        // If this is a root iframe, force a new tile cache both before and after
        // adding primitives for this iframe.
        if self.iframe_size.is_empty() {
            assert!(self.root_iframe_clip.is_none());
            self.root_iframe_clip = Some(ClipId::root(iframe_pipeline_id));
            self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
        }
        self.iframe_size.push(bounds.size());

        self.build_spatial_tree_for_display_list(
            &pipeline.display_list.display_list,
            iframe_pipeline_id,
            instance_id,
        );

        Some(pipeline.display_list.iter())
    }

    fn get_space(
        &self,
        spatial_id: SpatialId,
    ) -> SpatialNodeIndex {
        self.id_to_index_mapper_stack.last().unwrap().get_spatial_node_index(spatial_id)
    }

    fn get_clip_node(
        &mut self,
        clip_chain_id: api::ClipChainId,
    ) -> ClipNodeId {
        self.clip_tree_builder.build_clip_set(
            clip_chain_id,
        )
    }

    fn process_common_properties(
        &mut self,
        common: &CommonItemProperties,
        bounds: Option<LayoutRect>,
    ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) {
        let spatial_node_index = self.get_space(common.spatial_id);

        // If no bounds rect is given, default to clip rect.
        let (rect, clip_rect) = if common.flags.contains(PrimitiveFlags::ANTIALISED) {
            (bounds.unwrap_or(common.clip_rect), common.clip_rect)
        } else {
            let clip_rect = self.snap_rect(
                &common.clip_rect,
                spatial_node_index,
            );

            let rect = bounds.map_or(clip_rect, |bounds| {
                self.snap_rect(
                    &bounds,
                    spatial_node_index,
                )
            });

            (rect, clip_rect)
        };

        let current_offset = self.current_external_scroll_offset(spatial_node_index);

        let rect = rect.translate(current_offset);
        let clip_rect = clip_rect.translate(current_offset);
        let unsnapped_rect = bounds.unwrap_or(common.clip_rect).translate(current_offset);

        let clip_node_id = self.get_clip_node(
            common.clip_chain_id,
        );

        let layout = LayoutPrimitiveInfo {
            rect,
            clip_rect,
            flags: common.flags,
        };

        (layout, unsnapped_rect, spatial_node_index, clip_node_id)
    }

    fn process_common_properties_with_bounds(
        &mut self,
        common: &CommonItemProperties,
        bounds: LayoutRect,
    ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) {
        self.process_common_properties(
            common,
            Some(bounds),
        )
    }

    pub fn snap_rect(
        &mut self,
        rect: &LayoutRect,
        target_spatial_node: SpatialNodeIndex,
    ) -> LayoutRect {
        self.snap_to_device.set_target_spatial_node(
            target_spatial_node,
            self.spatial_tree,
        );
        self.snap_to_device.snap_rect(&rect)
    }

    fn build_item<'b>(
        &'b mut self,
        item: DisplayItemRef,
    ) {
        match *item.item() {
            DisplayItem::Image(ref info) => {
                profile_scope!("image");

                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                self.add_image(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                    layout.rect.size(),
                    LayoutSize::zero(),
                    info.image_key,
                    info.image_rendering,
                    info.alpha_type,
                    info.color,
                );
            }
            DisplayItem::RepeatingImage(ref info) => {
                profile_scope!("repeating_image");

                let (layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                let stretch_size = process_repeat_size(
                    &layout.rect,
                    &unsnapped_rect,
                    info.stretch_size,
                );

                self.add_image(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                    stretch_size,
                    info.tile_spacing,
                    info.image_key,
                    info.image_rendering,
                    info.alpha_type,
                    info.color,
                );
            }
            DisplayItem::YuvImage(ref info) => {
                profile_scope!("yuv_image");

                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                self.add_yuv_image(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                    info.yuv_data,
                    info.color_depth,
                    info.color_space,
                    info.color_range,
                    info.image_rendering,
                );
            }
            DisplayItem::Text(ref info) => {
                profile_scope!("text");

                // TODO(aosmond): Snapping text primitives does not make much sense, given the
                // primitive bounds and clip are supposed to be conservative, not definitive.
                // E.g. they should be able to grow and not impact the output. However there
                // are subtle interactions between the primitive origin and the glyph offset
                // which appear to be significant (presumably due to some sort of accumulated
                // error throughout the layers). We should fix this at some point.
                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                self.add_text(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                    &info.font_key,
                    &info.color,
                    item.glyphs(),
                    info.glyph_options,
                    info.ref_frame_offset,
                );
            }
            DisplayItem::Rectangle(ref info) => {
                profile_scope!("rect");

                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                self.add_primitive(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                    Vec::new(),
                    PrimitiveKeyKind::Rectangle {
                        color: info.color.into(),
                    },
                );

                if info.common.flags.contains(PrimitiveFlags::CHECKERBOARD_BACKGROUND) {
                    self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
                }
            }
            DisplayItem::HitTest(ref info) => {
                profile_scope!("hit_test");

                let spatial_node_index = self.get_space(info.spatial_id);
                let current_offset = self.current_external_scroll_offset(spatial_node_index);

                let mut rect = self.snap_rect(
                    &info.rect,
                    spatial_node_index,
                );

                rect = rect.translate(current_offset);

                let layout = LayoutPrimitiveInfo {
                    rect,
                    clip_rect: rect,
                    flags: info.flags,
                };

                let spatial_node = self.spatial_tree.get_node_info(spatial_node_index);
                let anim_id: u64 =  match spatial_node.node_type {
                    SpatialNodeType::ReferenceFrame(ReferenceFrameInfo {
                        source_transform: PropertyBinding::Binding(key, _),
                        ..
                    }) => key.clone().into(),
                    SpatialNodeType::StickyFrame(StickyFrameInfo {
                        transform: Some(PropertyBinding::Binding(key, _)),
                        ..
                    }) => key.clone().into(),
                    _ => 0,
                };

                let clip_node_id = self.get_clip_node(info.clip_chain_id);

                self.add_primitive_to_hit_testing_list(
                    &layout,
                    spatial_node_index,
                    clip_node_id,
                    info.tag,
                    anim_id,
                );
            }
            DisplayItem::ClearRectangle(ref info) => {
                profile_scope!("clear");

                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                self.add_clear_rectangle(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                );
            }
            DisplayItem::Line(ref info) => {
                profile_scope!("line");

                let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.area,
                );

                self.add_line(
                    spatial_node_index,
                    clip_node_id,
                    &layout,
                    info.wavy_line_thickness,
                    info.orientation,
                    info.color,
                    info.style,
                );
            }
            DisplayItem::Gradient(ref info) => {
                profile_scope!("gradient");

                if !info.gradient.is_valid() {
                    return;
                }

                let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
                    &info.common,
                    info.bounds,
                );

                let mut tile_size = process_repeat_size(
                    &layout.rect,
                    &unsnapped_rect,
                    info.tile_size,
                );

                let mut stops = read_gradient_stops(item.gradient_stops());
                let mut start = info.gradient.start_point;
                let mut end = info.gradient.end_point;
                let flags = layout.flags;

                let optimized = optimize_linear_gradient(
                    &mut layout.rect,
                    &mut tile_size,
                    info.tile_spacing,
                    &layout.clip_rect,
                    &mut start,
                    &mut end,
                    info.gradient.extend_mode,
                    &mut stops,
                    &mut |rect, start, end, stops, edge_aa_mask| {
                        let layout = LayoutPrimitiveInfo { rect: *rect, clip_rect: *rect, flags };
                        if let Some(prim_key_kind) = self.create_linear_gradient_prim(
                            &layout,
                            start,
                            end,
                            stops.to_vec(),
                            ExtendMode::Clamp,
                            rect.size(),
                            LayoutSize::zero(),
                            None,
                            edge_aa_mask,
                        ) {
                            self.add_nonshadowable_primitive(
                                spatial_node_index,
                                clip_node_id,
                                &layout,
                                Vec::new(),
                                prim_key_kind,
                            );
                        }
                    }
                );

                if !optimized && !tile_size.ceil().is_empty() {
--> --------------------

--> maximum size reached

--> --------------------

[ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge