Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/wr/webrender/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 84 kB image not shown  

Quelle  clip.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/. */

//! Internal representation of clips in WebRender.
//!
//! # Data structures
//!
//! There are a number of data structures involved in the clip module:
//!
//! - ClipStore - Main interface used by other modules.
//!
//! - ClipItem - A single clip item (e.g. a rounded rect, or a box shadow).
//!              These are an exposed API type, stored inline in a ClipNode.
//!
//! - ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated
//!              when a ClipNodeInstance is built from this node (which happens while
//!              preparing primitives for render).
//!
//! ClipNodeInstance - A ClipNode with attached positioning information (a spatial
//!                    node index). This is stored as a contiguous array of nodes
//!                    within the ClipStore.
//!
//! ```ascii
//! +-----------------------+-----------------------+-----------------------+
//! | ClipNodeInstance      | ClipNodeInstance      | ClipNodeInstance      |
//! +-----------------------+-----------------------+-----------------------+
//! | ClipItem              | ClipItem              | ClipItem              |
//! | Spatial Node Index    | Spatial Node Index    | Spatial Node Index    |
//! | GPU cache handle      | GPU cache handle      | GPU cache handle      |
//! | ...                   | ...                   | ...                   |
//! +-----------------------+-----------------------+-----------------------+
//!            0                        1                       2
//!    +----------------+    |                                              |
//!    | ClipNodeRange  |____|                                              |
//!    |    index: 1    |                                                   |
//!    |    count: 2    |___________________________________________________|
//!    +----------------+
//! ```
//!
//! - ClipNodeRange - A clip item range identifies a range of clip nodes instances.
//!                   It is stored as an (index, count).
//!
//! - ClipChainNode - A clip chain node contains a handle to an interned clip item,
//!                   positioning information (from where the clip was defined), and
//!                   an optional parent link to another ClipChainNode. ClipChainId
//!                   is an index into an array, or ClipChainId::NONE for no parent.
//!
//! ```ascii
//! +----------------+    ____+----------------+    ____+----------------+   /---> ClipChainId::NONE
//! | ClipChainNode  |   |    | ClipChainNode  |   |    | ClipChainNode  |   |
//! +----------------+   |    +----------------+   |    +----------------+   |
//! | ClipDataHandle |   |    | ClipDataHandle |   |    | ClipDataHandle |   |
//! | Spatial index  |   |    | Spatial index  |   |    | Spatial index  |   |
//! | Parent Id      |___|    | Parent Id      |___|    | Parent Id      |___|
//! | ...            |        | ...            |        | ...            |
//! +----------------+        +----------------+        +----------------+
//! ```
//!
//! - ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node.
//!
//!    When given a clip chain ID, and a local primitive rect and its spatial node, the clip module
//!    creates a clip chain instance. This is a struct with various pieces of useful information
//!    (such as a local clip rect). It also contains a (index, count)
//!    range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant
//!    for this clip chain instance. The index buffer structure allows a single array to be used for
//!    all of the clip-chain instances built in a single frame. Each entry in the index buffer
//!    also stores some flags relevant to the clip node in this positioning context.
//!
//! ```ascii
//! +----------------------+
//! | ClipChainInstance    |
//! +----------------------+
//! | ...                  |
//! | local_clip_rect      |________________________________________________________________________
//! | clips_range          |_______________                                                        |
//! +----------------------+              |                                                        |
//!                                       |                                                        |
//! +------------------+------------------+------------------+------------------+------------------+
//! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
//! +------------------+------------------+------------------+------------------+------------------+
//! | flags            | flags            | flags            | flags            | flags            |
//! | ...              | ...              | ...              | ...              | ...              |
//! +------------------+------------------+------------------+------------------+------------------+
//! ```
//!
//! # Rendering clipped primitives
//!
//! See the [`segment` module documentation][segment.rs].
//!
//!
//! [segment.rs]: ../segment/index.html
//!

use api::{BorderRadius, ClipMode, ImageMask, ClipId, ClipChainId};
use api::{BoxShadowClipMode, FillRule, ImageKey, ImageRendering};
use api::units::*;
use crate::image_tiling::{self, Repetition};
use crate::border::{ensure_no_corner_overlap, BorderRadiusAu};
use crate::box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::ellipse::Ellipse;
use crate::gpu_cache::GpuCache;
use crate::gpu_types::{BoxShadowStretchMode};
use crate::intern;
use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo};
use crate::prim_store::{VisibleMaskImageTile};
use crate::prim_store::{PointKey, SizeKey, RectangleKey, PolygonKey};
use crate::render_task_cache::to_cache_size;
use crate::render_task::RenderTask;
use crate::render_task_graph::RenderTaskGraphBuilder;
use crate::resource_cache::{ImageRequest, ResourceCache};
use crate::scene_builder_thread::Interners;
use crate::space::SpaceMapper;
use crate::util::{clamp_to_scale_factor, MaxRect, extract_inner_rect_safe, project_rect, ScaleOffset};
use euclid::approxeq::ApproxEq;
use std::{iter, ops, u32, mem};

/// A (non-leaf) node inside a clip-tree
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(MallocSizeOf)]
pub struct ClipTreeNode {
    pub handle: ClipDataHandle,
    pub parent: ClipNodeId,

    children: Vec<ClipNodeId>,

    // TODO(gw): Consider adding a default leaf for cases when the local_clip_rect is not relevant,
    //           that can be shared among primitives (to reduce amount of clip-chain building).
}

/// A leaf node in a clip-tree. Any primitive that is clipped will have a handle to
/// a clip-tree leaf.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(MallocSizeOf)]
pub struct ClipTreeLeaf {
    pub node_id: ClipNodeId,

    // TODO(gw): For now, this preserves the ability to build a culling rect
    //           from the supplied leaf local clip rect on the primitive. In
    //           future, we'll expand this to be more efficient by combining
    //           it will compatible clip rects from the `node_id`.
    pub local_clip_rect: LayoutRect,
}

/// ID for a ClipTreeNode
#[derive(Debug, Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipNodeId(u32);

impl ClipNodeId {
    pub const NONE: ClipNodeId = ClipNodeId(0);
}

/// ID for a ClipTreeLeaf
#[derive(Debug, Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipLeafId(u32);

/// A clip-tree built during scene building and used during frame-building to apply clips to primitives.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipTree {
    nodes: Vec<ClipTreeNode>,
    leaves: Vec<ClipTreeLeaf>,
    clip_root_stack: Vec<ClipNodeId>,
}

impl ClipTree {
    pub fn new() -> Self {
        ClipTree {
            nodes: vec![
                ClipTreeNode {
                    handle: ClipDataHandle::INVALID,
                    children: Vec::new(),
                    parent: ClipNodeId::NONE,
                }
            ],
            leaves: Vec::new(),
            clip_root_stack: vec![
                ClipNodeId::NONE,
            ],
        }
    }

