1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 //! Internal representation of clips in WebRender.
9 //! There are a number of data structures involved in the clip module:
11 //! - ClipStore - Main interface used by other modules.
13 //! - ClipItem - A single clip item (e.g. a rounded rect, or a box shadow).
14 //! These are an exposed API type, stored inline in a ClipNode.
16 //! - ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated
17 //! when a ClipNodeInstance is built from this node (which happens while
18 //! preparing primitives for render).
20 //! ClipNodeInstance - A ClipNode with attached positioning information (a spatial
21 //! node index). This is stored as a contiguous array of nodes
22 //! within the ClipStore.
25 //! +-----------------------+-----------------------+-----------------------+
26 //! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
27 //! +-----------------------+-----------------------+-----------------------+
28 //! | ClipItem | ClipItem | ClipItem |
29 //! | Spatial Node Index | Spatial Node Index | Spatial Node Index |
30 //! | GPU cache handle | GPU cache handle | GPU cache handle |
31 //! | ... | ... | ... |
32 //! +-----------------------+-----------------------+-----------------------+
34 //! +----------------+ | |
35 //! | ClipNodeRange |____| |
37 //! | count: 2 |___________________________________________________|
38 //! +----------------+
41 //! - ClipNodeRange - A clip item range identifies a range of clip nodes instances.
42 //! It is stored as an (index, count).
44 //! - ClipChainNode - A clip chain node contains a handle to an interned clip item,
45 //! positioning information (from where the clip was defined), and
46 //! an optional parent link to another ClipChainNode. ClipChainId
47 //! is an index into an array, or ClipChainId::NONE for no parent.
50 //! +----------------+ ____+----------------+ ____+----------------+ /---> ClipChainId::NONE
51 //! | ClipChainNode | | | ClipChainNode | | | ClipChainNode | |
52 //! +----------------+ | +----------------+ | +----------------+ |
53 //! | ClipDataHandle | | | ClipDataHandle | | | ClipDataHandle | |
54 //! | Spatial index | | | Spatial index | | | Spatial index | |
55 //! | Parent Id |___| | Parent Id |___| | Parent Id |___|
56 //! | ... | | ... | | ... |
57 //! +----------------+ +----------------+ +----------------+
60 //! - ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node.
62 //! When given a clip chain ID, and a local primitive rect and its spatial node, the clip module
63 //! creates a clip chain instance. This is a struct with various pieces of useful information
64 //! (such as a local clip rect). It also contains a (index, count)
65 //! range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant
66 //! for this clip chain instance. The index buffer structure allows a single array to be used for
67 //! all of the clip-chain instances built in a single frame. Each entry in the index buffer
68 //! also stores some flags relevant to the clip node in this positioning context.
71 //! +----------------------+
72 //! | ClipChainInstance |
73 //! +----------------------+
75 //! | local_clip_rect |________________________________________________________________________
76 //! | clips_range |_______________ |
77 //! +----------------------+ | |
79 //! +------------------+------------------+------------------+------------------+------------------+
80 //! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
81 //! +------------------+------------------+------------------+------------------+------------------+
82 //! | flags | flags | flags | flags | flags |
83 //! | ... | ... | ... | ... | ... |
84 //! +------------------+------------------+------------------+------------------+------------------+
87 //! # Rendering clipped primitives
89 //! See the [`segment` module documentation][segment.rs].
92 //! [segment.rs]: ../segment/index.html
95 use api::{BorderRadius, ClipMode, ComplexClipRegion, ImageMask};
96 use api::{BoxShadowClipMode, ClipId, FillRule, ImageKey, ImageRendering, PipelineId};
98 use crate::image_tiling::{self, Repetition};
99 use crate::border::{ensure_no_corner_overlap, BorderRadiusAu};
100 use crate::box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
101 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex, CoordinateSystemId};
102 use crate::ellipse::Ellipse;
103 use crate::gpu_cache::GpuCache;
104 use crate::gpu_types::{BoxShadowStretchMode};
105 use crate::intern::{self, ItemUid};
106 use crate::internal_types::{FastHashMap, FastHashSet};
107 use crate::prim_store::{VisibleMaskImageTile};
108 use crate::prim_store::{PointKey, SizeKey, RectangleKey, PolygonKey};
109 use crate::render_task_cache::to_cache_size;
110 use crate::resource_cache::{ImageRequest, ResourceCache};
111 use crate::space::SpaceMapper;
112 use crate::util::{clamp_to_scale_factor, MaxRect, extract_inner_rect_safe, project_rect, ScaleOffset, VecHelper};
113 use euclid::approxeq::ApproxEq;
114 use std::{iter, ops, u32};
115 use smallvec::SmallVec;
117 // Type definitions for interning clip nodes.
119 #[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq)]
120 #[cfg_attr(any(feature = "serde"), derive(Deserialize, Serialize))]
121 pub enum ClipIntern {}
123 pub type ClipDataStore = intern::DataStore<ClipIntern>;
124 pub type ClipDataHandle = intern::Handle<ClipIntern>;
126 /// Defines a clip that is positioned by a specific spatial node
127 #[cfg_attr(feature = "capture", derive(Serialize))]
128 #[derive(Copy, Clone, PartialEq)]
129 pub struct ClipInstance {
130 /// Handle to the interned clip
131 pub handle: ClipDataHandle,
132 /// Positioning node for this clip
133 pub spatial_node_index: SpatialNodeIndex,
137 /// Construct a new positioned clip
139 handle: ClipDataHandle,
140 spatial_node_index: SpatialNodeIndex,
149 /// Defines a clip instance with some extra information that is available
150 /// during scene building (since interned clips cannot retrieve the underlying
151 /// data from the scene building thread).
152 #[cfg_attr(feature = "capture", derive(Serialize))]
153 #[derive(Copy, Clone)]
154 pub struct SceneClipInstance {
155 /// The interned clip + positioning information that is used during frame building.
156 pub clip: ClipInstance,
157 /// The definition of the clip, used during scene building to optimize clip-chains.
158 pub key: ClipItemKey,
161 /// A clip template defines clips in terms of the public API. Specifically,
162 /// this is a parent `ClipId` and some number of clip instances. See the
163 /// CLIPPING_AND_POSITIONING.md document in doc/ for more information.
164 #[cfg_attr(feature = "capture", derive(Serialize))]
165 pub struct ClipTemplate {
166 /// Parent of this clip, in terms of the public clip API
168 /// List of instances that define this clip template
169 pub clips: SmallVec<[SceneClipInstance; 2]>,
172 /// A helper used during scene building to construct (internal) clip chains from
173 /// the public API definitions (a hierarchy of ClipIds)
174 #[cfg_attr(feature = "capture", derive(Serialize))]
175 pub struct ClipChainBuilder {
176 /// The built clip chain id for this level of the stack
177 clip_chain_id: ClipChainId,
178 /// A list of parent clips in the current clip chain, to de-duplicate clips as
179 /// we build child chains from this level.
180 parent_clips: FastHashSet<(ItemUid, SpatialNodeIndex)>,
181 /// A cache used during building child clip chains. Retained here to avoid
182 /// extra memory allocations each time we build a clip.
183 existing_clips_cache: FastHashSet<(ItemUid, SpatialNodeIndex)>,
184 /// Cache the previous ClipId we built, since it's quite common to share clip
185 /// id between primitives.
186 prev_clip_id: ClipId,
187 prev_clip_chain_id: ClipChainId,
190 impl ClipChainBuilder {
191 /// Construct a new clip chain builder with specified parent clip chain. If
192 /// the clip_id is Some(..), the clips in that template will be added to the
193 /// clip chain at this level (this functionality isn't currently used, but will
194 /// be in the follow up patches).
196 parent_clip_chain_id: ClipChainId,
197 clip_id: Option<ClipId>,
198 clip_chain_nodes: &mut Vec<ClipChainNode>,
199 templates: &FastHashMap<ClipId, ClipTemplate>,
201 let mut parent_clips = FastHashSet::default();
203 // Walk the current clip chain ID, building a set of existing clips
204 let mut current_clip_chain_id = parent_clip_chain_id;
205 while current_clip_chain_id != ClipChainId::NONE {
206 let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
207 parent_clips.insert((clip_chain_node.handle.uid(), clip_chain_node.spatial_node_index));
208 current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
211 // If specified, add the clips from the supplied template to this builder
212 let clip_chain_id = match clip_id {
214 ClipChainBuilder::add_new_clips_to_chain(
216 parent_clip_chain_id,
223 // Even if the clip id is None, it's possible that there were parent clips in the builder
224 // that need to be applied and set as the root of this clip-chain builder.
231 existing_clips_cache: parent_clips.clone(),
233 prev_clip_id: ClipId::root(PipelineId::dummy()),
234 prev_clip_chain_id: ClipChainId::NONE,
238 /// Internal helper function that appends all clip instances from a template
239 /// to a clip-chain (if they don't already exist in this chain).
240 fn add_new_clips_to_chain(
242 parent_clip_chain_id: ClipChainId,
243 existing_clips: &mut FastHashSet<(ItemUid, SpatialNodeIndex)>,
244 clip_chain_nodes: &mut Vec<ClipChainNode>,
245 templates: &FastHashMap<ClipId, ClipTemplate>,
247 let template = &templates[&clip_id];
248 let mut clip_chain_id = parent_clip_chain_id;
250 for clip in &template.clips {
251 let key = (clip.clip.handle.uid(), clip.clip.spatial_node_index);
253 // If this clip chain already has this clip instance, skip it
254 if existing_clips.contains(&key) {
258 // Create a new clip-chain entry for this instance
259 let new_clip_chain_id = ClipChainId(clip_chain_nodes.len() as u32);
260 existing_clips.insert(key);
261 clip_chain_nodes.push(ClipChainNode {
262 handle: clip.clip.handle,
263 spatial_node_index: clip.clip.spatial_node_index,
264 parent_clip_chain_id: clip_chain_id,
266 clip_chain_id = new_clip_chain_id;
269 // The ClipId parenting is terminated when we reach the root ClipId
270 if clip_id == template.parent {
271 return clip_chain_id;
274 ClipChainBuilder::add_new_clips_to_chain(
283 /// Return true if any of the clips in the hierarchy from clip_id to the
284 /// root clip are complex.
285 // TODO(gw): This method should only be required until the shared_clip
286 // optimization patches are complete, and can then be removed.
287 fn has_complex_clips(
290 templates: &FastHashMap<ClipId, ClipTemplate>,
292 let template = &templates[&clip_id];
294 // Check if any of the clips in this template are complex
295 for clip in &template.clips {
296 if let ClipNodeKind::Complex = clip.key.kind.node_kind() {
301 // The ClipId parenting is terminated when we reach the root ClipId
302 if clip_id == template.parent {
306 // Recurse into parent clip template to also check those
307 self.has_complex_clips(
313 /// This is the main method used to get a clip chain for a primitive. Given a
314 /// clip id, it builds a clip-chain for that primitive, parented to the current
315 /// root clip chain hosted in this builder.
316 fn get_or_build_clip_chain_id(
319 clip_chain_nodes: &mut Vec<ClipChainNode>,
320 templates: &FastHashMap<ClipId, ClipTemplate>,
322 if self.prev_clip_id == clip_id {
323 return self.prev_clip_chain_id;
326 // Instead of cloning here, do a clear and manual insertions, to
327 // avoid any extra heap allocations each time we build a clip-chain here.
328 // Maybe there is a better way to do this?
329 self.existing_clips_cache.clear();
330 self.existing_clips_cache.reserve(self.parent_clips.len());
331 for clip in &self.parent_clips {
332 self.existing_clips_cache.insert(*clip);
335 let clip_chain_id = ClipChainBuilder::add_new_clips_to_chain(
338 &mut self.existing_clips_cache,
343 self.prev_clip_id = clip_id;
344 self.prev_clip_chain_id = clip_chain_id;
350 /// Helper to identify simple clips (normal rects) from other kinds of clips,
351 /// which can often be handled via fast code paths.
352 #[cfg_attr(feature = "capture", derive(Serialize))]
353 #[cfg_attr(feature = "replay", derive(Deserialize))]
354 #[derive(Debug, Copy, Clone, MallocSizeOf)]
355 pub enum ClipNodeKind {
356 /// A normal clip rectangle, with Clip mode.
358 /// A rectangle with ClipOut, or any other kind of clip.
362 // Result of comparing a clip node instance against a local rect.
365 // The clip does not affect the region at all.
367 // The clip prevents the region from being drawn.
369 // The clip affects part of the region. This may
370 // require a clip mask, depending on other factors.
374 // A clip node is a single clip source, along with some
375 // positioning information and implementation details
376 // that control where the GPU data for this clip source
379 #[cfg_attr(feature = "capture", derive(Serialize))]
380 #[cfg_attr(feature = "replay", derive(Deserialize))]
381 #[derive(MallocSizeOf)]
382 pub struct ClipNode {
386 // Convert from an interning key for a clip item
387 // to a clip node, which is cached in the document.
388 impl From<ClipItemKey> for ClipNode {
389 fn from(item: ClipItemKey) -> Self {
390 let kind = match item.kind {
391 ClipItemKeyKind::Rectangle(rect, mode) => {
392 ClipItemKind::Rectangle { rect: rect.into(), mode }
394 ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => {
395 ClipItemKind::RoundedRectangle {
397 radius: radius.into(),
401 ClipItemKeyKind::ImageMask(rect, image, repeat, polygon) => {
402 ClipItemKind::Image {
409 ClipItemKeyKind::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
410 ClipItemKind::new_box_shadow(
411 shadow_rect_fract_offset.into(),
412 shadow_rect_size.into(),
413 shadow_radius.into(),
414 prim_shadow_rect.into(),
415 blur_radius.to_f32_px(),
429 // Flags that are attached to instances of clip nodes.
431 #[cfg_attr(feature = "capture", derive(Serialize))]
432 #[cfg_attr(feature = "replay", derive(Deserialize))]
433 #[derive(MallocSizeOf)]
434 pub struct ClipNodeFlags: u8 {
435 const SAME_SPATIAL_NODE = 0x1;
436 const SAME_COORD_SYSTEM = 0x2;
437 const USE_FAST_PATH = 0x4;
441 // Identifier for a clip chain. Clip chains are stored
442 // in a contiguous array in the clip store. They are
443 // identified by a simple index into that array.
444 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Hash)]
445 #[cfg_attr(feature = "capture", derive(Serialize))]
446 pub struct ClipChainId(pub u32);
448 // The root of each clip chain is the NONE id. The
449 // value is specifically set to u32::MAX so that if
450 // any code accidentally tries to access the root
451 // node, a bounds error will occur.
453 pub const NONE: Self = ClipChainId(u32::MAX);
454 pub const INVALID: Self = ClipChainId(0xDEADBEEF);
457 // A clip chain node is an id for a range of clip sources,
458 // and a link to a parent clip chain node, or ClipChainId::NONE.
459 #[derive(Clone, Debug, MallocSizeOf)]
460 #[cfg_attr(feature = "capture", derive(Serialize))]
461 pub struct ClipChainNode {
462 pub handle: ClipDataHandle,
463 pub spatial_node_index: SpatialNodeIndex,
464 pub parent_clip_chain_id: ClipChainId,
468 #[cfg_attr(feature = "capture", derive(Serialize))]
470 /// Local space clip rect
471 pub local_clip_rect: LayoutRect,
473 /// ID of the clip chain that this set is clipped by.
474 pub clip_chain_id: ClipChainId,
477 // When a clip node is found to be valid for a
478 // clip chain instance, it's stored in an index
479 // buffer style structure. This struct contains
480 // an index to the node data itself, as well as
481 // some flags describing how this clip node instance
483 #[derive(Debug, MallocSizeOf)]
484 #[cfg_attr(feature = "capture", derive(Serialize))]
485 #[cfg_attr(feature = "replay", derive(Deserialize))]
486 pub struct ClipNodeInstance {
487 pub handle: ClipDataHandle,
488 pub spatial_node_index: SpatialNodeIndex,
489 pub flags: ClipNodeFlags,
490 pub visible_tiles: Option<Vec<VisibleMaskImageTile>>,
493 // A range of clip node instances that were found by
494 // building a clip chain instance.
495 #[derive(Debug, Copy, Clone)]
496 #[cfg_attr(feature = "capture", derive(Serialize))]
497 #[cfg_attr(feature = "replay", derive(Deserialize))]
498 pub struct ClipNodeRange {
504 pub fn to_range(&self) -> ops::Range<usize> {
505 let start = self.first as usize;
506 let end = start + self.count as usize;
515 /// A helper struct for converting between coordinate systems
516 /// of clip sources and primitives.
517 // todo(gw): optimize:
518 // separate arrays for matrices
519 // cache and only build as needed.
520 //TODO: merge with `CoordinateSpaceMapping`?
521 #[derive(Debug, MallocSizeOf)]
522 #[cfg_attr(feature = "capture", derive(Serialize))]
523 enum ClipSpaceConversion {
525 ScaleOffset(ScaleOffset),
526 Transform(LayoutToWorldTransform),
529 impl ClipSpaceConversion {
530 /// Construct a new clip space converter between two spatial nodes.
532 prim_spatial_node_index: SpatialNodeIndex,
533 clip_spatial_node_index: SpatialNodeIndex,
534 spatial_tree: &SpatialTree,
536 //Note: this code is different from `get_relative_transform` in a way that we only try
537 // getting the relative transform if it's Local or ScaleOffset,
538 // falling back to the world transform otherwise.
539 let clip_spatial_node = &spatial_tree
540 .spatial_nodes[clip_spatial_node_index.0 as usize];
541 let prim_spatial_node = &spatial_tree
542 .spatial_nodes[prim_spatial_node_index.0 as usize];
544 if prim_spatial_node_index == clip_spatial_node_index {
545 ClipSpaceConversion::Local
546 } else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
547 let scale_offset = prim_spatial_node.content_transform
549 .accumulate(&clip_spatial_node.content_transform);
550 ClipSpaceConversion::ScaleOffset(scale_offset)
552 ClipSpaceConversion::Transform(
554 .get_world_transform(clip_spatial_node_index)
560 fn to_flags(&self) -> ClipNodeFlags {
562 ClipSpaceConversion::Local => {
563 ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
565 ClipSpaceConversion::ScaleOffset(..) => {
566 ClipNodeFlags::SAME_COORD_SYSTEM
568 ClipSpaceConversion::Transform(..) => {
569 ClipNodeFlags::empty()
575 // Temporary information that is cached and reused
576 // during building of a clip chain instance.
577 #[derive(MallocSizeOf)]
578 #[cfg_attr(feature = "capture", derive(Serialize))]
579 struct ClipNodeInfo {
580 conversion: ClipSpaceConversion,
581 handle: ClipDataHandle,
582 spatial_node_index: SpatialNodeIndex,
589 clipped_rect: &LayoutRect,
590 gpu_cache: &mut GpuCache,
591 resource_cache: &mut ResourceCache,
592 spatial_tree: &SpatialTree,
593 request_resources: bool,
594 ) -> Option<ClipNodeInstance> {
595 // Calculate some flags that are required for the segment
597 let mut flags = self.conversion.to_flags();
599 // Some clip shaders support a fast path mode for simple clips.
600 // TODO(gw): We could also apply fast path when segments are created, since we only write
601 // the mask for a single corner at a time then, so can always consider radii uniform.
603 flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
605 .get_world_viewport_transform(self.spatial_node_index)
606 .is_2d_axis_aligned();
607 if is_raster_2d && node.item.kind.supports_fast_path_rendering() {
608 flags |= ClipNodeFlags::USE_FAST_PATH;
611 let mut visible_tiles = None;
613 if let ClipItemKind::Image { rect, image, repeat, .. } = node.item.kind {
614 let request = ImageRequest {
616 rendering: ImageRendering::Auto,
620 if let Some(props) = resource_cache.get_image_properties(image) {
621 if let Some(tile_size) = props.tiling {
622 let mut mask_tiles = Vec::new();
624 let visible_rect = if repeat {
627 // Bug 1648323 - It is unclear why on rare occasions we get
628 // a clipped_rect that does not intersect the clip's mask rect.
629 // defaulting to clipped_rect here results in zero repetitions
630 // which clips the primitive entirely.
631 clipped_rect.intersection(&rect).unwrap_or(*clipped_rect)
634 let repetitions = image_tiling::repetitions(
640 for Repetition { origin, .. } in repetitions {
641 let layout_image_rect = LayoutRect {
645 let tiles = image_tiling::tiles(
652 if request_resources {
653 resource_cache.request_image(
654 request.with_tile(tile.offset),
658 mask_tiles.push(VisibleMaskImageTile {
659 tile_offset: tile.offset,
660 tile_rect: tile.rect,
664 visible_tiles = Some(mask_tiles);
665 } else if request_resources {
666 resource_cache.request_image(request, gpu_cache);
669 // If the supplied image key doesn't exist in the resource cache,
670 // skip the clip node since there is nothing to mask with.
671 warn!("Clip mask with missing image key {:?}", request.key);
676 Some(ClipNodeInstance {
680 spatial_node_index: self.spatial_node_index,
688 device_pixel_scale: DevicePixelScale,
690 match self.item.kind {
691 ClipItemKind::Image { .. } |
692 ClipItemKind::Rectangle { .. } |
693 ClipItemKind::RoundedRectangle { .. } => {}
695 ClipItemKind::BoxShadow { ref mut source } => {
696 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
697 // "the image that would be generated by applying to the shadow a
698 // Gaussian blur with a standard deviation equal to half the blur radius."
699 let blur_radius_dp = source.blur_radius * 0.5;
701 // Create scaling from requested size to cache size.
702 let mut content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
703 content_scale.0 = clamp_to_scale_factor(content_scale.0, false);
705 // Create the cache key for this box-shadow render task.
706 let cache_size = to_cache_size(source.shadow_rect_alloc_size, &mut content_scale);
708 let bs_cache_key = BoxShadowCacheKey {
709 blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
710 clip_mode: source.clip_mode,
711 original_alloc_size: (source.original_alloc_size * content_scale).round().to_i32(),
712 br_top_left: (source.shadow_radius.top_left * content_scale).round().to_i32(),
713 br_top_right: (source.shadow_radius.top_right * content_scale).round().to_i32(),
714 br_bottom_right: (source.shadow_radius.bottom_right * content_scale).round().to_i32(),
715 br_bottom_left: (source.shadow_radius.bottom_left * content_scale).round().to_i32(),
716 device_pixel_scale: Au::from_f32_px(content_scale.0),
719 source.cache_key = Some((cache_size, bs_cache_key));
725 /// The main clipping public interface that other modules access.
726 #[derive(MallocSizeOf)]
727 #[cfg_attr(feature = "capture", derive(Serialize))]
728 pub struct ClipStore {
729 pub clip_chain_nodes: Vec<ClipChainNode>,
730 pub clip_node_instances: Vec<ClipNodeInstance>,
732 active_clip_node_info: Vec<ClipNodeInfo>,
733 active_local_clip_rect: Option<LayoutRect>,
734 active_pic_clip_rect: PictureRect,
736 // No malloc sizeof since it's not implemented for ops::Range, but these
737 // allocations are tiny anyway.
739 /// Map of all clip templates defined by the public API to templates
740 #[ignore_malloc_size_of = "range missing"]
741 pub templates: FastHashMap<ClipId, ClipTemplate>,
743 /// A stack of current clip-chain builders. A new clip-chain builder is
744 /// typically created each time a clip root (such as an iframe or stacking
745 /// context) is defined.
746 #[ignore_malloc_size_of = "range missing"]
747 chain_builder_stack: Vec<ClipChainBuilder>,
750 // A clip chain instance is what gets built for a given clip
751 // chain id + local primitive region + positioning node.
753 #[cfg_attr(feature = "capture", derive(Serialize))]
754 pub struct ClipChainInstance {
755 pub clips_range: ClipNodeRange,
756 // Combined clip rect for clips that are in the
757 // same coordinate system as the primitive.
758 pub local_clip_rect: LayoutRect,
759 pub has_non_local_clips: bool,
760 // If true, this clip chain requires allocation
762 pub needs_mask: bool,
763 // Combined clip rect in picture space (may
764 // be more conservative that local_clip_rect).
765 pub pic_clip_rect: PictureRect,
766 // Space, in which the `pic_clip_rect` is defined.
767 pub pic_spatial_node_index: SpatialNodeIndex,
770 impl ClipChainInstance {
771 pub fn empty() -> Self {
773 clips_range: ClipNodeRange {
777 local_clip_rect: LayoutRect::zero(),
778 has_non_local_clips: false,
780 pic_clip_rect: PictureRect::zero(),
781 pic_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
786 /// Maintains a (flattened) list of clips for a given level in the surface level stack.
787 pub struct ClipChainLevel {
788 /// These clips will be handled when compositing this surface into the parent,
789 /// and can thus be ignored on the primitives that are drawn as part of this surface.
790 shared_clips: Vec<ClipInstance>,
792 /// Index of the first element in ClipChainStack::clip that belongs to this level.
793 first_clip_index: usize,
794 /// Used to sanity check push/pop balance.
795 initial_clip_counts_len: usize,
798 /// Maintains a stack of clip chain ids that are currently active,
799 /// when a clip exists on a picture that has no surface, and is passed
800 /// on down to the child primitive(s).
803 /// In order to avoid many small vector allocations, all clip chain ids are
804 /// stored in a single vector instead of per-level.
805 /// Since we only work with the top-most level of the stack, we only need to
806 /// know the first index in the clips vector that belongs to each level. The
807 /// last index for the top-most level is always the end of the clips array.
809 /// Likewise, we push several clip chain ids to the clips array at each
810 /// push_clip, and the number of clip chain ids removed during pop_clip
811 /// must match. This is done by having a separate stack of clip counts
812 /// in the clip-stack rather than per-level to avoid vector allocations.
816 /// levels: | | | ...
821 /// +--+--+--+--+--+--+--
822 /// clips: | | | | | | | ...
823 /// +--+--+--+--+--+--+--
828 /// clip_counts: | 1| 2| 2| ...
831 pub struct ClipChainStack {
832 /// A stack of clip chain lists. Each time a new surface is pushed,
833 /// a new level is added. Each time a new picture without surface is
834 /// pushed, it adds the picture clip chain to the clips vector in the
835 /// range belonging to the level (always the top-most level, so always
836 /// at the end of the clips array).
837 levels: Vec<ClipChainLevel>,
838 /// The actual stack of clip ids.
839 clips: Vec<ClipChainId>,
840 /// How many clip ids to pop from the vector each time we call pop_clip.
841 clip_counts: Vec<usize>,
844 impl ClipChainStack {
845 pub fn new() -> Self {
849 shared_clips: Vec::new(),
851 initial_clip_counts_len: 0,
855 clip_counts: Vec::new(),
859 pub fn clear(&mut self) {
861 self.clip_counts.clear();
863 self.levels.push(ClipChainLevel {
864 shared_clips: Vec::new(),
866 initial_clip_counts_len: 0,
870 pub fn take(&mut self) -> Self {
872 levels: self.levels.take(),
873 clips: self.clips.take(),
874 clip_counts: self.clip_counts.take(),
878 /// Push a clip chain root onto the currently active list.
881 clip_chain_id: ClipChainId,
882 clip_store: &ClipStore,
884 let mut clip_count = 0;
886 let mut current_clip_chain_id = clip_chain_id;
887 while current_clip_chain_id != ClipChainId::NONE {
888 let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
889 let clip_uid = clip_chain_node.handle.uid();
891 // The clip is required, so long as it doesn't exist in any of the shared_clips
892 // array from this or any parent surfaces.
893 // TODO(gw): We could consider making this a HashSet if it ever shows up in
894 // profiles, but the typical array length is 2-3 elements.
895 let mut valid_clip = true;
896 for level in &self.levels {
897 if level.shared_clips.iter().any(|instance| {
898 instance.handle.uid() == clip_uid &&
899 instance.spatial_node_index == clip_chain_node.spatial_node_index
907 self.clips.push(current_clip_chain_id);
911 current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
914 self.clip_counts.push(clip_count);
917 /// Pop a clip chain root from the currently active list.
918 pub fn pop_clip(&mut self) {
919 let count = self.clip_counts.pop().unwrap();
920 for _ in 0 .. count {
921 self.clips.pop().unwrap();
925 /// When a surface is created, it takes all clips and establishes a new
926 /// stack of clips to be propagated.
929 maybe_shared_clips: &[ClipInstance],
930 spatial_tree: &SpatialTree,
932 let mut shared_clips = Vec::with_capacity(maybe_shared_clips.len());
934 // If there are clips in the shared list for a picture cache, only include
935 // them if they are simple, axis-aligned clips (i.e. in the root coordinate
936 // system). This is necessary since when compositing picture cache tiles
937 // into the parent, we don't support applying a clip mask. This only ever
938 // occurs in wrench tests, not in display lists supplied by Gecko.
939 // TODO(gw): We can remove this when we update the WR API to have better
940 // knowledge of what coordinate system a clip must be in (by
941 // knowing if a reference frame exists in the chain between the
942 // clip's spatial node and the picture cache reference spatial node).
943 for clip in maybe_shared_clips {
944 let spatial_node = &spatial_tree.spatial_nodes[clip.spatial_node_index.0 as usize];
945 if spatial_node.coordinate_system_id == CoordinateSystemId::root() {
946 shared_clips.push(*clip);
950 let level = ClipChainLevel {
952 first_clip_index: self.clips.len(),
953 initial_clip_counts_len: self.clip_counts.len(),
956 self.levels.push(level);
959 /// Pop a surface from the clip chain stack
960 pub fn pop_surface(&mut self) {
961 let level = self.levels.pop().unwrap();
962 assert!(self.clip_counts.len() == level.initial_clip_counts_len);
963 assert!(self.clips.len() == level.first_clip_index);
966 /// Get the list of currently active clip chains
967 pub fn current_clips_array(&self) -> &[ClipChainId] {
968 let first = self.levels.last().unwrap().first_clip_index;
974 pub fn new() -> Self {
976 clip_chain_nodes: Vec::new(),
977 clip_node_instances: Vec::new(),
978 active_clip_node_info: Vec::new(),
979 active_local_clip_rect: None,
980 active_pic_clip_rect: PictureRect::max_rect(),
981 templates: FastHashMap::default(),
982 chain_builder_stack: Vec::new(),
986 /// Register a new clip template for the clip_id defined in the display list.
987 pub fn register_clip_template(
991 clips: &[SceneClipInstance],
993 self.templates.insert(clip_id, ClipTemplate {
1002 ) -> &ClipTemplate {
1003 &self.templates[&clip_id]
1006 /// The main method used to build a clip-chain for a given ClipId on a primitive
1007 pub fn get_or_build_clip_chain_id(
1011 // TODO(gw): If many primitives reference the same ClipId, it might be worth
1012 // maintaining a hash map cache of ClipId -> ClipChainId in each
1015 self.chain_builder_stack
1018 .get_or_build_clip_chain_id(
1020 &mut self.clip_chain_nodes,
1025 /// Return true if any of the clips in the hierarchy from clip_id to the
1026 /// root clip are complex.
1027 // TODO(gw): This method should only be required until the shared_clip
1028 // optimization patches are complete, and can then be removed.
1029 pub fn has_complex_clips(
1033 self.chain_builder_stack
1042 /// Push a new clip root. This is used at boundaries of clips (such as iframes
1043 /// and stacking contexts). This means that any clips on the existing clip
1044 /// chain builder will not be added to clip-chains defined within this level,
1045 /// since the clips will be applied by the parent.
1046 pub fn push_clip_root(
1048 clip_id: Option<ClipId>,
1049 link_to_parent: bool,
1051 let parent_clip_chain_id = if link_to_parent {
1052 self.chain_builder_stack.last().unwrap().clip_chain_id
1057 let builder = ClipChainBuilder::new(
1058 parent_clip_chain_id,
1060 &mut self.clip_chain_nodes,
1064 self.chain_builder_stack.push(builder);
1067 /// On completion of a stacking context or iframe, pop the current clip root.
1068 pub fn pop_clip_root(
1071 self.chain_builder_stack.pop().unwrap();
1074 pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
1075 &self.clip_chain_nodes[clip_chain_id.0 as usize]
1078 pub fn add_clip_chain_node(
1080 handle: ClipDataHandle,
1081 spatial_node_index: SpatialNodeIndex,
1082 parent_clip_chain_id: ClipChainId,
1084 let id = ClipChainId(self.clip_chain_nodes.len() as u32);
1085 self.clip_chain_nodes.push(ClipChainNode {
1088 parent_clip_chain_id,
1093 pub fn get_instance_from_range(
1095 node_range: &ClipNodeRange,
1097 ) -> &ClipNodeInstance {
1098 &self.clip_node_instances[(node_range.first + index) as usize]
1101 /// Setup the active clip chains for building a clip chain instance.
1102 pub fn set_active_clips(
1104 local_prim_clip_rect: LayoutRect,
1105 prim_spatial_node_index: SpatialNodeIndex,
1106 pic_spatial_node_index: SpatialNodeIndex,
1107 clip_chains: &[ClipChainId],
1108 spatial_tree: &SpatialTree,
1109 clip_data_store: &ClipDataStore,
1111 self.active_clip_node_info.clear();
1112 self.active_local_clip_rect = None;
1113 self.active_pic_clip_rect = PictureRect::max_rect();
1115 let mut local_clip_rect = local_prim_clip_rect;
1117 for clip_chain_id in clip_chains {
1118 let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize];
1120 if !add_clip_node_to_current_chain(
1122 prim_spatial_node_index,
1123 pic_spatial_node_index,
1124 &mut local_clip_rect,
1125 &mut self.active_clip_node_info,
1126 &mut self.active_pic_clip_rect,
1134 self.active_local_clip_rect = Some(local_clip_rect);
1137 /// Setup the active clip chains, based on an existing primitive clip chain instance.
1138 pub fn set_active_clips_from_clip_chain(
1140 prim_clip_chain: &ClipChainInstance,
1141 prim_spatial_node_index: SpatialNodeIndex,
1142 spatial_tree: &SpatialTree,
1144 // TODO(gw): Although this does less work than set_active_clips(), it does
1145 // still do some unnecessary work (such as the clip space conversion).
1146 // We could consider optimizing this if it ever shows up in a profile.
1148 self.active_clip_node_info.clear();
1149 self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect);
1150 self.active_pic_clip_rect = prim_clip_chain.pic_clip_rect;
1152 let clip_instances = &self
1153 .clip_node_instances[prim_clip_chain.clips_range.to_range()];
1154 for clip_instance in clip_instances {
1155 let conversion = ClipSpaceConversion::new(
1156 prim_spatial_node_index,
1157 clip_instance.spatial_node_index,
1160 self.active_clip_node_info.push(ClipNodeInfo {
1161 handle: clip_instance.handle,
1162 spatial_node_index: clip_instance.spatial_node_index,
1168 /// The main interface external code uses. Given a local primitive, positioning
1169 /// information, and a clip chain id, build an optimized clip chain instance.
1170 pub fn build_clip_chain_instance(
1172 local_prim_rect: LayoutRect,
1173 prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
1174 pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
1175 spatial_tree: &SpatialTree,
1176 gpu_cache: &mut GpuCache,
1177 resource_cache: &mut ResourceCache,
1178 device_pixel_scale: DevicePixelScale,
1179 world_rect: &WorldRect,
1180 clip_data_store: &mut ClipDataStore,
1181 request_resources: bool,
1183 ) -> Option<ClipChainInstance> {
1184 let local_clip_rect = match self.active_local_clip_rect {
1186 None => return None,
1188 profile_scope!("build_clip_chain_instance");
1190 println!("\tbuilding clip chain instance with local rect {:?}", local_prim_rect);
1193 let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
1194 let mut pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
1195 let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?;
1197 // Now, we've collected all the clip nodes that *potentially* affect this
1198 // primitive region, and reduced the size of the prim region as much as possible.
1200 // Run through the clip nodes, and see which ones affect this prim region.
1202 let first_clip_node_index = self.clip_node_instances.len() as u32;
1203 let mut has_non_local_clips = false;
1204 let mut needs_mask = false;
1206 // For each potential clip node
1207 for node_info in self.active_clip_node_info.drain(..) {
1208 let node = &mut clip_data_store[node_info.handle];
1210 // See how this clip affects the prim region.
1211 let clip_result = match node_info.conversion {
1212 ClipSpaceConversion::Local => {
1213 node.item.kind.get_clip_result(&local_bounding_rect)
1215 ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
1216 has_non_local_clips = true;
1217 node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect))
1219 ClipSpaceConversion::Transform(ref transform) => {
1220 has_non_local_clips = true;
1221 node.item.kind.get_clip_result_complex(
1230 println!("\t\tclip {:?}", node.item);
1231 println!("\t\tflags {:?}, resulted in {:?}", node_info.conversion.to_flags(), clip_result);
1235 ClipResult::Accept => {
1236 // Doesn't affect the primitive at all, so skip adding to list
1238 ClipResult::Reject => {
1239 // Completely clips the supplied prim rect
1242 ClipResult::Partial => {
1243 // Needs a mask -> add to clip node indices
1245 // TODO(gw): Ensure this only runs once on each node per frame?
1246 node.update(device_pixel_scale);
1248 // Create the clip node instance for this clip node
1249 if let Some(instance) = node_info.create_instance(
1251 &local_bounding_rect,
1257 // As a special case, a partial accept of a clip rect that is
1258 // in the same coordinate system as the primitive doesn't need
1259 // a clip mask. Instead, it can be handled by the primitive
1260 // vertex shader as part of the local clip rect. This is an
1261 // important optimization for reducing the number of clip
1262 // masks that are allocated on common pages.
1263 needs_mask |= match node.item.kind {
1264 ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
1265 ClipItemKind::RoundedRectangle { .. } |
1266 ClipItemKind::Image { .. } |
1267 ClipItemKind::BoxShadow { .. } => {
1271 ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {
1272 !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
1276 // Store this in the index buffer for this clip chain instance.
1277 self.clip_node_instances.push(instance);
1283 // Get the range identifying the clip nodes in the index buffer.
1284 let clips_range = ClipNodeRange {
1285 first: first_clip_node_index,
1286 count: self.clip_node_instances.len() as u32 - first_clip_node_index,
1289 // If this clip chain needs a mask, reduce the size of the mask allocation
1290 // by any clips that were in the same space as the picture. This can result
1291 // in much smaller clip mask allocations in some cases. Note that the ordering
1292 // here is important - the reduction must occur *after* the clip item accept
1293 // reject checks above, so that we don't eliminate masks accidentally (since
1294 // we currently only support a local clip rect in the vertex shader).
1296 pic_clip_rect = pic_clip_rect.intersection(&self.active_pic_clip_rect)?;
1299 // Return a valid clip chain instance
1300 Some(ClipChainInstance {
1302 has_non_local_clips,
1305 pic_spatial_node_index: prim_to_pic_mapper.ref_spatial_node_index,
1310 pub fn clear_old_instances(&mut self) {
1311 self.clip_node_instances.clear();
1315 pub struct ComplexTranslateIter<I> {
1317 offset: LayoutVector2D,
1320 impl<I: Iterator<Item = ComplexClipRegion>> Iterator for ComplexTranslateIter<I> {
1321 type Item = ComplexClipRegion;
1322 fn next(&mut self) -> Option<Self::Item> {
1325 .map(|mut complex| {
1326 complex.rect = complex.rect.translate(self.offset);
1332 #[derive(Clone, Debug)]
1333 pub struct ClipRegion<I> {
1334 pub main: LayoutRect,
1335 pub complex_clips: I,
1338 impl<J> ClipRegion<ComplexTranslateIter<J>> {
1339 pub fn create_for_clip_node(
1342 reference_frame_relative_offset: &LayoutVector2D,
1345 J: Iterator<Item = ComplexClipRegion>
1348 main: rect.translate(*reference_frame_relative_offset),
1349 complex_clips: ComplexTranslateIter {
1350 source: complex_clips,
1351 offset: *reference_frame_relative_offset,
1357 impl ClipRegion<Option<ComplexClipRegion>> {
1358 pub fn create_for_clip_node_with_local_clip(
1359 local_clip: &LayoutRect,
1360 reference_frame_relative_offset: &LayoutVector2D
1363 main: local_clip.translate(*reference_frame_relative_offset),
1364 complex_clips: None,
1369 // The ClipItemKey is a hashable representation of the contents
1370 // of a clip item. It is used during interning to de-duplicate
1371 // clip nodes between frames and display lists. This allows quick
1372 // comparison of clip node equality by handle, and also allows
1373 // the uploaded GPU cache handle to be retained between display lists.
1374 // TODO(gw): Maybe we should consider constructing these directly
1375 // in the DL builder?
1376 #[derive(Copy, Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
1377 #[cfg_attr(feature = "capture", derive(Serialize))]
1378 #[cfg_attr(feature = "replay", derive(Deserialize))]
1379 pub enum ClipItemKeyKind {
1380 Rectangle(RectangleKey, ClipMode),
1381 RoundedRectangle(RectangleKey, BorderRadiusAu, ClipMode),
1382 ImageMask(RectangleKey, ImageKey, bool, PolygonKey),
1383 BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode),
1386 impl ClipItemKeyKind {
1387 pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self {
1388 ClipItemKeyKind::Rectangle(rect.into(), mode)
1391 pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self {
1392 if radii.is_zero() {
1393 ClipItemKeyKind::rectangle(rect, mode)
1395 ensure_no_corner_overlap(&mut radii, rect.size);
1396 ClipItemKeyKind::RoundedRectangle(
1404 pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect,
1405 points: Vec<LayoutPoint>, fill_rule: FillRule) -> Self {
1406 ClipItemKeyKind::ImageMask(
1410 PolygonKey::new(&points, fill_rule)
1415 shadow_rect: LayoutRect,
1416 shadow_radius: BorderRadius,
1417 prim_shadow_rect: LayoutRect,
1419 clip_mode: BoxShadowClipMode,
1421 // Get the fractional offsets required to match the
1422 // source rect with a minimal rect.
1423 let fract_offset = LayoutPoint::new(
1424 shadow_rect.origin.x.fract().abs(),
1425 shadow_rect.origin.y.fract().abs(),
1428 ClipItemKeyKind::BoxShadow(
1429 fract_offset.into(),
1430 shadow_rect.size.into(),
1431 shadow_radius.into(),
1432 prim_shadow_rect.into(),
1433 Au::from_f32_px(blur_radius),
1438 pub fn node_kind(&self) -> ClipNodeKind {
1440 ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => ClipNodeKind::Rectangle,
1442 ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
1443 ClipItemKeyKind::RoundedRectangle(..) |
1444 ClipItemKeyKind::ImageMask(..) |
1445 ClipItemKeyKind::BoxShadow(..) => ClipNodeKind::Complex,
1450 #[derive(Debug, Copy, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
1451 #[cfg_attr(feature = "capture", derive(Serialize))]
1452 #[cfg_attr(feature = "replay", derive(Deserialize))]
1453 pub struct ClipItemKey {
1454 pub kind: ClipItemKeyKind,
1457 /// The data available about an interned clip node during scene building
1458 #[derive(Debug, MallocSizeOf)]
1459 #[cfg_attr(feature = "capture", derive(Serialize))]
1460 #[cfg_attr(feature = "replay", derive(Deserialize))]
1461 pub struct ClipInternData {
1462 /// Whether this is a simple rectangle clip
1463 pub clip_node_kind: ClipNodeKind,
1466 impl intern::InternDebug for ClipItemKey {}
1468 impl intern::Internable for ClipIntern {
1469 type Key = ClipItemKey;
1470 type StoreData = ClipNode;
1471 type InternData = ClipInternData;
1472 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CLIPS;
1475 #[derive(Debug, MallocSizeOf)]
1476 #[cfg_attr(feature = "capture", derive(Serialize))]
1477 #[cfg_attr(feature = "replay", derive(Deserialize))]
1478 pub enum ClipItemKind {
1485 radius: BorderRadius,
1492 polygon: PolygonKey,
1495 source: BoxShadowClipSource,
1499 #[derive(Debug, MallocSizeOf)]
1500 #[cfg_attr(feature = "capture", derive(Serialize))]
1501 #[cfg_attr(feature = "replay", derive(Deserialize))]
1502 pub struct ClipItem {
1503 pub kind: ClipItemKind,
1506 fn compute_box_shadow_parameters(
1507 shadow_rect_fract_offset: LayoutPoint,
1508 shadow_rect_size: LayoutSize,
1509 mut shadow_radius: BorderRadius,
1510 prim_shadow_rect: LayoutRect,
1512 clip_mode: BoxShadowClipMode,
1513 ) -> BoxShadowClipSource {
1514 // Make sure corners don't overlap.
1515 ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);
1517 let fract_size = LayoutSize::new(
1518 shadow_rect_size.width.fract().abs(),
1519 shadow_rect_size.height.fract().abs(),
1522 // Create a minimal size primitive mask to blur. In this
1523 // case, we ensure the size of each corner is the same,
1524 // to simplify the shader logic that stretches the blurred
1525 // result across the primitive.
1526 let max_corner_width = shadow_radius.top_left.width
1527 .max(shadow_radius.bottom_left.width)
1528 .max(shadow_radius.top_right.width)
1529 .max(shadow_radius.bottom_right.width);
1530 let max_corner_height = shadow_radius.top_left.height
1531 .max(shadow_radius.bottom_left.height)
1532 .max(shadow_radius.top_right.height)
1533 .max(shadow_radius.bottom_right.height);
1535 // Get maximum distance that can be affected by given blur radius.
1536 let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
1538 // If the largest corner is smaller than the blur radius, we need to ensure
1539 // that it's big enough that the corners don't affect the middle segments.
1540 let used_corner_width = max_corner_width.max(blur_region);
1541 let used_corner_height = max_corner_height.max(blur_region);
1543 // Minimal nine-patch size, corner + internal + corner.
1544 let min_shadow_rect_size = LayoutSize::new(
1545 2.0 * used_corner_width + blur_region,
1546 2.0 * used_corner_height + blur_region,
1549 // The minimal rect to blur.
1550 let mut minimal_shadow_rect = LayoutRect::new(
1552 blur_region + shadow_rect_fract_offset.x,
1553 blur_region + shadow_rect_fract_offset.y,
1556 min_shadow_rect_size.width + fract_size.width,
1557 min_shadow_rect_size.height + fract_size.height,
1561 // If the width or height ends up being bigger than the original
1562 // primitive shadow rect, just blur the entire rect along that
1563 // axis and draw that as a simple blit. This is necessary for
1564 // correctness, since the blur of one corner may affect the blur
1565 // in another corner.
1566 let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
1567 if shadow_rect_size.width < minimal_shadow_rect.size.width {
1568 minimal_shadow_rect.size.width = shadow_rect_size.width;
1569 stretch_mode_x = BoxShadowStretchMode::Simple;
1572 let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
1573 if shadow_rect_size.height < minimal_shadow_rect.size.height {
1574 minimal_shadow_rect.size.height = shadow_rect_size.height;
1575 stretch_mode_y = BoxShadowStretchMode::Simple;
1578 // Expand the shadow rect by enough room for the blur to take effect.
1579 let shadow_rect_alloc_size = LayoutSize::new(
1580 2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
1581 2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
1584 BoxShadowClipSource {
1585 original_alloc_size: shadow_rect_alloc_size,
1586 shadow_rect_alloc_size,
1595 minimal_shadow_rect,
1600 pub fn new_box_shadow(
1601 shadow_rect_fract_offset: LayoutPoint,
1602 shadow_rect_size: LayoutSize,
1603 mut shadow_radius: BorderRadius,
1604 prim_shadow_rect: LayoutRect,
1606 clip_mode: BoxShadowClipMode,
1608 let mut source = compute_box_shadow_parameters(
1609 shadow_rect_fract_offset,
1617 fn needed_downscaling(source: &BoxShadowClipSource) -> Option<f32> {
1618 // This size is fairly arbitrary, but it's the same as the size that
1619 // we use to avoid caching big blurred stacking contexts.
1621 // If you change it, ensure that the reftests
1622 // box-shadow-large-blur-radius-* still hit the downscaling path,
1623 // and that they render correctly.
1624 const MAX_SIZE: f32 = 2048.;
1627 source.shadow_rect_alloc_size.width.max(source.shadow_rect_alloc_size.height);
1629 if max_dimension > MAX_SIZE {
1630 Some(MAX_SIZE / max_dimension)
1636 if let Some(downscale) = needed_downscaling(&source) {
1637 shadow_radius.bottom_left.height *= downscale;
1638 shadow_radius.bottom_left.width *= downscale;
1639 shadow_radius.bottom_right.height *= downscale;
1640 shadow_radius.bottom_right.width *= downscale;
1641 shadow_radius.top_left.height *= downscale;
1642 shadow_radius.top_left.width *= downscale;
1643 shadow_radius.top_right.height *= downscale;
1644 shadow_radius.top_right.width *= downscale;
1646 let original_alloc_size = source.shadow_rect_alloc_size;
1648 source = compute_box_shadow_parameters(
1649 shadow_rect_fract_offset * downscale,
1650 shadow_rect_size * downscale,
1653 blur_radius * downscale,
1656 source.original_alloc_size = original_alloc_size;
1658 ClipItemKind::BoxShadow { source }
1661 /// Returns true if this clip mask can run through the fast path
1662 /// for the given clip item type.
1664 /// Note: this logic has to match `ClipBatcher::add` behavior.
1665 fn supports_fast_path_rendering(&self) -> bool {
1667 ClipItemKind::Rectangle { .. } |
1668 ClipItemKind::Image { .. } |
1669 ClipItemKind::BoxShadow { .. } => {
1672 ClipItemKind::RoundedRectangle { ref radius, .. } => {
1673 // The rounded clip rect fast path shader can only work
1674 // if the radii are uniform.
1675 radius.is_uniform().is_some()
1680 // Get an optional clip rect that a clip source can provide to
1681 // reduce the size of a primitive region. This is typically
1682 // used to eliminate redundant clips, and reduce the size of
1683 // any clip mask that eventually gets drawn.
1684 pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
1686 ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => Some(rect),
1687 ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } => None,
1688 ClipItemKind::RoundedRectangle { rect, mode: ClipMode::Clip, .. } => Some(rect),
1689 ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => None,
1690 ClipItemKind::Image { repeat, rect, .. } => {
1697 ClipItemKind::BoxShadow { .. } => None,
1701 fn get_clip_result_complex(
1703 transform: &LayoutToWorldTransform,
1704 prim_world_rect: &WorldRect,
1705 world_rect: &WorldRect,
1707 let visible_rect = match prim_world_rect.intersection(world_rect) {
1709 None => return ClipResult::Reject,
1712 let (clip_rect, inner_rect, mode) = match *self {
1713 ClipItemKind::Rectangle { rect, mode } => {
1714 (rect, Some(rect), mode)
1716 ClipItemKind::RoundedRectangle { rect, ref radius, mode } => {
1717 let inner_clip_rect = extract_inner_rect_safe(&rect, radius);
1718 (rect, inner_clip_rect, mode)
1720 ClipItemKind::Image { rect, repeat: false, .. } => {
1721 (rect, None, ClipMode::Clip)
1723 ClipItemKind::Image { repeat: true, .. } |
1724 ClipItemKind::BoxShadow { .. } => {
1725 return ClipResult::Partial;
1729 if let Some(ref inner_clip_rect) = inner_rect {
1730 if let Some(()) = projected_rect_contains(inner_clip_rect, transform, &visible_rect) {
1732 ClipMode::Clip => ClipResult::Accept,
1733 ClipMode::ClipOut => ClipResult::Reject,
1740 let outer_clip_rect = match project_rect(
1745 Some(outer_clip_rect) => outer_clip_rect,
1746 None => return ClipResult::Partial,
1749 match outer_clip_rect.intersection(prim_world_rect) {
1758 ClipMode::ClipOut => ClipResult::Partial,
1762 // Check how a given clip source affects a local primitive region.
1765 prim_rect: &LayoutRect,
1768 ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => {
1769 if rect.contains_rect(prim_rect) {
1770 return ClipResult::Accept;
1773 match rect.intersection(prim_rect) {
1782 ClipItemKind::Rectangle { rect, mode: ClipMode::ClipOut } => {
1783 if rect.contains_rect(prim_rect) {
1784 return ClipResult::Reject;
1787 match rect.intersection(prim_rect) {
1796 ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::Clip } => {
1797 // TODO(gw): Consider caching this in the ClipNode
1798 // if it ever shows in profiles.
1799 if rounded_rectangle_contains_rect_quick(&rect, radius, &prim_rect) {
1800 return ClipResult::Accept;
1803 match rect.intersection(prim_rect) {
1812 ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::ClipOut } => {
1813 // TODO(gw): Consider caching this in the ClipNode
1814 // if it ever shows in profiles.
1815 if rounded_rectangle_contains_rect_quick(&rect, radius, &prim_rect) {
1816 return ClipResult::Reject;
1819 match rect.intersection(prim_rect) {
1828 ClipItemKind::Image { rect, repeat, .. } => {
1832 match rect.intersection(prim_rect) {
1842 ClipItemKind::BoxShadow { .. } => {
1849 /// Represents a local rect and a device space
1850 /// rectangles that are either outside or inside bounds.
1851 #[derive(Clone, Debug, PartialEq)]
1852 pub struct Geometry {
1853 pub local_rect: LayoutRect,
1854 pub device_rect: DeviceIntRect,
1857 impl From<LayoutRect> for Geometry {
1858 fn from(local_rect: LayoutRect) -> Self {
1861 device_rect: DeviceIntRect::zero(),
1866 pub fn rounded_rectangle_contains_point(
1867 point: &LayoutPoint,
1869 radii: &BorderRadius
1871 if !rect.contains(*point) {
1875 let top_left_center = rect.origin + radii.top_left.to_vector();
1876 if top_left_center.x > point.x && top_left_center.y > point.y &&
1877 !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
1881 let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
1882 if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
1883 !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
1887 let top_right_center = rect.top_right() +
1888 LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
1889 if top_right_center.x < point.x && top_right_center.y > point.y &&
1890 !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
1894 let bottom_left_center = rect.bottom_left() +
1895 LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
1896 if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
1897 !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
1904 /// Return true if the rounded rectangle described by `container` and `radii`
1905 /// definitely contains `containee`. May return false negatives, but never false
1907 fn rounded_rectangle_contains_rect_quick(
1908 container: &LayoutRect,
1909 radii: &BorderRadius,
1910 containee: &LayoutRect,
1912 if !container.contains_rect(containee) {
1916 /// Return true if `point` falls within `corner`. This only covers the
1917 /// upper-left case; we transform the other corners into that form.
1918 fn foul(point: LayoutPoint, corner: LayoutPoint) -> bool {
1919 point.x < corner.x && point.y < corner.y
1922 /// Flip `pt` about the y axis (i.e. negate `x`).
1923 fn flip_x(pt: LayoutPoint) -> LayoutPoint {
1924 LayoutPoint { x: -pt.x, .. pt }
1927 /// Flip `pt` about the x axis (i.e. negate `y`).
1928 fn flip_y(pt: LayoutPoint) -> LayoutPoint {
1929 LayoutPoint { y: -pt.y, .. pt }
1932 if foul(containee.top_left(), container.top_left() + radii.top_left) ||
1933 foul(flip_x(containee.top_right()), flip_x(container.top_right()) + radii.top_right) ||
1934 foul(flip_y(containee.bottom_left()), flip_y(container.bottom_left()) + radii.bottom_left) ||
1935 foul(-containee.bottom_right(), -container.bottom_right() + radii.bottom_right)
1943 /// Test where point p is relative to the infinite line that passes through the segment
1944 /// defined by p0 and p1. Point p is on the "left" of the line if the triangle (p0, p1, p)
1945 /// forms a counter-clockwise triangle.
1946 /// > 0 is left of the line
1947 /// < 0 is right of the line
1948 /// == 0 is on the line
1949 pub fn is_left_of_line(
1957 (p1_x - p0_x) * (p_y - p0_y) - (p_x - p0_x) * (p1_y - p0_y)
1960 pub fn polygon_contains_point(
1961 point: &LayoutPoint,
1963 polygon: &PolygonKey,
1965 if !rect.contains(*point) {
1969 // p is a LayoutPoint that we'll be comparing to dimensionless PointKeys,
1970 // which were created from LayoutPoints, so it all works out.
1971 let p = LayoutPoint::new(point.x - rect.origin.x, point.y - rect.origin.y);
1973 // Calculate a winding number for this point.
1974 let mut winding_number: i32 = 0;
1976 let count = polygon.point_count as usize;
1979 let p0 = polygon.points[i];
1980 let p1 = polygon.points[(i + 1) % count];
1984 if is_left_of_line(p.x, p.y, p0.x, p0.y, p1.x, p1.y) > 0.0 {
1985 winding_number = winding_number + 1;
1988 } else if p1.y <= p.y {
1989 if is_left_of_line(p.x, p.y, p0.x, p0.y, p1.x, p1.y) < 0.0 {
1990 winding_number = winding_number - 1;
1995 match polygon.fill_rule {
1996 FillRule::Nonzero => winding_number != 0,
1997 FillRule::Evenodd => winding_number.abs() % 2 == 1,
2001 pub fn projected_rect_contains(
2002 source_rect: &LayoutRect,
2003 transform: &LayoutToWorldTransform,
2004 target_rect: &WorldRect,
2007 transform.transform_point2d(source_rect.origin)?,
2008 transform.transform_point2d(source_rect.top_right())?,
2009 transform.transform_point2d(source_rect.bottom_right())?,
2010 transform.transform_point2d(source_rect.bottom_left())?,
2012 let target_points = [
2014 target_rect.top_right(),
2015 target_rect.bottom_right(),
2016 target_rect.bottom_left(),
2018 // iterate the edges of the transformed polygon
2019 for (a, b) in points
2022 .zip(points[1..].iter().cloned().chain(iter::once(points[0])))
2024 // If this edge is redundant, it's a weird, case, and we shouldn't go
2025 // length in trying to take the fast path (e.g. when the whole rectangle is a point).
2026 // If any of edges of the target rectangle crosses the edge, it's not completely
2027 // inside our transformed polygon either.
2028 if a.approx_eq(&b) || target_points.iter().any(|&c| (b - a).cross(c - a) < 0.0) {
2037 // Add a clip node into the list of clips to be processed
2038 // for the current clip chain. Returns false if the clip
2039 // results in the entire primitive being culled out.
2040 fn add_clip_node_to_current_chain(
2041 node: &ClipChainNode,
2042 prim_spatial_node_index: SpatialNodeIndex,
2043 pic_spatial_node_index: SpatialNodeIndex,
2044 local_clip_rect: &mut LayoutRect,
2045 clip_node_info: &mut Vec<ClipNodeInfo>,
2046 current_pic_clip_rect: &mut PictureRect,
2047 clip_data_store: &ClipDataStore,
2048 spatial_tree: &SpatialTree,
2050 let clip_node = &clip_data_store[node.handle];
2052 // Determine the most efficient way to convert between coordinate
2053 // systems of the primitive and clip node.
2054 let conversion = ClipSpaceConversion::new(
2055 prim_spatial_node_index,
2056 node.spatial_node_index,
2060 // If we can convert spaces, try to reduce the size of the region
2061 // requested, and cache the conversion information for the next step.
2062 if let Some(clip_rect) = clip_node.item.kind.get_local_clip_rect() {
2064 ClipSpaceConversion::Local => {
2065 *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
2067 None => return false,
2070 ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
2071 let clip_rect = scale_offset.map_rect(&clip_rect);
2072 *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
2074 None => return false,
2077 ClipSpaceConversion::Transform(..) => {
2078 // Map the local clip rect directly into the same space as the picture
2079 // surface. This will often be the same space as the clip itself, which
2080 // results in a reduction in allocated clip mask size.
2082 // For simplicity, only apply this optimization if the clip is in the
2083 // same coord system as the picture. There are some 'advanced' perspective
2084 // clip tests in wrench that break without this check. Those cases are
2085 // never used in Gecko, and we aim to remove support in WR for that
2086 // in future to simplify the clipping pipeline.
2087 let pic_coord_system = spatial_tree
2088 .spatial_nodes[pic_spatial_node_index.0 as usize]
2089 .coordinate_system_id;
2091 let clip_coord_system = spatial_tree
2092 .spatial_nodes[node.spatial_node_index.0 as usize]
2093 .coordinate_system_id;
2095 if pic_coord_system == clip_coord_system {
2096 let mapper = SpaceMapper::new_with_target(
2097 pic_spatial_node_index,
2098 node.spatial_node_index,
2099 PictureRect::max_rect(),
2103 if let Some(pic_clip_rect) = mapper.map(&clip_rect) {
2104 *current_pic_clip_rect = pic_clip_rect
2105 .intersection(current_pic_clip_rect)
2106 .unwrap_or(PictureRect::zero());
2113 clip_node_info.push(ClipNodeInfo {
2115 spatial_node_index: node.spatial_node_index,
2116 handle: node.handle,
2124 use super::projected_rect_contains;
2125 use euclid::{Transform3D, rect};
2128 fn test_empty_projected_rect() {
2131 projected_rect_contains(
2132 &rect(10.0, 10.0, 0.0, 0.0),
2133 &Transform3D::identity(),
2134 &rect(20.0, 20.0, 10.0, 10.0),
2136 "Empty rectangle is considered to include a non-empty!"