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