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