Skip to main content

webrender/
clip.rs

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