    pub fn reset(&mut self) {
        self.nodes.clear();
        self.nodes.push(ClipTreeNode {
            handle: ClipDataHandle::INVALID,
            children: Vec::new(),
            parent: ClipNodeId::NONE,
        });

        self.leaves.clear();

        self.clip_root_stack.clear();
        self.clip_root_stack.push(ClipNodeId::NONE);
    }

    /// Add a set of clips to the provided tree node id, reusing existing
    /// nodes in the tree where possible
    fn add_impl(
        id: ClipNodeId,
        clips: &[ClipDataHandle],
        nodes: &mut Vec<ClipTreeNode>,
    ) -> ClipNodeId {
        if clips.is_empty() {
            return id;
        }

        let handle = clips[0];
        let next_clips = &clips[1..];

        let node_index = nodes[id.0 as usize]
            .children
            .iter()
            .find(|n| nodes[n.0 as usize].handle == handle)
            .cloned();

        let node_index = match node_index {
            Some(node_index) => node_index,
            None => {
                let node_index = ClipNodeId(nodes.len() as u32);
                nodes[id.0 as usize].children.push(node_index);
                let node = ClipTreeNode {
                    handle,
                    children: Vec::new(),
                    parent: id,
                };
                nodes.push(node);
                node_index
            }
        };

        ClipTree::add_impl(
            node_index,
            next_clips,
            nodes,
        )
    }

    /// Add a set of clips to the provided tree node id, reusing existing
    /// nodes in the tree where possible
    pub fn add(
        &mut self,
        root: ClipNodeId,
        clips: &[ClipDataHandle],
    ) -> ClipNodeId {
        ClipTree::add_impl(
            root,
            clips,
            &mut self.nodes,
        )
    }

    /// Get the current clip root (the node in the clip-tree where clips can be
    /// ignored when building the clip-chain instance for a primitive)
    pub fn current_clip_root(&self) -> ClipNodeId {
        self.clip_root_stack.last().cloned().unwrap()
    }

    /// Push a clip root (e.g. when a surface is encountered) that prevents clips
    /// from this node and above being applied to primitives within the root.
    pub fn push_clip_root_leaf(&mut self, clip_leaf_id: ClipLeafId) {
        let leaf = &self.leaves[clip_leaf_id.0 as usize];
        self.clip_root_stack.push(leaf.node_id);
    }

    /// Push a clip root (e.g. when a surface is encountered) that prevents clips
    /// from this node and above being applied to primitives within the root.
    pub fn push_clip_root_node(&mut self, clip_node_id: ClipNodeId) {
        self.clip_root_stack.push(clip_node_id);
    }

    /// Pop a clip root, when exiting a surface.
    pub fn pop_clip_root(&mut self) {
        self.clip_root_stack.pop().unwrap();
    }

    /// Retrieve a clip tree node by id
    pub fn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
        assert!(id != ClipNodeId::NONE);

        &self.nodes[id.0 as usize]
    }

    /// Retrieve a clip tree leaf by id
    pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
        &self.leaves[id.0 as usize]
    }

    /// Debug print the clip-tree
    #[allow(unused)]
    pub fn print(&self) {
        use crate::print_tree::PrintTree;

        fn print_node<T: crate::print_tree::PrintTreePrinter>(
            id: ClipNodeId,
            nodes: &[ClipTreeNode],
            pt: &mut T,
        ) {
            let node = &nodes[id.0 as usize];

            pt.new_level(format!("{:?}", id));
            pt.add_item(format!("{:?}", node.handle));

            for child_id in &node.children {
                print_node(*child_id, nodes, pt);
            }

            pt.end_level();
        }

        fn print_leaf<T: crate::print_tree::PrintTreePrinter>(
            id: ClipLeafId,
            leaves: &[ClipTreeLeaf],
            pt: &mut T,
        ) {
            let leaf = &leaves[id.0 as usize];

            pt.new_level(format!("{:?}", id));
            pt.add_item(format!("node_id: {:?}", leaf.node_id));
            pt.add_item(format!("local_clip_rect: {:?}", leaf.local_clip_rect));
            pt.end_level();
        }

        let mut pt = PrintTree::new("clip tree");
        print_node(ClipNodeId::NONE, &self.nodes, &mut pt);

        for i in 0 .. self.leaves.len() {
            print_leaf(ClipLeafId(i as u32), &self.leaves, &mut pt);
        }
    }

    /// Find the lowest common ancestor of two clip tree nodes. This is useful
    /// to identify shared clips between primitives attached to different clip-leaves.
    pub fn find_lowest_common_ancestor(
        &self,
        mut node1: ClipNodeId,
        mut node2: ClipNodeId,
    ) -> ClipNodeId {
        // TODO(gw): Consider caching / storing the depth in the node?
        fn get_node_depth(
            id: ClipNodeId,
            nodes: &[ClipTreeNode],
        ) -> usize {
            let mut depth = 0;
            let mut current = id;

            while current != ClipNodeId::NONE {
                let node = &nodes[current.0 as usize];
                depth += 1;
                current = node.parent;
            }

            depth
        }

        let mut depth1 = get_node_depth(node1, &self.nodes);
        let mut depth2 = get_node_depth(node2, &self.nodes);

        while depth1 > depth2 {
            node1 = self.nodes[node1.0 as usize].parent;
            depth1 -= 1;
        }

        while depth2 > depth1 {
            node2 = self.nodes[node2.0 as usize].parent;
            depth2 -= 1;
        }

        while node1 != node2 {
            node1 = self.nodes[node1.0 as usize].parent;
            node2 = self.nodes[node2.0 as usize].parent;
        }

        node1
    }
}

/// Represents a clip-chain as defined by the public API that we decompose in to
/// the clip-tree. In future, we would like to remove this and have Gecko directly
/// build the clip-tree.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipChain {
    parent: Option<usize>,
    clips: Vec<ClipDataHandle>,
}

#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipStackEntry {
    /// Cache the previous clip-chain build, since this is a common case
    last_clip_chain_cache: Option<(ClipChainId, ClipNodeId)>,

    /// Set of clips that were already seen and included in clip_node_id
    seen_clips: FastHashSet<ClipDataHandle>,

    /// The build clip_node_id for this level of the stack
    clip_node_id: ClipNodeId,
}

/// Used by the scene builder to build the clip-tree that is part of the built scene.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipTreeBuilder {
    /// Clips defined by the display list
    clip_map: FastHashMap<ClipId, ClipDataHandle>,

    /// Clip-chains defined by the display list
    clip_chains: Vec<ClipChain>,
    clip_chain_map: FastHashMap<ClipChainId, usize>,

    /// List of clips pushed/popped by grouping items, such as stacking contexts and iframes
    clip_stack: Vec<ClipStackEntry>,

    /// The tree we are building
    tree: ClipTree,

    /// A temporary buffer stored here to avoid constant heap allocs/frees
    clip_handles_buffer: Vec<ClipDataHandle>,
}

