spatial_tree.rs (80601B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use api::{ExternalScrollId, PropertyBinding, ReferenceFrameKind, TransformStyle, PropertyBindingId}; 6 use api::{APZScrollGeneration, HasScrollLinkedEffect, PipelineId, SampledScrollOffset, SpatialTreeItemKey}; 7 use api::units::*; 8 use euclid::Transform3D; 9 use crate::gpu_types::TransformPalette; 10 use crate::internal_types::{FastHashMap, FastHashSet, FrameMemory, PipelineInstanceId}; 11 use crate::print_tree::{PrintableTree, PrintTree, PrintTreePrinter}; 12 use crate::scene::SceneProperties; 13 use crate::spatial_node::{ReferenceFrameInfo, SpatialNode, SpatialNodeDescriptor, SpatialNodeType, StickyFrameInfo}; 14 use crate::spatial_node::{SpatialNodeUid, ScrollFrameKind, SceneSpatialNode, SpatialNodeInfo, SpatialNodeUidKind}; 15 use std::{ops, u32}; 16 use crate::util::{FastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, scale_factors}; 17 use smallvec::SmallVec; 18 use std::collections::hash_map::Entry; 19 use crate::util::TransformedRectKind; 20 use peek_poke::PeekPoke; 21 22 23 /// An id that identifies coordinate systems in the SpatialTree. Each 24 /// coordinate system has an id and those ids will be shared when the coordinates 25 /// system are the same or are in the same axis-aligned space. This allows 26 /// for optimizing mask generation. 27 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 28 #[cfg_attr(feature = "capture", derive(Serialize))] 29 #[cfg_attr(feature = "replay", derive(Deserialize))] 30 pub struct CoordinateSystemId(pub u32); 31 32 /// A node in the hierarchy of coordinate system 33 /// transforms. 34 #[derive(Debug)] 35 #[cfg_attr(feature = "capture", derive(Serialize))] 36 #[cfg_attr(feature = "replay", derive(Deserialize))] 37 pub struct CoordinateSystem { 38 pub transform: LayoutTransform, 39 pub world_transform: LayoutToWorldTransform, 40 pub should_flatten: bool, 41 pub parent: Option<CoordinateSystemId>, 42 } 43 44 impl CoordinateSystem { 45 fn root() -> Self { 46 CoordinateSystem { 47 transform: LayoutTransform::identity(), 48 world_transform: LayoutToWorldTransform::identity(), 49 should_flatten: false, 50 parent: None, 51 } 52 } 53 } 54 55 #[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PeekPoke, Default)] 56 #[cfg_attr(feature = "capture", derive(Serialize))] 57 #[cfg_attr(feature = "replay", derive(Deserialize))] 58 pub struct SpatialNodeIndex(pub u32); 59 60 impl SpatialNodeIndex { 61 pub const INVALID: SpatialNodeIndex = SpatialNodeIndex(u32::MAX); 62 63 /// May be set on a cluster / picture during scene building if the spatial 64 /// node is not known at this time. It must be set to a valid value before 65 /// scene building is complete (by `finalize_picture`). In future, we could 66 /// make this type-safe with a wrapper type to ensure we know when a spatial 67 /// node index may have an unknown value. 68 pub const UNKNOWN: SpatialNodeIndex = SpatialNodeIndex(u32::MAX - 1); 69 } 70 71 // In some cases, the conversion from CSS pixels to device pixels can result in small 72 // rounding errors when calculating the scrollable distance of a scroll frame. Apply 73 // a small epsilon so that we don't detect these frames as "real" scroll frames. 74 const MIN_SCROLLABLE_AMOUNT: f32 = 0.01; 75 76 // The minimum size for a scroll frame for it to be considered for a scroll root. 77 const MIN_SCROLL_ROOT_SIZE: f32 = 128.0; 78 79 impl SpatialNodeIndex { 80 pub fn new(index: usize) -> Self { 81 debug_assert!(index < ::std::u32::MAX as usize); 82 SpatialNodeIndex(index as u32) 83 } 84 } 85 86 impl CoordinateSystemId { 87 pub fn root() -> Self { 88 CoordinateSystemId(0) 89 } 90 } 91 92 #[derive(Debug, Copy, Clone, PartialEq)] 93 pub enum VisibleFace { 94 Front, 95 Back, 96 } 97 98 impl Default for VisibleFace { 99 fn default() -> Self { 100 VisibleFace::Front 101 } 102 } 103 104 impl ops::Not for VisibleFace { 105 type Output = Self; 106 fn not(self) -> Self { 107 match self { 108 VisibleFace::Front => VisibleFace::Back, 109 VisibleFace::Back => VisibleFace::Front, 110 } 111 } 112 } 113 114 /// Allows functions and methods to retrieve common information about 115 /// a spatial node, whether during scene or frame building 116 pub trait SpatialNodeContainer { 117 /// Get the common information for a given spatial node 118 fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo; 119 120 fn get_snapping_info( 121 &self, 122 parent_index: Option<SpatialNodeIndex> 123 ) -> Option<ScaleOffset> { 124 match parent_index { 125 Some(parent_index) => { 126 let node_info = self.get_node_info(parent_index); 127 node_info.snapping_transform 128 } 129 None => { 130 Some(ScaleOffset::identity()) 131 } 132 } 133 } 134 } 135 136 #[cfg_attr(feature = "capture", derive(Serialize))] 137 #[cfg_attr(feature = "replay", derive(Deserialize))] 138 enum StoreElement<T> { 139 Empty, 140 Occupied(T), 141 } 142 143 #[cfg_attr(feature = "capture", derive(Serialize))] 144 #[cfg_attr(feature = "replay", derive(Deserialize))] 145 struct Store<T> { 146 elements: Vec<StoreElement<T>>, 147 free_indices: Vec<usize>, 148 } 149 150 impl<T> Store<T> { 151 fn new() -> Self { 152 Store { 153 elements: Vec::new(), 154 free_indices: Vec::new(), 155 } 156 } 157 158 fn insert(&mut self, element: T) -> usize { 159 match self.free_indices.pop() { 160 Some(index) => { 161 match &mut self.elements[index] { 162 e @ StoreElement::Empty => *e = StoreElement::Occupied(element), 163 StoreElement::Occupied(..) => panic!("bug: slot already occupied"), 164 }; 165 index 166 } 167 None => { 168 let index = self.elements.len(); 169 self.elements.push(StoreElement::Occupied(element)); 170 index 171 } 172 } 173 } 174 175 fn set(&mut self, index: usize, element: T) { 176 match &mut self.elements[index] { 177 StoreElement::Empty => panic!("bug: set on empty element!"), 178 StoreElement::Occupied(ref mut entry) => *entry = element, 179 } 180 } 181 182 fn free(&mut self, index: usize) -> T { 183 self.free_indices.push(index); 184 185 let value = std::mem::replace(&mut self.elements[index], StoreElement::Empty); 186 187 match value { 188 StoreElement::Occupied(value) => value, 189 StoreElement::Empty => panic!("bug: freeing an empty slot"), 190 } 191 } 192 } 193 194 impl<T> ops::Index<usize> for Store<T> { 195 type Output = T; 196 fn index(&self, index: usize) -> &Self::Output { 197 match self.elements[index] { 198 StoreElement::Occupied(ref e) => e, 199 StoreElement::Empty => panic!("bug: indexing an empty element!"), 200 } 201 } 202 } 203 204 impl<T> ops::IndexMut<usize> for Store<T> { 205 fn index_mut(&mut self, index: usize) -> &mut T { 206 match self.elements[index] { 207 StoreElement::Occupied(ref mut e) => e, 208 StoreElement::Empty => panic!("bug: indexing an empty element!"), 209 } 210 } 211 } 212 213 #[cfg_attr(feature = "capture", derive(Serialize))] 214 #[cfg_attr(feature = "replay", derive(Deserialize))] 215 struct SpatialNodeEntry { 216 index: usize, 217 last_used: u64, 218 } 219 220 /// The representation of the spatial tree during scene building, which is 221 /// mostly write-only, with a small number of queries for snapping, 222 /// picture cache building 223 #[cfg_attr(feature = "capture", derive(Serialize))] 224 #[cfg_attr(feature = "replay", derive(Deserialize))] 225 pub struct SceneSpatialTree { 226 /// Nodes which determine the positions (offsets and transforms) for primitives 227 /// and clips. 228 spatial_nodes: Store<SceneSpatialNode>, 229 230 /// A set of the uids we've encountered for spatial nodes, used to assert that 231 /// we're not seeing duplicates. Likely to be removed once we rely on this feature. 232 spatial_node_map: FastHashMap<SpatialNodeUid, SpatialNodeEntry>, 233 234 root_reference_frame_index: SpatialNodeIndex, 235 236 frame_counter: u64, 237 updates: SpatialTreeUpdates, 238 239 /// A debug check that the caller never adds a spatial node with duplicate 240 /// uid, since that can cause badness if it occurs (e.g. a malformed spatial 241 /// tree and infinite loops in is_ancestor etc) 242 spatial_nodes_set: FastHashSet<SpatialNodeUid>, 243 } 244 245 impl SpatialNodeContainer for SceneSpatialTree { 246 fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo { 247 let node = &self.spatial_nodes[index.0 as usize]; 248 249 SpatialNodeInfo { 250 parent: node.parent, 251 node_type: &node.descriptor.node_type, 252 snapping_transform: node.snapping_transform, 253 } 254 } 255 } 256 257 impl SceneSpatialTree { 258 pub fn new() -> Self { 259 let mut tree = SceneSpatialTree { 260 spatial_nodes: Store::new(), 261 spatial_node_map: FastHashMap::default(), 262 root_reference_frame_index: SpatialNodeIndex(0), 263 frame_counter: 0, 264 updates: SpatialTreeUpdates::new(), 265 spatial_nodes_set: FastHashSet::default(), 266 }; 267 268 let node = SceneSpatialNode::new_reference_frame( 269 None, 270 TransformStyle::Flat, 271 PropertyBinding::Value(LayoutTransform::identity()), 272 ReferenceFrameKind::Transform { 273 should_snap: true, 274 is_2d_scale_translation: true, 275 paired_with_perspective: false, 276 }, 277 LayoutVector2D::zero(), 278 PipelineId::dummy(), 279 true, 280 true, 281 ); 282 283 tree.add_spatial_node(node, SpatialNodeUid::root()); 284 285 tree 286 } 287 288 pub fn is_root_coord_system(&self, index: SpatialNodeIndex) -> bool { 289 self.spatial_nodes[index.0 as usize].is_root_coord_system 290 } 291 292 /// Complete building this scene, return the updates to apply to the frame spatial tree 293 pub fn end_frame_and_get_pending_updates(&mut self) -> SpatialTreeUpdates { 294 self.updates.root_reference_frame_index = self.root_reference_frame_index; 295 self.spatial_nodes_set.clear(); 296 297 let now = self.frame_counter; 298 let spatial_nodes = &mut self.spatial_nodes; 299 let updates = &mut self.updates; 300 301 self.spatial_node_map.get_mut(&SpatialNodeUid::root()).unwrap().last_used = now; 302 303 self.spatial_node_map.retain(|_, entry| { 304 if entry.last_used + 10 < now { 305 spatial_nodes.free(entry.index); 306 updates.updates.push(SpatialTreeUpdate::Remove { 307 index: entry.index, 308 }); 309 return false; 310 } 311 312 true 313 }); 314 315 let updates = std::mem::replace(&mut self.updates, SpatialTreeUpdates::new()); 316 317 self.frame_counter += 1; 318 319 updates 320 } 321 322 /// Check if a given spatial node is an ancestor of another spatial node. 323 pub fn is_ancestor( 324 &self, 325 maybe_parent: SpatialNodeIndex, 326 maybe_child: SpatialNodeIndex, 327 ) -> bool { 328 // Early out if same node 329 if maybe_parent == maybe_child { 330 return false; 331 } 332 333 let mut current_node = maybe_child; 334 335 while current_node != self.root_reference_frame_index { 336 let node = self.get_node_info(current_node); 337 current_node = node.parent.expect("bug: no parent"); 338 339 if current_node == maybe_parent { 340 return true; 341 } 342 } 343 344 false 345 } 346 347 /// Find the spatial node that is the scroll root for a given spatial node. 348 /// A scroll root is the first spatial node when found travelling up the 349 /// spatial node tree that is an explicit scroll frame. 350 pub fn find_scroll_root( 351 &self, 352 spatial_node_index: SpatialNodeIndex, 353 allow_sticky_frames: bool, 354 ) -> SpatialNodeIndex { 355 let mut real_scroll_root = self.root_reference_frame_index; 356 let mut outermost_scroll_root = self.root_reference_frame_index; 357 let mut current_scroll_root_is_sticky = false; 358 let mut node_index = spatial_node_index; 359 360 while node_index != self.root_reference_frame_index { 361 let node = self.get_node_info(node_index); 362 match node.node_type { 363 SpatialNodeType::ReferenceFrame(ref info) => { 364 match info.kind { 365 ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } => { 366 // We can handle scroll nodes that pass through a 2d scale/translation node 367 } 368 ReferenceFrameKind::Transform { is_2d_scale_translation: false, .. } | 369 ReferenceFrameKind::Perspective { .. } => { 370 // When a reference frame is encountered, forget any scroll roots 371 // we have encountered, as they may end up with a non-axis-aligned transform. 372 real_scroll_root = self.root_reference_frame_index; 373 outermost_scroll_root = self.root_reference_frame_index; 374 current_scroll_root_is_sticky = false; 375 } 376 } 377 } 378 SpatialNodeType::StickyFrame(..) => { 379 // Though not a scroll frame, we optionally treat sticky frames as scroll roots 380 // to ensure they are given a separate picture cache slice. 381 if allow_sticky_frames { 382 outermost_scroll_root = node_index; 383 real_scroll_root = node_index; 384 // Set this true so that we don't select an ancestor scroll frame as the scroll root 385 // on a subsequent iteration. 386 current_scroll_root_is_sticky = true; 387 } 388 } 389 SpatialNodeType::ScrollFrame(ref info) => { 390 match info.frame_kind { 391 ScrollFrameKind::PipelineRoot { is_root_pipeline } => { 392 // Once we encounter a pipeline root, there is no need to look further 393 if is_root_pipeline { 394 break; 395 } 396 } 397 ScrollFrameKind::Explicit => { 398 // Store the closest scroll root we find to the root, for use 399 // later on, even if it's not actually scrollable. 400 outermost_scroll_root = node_index; 401 402 // If the previously identified scroll root is sticky then we don't 403 // want to choose an ancestor scroll root, as we want the sticky item 404 // to have its own picture cache slice. 405 if !current_scroll_root_is_sticky { 406 // If the scroll root has no scrollable area, we don't want to 407 // consider it. This helps pages that have a nested scroll root 408 // within a redundant scroll root to avoid selecting the wrong 409 // reference spatial node for a picture cache. 410 if info.scrollable_size.width > MIN_SCROLLABLE_AMOUNT || 411 info.scrollable_size.height > MIN_SCROLLABLE_AMOUNT { 412 // Since we are skipping redundant scroll roots, we may end up 413 // selecting inner scroll roots that are very small. There is 414 // no performance benefit to creating a slice for these roots, 415 // as they are cheap to rasterize. The size comparison is in 416 // local-space, but makes for a reasonable estimate. The value 417 // is arbitrary, but is generally small enough to ignore things 418 // like scroll roots around text input elements. 419 if info.viewport_rect.width() > MIN_SCROLL_ROOT_SIZE && 420 info.viewport_rect.height() > MIN_SCROLL_ROOT_SIZE { 421 // If we've found a root that is scrollable, and a reasonable 422 // size, select that as the current root for this node 423 real_scroll_root = node_index; 424 } 425 } 426 } 427 } 428 } 429 } 430 } 431 node_index = node.parent.expect("unable to find parent node"); 432 } 433 434 // If we didn't find any real (scrollable) frames, then return the outermost 435 // redundant scroll frame. This is important so that we can correctly find 436 // the clips defined on the content which should be handled when drawing the 437 // picture cache tiles (by definition these clips are ancestors of the 438 // scroll root selected for the picture cache). 439 if real_scroll_root == self.root_reference_frame_index { 440 outermost_scroll_root 441 } else { 442 real_scroll_root 443 } 444 } 445 446 /// The root reference frame, which is the true root of the SpatialTree. 447 pub fn root_reference_frame_index(&self) -> SpatialNodeIndex { 448 self.root_reference_frame_index 449 } 450 451 fn add_spatial_node( 452 &mut self, 453 mut node: SceneSpatialNode, 454 uid: SpatialNodeUid, 455 ) -> SpatialNodeIndex { 456 let parent_info = self.get_snapping_info(node.parent); 457 458 node.snapping_transform = calculate_snapping_transform( 459 parent_info, 460 &node.descriptor.node_type, 461 ); 462 463 // Ensure a node with the same uid hasn't been added during this scene build 464 assert!(self.spatial_nodes_set.insert(uid), "duplicate key {:?}", uid); 465 466 let index = match self.spatial_node_map.entry(uid) { 467 Entry::Occupied(mut e) => { 468 let e = e.get_mut(); 469 e.last_used = self.frame_counter; 470 471 let existing_node = &self.spatial_nodes[e.index]; 472 473 if *existing_node != node { 474 self.updates.updates.push(SpatialTreeUpdate::Update { 475 index: e.index, 476 parent: node.parent, 477 descriptor: node.descriptor.clone(), 478 }); 479 self.spatial_nodes.set(e.index, node); 480 } 481 482 e.index 483 } 484 Entry::Vacant(e) => { 485 let descriptor = node.descriptor.clone(); 486 let parent = node.parent; 487 488 let index = self.spatial_nodes.insert(node); 489 490 e.insert(SpatialNodeEntry { 491 index, 492 last_used: self.frame_counter, 493 }); 494 495 self.updates.updates.push(SpatialTreeUpdate::Insert { 496 index, 497 descriptor, 498 parent, 499 }); 500 501 index 502 } 503 }; 504 505 SpatialNodeIndex(index as u32) 506 } 507 508 pub fn add_reference_frame( 509 &mut self, 510 parent_index: SpatialNodeIndex, 511 transform_style: TransformStyle, 512 source_transform: PropertyBinding<LayoutTransform>, 513 kind: ReferenceFrameKind, 514 origin_in_parent_reference_frame: LayoutVector2D, 515 pipeline_id: PipelineId, 516 uid: SpatialNodeUid, 517 ) -> SpatialNodeIndex { 518 // Determine if this reference frame creates a new static coordinate system 519 let new_static_coord_system = match kind { 520 ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } => { 521 // Client has guaranteed this transform will only be axis-aligned 522 false 523 } 524 ReferenceFrameKind::Transform { is_2d_scale_translation: false, .. } | ReferenceFrameKind::Perspective { .. } => { 525 // Even if client hasn't promised it's an axis-aligned transform, we can still 526 // check this so long as the transform isn't animated (and thus could change to 527 // anything by APZ during frame building) 528 match source_transform { 529 PropertyBinding::Value(m) => { 530 !m.is_2d_scale_translation() 531 } 532 PropertyBinding::Binding(..) => { 533 // Animated, so assume it may introduce a complex transform 534 true 535 } 536 } 537 } 538 }; 539 540 let is_root_coord_system = !new_static_coord_system && 541 self.spatial_nodes[parent_index.0 as usize].is_root_coord_system; 542 let is_pipeline_root = match uid.kind { 543 SpatialNodeUidKind::InternalReferenceFrame { .. } => true, 544 _ => false, 545 }; 546 547 let node = SceneSpatialNode::new_reference_frame( 548 Some(parent_index), 549 transform_style, 550 source_transform, 551 kind, 552 origin_in_parent_reference_frame, 553 pipeline_id, 554 is_root_coord_system, 555 is_pipeline_root, 556 ); 557 self.add_spatial_node(node, uid) 558 } 559 560 pub fn add_scroll_frame( 561 &mut self, 562 parent_index: SpatialNodeIndex, 563 external_id: ExternalScrollId, 564 pipeline_id: PipelineId, 565 frame_rect: &LayoutRect, 566 content_size: &LayoutSize, 567 frame_kind: ScrollFrameKind, 568 external_scroll_offset: LayoutVector2D, 569 scroll_offset_generation: APZScrollGeneration, 570 has_scroll_linked_effect: HasScrollLinkedEffect, 571 uid: SpatialNodeUid, 572 ) -> SpatialNodeIndex { 573 // Scroll frames are only 2d translations - they can't introduce a new static coord system 574 let is_root_coord_system = self.spatial_nodes[parent_index.0 as usize].is_root_coord_system; 575 576 let node = SceneSpatialNode::new_scroll_frame( 577 pipeline_id, 578 parent_index, 579 external_id, 580 frame_rect, 581 content_size, 582 frame_kind, 583 external_scroll_offset, 584 scroll_offset_generation, 585 has_scroll_linked_effect, 586 is_root_coord_system, 587 ); 588 self.add_spatial_node(node, uid) 589 } 590 591 pub fn add_sticky_frame( 592 &mut self, 593 parent_index: SpatialNodeIndex, 594 sticky_frame_info: StickyFrameInfo, 595 pipeline_id: PipelineId, 596 key: SpatialTreeItemKey, 597 instance_id: PipelineInstanceId, 598 ) -> SpatialNodeIndex { 599 // Sticky frames are only 2d translations - they can't introduce a new static coord system 600 let is_root_coord_system = self.spatial_nodes[parent_index.0 as usize].is_root_coord_system; 601 let uid = SpatialNodeUid::external(key, pipeline_id, instance_id); 602 603 let node = SceneSpatialNode::new_sticky_frame( 604 parent_index, 605 sticky_frame_info, 606 pipeline_id, 607 is_root_coord_system, 608 ); 609 self.add_spatial_node(node, uid) 610 } 611 } 612 613 #[cfg_attr(feature = "capture", derive(Serialize))] 614 #[cfg_attr(feature = "replay", derive(Deserialize))] 615 pub enum SpatialTreeUpdate { 616 Insert { 617 index: usize, 618 parent: Option<SpatialNodeIndex>, 619 descriptor: SpatialNodeDescriptor, 620 }, 621 Update { 622 index: usize, 623 parent: Option<SpatialNodeIndex>, 624 descriptor: SpatialNodeDescriptor, 625 }, 626 Remove { 627 index: usize, 628 }, 629 } 630 631 /// The delta updates to apply after building a new scene to the retained frame building 632 /// tree. 633 // TODO(gw): During the initial scaffolding work, this is the exact same as previous 634 // behavior - that is, a complete list of new spatial nodes. In future, this 635 // will instead be a list of deltas to apply to the frame spatial tree. 636 #[cfg_attr(feature = "capture", derive(Serialize))] 637 #[cfg_attr(feature = "replay", derive(Deserialize))] 638 pub struct SpatialTreeUpdates { 639 root_reference_frame_index: SpatialNodeIndex, 640 updates: Vec<SpatialTreeUpdate>, 641 } 642 643 impl SpatialTreeUpdates { 644 fn new() -> Self { 645 SpatialTreeUpdates { 646 root_reference_frame_index: SpatialNodeIndex::INVALID, 647 updates: Vec::new(), 648 } 649 } 650 } 651 652 /// Represents the spatial tree during frame building, which is mostly 653 /// read-only, apart from the tree update at the start of the frame 654 #[cfg_attr(feature = "capture", derive(Serialize))] 655 #[cfg_attr(feature = "replay", derive(Deserialize))] 656 pub struct SpatialTree { 657 /// Nodes which determine the positions (offsets and transforms) for primitives 658 /// and clips. 659 spatial_nodes: Vec<SpatialNode>, 660 661 /// A list of transforms that establish new coordinate systems. 662 /// Spatial nodes only establish a new coordinate system when 663 /// they have a transform that is not a simple 2d translation. 664 coord_systems: Vec<CoordinateSystem>, 665 666 root_reference_frame_index: SpatialNodeIndex, 667 668 /// Stack of current state for each parent node while traversing and updating tree 669 update_state_stack: Vec<TransformUpdateState>, 670 671 next_internal_uid: u64, 672 } 673 674 #[derive(Clone)] 675 #[cfg_attr(feature = "capture", derive(Serialize))] 676 #[cfg_attr(feature = "replay", derive(Deserialize))] 677 pub struct TransformUpdateState { 678 pub parent_reference_frame_transform: LayoutToWorldFastTransform, 679 pub parent_accumulated_scroll_offset: LayoutVector2D, 680 pub nearest_scrolling_ancestor_offset: LayoutVector2D, 681 pub nearest_scrolling_ancestor_viewport: LayoutRect, 682 683 /// An id for keeping track of the axis-aligned space of this node. This is used in 684 /// order to to track what kinds of clip optimizations can be done for a particular 685 /// display list item, since optimizations can usually only be done among 686 /// coordinate systems which are relatively axis aligned. 687 pub current_coordinate_system_id: CoordinateSystemId, 688 689 /// Scale and offset from the coordinate system that started this compatible coordinate system. 690 pub coordinate_system_relative_scale_offset: ScaleOffset, 691 692 /// True if this node is transformed by an invertible transform. If not, display items 693 /// transformed by this node will not be displayed and display items not transformed by this 694 /// node will not be clipped by clips that are transformed by this node. 695 pub invertible: bool, 696 697 /// True if this node is a part of Preserve3D hierarchy. 698 pub preserves_3d: bool, 699 700 /// True if the any parent nodes are currently zooming 701 pub is_ancestor_or_self_zooming: bool, 702 703 /// Set to true if this state represents a scroll node with external id 704 pub external_id: Option<ExternalScrollId>, 705 706 /// The node scroll offset if this state is a scroll/sticky node. Zero if a reference frame. 707 pub scroll_offset: LayoutVector2D, 708 } 709 710 /// Transformation between two nodes in the spatial tree that can sometimes be 711 /// encoded more efficiently than with a full matrix. 712 #[derive(Debug, Clone)] 713 pub enum CoordinateSpaceMapping<Src, Dst> { 714 Local, 715 ScaleOffset(ScaleOffset), 716 Transform(Transform3D<f32, Src, Dst>), 717 } 718 719 impl<Src, Dst> CoordinateSpaceMapping<Src, Dst> { 720 pub fn into_transform(self) -> Transform3D<f32, Src, Dst> { 721 match self { 722 CoordinateSpaceMapping::Local => Transform3D::identity(), 723 CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset.to_transform(), 724 CoordinateSpaceMapping::Transform(transform) => transform, 725 } 726 } 727 728 pub fn into_fast_transform(self) -> FastTransform<Src, Dst> { 729 match self { 730 CoordinateSpaceMapping::Local => FastTransform::identity(), 731 CoordinateSpaceMapping::ScaleOffset(scale_offset) => FastTransform::with_scale_offset(scale_offset), 732 CoordinateSpaceMapping::Transform(transform) => FastTransform::with_transform(transform), 733 } 734 } 735 736 pub fn is_perspective(&self) -> bool { 737 match *self { 738 CoordinateSpaceMapping::Local | 739 CoordinateSpaceMapping::ScaleOffset(_) => false, 740 CoordinateSpaceMapping::Transform(ref transform) => transform.has_perspective_component(), 741 } 742 } 743 744 pub fn is_2d_axis_aligned(&self) -> bool { 745 match *self { 746 CoordinateSpaceMapping::Local | 747 CoordinateSpaceMapping::ScaleOffset(_) => true, 748 CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(), 749 } 750 } 751 752 pub fn is_2d_scale_translation(&self) -> bool { 753 match *self { 754 CoordinateSpaceMapping::Local | 755 CoordinateSpaceMapping::ScaleOffset(_) => true, 756 CoordinateSpaceMapping::Transform(ref transform) => transform.is_2d_scale_translation(), 757 } 758 } 759 760 pub fn scale_factors(&self) -> (f32, f32) { 761 match *self { 762 CoordinateSpaceMapping::Local => (1.0, 1.0), 763 CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => (scale_offset.scale.x.abs(), scale_offset.scale.y.abs()), 764 CoordinateSpaceMapping::Transform(ref transform) => scale_factors(transform), 765 } 766 } 767 768 pub fn inverse(&self) -> Option<CoordinateSpaceMapping<Dst, Src>> { 769 match *self { 770 CoordinateSpaceMapping::Local => Some(CoordinateSpaceMapping::Local), 771 CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { 772 Some(CoordinateSpaceMapping::ScaleOffset(scale_offset.inverse())) 773 } 774 CoordinateSpaceMapping::Transform(ref transform) => { 775 transform.inverse().map(CoordinateSpaceMapping::Transform) 776 } 777 } 778 } 779 780 pub fn as_2d_scale_offset(&self) -> Option<ScaleOffset> { 781 Some(match *self { 782 CoordinateSpaceMapping::Local => ScaleOffset::identity(), 783 CoordinateSpaceMapping::ScaleOffset(transfrom) => transfrom, 784 CoordinateSpaceMapping::Transform(ref transform) => { 785 if !transform.is_2d_scale_translation() { 786 return None 787 } 788 ScaleOffset::new(transform.m11, transform.m22, transform.m41, transform.m42) 789 } 790 }) 791 } 792 } 793 794 enum TransformScroll { 795 Scrolled, 796 Unscrolled, 797 } 798 799 impl SpatialNodeContainer for SpatialTree { 800 fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo { 801 let node = self.get_spatial_node(index); 802 803 SpatialNodeInfo { 804 parent: node.parent, 805 node_type: &node.node_type, 806 snapping_transform: node.snapping_transform, 807 } 808 } 809 } 810 811 impl SpatialTree { 812 pub fn new() -> Self { 813 SpatialTree { 814 spatial_nodes: Vec::new(), 815 coord_systems: Vec::new(), 816 root_reference_frame_index: SpatialNodeIndex::INVALID, 817 update_state_stack: Vec::new(), 818 next_internal_uid: 1, 819 } 820 } 821 822 fn visit_node_impl_mut<F>( 823 &mut self, 824 index: SpatialNodeIndex, 825 f: &mut F, 826 ) where F: FnMut(SpatialNodeIndex, &mut SpatialNode) { 827 let mut child_indices: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new(); 828 829 let node = self.get_spatial_node_mut(index); 830 f(index, node); 831 child_indices.extend_from_slice(&node.children); 832 833 for child_index in child_indices { 834 self.visit_node_impl_mut(child_index, f); 835 } 836 } 837 838 fn visit_node_impl<F>( 839 &self, 840 index: SpatialNodeIndex, 841 f: &mut F, 842 ) where F: FnMut(SpatialNodeIndex, &SpatialNode) { 843 let node = self.get_spatial_node(index); 844 845 f(index, node); 846 847 for child_index in &node.children { 848 self.visit_node_impl(*child_index, f); 849 } 850 } 851 852 /// Visit all nodes from the root of the tree, invoking a closure on each one 853 pub fn visit_nodes<F>(&self, mut f: F) where F: FnMut(SpatialNodeIndex, &SpatialNode) { 854 if self.root_reference_frame_index == SpatialNodeIndex::INVALID { 855 return; 856 } 857 858 self.visit_node_impl(self.root_reference_frame_index, &mut f); 859 } 860 861 /// Visit all nodes from the root of the tree, invoking a closure on each one 862 pub fn visit_nodes_mut<F>(&mut self, mut f: F) where F: FnMut(SpatialNodeIndex, &mut SpatialNode) { 863 if self.root_reference_frame_index == SpatialNodeIndex::INVALID { 864 return; 865 } 866 867 self.visit_node_impl_mut(self.root_reference_frame_index, &mut f); 868 } 869 870 /// Apply updates from a new scene to the frame spatial tree 871 pub fn apply_updates( 872 &mut self, 873 updates: SpatialTreeUpdates, 874 ) { 875 self.root_reference_frame_index = updates.root_reference_frame_index; 876 877 for update in updates.updates { 878 match update { 879 SpatialTreeUpdate::Insert { index, parent, descriptor } => { 880 if let Some(parent) = parent { 881 self.get_spatial_node_mut(parent).add_child(SpatialNodeIndex(index as u32)); 882 } 883 884 let uid = self.next_internal_uid; 885 self.next_internal_uid += 1; 886 887 let node = SpatialNode { 888 viewport_transform: ScaleOffset::identity(), 889 content_transform: ScaleOffset::identity(), 890 snapping_transform: None, 891 coordinate_system_id: CoordinateSystemId(0), 892 transform_kind: TransformedRectKind::AxisAligned, 893 parent, 894 children: Vec::new(), 895 pipeline_id: descriptor.pipeline_id, 896 node_type: descriptor.node_type, 897 invertible: true, 898 is_async_zooming: false, 899 is_ancestor_or_self_zooming: false, 900 uid, 901 }; 902 903 assert!(index <= self.spatial_nodes.len()); 904 if index < self.spatial_nodes.len() { 905 self.spatial_nodes[index] = node; 906 } else { 907 self.spatial_nodes.push(node); 908 } 909 } 910 SpatialTreeUpdate::Update { index, descriptor, parent } => { 911 let current_parent = self.spatial_nodes[index].parent; 912 913 if current_parent != parent { 914 if let Some(current_parent) = current_parent { 915 let i = self.spatial_nodes[current_parent.0 as usize] 916 .children 917 .iter() 918 .position(|e| e.0 as usize == index) 919 .expect("bug: not found!"); 920 self.spatial_nodes[current_parent.0 as usize].children.remove(i); 921 } 922 923 let new_parent = parent.expect("todo: is this valid?"); 924 self.spatial_nodes[new_parent.0 as usize].add_child(SpatialNodeIndex(index as u32)); 925 } 926 927 let uid = self.next_internal_uid; 928 self.next_internal_uid += 1; 929 930 let node = &mut self.spatial_nodes[index]; 931 932 node.node_type = descriptor.node_type; 933 node.pipeline_id = descriptor.pipeline_id; 934 node.parent = parent; 935 node.uid = uid; 936 } 937 SpatialTreeUpdate::Remove { index, .. } => { 938 let node = &mut self.spatial_nodes[index]; 939 940 // Set the pipeline id to be invalid, so that even though this array 941 // entry still exists we can easily see it's invalid when debugging. 942 node.pipeline_id = PipelineId::dummy(); 943 944 if let Some(parent) = node.parent { 945 let i = self.spatial_nodes[parent.0 as usize] 946 .children 947 .iter() 948 .position(|e| e.0 as usize == index) 949 .expect("bug: not found!"); 950 self.spatial_nodes[parent.0 as usize].children.remove(i); 951 } 952 } 953 } 954 } 955 956 self.visit_nodes_mut(|_, node| { 957 match node.node_type { 958 SpatialNodeType::ScrollFrame(ref mut info) => { 959 info.offsets = vec![SampledScrollOffset{ 960 offset: -info.external_scroll_offset, 961 generation: info.offset_generation, 962 }]; 963 } 964 SpatialNodeType::StickyFrame(ref mut info) => { 965 info.current_offset = LayoutVector2D::zero(); 966 } 967 SpatialNodeType::ReferenceFrame(..) => {} 968 } 969 }); 970 } 971 972 pub fn get_last_sampled_scroll_offsets( 973 &self, 974 ) -> FastHashMap<ExternalScrollId, Vec<SampledScrollOffset>> { 975 let mut result = FastHashMap::default(); 976 self.visit_nodes(|_, node| { 977 if let SpatialNodeType::ScrollFrame(ref scrolling) = node.node_type { 978 result.insert(scrolling.external_id, scrolling.offsets.clone()); 979 } 980 }); 981 result 982 } 983 984 pub fn apply_last_sampled_scroll_offsets( 985 &mut self, 986 last_sampled_offsets: FastHashMap<ExternalScrollId, Vec<SampledScrollOffset>>, 987 ) { 988 self.visit_nodes_mut(|_, node| { 989 if let SpatialNodeType::ScrollFrame(ref mut scrolling) = node.node_type { 990 if let Some(offsets) = last_sampled_offsets.get(&scrolling.external_id) { 991 scrolling.offsets = offsets.clone(); 992 } 993 } 994 }); 995 } 996 997 pub fn get_spatial_node(&self, index: SpatialNodeIndex) -> &SpatialNode { 998 &self.spatial_nodes[index.0 as usize] 999 } 1000 1001 pub fn get_spatial_node_mut(&mut self, index: SpatialNodeIndex) -> &mut SpatialNode { 1002 &mut self.spatial_nodes[index.0 as usize] 1003 } 1004 1005 /// Get total number of spatial nodes 1006 pub fn spatial_node_count(&self) -> usize { 1007 self.spatial_nodes.len() 1008 } 1009 1010 pub fn find_spatial_node_by_anim_id( 1011 &self, 1012 id: PropertyBindingId, 1013 ) -> Option<SpatialNodeIndex> { 1014 let mut node_index = None; 1015 1016 self.visit_nodes(|index, node| { 1017 if node.is_transform_bound_to_property(id) { 1018 debug_assert!(node_index.is_none()); // Multiple nodes with same anim id 1019 node_index = Some(index); 1020 } 1021 }); 1022 1023 node_index 1024 } 1025 1026 /// Calculate the relative transform from `child_index` to `parent_index`. 1027 /// This method will panic if the nodes are not connected! 1028 pub fn get_relative_transform( 1029 &self, 1030 child_index: SpatialNodeIndex, 1031 parent_index: SpatialNodeIndex, 1032 ) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> { 1033 self.get_relative_transform_with_face(child_index, parent_index, None) 1034 } 1035 1036 /// Calculate the relative transform from `child_index` to `parent_index`. 1037 /// This method will panic if the nodes are not connected! 1038 /// Also, switch the visible face to `Back` if at any stage where the 1039 /// combined transform is flattened, we see the back face. 1040 pub fn get_relative_transform_with_face( 1041 &self, 1042 child_index: SpatialNodeIndex, 1043 parent_index: SpatialNodeIndex, 1044 mut visible_face: Option<&mut VisibleFace>, 1045 ) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> { 1046 if child_index == parent_index { 1047 return CoordinateSpaceMapping::Local; 1048 } 1049 1050 let child = self.get_spatial_node(child_index); 1051 let parent = self.get_spatial_node(parent_index); 1052 1053 // TODO(gw): We expect this never to fail, but it's possible that it might due to 1054 // either (a) a bug in WR / Gecko, or (b) some obscure real-world content 1055 // that we're unaware of. If we ever hit this, please open a bug with any 1056 // repro steps! 1057 assert!( 1058 child.coordinate_system_id.0 >= parent.coordinate_system_id.0, 1059 "bug: this is an unexpected case - please open a bug and talk to #gfx team!", 1060 ); 1061 1062 if child.coordinate_system_id == parent.coordinate_system_id { 1063 let scale_offset = child.content_transform.then(&parent.content_transform.inverse()); 1064 return CoordinateSpaceMapping::ScaleOffset(scale_offset); 1065 } 1066 1067 let mut coordinate_system_id = child.coordinate_system_id; 1068 let mut transform = child.content_transform.to_transform(); 1069 1070 // we need to update the associated parameters of a transform in two cases: 1071 // 1) when the flattening happens, so that we don't lose that original 3D aspects 1072 // 2) when we reach the end of iteration, so that our result is up to date 1073 1074 while coordinate_system_id != parent.coordinate_system_id { 1075 let coord_system = &self.coord_systems[coordinate_system_id.0 as usize]; 1076 1077 if coord_system.should_flatten { 1078 if let Some(ref mut face) = visible_face { 1079 if transform.is_backface_visible() { 1080 **face = VisibleFace::Back; 1081 } 1082 } 1083 transform.flatten_z_output(); 1084 } 1085 1086 coordinate_system_id = coord_system.parent.expect("invalid parent!"); 1087 transform = transform.then(&coord_system.transform); 1088 } 1089 1090 transform = transform.then( 1091 &parent.content_transform 1092 .inverse() 1093 .to_transform(), 1094 ); 1095 if let Some(face) = visible_face { 1096 if transform.is_backface_visible() { 1097 *face = VisibleFace::Back; 1098 } 1099 } 1100 1101 CoordinateSpaceMapping::Transform(transform) 1102 } 1103 1104 /// Returns true if both supplied spatial nodes are in the same coordinate system 1105 /// (implies the relative transform produce axis-aligned rects). 1106 pub fn is_matching_coord_system( 1107 &self, 1108 index0: SpatialNodeIndex, 1109 index1: SpatialNodeIndex, 1110 ) -> bool { 1111 let node0 = self.get_spatial_node(index0); 1112 let node1 = self.get_spatial_node(index1); 1113 1114 node0.coordinate_system_id == node1.coordinate_system_id 1115 } 1116 1117 fn get_world_transform_impl( 1118 &self, 1119 index: SpatialNodeIndex, 1120 scroll: TransformScroll, 1121 ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> { 1122 let child = self.get_spatial_node(index); 1123 1124 if child.coordinate_system_id.0 == 0 { 1125 if index == self.root_reference_frame_index { 1126 CoordinateSpaceMapping::Local 1127 } else { 1128 match scroll { 1129 TransformScroll::Scrolled => CoordinateSpaceMapping::ScaleOffset(child.content_transform), 1130 TransformScroll::Unscrolled => CoordinateSpaceMapping::ScaleOffset(child.viewport_transform), 1131 } 1132 } 1133 } else { 1134 let system = &self.coord_systems[child.coordinate_system_id.0 as usize]; 1135 let scale_offset = match scroll { 1136 TransformScroll::Scrolled => &child.content_transform, 1137 TransformScroll::Unscrolled => &child.viewport_transform, 1138 }; 1139 let transform = scale_offset 1140 .to_transform() 1141 .then(&system.world_transform); 1142 1143 CoordinateSpaceMapping::Transform(transform) 1144 } 1145 } 1146 1147 /// Calculate the relative transform from `index` to the root. 1148 pub fn get_world_transform( 1149 &self, 1150 index: SpatialNodeIndex, 1151 ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> { 1152 self.get_world_transform_impl(index, TransformScroll::Scrolled) 1153 } 1154 1155 /// Calculate the relative transform from `index` to the root. 1156 /// Unlike `get_world_transform`, this variant doesn't account for the local scroll offset. 1157 pub fn get_world_viewport_transform( 1158 &self, 1159 index: SpatialNodeIndex, 1160 ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> { 1161 self.get_world_transform_impl(index, TransformScroll::Unscrolled) 1162 } 1163 1164 /// The root reference frame, which is the true root of the SpatialTree. 1165 pub fn root_reference_frame_index(&self) -> SpatialNodeIndex { 1166 self.root_reference_frame_index 1167 } 1168 1169 pub fn set_scroll_offsets( 1170 &mut self, 1171 id: ExternalScrollId, 1172 offsets: Vec<SampledScrollOffset>, 1173 ) -> bool { 1174 let mut did_change = false; 1175 1176 self.visit_nodes_mut(|_, node| { 1177 if node.matches_external_id(id) { 1178 did_change |= node.set_scroll_offsets(offsets.clone()); 1179 } 1180 }); 1181 1182 did_change 1183 } 1184 1185 pub fn update_tree( 1186 &mut self, 1187 scene_properties: &SceneProperties, 1188 ) { 1189 if self.root_reference_frame_index == SpatialNodeIndex::INVALID { 1190 return; 1191 } 1192 1193 profile_scope!("update_tree"); 1194 self.coord_systems.clear(); 1195 self.coord_systems.push(CoordinateSystem::root()); 1196 1197 let root_node_index = self.root_reference_frame_index(); 1198 assert!(self.update_state_stack.is_empty()); 1199 1200 let state = TransformUpdateState { 1201 parent_reference_frame_transform: LayoutVector2D::zero().into(), 1202 parent_accumulated_scroll_offset: LayoutVector2D::zero(), 1203 nearest_scrolling_ancestor_offset: LayoutVector2D::zero(), 1204 nearest_scrolling_ancestor_viewport: LayoutRect::zero(), 1205 current_coordinate_system_id: CoordinateSystemId::root(), 1206 coordinate_system_relative_scale_offset: ScaleOffset::identity(), 1207 invertible: true, 1208 preserves_3d: false, 1209 is_ancestor_or_self_zooming: false, 1210 external_id: None, 1211 scroll_offset: LayoutVector2D::zero(), 1212 }; 1213 self.update_state_stack.push(state); 1214 1215 self.update_node( 1216 root_node_index, 1217 scene_properties, 1218 ); 1219 1220 self.update_state_stack.pop().unwrap(); 1221 } 1222 1223 fn update_node( 1224 &mut self, 1225 node_index: SpatialNodeIndex, 1226 scene_properties: &SceneProperties, 1227 ) { 1228 let parent_index = self.get_spatial_node(node_index).parent; 1229 let parent_info = self.get_snapping_info(parent_index); 1230 1231 let node = &mut self.spatial_nodes[node_index.0 as usize]; 1232 1233 node.snapping_transform = calculate_snapping_transform( 1234 parent_info, 1235 &node.node_type, 1236 ); 1237 1238 node.update( 1239 &self.update_state_stack, 1240 &mut self.coord_systems, 1241 scene_properties, 1242 ); 1243 1244 if !node.children.is_empty() { 1245 let mut child_state = self.update_state_stack.last().unwrap().clone(); 1246 node.prepare_state_for_children(&mut child_state); 1247 self.update_state_stack.push(child_state); 1248 1249 let mut child_indices: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new(); 1250 child_indices.extend_from_slice(&node.children); 1251 1252 for child_index in child_indices { 1253 self.update_node( 1254 child_index, 1255 scene_properties, 1256 ); 1257 } 1258 1259 self.update_state_stack.pop().unwrap(); 1260 } 1261 } 1262 1263 pub fn build_transform_palette(&self, memory: &FrameMemory) -> TransformPalette { 1264 profile_scope!("build_transform_palette"); 1265 TransformPalette::new(self.spatial_nodes.len(), memory) 1266 } 1267 1268 fn print_node<T: PrintTreePrinter>( 1269 &self, 1270 index: SpatialNodeIndex, 1271 pt: &mut T, 1272 ) { 1273 let node = self.get_spatial_node(index); 1274 match node.node_type { 1275 SpatialNodeType::StickyFrame(ref sticky_frame_info) => { 1276 pt.new_level(format!("StickyFrame")); 1277 pt.add_item(format!("sticky info: {:?}", sticky_frame_info)); 1278 } 1279 SpatialNodeType::ScrollFrame(ref scrolling_info) => { 1280 pt.new_level(format!("ScrollFrame")); 1281 pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect)); 1282 pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size)); 1283 pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset())); 1284 pt.add_item(format!("external_scroll_offset: {:?}", scrolling_info.external_scroll_offset)); 1285 pt.add_item(format!("offset generation: {:?}", scrolling_info.offset_generation)); 1286 if scrolling_info.has_scroll_linked_effect == HasScrollLinkedEffect::Yes { 1287 pt.add_item("has scroll-linked effect".to_string()); 1288 } 1289 pt.add_item(format!("kind: {:?}", scrolling_info.frame_kind)); 1290 } 1291 SpatialNodeType::ReferenceFrame(ref info) => { 1292 pt.new_level(format!("ReferenceFrame")); 1293 pt.add_item(format!("kind: {:?}", info.kind)); 1294 pt.add_item(format!("transform_style: {:?}", info.transform_style)); 1295 pt.add_item(format!("source_transform: {:?}", info.source_transform)); 1296 pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame)); 1297 } 1298 } 1299 1300 pt.add_item(format!("index: {:?}", index)); 1301 pt.add_item(format!("content_transform: {:?}", node.content_transform)); 1302 pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform)); 1303 pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform)); 1304 pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id)); 1305 1306 for child_index in &node.children { 1307 self.print_node(*child_index, pt); 1308 } 1309 1310 pt.end_level(); 1311 } 1312 1313 /// Get the visible face of the transfrom from the specified node to its parent. 1314 pub fn get_local_visible_face(&self, node_index: SpatialNodeIndex) -> VisibleFace { 1315 let node = self.get_spatial_node(node_index); 1316 let mut face = VisibleFace::Front; 1317 if let Some(mut parent_index) = node.parent { 1318 // Check if the parent is perspective. In CSS, a stacking context may 1319 // have both perspective and a regular transformation. Gecko translates the 1320 // perspective into a different `nsDisplayPerspective` and `nsDisplayTransform` items. 1321 // On WebRender side, we end up with 2 different reference frames: 1322 // one has kind of "transform", and it's parented to another of "perspective": 1323 // https://searchfox.org/mozilla-central/rev/72c7cef167829b6f1e24cae216fa261934c455fc/layout/generic/nsIFrame.cpp#3716 1324 if let SpatialNodeType::ReferenceFrame(ReferenceFrameInfo { kind: ReferenceFrameKind::Transform { 1325 paired_with_perspective: true, 1326 .. 1327 }, .. }) = node.node_type { 1328 let parent = self.get_spatial_node(parent_index); 1329 match parent.node_type { 1330 SpatialNodeType::ReferenceFrame(ReferenceFrameInfo { 1331 kind: ReferenceFrameKind::Perspective { .. }, 1332 .. 1333 }) => { 1334 parent_index = parent.parent.unwrap(); 1335 } 1336 _ => { 1337 log::error!("Unexpected parent {:?} is not perspective", parent_index); 1338 } 1339 } 1340 } 1341 1342 self.get_relative_transform_with_face(node_index, parent_index, Some(&mut face)); 1343 } 1344 face 1345 } 1346 1347 #[allow(dead_code)] 1348 pub fn print_to_string(&self) -> String { 1349 let mut result = String::new(); 1350 1351 if self.root_reference_frame_index != SpatialNodeIndex::INVALID { 1352 let mut buf = Vec::<u8>::new(); 1353 { 1354 let mut pt = PrintTree::new_with_sink("spatial tree", &mut buf); 1355 self.print_with(&mut pt); 1356 } 1357 result = std::str::from_utf8(&buf).unwrap_or("(Tree printer emitted non-utf8)").to_string(); 1358 } 1359 1360 result 1361 } 1362 1363 #[allow(dead_code)] 1364 pub fn print(&self) { 1365 let result = self.print_to_string(); 1366 // If running in Gecko, set RUST_LOG=webrender::spatial_tree=debug 1367 // to get this logging to be emitted to stderr/logcat. 1368 debug!("{}", result); 1369 } 1370 } 1371 1372 impl PrintableTree for SpatialTree { 1373 fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) { 1374 if self.root_reference_frame_index != SpatialNodeIndex::INVALID { 1375 self.print_node(self.root_reference_frame_index(), pt); 1376 } 1377 } 1378 } 1379 1380 /// Calculate the accumulated external scroll offset for a given spatial node. 1381 pub fn get_external_scroll_offset<S: SpatialNodeContainer>( 1382 spatial_tree: &S, 1383 node_index: SpatialNodeIndex, 1384 ) -> LayoutVector2D { 1385 let mut offset = LayoutVector2D::zero(); 1386 let mut current_node = Some(node_index); 1387 1388 while let Some(node_index) = current_node { 1389 let node_info = spatial_tree.get_node_info(node_index); 1390 1391 match node_info.node_type { 1392 SpatialNodeType::ScrollFrame(ref scrolling) => { 1393 offset += scrolling.external_scroll_offset; 1394 } 1395 SpatialNodeType::StickyFrame(ref sticky) => { 1396 // Remove the sticky offset that was applied in the 1397 // content process, so that primitive interning 1398 // sees stable values, and doesn't invalidate unnecessarily. 1399 offset -= sticky.previously_applied_offset; 1400 } 1401 SpatialNodeType::ReferenceFrame(..) => { 1402 // External scroll offsets are not propagated across 1403 // reference frames. 1404 break; 1405 } 1406 } 1407 1408 current_node = node_info.parent; 1409 } 1410 1411 offset 1412 } 1413 1414 fn calculate_snapping_transform( 1415 parent_scale_offset: Option<ScaleOffset>, 1416 node_type: &SpatialNodeType, 1417 ) -> Option<ScaleOffset> { 1418 // We need to incorporate the parent scale/offset with the child. 1419 // If the parent does not have a scale/offset, then we know we are 1420 // not 2d axis aligned and thus do not need to snap its children 1421 // either. 1422 let parent_scale_offset = match parent_scale_offset { 1423 Some(transform) => transform, 1424 None => return None, 1425 }; 1426 1427 let scale_offset = match node_type { 1428 SpatialNodeType::ReferenceFrame(ref info) => { 1429 let origin_offset = info.origin_in_parent_reference_frame; 1430 1431 match info.source_transform { 1432 PropertyBinding::Value(ref value) => { 1433 // We can only get a ScaleOffset if the transform is 2d axis 1434 // aligned. 1435 match ScaleOffset::from_transform(value) { 1436 Some(scale_offset) => { 1437 scale_offset.then(&ScaleOffset::from_offset(origin_offset.to_untyped())) 1438 } 1439 None => return None, 1440 } 1441 } 1442 1443 // Assume animations start at the identity transform for snapping purposes. 1444 // We still want to incorporate the reference frame offset however. 1445 // TODO(aosmond): Is there a better known starting point? 1446 PropertyBinding::Binding(..) => { 1447 ScaleOffset::from_offset(origin_offset.to_untyped()) 1448 } 1449 } 1450 } 1451 _ => ScaleOffset::identity(), 1452 }; 1453 1454 Some(scale_offset.then(&parent_scale_offset)) 1455 } 1456 1457 #[cfg(test)] 1458 fn add_reference_frame( 1459 cst: &mut SceneSpatialTree, 1460 parent: SpatialNodeIndex, 1461 transform: LayoutTransform, 1462 origin_in_parent_reference_frame: LayoutVector2D, 1463 key: SpatialTreeItemKey, 1464 ) -> SpatialNodeIndex { 1465 let pid = PipelineInstanceId::new(0); 1466 1467 cst.add_reference_frame( 1468 parent, 1469 TransformStyle::Preserve3D, 1470 PropertyBinding::Value(transform), 1471 ReferenceFrameKind::Transform { 1472 is_2d_scale_translation: false, 1473 should_snap: false, 1474 paired_with_perspective: false, 1475 }, 1476 origin_in_parent_reference_frame, 1477 PipelineId::dummy(), 1478 SpatialNodeUid::external(key, PipelineId::dummy(), pid), 1479 ) 1480 } 1481 1482 #[cfg(test)] 1483 fn test_pt( 1484 px: f32, 1485 py: f32, 1486 cst: &SpatialTree, 1487 child: SpatialNodeIndex, 1488 parent: SpatialNodeIndex, 1489 expected_x: f32, 1490 expected_y: f32, 1491 ) { 1492 use euclid::approxeq::ApproxEq; 1493 const EPSILON: f32 = 0.0001; 1494 1495 let p = LayoutPoint::new(px, py); 1496 let m = cst.get_relative_transform(child, parent).into_transform(); 1497 let pt = m.transform_point2d(p).unwrap(); 1498 assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) && 1499 pt.y.approx_eq_eps(&expected_y, &EPSILON), 1500 "p: {:?} -> {:?}\nm={:?}", 1501 p, pt, m, 1502 ); 1503 } 1504 1505 #[test] 1506 fn test_cst_simple_translation() { 1507 // Basic translations only 1508 1509 let mut cst = SceneSpatialTree::new(); 1510 let root_reference_frame_index = cst.root_reference_frame_index(); 1511 1512 let root = add_reference_frame( 1513 &mut cst, 1514 root_reference_frame_index, 1515 LayoutTransform::identity(), 1516 LayoutVector2D::zero(), 1517 SpatialTreeItemKey::new(0, 0), 1518 ); 1519 1520 let child1 = add_reference_frame( 1521 &mut cst, 1522 root, 1523 LayoutTransform::translation(100.0, 0.0, 0.0), 1524 LayoutVector2D::zero(), 1525 SpatialTreeItemKey::new(0, 1), 1526 ); 1527 1528 let child2 = add_reference_frame( 1529 &mut cst, 1530 child1, 1531 LayoutTransform::translation(0.0, 50.0, 0.0), 1532 LayoutVector2D::zero(), 1533 SpatialTreeItemKey::new(0, 2), 1534 ); 1535 1536 let child3 = add_reference_frame( 1537 &mut cst, 1538 child2, 1539 LayoutTransform::translation(200.0, 200.0, 0.0), 1540 LayoutVector2D::zero(), 1541 SpatialTreeItemKey::new(0, 3), 1542 ); 1543 1544 let mut st = SpatialTree::new(); 1545 st.apply_updates(cst.end_frame_and_get_pending_updates()); 1546 st.update_tree(&SceneProperties::new()); 1547 1548 test_pt(100.0, 100.0, &st, child1, root, 200.0, 100.0); 1549 test_pt(100.0, 100.0, &st, child2, root, 200.0, 150.0); 1550 test_pt(100.0, 100.0, &st, child2, child1, 100.0, 150.0); 1551 test_pt(100.0, 100.0, &st, child3, root, 400.0, 350.0); 1552 } 1553 1554 #[test] 1555 fn test_cst_simple_scale() { 1556 // Basic scale only 1557 1558 let mut cst = SceneSpatialTree::new(); 1559 let root_reference_frame_index = cst.root_reference_frame_index(); 1560 1561 let root = add_reference_frame( 1562 &mut cst, 1563 root_reference_frame_index, 1564 LayoutTransform::identity(), 1565 LayoutVector2D::zero(), 1566 SpatialTreeItemKey::new(0, 0), 1567 ); 1568 1569 let child1 = add_reference_frame( 1570 &mut cst, 1571 root, 1572 LayoutTransform::scale(4.0, 1.0, 1.0), 1573 LayoutVector2D::zero(), 1574 SpatialTreeItemKey::new(0, 1), 1575 ); 1576 1577 let child2 = add_reference_frame( 1578 &mut cst, 1579 child1, 1580 LayoutTransform::scale(1.0, 2.0, 1.0), 1581 LayoutVector2D::zero(), 1582 SpatialTreeItemKey::new(0, 2), 1583 ); 1584 1585 let child3 = add_reference_frame( 1586 &mut cst, 1587 child2, 1588 LayoutTransform::scale(2.0, 2.0, 1.0), 1589 LayoutVector2D::zero(), 1590 SpatialTreeItemKey::new(0, 3), 1591 ); 1592 1593 let mut st = SpatialTree::new(); 1594 st.apply_updates(cst.end_frame_and_get_pending_updates()); 1595 st.update_tree(&SceneProperties::new()); 1596 1597 test_pt(100.0, 100.0, &st, child1, root, 400.0, 100.0); 1598 test_pt(100.0, 100.0, &st, child2, root, 400.0, 200.0); 1599 test_pt(100.0, 100.0, &st, child3, root, 800.0, 400.0); 1600 test_pt(100.0, 100.0, &st, child2, child1, 100.0, 200.0); 1601 test_pt(100.0, 100.0, &st, child3, child1, 200.0, 400.0); 1602 } 1603 1604 #[test] 1605 fn test_cst_scale_translation() { 1606 // Scale + translation 1607 1608 let mut cst = SceneSpatialTree::new(); 1609 let root_reference_frame_index = cst.root_reference_frame_index(); 1610 1611 let root = add_reference_frame( 1612 &mut cst, 1613 root_reference_frame_index, 1614 LayoutTransform::identity(), 1615 LayoutVector2D::zero(), 1616 SpatialTreeItemKey::new(0, 0), 1617 ); 1618 1619 let child1 = add_reference_frame( 1620 &mut cst, 1621 root, 1622 LayoutTransform::translation(100.0, 50.0, 0.0), 1623 LayoutVector2D::zero(), 1624 SpatialTreeItemKey::new(0, 1), 1625 ); 1626 1627 let child2 = add_reference_frame( 1628 &mut cst, 1629 child1, 1630 LayoutTransform::scale(2.0, 4.0, 1.0), 1631 LayoutVector2D::zero(), 1632 SpatialTreeItemKey::new(0, 2), 1633 ); 1634 1635 let child3 = add_reference_frame( 1636 &mut cst, 1637 child2, 1638 LayoutTransform::translation(200.0, -100.0, 0.0), 1639 LayoutVector2D::zero(), 1640 SpatialTreeItemKey::new(0, 3), 1641 ); 1642 1643 let child4 = add_reference_frame( 1644 &mut cst, 1645 child3, 1646 LayoutTransform::scale(3.0, 2.0, 1.0), 1647 LayoutVector2D::zero(), 1648 SpatialTreeItemKey::new(0, 4), 1649 ); 1650 1651 let mut st = SpatialTree::new(); 1652 st.apply_updates(cst.end_frame_and_get_pending_updates()); 1653 st.update_tree(&SceneProperties::new()); 1654 1655 test_pt(100.0, 100.0, &st, child1, root, 200.0, 150.0); 1656 test_pt(100.0, 100.0, &st, child2, root, 300.0, 450.0); 1657 test_pt(100.0, 100.0, &st, child4, root, 1100.0, 450.0); 1658 1659 test_pt(0.0, 0.0, &st, child4, child1, 400.0, -400.0); 1660 test_pt(100.0, 100.0, &st, child4, child1, 1000.0, 400.0); 1661 test_pt(100.0, 100.0, &st, child2, child1, 200.0, 400.0); 1662 1663 test_pt(100.0, 100.0, &st, child3, child1, 600.0, 0.0); 1664 } 1665 1666 #[test] 1667 fn test_cst_translation_rotate() { 1668 // Rotation + translation 1669 use euclid::Angle; 1670 1671 let mut cst = SceneSpatialTree::new(); 1672 let root_reference_frame_index = cst.root_reference_frame_index(); 1673 1674 let root = add_reference_frame( 1675 &mut cst, 1676 root_reference_frame_index, 1677 LayoutTransform::identity(), 1678 LayoutVector2D::zero(), 1679 SpatialTreeItemKey::new(0, 0), 1680 ); 1681 1682 let child1 = add_reference_frame( 1683 &mut cst, 1684 root, 1685 LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(-90.0)), 1686 LayoutVector2D::zero(), 1687 SpatialTreeItemKey::new(0, 1), 1688 ); 1689 1690 let mut st = SpatialTree::new(); 1691 st.apply_updates(cst.end_frame_and_get_pending_updates()); 1692 st.update_tree(&SceneProperties::new()); 1693 1694 test_pt(100.0, 0.0, &st, child1, root, 0.0, -100.0); 1695 } 1696 1697 #[test] 1698 fn test_is_ancestor1() { 1699 let mut st = SceneSpatialTree::new(); 1700 let root_reference_frame_index = st.root_reference_frame_index(); 1701 1702 let root = add_reference_frame( 1703 &mut st, 1704 root_reference_frame_index, 1705 LayoutTransform::identity(), 1706 LayoutVector2D::zero(), 1707 SpatialTreeItemKey::new(0, 0), 1708 ); 1709 1710 let child1_0 = add_reference_frame( 1711 &mut st, 1712 root, 1713 LayoutTransform::identity(), 1714 LayoutVector2D::zero(), 1715 SpatialTreeItemKey::new(0, 1), 1716 ); 1717 1718 let child1_1 = add_reference_frame( 1719 &mut st, 1720 child1_0, 1721 LayoutTransform::identity(), 1722 LayoutVector2D::zero(), 1723 SpatialTreeItemKey::new(0, 2), 1724 ); 1725 1726 let child2 = add_reference_frame( 1727 &mut st, 1728 root, 1729 LayoutTransform::identity(), 1730 LayoutVector2D::zero(), 1731 SpatialTreeItemKey::new(0, 3), 1732 ); 1733 1734 assert!(!st.is_ancestor(root, root)); 1735 assert!(!st.is_ancestor(child1_0, child1_0)); 1736 assert!(!st.is_ancestor(child1_1, child1_1)); 1737 assert!(!st.is_ancestor(child2, child2)); 1738 1739 assert!(st.is_ancestor(root, child1_0)); 1740 assert!(st.is_ancestor(root, child1_1)); 1741 assert!(st.is_ancestor(child1_0, child1_1)); 1742 1743 assert!(!st.is_ancestor(child1_0, root)); 1744 assert!(!st.is_ancestor(child1_1, root)); 1745 assert!(!st.is_ancestor(child1_1, child1_0)); 1746 1747 assert!(st.is_ancestor(root, child2)); 1748 assert!(!st.is_ancestor(child2, root)); 1749 1750 assert!(!st.is_ancestor(child1_0, child2)); 1751 assert!(!st.is_ancestor(child1_1, child2)); 1752 assert!(!st.is_ancestor(child2, child1_0)); 1753 assert!(!st.is_ancestor(child2, child1_1)); 1754 } 1755 1756 /// Tests that we select the correct scroll root in the simple case. 1757 #[test] 1758 fn test_find_scroll_root_simple() { 1759 let mut st = SceneSpatialTree::new(); 1760 let pid = PipelineInstanceId::new(0); 1761 1762 let root = st.add_reference_frame( 1763 st.root_reference_frame_index(), 1764 TransformStyle::Flat, 1765 PropertyBinding::Value(LayoutTransform::identity()), 1766 ReferenceFrameKind::Transform { 1767 is_2d_scale_translation: true, 1768 should_snap: true, 1769 paired_with_perspective: false, 1770 }, 1771 LayoutVector2D::new(0.0, 0.0), 1772 PipelineId::dummy(), 1773 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 1774 ); 1775 1776 let scroll = st.add_scroll_frame( 1777 root, 1778 ExternalScrollId(1, PipelineId::dummy()), 1779 PipelineId::dummy(), 1780 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1781 &LayoutSize::new(800.0, 400.0), 1782 ScrollFrameKind::Explicit, 1783 LayoutVector2D::new(0.0, 0.0), 1784 APZScrollGeneration::default(), 1785 HasScrollLinkedEffect::No, 1786 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 1787 ); 1788 1789 assert_eq!(st.find_scroll_root(scroll, true), scroll); 1790 } 1791 1792 /// Tests that we select the root scroll frame rather than the subframe if both are scrollable. 1793 #[test] 1794 fn test_find_scroll_root_sub_scroll_frame() { 1795 let mut st = SceneSpatialTree::new(); 1796 let pid = PipelineInstanceId::new(0); 1797 1798 let root = st.add_reference_frame( 1799 st.root_reference_frame_index(), 1800 TransformStyle::Flat, 1801 PropertyBinding::Value(LayoutTransform::identity()), 1802 ReferenceFrameKind::Transform { 1803 is_2d_scale_translation: true, 1804 should_snap: true, 1805 paired_with_perspective: false, 1806 }, 1807 LayoutVector2D::new(0.0, 0.0), 1808 PipelineId::dummy(), 1809 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 1810 ); 1811 1812 let root_scroll = st.add_scroll_frame( 1813 root, 1814 ExternalScrollId(1, PipelineId::dummy()), 1815 PipelineId::dummy(), 1816 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1817 &LayoutSize::new(800.0, 400.0), 1818 ScrollFrameKind::Explicit, 1819 LayoutVector2D::new(0.0, 0.0), 1820 APZScrollGeneration::default(), 1821 HasScrollLinkedEffect::No, 1822 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 1823 ); 1824 1825 let sub_scroll = st.add_scroll_frame( 1826 root_scroll, 1827 ExternalScrollId(1, PipelineId::dummy()), 1828 PipelineId::dummy(), 1829 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1830 &LayoutSize::new(800.0, 400.0), 1831 ScrollFrameKind::Explicit, 1832 LayoutVector2D::new(0.0, 0.0), 1833 APZScrollGeneration::default(), 1834 HasScrollLinkedEffect::No, 1835 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid), 1836 ); 1837 1838 assert_eq!(st.find_scroll_root(sub_scroll, true), root_scroll); 1839 } 1840 1841 /// Tests that we select the sub scroll frame when the root scroll frame is not scrollable. 1842 #[test] 1843 fn test_find_scroll_root_not_scrollable() { 1844 let mut st = SceneSpatialTree::new(); 1845 let pid = PipelineInstanceId::new(0); 1846 1847 let root = st.add_reference_frame( 1848 st.root_reference_frame_index(), 1849 TransformStyle::Flat, 1850 PropertyBinding::Value(LayoutTransform::identity()), 1851 ReferenceFrameKind::Transform { 1852 is_2d_scale_translation: true, 1853 should_snap: true, 1854 paired_with_perspective: false, 1855 }, 1856 LayoutVector2D::new(0.0, 0.0), 1857 PipelineId::dummy(), 1858 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 1859 ); 1860 1861 let root_scroll = st.add_scroll_frame( 1862 root, 1863 ExternalScrollId(1, PipelineId::dummy()), 1864 PipelineId::dummy(), 1865 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1866 &LayoutSize::new(400.0, 400.0), 1867 ScrollFrameKind::Explicit, 1868 LayoutVector2D::new(0.0, 0.0), 1869 APZScrollGeneration::default(), 1870 HasScrollLinkedEffect::No, 1871 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 1872 ); 1873 1874 let sub_scroll = st.add_scroll_frame( 1875 root_scroll, 1876 ExternalScrollId(1, PipelineId::dummy()), 1877 PipelineId::dummy(), 1878 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1879 &LayoutSize::new(800.0, 400.0), 1880 ScrollFrameKind::Explicit, 1881 LayoutVector2D::new(0.0, 0.0), 1882 APZScrollGeneration::default(), 1883 HasScrollLinkedEffect::No, 1884 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid), 1885 ); 1886 1887 assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll); 1888 } 1889 1890 /// Tests that we select the sub scroll frame when the root scroll frame is too small. 1891 #[test] 1892 fn test_find_scroll_root_too_small() { 1893 let mut st = SceneSpatialTree::new(); 1894 let pid = PipelineInstanceId::new(0); 1895 1896 let root = st.add_reference_frame( 1897 st.root_reference_frame_index(), 1898 TransformStyle::Flat, 1899 PropertyBinding::Value(LayoutTransform::identity()), 1900 ReferenceFrameKind::Transform { 1901 is_2d_scale_translation: true, 1902 should_snap: true, 1903 paired_with_perspective: false, 1904 }, 1905 LayoutVector2D::new(0.0, 0.0), 1906 PipelineId::dummy(), 1907 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 1908 ); 1909 1910 let root_scroll = st.add_scroll_frame( 1911 root, 1912 ExternalScrollId(1, PipelineId::dummy()), 1913 PipelineId::dummy(), 1914 &LayoutRect::from_size(LayoutSize::new(MIN_SCROLL_ROOT_SIZE, MIN_SCROLL_ROOT_SIZE)), 1915 &LayoutSize::new(1000.0, 1000.0), 1916 ScrollFrameKind::Explicit, 1917 LayoutVector2D::new(0.0, 0.0), 1918 APZScrollGeneration::default(), 1919 HasScrollLinkedEffect::No, 1920 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 1921 ); 1922 1923 let sub_scroll = st.add_scroll_frame( 1924 root_scroll, 1925 ExternalScrollId(1, PipelineId::dummy()), 1926 PipelineId::dummy(), 1927 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1928 &LayoutSize::new(800.0, 400.0), 1929 ScrollFrameKind::Explicit, 1930 LayoutVector2D::new(0.0, 0.0), 1931 APZScrollGeneration::default(), 1932 HasScrollLinkedEffect::No, 1933 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid), 1934 ); 1935 1936 assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll); 1937 } 1938 1939 /// Tests that we select the root scroll node, even if it is not scrollable, 1940 /// when encountering a non-axis-aligned transform. 1941 #[test] 1942 fn test_find_scroll_root_perspective() { 1943 let mut st = SceneSpatialTree::new(); 1944 let pid = PipelineInstanceId::new(0); 1945 1946 let root = st.add_reference_frame( 1947 st.root_reference_frame_index(), 1948 TransformStyle::Flat, 1949 PropertyBinding::Value(LayoutTransform::identity()), 1950 ReferenceFrameKind::Transform { 1951 is_2d_scale_translation: true, 1952 should_snap: true, 1953 paired_with_perspective: false, 1954 }, 1955 LayoutVector2D::new(0.0, 0.0), 1956 PipelineId::dummy(), 1957 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 1958 ); 1959 1960 let root_scroll = st.add_scroll_frame( 1961 root, 1962 ExternalScrollId(1, PipelineId::dummy()), 1963 PipelineId::dummy(), 1964 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1965 &LayoutSize::new(400.0, 400.0), 1966 ScrollFrameKind::Explicit, 1967 LayoutVector2D::new(0.0, 0.0), 1968 APZScrollGeneration::default(), 1969 HasScrollLinkedEffect::No, 1970 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 1971 ); 1972 1973 let perspective = st.add_reference_frame( 1974 root_scroll, 1975 TransformStyle::Flat, 1976 PropertyBinding::Value(LayoutTransform::identity()), 1977 ReferenceFrameKind::Perspective { 1978 scrolling_relative_to: None, 1979 }, 1980 LayoutVector2D::new(0.0, 0.0), 1981 PipelineId::dummy(), 1982 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid), 1983 ); 1984 1985 let sub_scroll = st.add_scroll_frame( 1986 perspective, 1987 ExternalScrollId(1, PipelineId::dummy()), 1988 PipelineId::dummy(), 1989 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 1990 &LayoutSize::new(800.0, 400.0), 1991 ScrollFrameKind::Explicit, 1992 LayoutVector2D::new(0.0, 0.0), 1993 APZScrollGeneration::default(), 1994 HasScrollLinkedEffect::No, 1995 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid), 1996 ); 1997 1998 assert_eq!(st.find_scroll_root(sub_scroll, true), root_scroll); 1999 } 2000 2001 /// Tests that encountering a 2D scale or translation transform does not prevent 2002 /// us from selecting the sub scroll frame if the root scroll frame is unscrollable. 2003 #[test] 2004 fn test_find_scroll_root_2d_scale() { 2005 let mut st = SceneSpatialTree::new(); 2006 let pid = PipelineInstanceId::new(0); 2007 2008 let root = st.add_reference_frame( 2009 st.root_reference_frame_index(), 2010 TransformStyle::Flat, 2011 PropertyBinding::Value(LayoutTransform::identity()), 2012 ReferenceFrameKind::Transform { 2013 is_2d_scale_translation: true, 2014 should_snap: true, 2015 paired_with_perspective: false, 2016 }, 2017 LayoutVector2D::new(0.0, 0.0), 2018 PipelineId::dummy(), 2019 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 2020 ); 2021 2022 let root_scroll = st.add_scroll_frame( 2023 root, 2024 ExternalScrollId(1, PipelineId::dummy()), 2025 PipelineId::dummy(), 2026 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 2027 &LayoutSize::new(400.0, 400.0), 2028 ScrollFrameKind::Explicit, 2029 LayoutVector2D::new(0.0, 0.0), 2030 APZScrollGeneration::default(), 2031 HasScrollLinkedEffect::No, 2032 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 2033 ); 2034 2035 let scale = st.add_reference_frame( 2036 root_scroll, 2037 TransformStyle::Flat, 2038 PropertyBinding::Value(LayoutTransform::identity()), 2039 ReferenceFrameKind::Transform { 2040 is_2d_scale_translation: true, 2041 should_snap: false, 2042 paired_with_perspective: false, 2043 }, 2044 LayoutVector2D::new(0.0, 0.0), 2045 PipelineId::dummy(), 2046 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid), 2047 ); 2048 2049 let sub_scroll = st.add_scroll_frame( 2050 scale, 2051 ExternalScrollId(1, PipelineId::dummy()), 2052 PipelineId::dummy(), 2053 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 2054 &LayoutSize::new(800.0, 400.0), 2055 ScrollFrameKind::Explicit, 2056 LayoutVector2D::new(0.0, 0.0), 2057 APZScrollGeneration::default(), 2058 HasScrollLinkedEffect::No, 2059 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid), 2060 ); 2061 2062 assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll); 2063 } 2064 2065 /// Tests that a sticky spatial node is chosen as the scroll root rather than 2066 /// its parent scroll frame 2067 #[test] 2068 fn test_find_scroll_root_sticky() { 2069 let mut st = SceneSpatialTree::new(); 2070 let pid = PipelineInstanceId::new(0); 2071 2072 let root = st.add_reference_frame( 2073 st.root_reference_frame_index(), 2074 TransformStyle::Flat, 2075 PropertyBinding::Value(LayoutTransform::identity()), 2076 ReferenceFrameKind::Transform { 2077 is_2d_scale_translation: true, 2078 should_snap: true, 2079 paired_with_perspective: false, 2080 }, 2081 LayoutVector2D::new(0.0, 0.0), 2082 PipelineId::dummy(), 2083 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid), 2084 ); 2085 2086 let scroll = st.add_scroll_frame( 2087 root, 2088 ExternalScrollId(1, PipelineId::dummy()), 2089 PipelineId::dummy(), 2090 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 2091 &LayoutSize::new(400.0, 800.0), 2092 ScrollFrameKind::Explicit, 2093 LayoutVector2D::new(0.0, 0.0), 2094 APZScrollGeneration::default(), 2095 HasScrollLinkedEffect::No, 2096 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid), 2097 ); 2098 2099 let sticky = st.add_sticky_frame( 2100 scroll, 2101 StickyFrameInfo { 2102 frame_rect: LayoutRect::from_size(LayoutSize::new(400.0, 100.0)), 2103 margins: euclid::SideOffsets2D::new(Some(0.0), None, None, None), 2104 vertical_offset_bounds: api::StickyOffsetBounds::new(0.0, 0.0), 2105 horizontal_offset_bounds: api::StickyOffsetBounds::new(0.0, 0.0), 2106 previously_applied_offset: LayoutVector2D::zero(), 2107 current_offset: LayoutVector2D::zero(), 2108 transform: None 2109 }, 2110 PipelineId::dummy(), 2111 SpatialTreeItemKey::new(0, 2), 2112 pid, 2113 ); 2114 2115 assert_eq!(st.find_scroll_root(sticky, true), sticky); 2116 assert_eq!(st.find_scroll_root(sticky, false), scroll); 2117 } 2118 2119 #[test] 2120 fn test_world_transforms() { 2121 // Create a spatial tree with a scroll frame node with scroll offset (0, 200). 2122 let mut cst = SceneSpatialTree::new(); 2123 let pid = PipelineInstanceId::new(0); 2124 let scroll = cst.add_scroll_frame( 2125 cst.root_reference_frame_index(), 2126 ExternalScrollId(1, PipelineId::dummy()), 2127 PipelineId::dummy(), 2128 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)), 2129 &LayoutSize::new(400.0, 800.0), 2130 ScrollFrameKind::Explicit, 2131 LayoutVector2D::new(0.0, 200.0), 2132 APZScrollGeneration::default(), 2133 HasScrollLinkedEffect::No, 2134 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid)); 2135 2136 let mut st = SpatialTree::new(); 2137 st.apply_updates(cst.end_frame_and_get_pending_updates()); 2138 st.update_tree(&SceneProperties::new()); 2139 2140 // The node's world transform should reflect the scroll offset, 2141 // e.g. here it should be (0, -200) to reflect that the content has been 2142 // scrolled up by 200px. 2143 assert_eq!( 2144 st.get_world_transform(scroll).into_transform(), 2145 LayoutToWorldTransform::translation(0.0, -200.0, 0.0)); 2146 2147 // The node's world viewport transform only reflects enclosing scrolling 2148 // or transforms. Here we don't have any, so it should be the identity. 2149 assert_eq!( 2150 st.get_world_viewport_transform(scroll).into_transform(), 2151 LayoutToWorldTransform::identity()); 2152 } 2153 2154 /// Tests that a spatial node that is async zooming and all of its descendants 2155 /// are correctly marked as having themselves an ancestor that is zooming. 2156 #[test] 2157 fn test_is_ancestor_or_self_zooming() { 2158 let mut cst = SceneSpatialTree::new(); 2159 let root_reference_frame_index = cst.root_reference_frame_index(); 2160 2161 let root = add_reference_frame( 2162 &mut cst, 2163 root_reference_frame_index, 2164 LayoutTransform::identity(), 2165 LayoutVector2D::zero(), 2166 SpatialTreeItemKey::new(0, 0), 2167 ); 2168 let child1 = add_reference_frame( 2169 &mut cst, 2170 root, 2171 LayoutTransform::identity(), 2172 LayoutVector2D::zero(), 2173 SpatialTreeItemKey::new(0, 1), 2174 ); 2175 let child2 = add_reference_frame( 2176 &mut cst, 2177 child1, 2178 LayoutTransform::identity(), 2179 LayoutVector2D::zero(), 2180 SpatialTreeItemKey::new(0, 2), 2181 ); 2182 2183 let mut st = SpatialTree::new(); 2184 st.apply_updates(cst.end_frame_and_get_pending_updates()); 2185 2186 // Mark the root node as async zooming 2187 st.get_spatial_node_mut(root).is_async_zooming = true; 2188 st.update_tree(&SceneProperties::new()); 2189 2190 // Ensure that the root node and all descendants are marked as having 2191 // themselves or an ancestor zooming 2192 assert!(st.get_spatial_node(root).is_ancestor_or_self_zooming); 2193 assert!(st.get_spatial_node(child1).is_ancestor_or_self_zooming); 2194 assert!(st.get_spatial_node(child2).is_ancestor_or_self_zooming); 2195 }