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 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
6 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D};
7 use api::{BoxShadowClipMode, LayoutToWorldScale, PicturePixel, WorldPixel};
8 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
9 use api::{ImageKey};
10 use app_units::Au;
11 use border::{ensure_no_corner_overlap, BorderRadiusAu};
12 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
13 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
14 use ellipse::Ellipse;
15 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
16 use gpu_types::{BoxShadowStretchMode};
17 use image::{self, Repetition};
18 use intern;
19 use prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
20 use prim_store::{PointKey, SizeKey, RectangleKey};
21 use render_task::to_cache_size;
22 use resource_cache::{ImageRequest, ResourceCache};
23 use std::{cmp, u32};
24 use util::{extract_inner_rect_safe, project_rect, ScaleOffset};
25 
26 /*
27 
28  Module Overview
29 
30  There are a number of data structures involved in the clip module:
31 
32  ClipStore - Main interface used by other modules.
33 
34  ClipItem - A single clip item (e.g. a rounded rect, or a box shadow).
35             These are an exposed API type, stored inline in a ClipNode.
36 
37  ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated
38             when a ClipNodeInstance is built from this node (which happens while
39             preparing primitives for render).
40 
41  ClipNodeInstance - A ClipNode with attached positioning information (a spatial
42                     node index). This is stored as a contiguous array of nodes
43                     within the ClipStore.
44 
45     +-----------------------+-----------------------+-----------------------+
46     | ClipNodeInstance      | ClipNodeInstance      | ClipNodeInstance      |
47     +-----------------------+-----------------------+-----------------------+
48     | ClipItem              | ClipItem              | ClipItem              |
49     | Spatial Node Index    | Spatial Node Index    | Spatial Node Index    |
50     | GPU cache handle      | GPU cache handle      | GPU cache handle      |
51     | ...                   | ...                   | ...                   |
52     +-----------------------+-----------------------+-----------------------+
53                0                        1                       2
54 
55        +----------------+    |                                              |
56        | ClipNodeRange  |____|                                              |
57        |    index: 1    |                                                   |
58        |    count: 2    |___________________________________________________|
59        +----------------+
60 
61  ClipNodeRange - A clip item range identifies a range of clip nodes instances.
62                  It is stored as an (index, count).
63 
64  ClipChainNode - A clip chain node contains a handle to an interned clip item,
65                  positioning information (from where the clip was defined), and
66                  an optional parent link to another ClipChainNode. ClipChainId
67                  is an index into an array, or ClipChainId::NONE for no parent.
68 
69     +----------------+    ____+----------------+    ____+----------------+   /---> ClipChainId::NONE
70     | ClipChainNode  |   |    | ClipChainNode  |   |    | ClipChainNode  |   |
71     +----------------+   |    +----------------+   |    +----------------+   |
72     | ClipDataHandle |   |    | ClipDataHandle |   |    | ClipDataHandle |   |
73     | Spatial index  |   |    | Spatial index  |   |    | Spatial index  |   |
74     | Parent Id      |___|    | Parent Id      |___|    | Parent Id      |___|
75     | ...            |        | ...            |        | ...            |
76     +----------------+        +----------------+        +----------------+
77 
78  ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node.
79 
80     When given a clip chain ID, and a local primitive rect and its spatial node, the clip module
81     creates a clip chain instance. This is a struct with various pieces of useful information
82     (such as a local clip rect). It also contains a (index, count)
83     range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant
84     for this clip chain instance. The index buffer structure allows a single array to be used for
85     all of the clip-chain instances built in a single frame. Each entry in the index buffer
86     also stores some flags relevant to the clip node in this positioning context.
87 
88     +----------------------+
89     | ClipChainInstance    |
90     +----------------------+
91     | ...                  |
92     | local_clip_rect      |________________________________________________________________________
93     | clips_range          |_______________                                                        |
94     +----------------------+              |                                                        |
95                                           |                                                        |
96     +------------------+------------------+------------------+------------------+------------------+
97     | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
98     +------------------+------------------+------------------+------------------+------------------+
99     | flags            | flags            | flags            | flags            | flags            |
100     | ...              | ...              | ...              | ...              | ...              |
101     +------------------+------------------+------------------+------------------+------------------+
102 
103  */
104 
105 // Type definitions for interning clip nodes.
106 
107 pub use intern_types::clip::Store as ClipDataStore;
108 use intern_types::clip::Handle as ClipDataHandle;
109 
110 // Result of comparing a clip node instance against a local rect.
111 #[derive(Debug)]
112 enum ClipResult {
113     // The clip does not affect the region at all.
114     Accept,
115     // The clip prevents the region from being drawn.
116     Reject,
117     // The clip affects part of the region. This may
118     // require a clip mask, depending on other factors.
119     Partial,
120 }
121 
122 // A clip node is a single clip source, along with some
123 // positioning information and implementation details
124 // that control where the GPU data for this clip source
125 // can be found.
126 #[derive(Debug)]
127 #[cfg_attr(feature = "capture", derive(Serialize))]
128 #[cfg_attr(feature = "replay", derive(Deserialize))]
129 #[derive(MallocSizeOf)]
130 pub struct ClipNode {
131     pub item: ClipItem,
132     pub gpu_cache_handle: GpuCacheHandle,
133 }
134 
135 // Convert from an interning key for a clip item
136 // to a clip node, which is cached in the document.
137 // TODO(gw): These enums are a bit messy - we should
138 //           convert them to use named fields.
139 impl From<ClipItemKey> for ClipNode {
from(item: ClipItemKey) -> Self140     fn from(item: ClipItemKey) -> Self {
141         let item = match item {
142             ClipItemKey::Rectangle(size, mode) => {
143                 ClipItem::Rectangle(size.into(), mode)
144             }
145             ClipItemKey::RoundedRectangle(size, radius, mode) => {
146                 ClipItem::RoundedRectangle(
147                     size.into(),
148                     radius.into(),
149                     mode,
150                 )
151             }
152             ClipItemKey::ImageMask(size, image, repeat) => {
153                 ClipItem::Image {
154                     image,
155                     size: size.into(),
156                     repeat,
157                 }
158             }
159             ClipItemKey::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
160                 ClipItem::new_box_shadow(
161                     shadow_rect_fract_offset.into(),
162                     shadow_rect_size.into(),
163                     shadow_radius.into(),
164                     prim_shadow_rect.into(),
165                     blur_radius.to_f32_px(),
166                     clip_mode,
167                 )
168             }
169         };
170 
171         ClipNode {
172             item,
173             gpu_cache_handle: GpuCacheHandle::new(),
174         }
175     }
176 }
177 
178 // Flags that are attached to instances of clip nodes.
179 bitflags! {
180     #[cfg_attr(feature = "capture", derive(Serialize))]
181     #[cfg_attr(feature = "replay", derive(Deserialize))]
182     #[derive(MallocSizeOf)]
183     pub struct ClipNodeFlags: u8 {
184         const SAME_SPATIAL_NODE = 0x1;
185         const SAME_COORD_SYSTEM = 0x2;
186     }
187 }
188 
189 // Identifier for a clip chain. Clip chains are stored
190 // in a contiguous array in the clip store. They are
191 // identified by a simple index into that array.
192 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Hash)]
193 #[cfg_attr(feature = "capture", derive(Serialize))]
194 pub struct ClipChainId(pub u32);
195 
196 // The root of each clip chain is the NONE id. The
197 // value is specifically set to u32::MAX so that if
198 // any code accidentally tries to access the root
199 // node, a bounds error will occur.
200 impl ClipChainId {
201     pub const NONE: Self = ClipChainId(u32::MAX);
202     pub const INVALID: Self = ClipChainId(0xDEADBEEF);
203 }
204 
205 // A clip chain node is an id for a range of clip sources,
206 // and a link to a parent clip chain node, or ClipChainId::NONE.
207 #[derive(Clone, Debug, MallocSizeOf)]
208 #[cfg_attr(feature = "capture", derive(Serialize))]
209 pub struct ClipChainNode {
210     pub handle: ClipDataHandle,
211     pub local_pos: LayoutPoint,
212     pub spatial_node_index: SpatialNodeIndex,
213     pub parent_clip_chain_id: ClipChainId,
214 }
215 
216 // When a clip node is found to be valid for a
217 // clip chain instance, it's stored in an index
218 // buffer style structure. This struct contains
219 // an index to the node data itself, as well as
220 // some flags describing how this clip node instance
221 // is positioned.
222 #[derive(Debug, MallocSizeOf)]
223 #[cfg_attr(feature = "capture", derive(Serialize))]
224 #[cfg_attr(feature = "replay", derive(Deserialize))]
225 pub struct ClipNodeInstance {
226     pub handle: ClipDataHandle,
227     pub flags: ClipNodeFlags,
228     pub spatial_node_index: SpatialNodeIndex,
229     pub local_pos: LayoutPoint,
230 
231     pub visible_tiles: Option<Vec<VisibleMaskImageTile>>,
232 }
233 
234 // A range of clip node instances that were found by
235 // building a clip chain instance.
236 #[derive(Debug, Copy, Clone)]
237 #[cfg_attr(feature = "capture", derive(Serialize))]
238 #[cfg_attr(feature = "replay", derive(Deserialize))]
239 pub struct ClipNodeRange {
240     pub first: u32,
241     pub count: u32,
242 }
243 
244 // A helper struct for converting between coordinate systems
245 // of clip sources and primitives.
246 // todo(gw): optimize:
247 //  separate arrays for matrices
248 //  cache and only build as needed.
249 #[derive(Debug, MallocSizeOf)]
250 #[cfg_attr(feature = "capture", derive(Serialize))]
251 enum ClipSpaceConversion {
252     Local,
253     ScaleOffset(ScaleOffset),
254     Transform(LayoutToWorldTransform),
255 }
256 
257 // Temporary information that is cached and reused
258 // during building of a clip chain instance.
259 #[derive(MallocSizeOf)]
260 #[cfg_attr(feature = "capture", derive(Serialize))]
261 struct ClipNodeInfo {
262     conversion: ClipSpaceConversion,
263     handle: ClipDataHandle,
264     local_pos: LayoutPoint,
265     spatial_node_index: SpatialNodeIndex,
266 }
267 
268 impl ClipNodeInfo {
create_instance( &self, node: &ClipNode, clipped_rect: &LayoutRect, gpu_cache: &mut GpuCache, resource_cache: &mut ResourceCache, ) -> ClipNodeInstance269     fn create_instance(
270         &self,
271         node: &ClipNode,
272         clipped_rect: &LayoutRect,
273         gpu_cache: &mut GpuCache,
274         resource_cache: &mut ResourceCache,
275     ) -> ClipNodeInstance {
276         // Calculate some flags that are required for the segment
277         // building logic.
278         let flags = match self.conversion {
279             ClipSpaceConversion::Local => {
280                 ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
281             }
282             ClipSpaceConversion::ScaleOffset(..) => {
283                 ClipNodeFlags::SAME_COORD_SYSTEM
284             }
285             ClipSpaceConversion::Transform(..) => {
286                 ClipNodeFlags::empty()
287             }
288         };
289 
290         let mut visible_tiles = None;
291 
292         if let ClipItem::Image { size, image, repeat } = node.item {
293             let request = ImageRequest {
294                 key: image,
295                 rendering: ImageRendering::Auto,
296                 tile: None,
297             };
298 
299             if let Some(props) = resource_cache.get_image_properties(image) {
300                 if let Some(tile_size) = props.tiling {
301                     let mut mask_tiles = Vec::new();
302                     let mask_rect = LayoutRect::new(self.local_pos, size);
303 
304                     let device_image_size = props.descriptor.size;
305                     let visible_rect = if repeat {
306                         *clipped_rect
307                     } else {
308                         clipped_rect.intersection(&mask_rect).unwrap()
309                     };
310 
311                     let repetitions = image::repetitions(
312                         &mask_rect,
313                         &visible_rect,
314                         size,
315                     );
316 
317                     for Repetition { origin, .. } in repetitions {
318                         let image_rect = LayoutRect {
319                             origin,
320                             size,
321                         };
322                         let tiles = image::tiles(
323                             &image_rect,
324                             &visible_rect,
325                             &device_image_size,
326                             tile_size as i32,
327                         );
328                         for tile in tiles {
329                             resource_cache.request_image(
330                                 request.with_tile(tile.offset),
331                                 gpu_cache,
332                             );
333                             mask_tiles.push(VisibleMaskImageTile {
334                                 tile_offset: tile.offset,
335                                 tile_rect: tile.rect,
336                             });
337                         }
338                     }
339                     visible_tiles = Some(mask_tiles);
340                 } else {
341                     resource_cache.request_image(request, gpu_cache);
342                 }
343             }
344         }
345 
346         ClipNodeInstance {
347             handle: self.handle,
348             flags,
349             spatial_node_index: self.spatial_node_index,
350             local_pos: self.local_pos,
351             visible_tiles,
352         }
353     }
354 }
355 
356 impl ClipNode {
update( &mut self, gpu_cache: &mut GpuCache, device_pixel_scale: DevicePixelScale, )357     pub fn update(
358         &mut self,
359         gpu_cache: &mut GpuCache,
360         device_pixel_scale: DevicePixelScale,
361     ) {
362         match self.item {
363             ClipItem::Image { size, .. } => {
364                 if let Some(request) = gpu_cache.request(&mut self.gpu_cache_handle) {
365                     let data = ImageMaskData {
366                         local_mask_size: size,
367                     };
368                     data.write_gpu_blocks(request);
369                 }
370             }
371             ClipItem::BoxShadow(ref mut info) => {
372                 if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
373                     request.push([
374                         info.original_alloc_size.width,
375                         info.original_alloc_size.height,
376                         info.clip_mode as i32 as f32,
377                         0.0,
378                     ]);
379                     request.push([
380                         info.stretch_mode_x as i32 as f32,
381                         info.stretch_mode_y as i32 as f32,
382                         0.0,
383                         0.0,
384                     ]);
385                     request.push(info.prim_shadow_rect);
386                 }
387 
388                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
389                 // "the image that would be generated by applying to the shadow a
390                 // Gaussian blur with a standard deviation equal to half the blur radius."
391                 let blur_radius_dp = info.blur_radius * 0.5;
392 
393                 // Create scaling from requested size to cache size.
394                 let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
395 
396                 // Create the cache key for this box-shadow render task.
397                 let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
398                 let bs_cache_key = BoxShadowCacheKey {
399                     blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
400                     clip_mode: info.clip_mode,
401                     original_alloc_size: (info.original_alloc_size * content_scale).round().to_i32(),
402                     br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
403                     br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
404                     br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
405                     br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
406                 };
407 
408                 info.cache_key = Some((cache_size, bs_cache_key));
409 
410                 if let Some(mut request) = gpu_cache.request(&mut info.clip_data_handle) {
411                     let data = ClipData::rounded_rect(
412                         info.minimal_shadow_rect.size,
413                         &info.shadow_radius,
414                         ClipMode::Clip,
415                     );
416 
417                     data.write(&mut request);
418                 }
419             }
420             ClipItem::Rectangle(size, mode) => {
421                 if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
422                     let data = ClipData::uniform(size, 0.0, mode);
423                     data.write(&mut request);
424                 }
425             }
426             ClipItem::RoundedRectangle(size, ref radius, mode) => {
427                 if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
428                     let data = ClipData::rounded_rect(size, radius, mode);
429                     data.write(&mut request);
430                 }
431             }
432         }
433     }
434 }
435 
436 /// The main clipping public interface that other modules access.
437 #[derive(MallocSizeOf)]
438 #[cfg_attr(feature = "capture", derive(Serialize))]
439 pub struct ClipStore {
440     pub clip_chain_nodes: Vec<ClipChainNode>,
441     clip_node_instances: Vec<ClipNodeInstance>,
442     clip_node_info: Vec<ClipNodeInfo>,
443 }
444 
445 // A clip chain instance is what gets built for a given clip
446 // chain id + local primitive region + positioning node.
447 #[derive(Debug)]
448 pub struct ClipChainInstance {
449     pub clips_range: ClipNodeRange,
450     // Combined clip rect for clips that are in the
451     // same coordinate system as the primitive.
452     pub local_clip_rect: LayoutRect,
453     pub has_non_local_clips: bool,
454     // If true, this clip chain requires allocation
455     // of a clip mask.
456     pub needs_mask: bool,
457     // Combined clip rect in picture space (may
458     // be more conservative that local_clip_rect).
459     pub pic_clip_rect: PictureRect,
460 }
461 
462 impl ClipChainInstance {
empty() -> Self463     pub fn empty() -> Self {
464         ClipChainInstance {
465             clips_range: ClipNodeRange {
466                 first: 0,
467                 count: 0,
468             },
469             local_clip_rect: LayoutRect::zero(),
470             has_non_local_clips: false,
471             needs_mask: false,
472             pic_clip_rect: PictureRect::zero(),
473         }
474     }
475 }
476 
477 /// Maintains a stack of clip chain ids that are currently active,
478 /// when a clip exists on a picture that has no surface, and is passed
479 /// on down to the child primitive(s).
480 pub struct ClipChainStack {
481     // TODO(gw): Consider using SmallVec, or recycling the clip stacks here.
482     /// A stack of clip chain lists. Each time a new surface is pushed,
483     /// a new entry is added to the main stack. Each time a new picture
484     /// without surface is pushed, it adds the picture clip chain to the
485     /// current stack list.
486     pub stack: Vec<Vec<ClipChainId>>,
487 }
488 
489 impl ClipChainStack {
new() -> Self490     pub fn new() -> Self {
491         ClipChainStack {
492             stack: vec![vec![]],
493         }
494     }
495 
496     /// Push a clip chain root onto the currently active list.
push_clip(&mut self, clip_chain_id: ClipChainId)497     pub fn push_clip(&mut self, clip_chain_id: ClipChainId) {
498         self.stack.last_mut().unwrap().push(clip_chain_id);
499     }
500 
501     /// Pop a clip chain root from the currently active list.
pop_clip(&mut self)502     pub fn pop_clip(&mut self) {
503         self.stack.last_mut().unwrap().pop().unwrap();
504     }
505 
506     /// When a surface is created, it takes all clips and establishes a new
507     /// stack of clips to be propagated.
push_surface(&mut self)508     pub fn push_surface(&mut self) {
509         self.stack.push(Vec::new());
510     }
511 
512     /// Pop a surface from the clip chain stack
pop_surface(&mut self)513     pub fn pop_surface(&mut self) {
514         self.stack.pop().unwrap();
515     }
516 
517     /// Get the list of currently active clip chains
current_clips(&self) -> &[ClipChainId]518     pub fn current_clips(&self) -> &[ClipChainId] {
519         self.stack.last().unwrap()
520     }
521 }
522 
523 impl ClipStore {
new() -> Self524     pub fn new() -> Self {
525         ClipStore {
526             clip_chain_nodes: Vec::new(),
527             clip_node_instances: Vec::new(),
528             clip_node_info: Vec::new(),
529         }
530     }
531 
get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode532     pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
533         &self.clip_chain_nodes[clip_chain_id.0 as usize]
534     }
535 
add_clip_chain_node( &mut self, handle: ClipDataHandle, local_pos: LayoutPoint, spatial_node_index: SpatialNodeIndex, parent_clip_chain_id: ClipChainId, ) -> ClipChainId536     pub fn add_clip_chain_node(
537         &mut self,
538         handle: ClipDataHandle,
539         local_pos: LayoutPoint,
540         spatial_node_index: SpatialNodeIndex,
541         parent_clip_chain_id: ClipChainId,
542     ) -> ClipChainId {
543         let id = ClipChainId(self.clip_chain_nodes.len() as u32);
544         self.clip_chain_nodes.push(ClipChainNode {
545             handle,
546             spatial_node_index,
547             local_pos,
548             parent_clip_chain_id,
549         });
550         id
551     }
552 
get_instance_from_range( &self, node_range: &ClipNodeRange, index: u32, ) -> &ClipNodeInstance553     pub fn get_instance_from_range(
554         &self,
555         node_range: &ClipNodeRange,
556         index: u32,
557     ) -> &ClipNodeInstance {
558         &self.clip_node_instances[(node_range.first + index) as usize]
559     }
560 
561     // The main interface other code uses. Given a local primitive, positioning
562     // information, and a clip chain id, build an optimized clip chain instance.
build_clip_chain_instance( &mut self, clip_chains: &[ClipChainId], local_prim_rect: LayoutRect, local_prim_clip_rect: LayoutRect, spatial_node_index: SpatialNodeIndex, prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>, pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache, resource_cache: &mut ResourceCache, device_pixel_scale: DevicePixelScale, world_rect: &WorldRect, clip_data_store: &mut ClipDataStore, ) -> Option<ClipChainInstance>563     pub fn build_clip_chain_instance(
564         &mut self,
565         clip_chains: &[ClipChainId],
566         local_prim_rect: LayoutRect,
567         local_prim_clip_rect: LayoutRect,
568         spatial_node_index: SpatialNodeIndex,
569         prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
570         pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
571         clip_scroll_tree: &ClipScrollTree,
572         gpu_cache: &mut GpuCache,
573         resource_cache: &mut ResourceCache,
574         device_pixel_scale: DevicePixelScale,
575         world_rect: &WorldRect,
576         clip_data_store: &mut ClipDataStore,
577     ) -> Option<ClipChainInstance> {
578         let mut local_clip_rect = local_prim_clip_rect;
579 
580         // Walk the clip chain to build local rects, and collect the
581         // smallest possible local/device clip area.
582 
583         self.clip_node_info.clear();
584 
585         for clip_chain_root in clip_chains {
586             let mut current_clip_chain_id = *clip_chain_root;
587 
588             // for each clip chain node
589             while current_clip_chain_id != ClipChainId::NONE {
590                 let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
591 
592                 if !add_clip_node_to_current_chain(
593                     clip_chain_node,
594                     spatial_node_index,
595                     &mut local_clip_rect,
596                     &mut self.clip_node_info,
597                     clip_data_store,
598                     clip_scroll_tree,
599                 ) {
600                     return None;
601                 }
602 
603                 current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
604             }
605         }
606 
607         let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
608         let pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
609         let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?;
610 
611         // Now, we've collected all the clip nodes that *potentially* affect this
612         // primitive region, and reduced the size of the prim region as much as possible.
613 
614         // Run through the clip nodes, and see which ones affect this prim region.
615 
616         let first_clip_node_index = self.clip_node_instances.len() as u32;
617         let mut has_non_local_clips = false;
618         let mut needs_mask = false;
619 
620         // For each potential clip node
621         for node_info in self.clip_node_info.drain(..) {
622             let node = &mut clip_data_store[node_info.handle];
623 
624             // See how this clip affects the prim region.
625             let clip_result = match node_info.conversion {
626                 ClipSpaceConversion::Local => {
627                     node.item.get_clip_result(node_info.local_pos, &local_bounding_rect)
628                 }
629                 ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
630                     has_non_local_clips = true;
631                     node.item.get_clip_result(node_info.local_pos, &scale_offset.unmap_rect(&local_bounding_rect))
632                 }
633                 ClipSpaceConversion::Transform(ref transform) => {
634                     has_non_local_clips = true;
635                     node.item.get_clip_result_complex(
636                         node_info.local_pos,
637                         transform,
638                         &world_clip_rect,
639                         world_rect,
640                     )
641                 }
642             };
643 
644             match clip_result {
645                 ClipResult::Accept => {
646                     // Doesn't affect the primitive at all, so skip adding to list
647                 }
648                 ClipResult::Reject => {
649                     // Completely clips the supplied prim rect
650                     return None;
651                 }
652                 ClipResult::Partial => {
653                     // Needs a mask -> add to clip node indices
654 
655                     // TODO(gw): Ensure this only runs once on each node per frame?
656                     node.update(
657                         gpu_cache,
658                         device_pixel_scale,
659                     );
660 
661                     // Create the clip node instance for this clip node
662                     let instance = node_info.create_instance(
663                         node,
664                         &local_bounding_rect,
665                         gpu_cache,
666                         resource_cache,
667                     );
668 
669                     // As a special case, a partial accept of a clip rect that is
670                     // in the same coordinate system as the primitive doesn't need
671                     // a clip mask. Instead, it can be handled by the primitive
672                     // vertex shader as part of the local clip rect. This is an
673                     // important optimization for reducing the number of clip
674                     // masks that are allocated on common pages.
675                     needs_mask |= match node.item {
676                         ClipItem::Rectangle(_, ClipMode::ClipOut) |
677                         ClipItem::RoundedRectangle(..) |
678                         ClipItem::Image { .. } |
679                         ClipItem::BoxShadow(..) => {
680                             true
681                         }
682 
683                         ClipItem::Rectangle(_, ClipMode::Clip) => {
684                             !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
685                         }
686                     };
687 
688                     // Store this in the index buffer for this clip chain instance.
689                     self.clip_node_instances.push(instance);
690                 }
691             }
692         }
693 
694         // Get the range identifying the clip nodes in the index buffer.
695         let clips_range = ClipNodeRange {
696             first: first_clip_node_index,
697             count: self.clip_node_instances.len() as u32 - first_clip_node_index,
698         };
699 
700         // Return a valid clip chain instance
701         Some(ClipChainInstance {
702             clips_range,
703             has_non_local_clips,
704             local_clip_rect,
705             pic_clip_rect,
706             needs_mask,
707         })
708     }
709 
clear_old_instances(&mut self)710     pub fn clear_old_instances(&mut self) {
711         self.clip_node_instances.clear();
712     }
713 }
714 
715 pub struct ComplexTranslateIter<I> {
716     source: I,
717     offset: LayoutVector2D,
718 }
719 
720 impl<I: Iterator<Item = ComplexClipRegion>> Iterator for ComplexTranslateIter<I> {
721     type Item = ComplexClipRegion;
next(&mut self) -> Option<Self::Item>722     fn next(&mut self) -> Option<Self::Item> {
723         self.source
724             .next()
725             .map(|mut complex| {
726                 complex.rect = complex.rect.translate(&self.offset);
727                 complex
728             })
729     }
730 }
731 
732 #[derive(Clone, Debug)]
733 pub struct ClipRegion<I> {
734     pub main: LayoutRect,
735     pub image_mask: Option<ImageMask>,
736     pub complex_clips: I,
737 }
738 
739 impl<J> ClipRegion<ComplexTranslateIter<J>> {
create_for_clip_node( rect: LayoutRect, complex_clips: J, mut image_mask: Option<ImageMask>, reference_frame_relative_offset: &LayoutVector2D, ) -> Self where J: Iterator<Item = ComplexClipRegion>740     pub fn create_for_clip_node(
741         rect: LayoutRect,
742         complex_clips: J,
743         mut image_mask: Option<ImageMask>,
744         reference_frame_relative_offset: &LayoutVector2D,
745     ) -> Self
746     where
747         J: Iterator<Item = ComplexClipRegion>
748     {
749         if let Some(ref mut image_mask) = image_mask {
750             image_mask.rect = image_mask.rect.translate(reference_frame_relative_offset);
751         }
752 
753         ClipRegion {
754             main: rect.translate(reference_frame_relative_offset),
755             image_mask,
756             complex_clips: ComplexTranslateIter {
757                 source: complex_clips,
758                 offset: *reference_frame_relative_offset,
759             },
760         }
761     }
762 }
763 
764 impl ClipRegion<Option<ComplexClipRegion>> {
create_for_clip_node_with_local_clip( local_clip: &LayoutRect, reference_frame_relative_offset: &LayoutVector2D ) -> Self765     pub fn create_for_clip_node_with_local_clip(
766         local_clip: &LayoutRect,
767         reference_frame_relative_offset: &LayoutVector2D
768     ) -> Self {
769         ClipRegion {
770             main: local_clip.translate(reference_frame_relative_offset),
771             image_mask: None,
772             complex_clips: None,
773         }
774     }
775 }
776 
777 // The ClipItemKey is a hashable representation of the contents
778 // of a clip item. It is used during interning to de-duplicate
779 // clip nodes between frames and display lists. This allows quick
780 // comparison of clip node equality by handle, and also allows
781 // the uploaded GPU cache handle to be retained between display lists.
782 // TODO(gw): Maybe we should consider constructing these directly
783 //           in the DL builder?
784 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
785 #[cfg_attr(feature = "capture", derive(Serialize))]
786 #[cfg_attr(feature = "replay", derive(Deserialize))]
787 pub enum ClipItemKey {
788     Rectangle(SizeKey, ClipMode),
789     RoundedRectangle(SizeKey, BorderRadiusAu, ClipMode),
790     ImageMask(SizeKey, ImageKey, bool),
791     BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode),
792 }
793 
794 impl ClipItemKey {
rectangle(size: LayoutSize, mode: ClipMode) -> Self795     pub fn rectangle(size: LayoutSize, mode: ClipMode) -> Self {
796         ClipItemKey::Rectangle(size.into(), mode)
797     }
798 
rounded_rect(size: LayoutSize, mut radii: BorderRadius, mode: ClipMode) -> Self799     pub fn rounded_rect(size: LayoutSize, mut radii: BorderRadius, mode: ClipMode) -> Self {
800         if radii.is_zero() {
801             ClipItemKey::rectangle(size, mode)
802         } else {
803             ensure_no_corner_overlap(&mut radii, size);
804             ClipItemKey::RoundedRectangle(
805                 size.into(),
806                 radii.into(),
807                 mode,
808             )
809         }
810     }
811 
image_mask(image_mask: &ImageMask) -> Self812     pub fn image_mask(image_mask: &ImageMask) -> Self {
813         ClipItemKey::ImageMask(
814             image_mask.rect.size.into(),
815             image_mask.image,
816             image_mask.repeat,
817         )
818     }
819 
box_shadow( shadow_rect: LayoutRect, shadow_radius: BorderRadius, prim_shadow_rect: LayoutRect, blur_radius: f32, clip_mode: BoxShadowClipMode, ) -> Self820     pub fn box_shadow(
821         shadow_rect: LayoutRect,
822         shadow_radius: BorderRadius,
823         prim_shadow_rect: LayoutRect,
824         blur_radius: f32,
825         clip_mode: BoxShadowClipMode,
826     ) -> Self {
827         // Get the fractional offsets required to match the
828         // source rect with a minimal rect.
829         let fract_offset = LayoutPoint::new(
830             shadow_rect.origin.x.fract().abs(),
831             shadow_rect.origin.y.fract().abs(),
832         );
833 
834         ClipItemKey::BoxShadow(
835             fract_offset.into(),
836             shadow_rect.size.into(),
837             shadow_radius.into(),
838             prim_shadow_rect.into(),
839             Au::from_f32_px(blur_radius),
840             clip_mode,
841         )
842     }
843 }
844 
845 impl intern::InternDebug for ClipItemKey {}
846 
847 #[derive(Debug, MallocSizeOf)]
848 #[cfg_attr(feature = "capture", derive(Serialize))]
849 #[cfg_attr(feature = "replay", derive(Deserialize))]
850 pub enum ClipItem {
851     Rectangle(LayoutSize, ClipMode),
852     RoundedRectangle(LayoutSize, BorderRadius, ClipMode),
853     Image {
854         image: ImageKey,
855         size: LayoutSize,
856         repeat: bool,
857     },
858     BoxShadow(BoxShadowClipSource),
859 }
860 
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, ) -> BoxShadowClipSource861 fn compute_box_shadow_parameters(
862     shadow_rect_fract_offset: LayoutPoint,
863     shadow_rect_size: LayoutSize,
864     mut shadow_radius: BorderRadius,
865     prim_shadow_rect: LayoutRect,
866     blur_radius: f32,
867     clip_mode: BoxShadowClipMode,
868 ) -> BoxShadowClipSource {
869     // Make sure corners don't overlap.
870     ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);
871 
872     let fract_size = LayoutSize::new(
873         shadow_rect_size.width.fract().abs(),
874         shadow_rect_size.height.fract().abs(),
875     );
876 
877     // Create a minimal size primitive mask to blur. In this
878     // case, we ensure the size of each corner is the same,
879     // to simplify the shader logic that stretches the blurred
880     // result across the primitive.
881     let max_corner_width = shadow_radius.top_left.width
882                                 .max(shadow_radius.bottom_left.width)
883                                 .max(shadow_radius.top_right.width)
884                                 .max(shadow_radius.bottom_right.width);
885     let max_corner_height = shadow_radius.top_left.height
886                                 .max(shadow_radius.bottom_left.height)
887                                 .max(shadow_radius.top_right.height)
888                                 .max(shadow_radius.bottom_right.height);
889 
890     // Get maximum distance that can be affected by given blur radius.
891     let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
892 
893     // If the largest corner is smaller than the blur radius, we need to ensure
894     // that it's big enough that the corners don't affect the middle segments.
895     let used_corner_width = max_corner_width.max(blur_region);
896     let used_corner_height = max_corner_height.max(blur_region);
897 
898     // Minimal nine-patch size, corner + internal + corner.
899     let min_shadow_rect_size = LayoutSize::new(
900         2.0 * used_corner_width + blur_region,
901         2.0 * used_corner_height + blur_region,
902     );
903 
904     // The minimal rect to blur.
905     let mut minimal_shadow_rect = LayoutRect::new(
906         LayoutPoint::new(
907             blur_region + shadow_rect_fract_offset.x,
908             blur_region + shadow_rect_fract_offset.y,
909         ),
910         LayoutSize::new(
911             min_shadow_rect_size.width + fract_size.width,
912             min_shadow_rect_size.height + fract_size.height,
913         ),
914     );
915 
916     // If the width or height ends up being bigger than the original
917     // primitive shadow rect, just blur the entire rect along that
918     // axis and draw that as a simple blit. This is necessary for
919     // correctness, since the blur of one corner may affect the blur
920     // in another corner.
921     let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
922     if shadow_rect_size.width < minimal_shadow_rect.size.width {
923         minimal_shadow_rect.size.width = shadow_rect_size.width;
924         stretch_mode_x = BoxShadowStretchMode::Simple;
925     }
926 
927     let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
928     if shadow_rect_size.height < minimal_shadow_rect.size.height {
929         minimal_shadow_rect.size.height = shadow_rect_size.height;
930         stretch_mode_y = BoxShadowStretchMode::Simple;
931     }
932 
933     // Expand the shadow rect by enough room for the blur to take effect.
934     let shadow_rect_alloc_size = LayoutSize::new(
935         2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
936         2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
937     );
938 
939     BoxShadowClipSource {
940         original_alloc_size: shadow_rect_alloc_size,
941         shadow_rect_alloc_size,
942         shadow_radius,
943         prim_shadow_rect,
944         blur_radius,
945         clip_mode,
946         stretch_mode_x,
947         stretch_mode_y,
948         cache_handle: None,
949         cache_key: None,
950         clip_data_handle: GpuCacheHandle::new(),
951         minimal_shadow_rect,
952     }
953 }
954 
955 impl ClipItem {
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, ) -> Self956     pub fn new_box_shadow(
957         shadow_rect_fract_offset: LayoutPoint,
958         shadow_rect_size: LayoutSize,
959         mut shadow_radius: BorderRadius,
960         prim_shadow_rect: LayoutRect,
961         blur_radius: f32,
962         clip_mode: BoxShadowClipMode,
963     ) -> Self {
964         let mut source = compute_box_shadow_parameters(
965             shadow_rect_fract_offset,
966             shadow_rect_size,
967             shadow_radius,
968             prim_shadow_rect,
969             blur_radius,
970             clip_mode,
971         );
972 
973         fn needed_downscaling(source: &BoxShadowClipSource) -> Option<f32> {
974             // This size is fairly arbitrary, but it's the same as the size that
975             // we use to avoid caching big blurred stacking contexts.
976             //
977             // If you change it, ensure that the reftests
978             // box-shadow-large-blur-radius-* still hit the downscaling path,
979             // and that they render correctly.
980             const MAX_SIZE: f32 = 2048.;
981 
982             let max_dimension =
983                 source.shadow_rect_alloc_size.width.max(source.shadow_rect_alloc_size.height);
984 
985             if max_dimension > MAX_SIZE {
986                 Some(MAX_SIZE / max_dimension)
987             } else {
988                 None
989             }
990         }
991 
992         if let Some(downscale) = needed_downscaling(&source) {
993             shadow_radius.bottom_left.height *= downscale;
994             shadow_radius.bottom_left.width *= downscale;
995             shadow_radius.bottom_right.height *= downscale;
996             shadow_radius.bottom_right.width *= downscale;
997             shadow_radius.top_left.height *= downscale;
998             shadow_radius.top_left.width *= downscale;
999             shadow_radius.top_right.height *= downscale;
1000             shadow_radius.top_right.width *= downscale;
1001 
1002             let original_alloc_size = source.shadow_rect_alloc_size;
1003 
1004             source = compute_box_shadow_parameters(
1005                 shadow_rect_fract_offset * downscale,
1006                 shadow_rect_size * downscale,
1007                 shadow_radius,
1008                 prim_shadow_rect,
1009                 blur_radius * downscale,
1010                 clip_mode,
1011             );
1012             source.original_alloc_size = original_alloc_size;
1013         }
1014         ClipItem::BoxShadow(source)
1015     }
1016 
1017     // Get an optional clip rect that a clip source can provide to
1018     // reduce the size of a primitive region. This is typically
1019     // used to eliminate redundant clips, and reduce the size of
1020     // any clip mask that eventually gets drawn.
get_local_clip_rect(&self, local_pos: LayoutPoint) -> Option<LayoutRect>1021     pub fn get_local_clip_rect(&self, local_pos: LayoutPoint) -> Option<LayoutRect> {
1022         let size = match *self {
1023             ClipItem::Rectangle(size, ClipMode::Clip) => Some(size),
1024             ClipItem::Rectangle(_, ClipMode::ClipOut) => None,
1025             ClipItem::RoundedRectangle(size, _, ClipMode::Clip) => Some(size),
1026             ClipItem::RoundedRectangle(_, _, ClipMode::ClipOut) => None,
1027             ClipItem::Image { repeat, size, .. } => {
1028                 if repeat {
1029                     None
1030                 } else {
1031                     Some(size)
1032                 }
1033             }
1034             ClipItem::BoxShadow(..) => None,
1035         };
1036 
1037         size.map(|size| {
1038             LayoutRect::new(local_pos, size)
1039         })
1040     }
1041 
get_clip_result_complex( &self, local_pos: LayoutPoint, transform: &LayoutToWorldTransform, prim_world_rect: &WorldRect, world_rect: &WorldRect, ) -> ClipResult1042     fn get_clip_result_complex(
1043         &self,
1044         local_pos: LayoutPoint,
1045         transform: &LayoutToWorldTransform,
1046         prim_world_rect: &WorldRect,
1047         world_rect: &WorldRect,
1048     ) -> ClipResult {
1049         let (clip_rect, inner_rect) = match *self {
1050             ClipItem::Rectangle(size, ClipMode::Clip) => {
1051                 let clip_rect = LayoutRect::new(local_pos, size);
1052                 (clip_rect, Some(clip_rect))
1053             }
1054             ClipItem::RoundedRectangle(size, ref radius, ClipMode::Clip) => {
1055                 let clip_rect = LayoutRect::new(local_pos, size);
1056                 let inner_clip_rect = extract_inner_rect_safe(&clip_rect, radius);
1057                 (clip_rect, inner_clip_rect)
1058             }
1059             ClipItem::Rectangle(_, ClipMode::ClipOut) |
1060             ClipItem::RoundedRectangle(_, _, ClipMode::ClipOut) |
1061             ClipItem::Image { .. } |
1062             ClipItem::BoxShadow(..) => {
1063                 return ClipResult::Partial
1064             }
1065         };
1066 
1067         let inner_clip_rect = inner_rect.and_then(|ref inner_rect| {
1068             project_inner_rect(transform, inner_rect)
1069         });
1070 
1071         if let Some(inner_clip_rect) = inner_clip_rect {
1072             if inner_clip_rect.contains_rect(prim_world_rect) {
1073                 return ClipResult::Accept;
1074             }
1075         }
1076 
1077         let outer_clip_rect = match project_rect(
1078             transform,
1079             &clip_rect,
1080             world_rect,
1081         ) {
1082             Some(outer_clip_rect) => outer_clip_rect,
1083             None => return ClipResult::Partial,
1084         };
1085 
1086         match outer_clip_rect.intersection(prim_world_rect) {
1087             Some(..) => {
1088                 ClipResult::Partial
1089             }
1090             None => {
1091                 ClipResult::Reject
1092             }
1093         }
1094     }
1095 
1096     // Check how a given clip source affects a local primitive region.
get_clip_result( &self, local_pos: LayoutPoint, prim_rect: &LayoutRect, ) -> ClipResult1097     fn get_clip_result(
1098         &self,
1099         local_pos: LayoutPoint,
1100         prim_rect: &LayoutRect,
1101     ) -> ClipResult {
1102         match *self {
1103             ClipItem::Rectangle(size, ClipMode::Clip) => {
1104                 let clip_rect = LayoutRect::new(local_pos, size);
1105 
1106                 if clip_rect.contains_rect(prim_rect) {
1107                     return ClipResult::Accept;
1108                 }
1109 
1110                 match clip_rect.intersection(prim_rect) {
1111                     Some(..) => {
1112                         ClipResult::Partial
1113                     }
1114                     None => {
1115                         ClipResult::Reject
1116                     }
1117                 }
1118             }
1119             ClipItem::Rectangle(size, ClipMode::ClipOut) => {
1120                 let clip_rect = LayoutRect::new(local_pos, size);
1121 
1122                 if clip_rect.contains_rect(prim_rect) {
1123                     return ClipResult::Reject;
1124                 }
1125 
1126                 match clip_rect.intersection(prim_rect) {
1127                     Some(_) => {
1128                         ClipResult::Partial
1129                     }
1130                     None => {
1131                         ClipResult::Accept
1132                     }
1133                 }
1134             }
1135             ClipItem::RoundedRectangle(size, ref radius, ClipMode::Clip) => {
1136                 let clip_rect = LayoutRect::new(local_pos, size);
1137 
1138                 // TODO(gw): Consider caching this in the ClipNode
1139                 //           if it ever shows in profiles.
1140                 // TODO(gw): extract_inner_rect_safe is overly
1141                 //           conservative for this code!
1142                 let inner_clip_rect = extract_inner_rect_safe(&clip_rect, radius);
1143                 if let Some(inner_clip_rect) = inner_clip_rect {
1144                     if inner_clip_rect.contains_rect(prim_rect) {
1145                         return ClipResult::Accept;
1146                     }
1147                 }
1148 
1149                 match clip_rect.intersection(prim_rect) {
1150                     Some(..) => {
1151                         ClipResult::Partial
1152                     }
1153                     None => {
1154                         ClipResult::Reject
1155                     }
1156                 }
1157             }
1158             ClipItem::RoundedRectangle(size, ref radius, ClipMode::ClipOut) => {
1159                 let clip_rect = LayoutRect::new(local_pos, size);
1160 
1161                 // TODO(gw): Consider caching this in the ClipNode
1162                 //           if it ever shows in profiles.
1163                 // TODO(gw): extract_inner_rect_safe is overly
1164                 //           conservative for this code!
1165                 let inner_clip_rect = extract_inner_rect_safe(&clip_rect, radius);
1166                 if let Some(inner_clip_rect) = inner_clip_rect {
1167                     if inner_clip_rect.contains_rect(prim_rect) {
1168                         return ClipResult::Reject;
1169                     }
1170                 }
1171 
1172                 match clip_rect.intersection(prim_rect) {
1173                     Some(_) => {
1174                         ClipResult::Partial
1175                     }
1176                     None => {
1177                         ClipResult::Accept
1178                     }
1179                 }
1180             }
1181             ClipItem::Image { size, repeat, .. } => {
1182                 if repeat {
1183                     ClipResult::Partial
1184                 } else {
1185                     let mask_rect = LayoutRect::new(local_pos, size);
1186                     match mask_rect.intersection(prim_rect) {
1187                         Some(..) => {
1188                             ClipResult::Partial
1189                         }
1190                         None => {
1191                             ClipResult::Reject
1192                         }
1193                     }
1194                 }
1195             }
1196             ClipItem::BoxShadow(..) => {
1197                 ClipResult::Partial
1198             }
1199         }
1200     }
1201 }
1202 
1203 /// Represents a local rect and a device space
1204 /// rectangles that are either outside or inside bounds.
1205 #[derive(Clone, Debug, PartialEq)]
1206 pub struct Geometry {
1207     pub local_rect: LayoutRect,
1208     pub device_rect: DeviceIntRect,
1209 }
1210 
1211 impl From<LayoutRect> for Geometry {
from(local_rect: LayoutRect) -> Self1212     fn from(local_rect: LayoutRect) -> Self {
1213         Geometry {
1214             local_rect,
1215             device_rect: DeviceIntRect::zero(),
1216         }
1217     }
1218 }
1219 
rounded_rectangle_contains_point( point: &LayoutPoint, rect: &LayoutRect, radii: &BorderRadius ) -> bool1220 pub fn rounded_rectangle_contains_point(
1221     point: &LayoutPoint,
1222     rect: &LayoutRect,
1223     radii: &BorderRadius
1224 ) -> bool {
1225     if !rect.contains(point) {
1226         return false;
1227     }
1228 
1229     let top_left_center = rect.origin + radii.top_left.to_vector();
1230     if top_left_center.x > point.x && top_left_center.y > point.y &&
1231        !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
1232         return false;
1233     }
1234 
1235     let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
1236     if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
1237        !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
1238         return false;
1239     }
1240 
1241     let top_right_center = rect.top_right() +
1242                            LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
1243     if top_right_center.x < point.x && top_right_center.y > point.y &&
1244        !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
1245         return false;
1246     }
1247 
1248     let bottom_left_center = rect.bottom_left() +
1249                              LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
1250     if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
1251        !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
1252         return false;
1253     }
1254 
1255     true
1256 }
1257 
project_inner_rect( transform: &LayoutToWorldTransform, rect: &LayoutRect, ) -> Option<WorldRect>1258 pub fn project_inner_rect(
1259     transform: &LayoutToWorldTransform,
1260     rect: &LayoutRect,
1261 ) -> Option<WorldRect> {
1262     let points = [
1263         transform.transform_point2d(&rect.origin)?,
1264         transform.transform_point2d(&rect.top_right())?,
1265         transform.transform_point2d(&rect.bottom_left())?,
1266         transform.transform_point2d(&rect.bottom_right())?,
1267     ];
1268 
1269     let mut xs = [points[0].x, points[1].x, points[2].x, points[3].x];
1270     let mut ys = [points[0].y, points[1].y, points[2].y, points[3].y];
1271     xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal));
1272     ys.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal));
1273     Some(WorldRect::new(
1274         WorldPoint::new(xs[1], ys[1]),
1275         WorldSize::new(xs[2] - xs[1], ys[2] - ys[1]),
1276     ))
1277 }
1278 
1279 // Add a clip node into the list of clips to be processed
1280 // for the current clip chain. Returns false if the clip
1281 // results in the entire primitive being culled out.
add_clip_node_to_current_chain( node: &ClipChainNode, spatial_node_index: SpatialNodeIndex, local_clip_rect: &mut LayoutRect, clip_node_info: &mut Vec<ClipNodeInfo>, clip_data_store: &ClipDataStore, clip_scroll_tree: &ClipScrollTree, ) -> bool1282 fn add_clip_node_to_current_chain(
1283     node: &ClipChainNode,
1284     spatial_node_index: SpatialNodeIndex,
1285     local_clip_rect: &mut LayoutRect,
1286     clip_node_info: &mut Vec<ClipNodeInfo>,
1287     clip_data_store: &ClipDataStore,
1288     clip_scroll_tree: &ClipScrollTree,
1289 ) -> bool {
1290     let clip_node = &clip_data_store[node.handle];
1291     let clip_spatial_node = &clip_scroll_tree.spatial_nodes[node.spatial_node_index.0 as usize];
1292     let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0 as usize];
1293 
1294     // Determine the most efficient way to convert between coordinate
1295     // systems of the primitive and clip node.
1296     let conversion = if spatial_node_index == node.spatial_node_index {
1297         ClipSpaceConversion::Local
1298     } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
1299         let scale_offset = ref_spatial_node.coordinate_system_relative_scale_offset
1300             .inverse()
1301             .accumulate(&clip_spatial_node.coordinate_system_relative_scale_offset);
1302         ClipSpaceConversion::ScaleOffset(scale_offset)
1303     } else {
1304         match clip_scroll_tree.get_relative_transform(
1305             node.spatial_node_index,
1306             ROOT_SPATIAL_NODE_INDEX,
1307         ) {
1308             None => return true,
1309             Some(relative) => ClipSpaceConversion::Transform(
1310                 relative.flattened.with_destination::<WorldPixel>(),
1311             ),
1312         }
1313     };
1314 
1315     // If we can convert spaces, try to reduce the size of the region
1316     // requested, and cache the conversion information for the next step.
1317     if let Some(clip_rect) = clip_node.item.get_local_clip_rect(node.local_pos) {
1318         match conversion {
1319             ClipSpaceConversion::Local => {
1320                 *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
1321                     Some(rect) => rect,
1322                     None => return false,
1323                 };
1324             }
1325             ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
1326                 let clip_rect = scale_offset.map_rect(&clip_rect);
1327                 *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
1328                     Some(rect) => rect,
1329                     None => return false,
1330                 };
1331             }
1332             ClipSpaceConversion::Transform(..) => {
1333                 // TODO(gw): In the future, we can reduce the size
1334                 //           of the pic_clip_rect here. To do this,
1335                 //           we can use project_rect or the
1336                 //           inverse_rect_footprint method, depending
1337                 //           on the relationship of the clip, pic
1338                 //           and primitive spatial nodes.
1339                 //           I have left this for now until we
1340                 //           find some good test cases where this
1341                 //           would be a worthwhile perf win.
1342             }
1343         }
1344     }
1345 
1346     clip_node_info.push(ClipNodeInfo {
1347         conversion,
1348         local_pos: node.local_pos,
1349         handle: node.handle,
1350         spatial_node_index: node.spatial_node_index,
1351     });
1352 
1353     true
1354 }
1355