impl ClipTreeBuilder {
    pub fn new() -> Self {
        ClipTreeBuilder {
            clip_map: FastHashMap::default(),
            clip_chain_map: FastHashMap::default(),
            clip_chains: Vec::new(),
            clip_stack: vec![
                ClipStackEntry {
                    clip_node_id: ClipNodeId::NONE,
                    last_clip_chain_cache: None,
                    seen_clips: FastHashSet::default(),
                },
            ],
            tree: ClipTree::new(),
            clip_handles_buffer: Vec::new(),
        }
    }

    pub fn begin(&mut self) {
        self.clip_map.clear();
        self.clip_chain_map.clear();
        self.clip_chains.clear();
        self.clip_stack.clear();
        self.clip_stack.push(ClipStackEntry {
            clip_node_id: ClipNodeId::NONE,
            last_clip_chain_cache: None,
            seen_clips: FastHashSet::default(),
        });
        self.tree.reset();
        self.clip_handles_buffer.clear();
    }

    pub fn recycle_tree(&mut self, tree: ClipTree) {
        self.tree = tree;
    }

    /// Define a new rect clip
    pub fn define_rect_clip(
        &mut self,
        id: ClipId,
        handle: ClipDataHandle,
    ) {
        self.clip_map.insert(id, handle);
    }

    /// Define a new rounded rect clip
    pub fn define_rounded_rect_clip(
        &mut self,
        id: ClipId,
        handle: ClipDataHandle,
    ) {
        self.clip_map.insert(id, handle);
    }

    /// Define a image mask clip
    pub fn define_image_mask_clip(
        &mut self,
        id: ClipId,
        handle: ClipDataHandle,
    ) {
        self.clip_map.insert(id, handle);
    }

    /// Define a clip-chain
    pub fn define_clip_chain<I: Iterator<Item = ClipId>>(
        &mut self,
        id: ClipChainId,
        parent: Option<ClipChainId>,
        clips: I,
    ) {
        let parent = parent.map(|ref id| self.clip_chain_map[id]);
        let index = self.clip_chains.len();
        let clips = clips.map(|clip_id| {
            self.clip_map[&clip_id]
        }).collect();
        self.clip_chains.push(ClipChain {
            parent,
            clips,
        });
        self.clip_chain_map.insert(id, index);
    }

    /// Push a clip-chain that will be applied to any prims built prior to next pop
    pub fn push_clip_chain(
        &mut self,
        clip_chain_id: Option<ClipChainId>,
        reset_seen: bool,
    ) {
        let (mut clip_node_id, mut seen_clips) = {
            let prev = self.clip_stack.last().unwrap();
            (prev.clip_node_id, prev.seen_clips.clone())
        };

        if let Some(clip_chain_id) = clip_chain_id {
            if clip_chain_id != ClipChainId::INVALID {
                self.clip_handles_buffer.clear();

                let clip_chain_index = self.clip_chain_map[&clip_chain_id];
                ClipTreeBuilder::add_clips(
                    clip_chain_index,
                    &mut seen_clips,
                    &mut self.clip_handles_buffer,
                    &self.clip_chains,
                );

                clip_node_id = self.tree.add(
                    clip_node_id,
                    &self.clip_handles_buffer,
                );
            }
        }

        if reset_seen {
            seen_clips.clear();
        }

        self.clip_stack.push(ClipStackEntry {
            last_clip_chain_cache: None,
            clip_node_id,
            seen_clips,
        });
    }

    /// Push a clip-id that will be applied to any prims built prior to next pop
    pub fn push_clip_id(
        &mut self,
        clip_id: ClipId,
    ) {
        let (clip_node_id, mut seen_clips) = {
            let prev = self.clip_stack.last().unwrap();
            (prev.clip_node_id, prev.seen_clips.clone())
        };

        self.clip_handles_buffer.clear();
        let clip_index = self.clip_map[&clip_id];

        if seen_clips.insert(clip_index) {
            self.clip_handles_buffer.push(clip_index);
        }

        let clip_node_id = self.tree.add(
            clip_node_id,
            &self.clip_handles_buffer,
        );

        self.clip_stack.push(ClipStackEntry {
            last_clip_chain_cache: None,
            seen_clips,
            clip_node_id,
        });
    }

    /// Pop a clip off the clip_stack, when exiting a grouping item
    pub fn pop_clip(&mut self) {
        self.clip_stack.pop().unwrap();
    }

    /// Add clips from a given clip-chain to the set of clips for a primitive during clip-set building
    fn add_clips(
        clip_chain_index: usize,
        seen_clips: &mut FastHashSet<ClipDataHandle>,
        output: &mut Vec<ClipDataHandle>,
        clip_chains: &[ClipChain],
    ) {
        // TODO(gw): It's possible that we may see clip outputs that include identical clips
        //           (e.g. if there is a clip positioned by two spatial nodes, where one spatial
        //           node is a child of the other, and has an identity transform). If we ever
        //           see this in real-world cases, it might be worth checking for that here and
        //           excluding them, to ensure the shape of the tree matches what we need for
        //           finding shared_clips for tile caches etc.

        let clip_chain = &clip_chains[clip_chain_index];

        if let Some(parent) = clip_chain.parent {
            ClipTreeBuilder::add_clips(
                parent,
                seen_clips,
                output,
                clip_chains,
            );
        }

        for clip_index in clip_chain.clips.iter().rev() {
            if seen_clips.insert(*clip_index) {
                output.push(*clip_index);
            }
        }
    }

    /// Main entry point to build a path in the clip-tree for a given primitive
    pub fn build_clip_set(
        &mut self,
        clip_chain_id: ClipChainId,
    ) -> ClipNodeId {
        let clip_stack = self.clip_stack.last_mut().unwrap();

        if clip_chain_id == ClipChainId::INVALID {
            clip_stack.clip_node_id
        } else {
            if let Some((cached_clip_chain, cached_clip_node)) = clip_stack.last_clip_chain_cache {
                if cached_clip_chain == clip_chain_id {
                    return cached_clip_node;
                }
            }

            let clip_chain_index = self.clip_chain_map[&clip_chain_id];

            self.clip_handles_buffer.clear();

            ClipTreeBuilder::add_clips(
                clip_chain_index,
                &mut clip_stack.seen_clips,
                &mut self.clip_handles_buffer,
                &self.clip_chains,
            );

            // We mutated the `clip_stack.seen_clips` in order to remove duplicate clips from
            // the supplied `clip_chain_id`. Now step through and remove any clips we added
            // to the set, so we don't get incorrect results next time `build_clip_set` is
            // called for a different clip-chain. Doing it this way rather than cloning means
            // we avoid heap allocations for each `build_clip_set` call.
            for handle in &self.clip_handles_buffer {
                clip_stack.seen_clips.remove(handle);
            }

            let clip_node_id = self.tree.add(
                clip_stack.clip_node_id,
                &self.clip_handles_buffer,
            );

            clip_stack.last_clip_chain_cache = Some((clip_chain_id, clip_node_id));

            clip_node_id
        }
    }

