tor-browser

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

clip.rs (87662B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 //! Internal representation of clips in WebRender.
      6 //!
      7 //! # Data structures
      8 //!
      9 //! There are a number of data structures involved in the clip module:
     10 //!
     11 //! - ClipStore - Main interface used by other modules.
     12 //!
     13 //! - ClipItem - A single clip item (e.g. a rounded rect, or a box shadow).
     14 //!              These are an exposed API type, stored inline in a ClipNode.
     15 //!
     16 //! - ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated
     17 //!              when a ClipNodeInstance is built from this node (which happens while
     18 //!              preparing primitives for render).
     19 //!
     20 //! ClipNodeInstance - A ClipNode with attached positioning information (a spatial
     21 //!                    node index). This is stored as a contiguous array of nodes
     22 //!                    within the ClipStore.
     23 //!
     24 //! ```ascii
     25 //! +-----------------------+-----------------------+-----------------------+
     26 //! | ClipNodeInstance      | ClipNodeInstance      | ClipNodeInstance      |
     27 //! +-----------------------+-----------------------+-----------------------+
     28 //! | ClipItem              | ClipItem              | ClipItem              |
     29 //! | Spatial Node Index    | Spatial Node Index    | Spatial Node Index    |
     30 //! | GPU cache handle      | GPU cache handle      | GPU cache handle      |
     31 //! | ...                   | ...                   | ...                   |
     32 //! +-----------------------+-----------------------+-----------------------+
     33 //!            0                        1                       2
     34 //!    +----------------+    |                                              |
     35 //!    | ClipNodeRange  |____|                                              |
     36 //!    |    index: 1    |                                                   |
     37 //!    |    count: 2    |___________________________________________________|
     38 //!    +----------------+
     39 //! ```
     40 //!
     41 //! - ClipNodeRange - A clip item range identifies a range of clip nodes instances.
     42 //!                   It is stored as an (index, count).
     43 //!
     44 //! - ClipChainNode - A clip chain node contains a handle to an interned clip item,
     45 //!                   positioning information (from where the clip was defined), and
     46 //!                   an optional parent link to another ClipChainNode. ClipChainId
     47 //!                   is an index into an array, or ClipChainId::NONE for no parent.
     48 //!
     49 //! ```ascii
     50 //! +----------------+    ____+----------------+    ____+----------------+   /---> ClipChainId::NONE
     51 //! | ClipChainNode  |   |    | ClipChainNode  |   |    | ClipChainNode  |   |
     52 //! +----------------+   |    +----------------+   |    +----------------+   |
     53 //! | ClipDataHandle |   |    | ClipDataHandle |   |    | ClipDataHandle |   |
     54 //! | Spatial index  |   |    | Spatial index  |   |    | Spatial index  |   |
     55 //! | Parent Id      |___|    | Parent Id      |___|    | Parent Id      |___|
     56 //! | ...            |        | ...            |        | ...            |
     57 //! +----------------+        +----------------+        +----------------+
     58 //! ```
     59 //!
     60 //! - ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node.
     61 //!
     62 //!    When given a clip chain ID, and a local primitive rect and its spatial node, the clip module
     63 //!    creates a clip chain instance. This is a struct with various pieces of useful information
     64 //!    (such as a local clip rect). It also contains a (index, count)
     65 //!    range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant
     66 //!    for this clip chain instance. The index buffer structure allows a single array to be used for
     67 //!    all of the clip-chain instances built in a single frame. Each entry in the index buffer
     68 //!    also stores some flags relevant to the clip node in this positioning context.
     69 //!
     70 //! ```ascii
     71 //! +----------------------+
     72 //! | ClipChainInstance    |
     73 //! +----------------------+
     74 //! | ...                  |
     75 //! | local_clip_rect      |________________________________________________________________________
     76 //! | clips_range          |_______________                                                        |
     77 //! +----------------------+              |                                                        |
     78 //!                                       |                                                        |
     79 //! +------------------+------------------+------------------+------------------+------------------+
     80 //! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
     81 //! +------------------+------------------+------------------+------------------+------------------+
     82 //! | flags            | flags            | flags            | flags            | flags            |
     83 //! | ...              | ...              | ...              | ...              | ...              |
     84 //! +------------------+------------------+------------------+------------------+------------------+
     85 //! ```
     86 //!
     87 //! # Rendering clipped primitives
     88 //!
     89 //! See the [`segment` module documentation][segment.rs].
     90 //!
     91 //!
     92 //! [segment.rs]: ../segment/index.html
     93 //!
     94 
     95 use api::{BorderRadius, ClipMode, ImageMask, ClipId, ClipChainId};
     96 use api::{BoxShadowClipMode, FillRule, ImageKey, ImageRendering};
     97 use api::units::*;
     98 use crate::image_tiling::{self, Repetition};
     99 use crate::border::{ensure_no_corner_overlap, BorderRadiusAu};
    100 use crate::box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
    101 use crate::renderer::GpuBufferBuilderF;
    102 use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
    103 use crate::ellipse::Ellipse;
    104 use crate::gpu_types::{BoxShadowStretchMode};
    105 use crate::intern;
    106 use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo};
    107 use crate::prim_store::{VisibleMaskImageTile};
    108 use crate::prim_store::{PointKey, SizeKey, RectangleKey, PolygonKey};
    109 use crate::render_task_cache::to_cache_size;
    110 use crate::render_task::RenderTask;
    111 use crate::render_task_graph::RenderTaskGraphBuilder;
    112 use crate::resource_cache::{ImageRequest, ResourceCache};
    113 use crate::scene_builder_thread::Interners;
    114 use crate::space::SpaceMapper;
    115 use crate::util::{clamp_to_scale_factor, extract_inner_rect_safe, project_rect, MatrixHelpers, MaxRect, ScaleOffset};
    116 use euclid::approxeq::ApproxEq;
    117 use std::{iter, ops, u32, mem};
    118 
    119 /// A (non-leaf) node inside a clip-tree
    120 #[cfg_attr(feature = "capture", derive(Serialize))]
    121 #[cfg_attr(feature = "replay", derive(Deserialize))]
    122 #[derive(MallocSizeOf)]
    123 pub struct ClipTreeNode {
    124    pub handle: ClipDataHandle,
    125    pub parent: ClipNodeId,
    126 
    127    children: Vec<ClipNodeId>,
    128 
    129    // TODO(gw): Consider adding a default leaf for cases when the local_clip_rect is not relevant,
    130    //           that can be shared among primitives (to reduce amount of clip-chain building).
    131 }
    132 
    133 /// A leaf node in a clip-tree. Any primitive that is clipped will have a handle to
    134 /// a clip-tree leaf.
    135 #[cfg_attr(feature = "capture", derive(Serialize))]
    136 #[cfg_attr(feature = "replay", derive(Deserialize))]
    137 #[derive(MallocSizeOf)]
    138 pub struct ClipTreeLeaf {
    139    pub node_id: ClipNodeId,
    140 
    141    // TODO(gw): For now, this preserves the ability to build a culling rect
    142    //           from the supplied leaf local clip rect on the primitive. In
    143    //           future, we'll expand this to be more efficient by combining
    144    //           it will compatible clip rects from the `node_id`.
    145    pub local_clip_rect: LayoutRect,
    146 }
    147 
    148 /// ID for a ClipTreeNode
    149 #[derive(Debug, Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)]
    150 #[cfg_attr(feature = "capture", derive(Serialize))]
    151 #[cfg_attr(feature = "replay", derive(Deserialize))]
    152 pub struct ClipNodeId(u32);
    153 
    154 impl ClipNodeId {
    155    pub const NONE: ClipNodeId = ClipNodeId(0);
    156 }
    157 
    158 /// ID for a ClipTreeLeaf
    159 #[derive(Debug, Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)]
    160 #[cfg_attr(feature = "capture", derive(Serialize))]
    161 #[cfg_attr(feature = "replay", derive(Deserialize))]
    162 pub struct ClipLeafId(u32);
    163 
    164 /// A clip-tree built during scene building and used during frame-building to apply clips to primitives.
    165 #[cfg_attr(feature = "capture", derive(Serialize))]
    166 #[cfg_attr(feature = "replay", derive(Deserialize))]
    167 pub struct ClipTree {
    168    nodes: Vec<ClipTreeNode>,
    169    leaves: Vec<ClipTreeLeaf>,
    170    clip_root_stack: Vec<ClipNodeId>,
    171 }
    172 
    173 impl ClipTree {
    174    pub fn new() -> Self {
    175        ClipTree {
    176            nodes: vec![
    177                ClipTreeNode {
    178                    handle: ClipDataHandle::INVALID,
    179                    children: Vec::new(),
    180                    parent: ClipNodeId::NONE,
    181                }
    182            ],
    183            leaves: Vec::new(),
    184            clip_root_stack: vec![
    185                ClipNodeId::NONE,
    186            ],
    187        }
    188    }
    189 
    190    pub fn reset(&mut self) {
    191        self.nodes.clear();
    192        self.nodes.push(ClipTreeNode {
    193            handle: ClipDataHandle::INVALID,
    194            children: Vec::new(),
    195            parent: ClipNodeId::NONE,
    196        });
    197 
    198        self.leaves.clear();
    199 
    200        self.clip_root_stack.clear();
    201        self.clip_root_stack.push(ClipNodeId::NONE);
    202    }
    203 
    204    /// Add a set of clips to the provided tree node id, reusing existing
    205    /// nodes in the tree where possible
    206    fn add_impl(
    207        id: ClipNodeId,
    208        clips: &[ClipDataHandle],
    209        nodes: &mut Vec<ClipTreeNode>,
    210    ) -> ClipNodeId {
    211        if clips.is_empty() {
    212            return id;
    213        }
    214 
    215        let handle = clips[0];
    216        let next_clips = &clips[1..];
    217 
    218        let node_index = nodes[id.0 as usize]
    219            .children
    220            .iter()
    221            .find(|n| nodes[n.0 as usize].handle == handle)
    222            .cloned();
    223 
    224        let node_index = match node_index {
    225            Some(node_index) => node_index,
    226            None => {
    227                let node_index = ClipNodeId(nodes.len() as u32);
    228                nodes[id.0 as usize].children.push(node_index);
    229                let node = ClipTreeNode {
    230                    handle,
    231                    children: Vec::new(),
    232                    parent: id,
    233                };
    234                nodes.push(node);
    235                node_index
    236            }
    237        };
    238 
    239        ClipTree::add_impl(
    240            node_index,
    241            next_clips,
    242            nodes,
    243        )
    244    }
    245 
    246    /// Add a set of clips to the provided tree node id, reusing existing
    247    /// nodes in the tree where possible
    248    pub fn add(
    249        &mut self,
    250        root: ClipNodeId,
    251        clips: &[ClipDataHandle],
    252    ) -> ClipNodeId {
    253        ClipTree::add_impl(
    254            root,
    255            clips,
    256            &mut self.nodes,
    257        )
    258    }
    259 
    260    /// Get the current clip root (the node in the clip-tree where clips can be
    261    /// ignored when building the clip-chain instance for a primitive)
    262    pub fn current_clip_root(&self) -> ClipNodeId {
    263        self.clip_root_stack.last().cloned().unwrap()
    264    }
    265 
    266    /// Push a clip root (e.g. when a surface is encountered) that prevents clips
    267    /// from this node and above being applied to primitives within the root.
    268    pub fn push_clip_root_leaf(&mut self, clip_leaf_id: ClipLeafId) {
    269        let leaf = &self.leaves[clip_leaf_id.0 as usize];
    270        self.clip_root_stack.push(leaf.node_id);
    271    }
    272 
    273    /// Push a clip root (e.g. when a surface is encountered) that prevents clips
    274    /// from this node and above being applied to primitives within the root.
    275    pub fn push_clip_root_node(&mut self, clip_node_id: ClipNodeId) {
    276        self.clip_root_stack.push(clip_node_id);
    277    }
    278 
    279    /// Pop a clip root, when exiting a surface.
    280    pub fn pop_clip_root(&mut self) {
    281        self.clip_root_stack.pop().unwrap();
    282    }
    283 
    284    /// Retrieve a clip tree node by id
    285    pub fn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
    286        assert!(id != ClipNodeId::NONE);
    287 
    288        &self.nodes[id.0 as usize]
    289    }
    290 
    291    pub fn get_parent(&self, id: ClipNodeId) -> Option<ClipNodeId> {
    292        // Invalid ids point to the first item in the nodes vector which
    293        // has an invalid id for the parent so we don't need to handle
    294        // `id` being invalid separately.
    295        let parent = self.nodes[id.0 as usize].parent;
    296        if parent == ClipNodeId::NONE {
    297            return None;
    298        }
    299 
    300        return Some(parent)
    301    }
    302 
    303    /// Retrieve a clip tree leaf by id
    304    pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
    305        &self.leaves[id.0 as usize]
    306    }
    307 
    308    /// Debug print the clip-tree
    309    #[allow(unused)]
    310    pub fn print(&self) {
    311        use crate::print_tree::PrintTree;
    312 
    313        fn print_node<T: crate::print_tree::PrintTreePrinter>(
    314            id: ClipNodeId,
    315            nodes: &[ClipTreeNode],
    316            pt: &mut T,
    317        ) {
    318            let node = &nodes[id.0 as usize];
    319 
    320            pt.new_level(format!("{:?}", id));
    321            pt.add_item(format!("{:?}", node.handle));
    322 
    323            for child_id in &node.children {
    324                print_node(*child_id, nodes, pt);
    325            }
    326 
    327            pt.end_level();
    328        }
    329 
    330        fn print_leaf<T: crate::print_tree::PrintTreePrinter>(
    331            id: ClipLeafId,
    332            leaves: &[ClipTreeLeaf],
    333            pt: &mut T,
    334        ) {
    335            let leaf = &leaves[id.0 as usize];
    336 
    337            pt.new_level(format!("{:?}", id));
    338            pt.add_item(format!("node_id: {:?}", leaf.node_id));
    339            pt.add_item(format!("local_clip_rect: {:?}", leaf.local_clip_rect));
    340            pt.end_level();
    341        }
    342 
    343        let mut pt = PrintTree::new("clip tree");
    344        print_node(ClipNodeId::NONE, &self.nodes, &mut pt);
    345 
    346        for i in 0 .. self.leaves.len() {
    347            print_leaf(ClipLeafId(i as u32), &self.leaves, &mut pt);
    348        }
    349    }
    350 
    351    /// Find the lowest common ancestor of two clip tree nodes. This is useful
    352    /// to identify shared clips between primitives attached to different clip-leaves.
    353    pub fn find_lowest_common_ancestor(
    354        &self,
    355        mut node1: ClipNodeId,
    356        mut node2: ClipNodeId,
    357    ) -> ClipNodeId {
    358        // TODO(gw): Consider caching / storing the depth in the node?
    359        fn get_node_depth(
    360            id: ClipNodeId,
    361            nodes: &[ClipTreeNode],
    362        ) -> usize {
    363            let mut depth = 0;
    364            let mut current = id;
    365 
    366            while current != ClipNodeId::NONE {
    367                let node = &nodes[current.0 as usize];
    368                depth += 1;
    369                current = node.parent;
    370            }
    371 
    372            depth
    373        }
    374 
    375        let mut depth1 = get_node_depth(node1, &self.nodes);
    376        let mut depth2 = get_node_depth(node2, &self.nodes);
    377 
    378        while depth1 > depth2 {
    379            node1 = self.nodes[node1.0 as usize].parent;
    380            depth1 -= 1;
    381        }
    382 
    383        while depth2 > depth1 {
    384            node2 = self.nodes[node2.0 as usize].parent;
    385            depth2 -= 1;
    386        }
    387 
    388        while node1 != node2 {
    389            node1 = self.nodes[node1.0 as usize].parent;
    390            node2 = self.nodes[node2.0 as usize].parent;
    391        }
    392 
    393        node1
    394    }
    395 }
    396 
    397 /// Represents a clip-chain as defined by the public API that we decompose in to
    398 /// the clip-tree. In future, we would like to remove this and have Gecko directly
    399 /// build the clip-tree.
    400 #[cfg_attr(feature = "capture", derive(Serialize))]
    401 #[cfg_attr(feature = "replay", derive(Deserialize))]
    402 pub struct ClipChain {
    403    parent: Option<usize>,
    404    clips: Vec<ClipDataHandle>,
    405 }
    406 
    407 #[cfg_attr(feature = "capture", derive(Serialize))]
    408 #[cfg_attr(feature = "replay", derive(Deserialize))]
    409 pub struct ClipStackEntry {
    410    /// Cache the previous clip-chain build, since this is a common case
    411    last_clip_chain_cache: Option<(ClipChainId, ClipNodeId)>,
    412 
    413    /// Set of clips that were already seen and included in clip_node_id
    414    seen_clips: FastHashSet<ClipDataHandle>,
    415 
    416    /// The build clip_node_id for this level of the stack
    417    clip_node_id: ClipNodeId,
    418 }
    419 
    420 /// Used by the scene builder to build the clip-tree that is part of the built scene.
    421 #[cfg_attr(feature = "capture", derive(Serialize))]
    422 #[cfg_attr(feature = "replay", derive(Deserialize))]
    423 pub struct ClipTreeBuilder {
    424    /// Clips defined by the display list
    425    clip_map: FastHashMap<ClipId, ClipDataHandle>,
    426 
    427    /// Clip-chains defined by the display list
    428    clip_chains: Vec<ClipChain>,
    429    clip_chain_map: FastHashMap<ClipChainId, usize>,
    430 
    431    /// List of clips pushed/popped by grouping items, such as stacking contexts and iframes
    432    clip_stack: Vec<ClipStackEntry>,
    433 
    434    /// The tree we are building
    435    tree: ClipTree,
    436 
    437    /// A temporary buffer stored here to avoid constant heap allocs/frees
    438    clip_handles_buffer: Vec<ClipDataHandle>,
    439 }
    440 
    441 impl ClipTreeBuilder {
    442    pub fn new() -> Self {
    443        ClipTreeBuilder {
    444            clip_map: FastHashMap::default(),
    445            clip_chain_map: FastHashMap::default(),
    446            clip_chains: Vec::new(),
    447            clip_stack: vec![
    448                ClipStackEntry {
    449                    clip_node_id: ClipNodeId::NONE,
    450                    last_clip_chain_cache: None,
    451                    seen_clips: FastHashSet::default(),
    452                },
    453            ],
    454            tree: ClipTree::new(),
    455            clip_handles_buffer: Vec::new(),
    456        }
    457    }
    458 
    459    pub fn begin(&mut self) {
    460        self.clip_map.clear();
    461        self.clip_chain_map.clear();
    462        self.clip_chains.clear();
    463        self.clip_stack.clear();
    464        self.clip_stack.push(ClipStackEntry {
    465            clip_node_id: ClipNodeId::NONE,
    466            last_clip_chain_cache: None,
    467            seen_clips: FastHashSet::default(),
    468        });
    469        self.tree.reset();
    470        self.clip_handles_buffer.clear();
    471    }
    472 
    473    pub fn recycle_tree(&mut self, tree: ClipTree) {
    474        self.tree = tree;
    475    }
    476 
    477    /// Define a new rect clip
    478    pub fn define_rect_clip(
    479        &mut self,
    480        id: ClipId,
    481        handle: ClipDataHandle,
    482    ) {
    483        self.clip_map.insert(id, handle);
    484    }
    485 
    486    /// Define a new rounded rect clip
    487    pub fn define_rounded_rect_clip(
    488        &mut self,
    489        id: ClipId,
    490        handle: ClipDataHandle,
    491    ) {
    492        self.clip_map.insert(id, handle);
    493    }
    494 
    495    /// Define a image mask clip
    496    pub fn define_image_mask_clip(
    497        &mut self,
    498        id: ClipId,
    499        handle: ClipDataHandle,
    500    ) {
    501        self.clip_map.insert(id, handle);
    502    }
    503 
    504    /// Define a clip-chain
    505    pub fn define_clip_chain<I: Iterator<Item = ClipId>>(
    506        &mut self,
    507        id: ClipChainId,
    508        parent: Option<ClipChainId>,
    509        clips: I,
    510    ) {
    511        let parent = parent.map(|ref id| self.clip_chain_map[id]);
    512        let index = self.clip_chains.len();
    513        let clips = clips.map(|clip_id| {
    514            self.clip_map[&clip_id]
    515        }).collect();
    516        self.clip_chains.push(ClipChain {
    517            parent,
    518            clips,
    519        });
    520        self.clip_chain_map.insert(id, index);
    521    }
    522 
    523    /// Push a clip-chain that will be applied to any prims built prior to next pop
    524    pub fn push_clip_chain(
    525        &mut self,
    526        clip_chain_id: Option<ClipChainId>,
    527        reset_seen: bool,
    528        ignore_ancestor_clips: bool,
    529    ) {
    530        let (mut clip_node_id, mut seen_clips) = {
    531            let prev = self.clip_stack.last().unwrap();
    532            let clip_node_id = if ignore_ancestor_clips {
    533                ClipNodeId::NONE
    534            } else {
    535                prev.clip_node_id
    536            };
    537            (clip_node_id, prev.seen_clips.clone())
    538        };
    539 
    540        if let Some(clip_chain_id) = clip_chain_id {
    541            if clip_chain_id != ClipChainId::INVALID {
    542                self.clip_handles_buffer.clear();
    543 
    544                let clip_chain_index = self.clip_chain_map[&clip_chain_id];
    545                ClipTreeBuilder::add_clips(
    546                    clip_chain_index,
    547                    &mut seen_clips,
    548                    &mut self.clip_handles_buffer,
    549                    &self.clip_chains,
    550                );
    551 
    552                clip_node_id = self.tree.add(
    553                    clip_node_id,
    554                    &self.clip_handles_buffer,
    555                );
    556            }
    557        }
    558 
    559        if reset_seen {
    560            seen_clips.clear();
    561        }
    562 
    563        self.clip_stack.push(ClipStackEntry {
    564            last_clip_chain_cache: None,
    565            clip_node_id,
    566            seen_clips,
    567        });
    568    }
    569 
    570    /// Push a clip-id that will be applied to any prims built prior to next pop
    571    pub fn push_clip_id(
    572        &mut self,
    573        clip_id: ClipId,
    574    ) {
    575        let (clip_node_id, mut seen_clips) = {
    576            let prev = self.clip_stack.last().unwrap();
    577            (prev.clip_node_id, prev.seen_clips.clone())
    578        };
    579 
    580        self.clip_handles_buffer.clear();
    581        let clip_index = self.clip_map[&clip_id];
    582 
    583        if seen_clips.insert(clip_index) {
    584            self.clip_handles_buffer.push(clip_index);
    585        }
    586 
    587        let clip_node_id = self.tree.add(
    588            clip_node_id,
    589            &self.clip_handles_buffer,
    590        );
    591 
    592        self.clip_stack.push(ClipStackEntry {
    593            last_clip_chain_cache: None,
    594            seen_clips,
    595            clip_node_id,
    596        });
    597    }
    598 
    599    /// Pop a clip off the clip_stack, when exiting a grouping item
    600    pub fn pop_clip(&mut self) {
    601        self.clip_stack.pop().unwrap();
    602    }
    603 
    604    /// Add clips from a given clip-chain to the set of clips for a primitive during clip-set building
    605    fn add_clips(
    606        clip_chain_index: usize,
    607        seen_clips: &mut FastHashSet<ClipDataHandle>,
    608        output: &mut Vec<ClipDataHandle>,
    609        clip_chains: &[ClipChain],
    610    ) {
    611        // TODO(gw): It's possible that we may see clip outputs that include identical clips
    612        //           (e.g. if there is a clip positioned by two spatial nodes, where one spatial
    613        //           node is a child of the other, and has an identity transform). If we ever
    614        //           see this in real-world cases, it might be worth checking for that here and
    615        //           excluding them, to ensure the shape of the tree matches what we need for
    616        //           finding shared_clips for tile caches etc.
    617 
    618        let clip_chain = &clip_chains[clip_chain_index];
    619 
    620        if let Some(parent) = clip_chain.parent {
    621            ClipTreeBuilder::add_clips(
    622                parent,
    623                seen_clips,
    624                output,
    625                clip_chains,
    626            );
    627        }
    628 
    629        for clip_index in clip_chain.clips.iter().rev() {
    630            if seen_clips.insert(*clip_index) {
    631                output.push(*clip_index);
    632            }
    633        }
    634    }
    635 
    636    /// Main entry point to build a path in the clip-tree for a given primitive
    637    pub fn build_clip_set(
    638        &mut self,
    639        clip_chain_id: ClipChainId,
    640    ) -> ClipNodeId {
    641        let clip_stack = self.clip_stack.last_mut().unwrap();
    642 
    643        if clip_chain_id == ClipChainId::INVALID {
    644            clip_stack.clip_node_id
    645        } else {
    646            if let Some((cached_clip_chain, cached_clip_node)) = clip_stack.last_clip_chain_cache {
    647                if cached_clip_chain == clip_chain_id {
    648                    return cached_clip_node;
    649                }
    650            }
    651 
    652            let clip_chain_index = self.clip_chain_map[&clip_chain_id];
    653 
    654            self.clip_handles_buffer.clear();
    655 
    656            ClipTreeBuilder::add_clips(
    657                clip_chain_index,
    658                &mut clip_stack.seen_clips,
    659                &mut self.clip_handles_buffer,
    660                &self.clip_chains,
    661            );
    662 
    663            // We mutated the `clip_stack.seen_clips` in order to remove duplicate clips from
    664            // the supplied `clip_chain_id`. Now step through and remove any clips we added
    665            // to the set, so we don't get incorrect results next time `build_clip_set` is
    666            // called for a different clip-chain. Doing it this way rather than cloning means
    667            // we avoid heap allocations for each `build_clip_set` call.
    668            for handle in &self.clip_handles_buffer {
    669                clip_stack.seen_clips.remove(handle);
    670            }
    671 
    672            let clip_node_id = self.tree.add(
    673                clip_stack.clip_node_id,
    674                &self.clip_handles_buffer,
    675            );
    676 
    677            clip_stack.last_clip_chain_cache = Some((clip_chain_id, clip_node_id));
    678 
    679            clip_node_id
    680        }
    681    }
    682 
    683    /// Recursive impl to check if a clip-chain has complex (non-rectangular) clips
    684    fn has_complex_clips_impl(
    685        &self,
    686        clip_chain_index: usize,
    687        interners: &Interners,
    688    ) -> bool {
    689        let clip_chain = &self.clip_chains[clip_chain_index];
    690 
    691        for clip_handle in &clip_chain.clips {
    692            let clip_info = &interners.clip[*clip_handle];
    693 
    694            if let ClipNodeKind::Complex = clip_info.key.kind.node_kind() {
    695                return true;
    696            }
    697        }
    698 
    699        match clip_chain.parent {
    700            Some(parent) => self.has_complex_clips_impl(parent, interners),
    701            None => false,
    702        }
    703    }
    704 
    705    /// Check if a clip-chain has complex (non-rectangular) clips
    706    pub fn clip_chain_has_complex_clips(
    707        &self,
    708        clip_chain_id: ClipChainId,
    709        interners: &Interners,
    710    ) -> bool {
    711        let clip_chain_index = self.clip_chain_map[&clip_chain_id];
    712        self.has_complex_clips_impl(clip_chain_index, interners)
    713    }
    714 
    715    /// Check if a clip-node has complex (non-rectangular) clips
    716    pub fn clip_node_has_complex_clips(
    717        &self,
    718        clip_node_id: ClipNodeId,
    719        interners: &Interners,
    720    ) -> bool {
    721        let mut current = clip_node_id;
    722 
    723        while current != ClipNodeId::NONE {
    724            let node = &self.tree.nodes[current.0 as usize];
    725            let clip_info = &interners.clip[node.handle];
    726 
    727            if let ClipNodeKind::Complex = clip_info.key.kind.node_kind() {
    728                return true;
    729            }
    730 
    731            current = node.parent;
    732        }
    733 
    734        false
    735    }
    736 
    737    pub fn get_parent(&self, id: ClipNodeId) -> Option<ClipNodeId> {
    738        self.tree.get_parent(id)
    739    }
    740 
    741    /// Finalize building and return the clip-tree
    742    pub fn finalize(&mut self) -> ClipTree {
    743        // Note: After this, the builder's clip tree does not hold allocations and
    744        // is not in valid state. `ClipTreeBuilder::begin()` must be called before
    745        // building can happen again.
    746        std::mem::replace(&mut self.tree, ClipTree {
    747            nodes: Vec::new(),
    748            leaves: Vec::new(),
    749            clip_root_stack: Vec::new(),
    750        })
    751    }
    752 
    753    /// Get a clip node by id
    754    pub fn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
    755        assert!(id != ClipNodeId::NONE);
    756 
    757        &self.tree.nodes[id.0 as usize]
    758    }
    759 
    760    /// Get a clip leaf by id
    761    pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
    762        &self.tree.leaves[id.0 as usize]
    763    }
    764 
    765    /// Build a clip-leaf for a tile-cache
    766    pub fn build_for_tile_cache(
    767        &mut self,
    768        clip_node_id: ClipNodeId,
    769        extra_clips: &[ClipId],
    770    ) -> ClipLeafId {
    771        self.clip_handles_buffer.clear();
    772 
    773        for clip_id in extra_clips {
    774            let handle = self.clip_map[clip_id];
    775            self.clip_handles_buffer.push(handle);
    776        }
    777 
    778        let node_id = self.tree.add(
    779            clip_node_id,
    780            &self.clip_handles_buffer,
    781        );
    782 
    783        let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
    784 
    785        self.tree.leaves.push(ClipTreeLeaf {
    786            node_id,
    787            local_clip_rect: LayoutRect::max_rect(),
    788        });
    789 
    790        clip_leaf_id
    791    }
    792 
    793    /// Build a clip-leaf for a picture
    794    pub fn build_for_picture(
    795        &mut self,
    796        clip_node_id: ClipNodeId,
    797    ) -> ClipLeafId {
    798        let node_id = self.tree.add(
    799            clip_node_id,
    800            &[],
    801        );
    802 
    803        let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
    804 
    805        self.tree.leaves.push(ClipTreeLeaf {
    806            node_id,
    807            local_clip_rect: LayoutRect::max_rect(),
    808        });
    809 
    810        clip_leaf_id
    811    }
    812 
    813    /// Build a clip-leaf for a normal primitive
    814    pub fn build_for_prim(
    815        &mut self,
    816        clip_node_id: ClipNodeId,
    817        info: &LayoutPrimitiveInfo,
    818        extra_clips: &[ClipItemKey],
    819        interners: &mut Interners,
    820    ) -> ClipLeafId {
    821 
    822        let node_id = if extra_clips.is_empty() {
    823            clip_node_id
    824        } else {
    825            // TODO(gw): Cache the previous build of clip-node / clip-leaf to handle cases where we get a
    826            //           lot of primitives referencing the same clip set (e.g. dl_mutate and similar tests)
    827            self.clip_handles_buffer.clear();
    828 
    829            for item in extra_clips {
    830                // Intern this clip item, and store the handle
    831                // in the clip chain node.
    832                let handle = interners.clip.intern(item, || {
    833                    ClipInternData {
    834                        key: item.clone(),
    835                    }
    836                });
    837 
    838                self.clip_handles_buffer.push(handle);
    839            }
    840 
    841            self.tree.add(
    842                clip_node_id,
    843                &self.clip_handles_buffer,
    844            )
    845        };
    846 
    847        let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
    848 
    849        self.tree.leaves.push(ClipTreeLeaf {
    850            node_id,
    851            local_clip_rect: info.clip_rect,
    852        });
    853 
    854        clip_leaf_id
    855    }
    856 
    857    // Find the LCA for two given clip nodes
    858    pub fn find_lowest_common_ancestor(
    859        &self,
    860        node1: ClipNodeId,
    861        node2: ClipNodeId,
    862    ) -> ClipNodeId {
    863        self.tree.find_lowest_common_ancestor(node1, node2)
    864    }
    865 }
    866 
    867 // Type definitions for interning clip nodes.
    868 
    869 #[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, Eq, Hash)]
    870 #[cfg_attr(any(feature = "serde"), derive(Deserialize, Serialize))]
    871 pub enum ClipIntern {}
    872 
    873 pub type ClipDataStore = intern::DataStore<ClipIntern>;
    874 pub type ClipDataHandle = intern::Handle<ClipIntern>;
    875 
    876 /// Helper to identify simple clips (normal rects) from other kinds of clips,
    877 /// which can often be handled via fast code paths.
    878 #[cfg_attr(feature = "capture", derive(Serialize))]
    879 #[cfg_attr(feature = "replay", derive(Deserialize))]
    880 #[derive(Debug, Copy, Clone, MallocSizeOf)]
    881 pub enum ClipNodeKind {
    882    /// A normal clip rectangle, with Clip mode.
    883    Rectangle,
    884    /// A rectangle with ClipOut, or any other kind of clip.
    885    Complex,
    886 }
    887 
    888 // Result of comparing a clip node instance against a local rect.
    889 #[derive(Debug)]
    890 enum ClipResult {
    891    // The clip does not affect the region at all.
    892    Accept,
    893    // The clip prevents the region from being drawn.
    894    Reject,
    895    // The clip affects part of the region. This may
    896    // require a clip mask, depending on other factors.
    897    Partial,
    898 }
    899 
    900 // A clip node is a single clip source, along with some
    901 // positioning information and implementation details
    902 // that control where the GPU data for this clip source
    903 // can be found.
    904 #[derive(Debug)]
    905 #[cfg_attr(feature = "capture", derive(Serialize))]
    906 #[cfg_attr(feature = "replay", derive(Deserialize))]
    907 #[derive(MallocSizeOf)]
    908 pub struct ClipNode {
    909    pub item: ClipItem,
    910 }
    911 
    912 // Convert from an interning key for a clip item
    913 // to a clip node, which is cached in the document.
    914 impl From<ClipItemKey> for ClipNode {
    915    fn from(item: ClipItemKey) -> Self {
    916        let kind = match item.kind {
    917            ClipItemKeyKind::Rectangle(rect, mode) => {
    918                ClipItemKind::Rectangle { rect: rect.into(), mode }
    919            }
    920            ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => {
    921                ClipItemKind::RoundedRectangle {
    922                    rect: rect.into(),
    923                    radius: radius.into(),
    924                    mode,
    925                }
    926            }
    927            ClipItemKeyKind::ImageMask(rect, image, polygon_handle) => {
    928                ClipItemKind::Image {
    929                    image,
    930                    rect: rect.into(),
    931                    polygon_handle,
    932                }
    933            }
    934            ClipItemKeyKind::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
    935                ClipItemKind::new_box_shadow(
    936                    shadow_rect_fract_offset.into(),
    937                    shadow_rect_size.into(),
    938                    shadow_radius.into(),
    939                    prim_shadow_rect.into(),
    940                    blur_radius.to_f32_px(),
    941                    clip_mode,
    942                )
    943            }
    944        };
    945 
    946        ClipNode {
    947            item: ClipItem {
    948                kind,
    949                spatial_node_index: item.spatial_node_index,
    950            },
    951        }
    952    }
    953 }
    954 
    955 // Flags that are attached to instances of clip nodes.
    956 #[cfg_attr(feature = "capture", derive(Serialize))]
    957 #[cfg_attr(feature = "replay", derive(Deserialize))]
    958 #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, MallocSizeOf)]
    959 pub struct ClipNodeFlags(u8);
    960 
    961 bitflags! {
    962    impl ClipNodeFlags : u8 {
    963        const SAME_SPATIAL_NODE = 0x1;
    964        const SAME_COORD_SYSTEM = 0x2;
    965        const USE_FAST_PATH = 0x4;
    966    }
    967 }
    968 
    969 impl core::fmt::Debug for ClipNodeFlags {
    970    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
    971        if self.is_empty() {
    972            write!(f, "{:#x}", Self::empty().bits())
    973        } else {
    974            bitflags::parser::to_writer(self, f)
    975        }
    976    }
    977 }
    978 
    979 // When a clip node is found to be valid for a
    980 // clip chain instance, it's stored in an index
    981 // buffer style structure. This struct contains
    982 // an index to the node data itself, as well as
    983 // some flags describing how this clip node instance
    984 // is positioned.
    985 #[derive(Debug, Clone, MallocSizeOf)]
    986 #[cfg_attr(feature = "capture", derive(Serialize))]
    987 #[cfg_attr(feature = "replay", derive(Deserialize))]
    988 pub struct ClipNodeInstance {
    989    pub handle: ClipDataHandle,
    990    pub flags: ClipNodeFlags,
    991    pub visible_tiles: Option<ops::Range<usize>>,
    992 }
    993 
    994 impl ClipNodeInstance {
    995    pub fn has_visible_tiles(&self) -> bool {
    996        self.visible_tiles.is_some()
    997    }
    998 }
    999 
   1000 // A range of clip node instances that were found by
   1001 // building a clip chain instance.
   1002 #[derive(Debug, Copy, Clone)]
   1003 #[cfg_attr(feature = "capture", derive(Serialize))]
   1004 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1005 pub struct ClipNodeRange {
   1006    pub first: u32,
   1007    pub count: u32,
   1008 }
   1009 
   1010 impl ClipNodeRange {
   1011    pub fn to_range(&self) -> ops::Range<usize> {
   1012        let start = self.first as usize;
   1013        let end = start + self.count as usize;
   1014 
   1015        ops::Range {
   1016            start,
   1017            end,
   1018        }
   1019    }
   1020 }
   1021 
   1022 /// A helper struct for converting between coordinate systems
   1023 /// of clip sources and primitives.
   1024 // todo(gw): optimize:
   1025 //  separate arrays for matrices
   1026 //  cache and only build as needed.
   1027 //TODO: merge with `CoordinateSpaceMapping`?
   1028 #[derive(Debug, MallocSizeOf)]
   1029 #[cfg_attr(feature = "capture", derive(Serialize))]
   1030 pub enum ClipSpaceConversion {
   1031    Local,
   1032    ScaleOffset(ScaleOffset),
   1033    Transform(LayoutToVisTransform),
   1034 }
   1035 
   1036 impl ClipSpaceConversion {
   1037    /// Construct a new clip space converter between two spatial nodes.
   1038    pub fn new(
   1039        prim_spatial_node_index: SpatialNodeIndex,
   1040        clip_spatial_node_index: SpatialNodeIndex,
   1041        visibility_spatial_node_index: SpatialNodeIndex,
   1042        spatial_tree: &SpatialTree,
   1043    ) -> Self {
   1044        //Note: this code is different from `get_relative_transform` in a way that we only try
   1045        // getting the relative transform if it's Local or ScaleOffset,
   1046        // falling back to the world transform otherwise.
   1047        let clip_spatial_node = spatial_tree.get_spatial_node(clip_spatial_node_index);
   1048        let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index);
   1049 
   1050        if prim_spatial_node_index == clip_spatial_node_index {
   1051            ClipSpaceConversion::Local
   1052        } else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
   1053            let scale_offset = clip_spatial_node.content_transform
   1054                .then(&prim_spatial_node.content_transform.inverse());
   1055            ClipSpaceConversion::ScaleOffset(scale_offset)
   1056        } else {
   1057            ClipSpaceConversion::Transform(
   1058                spatial_tree.get_relative_transform(
   1059                    clip_spatial_node_index,
   1060                    visibility_spatial_node_index,
   1061                ).into_transform().cast_unit()
   1062            )
   1063        }
   1064    }
   1065 
   1066    fn to_flags(&self) -> ClipNodeFlags {
   1067        match *self {
   1068            ClipSpaceConversion::Local => {
   1069                ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
   1070            }
   1071            ClipSpaceConversion::ScaleOffset(..) => {
   1072                ClipNodeFlags::SAME_COORD_SYSTEM
   1073            }
   1074            ClipSpaceConversion::Transform(..) => {
   1075                ClipNodeFlags::empty()
   1076            }
   1077        }
   1078    }
   1079 }
   1080 
   1081 // Temporary information that is cached and reused
   1082 // during building of a clip chain instance.
   1083 #[derive(MallocSizeOf)]
   1084 #[cfg_attr(feature = "capture", derive(Serialize))]
   1085 struct ClipNodeInfo {
   1086    conversion: ClipSpaceConversion,
   1087    handle: ClipDataHandle,
   1088 }
   1089 
   1090 impl ClipNodeInfo {
   1091    fn create_instance(
   1092        &self,
   1093        node: &ClipNode,
   1094        clipped_rect: &LayoutRect,
   1095        gpu_buffer: &mut GpuBufferBuilderF,
   1096        resource_cache: &mut ResourceCache,
   1097        mask_tiles: &mut Vec<VisibleMaskImageTile>,
   1098        spatial_tree: &SpatialTree,
   1099        rg_builder: &mut RenderTaskGraphBuilder,
   1100        request_resources: bool,
   1101    ) -> Option<ClipNodeInstance> {
   1102        // Calculate some flags that are required for the segment
   1103        // building logic.
   1104        let mut flags = self.conversion.to_flags();
   1105 
   1106        // Some clip shaders support a fast path mode for simple clips.
   1107        // TODO(gw): We could also apply fast path when segments are created, since we only write
   1108        //           the mask for a single corner at a time then, so can always consider radii uniform.
   1109        let is_raster_2d =
   1110            flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
   1111            spatial_tree
   1112                .get_world_viewport_transform(node.item.spatial_node_index)
   1113                .is_2d_axis_aligned();
   1114        if is_raster_2d && node.item.kind.supports_fast_path_rendering() {
   1115            flags |= ClipNodeFlags::USE_FAST_PATH;
   1116        }
   1117 
   1118        let mut visible_tiles = None;
   1119 
   1120        if let ClipItemKind::Image { rect, image, .. } = node.item.kind {
   1121            let request = ImageRequest {
   1122                key: image,
   1123                rendering: ImageRendering::Auto,
   1124                tile: None,
   1125            };
   1126 
   1127            if let Some(props) = resource_cache.get_image_properties(image) {
   1128                if let Some(tile_size) = props.tiling {
   1129                    let tile_range_start = mask_tiles.len();
   1130 
   1131                    // Bug 1648323 - It is unclear why on rare occasions we get
   1132                    // a clipped_rect that does not intersect the clip's mask rect.
   1133                    // defaulting to clipped_rect here results in zero repetitions
   1134                    // which clips the primitive entirely.
   1135                    let visible_rect =
   1136                        clipped_rect.intersection(&rect).unwrap_or(*clipped_rect);
   1137 
   1138                    let repetitions = image_tiling::repetitions(
   1139                        &rect,
   1140                        &visible_rect,
   1141                        rect.size(),
   1142                    );
   1143 
   1144                    for Repetition { origin, .. } in repetitions {
   1145                        let layout_image_rect = LayoutRect::from_origin_and_size(
   1146                            origin,
   1147                            rect.size(),
   1148                        );
   1149                        let tiles = image_tiling::tiles(
   1150                            &layout_image_rect,
   1151                            &visible_rect,
   1152                            &props.visible_rect,
   1153                            tile_size as i32,
   1154                        );
   1155                        for tile in tiles {
   1156                            let req = request.with_tile(tile.offset);
   1157 
   1158                            if request_resources {
   1159                                resource_cache.request_image(
   1160                                    req,
   1161                                    gpu_buffer,
   1162                                );
   1163                            }
   1164 
   1165                            let task_id = rg_builder.add().init(
   1166                                RenderTask::new_image(props.descriptor.size, req, false)
   1167                            );
   1168 
   1169                            mask_tiles.push(VisibleMaskImageTile {
   1170                                tile_offset: tile.offset,
   1171                                tile_rect: tile.rect,
   1172                                task_id,
   1173                            });
   1174                        }
   1175                    }
   1176                    visible_tiles = Some(tile_range_start..mask_tiles.len());
   1177                } else {
   1178                    if request_resources {
   1179                        resource_cache.request_image(request, gpu_buffer);
   1180                    }
   1181 
   1182                    let tile_range_start = mask_tiles.len();
   1183 
   1184                    let task_id = rg_builder.add().init(
   1185                        RenderTask::new_image(props.descriptor.size, request, false)
   1186                    );
   1187 
   1188                    mask_tiles.push(VisibleMaskImageTile {
   1189                        tile_rect: rect,
   1190                        tile_offset: TileOffset::zero(),
   1191                        task_id,
   1192                    });
   1193 
   1194                    visible_tiles = Some(tile_range_start .. mask_tiles.len());
   1195                }
   1196            } else {
   1197                // If the supplied image key doesn't exist in the resource cache,
   1198                // skip the clip node since there is nothing to mask with.
   1199                warn!("Clip mask with missing image key {:?}", request.key);
   1200                return None;
   1201            }
   1202        }
   1203 
   1204        Some(ClipNodeInstance {
   1205            handle: self.handle,
   1206            flags,
   1207            visible_tiles,
   1208        })
   1209    }
   1210 }
   1211 
   1212 impl ClipNode {
   1213    pub fn update(
   1214        &mut self,
   1215        device_pixel_scale: DevicePixelScale,
   1216    ) {
   1217        match self.item.kind {
   1218            ClipItemKind::Image { .. } |
   1219            ClipItemKind::Rectangle { .. } |
   1220            ClipItemKind::RoundedRectangle { .. } => {}
   1221 
   1222            ClipItemKind::BoxShadow { ref mut source } => {
   1223                // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
   1224                // "the image that would be generated by applying to the shadow a
   1225                // Gaussian blur with a standard deviation equal to half the blur radius."
   1226                let blur_radius_dp = source.blur_radius * 0.5;
   1227 
   1228                // Create scaling from requested size to cache size.
   1229                let mut content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
   1230                content_scale.0 = clamp_to_scale_factor(content_scale.0, false);
   1231 
   1232                // Create the cache key for this box-shadow render task.
   1233                let cache_size = to_cache_size(source.shadow_rect_alloc_size, &mut content_scale);
   1234 
   1235                let bs_cache_key = BoxShadowCacheKey {
   1236                    blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
   1237                    clip_mode: source.clip_mode,
   1238                    original_alloc_size: (source.original_alloc_size * content_scale).round().to_i32(),
   1239                    br_top_left: (source.shadow_radius.top_left * content_scale).round().to_i32(),
   1240                    br_top_right: (source.shadow_radius.top_right * content_scale).round().to_i32(),
   1241                    br_bottom_right: (source.shadow_radius.bottom_right * content_scale).round().to_i32(),
   1242                    br_bottom_left: (source.shadow_radius.bottom_left * content_scale).round().to_i32(),
   1243                    device_pixel_scale: Au::from_f32_px(content_scale.0),
   1244                };
   1245 
   1246                source.cache_key = Some((cache_size, bs_cache_key));
   1247            }
   1248        }
   1249    }
   1250 }
   1251 
   1252 #[derive(Default)]
   1253 pub struct ClipStoreScratchBuffer {
   1254    clip_node_instances: Vec<ClipNodeInstance>,
   1255    mask_tiles: Vec<VisibleMaskImageTile>,
   1256 }
   1257 
   1258 /// The main clipping public interface that other modules access.
   1259 #[derive(MallocSizeOf)]
   1260 #[cfg_attr(feature = "capture", derive(Serialize))]
   1261 pub struct ClipStore {
   1262    pub clip_node_instances: Vec<ClipNodeInstance>,
   1263    mask_tiles: Vec<VisibleMaskImageTile>,
   1264 
   1265    active_clip_node_info: Vec<ClipNodeInfo>,
   1266    active_local_clip_rect: Option<LayoutRect>,
   1267    active_pic_coverage_rect: PictureRect,
   1268 }
   1269 
   1270 // A clip chain instance is what gets built for a given clip
   1271 // chain id + local primitive region + positioning node.
   1272 #[derive(Debug)]
   1273 #[cfg_attr(feature = "capture", derive(Serialize))]
   1274 pub struct ClipChainInstance {
   1275    pub clips_range: ClipNodeRange,
   1276    // Combined clip rect for clips that are in the
   1277    // same coordinate system as the primitive.
   1278    pub local_clip_rect: LayoutRect,
   1279    pub has_non_local_clips: bool,
   1280    // If true, this clip chain requires allocation
   1281    // of a clip mask.
   1282    pub needs_mask: bool,
   1283    // Combined clip rect in picture space (may
   1284    // be more conservative that local_clip_rect).
   1285    pub pic_coverage_rect: PictureRect,
   1286    // Space, in which the `pic_coverage_rect` is defined.
   1287    pub pic_spatial_node_index: SpatialNodeIndex,
   1288 }
   1289 
   1290 impl ClipChainInstance {
   1291    pub fn empty() -> Self {
   1292        ClipChainInstance {
   1293            clips_range: ClipNodeRange {
   1294                first: 0,
   1295                count: 0,
   1296            },
   1297            local_clip_rect: LayoutRect::zero(),
   1298            has_non_local_clips: false,
   1299            needs_mask: false,
   1300            pic_coverage_rect: PictureRect::zero(),
   1301            pic_spatial_node_index: SpatialNodeIndex::INVALID,
   1302        }
   1303    }
   1304 }
   1305 
   1306 impl ClipStore {
   1307    pub fn new() -> Self {
   1308        ClipStore {
   1309            clip_node_instances: Vec::new(),
   1310            mask_tiles: Vec::new(),
   1311            active_clip_node_info: Vec::new(),
   1312            active_local_clip_rect: None,
   1313            active_pic_coverage_rect: PictureRect::max_rect(),
   1314        }
   1315    }
   1316 
   1317    pub fn reset(&mut self) {
   1318        self.clip_node_instances.clear();
   1319        self.mask_tiles.clear();
   1320        self.active_clip_node_info.clear();
   1321        self.active_local_clip_rect = None;
   1322        self.active_pic_coverage_rect = PictureRect::max_rect();
   1323    }
   1324 
   1325    pub fn get_instance_from_range(
   1326        &self,
   1327        node_range: &ClipNodeRange,
   1328        index: u32,
   1329    ) -> &ClipNodeInstance {
   1330        &self.clip_node_instances[(node_range.first + index) as usize]
   1331    }
   1332 
   1333    /// Setup the active clip chains for building a clip chain instance.
   1334    pub fn set_active_clips(
   1335        &mut self,
   1336        prim_spatial_node_index: SpatialNodeIndex,
   1337        pic_spatial_node_index: SpatialNodeIndex,
   1338        visibility_spatial_node_index: SpatialNodeIndex,
   1339        clip_leaf_id: ClipLeafId,
   1340        spatial_tree: &SpatialTree,
   1341        clip_data_store: &ClipDataStore,
   1342        clip_tree: &ClipTree,
   1343    ) {
   1344        self.active_clip_node_info.clear();
   1345        self.active_local_clip_rect = None;
   1346        self.active_pic_coverage_rect = PictureRect::max_rect();
   1347 
   1348        let clip_root = clip_tree.current_clip_root();
   1349        let clip_leaf = clip_tree.get_leaf(clip_leaf_id);
   1350 
   1351        let mut local_clip_rect = clip_leaf.local_clip_rect;
   1352        let mut current = clip_leaf.node_id;
   1353 
   1354        while current != clip_root && current != ClipNodeId::NONE {
   1355            let node = clip_tree.get_node(current);
   1356 
   1357            if !add_clip_node_to_current_chain(
   1358                node.handle,
   1359                prim_spatial_node_index,
   1360                pic_spatial_node_index,
   1361                visibility_spatial_node_index,
   1362                &mut local_clip_rect,
   1363                &mut self.active_clip_node_info,
   1364                &mut self.active_pic_coverage_rect,
   1365                clip_data_store,
   1366                spatial_tree,
   1367            ) {
   1368                return;
   1369            }
   1370 
   1371            current = node.parent;
   1372        }
   1373 
   1374        self.active_local_clip_rect = Some(local_clip_rect);
   1375    }
   1376 
   1377    /// Setup the active clip chains, based on an existing primitive clip chain instance.
   1378    pub fn set_active_clips_from_clip_chain(
   1379        &mut self,
   1380        prim_clip_chain: &ClipChainInstance,
   1381        prim_spatial_node_index: SpatialNodeIndex,
   1382        visibility_spatial_node_index: SpatialNodeIndex,
   1383        spatial_tree: &SpatialTree,
   1384        clip_data_store: &ClipDataStore,
   1385    ) {
   1386        // TODO(gw): Although this does less work than set_active_clips(), it does
   1387        //           still do some unnecessary work (such as the clip space conversion).
   1388        //           We could consider optimizing this if it ever shows up in a profile.
   1389 
   1390        self.active_clip_node_info.clear();
   1391        self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect);
   1392        self.active_pic_coverage_rect = prim_clip_chain.pic_coverage_rect;
   1393 
   1394        let clip_instances = &self
   1395            .clip_node_instances[prim_clip_chain.clips_range.to_range()];
   1396        for clip_instance in clip_instances {
   1397            let clip = &clip_data_store[clip_instance.handle];
   1398            let conversion = ClipSpaceConversion::new(
   1399                prim_spatial_node_index,
   1400                clip.item.spatial_node_index,
   1401                visibility_spatial_node_index,
   1402                spatial_tree,
   1403            );
   1404            self.active_clip_node_info.push(ClipNodeInfo {
   1405                handle: clip_instance.handle,
   1406                conversion,
   1407            });
   1408        }
   1409    }
   1410 
   1411    /// Given a clip-chain instance, return a safe rect within the visible region
   1412    /// that can be assumed to be unaffected by clip radii. Returns None if it
   1413    /// encounters any complex cases, just handling rounded rects in the same
   1414    /// coordinate system as the clip-chain for now.
   1415    pub fn get_inner_rect_for_clip_chain(
   1416        &self,
   1417        clip_chain: &ClipChainInstance,
   1418        clip_data_store: &ClipDataStore,
   1419        spatial_tree: &SpatialTree,
   1420    ) -> Option<PictureRect> {
   1421        let mut inner_rect = clip_chain.pic_coverage_rect;
   1422        let clip_instances = &self
   1423            .clip_node_instances[clip_chain.clips_range.to_range()];
   1424 
   1425        for clip_instance in clip_instances {
   1426            // Don't handle mapping between coord systems for now
   1427            if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
   1428                return None;
   1429            }
   1430 
   1431            let clip_node = &clip_data_store[clip_instance.handle];
   1432 
   1433            match clip_node.item.kind {
   1434                // Ignore any clips which are complex or impossible to calculate
   1435                // inner rects for now
   1436                ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
   1437                ClipItemKind::Image { .. } |
   1438                ClipItemKind::BoxShadow { .. } |
   1439                ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => {
   1440                    return None;
   1441                }
   1442                // Normal Clip rects are already handled by the clip-chain pic_coverage_rect,
   1443                // no need to do anything here
   1444                ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {}
   1445                ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, rect, radius } => {
   1446                    // Get an inner rect for the rounded-rect clip
   1447                    let local_inner_rect = match extract_inner_rect_safe(&rect, &radius) {
   1448                        Some(rect) => rect,
   1449                        None => return None,
   1450                    };
   1451 
   1452                    // Map it from local -> picture space
   1453                    let mapper = SpaceMapper::new_with_target(
   1454                        clip_chain.pic_spatial_node_index,
   1455                        clip_node.item.spatial_node_index,
   1456                        PictureRect::max_rect(),
   1457                        spatial_tree,
   1458                    );
   1459 
   1460                    // Accumulate in to the inner_rect, in case there are multiple rounded-rect clips
   1461                    if let Some(pic_inner_rect) = mapper.map(&local_inner_rect) {
   1462                        inner_rect = inner_rect.intersection(&pic_inner_rect).unwrap_or(PictureRect::zero());
   1463                    }
   1464                }
   1465            }
   1466        }
   1467 
   1468        Some(inner_rect)
   1469    }
   1470 
   1471    // Directly construct a clip node range, ready for rendering, from an interned clip handle.
   1472    // Typically useful for drawing specific clips on custom pattern / child render tasks that
   1473    // aren't primitives.
   1474    // TODO(gw): For now, we assume they are local clips only - in future we might want to support
   1475    //           non-local clips.
   1476    pub fn push_clip_instance(
   1477        &mut self,
   1478        handle: ClipDataHandle,
   1479    ) -> ClipNodeRange {
   1480        let first = self.clip_node_instances.len() as u32;
   1481 
   1482        self.clip_node_instances.push(ClipNodeInstance {
   1483            handle,
   1484            flags: ClipNodeFlags::SAME_COORD_SYSTEM | ClipNodeFlags::SAME_SPATIAL_NODE,
   1485            visible_tiles: None,
   1486        });
   1487 
   1488        ClipNodeRange {
   1489            first,
   1490            count: 1,
   1491        }
   1492    }
   1493 
   1494    /// The main interface external code uses. Given a local primitive, positioning
   1495    /// information, and a clip chain id, build an optimized clip chain instance.
   1496    pub fn build_clip_chain_instance(
   1497        &mut self,
   1498        local_prim_rect: LayoutRect,
   1499        prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
   1500        pic_to_vis_mapper: &SpaceMapper<PicturePixel, VisPixel>,
   1501        spatial_tree: &SpatialTree,
   1502        gpu_buffer: &mut GpuBufferBuilderF,
   1503        resource_cache: &mut ResourceCache,
   1504        device_pixel_scale: DevicePixelScale,
   1505        culling_rect: &VisRect,
   1506        clip_data_store: &mut ClipDataStore,
   1507        rg_builder: &mut RenderTaskGraphBuilder,
   1508        request_resources: bool,
   1509    ) -> Option<ClipChainInstance> {
   1510        let local_clip_rect = match self.active_local_clip_rect {
   1511            Some(rect) => rect,
   1512            None => return None,
   1513        };
   1514        profile_scope!("build_clip_chain_instance");
   1515 
   1516        let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
   1517        let mut pic_coverage_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
   1518        let vis_clip_rect = pic_to_vis_mapper.map(&pic_coverage_rect)?;
   1519 
   1520        // Now, we've collected all the clip nodes that *potentially* affect this
   1521        // primitive region, and reduced the size of the prim region as much as possible.
   1522 
   1523        // Run through the clip nodes, and see which ones affect this prim region.
   1524 
   1525        let first_clip_node_index = self.clip_node_instances.len() as u32;
   1526        let mut has_non_local_clips = false;
   1527        let mut needs_mask = false;
   1528 
   1529        // For each potential clip node
   1530        for node_info in self.active_clip_node_info.drain(..) {
   1531            let node = &mut clip_data_store[node_info.handle];
   1532 
   1533            // See how this clip affects the prim region.
   1534            let clip_result = match node_info.conversion {
   1535                ClipSpaceConversion::Local => {
   1536                    node.item.kind.get_clip_result(&local_bounding_rect)
   1537                }
   1538                ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
   1539                    has_non_local_clips = true;
   1540                    node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect))
   1541                }
   1542                ClipSpaceConversion::Transform(ref transform) => {
   1543                    has_non_local_clips = true;
   1544                    node.item.kind.get_clip_result_complex(
   1545                        transform,
   1546                        &vis_clip_rect,
   1547                        culling_rect,
   1548                    )
   1549                }
   1550            };
   1551 
   1552            match clip_result {
   1553                ClipResult::Accept => {
   1554                    // Doesn't affect the primitive at all, so skip adding to list
   1555                }
   1556                ClipResult::Reject => {
   1557                    // Completely clips the supplied prim rect
   1558                    return None;
   1559                }
   1560                ClipResult::Partial => {
   1561                    // Needs a mask -> add to clip node indices
   1562 
   1563                    // TODO(gw): Ensure this only runs once on each node per frame?
   1564                    node.update(device_pixel_scale);
   1565 
   1566                    // Create the clip node instance for this clip node
   1567                    if let Some(instance) = node_info.create_instance(
   1568                        node,
   1569                        &local_bounding_rect,
   1570                        gpu_buffer,
   1571                        resource_cache,
   1572                        &mut self.mask_tiles,
   1573                        spatial_tree,
   1574                        rg_builder,
   1575                        request_resources,
   1576                    ) {
   1577                        // As a special case, a partial accept of a clip rect that is
   1578                        // in the same coordinate system as the primitive doesn't need
   1579                        // a clip mask. Instead, it can be handled by the primitive
   1580                        // vertex shader as part of the local clip rect. This is an
   1581                        // important optimization for reducing the number of clip
   1582                        // masks that are allocated on common pages.
   1583                        needs_mask |= match node.item.kind {
   1584                            ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
   1585                            ClipItemKind::RoundedRectangle { .. } |
   1586                            ClipItemKind::Image { .. } |
   1587                            ClipItemKind::BoxShadow { .. } => {
   1588                                true
   1589                            }
   1590 
   1591                            ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {
   1592                                !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
   1593                            }
   1594                        };
   1595 
   1596                        // Store this in the index buffer for this clip chain instance.
   1597                        self.clip_node_instances.push(instance);
   1598                    }
   1599                }
   1600            }
   1601        }
   1602 
   1603        // Get the range identifying the clip nodes in the index buffer.
   1604        let clips_range = ClipNodeRange {
   1605            first: first_clip_node_index,
   1606            count: self.clip_node_instances.len() as u32 - first_clip_node_index,
   1607        };
   1608 
   1609        // If this clip chain needs a mask, reduce the size of the mask allocation
   1610        // by any clips that were in the same space as the picture. This can result
   1611        // in much smaller clip mask allocations in some cases. Note that the ordering
   1612        // here is important - the reduction must occur *after* the clip item accept
   1613        // reject checks above, so that we don't eliminate masks accidentally (since
   1614        // we currently only support a local clip rect in the vertex shader).
   1615        if needs_mask {
   1616            pic_coverage_rect = pic_coverage_rect.intersection(&self.active_pic_coverage_rect)?;
   1617        }
   1618 
   1619        // Return a valid clip chain instance
   1620        Some(ClipChainInstance {
   1621            clips_range,
   1622            has_non_local_clips,
   1623            local_clip_rect,
   1624            pic_coverage_rect,
   1625            pic_spatial_node_index: prim_to_pic_mapper.ref_spatial_node_index,
   1626            needs_mask,
   1627        })
   1628    }
   1629 
   1630    pub fn begin_frame(&mut self, scratch: &mut ClipStoreScratchBuffer) {
   1631        mem::swap(&mut self.clip_node_instances, &mut scratch.clip_node_instances);
   1632        mem::swap(&mut self.mask_tiles, &mut scratch.mask_tiles);
   1633        self.clip_node_instances.clear();
   1634        self.mask_tiles.clear();
   1635    }
   1636 
   1637    pub fn end_frame(&mut self, scratch: &mut ClipStoreScratchBuffer) {
   1638        mem::swap(&mut self.clip_node_instances, &mut scratch.clip_node_instances);
   1639        mem::swap(&mut self.mask_tiles, &mut scratch.mask_tiles);
   1640    }
   1641 
   1642    pub fn visible_mask_tiles(&self, instance: &ClipNodeInstance) -> &[VisibleMaskImageTile] {
   1643        if let Some(range) = &instance.visible_tiles {
   1644            &self.mask_tiles[range.clone()]
   1645        } else {
   1646            &[]
   1647        }
   1648    }
   1649 }
   1650 
   1651 impl Default for ClipStore {
   1652    fn default() -> Self {
   1653        ClipStore::new()
   1654    }
   1655 }
   1656 
   1657 // The ClipItemKey is a hashable representation of the contents
   1658 // of a clip item. It is used during interning to de-duplicate
   1659 // clip nodes between frames and display lists. This allows quick
   1660 // comparison of clip node equality by handle, and also allows
   1661 // the uploaded GPU cache handle to be retained between display lists.
   1662 // TODO(gw): Maybe we should consider constructing these directly
   1663 //           in the DL builder?
   1664 #[derive(Copy, Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
   1665 #[cfg_attr(feature = "capture", derive(Serialize))]
   1666 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1667 pub enum ClipItemKeyKind {
   1668    Rectangle(RectangleKey, ClipMode),
   1669    RoundedRectangle(RectangleKey, BorderRadiusAu, ClipMode),
   1670    ImageMask(RectangleKey, ImageKey, Option<PolygonDataHandle>),
   1671    BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode),
   1672 }
   1673 
   1674 impl ClipItemKeyKind {
   1675    pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self {
   1676        ClipItemKeyKind::Rectangle(rect.into(), mode)
   1677    }
   1678 
   1679    pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self {
   1680        if radii.is_zero() {
   1681            ClipItemKeyKind::rectangle(rect, mode)
   1682        } else {
   1683            ensure_no_corner_overlap(&mut radii, rect.size());
   1684            ClipItemKeyKind::RoundedRectangle(
   1685                rect.into(),
   1686                radii.into(),
   1687                mode,
   1688            )
   1689        }
   1690    }
   1691 
   1692    pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect,
   1693                      polygon_handle: Option<PolygonDataHandle>) -> Self {
   1694        ClipItemKeyKind::ImageMask(
   1695            mask_rect.into(),
   1696            image_mask.image,
   1697            polygon_handle,
   1698        )
   1699    }
   1700 
   1701    pub fn box_shadow(
   1702        shadow_rect: LayoutRect,
   1703        shadow_radius: BorderRadius,
   1704        prim_shadow_rect: LayoutRect,
   1705        blur_radius: f32,
   1706        clip_mode: BoxShadowClipMode,
   1707    ) -> Self {
   1708        // Get the fractional offsets required to match the
   1709        // source rect with a minimal rect.
   1710        let fract_offset = LayoutPoint::new(
   1711            shadow_rect.min.x.fract().abs(),
   1712            shadow_rect.min.y.fract().abs(),
   1713        );
   1714 
   1715        ClipItemKeyKind::BoxShadow(
   1716            fract_offset.into(),
   1717            shadow_rect.size().into(),
   1718            shadow_radius.into(),
   1719            prim_shadow_rect.into(),
   1720            Au::from_f32_px(blur_radius),
   1721            clip_mode,
   1722        )
   1723    }
   1724 
   1725    pub fn node_kind(&self) -> ClipNodeKind {
   1726        match *self {
   1727            ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => ClipNodeKind::Rectangle,
   1728 
   1729            ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
   1730            ClipItemKeyKind::RoundedRectangle(..) |
   1731            ClipItemKeyKind::ImageMask(..) |
   1732            ClipItemKeyKind::BoxShadow(..) => ClipNodeKind::Complex,
   1733        }
   1734    }
   1735 }
   1736 
   1737 #[derive(Debug, Copy, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
   1738 #[cfg_attr(feature = "capture", derive(Serialize))]
   1739 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1740 pub struct ClipItemKey {
   1741    pub kind: ClipItemKeyKind,
   1742    pub spatial_node_index: SpatialNodeIndex,
   1743 }
   1744 
   1745 /// The data available about an interned clip node during scene building
   1746 #[derive(Debug, MallocSizeOf)]
   1747 #[cfg_attr(feature = "capture", derive(Serialize))]
   1748 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1749 pub struct ClipInternData {
   1750    pub key: ClipItemKey,
   1751 }
   1752 
   1753 impl intern::InternDebug for ClipItemKey {}
   1754 
   1755 impl intern::Internable for ClipIntern {
   1756    type Key = ClipItemKey;
   1757    type StoreData = ClipNode;
   1758    type InternData = ClipInternData;
   1759    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CLIPS;
   1760 }
   1761 
   1762 #[derive(Debug, MallocSizeOf)]
   1763 #[cfg_attr(feature = "capture", derive(Serialize))]
   1764 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1765 pub enum ClipItemKind {
   1766    Rectangle {
   1767        rect: LayoutRect,
   1768        mode: ClipMode,
   1769    },
   1770    RoundedRectangle {
   1771        rect: LayoutRect,
   1772        radius: BorderRadius,
   1773        mode: ClipMode,
   1774    },
   1775    Image {
   1776        image: ImageKey,
   1777        rect: LayoutRect,
   1778        polygon_handle: Option<PolygonDataHandle>,
   1779    },
   1780    BoxShadow {
   1781        source: BoxShadowClipSource,
   1782    },
   1783 }
   1784 
   1785 #[derive(Debug, MallocSizeOf)]
   1786 #[cfg_attr(feature = "capture", derive(Serialize))]
   1787 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1788 pub struct ClipItem {
   1789    pub kind: ClipItemKind,
   1790    pub spatial_node_index: SpatialNodeIndex,
   1791 }
   1792 
   1793 fn compute_box_shadow_parameters(
   1794    shadow_rect_fract_offset: LayoutPoint,
   1795    shadow_rect_size: LayoutSize,
   1796    mut shadow_radius: BorderRadius,
   1797    prim_shadow_rect: LayoutRect,
   1798    blur_radius: f32,
   1799    clip_mode: BoxShadowClipMode,
   1800 ) -> BoxShadowClipSource {
   1801    // Make sure corners don't overlap.
   1802    ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);
   1803 
   1804    let fract_size = LayoutSize::new(
   1805        shadow_rect_size.width.fract().abs(),
   1806        shadow_rect_size.height.fract().abs(),
   1807    );
   1808 
   1809    // Create a minimal size primitive mask to blur. In this
   1810    // case, we ensure the size of each corner is the same,
   1811    // to simplify the shader logic that stretches the blurred
   1812    // result across the primitive.
   1813    let max_corner_width = shadow_radius.top_left.width
   1814                                .max(shadow_radius.bottom_left.width)
   1815                                .max(shadow_radius.top_right.width)
   1816                                .max(shadow_radius.bottom_right.width);
   1817    let max_corner_height = shadow_radius.top_left.height
   1818                                .max(shadow_radius.bottom_left.height)
   1819                                .max(shadow_radius.top_right.height)
   1820                                .max(shadow_radius.bottom_right.height);
   1821 
   1822    // Get maximum distance that can be affected by given blur radius.
   1823    let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
   1824 
   1825    // If the largest corner is smaller than the blur radius, we need to ensure
   1826    // that it's big enough that the corners don't affect the middle segments.
   1827    let used_corner_width = max_corner_width.max(blur_region);
   1828    let used_corner_height = max_corner_height.max(blur_region);
   1829 
   1830    // Minimal nine-patch size, corner + internal + corner.
   1831    let min_shadow_rect_size = LayoutSize::new(
   1832        2.0 * used_corner_width + blur_region,
   1833        2.0 * used_corner_height + blur_region,
   1834    );
   1835 
   1836    // The minimal rect to blur.
   1837    let mut minimal_shadow_rect = LayoutRect::from_origin_and_size(
   1838        LayoutPoint::new(
   1839            blur_region + shadow_rect_fract_offset.x,
   1840            blur_region + shadow_rect_fract_offset.y,
   1841        ),
   1842        LayoutSize::new(
   1843            min_shadow_rect_size.width + fract_size.width,
   1844            min_shadow_rect_size.height + fract_size.height,
   1845        ),
   1846    );
   1847 
   1848    // If the width or height ends up being bigger than the original
   1849    // primitive shadow rect, just blur the entire rect along that
   1850    // axis and draw that as a simple blit. This is necessary for
   1851    // correctness, since the blur of one corner may affect the blur
   1852    // in another corner.
   1853    let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
   1854    if shadow_rect_size.width < minimal_shadow_rect.width() {
   1855        minimal_shadow_rect.max.x = minimal_shadow_rect.min.x + shadow_rect_size.width;
   1856        stretch_mode_x = BoxShadowStretchMode::Simple;
   1857    }
   1858 
   1859    let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
   1860    if shadow_rect_size.height < minimal_shadow_rect.height() {
   1861        minimal_shadow_rect.max.y = minimal_shadow_rect.min.y + shadow_rect_size.height;
   1862        stretch_mode_y = BoxShadowStretchMode::Simple;
   1863    }
   1864 
   1865    // Expand the shadow rect by enough room for the blur to take effect.
   1866    let shadow_rect_alloc_size = LayoutSize::new(
   1867        2.0 * blur_region + minimal_shadow_rect.width().ceil(),
   1868        2.0 * blur_region + minimal_shadow_rect.height().ceil(),
   1869    );
   1870 
   1871    BoxShadowClipSource {
   1872        original_alloc_size: shadow_rect_alloc_size,
   1873        shadow_rect_alloc_size,
   1874        shadow_radius,
   1875        prim_shadow_rect,
   1876        blur_radius,
   1877        clip_mode,
   1878        stretch_mode_x,
   1879        stretch_mode_y,
   1880        render_task: None,
   1881        cache_key: None,
   1882        minimal_shadow_rect,
   1883    }
   1884 }
   1885 
   1886 impl ClipItemKind {
   1887    pub fn new_box_shadow(
   1888        shadow_rect_fract_offset: LayoutPoint,
   1889        shadow_rect_size: LayoutSize,
   1890        mut shadow_radius: BorderRadius,
   1891        prim_shadow_rect: LayoutRect,
   1892        blur_radius: f32,
   1893        clip_mode: BoxShadowClipMode,
   1894    ) -> Self {
   1895        let mut source = compute_box_shadow_parameters(
   1896            shadow_rect_fract_offset,
   1897            shadow_rect_size,
   1898            shadow_radius,
   1899            prim_shadow_rect,
   1900            blur_radius,
   1901            clip_mode,
   1902        );
   1903 
   1904        fn needed_downscaling(source: &BoxShadowClipSource) -> Option<f32> {
   1905            // This size is fairly arbitrary, but it's the same as the size that
   1906            // we use to avoid caching big blurred stacking contexts.
   1907            //
   1908            // If you change it, ensure that the reftests
   1909            // box-shadow-large-blur-radius-* still hit the downscaling path,
   1910            // and that they render correctly.
   1911            const MAX_SIZE: f32 = 2048.;
   1912 
   1913            let max_dimension =
   1914                source.shadow_rect_alloc_size.width.max(source.shadow_rect_alloc_size.height);
   1915 
   1916            if max_dimension > MAX_SIZE {
   1917                Some(MAX_SIZE / max_dimension)
   1918            } else {
   1919                None
   1920            }
   1921        }
   1922 
   1923        if let Some(downscale) = needed_downscaling(&source) {
   1924            shadow_radius.bottom_left.height *= downscale;
   1925            shadow_radius.bottom_left.width *= downscale;
   1926            shadow_radius.bottom_right.height *= downscale;
   1927            shadow_radius.bottom_right.width *= downscale;
   1928            shadow_radius.top_left.height *= downscale;
   1929            shadow_radius.top_left.width *= downscale;
   1930            shadow_radius.top_right.height *= downscale;
   1931            shadow_radius.top_right.width *= downscale;
   1932 
   1933            let original_alloc_size = source.shadow_rect_alloc_size;
   1934 
   1935            source = compute_box_shadow_parameters(
   1936                shadow_rect_fract_offset * downscale,
   1937                shadow_rect_size * downscale,
   1938                shadow_radius,
   1939                prim_shadow_rect,
   1940                blur_radius * downscale,
   1941                clip_mode,
   1942            );
   1943            source.original_alloc_size = original_alloc_size;
   1944        }
   1945        ClipItemKind::BoxShadow { source }
   1946    }
   1947 
   1948    /// Returns true if this clip mask can run through the fast path
   1949    /// for the given clip item type.
   1950    ///
   1951    /// Note: this logic has to match `ClipBatcher::add` behavior.
   1952    fn supports_fast_path_rendering(&self) -> bool {
   1953        match *self {
   1954            ClipItemKind::Rectangle { .. } |
   1955            ClipItemKind::Image { .. } |
   1956            ClipItemKind::BoxShadow { .. } => {
   1957                false
   1958            }
   1959            ClipItemKind::RoundedRectangle { ref rect, ref radius, .. } => {
   1960                radius.can_use_fast_path_in(rect)
   1961            }
   1962        }
   1963    }
   1964 
   1965    // Get an optional clip rect that a clip source can provide to
   1966    // reduce the size of a primitive region. This is typically
   1967    // used to eliminate redundant clips, and reduce the size of
   1968    // any clip mask that eventually gets drawn.
   1969    pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
   1970        match *self {
   1971            ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => Some(rect),
   1972            ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } => None,
   1973            ClipItemKind::RoundedRectangle { rect, mode: ClipMode::Clip, .. } => Some(rect),
   1974            ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => None,
   1975            ClipItemKind::Image { rect, .. } => {
   1976                Some(rect)
   1977            }
   1978            ClipItemKind::BoxShadow { .. } => None,
   1979        }
   1980    }
   1981 
   1982    fn get_clip_result_complex(
   1983        &self,
   1984        transform: &LayoutToVisTransform,
   1985        prim_rect: &VisRect,
   1986        culling_rect: &VisRect,
   1987    ) -> ClipResult {
   1988        let visible_rect = match prim_rect.intersection(culling_rect) {
   1989            Some(rect) => rect,
   1990            None => return ClipResult::Reject,
   1991        };
   1992 
   1993        let (clip_rect, inner_rect, mode) = match *self {
   1994            ClipItemKind::Rectangle { rect, mode } => {
   1995                (rect, Some(rect), mode)
   1996            }
   1997            ClipItemKind::RoundedRectangle { rect, ref radius, mode } => {
   1998                let inner_clip_rect = extract_inner_rect_safe(&rect, radius);
   1999                (rect, inner_clip_rect, mode)
   2000            }
   2001            ClipItemKind::Image { rect, .. } => {
   2002                (rect, None, ClipMode::Clip)
   2003            }
   2004            ClipItemKind::BoxShadow { .. } => {
   2005                return ClipResult::Partial;
   2006            }
   2007        };
   2008 
   2009        if let Some(ref inner_clip_rect) = inner_rect {
   2010            if let Some(()) = projected_rect_contains(inner_clip_rect, transform, &visible_rect) {
   2011                return match mode {
   2012                    ClipMode::Clip => ClipResult::Accept,
   2013                    ClipMode::ClipOut => ClipResult::Reject,
   2014                };
   2015            }
   2016        }
   2017 
   2018        match mode {
   2019            ClipMode::Clip => {
   2020                let outer_clip_rect = match project_rect(
   2021                    transform,
   2022                    &clip_rect,
   2023                    &culling_rect,
   2024                ) {
   2025                    Some(outer_clip_rect) => outer_clip_rect,
   2026                    None => return ClipResult::Partial,
   2027                };
   2028 
   2029                match outer_clip_rect.intersection(prim_rect) {
   2030                    Some(..) => {
   2031                        ClipResult::Partial
   2032                    }
   2033                    None => {
   2034                        ClipResult::Reject
   2035                    }
   2036                }
   2037            }
   2038            ClipMode::ClipOut => ClipResult::Partial,
   2039        }
   2040    }
   2041 
   2042    // Check how a given clip source affects a local primitive region.
   2043    fn get_clip_result(
   2044        &self,
   2045        prim_rect: &LayoutRect,
   2046    ) -> ClipResult {
   2047        match *self {
   2048            ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => {
   2049                if rect.contains_box(prim_rect) {
   2050                    return ClipResult::Accept;
   2051                }
   2052 
   2053                match rect.intersection(prim_rect) {
   2054                    Some(..) => {
   2055                        ClipResult::Partial
   2056                    }
   2057                    None => {
   2058                        ClipResult::Reject
   2059                    }
   2060                }
   2061            }
   2062            ClipItemKind::Rectangle { rect, mode: ClipMode::ClipOut } => {
   2063                if rect.contains_box(prim_rect) {
   2064                    return ClipResult::Reject;
   2065                }
   2066 
   2067                match rect.intersection(prim_rect) {
   2068                    Some(_) => {
   2069                        ClipResult::Partial
   2070                    }
   2071                    None => {
   2072                        ClipResult::Accept
   2073                    }
   2074                }
   2075            }
   2076            ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::Clip } => {
   2077                // TODO(gw): Consider caching this in the ClipNode
   2078                //           if it ever shows in profiles.
   2079                if rounded_rectangle_contains_box_quick(&rect, radius, &prim_rect) {
   2080                    return ClipResult::Accept;
   2081                }
   2082 
   2083                match rect.intersection(prim_rect) {
   2084                    Some(..) => {
   2085                        ClipResult::Partial
   2086                    }
   2087                    None => {
   2088                        ClipResult::Reject
   2089                    }
   2090                }
   2091            }
   2092            ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::ClipOut } => {
   2093                // TODO(gw): Consider caching this in the ClipNode
   2094                //           if it ever shows in profiles.
   2095                if rounded_rectangle_contains_box_quick(&rect, radius, &prim_rect) {
   2096                    return ClipResult::Reject;
   2097                }
   2098 
   2099                match rect.intersection(prim_rect) {
   2100                    Some(_) => {
   2101                        ClipResult::Partial
   2102                    }
   2103                    None => {
   2104                        ClipResult::Accept
   2105                    }
   2106                }
   2107            }
   2108            ClipItemKind::Image { rect, .. } => {
   2109                match rect.intersection(prim_rect) {
   2110                    Some(..) => {
   2111                        ClipResult::Partial
   2112                    }
   2113                    None => {
   2114                        ClipResult::Reject
   2115                    }
   2116                }
   2117            }
   2118            ClipItemKind::BoxShadow { .. } => {
   2119                ClipResult::Partial
   2120            }
   2121        }
   2122    }
   2123 }
   2124 
   2125 pub fn rounded_rectangle_contains_point(
   2126    point: &LayoutPoint,
   2127    rect: &LayoutRect,
   2128    radii: &BorderRadius
   2129 ) -> bool {
   2130    if !rect.contains(*point) {
   2131        return false;
   2132    }
   2133 
   2134    let top_left_center = rect.min + radii.top_left.to_vector();
   2135    if top_left_center.x > point.x && top_left_center.y > point.y &&
   2136       !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
   2137        return false;
   2138    }
   2139 
   2140    let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
   2141    if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
   2142       !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
   2143        return false;
   2144    }
   2145 
   2146    let top_right_center = rect.top_right() +
   2147                           LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
   2148    if top_right_center.x < point.x && top_right_center.y > point.y &&
   2149       !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
   2150        return false;
   2151    }
   2152 
   2153    let bottom_left_center = rect.bottom_left() +
   2154                             LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
   2155    if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
   2156       !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
   2157        return false;
   2158    }
   2159 
   2160    true
   2161 }
   2162 
   2163 /// Return true if the rounded rectangle described by `container` and `radii`
   2164 /// definitely contains `containee`. May return false negatives, but never false
   2165 /// positives.
   2166 fn rounded_rectangle_contains_box_quick(
   2167    container: &LayoutRect,
   2168    radii: &BorderRadius,
   2169    containee: &LayoutRect,
   2170 ) -> bool {
   2171    if !container.contains_box(containee) {
   2172        return false;
   2173    }
   2174 
   2175    /// Return true if `point` falls within `corner`. This only covers the
   2176    /// upper-left case; we transform the other corners into that form.
   2177    fn foul(point: LayoutPoint, corner: LayoutPoint) -> bool {
   2178        point.x < corner.x && point.y < corner.y
   2179    }
   2180 
   2181    /// Flip `pt` about the y axis (i.e. negate `x`).
   2182    fn flip_x(pt: LayoutPoint) -> LayoutPoint {
   2183        LayoutPoint { x: -pt.x, .. pt }
   2184    }
   2185 
   2186    /// Flip `pt` about the x axis (i.e. negate `y`).
   2187    fn flip_y(pt: LayoutPoint) -> LayoutPoint {
   2188        LayoutPoint { y: -pt.y, .. pt }
   2189    }
   2190 
   2191    if foul(containee.top_left(), container.top_left() + radii.top_left) ||
   2192        foul(flip_x(containee.top_right()), flip_x(container.top_right()) + radii.top_right) ||
   2193        foul(flip_y(containee.bottom_left()), flip_y(container.bottom_left()) + radii.bottom_left) ||
   2194        foul(-containee.bottom_right(), -container.bottom_right() + radii.bottom_right)
   2195    {
   2196        return false;
   2197    }
   2198 
   2199    true
   2200 }
   2201 
   2202 /// Test where point p is relative to the infinite line that passes through the segment
   2203 /// defined by p0 and p1. Point p is on the "left" of the line if the triangle (p0, p1, p)
   2204 /// forms a counter-clockwise triangle.
   2205 /// > 0 is left of the line
   2206 /// < 0 is right of the line
   2207 /// == 0 is on the line
   2208 pub fn is_left_of_line(
   2209    p_x: f32,
   2210    p_y: f32,
   2211    p0_x: f32,
   2212    p0_y: f32,
   2213    p1_x: f32,
   2214    p1_y: f32,
   2215 ) -> f32 {
   2216    (p1_x - p0_x) * (p_y - p0_y) - (p_x - p0_x) * (p1_y - p0_y)
   2217 }
   2218 
   2219 pub fn polygon_contains_point(
   2220    point: &LayoutPoint,
   2221    rect: &LayoutRect,
   2222    polygon: &PolygonKey,
   2223 ) -> bool {
   2224    if !rect.contains(*point) {
   2225        return false;
   2226    }
   2227 
   2228    // p is a LayoutPoint that we'll be comparing to dimensionless PointKeys,
   2229    // which were created from LayoutPoints, so it all works out.
   2230    let p = LayoutPoint::new(point.x - rect.min.x, point.y - rect.min.y);
   2231 
   2232    // Calculate a winding number for this point.
   2233    let mut winding_number: i32 = 0;
   2234 
   2235    let count = polygon.point_count as usize;
   2236 
   2237    for i in 0..count {
   2238        let p0 = polygon.points[i];
   2239        let p1 = polygon.points[(i + 1) % count];
   2240 
   2241        if p0.y <= p.y {
   2242            if p1.y > p.y {
   2243                if is_left_of_line(p.x, p.y, p0.x, p0.y, p1.x, p1.y) > 0.0 {
   2244                    winding_number = winding_number + 1;
   2245                }
   2246            }
   2247        } else if p1.y <= p.y {
   2248            if is_left_of_line(p.x, p.y, p0.x, p0.y, p1.x, p1.y) < 0.0 {
   2249                winding_number = winding_number - 1;
   2250            }
   2251        }
   2252    }
   2253 
   2254    match polygon.fill_rule {
   2255        FillRule::Nonzero => winding_number != 0,
   2256        FillRule::Evenodd => winding_number.abs() % 2 == 1,
   2257    }
   2258 }
   2259 
   2260 pub fn projected_rect_contains(
   2261    source_rect: &LayoutRect,
   2262    transform: &LayoutToVisTransform,
   2263    target_rect: &VisRect,
   2264 ) -> Option<()> {
   2265    let points = [
   2266        transform.transform_point2d(source_rect.top_left())?,
   2267        transform.transform_point2d(source_rect.top_right())?,
   2268        transform.transform_point2d(source_rect.bottom_right())?,
   2269        transform.transform_point2d(source_rect.bottom_left())?,
   2270    ];
   2271    let target_points = [
   2272        target_rect.top_left(),
   2273        target_rect.top_right(),
   2274        target_rect.bottom_right(),
   2275        target_rect.bottom_left(),
   2276    ];
   2277    // iterate the edges of the transformed polygon
   2278    for (a, b) in points
   2279        .iter()
   2280        .cloned()
   2281        .zip(points[1..].iter().cloned().chain(iter::once(points[0])))
   2282    {
   2283        // If this edge is redundant, it's a weird, case, and we shouldn't go
   2284        // length in trying to take the fast path (e.g. when the whole rectangle is a point).
   2285        // If any of edges of the target rectangle crosses the edge, it's not completely
   2286        // inside our transformed polygon either.
   2287        if a.approx_eq(&b) || target_points.iter().any(|&c| (b - a).cross(c - a) < 0.0) {
   2288            return None
   2289        }
   2290    }
   2291 
   2292    Some(())
   2293 }
   2294 
   2295 
   2296 // Add a clip node into the list of clips to be processed
   2297 // for the current clip chain. Returns false if the clip
   2298 // results in the entire primitive being culled out.
   2299 fn add_clip_node_to_current_chain(
   2300    handle: ClipDataHandle,
   2301    prim_spatial_node_index: SpatialNodeIndex,
   2302    pic_spatial_node_index: SpatialNodeIndex,
   2303    visibility_spatial_node_index: SpatialNodeIndex,
   2304    local_clip_rect: &mut LayoutRect,
   2305    clip_node_info: &mut Vec<ClipNodeInfo>,
   2306    pic_coverage_rect: &mut PictureRect,
   2307    clip_data_store: &ClipDataStore,
   2308    spatial_tree: &SpatialTree,
   2309 ) -> bool {
   2310    let clip_node = &clip_data_store[handle];
   2311 
   2312    // Determine the most efficient way to convert between coordinate
   2313    // systems of the primitive and clip node.
   2314    let conversion = ClipSpaceConversion::new(
   2315        prim_spatial_node_index,
   2316        clip_node.item.spatial_node_index,
   2317        visibility_spatial_node_index,
   2318        spatial_tree,
   2319    );
   2320 
   2321    // If we can convert spaces, try to reduce the size of the region
   2322    // requested, and cache the conversion information for the next step.
   2323    if let Some(clip_rect) = clip_node.item.kind.get_local_clip_rect() {
   2324        match conversion {
   2325            ClipSpaceConversion::Local => {
   2326                *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
   2327                    Some(rect) => rect,
   2328                    None => return false,
   2329                };
   2330            }
   2331            ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
   2332                let clip_rect = scale_offset.map_rect(&clip_rect);
   2333                *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
   2334                    Some(rect) => rect,
   2335                    None => return false,
   2336                };
   2337            }
   2338            ClipSpaceConversion::Transform(..) => {
   2339                // Map the local clip rect directly into the same space as the picture
   2340                // surface. This will often be the same space as the clip itself, which
   2341                // results in a reduction in allocated clip mask size.
   2342 
   2343                // For simplicity, only apply this optimization if the clip is in the
   2344                // same coord system as the picture. There are some 'advanced' perspective
   2345                // clip tests in wrench that break without this check. Those cases are
   2346                // never used in Gecko, and we aim to remove support in WR for that
   2347                // in future to simplify the clipping pipeline.
   2348                let pic_coord_system = spatial_tree
   2349                    .get_spatial_node(pic_spatial_node_index)
   2350                    .coordinate_system_id;
   2351 
   2352                let clip_coord_system = spatial_tree
   2353                    .get_spatial_node(clip_node.item.spatial_node_index)
   2354                    .coordinate_system_id;
   2355 
   2356                if pic_coord_system == clip_coord_system {
   2357                    let mapper = SpaceMapper::new_with_target(
   2358                        pic_spatial_node_index,
   2359                        clip_node.item.spatial_node_index,
   2360                        PictureRect::max_rect(),
   2361                        spatial_tree,
   2362                    );
   2363 
   2364                    if let Some(pic_clip_rect) = mapper.map(&clip_rect) {
   2365                        *pic_coverage_rect = pic_clip_rect
   2366                            .intersection(pic_coverage_rect)
   2367                            .unwrap_or(PictureRect::zero());
   2368                    }
   2369                }
   2370            }
   2371        }
   2372    }
   2373 
   2374    clip_node_info.push(ClipNodeInfo {
   2375        conversion,
   2376        handle,
   2377    });
   2378 
   2379    true
   2380 }
   2381 
   2382 #[cfg(test)]
   2383 mod tests {
   2384    use super::projected_rect_contains;
   2385    use euclid::{Transform3D, rect};
   2386 
   2387    #[test]
   2388    fn test_empty_projected_rect() {
   2389        assert_eq!(
   2390            None,
   2391            projected_rect_contains(
   2392                &rect(10.0, 10.0, 0.0, 0.0).to_box2d(),
   2393                &Transform3D::identity(),
   2394                &rect(20.0, 20.0, 10.0, 10.0).to_box2d(),
   2395            ),
   2396            "Empty rectangle is considered to include a non-empty!"
   2397        );
   2398    }
   2399 }
   2400 
   2401 /// PolygonKeys get interned, because it's a convenient way to move the data
   2402 /// for the polygons out of the ClipItemKind and ClipItemKeyKind enums. The
   2403 /// polygon data is both interned and retrieved by the scene builder, and not
   2404 /// accessed at all by the frame builder. Another oddity is that the
   2405 /// PolygonKey contains the totality of the information about the polygon, so
   2406 /// the InternData and StoreData types are both PolygonKey.
   2407 #[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
   2408 #[cfg_attr(any(feature = "serde"), derive(Deserialize, Serialize))]
   2409 pub enum PolygonIntern {}
   2410 
   2411 pub type PolygonDataHandle = intern::Handle<PolygonIntern>;
   2412 
   2413 impl intern::InternDebug for PolygonKey {}
   2414 
   2415 impl intern::Internable for PolygonIntern {
   2416    type Key = PolygonKey;
   2417    type StoreData = PolygonKey;
   2418    type InternData = PolygonKey;
   2419    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_POLYGONS;
   2420 }