diff --git a/gfx/wr/webrender/src/hit_test.rs b/gfx/wr/webrender/src/hit_test.rs index 9328305eee6c..650c25d24fcc 100644 --- a/gfx/wr/webrender/src/hit_test.rs +++ b/gfx/wr/webrender/src/hit_test.rs @@ -10,6 +10,7 @@ use crate::clip::{polygon_contains_point}; use crate::prim_store::PolygonKey; use crate::scene_builder_thread::Interners; use crate::spatial_tree::{SpatialNodeIndex, SpatialTree, get_external_scroll_offset}; +use crate::spatial_node::SpatialNodeType; use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo}; use std::ops; use std::sync::{Arc, Mutex}; @@ -352,12 +353,18 @@ impl HitTester { spatial_tree: &SpatialTree, ) { self.spatial_nodes.clear(); - self.spatial_nodes.reserve(spatial_tree.spatial_node_count()); - spatial_tree.iter_nodes(|index, node| { - // If we haven't already seen a node for this pipeline, record this one as the root - // node. - self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index); + self.pipeline_root_nodes.clear(); + + spatial_tree.visit_nodes(|index, node| { + + // Store root node for this pipeline so we can return pipeline-relative points + if let SpatialNodeType::ReferenceFrame(ref info) = node.node_type { + if info.is_pipeline_root { + let _old = self.pipeline_root_nodes.insert(node.pipeline_id, index); + debug_assert!(_old.is_none()); + } + } //TODO: avoid inverting more than necessary: // - if the coordinate system is non-invertible, no need to try any of these concrete transforms diff --git a/gfx/wr/webrender/src/render_backend.rs b/gfx/wr/webrender/src/render_backend.rs index 77d31ae2b4b6..836280e4da2f 100644 --- a/gfx/wr/webrender/src/render_backend.rs +++ b/gfx/wr/webrender/src/render_backend.rs @@ -417,7 +417,9 @@ impl Document { self.dynamic_properties.add_transforms(property_bindings); } FrameMsg::SetIsTransformAsyncZooming(is_zooming, animation_id) => { - if let Some(node) = self.spatial_tree.get_node_by_anim_id(animation_id) { + if let Some(node_index) = self.spatial_tree.find_spatial_node_by_anim_id(animation_id) { + let node = self.spatial_tree.get_spatial_node_mut(node_index); + if node.is_async_zooming != is_zooming { node.is_async_zooming = is_zooming; self.frame_is_valid = false; diff --git a/gfx/wr/webrender/src/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs index c2b5d1bc0a38..9336516d1c22 100644 --- a/gfx/wr/webrender/src/scene_building.rs +++ b/gfx/wr/webrender/src/scene_building.rs @@ -80,7 +80,6 @@ use crate::scene::{Scene, ScenePipeline, BuiltScene, SceneStats, StackingContext use crate::scene_builder_thread::Interners; use crate::space::SpaceSnapper; use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind, SpatialNodeUid}; -use crate::spatial_tree::SpatialNodeContainer; use crate::tile_cache::TileCacheBuilder; use euclid::approxeq::ApproxEq; use std::{f32, mem, usize}; @@ -853,7 +852,7 @@ impl<'a> SceneBuilder<'a> { transform, info.reference_frame.kind, info.origin.to_vector(), - SpatialNodeUid::external(info.reference_frame.key), + SpatialNodeUid::external(info.reference_frame.key, pipeline_id), ); } @@ -877,7 +876,7 @@ impl<'a> SceneBuilder<'a> { &content_size, ScrollFrameKind::Explicit, info.external_scroll_offset, - SpatialNodeUid::external(info.key), + SpatialNodeUid::external(info.key, pipeline_id), ); } @@ -2045,7 +2044,7 @@ impl<'a> SceneBuilder<'a> { if stacking_context.flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) && self.sc_stack.is_empty() && self.tile_cache_builder.can_add_container_tile_cache() && - self.spatial_tree.get_node_info(stacking_context.spatial_node_index).is_root_coord_system + self.spatial_tree.is_root_coord_system(stacking_context.spatial_node_index) { self.tile_cache_builder.add_tile_cache( stacking_context.prim_list, diff --git a/gfx/wr/webrender/src/spatial_node.rs b/gfx/wr/webrender/src/spatial_node.rs index d80eb29e2e71..225716affcfb 100644 --- a/gfx/wr/webrender/src/spatial_node.rs +++ b/gfx/wr/webrender/src/spatial_node.rs @@ -24,13 +24,9 @@ pub enum SpatialNodeUidKind { /// The root node of the entire spatial tree Root, /// Internal scroll frame created during scene building for each iframe - InternalScrollFrame { - pipeline_id: PipelineId, - }, + InternalScrollFrame, /// Internal reference frame created during scene building for each iframe - InternalReferenceFrame { - pipeline_id: PipelineId, - }, + InternalReferenceFrame, /// A normal spatial node uid, defined by a caller provided unique key External { key: SpatialTreeItemKey, @@ -44,12 +40,15 @@ pub enum SpatialNodeUidKind { pub struct SpatialNodeUid { /// The unique key for a given pipeline for this uid pub kind: SpatialNodeUidKind, + /// Pipeline id to namespace key kinds + pub pipeline_id: PipelineId, } impl SpatialNodeUid { pub fn root() -> Self { SpatialNodeUid { kind: SpatialNodeUidKind::Root, + pipeline_id: PipelineId::dummy(), } } @@ -57,9 +56,8 @@ impl SpatialNodeUid { pipeline_id: PipelineId, ) -> Self { SpatialNodeUid { - kind: SpatialNodeUidKind::InternalScrollFrame { - pipeline_id, - }, + kind: SpatialNodeUidKind::InternalScrollFrame, + pipeline_id, } } @@ -67,24 +65,39 @@ impl SpatialNodeUid { pipeline_id: PipelineId, ) -> Self { SpatialNodeUid { - kind: SpatialNodeUidKind::InternalReferenceFrame { - pipeline_id, - }, + kind: SpatialNodeUidKind::InternalReferenceFrame, + pipeline_id, } } pub fn external( key: SpatialTreeItemKey, + pipeline_id: PipelineId, ) -> Self { SpatialNodeUid { kind: SpatialNodeUidKind::External { key, }, + pipeline_id, } } } -#[derive(Clone)] +/// Defines the content of a spatial node. If the values in the descriptor don't +/// change, that means the rest of the fields in a spatial node will end up with +/// the same result +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SpatialNodeDescriptor { + /// The type of this node and any data associated with that node type. + pub node_type: SpatialNodeType, + + /// Pipeline that this layer belongs to + pub pipeline_id: PipelineId, +} + +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub enum SpatialNodeType { @@ -111,10 +124,6 @@ pub struct SpatialNodeInfo<'a> { /// Parent spatial node. If this is None, we are the root node. pub parent: Option, - /// If true, this spatial node is known to exist in the root coordinate - /// system in all cases (it has no animated or complex transforms) - pub is_root_coord_system: bool, - /// Snapping scale/offset relative to the coordinate system. If None, then /// we should not snap entities bound to this spatial node. pub snapping_transform: Option, @@ -125,9 +134,6 @@ pub struct SpatialNodeInfo<'a> { #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct SceneSpatialNode { - /// Child nodes - children: Vec, - /// Snapping scale/offset relative to the coordinate system. If None, then /// we should not snap entities bound to this spatial node. pub snapping_transform: Option, @@ -135,11 +141,8 @@ pub struct SceneSpatialNode { /// Parent spatial node. If this is None, we are the root node. pub parent: Option, - /// The type of this node and any data associated with that node type. - pub node_type: SpatialNodeType, - - /// Pipeline that this layer belongs to - pipeline_id: PipelineId, + /// Descriptor describing how this spatial node behaves + pub descriptor: SpatialNodeDescriptor, /// If true, this spatial node is known to exist in the root coordinate /// system in all cases (it has no animated or complex transforms) @@ -155,12 +158,14 @@ impl SceneSpatialNode { origin_in_parent_reference_frame: LayoutVector2D, pipeline_id: PipelineId, is_root_coord_system: bool, + is_pipeline_root: bool, ) -> Self { let info = ReferenceFrameInfo { transform_style, source_transform, kind, origin_in_parent_reference_frame, + is_pipeline_root, }; Self::new( pipeline_id, @@ -214,62 +219,6 @@ impl SceneSpatialNode { ) } - pub fn add_child(&mut self, child: SpatialNodeIndex) { - self.children.push(child); - } - - pub fn update_snapping( - &mut self, - parent: Option<&SceneSpatialNode>, - ) { - // Reset in case of an early return. - self.snapping_transform = None; - - // We need to incorporate the parent scale/offset with the child. - // If the parent does not have a scale/offset, then we know we are - // not 2d axis aligned and thus do not need to snap its children - // either. - let parent_scale_offset = match parent { - Some(parent) => { - match parent.snapping_transform { - Some(scale_offset) => scale_offset, - None => return, - } - }, - _ => ScaleOffset::identity(), - }; - - let scale_offset = match self.node_type { - SpatialNodeType::ReferenceFrame(ref info) => { - match info.source_transform { - PropertyBinding::Value(ref value) => { - // We can only get a ScaleOffset if the transform is 2d axis - // aligned. - match ScaleOffset::from_transform(value) { - Some(scale_offset) => { - let origin_offset = info.origin_in_parent_reference_frame; - ScaleOffset::from_offset(origin_offset.to_untyped()) - .accumulate(&scale_offset) - } - None => return, - } - } - - // Assume animations start at the identity transform for snapping purposes. - // We still want to incorporate the reference frame offset however. - // TODO(aosmond): Is there a better known starting point? - PropertyBinding::Binding(..) => { - let origin_offset = info.origin_in_parent_reference_frame; - ScaleOffset::from_offset(origin_offset.to_untyped()) - } - } - } - _ => ScaleOffset::identity(), - }; - - self.snapping_transform = Some(parent_scale_offset.accumulate(&scale_offset)); - } - fn new( pipeline_id: PipelineId, parent_index: Option, @@ -278,11 +227,12 @@ impl SceneSpatialNode { ) -> Self { SceneSpatialNode { parent: parent_index, - children: Vec::new(), - node_type, + descriptor: SpatialNodeDescriptor { + pipeline_id, + node_type, + }, snapping_transform: None, is_root_coord_system, - pipeline_id, } } } @@ -334,32 +284,6 @@ pub struct SpatialNode { /// This is calculated in update(). This will be used to decide whether /// to override corresponding picture's raster space as an optimisation. pub is_ancestor_or_self_zooming: bool, - - /// If true, this spatial node is known to exist in the root coordinate - /// system in all cases (it has no animated or complex transforms) - pub is_root_coord_system: bool, -} - -impl From<&SceneSpatialNode> for SpatialNode { - /// Construct a complete spatial node from the lightweight scene building - /// spatial node representation - fn from(node: &SceneSpatialNode) -> Self { - SpatialNode { - viewport_transform: ScaleOffset::identity(), - content_transform: ScaleOffset::identity(), - snapping_transform: node.snapping_transform, - coordinate_system_id: CoordinateSystemId(0), - transform_kind: TransformedRectKind::AxisAligned, - parent: node.parent, - children: node.children.clone(), - pipeline_id: node.pipeline_id, - node_type: node.node_type.clone(), - invertible: true, - is_async_zooming: false, - is_ancestor_or_self_zooming: false, - is_root_coord_system: node.is_root_coord_system, - } - } } /// Snap an offset to be incorporated into a transform, where the local space @@ -381,6 +305,10 @@ fn snap_offset( } impl SpatialNode { + pub fn add_child(&mut self, child: SpatialNodeIndex) { + self.children.push(child); + } + pub fn set_scroll_origin(&mut self, origin: &LayoutPoint, clamp: ScrollClamping) -> bool { let scrolling = match self.node_type { SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling, @@ -871,7 +799,7 @@ impl SpatialNode { /// Defines whether we have an implicit scroll frame for a pipeline root, /// or an explicitly defined scroll frame from the display list. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub enum ScrollFrameKind { @@ -881,7 +809,7 @@ pub enum ScrollFrameKind { Explicit, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ScrollFrameInfo { @@ -942,7 +870,7 @@ impl ScrollFrameInfo { } /// Contains information about reference frames. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ReferenceFrameInfo { @@ -958,9 +886,13 @@ pub struct ReferenceFrameInfo { /// origin of this reference frame. This is already rolled into the `transform' property, but /// we also store it here to properly transform the viewport for sticky positioning. pub origin_in_parent_reference_frame: LayoutVector2D, + + /// True if this is the root reference frame for a given pipeline. This is only used + /// by the hit-test code, perhaps we can change the interface to not require this. + pub is_pipeline_root: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct StickyFrameInfo { @@ -1020,7 +952,7 @@ fn test_cst_perspective_relative_scroll() { }, LayoutVector2D::zero(), pipeline_id, - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let scroll_frame_1 = cst.add_scroll_frame( @@ -1031,7 +963,7 @@ fn test_cst_perspective_relative_scroll() { &LayoutSize::new(100.0, 500.0), ScrollFrameKind::Explicit, LayoutVector2D::zero(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); let scroll_frame_2 = cst.add_scroll_frame( @@ -1042,7 +974,7 @@ fn test_cst_perspective_relative_scroll() { &LayoutSize::new(100.0, 500.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 50.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy()), ); let ref_frame = cst.add_reference_frame( @@ -1054,7 +986,7 @@ fn test_cst_perspective_relative_scroll() { }, LayoutVector2D::zero(), pipeline_id, - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 4)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 4), PipelineId::dummy()), ); let mut st = SpatialTree::new(); diff --git a/gfx/wr/webrender/src/spatial_tree.rs b/gfx/wr/webrender/src/spatial_tree.rs index 5998939bf071..9a692fce0dcb 100644 --- a/gfx/wr/webrender/src/spatial_tree.rs +++ b/gfx/wr/webrender/src/spatial_tree.rs @@ -7,14 +7,17 @@ use api::{PipelineId, ScrollClamping, SpatialTreeItemKey}; use api::units::*; use euclid::Transform3D; use crate::gpu_types::TransformPalette; -use crate::internal_types::FastHashSet; +use crate::internal_types::{FastHashMap, FastHashSet}; use crate::print_tree::{PrintableTree, PrintTree, PrintTreePrinter}; use crate::scene::SceneProperties; -use crate::spatial_node::{SpatialNode, SpatialNodeType, StickyFrameInfo}; -use crate::spatial_node::{SpatialNodeUid, ScrollFrameKind, SceneSpatialNode, SpatialNodeInfo}; +use crate::spatial_node::{SpatialNode, SpatialNodeType, StickyFrameInfo, SpatialNodeDescriptor}; +use crate::spatial_node::{SpatialNodeUid, ScrollFrameKind, SceneSpatialNode, SpatialNodeInfo, SpatialNodeUidKind}; use std::{ops, u32}; use crate::util::{FastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, scale_factors}; use smallvec::SmallVec; +use std::collections::hash_map::Entry; +use crate::util::TransformedRectKind; + /// An id that identifies coordinate systems in the SpatialTree. Each /// coordinate system has an id and those ids will be shared when the coordinates @@ -107,6 +110,90 @@ pub trait SpatialNodeContainer { fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo; } +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +enum StoreElement { + Empty, + Occupied(T), +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Store { + elements: Vec>, + free_indices: Vec, +} + +impl Store { + fn new() -> Self { + Store { + elements: Vec::new(), + free_indices: Vec::new(), + } + } + + fn insert(&mut self, element: T) -> usize { + match self.free_indices.pop() { + Some(index) => { + match &mut self.elements[index] { + e @ StoreElement::Empty => *e = StoreElement::Occupied(element), + StoreElement::Occupied(..) => panic!("bug: slot already occupied"), + }; + index + } + None => { + let index = self.elements.len(); + self.elements.push(StoreElement::Occupied(element)); + index + } + } + } + + fn set(&mut self, index: usize, element: T) { + match &mut self.elements[index] { + StoreElement::Empty => panic!("bug: set on empty element!"), + StoreElement::Occupied(ref mut entry) => *entry = element, + } + } + + fn free(&mut self, index: usize) -> T { + self.free_indices.push(index); + + let value = std::mem::replace(&mut self.elements[index], StoreElement::Empty); + + match value { + StoreElement::Occupied(value) => value, + StoreElement::Empty => panic!("bug: freeing an empty slot"), + } + } +} + +impl ops::Index for Store { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + match self.elements[index] { + StoreElement::Occupied(ref e) => e, + StoreElement::Empty => panic!("bug: indexing an empty element!"), + } + } +} + +impl ops::IndexMut for Store { + fn index_mut(&mut self, index: usize) -> &mut T { + match self.elements[index] { + StoreElement::Occupied(ref mut e) => e, + StoreElement::Empty => panic!("bug: indexing an empty element!"), + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct SpatialNodeEntry { + index: usize, + last_used: u64, +} + /// The representation of the spatial tree during scene building, which is /// mostly write-only, with a small number of queries for snapping, /// picture cache building @@ -115,13 +202,21 @@ pub trait SpatialNodeContainer { pub struct SceneSpatialTree { /// Nodes which determine the positions (offsets and transforms) for primitives /// and clips. - spatial_nodes: Vec, + spatial_nodes: Store, /// A set of the uids we've encountered for spatial nodes, used to assert that /// we're not seeing duplicates. Likely to be removed once we rely on this feature. - spatial_node_uids: FastHashSet, + spatial_node_map: FastHashMap, root_reference_frame_index: SpatialNodeIndex, + + frame_counter: u64, + updates: SpatialTreeUpdates, + + /// A debug check that the caller never adds a spatial node with duplicate + /// uid, since that can cause badness if it occurs (e.g. a malformed spatial + /// tree and infinite loops in is_ancestor etc) + spatial_nodes_set: FastHashSet, } impl SpatialNodeContainer for SceneSpatialTree { @@ -130,8 +225,7 @@ impl SpatialNodeContainer for SceneSpatialTree { SpatialNodeInfo { parent: node.parent, - node_type: &node.node_type, - is_root_coord_system: node.is_root_coord_system, + node_type: &node.descriptor.node_type, snapping_transform: node.snapping_transform, } } @@ -140,21 +234,14 @@ impl SpatialNodeContainer for SceneSpatialTree { impl SceneSpatialTree { pub fn new() -> Self { let mut tree = SceneSpatialTree { - spatial_nodes: Vec::new(), - spatial_node_uids: FastHashSet::default(), + spatial_nodes: Store::new(), + spatial_node_map: FastHashMap::default(), root_reference_frame_index: SpatialNodeIndex(0), + frame_counter: 0, + updates: SpatialTreeUpdates::new(), + spatial_nodes_set: FastHashSet::default(), }; - tree.reset(); - - tree - } - - /// Reset the retained scene spatial tree, ready to build a new scene - fn reset(&mut self) { - self.spatial_nodes.clear(); - self.spatial_node_uids.clear(); - let node = SceneSpatialNode::new_reference_frame( None, TransformStyle::Flat, @@ -166,26 +253,46 @@ impl SceneSpatialTree { LayoutVector2D::zero(), PipelineId::dummy(), true, + true, ); - self.add_spatial_node(node, SpatialNodeUid::root()); + tree.add_spatial_node(node, SpatialNodeUid::root()); + + tree + } + + pub fn is_root_coord_system(&self, index: SpatialNodeIndex) -> bool { + self.spatial_nodes[index.0 as usize].is_root_coord_system } /// Complete building this scene, return the updates to apply to the frame spatial tree pub fn end_frame_and_get_pending_updates(&mut self) -> SpatialTreeUpdates { - let spatial_nodes = self.spatial_nodes - .iter() - .map(|node| { - SpatialNode::from(node) - }) - .collect(); + self.updates.root_reference_frame_index = self.root_reference_frame_index; + self.spatial_nodes_set.clear(); - self.reset(); + let now = self.frame_counter; + let spatial_nodes = &mut self.spatial_nodes; + let updates = &mut self.updates; - SpatialTreeUpdates { - root_reference_frame_index: self.root_reference_frame_index, - spatial_nodes, - } + self.spatial_node_map.get_mut(&SpatialNodeUid::root()).unwrap().last_used = now; + + self.spatial_node_map.retain(|_, entry| { + if entry.last_used + 10 < now { + spatial_nodes.free(entry.index); + updates.updates.push(SpatialTreeUpdate::Remove { + index: entry.index, + }); + return false; + } + + true + }); + + let updates = std::mem::replace(&mut self.updates, SpatialTreeUpdates::new()); + + self.frame_counter += 1; + + updates } /// Check if a given spatial node is an ancestor of another spatial node. @@ -304,24 +411,63 @@ impl SceneSpatialTree { mut node: SceneSpatialNode, uid: SpatialNodeUid, ) -> SpatialNodeIndex { - let index = SpatialNodeIndex::new(self.spatial_nodes.len()); + let parent_snapping_transform = match node.parent { + Some(parent_index) => { + self.get_node_info(parent_index).snapping_transform + } + None => { + Some(ScaleOffset::identity()) + } + }; - // TODO(gw): For initial testing, just debug assert that the caller never provides - // a duplicate uid. In future, we'll start to build on this infrastructure - // to retain and cache information across display lists. - debug_assert!(self.spatial_node_uids.insert(uid)); + node.snapping_transform = calculate_snapping_transform( + parent_snapping_transform, + &node.descriptor.node_type, + ); - // When the parent node is None this means we are adding the root. - if let Some(parent_index) = node.parent { - let parent_node = &mut self.spatial_nodes[parent_index.0 as usize]; - parent_node.add_child(index); - node.update_snapping(Some(parent_node)); - } else { - node.update_snapping(None); - } + // Ensure a node with the same uid hasn't been added during this scene build + assert!(self.spatial_nodes_set.insert(uid)); - self.spatial_nodes.push(node); - index + let index = match self.spatial_node_map.entry(uid) { + Entry::Occupied(mut e) => { + let e = e.get_mut(); + e.last_used = self.frame_counter; + + let existing_node = &self.spatial_nodes[e.index]; + + if existing_node.descriptor != node.descriptor || existing_node.parent != node.parent { + self.updates.updates.push(SpatialTreeUpdate::Update { + index: e.index, + parent: node.parent, + descriptor: node.descriptor.clone(), + }); + self.spatial_nodes.set(e.index, node); + } + + e.index + } + Entry::Vacant(e) => { + let descriptor = node.descriptor.clone(); + let parent = node.parent; + + let index = self.spatial_nodes.insert(node); + + e.insert(SpatialNodeEntry { + index, + last_used: self.frame_counter, + }); + + self.updates.updates.push(SpatialTreeUpdate::Insert { + index, + descriptor, + parent, + }); + + index + } + }; + + SpatialNodeIndex(index as u32) } pub fn add_reference_frame( @@ -356,7 +502,12 @@ impl SceneSpatialTree { } }; - let is_root_coord_system = self.get_node_info(parent_index).is_root_coord_system && !new_static_coord_system; + let is_root_coord_system = !new_static_coord_system && + self.spatial_nodes[parent_index.0 as usize].is_root_coord_system; + let is_pipeline_root = match uid.kind { + SpatialNodeUidKind::InternalReferenceFrame { .. } => true, + _ => false, + }; let node = SceneSpatialNode::new_reference_frame( Some(parent_index), @@ -366,6 +517,7 @@ impl SceneSpatialTree { origin_in_parent_reference_frame, pipeline_id, is_root_coord_system, + is_pipeline_root, ); self.add_spatial_node(node, uid) } @@ -382,7 +534,7 @@ impl SceneSpatialTree { uid: SpatialNodeUid, ) -> SpatialNodeIndex { // Scroll frames are only 2d translations - they can't introduce a new static coord system - let is_root_coord_system = self.get_node_info(parent_index).is_root_coord_system; + let is_root_coord_system = self.spatial_nodes[parent_index.0 as usize].is_root_coord_system; let node = SceneSpatialNode::new_scroll_frame( pipeline_id, @@ -405,8 +557,8 @@ impl SceneSpatialTree { key: SpatialTreeItemKey, ) -> SpatialNodeIndex { // Sticky frames are only 2d translations - they can't introduce a new static coord system - let is_root_coord_system = self.get_node_info(parent_index).is_root_coord_system; - let uid = SpatialNodeUid::external(key); + let is_root_coord_system = self.spatial_nodes[parent_index.0 as usize].is_root_coord_system; + let uid = SpatialNodeUid::external(key, pipeline_id); let node = SceneSpatialNode::new_sticky_frame( parent_index, @@ -418,14 +570,43 @@ impl SceneSpatialTree { } } +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum SpatialTreeUpdate { + Insert { + index: usize, + parent: Option, + descriptor: SpatialNodeDescriptor, + }, + Update { + index: usize, + parent: Option, + descriptor: SpatialNodeDescriptor, + }, + Remove { + index: usize, + }, +} + /// The delta updates to apply after building a new scene to the retained frame building /// tree. // TODO(gw): During the initial scaffolding work, this is the exact same as previous // behavior - that is, a complete list of new spatial nodes. In future, this // will instead be a list of deltas to apply to the frame spatial tree. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct SpatialTreeUpdates { root_reference_frame_index: SpatialNodeIndex, - spatial_nodes: Vec, + updates: Vec, +} + +impl SpatialTreeUpdates { + fn new() -> Self { + SpatialTreeUpdates { + root_reference_frame_index: SpatialNodeIndex::INVALID, + updates: Vec::new(), + } + } } /// Represents the spatial tree during frame building, which is mostly @@ -554,12 +735,11 @@ enum TransformScroll { impl SpatialNodeContainer for SpatialTree { fn get_node_info(&self, index: SpatialNodeIndex) -> SpatialNodeInfo { - let node = &self.spatial_nodes[index.0 as usize]; + let node = self.get_spatial_node(index); SpatialNodeInfo { parent: node.parent, node_type: &node.node_type, - is_root_coord_system: node.is_root_coord_system, snapping_transform: node.snapping_transform, } } @@ -575,43 +755,172 @@ impl SpatialTree { } } + fn visit_node_impl_mut( + &mut self, + index: SpatialNodeIndex, + f: &mut F, + ) where F: FnMut(SpatialNodeIndex, &mut SpatialNode) { + let mut child_indices: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new(); + + let node = self.get_spatial_node_mut(index); + f(index, node); + child_indices.extend_from_slice(&node.children); + + for child_index in child_indices { + self.visit_node_impl_mut(child_index, f); + } + } + + fn visit_node_impl( + &self, + index: SpatialNodeIndex, + f: &mut F, + ) where F: FnMut(SpatialNodeIndex, &SpatialNode) { + let node = self.get_spatial_node(index); + + f(index, node); + + for child_index in &node.children { + self.visit_node_impl(*child_index, f); + } + } + + /// Visit all nodes from the root of the tree, invoking a closure on each one + pub fn visit_nodes(&self, mut f: F) where F: FnMut(SpatialNodeIndex, &SpatialNode) { + if self.root_reference_frame_index == SpatialNodeIndex::INVALID { + return; + } + + self.visit_node_impl(self.root_reference_frame_index, &mut f); + } + + /// Visit all nodes from the root of the tree, invoking a closure on each one + pub fn visit_nodes_mut(&mut self, mut f: F) where F: FnMut(SpatialNodeIndex, &mut SpatialNode) { + if self.root_reference_frame_index == SpatialNodeIndex::INVALID { + return; + } + + self.visit_node_impl_mut(self.root_reference_frame_index, &mut f); + } + /// Apply updates from a new scene to the frame spatial tree pub fn apply_updates( &mut self, updates: SpatialTreeUpdates, ) { self.root_reference_frame_index = updates.root_reference_frame_index; - self.spatial_nodes = updates.spatial_nodes; + + for update in updates.updates { + match update { + SpatialTreeUpdate::Insert { index, parent, descriptor } => { + if let Some(parent) = parent { + self.get_spatial_node_mut(parent).add_child(SpatialNodeIndex(index as u32)); + } + + let node = SpatialNode { + viewport_transform: ScaleOffset::identity(), + content_transform: ScaleOffset::identity(), + snapping_transform: None, + coordinate_system_id: CoordinateSystemId(0), + transform_kind: TransformedRectKind::AxisAligned, + parent, + children: Vec::new(), + pipeline_id: descriptor.pipeline_id, + node_type: descriptor.node_type, + invertible: true, + is_async_zooming: false, + is_ancestor_or_self_zooming: false, + }; + + assert!(index <= self.spatial_nodes.len()); + if index < self.spatial_nodes.len() { + self.spatial_nodes[index] = node; + } else { + self.spatial_nodes.push(node); + } + } + SpatialTreeUpdate::Update { index, descriptor, parent } => { + let current_parent = self.spatial_nodes[index].parent; + + if current_parent != parent { + if let Some(current_parent) = current_parent { + let i = self.spatial_nodes[current_parent.0 as usize] + .children + .iter() + .position(|e| e.0 as usize == index) + .expect("bug: not found!"); + self.spatial_nodes[current_parent.0 as usize].children.remove(i); + } + + let new_parent = parent.expect("todo: is this valid?"); + self.spatial_nodes[new_parent.0 as usize].add_child(SpatialNodeIndex(index as u32)); + } + + let node = &mut self.spatial_nodes[index]; + + node.node_type = descriptor.node_type; + node.pipeline_id = descriptor.pipeline_id; + node.parent = parent; + } + SpatialTreeUpdate::Remove { index, .. } => { + let node = &mut self.spatial_nodes[index]; + + // Set the pipeline id to be invalid, so that even though this array + // entry still exists we can easily see it's invalid when debugging. + node.pipeline_id = PipelineId::dummy(); + + if let Some(parent) = node.parent { + let i = self.spatial_nodes[parent.0 as usize] + .children + .iter() + .position(|e| e.0 as usize == index) + .expect("bug: not found!"); + self.spatial_nodes[parent.0 as usize].children.remove(i); + } + } + } + } + + self.visit_nodes_mut(|_, node| { + match node.node_type { + SpatialNodeType::ScrollFrame(ref mut info) => { + info.offset = -info.external_scroll_offset; + } + SpatialNodeType::StickyFrame(ref mut info) => { + info.current_offset = LayoutVector2D::zero(); + } + SpatialNodeType::ReferenceFrame(..) => {} + } + }); } pub fn get_spatial_node(&self, index: SpatialNodeIndex) -> &SpatialNode { &self.spatial_nodes[index.0 as usize] } + pub fn get_spatial_node_mut(&mut self, index: SpatialNodeIndex) -> &mut SpatialNode { + &mut self.spatial_nodes[index.0 as usize] + } + /// Get total number of spatial nodes pub fn spatial_node_count(&self) -> usize { self.spatial_nodes.len() } - /// Iterate all nodes and invoke a closure on each node - pub fn iter_nodes(&self, mut f: F) where F: FnMut(SpatialNodeIndex, &SpatialNode) { - for (index, node) in self.spatial_nodes.iter().enumerate() { - f(SpatialNodeIndex(index as u32), node); - } - } - - /// Get a node, if it exists, that matches a given animation binding id - pub fn get_node_by_anim_id( - &mut self, + pub fn find_spatial_node_by_anim_id( + &self, id: PropertyBindingId, - ) -> Option<&mut SpatialNode> { - for node in &mut self.spatial_nodes { - if node.is_transform_bound_to_property(id) { - return Some(node); - } - } + ) -> Option { + let mut node_index = None; - None + self.visit_nodes(|index, node| { + if node.is_transform_bound_to_property(id) { + debug_assert!(node_index.is_none()); // Multiple nodes with same anim id + node_index = Some(index); + } + }); + + node_index } /// Calculate the relative transform from `child_index` to `parent_index`. @@ -764,20 +1073,22 @@ impl SpatialTree { id: ExternalScrollId, clamp: ScrollClamping ) -> bool { - for node in &mut self.spatial_nodes { - if node.matches_external_id(id) { - return node.set_scroll_origin(&origin, clamp); - } - } + let mut did_scroll_node = false; - false + self.visit_nodes_mut(|_, node| { + if node.matches_external_id(id) { + did_scroll_node |= node.set_scroll_origin(&origin, clamp); + } + }); + + did_scroll_node } pub fn update_tree( &mut self, scene_properties: &SceneProperties, ) { - if self.spatial_nodes.is_empty() { + if self.root_reference_frame_index == SpatialNodeIndex::INVALID { return; } @@ -816,8 +1127,22 @@ impl SpatialTree { node_index: SpatialNodeIndex, scene_properties: &SceneProperties, ) { + let parent_snapping_transform = match self.get_spatial_node(node_index).parent { + Some(parent_index) => { + self.get_node_info(parent_index).snapping_transform + } + None => { + Some(ScaleOffset::identity()) + } + }; + let node = &mut self.spatial_nodes[node_index.0 as usize]; + node.snapping_transform = calculate_snapping_transform( + parent_snapping_transform, + &node.node_type, + ); + node.update( &self.update_state_stack, &mut self.coord_systems, @@ -901,7 +1226,7 @@ impl SpatialTree { #[allow(dead_code)] pub fn print(&self) { - if !self.spatial_nodes.is_empty() { + if self.root_reference_frame_index != SpatialNodeIndex::INVALID { let mut buf = Vec::::new(); { let mut pt = PrintTree::new_with_sink("spatial tree", &mut buf); @@ -916,7 +1241,7 @@ impl SpatialTree { impl PrintableTree for SpatialTree { fn print_with(&self, pt: &mut T) { - if !self.spatial_nodes.is_empty() { + if self.root_reference_frame_index != SpatialNodeIndex::INVALID { self.print_node(self.root_reference_frame_index(), pt); } } @@ -953,6 +1278,50 @@ pub fn get_external_scroll_offset( offset } +fn calculate_snapping_transform( + parent_snapping_transform: Option, + node_type: &SpatialNodeType, +) -> Option { + // We need to incorporate the parent scale/offset with the child. + // If the parent does not have a scale/offset, then we know we are + // not 2d axis aligned and thus do not need to snap its children + // either. + let parent_scale_offset = match parent_snapping_transform { + Some(parent_snapping_transform) => parent_snapping_transform, + None => return None, + }; + + let scale_offset = match node_type { + SpatialNodeType::ReferenceFrame(ref info) => { + match info.source_transform { + PropertyBinding::Value(ref value) => { + // We can only get a ScaleOffset if the transform is 2d axis + // aligned. + match ScaleOffset::from_transform(value) { + Some(scale_offset) => { + let origin_offset = info.origin_in_parent_reference_frame; + ScaleOffset::from_offset(origin_offset.to_untyped()) + .accumulate(&scale_offset) + } + None => return None, + } + } + + // Assume animations start at the identity transform for snapping purposes. + // We still want to incorporate the reference frame offset however. + // TODO(aosmond): Is there a better known starting point? + PropertyBinding::Binding(..) => { + let origin_offset = info.origin_in_parent_reference_frame; + ScaleOffset::from_offset(origin_offset.to_untyped()) + } + } + } + _ => ScaleOffset::identity(), + }; + + Some(parent_scale_offset.accumulate(&scale_offset)) +} + #[cfg(test)] fn add_reference_frame( cst: &mut SceneSpatialTree, @@ -971,7 +1340,7 @@ fn add_reference_frame( }, origin_in_parent_reference_frame, PipelineId::dummy(), - SpatialNodeUid::external(key), + SpatialNodeUid::external(key, PipelineId::dummy()), ) } @@ -1264,7 +1633,7 @@ fn test_find_scroll_root_simple() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let scroll = st.add_scroll_frame( @@ -1275,7 +1644,7 @@ fn test_find_scroll_root_simple() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); assert_eq!(st.find_scroll_root(scroll), scroll); @@ -1296,7 +1665,7 @@ fn test_find_scroll_root_sub_scroll_frame() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let root_scroll = st.add_scroll_frame( @@ -1307,7 +1676,7 @@ fn test_find_scroll_root_sub_scroll_frame() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); let sub_scroll = st.add_scroll_frame( @@ -1318,7 +1687,7 @@ fn test_find_scroll_root_sub_scroll_frame() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy()), ); assert_eq!(st.find_scroll_root(sub_scroll), root_scroll); @@ -1339,7 +1708,7 @@ fn test_find_scroll_root_not_scrollable() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let root_scroll = st.add_scroll_frame( @@ -1350,7 +1719,7 @@ fn test_find_scroll_root_not_scrollable() { &LayoutSize::new(400.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); let sub_scroll = st.add_scroll_frame( @@ -1361,7 +1730,7 @@ fn test_find_scroll_root_not_scrollable() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy()), ); assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll); @@ -1382,7 +1751,7 @@ fn test_find_scroll_root_too_small() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let root_scroll = st.add_scroll_frame( @@ -1393,7 +1762,7 @@ fn test_find_scroll_root_too_small() { &LayoutSize::new(1000.0, 1000.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); let sub_scroll = st.add_scroll_frame( @@ -1404,7 +1773,7 @@ fn test_find_scroll_root_too_small() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy()), ); assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll); @@ -1426,7 +1795,7 @@ fn test_find_scroll_root_perspective() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let root_scroll = st.add_scroll_frame( @@ -1437,7 +1806,7 @@ fn test_find_scroll_root_perspective() { &LayoutSize::new(400.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); let perspective = st.add_reference_frame( @@ -1449,7 +1818,7 @@ fn test_find_scroll_root_perspective() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy()), ); let sub_scroll = st.add_scroll_frame( @@ -1460,7 +1829,7 @@ fn test_find_scroll_root_perspective() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy()), ); assert_eq!(st.find_scroll_root(sub_scroll), root_scroll); @@ -1482,7 +1851,7 @@ fn test_find_scroll_root_2d_scale() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy()), ); let root_scroll = st.add_scroll_frame( @@ -1493,7 +1862,7 @@ fn test_find_scroll_root_2d_scale() { &LayoutSize::new(400.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy()), ); let scale = st.add_reference_frame( @@ -1506,7 +1875,7 @@ fn test_find_scroll_root_2d_scale() { }, LayoutVector2D::new(0.0, 0.0), PipelineId::dummy(), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy()), ); let sub_scroll = st.add_scroll_frame( @@ -1517,7 +1886,7 @@ fn test_find_scroll_root_2d_scale() { &LayoutSize::new(800.0, 400.0), ScrollFrameKind::Explicit, LayoutVector2D::new(0.0, 0.0), - SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3)), + SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy()), ); assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll); diff --git a/gfx/wr/webrender_api/src/lib.rs b/gfx/wr/webrender_api/src/lib.rs index 5d2370afe391..583d201a290b 100644 --- a/gfx/wr/webrender_api/src/lib.rs +++ b/gfx/wr/webrender_api/src/lib.rs @@ -188,7 +188,7 @@ impl ExternalEvent { } /// Describe whether or not scrolling should be clamped by the content bounds. -#[derive(Clone, Deserialize, Serialize)] +#[derive(Copy, Clone, Deserialize, Serialize)] pub enum ScrollClamping { /// ToContentBounds,