    /// Recursive impl to check if a clip-chain has complex (non-rectangular) clips
    fn has_complex_clips_impl(
        &self,
        clip_chain_index: usize,
        interners: &Interners,
    ) -> bool {
        let clip_chain = &self.clip_chains[clip_chain_index];

        for clip_handle in &clip_chain.clips {
            let clip_info = &interners.clip[*clip_handle];

            if let ClipNodeKind::Complex = clip_info.key.kind.node_kind() {
                return true;
            }
        }

        match clip_chain.parent {
            Some(parent) => self.has_complex_clips_impl(parent, interners),
            None => false,
        }
    }

    /// Check if a clip-chain has complex (non-rectangular) clips
    pub fn clip_chain_has_complex_clips(
        &self,
        clip_chain_id: ClipChainId,
        interners: &Interners,
    ) -> bool {
        let clip_chain_index = self.clip_chain_map[&clip_chain_id];
        self.has_complex_clips_impl(clip_chain_index, interners)
    }

    /// Check if a clip-node has complex (non-rectangular) clips
    pub fn clip_node_has_complex_clips(
        &self,
        clip_node_id: ClipNodeId,
        interners: &Interners,
    ) -> bool {
        let mut current = clip_node_id;

        while current != ClipNodeId::NONE {
            let node = &self.tree.nodes[current.0 as usize];
            let clip_info = &interners.clip[node.handle];

            if let ClipNodeKind::Complex = clip_info.key.kind.node_kind() {
                return true;
            }

            current = node.parent;
        }

        false
    }

    /// Finalize building and return the clip-tree
    pub fn finalize(&mut self) -> ClipTree {
        // Note: After this, the builder's clip tree does not hold allocations and
        // is not in valid state. `ClipTreeBuilder::begin()` must be called before
        // building can happen again.
        std::mem::replace(&mut self.tree, ClipTree {
            nodes: Vec::new(),
            leaves: Vec::new(),
            clip_root_stack: Vec::new(),
        })
    }

    /// Get a clip node by id
    pub fn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
        assert!(id != ClipNodeId::NONE);

        &self.tree.nodes[id.0 as usize]
    }

    /// Get a clip leaf by id
    pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
        &self.tree.leaves[id.0 as usize]
    }

    /// Build a clip-leaf for a tile-cache
    pub fn build_for_tile_cache(
        &mut self,
        clip_node_id: ClipNodeId,
        extra_clips: &[ClipId],
    ) -> ClipLeafId {
        self.clip_handles_buffer.clear();

        for clip_id in extra_clips {
            let handle = self.clip_map[clip_id];
            self.clip_handles_buffer.push(handle);
        }

        let node_id = self.tree.add(
            clip_node_id,
            &self.clip_handles_buffer,
        );

        let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);

        self.tree.leaves.push(ClipTreeLeaf {
            node_id,
            local_clip_rect: LayoutRect::max_rect(),
        });

        clip_leaf_id
    }

    /// Build a clip-leaf for a picture
    pub fn build_for_picture(
        &mut self,
        clip_node_id: ClipNodeId,
    ) -> ClipLeafId {
        let node_id = self.tree.add(
            clip_node_id,
            &[],
        );

        let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);

        self.tree.leaves.push(ClipTreeLeaf {
            node_id,
            local_clip_rect: LayoutRect::max_rect(),
        });

        clip_leaf_id
    }

    /// Build a clip-leaf for a normal primitive
    pub fn build_for_prim(
        &mut self,
        clip_node_id: ClipNodeId,
        info: &LayoutPrimitiveInfo,
        extra_clips: &[ClipItemKey],
        interners: &mut Interners,
    ) -> ClipLeafId {

        let node_id = if extra_clips.is_empty() {
            clip_node_id
        } else {
            // TODO(gw): Cache the previous build of clip-node / clip-leaf to handle cases where we get a
            //           lot of primitives referencing the same clip set (e.g. dl_mutate and similar tests)
            self.clip_handles_buffer.clear();

            for item in extra_clips {
                // Intern this clip item, and store the handle
                // in the clip chain node.
                let handle = interners.clip.intern(item, || {
                    ClipInternData {
                        key: item.clone(),
                    }
                });

                self.clip_handles_buffer.push(handle);
            }

            self.tree.add(
                clip_node_id,
                &self.clip_handles_buffer,
            )
        };

        let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);

        self.tree.leaves.push(ClipTreeLeaf {
            node_id,
            local_clip_rect: info.clip_rect,
        });

        clip_leaf_id
    }

    // Find the LCA for two given clip nodes
    pub fn find_lowest_common_ancestor(
        &self,
        node1: ClipNodeId,
        node2: ClipNodeId,
    ) -> ClipNodeId {
        self.tree.find_lowest_common_ancestor(node1, node2)
    }
}

// Type definitions for interning clip nodes.

#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, Eq, Hash)]
#[cfg_attr(any(feature = "serde"), derive(Deserialize, Serialize))]
pub enum ClipIntern {}

pub type ClipDataStore = intern::DataStore<ClipIntern>;
pub type ClipDataHandle = intern::Handle<ClipIntern>;

/// Helper to identify simple clips (normal rects) from other kinds of clips,
/// which can often be handled via fast code paths.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Copy, Clone, MallocSizeOf)]
pub enum ClipNodeKind {
    /// A normal clip rectangle, with Clip mode.
    Rectangle,
    /// A rectangle with ClipOut, or any other kind of clip.
    Complex,
}

// Result of comparing a clip node instance against a local rect.
#[derive(Debug)]
enum ClipResult {
    // The clip does not affect the region at all.
    Accept,
    // The clip prevents the region from being drawn.
    Reject,
    // The clip affects part of the region. This may
    // require a clip mask, depending on other factors.
    Partial,
}

// A clip node is a single clip source, along with some
// positioning information and implementation details
// that control where the GPU data for this clip source
// can be found.
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(MallocSizeOf)]
pub struct ClipNode {
    pub item: ClipItem,
}

// Convert from an interning key for a clip item
// to a clip node, which is cached in the document.
impl From<ClipItemKey> for ClipNode {
    fn from(item: ClipItemKey) -> Self {
        let kind = match item.kind {
            ClipItemKeyKind::Rectangle(rect, mode) => {
                ClipItemKind::Rectangle { rect: rect.into(), mode }
            }
            ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => {
                ClipItemKind::RoundedRectangle {
                    rect: rect.into(),
                    radius: radius.into(),
                    mode,
                }
            }
            ClipItemKeyKind::ImageMask(rect, image, polygon_handle) => {
                ClipItemKind::Image {
                    image,
                    rect: rect.into(),
                    polygon_handle,
                }
            }
            ClipItemKeyKind::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
                ClipItemKind::new_box_shadow(
                    shadow_rect_fract_offset.into(),
                    shadow_rect_size.into(),
                    shadow_radius.into(),
                    prim_shadow_rect.into(),
                    blur_radius.to_f32_px(),
                    clip_mode,
                )
            }
        };

