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