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, LayerRect, LayerSize, LayoutPoint, LayoutVector2D, LocalClip};
7 use api::{BoxShadowClipMode, LayerPoint, LayerToWorldScale};
8 use border::{BorderCornerClipSource, ensure_no_corner_overlap};
9 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
10 use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
11 use ellipse::Ellipse;
12 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
13 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
14 use gpu_types::ClipScrollNodeIndex;
15 use prim_store::{ClipData, ImageMaskData};
16 use render_task::to_cache_size;
17 use resource_cache::{CacheItem, ImageRequest, ResourceCache};
18 use util::{LayerToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
19 use util::extract_inner_rect_safe;
20 use std::sync::Arc;
21
22 pub type ClipStore = FreeList<ClipSources>;
23 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
24 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
25
26 #[derive(Clone, Debug)]
27 pub struct ClipRegion {
28 pub main: LayerRect,
29 pub image_mask: Option<ImageMask>,
30 pub complex_clips: Vec<ComplexClipRegion>,
31 }
32
33 impl ClipRegion {
create_for_clip_node( rect: LayerRect, mut complex_clips: Vec<ComplexClipRegion>, mut image_mask: Option<ImageMask>, reference_frame_relative_offset: &LayoutVector2D, ) -> ClipRegion34 pub fn create_for_clip_node(
35 rect: LayerRect,
36 mut complex_clips: Vec<ComplexClipRegion>,
37 mut image_mask: Option<ImageMask>,
38 reference_frame_relative_offset: &LayoutVector2D,
39 ) -> ClipRegion {
40 let rect = rect.translate(reference_frame_relative_offset);
41
42 if let Some(ref mut image_mask) = image_mask {
43 image_mask.rect = image_mask.rect.translate(reference_frame_relative_offset);
44 }
45
46 for complex_clip in complex_clips.iter_mut() {
47 complex_clip.rect = complex_clip.rect.translate(reference_frame_relative_offset);
48 }
49
50 ClipRegion {
51 main: rect,
52 image_mask,
53 complex_clips,
54 }
55 }
56
create_for_clip_node_with_local_clip( local_clip: &LocalClip, reference_frame_relative_offset: &LayoutVector2D ) -> ClipRegion57 pub fn create_for_clip_node_with_local_clip(
58 local_clip: &LocalClip,
59 reference_frame_relative_offset: &LayoutVector2D
60 ) -> ClipRegion {
61 let complex_clips = match local_clip {
62 &LocalClip::Rect(_) => Vec::new(),
63 &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
64 };
65 ClipRegion::create_for_clip_node(
66 *local_clip.clip_rect(),
67 complex_clips,
68 None,
69 reference_frame_relative_offset
70 )
71 }
72 }
73
74 #[derive(Debug)]
75 pub enum ClipSource {
76 Rectangle(LayerRect),
77 RoundedRectangle(LayerRect, BorderRadius, ClipMode),
78 Image(ImageMask),
79 /// TODO(gw): This currently only handles dashed style
80 /// clips, where the border style is dashed for both
81 /// adjacent border edges. Expand to handle dotted style
82 /// and different styles per edge.
83 BorderCorner(BorderCornerClipSource),
84 BoxShadow(BoxShadowClipSource),
85 }
86
87 impl From<ClipRegion> for ClipSources {
from(region: ClipRegion) -> ClipSources88 fn from(region: ClipRegion) -> ClipSources {
89 let mut clips = Vec::new();
90
91 if let Some(info) = region.image_mask {
92 clips.push(ClipSource::Image(info));
93 }
94
95 clips.push(ClipSource::Rectangle(region.main));
96
97 for complex in region.complex_clips {
98 clips.push(ClipSource::new_rounded_rect(
99 complex.rect,
100 complex.radii,
101 complex.mode,
102 ));
103 }
104
105 ClipSources::new(clips)
106 }
107 }
108
109 impl ClipSource {
new_rounded_rect( rect: LayerRect, mut radii: BorderRadius, clip_mode: ClipMode ) -> ClipSource110 pub fn new_rounded_rect(
111 rect: LayerRect,
112 mut radii: BorderRadius,
113 clip_mode: ClipMode
114 ) -> ClipSource {
115 ensure_no_corner_overlap(&mut radii, &rect);
116 ClipSource::RoundedRectangle(
117 rect,
118 radii,
119 clip_mode,
120 )
121 }
122
new_box_shadow( shadow_rect: LayerRect, shadow_radius: BorderRadius, prim_shadow_rect: LayerRect, blur_radius: f32, clip_mode: BoxShadowClipMode, ) -> ClipSource123 pub fn new_box_shadow(
124 shadow_rect: LayerRect,
125 shadow_radius: BorderRadius,
126 prim_shadow_rect: LayerRect,
127 blur_radius: f32,
128 clip_mode: BoxShadowClipMode,
129 ) -> ClipSource {
130 // Get the fractional offsets required to match the
131 // source rect with a minimal rect.
132 let fract_offset = LayerPoint::new(
133 shadow_rect.origin.x.fract().abs(),
134 shadow_rect.origin.y.fract().abs(),
135 );
136 let fract_size = LayerSize::new(
137 shadow_rect.size.width.fract().abs(),
138 shadow_rect.size.height.fract().abs(),
139 );
140
141 // Create a minimal size primitive mask to blur. In this
142 // case, we ensure the size of each corner is the same,
143 // to simplify the shader logic that stretches the blurred
144 // result across the primitive.
145 let max_corner_width = shadow_radius.top_left.width
146 .max(shadow_radius.bottom_left.width)
147 .max(shadow_radius.top_right.width)
148 .max(shadow_radius.bottom_right.width);
149 let max_corner_height = shadow_radius.top_left.height
150 .max(shadow_radius.bottom_left.height)
151 .max(shadow_radius.top_right.height)
152 .max(shadow_radius.bottom_right.height);
153
154 // Get maximum distance that can be affected by given blur radius.
155 let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
156
157 // If the largest corner is smaller than the blur radius, we need to ensure
158 // that it's big enough that the corners don't affect the middle segments.
159 let used_corner_width = max_corner_width.max(blur_region);
160 let used_corner_height = max_corner_height.max(blur_region);
161
162 // Minimal nine-patch size, corner + internal + corner.
163 let min_shadow_rect_size = LayerSize::new(
164 2.0 * used_corner_width + blur_region,
165 2.0 * used_corner_height + blur_region,
166 );
167
168 // The minimal rect to blur.
169 let mut minimal_shadow_rect = LayerRect::new(
170 LayerPoint::new(
171 blur_region + fract_offset.x,
172 blur_region + fract_offset.y,
173 ),
174 LayerSize::new(
175 min_shadow_rect_size.width + fract_size.width,
176 min_shadow_rect_size.height + fract_size.height,
177 ),
178 );
179
180 // If the width or height ends up being bigger than the original
181 // primitive shadow rect, just blur the entire rect and draw that
182 // as a simple blit. This is necessary for correctness, since the
183 // blur of one corner may affect the blur in another corner.
184 minimal_shadow_rect.size.width = minimal_shadow_rect.size.width.min(shadow_rect.size.width);
185 minimal_shadow_rect.size.height = minimal_shadow_rect.size.height.min(shadow_rect.size.height);
186
187 // Expand the shadow rect by enough room for the blur to take effect.
188 let shadow_rect_alloc_size = LayerSize::new(
189 2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
190 2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
191 );
192
193 ClipSource::BoxShadow(BoxShadowClipSource {
194 shadow_rect_alloc_size,
195 shadow_radius,
196 prim_shadow_rect,
197 blur_radius,
198 clip_mode,
199 cache_item: CacheItem::invalid(),
200 cache_key: None,
201 clip_data_handle: GpuCacheHandle::new(),
202 minimal_shadow_rect,
203 })
204 }
205 }
206
207 #[derive(Debug)]
208 pub struct ClipSources {
209 pub clips: Vec<(ClipSource, GpuCacheHandle)>,
210 pub local_inner_rect: LayerRect,
211 pub local_outer_rect: Option<LayerRect>
212 }
213
214 impl ClipSources {
new(clips: Vec<ClipSource>) -> ClipSources215 pub fn new(clips: Vec<ClipSource>) -> ClipSources {
216 let (local_inner_rect, local_outer_rect) = Self::calculate_inner_and_outer_rects(&clips);
217
218 let clips = clips
219 .into_iter()
220 .map(|clip| (clip, GpuCacheHandle::new()))
221 .collect();
222
223 ClipSources {
224 clips,
225 local_inner_rect,
226 local_outer_rect,
227 }
228 }
229
clips(&self) -> &[(ClipSource, GpuCacheHandle)]230 pub fn clips(&self) -> &[(ClipSource, GpuCacheHandle)] {
231 &self.clips
232 }
233
calculate_inner_and_outer_rects(clips: &Vec<ClipSource>) -> (LayerRect, Option<LayerRect>)234 fn calculate_inner_and_outer_rects(clips: &Vec<ClipSource>) -> (LayerRect, Option<LayerRect>) {
235 if clips.is_empty() {
236 return (LayerRect::zero(), None);
237 }
238
239 // Depending on the complexity of the clip, we may either know the outer and/or inner
240 // rect, or neither or these. In the case of a clip-out, we currently set the mask bounds
241 // to be unknown. This is conservative, but ensures correctness. In the future we can make
242 // this a lot more clever with some proper region handling.
243 let mut local_outer = Some(LayerRect::max_rect());
244 let mut local_inner = local_outer;
245 let mut can_calculate_inner_rect = true;
246 let mut can_calculate_outer_rect = false;
247 for source in clips {
248 match *source {
249 ClipSource::Image(ref mask) => {
250 if !mask.repeat {
251 can_calculate_outer_rect = true;
252 local_outer = local_outer.and_then(|r| r.intersection(&mask.rect));
253 }
254 local_inner = None;
255 }
256 ClipSource::Rectangle(rect) => {
257 can_calculate_outer_rect = true;
258 local_outer = local_outer.and_then(|r| r.intersection(&rect));
259 local_inner = local_inner.and_then(|r| r.intersection(&rect));
260 }
261 ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
262 // Once we encounter a clip-out, we just assume the worst
263 // case clip mask size, for now.
264 if mode == ClipMode::ClipOut {
265 can_calculate_inner_rect = false;
266 break;
267 }
268
269 can_calculate_outer_rect = true;
270 local_outer = local_outer.and_then(|r| r.intersection(rect));
271
272 let inner_rect = extract_inner_rect_safe(rect, radius);
273 local_inner = local_inner
274 .and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
275 }
276 ClipSource::BoxShadow(..) |
277 ClipSource::BorderCorner { .. } => {
278 can_calculate_inner_rect = false;
279 break;
280 }
281 }
282 }
283
284 let outer = match can_calculate_outer_rect {
285 true => Some(local_outer.unwrap_or_else(LayerRect::zero)),
286 false => None,
287 };
288
289 let inner = match can_calculate_inner_rect {
290 true => local_inner.unwrap_or_else(LayerRect::zero),
291 false => LayerRect::zero(),
292 };
293
294 (inner, outer)
295 }
296
update( &mut self, gpu_cache: &mut GpuCache, resource_cache: &mut ResourceCache, device_pixel_scale: DevicePixelScale, )297 pub fn update(
298 &mut self,
299 gpu_cache: &mut GpuCache,
300 resource_cache: &mut ResourceCache,
301 device_pixel_scale: DevicePixelScale,
302 ) {
303 for &mut (ref mut source, ref mut handle) in &mut self.clips {
304 if let Some(mut request) = gpu_cache.request(handle) {
305 match *source {
306 ClipSource::Image(ref mask) => {
307 let data = ImageMaskData { local_rect: mask.rect };
308 data.write_gpu_blocks(request);
309 }
310 ClipSource::BoxShadow(ref info) => {
311 request.push([
312 info.shadow_rect_alloc_size.width,
313 info.shadow_rect_alloc_size.height,
314 info.clip_mode as i32 as f32,
315 0.0,
316 ]);
317 request.push(info.prim_shadow_rect);
318 }
319 ClipSource::Rectangle(rect) => {
320 let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
321 data.write(&mut request);
322 }
323 ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
324 let data = ClipData::rounded_rect(rect, radius, mode);
325 data.write(&mut request);
326 }
327 ClipSource::BorderCorner(ref mut source) => {
328 source.write(request);
329 }
330 }
331 }
332
333 match *source {
334 ClipSource::Image(ref mask) => {
335 resource_cache.request_image(
336 ImageRequest {
337 key: mask.image,
338 rendering: ImageRendering::Auto,
339 tile: None,
340 },
341 gpu_cache,
342 );
343 }
344 ClipSource::BoxShadow(ref mut info) => {
345 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
346 // "the image that would be generated by applying to the shadow a
347 // Gaussian blur with a standard deviation equal to half the blur radius."
348 let blur_radius_dp = (info.blur_radius * 0.5 * device_pixel_scale.0).round();
349
350 // Create the cache key for this box-shadow render task.
351 let content_scale = LayerToWorldScale::new(1.0) * device_pixel_scale;
352 let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
353 let bs_cache_key = BoxShadowCacheKey {
354 blur_radius_dp: blur_radius_dp as i32,
355 clip_mode: info.clip_mode,
356 rect_size: (info.shadow_rect_alloc_size * content_scale).round().to_i32(),
357 br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
358 br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
359 br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
360 br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
361 };
362
363 info.cache_key = Some((cache_size, bs_cache_key));
364
365 if let Some(mut request) = gpu_cache.request(&mut info.clip_data_handle) {
366 let data = ClipData::rounded_rect(
367 &info.minimal_shadow_rect,
368 &info.shadow_radius,
369 ClipMode::Clip,
370 );
371
372 data.write(&mut request);
373 }
374 }
375 _ => {}
376 }
377 }
378 }
379
get_screen_bounds( &self, transform: &LayerToWorldFastTransform, device_pixel_scale: DevicePixelScale, ) -> (DeviceIntRect, Option<DeviceIntRect>)380 pub fn get_screen_bounds(
381 &self,
382 transform: &LayerToWorldFastTransform,
383 device_pixel_scale: DevicePixelScale,
384 ) -> (DeviceIntRect, Option<DeviceIntRect>) {
385 // If this translation isn't axis aligned or has a perspective component, don't try to
386 // calculate the inner rectangle. The rectangle that we produce would include potentially
387 // clipped screen area.
388 // TODO(mrobinson): We should eventually try to calculate an inner region or some inner
389 // rectangle so that we can do screen inner rectangle optimizations for these kind of
390 // cilps.
391 let can_calculate_inner_rect =
392 transform.preserves_2d_axis_alignment() && !transform.has_perspective_component();
393 let screen_inner_rect = if can_calculate_inner_rect {
394 calculate_screen_bounding_rect(transform, &self.local_inner_rect, device_pixel_scale)
395 } else {
396 DeviceIntRect::zero()
397 };
398
399 let screen_outer_rect = self.local_outer_rect.map(|outer_rect|
400 calculate_screen_bounding_rect(transform, &outer_rect, device_pixel_scale)
401 );
402
403 (screen_inner_rect, screen_outer_rect)
404 }
405 }
406
407 /// Represents a local rect and a device space
408 /// rectangles that are either outside or inside bounds.
409 #[derive(Clone, Debug, PartialEq)]
410 pub struct Geometry {
411 pub local_rect: LayerRect,
412 pub device_rect: DeviceIntRect,
413 }
414
415 impl From<LayerRect> for Geometry {
from(local_rect: LayerRect) -> Self416 fn from(local_rect: LayerRect) -> Self {
417 Geometry {
418 local_rect,
419 device_rect: DeviceIntRect::zero(),
420 }
421 }
422 }
423
424 pub trait Contains {
contains(&self, point: &LayoutPoint) -> bool425 fn contains(&self, point: &LayoutPoint) -> bool;
426 }
427
428 impl Contains for LocalClip {
contains(&self, point: &LayoutPoint) -> bool429 fn contains(&self, point: &LayoutPoint) -> bool {
430 if !self.clip_rect().contains(point) {
431 return false;
432 }
433 match self {
434 &LocalClip::Rect(..) => true,
435 &LocalClip::RoundedRect(_, complex_clip) => complex_clip.contains(point),
436 }
437 }
438 }
439
440 impl Contains for ComplexClipRegion {
contains(&self, point: &LayoutPoint) -> bool441 fn contains(&self, point: &LayoutPoint) -> bool {
442 rounded_rectangle_contains_point(point, &self.rect, &self.radii)
443 }
444 }
445
rounded_rectangle_contains_point(point: &LayoutPoint, rect: &LayerRect, radii: &BorderRadius) -> bool446 pub fn rounded_rectangle_contains_point(point: &LayoutPoint,
447 rect: &LayerRect,
448 radii: &BorderRadius)
449 -> bool {
450 if !rect.contains(point) {
451 return false;
452 }
453
454 let top_left_center = rect.origin + radii.top_left.to_vector();
455 if top_left_center.x > point.x && top_left_center.y > point.y &&
456 !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
457 return false;
458 }
459
460 let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
461 if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
462 !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
463 return false;
464 }
465
466 let top_right_center = rect.top_right() +
467 LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
468 if top_right_center.x < point.x && top_right_center.y > point.y &&
469 !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
470 return false;
471 }
472
473 let bottom_left_center = rect.bottom_left() +
474 LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
475 if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
476 !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
477 return false;
478 }
479
480 true
481 }
482
483 pub type ClipChainNodeRef = Option<Arc<ClipChainNode>>;
484
485 #[derive(Debug, Clone)]
486 pub struct ClipChainNode {
487 pub work_item: ClipWorkItem,
488 pub local_clip_rect: LayerRect,
489 pub screen_outer_rect: DeviceIntRect,
490 pub screen_inner_rect: DeviceIntRect,
491 pub prev: ClipChainNodeRef,
492 }
493
494 #[derive(Debug, Clone)]
495 pub struct ClipChain {
496 pub parent_index: Option<ClipChainIndex>,
497 pub combined_outer_screen_rect: DeviceIntRect,
498 pub combined_inner_screen_rect: DeviceIntRect,
499 pub nodes: ClipChainNodeRef,
500 }
501
502 impl ClipChain {
empty(screen_rect: &DeviceIntRect) -> ClipChain503 pub fn empty(screen_rect: &DeviceIntRect) -> ClipChain {
504 ClipChain {
505 parent_index: None,
506 combined_inner_screen_rect: *screen_rect,
507 combined_outer_screen_rect: *screen_rect,
508 nodes: None,
509 }
510 }
511
new_with_added_node(&self, new_node: &ClipChainNode) -> ClipChain512 pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> ClipChain {
513 // If the new node's inner rectangle completely surrounds our outer rectangle,
514 // we can discard the new node entirely since it isn't going to affect anything.
515 if new_node.screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
516 return self.clone();
517 }
518
519 let mut new_chain = self.clone();
520 new_chain.add_node(new_node.clone());
521 new_chain
522 }
523
add_node(&mut self, mut new_node: ClipChainNode)524 pub fn add_node(&mut self, mut new_node: ClipChainNode) {
525 new_node.prev = self.nodes.clone();
526
527 // If this clip's outer rectangle is completely enclosed by the clip
528 // chain's inner rectangle, then the only clip that matters from this point
529 // on is this clip. We can disconnect this clip from the parent clip chain.
530 if self.combined_inner_screen_rect.contains_rect(&new_node.screen_outer_rect) {
531 new_node.prev = None;
532 }
533
534 self.combined_outer_screen_rect =
535 self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
536 .unwrap_or_else(DeviceIntRect::zero);
537 self.combined_inner_screen_rect =
538 self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
539 .unwrap_or_else(DeviceIntRect::zero);
540
541 self.nodes = Some(Arc::new(new_node));
542 }
543 }
544
545 pub struct ClipChainNodeIter {
546 pub current: ClipChainNodeRef,
547 }
548
549 impl Iterator for ClipChainNodeIter {
550 type Item = Arc<ClipChainNode>;
551
next(&mut self) -> ClipChainNodeRef552 fn next(&mut self) -> ClipChainNodeRef {
553 let previous = self.current.clone();
554 self.current = match self.current {
555 Some(ref item) => item.prev.clone(),
556 None => return None,
557 };
558 previous
559 }
560 }
561
562 #[derive(Debug, Clone)]
563 #[cfg_attr(feature = "capture", derive(Serialize))]
564 #[cfg_attr(feature = "replay", derive(Deserialize))]
565 pub struct ClipWorkItem {
566 pub scroll_node_data_index: ClipScrollNodeIndex,
567 pub clip_sources: ClipSourcesWeakHandle,
568 pub coordinate_system_id: CoordinateSystemId,
569 }
570
571