        ClipNode {
            item: ClipItem {
                kind,
                spatial_node_index: item.spatial_node_index,
            },
        }
    }
}

// Flags that are attached to instances of clip nodes.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, MallocSizeOf)]
pub struct ClipNodeFlags(u8);

bitflags! {
    impl ClipNodeFlags : u8 {
        const SAME_SPATIAL_NODE = 0x1;
        const SAME_COORD_SYSTEM = 0x2;
        const USE_FAST_PATH = 0x4;
    }
}

impl core::fmt::Debug for ClipNodeFlags {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        if self.is_empty() {
            write!(f, "{:#x}", Self::empty().bits())
        } else {
            bitflags::parser::to_writer(self, f)
        }
    }
}

// When a clip node is found to be valid for a
// clip chain instance, it's stored in an index
// buffer style structure. This struct contains
// an index to the node data itself, as well as
// some flags describing how this clip node instance
// is positioned.
#[derive(Debug, Clone, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipNodeInstance {
    pub handle: ClipDataHandle,
    pub flags: ClipNodeFlags,
    pub visible_tiles: Option<ops::Range<usize>>,
}

impl ClipNodeInstance {
    pub fn has_visible_tiles(&self) -> bool {
        self.visible_tiles.is_some()
    }
}

// A range of clip node instances that were found by
// building a clip chain instance.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipNodeRange {
    pub first: u32,
    pub count: u32,
}

impl ClipNodeRange {
    pub fn to_range(&self) -> ops::Range<usize> {
        let start = self.first as usize;
        let end = start + self.count as usize;

        ops::Range {
            start,
            end,
        }
    }
}

/// A helper struct for converting between coordinate systems
/// of clip sources and primitives.
// todo(gw): optimize:
//  separate arrays for matrices
//  cache and only build as needed.
//TODO: merge with `CoordinateSpaceMapping`?
#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub enum ClipSpaceConversion {
    Local,
    ScaleOffset(ScaleOffset),
    Transform(LayoutToWorldTransform),
}

impl ClipSpaceConversion {
    /// Construct a new clip space converter between two spatial nodes.
    pub fn new(
        prim_spatial_node_index: SpatialNodeIndex,
        clip_spatial_node_index: SpatialNodeIndex,
        spatial_tree: &SpatialTree,
    ) -> Self {
        //Note: this code is different from `get_relative_transform` in a way that we only try
        // getting the relative transform if it's Local or ScaleOffset,
        // falling back to the world transform otherwise.
        let clip_spatial_node = spatial_tree.get_spatial_node(clip_spatial_node_index);
        let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index);

        if prim_spatial_node_index == clip_spatial_node_index {
            ClipSpaceConversion::Local
        } else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
            let scale_offset = clip_spatial_node.content_transform
                .then(&prim_spatial_node.content_transform.inverse());
            ClipSpaceConversion::ScaleOffset(scale_offset)
        } else {
            ClipSpaceConversion::Transform(
                spatial_tree
                    .get_world_transform(clip_spatial_node_index)
                    .into_transform()
            )
        }
    }

    fn to_flags(&self) -> ClipNodeFlags {
        match *self {
            ClipSpaceConversion::Local => {
                ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
            }
            ClipSpaceConversion::ScaleOffset(..) => {
                ClipNodeFlags::SAME_COORD_SYSTEM
            }
            ClipSpaceConversion::Transform(..) => {
                ClipNodeFlags::empty()
            }
        }
    }
}

// Temporary information that is cached and reused
// during building of a clip chain instance.
#[derive(MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
struct ClipNodeInfo {
    conversion: ClipSpaceConversion,
    handle: ClipDataHandle,
}

impl ClipNodeInfo {
    fn create_instance(
        &self,
        node: &ClipNode,
        clipped_rect: &LayoutRect,
        gpu_cache: &mut GpuCache,
        resource_cache: &mut ResourceCache,
        mask_tiles: &mut Vec<VisibleMaskImageTile>,
        spatial_tree: &SpatialTree,
        rg_builder: &mut RenderTaskGraphBuilder,
        request_resources: bool,
    ) -> Option<ClipNodeInstance> {
        // Calculate some flags that are required for the segment
        // building logic.
        let mut flags = self.conversion.to_flags();

        // Some clip shaders support a fast path mode for simple clips.
        // TODO(gw): We could also apply fast path when segments are created, since we only write
        //           the mask for a single corner at a time then, so can always consider radii uniform.
        let is_raster_2d =
            flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
            spatial_tree
                .get_world_viewport_transform(node.item.spatial_node_index)
                .is_2d_axis_aligned();
        if is_raster_2d && node.item.kind.supports_fast_path_rendering() {
            flags |= ClipNodeFlags::USE_FAST_PATH;
        }

        let mut visible_tiles = None;

        if let ClipItemKind::Image { rect, image, .. } = node.item.kind {
            let request = ImageRequest {
                key: image,
                rendering: ImageRendering::Auto,
                tile: None,
            };

            if let Some(props) = resource_cache.get_image_properties(image) {
                if let Some(tile_size) = props.tiling {
                    let tile_range_start = mask_tiles.len();

                    // Bug 1648323 - It is unclear why on rare occasions we get
                    // a clipped_rect that does not intersect the clip's mask rect.
                    // defaulting to clipped_rect here results in zero repetitions
                    // which clips the primitive entirely.
                    let visible_rect =
                        clipped_rect.intersection(&rect).unwrap_or(*clipped_rect);

                    let repetitions = image_tiling::repetitions(
                        &rect,
                        &visible_rect,
                        rect.size(),
                    );

                    for Repetition { origin, .. } in repetitions {
                        let layout_image_rect = LayoutRect::from_origin_and_size(
                            origin,
                            rect.size(),
                        );
                        let tiles = image_tiling::tiles(
                            &layout_image_rect,
                            &visible_rect,
                            &props.visible_rect,
                            tile_size as i32,
                        );
                        for tile in tiles {
                            let req = request.with_tile(tile.offset);

                            if request_resources {
                                resource_cache.request_image(
                                    req,
                                    gpu_cache,
                                );
                            }

                            let task_id = rg_builder.add().init(
                                RenderTask::new_image(props.descriptor.size, req)
                            );

                            mask_tiles.push(VisibleMaskImageTile {
                                tile_offset: tile.offset,
                                tile_rect: tile.rect,
                                task_id,
                            });
                        }
                    }
                    visible_tiles = Some(tile_range_start..mask_tiles.len());
                } else {
                    if request_resources {
                        resource_cache.request_image(request, gpu_cache);
                    }

                    let tile_range_start = mask_tiles.len();

                    let task_id = rg_builder.add().init(
                        RenderTask::new_image(props.descriptor.size, request)
                    );

                    mask_tiles.push(VisibleMaskImageTile {
                        tile_rect: rect,
                        tile_offset: TileOffset::zero(),
                        task_id,
                    });

                    visible_tiles = Some(tile_range_start .. mask_tiles.len());
                }
            } else {
                // If the supplied image key doesn't exist in the resource cache,
                // skip the clip node since there is nothing to mask with.
                warn!("Clip mask with missing image key {:?}", request.key);
                return None;
            }
        }

        Some(ClipNodeInstance {
            handle: self.handle,
            flags,
            visible_tiles,
        })
    }
}

