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