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