impl ClipNode {
    pub fn update(
        &mut self,
        device_pixel_scale: DevicePixelScale,
    ) {
        match self.item.kind {
            ClipItemKind::Image { .. } |
            ClipItemKind::Rectangle { .. } |
            ClipItemKind::RoundedRectangle { .. } => {}

            ClipItemKind::BoxShadow { ref mut source } => {
                // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                // "the image that would be generated by applying to the shadow a
                // Gaussian blur with a standard deviation equal to half the blur radius."
                let blur_radius_dp = source.blur_radius * 0.5;

                // Create scaling from requested size to cache size.
                let mut content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
                content_scale.0 = clamp_to_scale_factor(content_scale.0, false);

                // Create the cache key for this box-shadow render task.
                let cache_size = to_cache_size(source.shadow_rect_alloc_size, &mut content_scale);

                let bs_cache_key = BoxShadowCacheKey {
                    blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
                    clip_mode: source.clip_mode,
                    original_alloc_size: (source.original_alloc_size * content_scale).round().to_i32(),
                    br_top_left: (source.shadow_radius.top_left * content_scale).round().to_i32(),
                    br_top_right: (source.shadow_radius.top_right * content_scale).round().to_i32(),
                    br_bottom_right: (source.shadow_radius.bottom_right * content_scale).round().to_i32(),
                    br_bottom_left: (source.shadow_radius.bottom_left * content_scale).round().to_i32(),
                    device_pixel_scale: Au::from_f32_px(content_scale.0),
                };

                source.cache_key = Some((cache_size, bs_cache_key));
            }
        }
    }
}

#[derive(Default)]
pub struct ClipStoreScratchBuffer {
    clip_node_instances: Vec<ClipNodeInstance>,
    mask_tiles: Vec<VisibleMaskImageTile>,
}

/// The main clipping public interface that other modules access.
#[derive(MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClipStore {
    pub clip_node_instances: Vec<ClipNodeInstance>,
    mask_tiles: Vec<VisibleMaskImageTile>,

    active_clip_node_info: Vec<ClipNodeInfo>,
    active_local_clip_rect: Option<LayoutRect>,
    active_pic_coverage_rect: PictureRect,
}

// A clip chain instance is what gets built for a given clip
// chain id + local primitive region + positioning node.
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClipChainInstance {
    pub clips_range: ClipNodeRange,
    // Combined clip rect for clips that are in the
    // same coordinate system as the primitive.
    pub local_clip_rect: LayoutRect,
    pub has_non_local_clips: bool,
    // If true, this clip chain requires allocation
    // of a clip mask.
    pub needs_mask: bool,
    // Combined clip rect in picture space (may
    // be more conservative that local_clip_rect).
    pub pic_coverage_rect: PictureRect,
    // Space, in which the `pic_coverage_rect` is defined.
    pub pic_spatial_node_index: SpatialNodeIndex,
}

impl ClipChainInstance {
    pub fn empty() -> Self {
        ClipChainInstance {
            clips_range: ClipNodeRange {
                first: 0,
                count: 0,
            },
            local_clip_rect: LayoutRect::zero(),
            has_non_local_clips: false,
            needs_mask: false,
            pic_coverage_rect: PictureRect::zero(),
            pic_spatial_node_index: SpatialNodeIndex::INVALID,
        }
    }
}

impl ClipStore {
    pub fn new() -> Self {
        ClipStore {
            clip_node_instances: Vec::new(),
            mask_tiles: Vec::new(),
            active_clip_node_info: Vec::new(),
            active_local_clip_rect: None,
            active_pic_coverage_rect: PictureRect::max_rect(),
        }
    }

    pub fn reset(&mut self) {
        self.clip_node_instances.clear();
        self.mask_tiles.clear();
        self.active_clip_node_info.clear();
        self.active_local_clip_rect = None;
        self.active_pic_coverage_rect = PictureRect::max_rect();
    }

    pub fn get_instance_from_range(
        &self,
        node_range: &ClipNodeRange,
        index: u32,
    ) -> &ClipNodeInstance {
        &self.clip_node_instances[(node_range.first + index) as usize]
    }

    /// Setup the active clip chains for building a clip chain instance.
    pub fn set_active_clips(
        &mut self,
        prim_spatial_node_index: SpatialNodeIndex,
        pic_spatial_node_index: SpatialNodeIndex,
        clip_leaf_id: ClipLeafId,
        spatial_tree: &SpatialTree,
        clip_data_store: &ClipDataStore,
        clip_tree: &ClipTree,
    ) {
        self.active_clip_node_info.clear();
        self.active_local_clip_rect = None;
        self.active_pic_coverage_rect = PictureRect::max_rect();

        let clip_root = clip_tree.current_clip_root();
        let clip_leaf = clip_tree.get_leaf(clip_leaf_id);

        let mut local_clip_rect = clip_leaf.local_clip_rect;
        let mut current = clip_leaf.node_id;

        while current != clip_root {
            let node = clip_tree.get_node(current);

            if !add_clip_node_to_current_chain(
                node.handle,
                prim_spatial_node_index,
                pic_spatial_node_index,
                &mut local_clip_rect,
                &mut self.active_clip_node_info,
                &mut self.active_pic_coverage_rect,
                clip_data_store,
                spatial_tree,
            ) {
                return;
            }

            current = node.parent;
        }

        self.active_local_clip_rect = Some(local_clip_rect);
    }

    /// Setup the active clip chains, based on an existing primitive clip chain instance.
    pub fn set_active_clips_from_clip_chain(
        &mut self,
        prim_clip_chain: &ClipChainInstance,
        prim_spatial_node_index: SpatialNodeIndex,
        spatial_tree: &SpatialTree,
        clip_data_store: &ClipDataStore,
    ) {
        // TODO(gw): Although this does less work than set_active_clips(), it does
        //           still do some unnecessary work (such as the clip space conversion).
        //           We could consider optimizing this if it ever shows up in a profile.

        self.active_clip_node_info.clear();
        self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect);
        self.active_pic_coverage_rect = prim_clip_chain.pic_coverage_rect;

