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