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