        let clip_instances = &self
            .clip_node_instances[prim_clip_chain.clips_range.to_range()];
        for clip_instance in clip_instances {
            let clip = &clip_data_store[clip_instance.handle];
            let conversion = ClipSpaceConversion::new(
                prim_spatial_node_index,
                clip.item.spatial_node_index,
                spatial_tree,
            );
            self.active_clip_node_info.push(ClipNodeInfo {
                handle: clip_instance.handle,
                conversion,
            });
        }
    }

    /// Given a clip-chain instance, return a safe rect within the visible region
    /// that can be assumed to be unaffected by clip radii. Returns None if it
    /// encounters any complex cases, just handling rounded rects in the same
    /// coordinate system as the clip-chain for now.
    pub fn get_inner_rect_for_clip_chain(
        &self,
        clip_chain: &ClipChainInstance,
        clip_data_store: &ClipDataStore,
        spatial_tree: &SpatialTree,
    ) -> Option<PictureRect> {
        let mut inner_rect = clip_chain.pic_coverage_rect;
        let clip_instances = &self
            .clip_node_instances[clip_chain.clips_range.to_range()];

        for clip_instance in clip_instances {
            // Don't handle mapping between coord systems for now
            if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
                return None;
            }

            let clip_node = &clip_data_store[clip_instance.handle];

            match clip_node.item.kind {
                // Ignore any clips which are complex or impossible to calculate
                // inner rects for now
                ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
                ClipItemKind::Image { .. } |
                ClipItemKind::BoxShadow { .. } |
                ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => {
                    return None;
                }
                // Normal Clip rects are already handled by the clip-chain pic_coverage_rect,
                // no need to do anything here
                ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {}
                ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, rect, radius } => {
                    // Get an inner rect for the rounded-rect clip
                    let local_inner_rect = match extract_inner_rect_safe(&rect, &radius) {
                        Some(rect) => rect,
                        None => return None,
                    };

                    // Map it from local -> picture space
                    let mapper = SpaceMapper::new_with_target(
                        clip_chain.pic_spatial_node_index,
                        clip_node.item.spatial_node_index,
                        PictureRect::max_rect(),
                        spatial_tree,
                    );

                    // Accumulate in to the inner_rect, in case there are multiple rounded-rect clips
                    if let Some(pic_inner_rect) = mapper.map(&local_inner_rect) {
                        inner_rect = inner_rect.intersection(&pic_inner_rect).unwrap_or(PictureRect::zero());
                    }
                }
            }
        }

        Some(inner_rect)
    }

    // Directly construct a clip node range, ready for rendering, from an interned clip handle.
    // Typically useful for drawing specific clips on custom pattern / child render tasks that
    // aren't primitives.
    // TODO(gw): For now, we assume they are local clips only - in future we might want to support
    //           non-local clips.
    pub fn push_clip_instance(
        &mut self,
        handle: ClipDataHandle,
    ) -> ClipNodeRange {
        let first = self.clip_node_instances.len() as u32;

        self.clip_node_instances.push(ClipNodeInstance {
            handle,
            flags: ClipNodeFlags::SAME_COORD_SYSTEM | ClipNodeFlags::SAME_SPATIAL_NODE,
            visible_tiles: None,
        });

        ClipNodeRange {
            first,
            count: 1,
        }
    }

    /// The main interface external code uses. Given a local primitive, positioning
    /// information, and a clip chain id, build an optimized clip chain instance.
    pub fn build_clip_chain_instance(
        &mut self,
        local_prim_rect: LayoutRect,
        prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
        pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
        spatial_tree: &SpatialTree,
        gpu_cache: &mut GpuCache,
        resource_cache: &mut ResourceCache,
        device_pixel_scale: DevicePixelScale,
        world_rect: &WorldRect,
        clip_data_store: &mut ClipDataStore,
        rg_builder: &mut RenderTaskGraphBuilder,
        request_resources: bool,
    ) -> Option<ClipChainInstance> {
        let local_clip_rect = match self.active_local_clip_rect {
            Some(rect) => rect,
            None => return None,
        };
        profile_scope!("build_clip_chain_instance");

        let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
        let mut pic_coverage_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
        let world_clip_rect = pic_to_world_mapper.map(&pic_coverage_rect)?;

        // Now, we've collected all the clip nodes that *potentially* affect this
        // primitive region, and reduced the size of the prim region as much as possible.

        // Run through the clip nodes, and see which ones affect this prim region.

        let first_clip_node_index = self.clip_node_instances.len() as u32;
        let mut has_non_local_clips = false;
        let mut needs_mask = false;

        // For each potential clip node
        for node_info in self.active_clip_node_info.drain(..) {
            let node = &mut clip_data_store[node_info.handle];

            // See how this clip affects the prim region.
            let clip_result = match node_info.conversion {
                ClipSpaceConversion::Local => {
                    node.item.kind.get_clip_result(&local_bounding_rect)
                }
                ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
                    has_non_local_clips = true;
                    node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect))
                }
                ClipSpaceConversion::Transform(ref transform) => {
                    has_non_local_clips = true;
                    node.item.kind.get_clip_result_complex(
                        transform,
                        &world_clip_rect,
                        world_rect,
                    )
                }
            };

            match clip_result {
                ClipResult::Accept => {
                    // Doesn't affect the primitive at all, so skip adding to list
                }
                ClipResult::Reject => {
                    // Completely clips the supplied prim rect
                    return None;
                }
                ClipResult::Partial => {
                    // Needs a mask -> add to clip node indices

                    // TODO(gw): Ensure this only runs once on each node per frame?
                    node.update(device_pixel_scale);

                    // Create the clip node instance for this clip node
                    if let Some(instance) = node_info.create_instance(
                        node,
                        &local_bounding_rect,
                        gpu_cache,
                        resource_cache,
                        &mut self.mask_tiles,
                        spatial_tree,
                        rg_builder,
                        request_resources,
                    ) {
                        // As a special case, a partial accept of a clip rect that is
                        // in the same coordinate system as the primitive doesn't need
                        // a clip mask. Instead, it can be handled by the primitive
                        // vertex shader as part of the local clip rect. This is an
                        // important optimization for reducing the number of clip
                        // masks that are allocated on common pages.
                        needs_mask |= match node.item.kind {
                            ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
                            ClipItemKind::RoundedRectangle { .. } |
                            ClipItemKind::Image { .. } |
                            ClipItemKind::BoxShadow { .. } => {
                                true
                            }

                            ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {
                                !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
                            }
                        };

                        // Store this in the index buffer for this clip chain instance.
                        self.clip_node_instances.push(instance);
                    }
                }
            }
        }

        // Get the range identifying the clip nodes in the index buffer.
        let clips_range = ClipNodeRange {
            first: first_clip_node_index,
            count: self.clip_node_instances.len() as u32 - first_clip_node_index,
        };

        // If this clip chain needs a mask, reduce the size of the mask allocation
        // by any clips that were in the same space as the picture. This can result
        // in much smaller clip mask allocations in some cases. Note that the ordering
        // here is important - the reduction must occur *after* the clip item accept
        // reject checks above, so that we don't eliminate masks accidentally (since
        // we currently only support a local clip rect in the vertex shader).
        if needs_mask {
            pic_coverage_rect = pic_coverage_rect.intersection(&self.active_pic_coverage_rect)?;
        }

        // Return a valid clip chain instance
        Some(ClipChainInstance {
            clips_range,
            has_non_local_clips,
            local_clip_rect,
            pic_coverage_rect,
            pic_spatial_node_index: prim_to_pic_mapper.ref_spatial_node_index,
            needs_mask,
        })
    }

    pub fn begin_frame(&mut self, scratch: &mut ClipStoreScratchBuffer) {
        mem::swap(&mut self.clip_node_instances, &mut scratch.clip_node_instances);
        mem::swap(&mut self.mask_tiles, &mut scratch.mask_tiles);
        self.clip_node_instances.clear();
        self.mask_tiles.clear();
    }

    pub fn end_frame(&mut self, scratch: &mut ClipStoreScratchBuffer) {
        mem::swap(&mut self.clip_node_instances, &mut scratch.clip_node_instances);
        mem::swap(&mut self.mask_tiles, &mut scratch.mask_tiles);
    }

    pub fn visible_mask_tiles(&self, instance: &ClipNodeInstance) -> &[VisibleMaskImageTile] {
        if let Some(range) = &instance.visible_tiles {
            &self.mask_tiles[range.clone()]
        } else {
            &[]
        }
    }
}

