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