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