impl Default for ClipStore {
    fn default() -> Self {
        ClipStore::new()
    }
}

// The ClipItemKey is a hashable representation of the contents
// of a clip item. It is used during interning to de-duplicate
// clip nodes between frames and display lists. This allows quick
// comparison of clip node equality by handle, and also allows
// the uploaded GPU cache handle to be retained between display lists.
// TODO(gw): Maybe we should consider constructing these directly
//           in the DL builder?
#[derive(Copy, Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum ClipItemKeyKind {
    Rectangle(RectangleKey, ClipMode),
    RoundedRectangle(RectangleKey, BorderRadiusAu, ClipMode),
    ImageMask(RectangleKey, ImageKey, Option<PolygonDataHandle>),
    BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode),
}

impl ClipItemKeyKind {
    pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self {
        ClipItemKeyKind::Rectangle(rect.into(), mode)
    }

    pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self {
        if radii.is_zero() {
            ClipItemKeyKind::rectangle(rect, mode)
        } else {
            ensure_no_corner_overlap(&mut radii, rect.size());
            ClipItemKeyKind::RoundedRectangle(
                rect.into(),
                radii.into(),
                mode,
            )
        }
    }

    pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect,
                      polygon_handle: Option<PolygonDataHandle>) -> Self {
        ClipItemKeyKind::ImageMask(
            mask_rect.into(),
            image_mask.image,
            polygon_handle,
        )
    }

    pub fn box_shadow(
        shadow_rect: LayoutRect,
        shadow_radius: BorderRadius,
        prim_shadow_rect: LayoutRect,
        blur_radius: f32,
        clip_mode: BoxShadowClipMode,
    ) -> Self {
        // Get the fractional offsets required to match the
        // source rect with a minimal rect.
        let fract_offset = LayoutPoint::new(
            shadow_rect.min.x.fract().abs(),
            shadow_rect.min.y.fract().abs(),
        );

        ClipItemKeyKind::BoxShadow(
            fract_offset.into(),
            shadow_rect.size().into(),
            shadow_radius.into(),
            prim_shadow_rect.into(),
            Au::from_f32_px(blur_radius),
            clip_mode,
        )
    }

    pub fn node_kind(&self) -> ClipNodeKind {
        match *self {
            ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => ClipNodeKind::Rectangle,

            ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
            ClipItemKeyKind::RoundedRectangle(..) |
            ClipItemKeyKind::ImageMask(..) |
            ClipItemKeyKind::BoxShadow(..) => ClipNodeKind::Complex,
        }
    }
}

#[derive(Debug, Copy, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipItemKey {
    pub kind: ClipItemKeyKind,
    pub spatial_node_index: SpatialNodeIndex,
}

/// The data available about an interned clip node during scene building
#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipInternData {
    pub key: ClipItemKey,
}

impl intern::InternDebug for ClipItemKey {}

impl intern::Internable for ClipIntern {
    type Key = ClipItemKey;
    type StoreData = ClipNode;
    type InternData = ClipInternData;
    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CLIPS;
}

#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum ClipItemKind {
    Rectangle {
        rect: LayoutRect,
        mode: ClipMode,
    },
    RoundedRectangle {
        rect: LayoutRect,
        radius: BorderRadius,
        mode: ClipMode,
    },
    Image {
        image: ImageKey,
        rect: LayoutRect,
        polygon_handle: Option<PolygonDataHandle>,
    },
    BoxShadow {
        source: BoxShadowClipSource,
    },
}

#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipItem {
    pub kind: ClipItemKind,
    pub spatial_node_index: SpatialNodeIndex,
}

fn compute_box_shadow_parameters(
    shadow_rect_fract_offset: LayoutPoint,
    shadow_rect_size: LayoutSize,
    mut shadow_radius: BorderRadius,
    prim_shadow_rect: LayoutRect,
    blur_radius: f32,
    clip_mode: BoxShadowClipMode,
) -> BoxShadowClipSource {
    // Make sure corners don't overlap.
    ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);

    let fract_size = LayoutSize::new(
        shadow_rect_size.width.fract().abs(),
        shadow_rect_size.height.fract().abs(),
    );

    // Create a minimal size primitive mask to blur. In this
    // case, we ensure the size of each corner is the same,
    // to simplify the shader logic that stretches the blurred
    // result across the primitive.
    let max_corner_width = shadow_radius.top_left.width
                                .max(shadow_radius.bottom_left.width)
                                .max(shadow_radius.top_right.width)
                                .max(shadow_radius.bottom_right.width);
    let max_corner_height = shadow_radius.top_left.height
                                .max(shadow_radius.bottom_left.height)
                                .max(shadow_radius.top_right.height)
                                .max(shadow_radius.bottom_right.height);

    // Get maximum distance that can be affected by given blur radius.
    let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();

    // If the largest corner is smaller than the blur radius, we need to ensure
    // that it's big enough that the corners don't affect the middle segments.
    let used_corner_width = max_corner_width.max(blur_region);
    let used_corner_height = max_corner_height.max(blur_region);

    // Minimal nine-patch size, corner + internal + corner.
    let min_shadow_rect_size = LayoutSize::new(
        2.0 * used_corner_width + blur_region,
        2.0 * used_corner_height + blur_region,
    );

    // The minimal rect to blur.
    let mut minimal_shadow_rect = LayoutRect::from_origin_and_size(
--> --------------------

--> maximum size reached

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

[ Dauer der Verarbeitung: 0.47 Sekunden  (vorverarbeitet)  ]