| author | Dzmitry Malyshau <dmalyshau@mozilla.com> |
| Tue, 12 Feb 2019 15:05:01 +0000 | |
| changeset 458740 | 87dac4d84c6b78511849fe117c327620980b4ee2 |
| parent 458739 | 9a69e1fea9be909ba3ebcb2f326a7ed18a0d9003 |
| child 458741 | 9571930df7485f8c176c27e508f4c5d91fff8412 |
| push id | 35548 |
| push user | opoprus@mozilla.com |
| push date | Wed, 13 Feb 2019 09:48:26 +0000 |
| treeherder | mozilla-central@93e37c529818 [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | gw |
| bugs | 1524797 |
| milestone | 67.0a1 |
| first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/gfx/wr/webrender/src/clip.rs +++ b/gfx/wr/webrender/src/clip.rs @@ -1301,17 +1301,19 @@ fn add_clip_node_to_current_chain( .accumulate(&clip_spatial_node.coordinate_system_relative_scale_offset); ClipSpaceConversion::ScaleOffset(scale_offset) } else { match clip_scroll_tree.get_relative_transform( node.spatial_node_index, ROOT_SPATIAL_NODE_INDEX, ) { None => return true, - Some(xf) => ClipSpaceConversion::Transform(xf.with_destination::<WorldPixel>()), + Some(relative) => ClipSpaceConversion::Transform( + relative.flattened.with_destination::<WorldPixel>(), + ), } }; // If we can convert spaces, try to reduce the size of the region // requested, and cache the conversion information for the next step. if let Some(clip_rect) = clip_node.item.get_local_clip_rect(node.local_pos) { match conversion { ClipSpaceConversion::Local => {
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs +++ b/gfx/wr/webrender/src/clip_scroll_tree.rs @@ -4,19 +4,19 @@ use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D, ReferenceFrameKind}; use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation, ScrollSensitivity}; use api::{LayoutSize, LayoutTransform, PropertyBinding, TransformStyle, WorldPoint}; use gpu_types::TransformPalette; use internal_types::{FastHashMap, FastHashSet}; use print_tree::{PrintableTree, PrintTree, PrintTreePrinter}; use scene::SceneProperties; -use smallvec::SmallVec; use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind}; -use util::{LayoutToWorldFastTransform, ScaleOffset}; +use std::ops; +use util::{LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset}; pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>; /// An id that identifies coordinate systems in the ClipScrollTree. Each /// coordinate system has an id and those ids will be shared when the coordinates /// system are the same or are in the same axis-aligned space. This allows /// for optimizing mask generation. #[derive(Debug, Copy, Clone, PartialEq)] @@ -24,23 +24,27 @@ pub type ScrollStates = FastHashMap<Exte #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct CoordinateSystemId(pub u32); /// A node in the hierarchy of coordinate system /// transforms. #[derive(Debug)] pub struct CoordinateSystem { pub transform: LayoutTransform, + /// True if the Z component of the resulting transform, when ascending + /// from children to a parent, needs to be flattened upon passing this system. + pub is_flatten_root: bool, pub parent: Option<CoordinateSystemId>, } impl CoordinateSystem { fn root() -> Self { CoordinateSystem { transform: LayoutTransform::identity(), + is_flatten_root: true, parent: None, } } } #[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] @@ -58,16 +62,38 @@ impl SpatialNodeIndex { } impl CoordinateSystemId { pub fn root() -> Self { CoordinateSystemId(0) } } +#[derive(Debug, Copy, Clone)] +pub enum VisibleFace { + Front, + Back, +} + +impl Default for VisibleFace { + fn default() -> Self { + VisibleFace::Front + } +} + +impl ops::Not for VisibleFace { + type Output = Self; + fn not(self) -> Self { + match self { + VisibleFace::Front => VisibleFace::Back, + VisibleFace::Back => VisibleFace::Front, + } + } +} + pub struct ClipScrollTree { /// Nodes which determine the positions (offsets and transforms) for primitives /// and clips. pub spatial_nodes: Vec<SpatialNode>, /// A list of transforms that establish new coordinate systems. /// Spatial nodes only establish a new coordinate system when /// they have a transform that is not a simple 2d translation. @@ -98,76 +124,96 @@ pub struct TransformUpdateState { /// Scale and offset from the coordinate system that started this compatible coordinate system. pub coordinate_system_relative_scale_offset: ScaleOffset, /// True if this node is transformed by an invertible transform. If not, display items /// transformed by this node will not be displayed and display items not transformed by this /// node will not be clipped by clips that are transformed by this node. pub invertible: bool, + + /// True if this node is a part of Preserve3D hierarchy. + pub preserves_3d: bool, +} + +/// A processed relative transform between two nodes in the clip-scroll tree. +#[derive(Debug, Default)] +pub struct RelativeTransform { + /// The flattened transform, produces Z = 0 at all times. + pub flattened: LayoutTransform, + /// Visible face of the original transform. + pub visible_face: VisibleFace, + /// True if the original transform had perspective. + pub is_perspective: bool, } impl ClipScrollTree { pub fn new() -> Self { ClipScrollTree { spatial_nodes: Vec::new(), coord_systems: Vec::new(), pending_scroll_offsets: FastHashMap::default(), pipelines_to_discard: FastHashSet::default(), nodes_to_update: Vec::new(), } } - /// Calculate the relative transform from `from_node_index` - /// to `to_node_index`. It's assumed that `from_node_index` - /// is an ancestor or a descendant of `to_node_index`. This method will - /// panic if that invariant isn't true! + /// Calculate the relative transform from `child_index` to `parent_index`. + /// This method will panic if the nodes are not connected! pub fn get_relative_transform( &self, - from_node_index: SpatialNodeIndex, - to_node_index: SpatialNodeIndex, - ) -> Option<LayoutTransform> { - let from_node = &self.spatial_nodes[from_node_index.0 as usize]; - let to_node = &self.spatial_nodes[to_node_index.0 as usize]; - - let (child, parent, inverse) = if from_node_index.0 > to_node_index.0 { - (from_node, to_node, false) - } else { - (to_node, from_node, true) - }; + child_index: SpatialNodeIndex, + parent_index: SpatialNodeIndex, + ) -> Option<RelativeTransform> { + assert!(child_index.0 >= parent_index.0); + let child = &self.spatial_nodes[child_index.0 as usize]; + let parent = &self.spatial_nodes[parent_index.0 as usize]; let mut coordinate_system_id = child.coordinate_system_id; - let mut nodes: SmallVec<[_; 16]> = SmallVec::new(); + let mut transform = child.coordinate_system_relative_scale_offset.to_transform(); + let mut visible_face = VisibleFace::Front; + let mut is_perspective = false; while coordinate_system_id != parent.coordinate_system_id { - nodes.push(coordinate_system_id); let coord_system = &self.coord_systems[coordinate_system_id.0 as usize]; coordinate_system_id = coord_system.parent.expect("invalid parent!"); + transform = transform.post_mul(&coord_system.transform); + // we need to update the associated parameters of a transform in two cases: + // 1) when the flattening happens, so that we don't lose that original 3D aspects + // 2) when we reach the end of iteration, so that our result is up to date + if coord_system.is_flatten_root || coordinate_system_id == parent.coordinate_system_id { + visible_face = if transform.is_backface_visible() { + VisibleFace::Back + } else { + VisibleFace::Front + }; + is_perspective = transform.has_perspective_component(); + } + if coord_system.is_flatten_root { + //Note: this function makes the transform to ignore the Z coordinate of inputs + // *even* for computing the X and Y coordinates of the output. + //transform = transform.project_to_2d(); + transform.m13 = 0.0; + transform.m23 = 0.0; + transform.m33 = 0.0; + transform.m43 = 0.0; + } } - nodes.reverse(); - - let mut transform = parent.coordinate_system_relative_scale_offset - .inverse() - .to_transform(); - - for node in nodes { - let coord_system = &self.coord_systems[node.0 as usize]; - transform = transform.pre_mul(&coord_system.transform); - } - - let transform = transform.pre_mul( - &child.coordinate_system_relative_scale_offset.to_transform(), + transform = transform.post_mul( + &parent.coordinate_system_relative_scale_offset + .inverse() + .to_transform() ); - if inverse { - transform.inverse() - } else { - Some(transform) - } + Some(RelativeTransform { + flattened: transform, + visible_face, + is_perspective, + }) } /// Returns true if the spatial node is the same as the parent, or is /// a child of the parent. pub fn is_same_or_child_of( &self, spatial_node_index: SpatialNodeIndex, parent_spatial_node_index: SpatialNodeIndex, @@ -299,16 +345,17 @@ impl ClipScrollTree { let state = TransformUpdateState { parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(), parent_accumulated_scroll_offset: LayoutVector2D::zero(), nearest_scrolling_ancestor_offset: LayoutVector2D::zero(), nearest_scrolling_ancestor_viewport: LayoutRect::zero(), current_coordinate_system_id: CoordinateSystemId::root(), coordinate_system_relative_scale_offset: ScaleOffset::identity(), invertible: true, + preserves_3d: false, }; debug_assert!(self.nodes_to_update.is_empty()); self.nodes_to_update.push((root_node_index, state)); while let Some((node_index, mut state)) = self.nodes_to_update.pop() { let (previous, following) = self.spatial_nodes.split_at_mut(node_index.0 as usize); let node = match following.get_mut(0) { Some(node) => node, @@ -533,26 +580,26 @@ fn add_reference_frame( ) } #[cfg(test)] fn test_pt( px: f32, py: f32, cst: &ClipScrollTree, - from: SpatialNodeIndex, - to: SpatialNodeIndex, + child: SpatialNodeIndex, + parent: SpatialNodeIndex, expected_x: f32, expected_y: f32, ) { use euclid::approxeq::ApproxEq; const EPSILON: f32 = 0.0001; let p = LayoutPoint::new(px, py); - let m = cst.get_relative_transform(from, to).unwrap(); + let m = cst.get_relative_transform(child, parent).unwrap().flattened; let pt = m.transform_point2d(&p).unwrap(); assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) && pt.y.approx_eq_eps(&expected_y, &EPSILON), "p: {:?} -> {:?}\nm={:?}", p, pt, m, ); } @@ -588,21 +635,18 @@ fn test_cst_simple_translation() { Some(child2), LayoutTransform::create_translation(200.0, 200.0, 0.0), LayoutVector2D::zero(), ); cst.update_tree(WorldPoint::zero(), &SceneProperties::new(), None); test_pt(100.0, 100.0, &cst, child1, root, 200.0, 100.0); - test_pt(100.0, 100.0, &cst, root, child1, 0.0, 100.0); test_pt(100.0, 100.0, &cst, child2, root, 200.0, 150.0); - test_pt(100.0, 100.0, &cst, root, child2, 0.0, 50.0); test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 150.0); - test_pt(100.0, 100.0, &cst, child1, child2, 100.0, 50.0); test_pt(100.0, 100.0, &cst, child3, root, 400.0, 350.0); } #[test] fn test_cst_simple_scale() { // Basic scale only let mut cst = ClipScrollTree::new(); @@ -633,24 +677,20 @@ fn test_cst_simple_scale() { Some(child2), LayoutTransform::create_scale(2.0, 2.0, 1.0), LayoutVector2D::zero(), ); cst.update_tree(WorldPoint::zero(), &SceneProperties::new(), None); test_pt(100.0, 100.0, &cst, child1, root, 400.0, 100.0); - test_pt(100.0, 100.0, &cst, root, child1, 25.0, 100.0); test_pt(100.0, 100.0, &cst, child2, root, 400.0, 200.0); - test_pt(100.0, 100.0, &cst, root, child2, 25.0, 50.0); test_pt(100.0, 100.0, &cst, child3, root, 800.0, 400.0); test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 200.0); - test_pt(100.0, 100.0, &cst, child1, child2, 100.0, 50.0); test_pt(100.0, 100.0, &cst, child3, child1, 200.0, 400.0); - test_pt(100.0, 100.0, &cst, child1, child3, 50.0, 25.0); } #[test] fn test_cst_scale_translation() { // Scale + translation let mut cst = ClipScrollTree::new(); @@ -688,28 +728,23 @@ fn test_cst_scale_translation() { LayoutTransform::create_scale(3.0, 2.0, 1.0), LayoutVector2D::zero(), ); cst.update_tree(WorldPoint::zero(), &SceneProperties::new(), None); test_pt(100.0, 100.0, &cst, child1, root, 200.0, 150.0); test_pt(100.0, 100.0, &cst, child2, root, 300.0, 450.0); - test_pt(100.0, 100.0, &cst, root, child1, 0.0, 50.0); - test_pt(100.0, 100.0, &cst, root, child2, 0.0, 12.5); test_pt(100.0, 100.0, &cst, child4, root, 1100.0, 450.0); - test_pt(1100.0, 450.0, &cst, root, child4, 100.0, 100.0); test_pt(0.0, 0.0, &cst, child4, child1, 400.0, -400.0); test_pt(100.0, 100.0, &cst, child4, child1, 1000.0, 400.0); test_pt(100.0, 100.0, &cst, child2, child1, 200.0, 400.0); - test_pt(200.0, 400.0, &cst, child1, child2, 100.0, 100.0); test_pt(100.0, 100.0, &cst, child3, child1, 600.0, 0.0); - test_pt(400.0, 300.0, &cst, child1, child3, 0.0, 175.0); } #[test] fn test_cst_translation_rotate() { // Rotation + translation use euclid::Angle; let mut cst = ClipScrollTree::new();
--- a/gfx/wr/webrender/src/gpu_types.rs +++ b/gfx/wr/webrender/src/gpu_types.rs @@ -1,15 +1,15 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{ DeviceHomogeneousVector, DevicePoint, DeviceSize, DeviceRect, - LayoutRect, LayoutToWorldTransform, LayoutTransform, + LayoutRect, LayoutToWorldTransform, PremultipliedColorF, LayoutToPictureTransform, PictureToLayoutTransform, PicturePixel, WorldPixel, WorldToLayoutTransform, LayoutPoint, DeviceVector2D }; use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex}; use gpu_cache::{GpuCacheAddress, GpuDataRequest}; use internal_types::FastHashMap; use prim_store::EdgeAaSegmentMask; use render_task::RenderTaskAddress; @@ -467,48 +467,49 @@ impl TransformPalette { ROOT_SPATIAL_NODE_INDEX, // We know the root picture space == world space transform.with_destination::<PicturePixel>(), ); } fn get_index( &mut self, - from_index: SpatialNodeIndex, - to_index: SpatialNodeIndex, + child_index: SpatialNodeIndex, + parent_index: SpatialNodeIndex, clip_scroll_tree: &ClipScrollTree, ) -> usize { - if to_index == ROOT_SPATIAL_NODE_INDEX { - from_index.0 as usize - } else if from_index == to_index { + if parent_index == ROOT_SPATIAL_NODE_INDEX { + child_index.0 as usize + } else if child_index == parent_index { 0 } else { let key = RelativeTransformKey { - from_index, - to_index, + from_index: child_index, + to_index: parent_index, }; let metadata = &mut self.metadata; let transforms = &mut self.transforms; *self.map .entry(key) .or_insert_with(|| { let transform = clip_scroll_tree.get_relative_transform( - from_index, - to_index, + child_index, + parent_index, ) - .unwrap_or(LayoutTransform::identity()) + .unwrap_or_default() + .flattened .with_destination::<PicturePixel>(); register_transform( metadata, transforms, - from_index, - to_index, + child_index, + parent_index, transform, ) }) } } pub fn get_world_transform( &self,
--- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -5,29 +5,29 @@ use api::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint}; use api::{DeviceIntRect, DeviceIntSize, DevicePoint, DeviceRect}; use api::{LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId}; use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode, LayoutSize}; use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint}; use api::{DebugFlags, DeviceHomogeneousVector, DeviceVector2D}; use box_shadow::{BLUR_SAMPLE_SCALE}; use clip::{ClipChainId, ClipChainNode, ClipItem, ClipStore, ClipDataStore, ClipChainStack}; -use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId}; +use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId, VisibleFace}; use debug_colors; use device::TextureFilter; use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D}; use euclid::approxeq::ApproxEq; use frame_builder::{FrameVisibilityContext, FrameVisibilityState}; use intern::ItemUid; use internal_types::{FastHashMap, FastHashSet, PlaneSplitter}; use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext}; use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; use gpu_types::{TransformPalette, UvRectKind}; use plane_split::{Clipper, Polygon, Splitter}; -use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind}; +use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, PrimitiveInstanceKind}; use prim_store::{get_raster_rects, PrimitiveScratchBuffer, VectorKey, PointKey}; use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey}; use print_tree::PrintTreePrinter; use render_backend::DataStores; use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit}; use render_task::{RenderTaskId, RenderTaskLocation}; use resource_cache::ResourceCache; use scene::{FilterOpHelpers, SceneProperties}; @@ -781,24 +781,27 @@ impl TileCache { // to see if any other memory should be freed. return; } let DeviceIntSize { width: tile_width, height: tile_height, _unit: _ } = self.tile_dimensions(frame_context.config.testing); // Work out the scroll offset to apply to the world reference point. - let scroll_transform = frame_context.clip_scroll_tree.get_relative_transform( - ROOT_SPATIAL_NODE_INDEX, - self.spatial_node_index, - ).expect("bug: unable to get scroll transform"); - let scroll_offset = WorldVector2D::new( - scroll_transform.m41, - scroll_transform.m42, - ); + let scroll_offset_point = frame_context.clip_scroll_tree + .get_relative_transform( + self.spatial_node_index, + ROOT_SPATIAL_NODE_INDEX, + ) + .expect("bug: unable to get scroll transform") + .flattened + .inverse_project_2d_origin() + .unwrap_or_else(LayoutPoint::zero); + + let scroll_offset = WorldVector2D::new(scroll_offset_point.x, scroll_offset_point.y); let scroll_delta = match self.scroll_offset { Some(prev) => prev - scroll_offset, None => WorldVector2D::zero(), }; self.scroll_offset = Some(scroll_offset); // Pull any retained tiles from the previous scene. let world_offset = if frame_state.retained_tiles.tiles.is_empty() { @@ -1425,25 +1428,44 @@ impl TileCache { tile.transforms.insert(*spatial_node_index); } } // Update tile transforms let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect(); transform_spatial_nodes.sort(); for spatial_node_index in transform_spatial_nodes { - let xf = frame_context.clip_scroll_tree.get_relative_transform( - self.spatial_node_index, - spatial_node_index, - ).expect("BUG: unable to get relative transform"); + // Note: this is the only place where we don't know beforehand if the tile-affecting + // spatial node is below or above the current picture. + let inverse_origin = if self.spatial_node_index >= spatial_node_index { + frame_context.clip_scroll_tree + .get_relative_transform( + self.spatial_node_index, + spatial_node_index, + ) + .expect("BUG: unable to get relative transform") + .flattened + .transform_point2d(&LayoutPoint::zero()) + } else { + frame_context.clip_scroll_tree + .get_relative_transform( + spatial_node_index, + self.spatial_node_index, + ) + .expect("BUG: unable to get relative transform") + .flattened + .inverse_project_2d_origin() + }; // Store the result of transforming a fixed point by this // transform. // TODO(gw): This could in theory give incorrect results for a // primitive behind the near plane. - let key = xf.transform_point2d(&LayoutPoint::zero()).unwrap_or(LayoutPoint::zero()).round(); + let key = inverse_origin + .unwrap_or_else(LayoutPoint::zero) + .round(); tile.descriptor.transforms.push(key.into()); } // Invalidate if the backing texture was evicted. if resource_cache.texture_cache.is_allocated(&tile.handle) { // Request the backing texture so it won't get evicted this frame. // We specifically want to mark the tile texture as used, even // if it's detected not visible below and skipped. This is because @@ -2665,19 +2687,19 @@ impl PicturePrimitive { _ => { 0.0 } }; // Check if there is perspective, and thus whether a new // rasterization root should be established. let establishes_raster_root = frame_context.clip_scroll_tree - .get_relative_transform(parent_raster_node_index, surface_spatial_node_index) + .get_relative_transform(surface_spatial_node_index, parent_raster_node_index) .expect("BUG: unable to get relative transform") - .has_perspective_component(); + .is_perspective; let surface = SurfaceInfo::new( surface_spatial_node_index, if establishes_raster_root { surface_spatial_node_index } else { parent_raster_node_index },
--- a/gfx/wr/webrender/src/prim_store/mod.rs +++ b/gfx/wr/webrender/src/prim_store/mod.rs @@ -9,17 +9,17 @@ use api::{LayoutPoint, LayoutRect, Layou use api::{PremultipliedColorF, PropertyBinding, Shadow, DeviceVector2D}; use api::{WorldPixel, BoxShadowClipMode, WorldRect, LayoutToWorldScale}; use api::{PicturePixel, RasterPixel, LineStyle, LineOrientation, AuHelpers}; use api::{LayoutPrimitiveInfo}; use api::DevicePoint; use border::{get_max_scale_for_border, build_border_instances}; use border::BorderSegmentCacheKey; use clip::{ClipStore}; -use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex, ROOT_SPATIAL_NODE_INDEX}; +use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, VisibleFace}; use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem}; use debug_colors; use debug_render::DebugItem; use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible}; use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D}; use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState}; use frame_builder::{PrimitiveContext, FrameVisibilityContext, FrameVisibilityState}; use glyph_rasterizer::GlyphKey; @@ -122,92 +122,74 @@ impl PrimitiveOpacity { pub fn combine(&self, other: PrimitiveOpacity) -> PrimitiveOpacity { PrimitiveOpacity{ is_opaque: self.is_opaque && other.is_opaque } } } -#[derive(Debug, Copy, Clone)] -pub enum VisibleFace { - Front, - Back, -} - -impl ops::Not for VisibleFace { - type Output = Self; - fn not(self) -> Self { - match self { - VisibleFace::Front => VisibleFace::Back, - VisibleFace::Back => VisibleFace::Front, - } - } -} #[derive(Debug, Clone)] pub enum CoordinateSpaceMapping<F, T> { Local, ScaleOffset(ScaleOffset), Transform(TypedTransform3D<f32, F, T>), } impl<F, T> CoordinateSpaceMapping<F, T> { pub fn new( ref_spatial_node_index: SpatialNodeIndex, target_node_index: SpatialNodeIndex, clip_scroll_tree: &ClipScrollTree, - ) -> Option<Self> { + ) -> Option<(Self, VisibleFace)> { let spatial_nodes = &clip_scroll_tree.spatial_nodes; let ref_spatial_node = &spatial_nodes[ref_spatial_node_index.0 as usize]; let target_spatial_node = &spatial_nodes[target_node_index.0 as usize]; if ref_spatial_node_index == target_node_index { - Some(CoordinateSpaceMapping::Local) + Some((CoordinateSpaceMapping::Local, VisibleFace::Front)) } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id { - Some(CoordinateSpaceMapping::ScaleOffset( - ref_spatial_node.coordinate_system_relative_scale_offset - .inverse() - .accumulate( - &target_spatial_node.coordinate_system_relative_scale_offset - ) - )) + let scale_offset = ref_spatial_node.coordinate_system_relative_scale_offset + .inverse() + .accumulate(&target_spatial_node.coordinate_system_relative_scale_offset); + Some((CoordinateSpaceMapping::ScaleOffset(scale_offset), VisibleFace::Front)) } else { - let transform = clip_scroll_tree.get_relative_transform( - target_node_index, - ref_spatial_node_index, - ); - - transform.map(|transform| { - CoordinateSpaceMapping::Transform( - transform.with_source::<F>().with_destination::<T>() - ) - }) + clip_scroll_tree + .get_relative_transform(target_node_index, ref_spatial_node_index) + .map(|relative| ( + CoordinateSpaceMapping::Transform( + relative.flattened.with_source::<F>().with_destination::<T>() + ), + relative.visible_face, + )) } } } #[derive(Debug, Clone)] pub struct SpaceMapper<F, T> { kind: CoordinateSpaceMapping<F, T>, pub ref_spatial_node_index: SpatialNodeIndex, pub current_target_spatial_node_index: SpatialNodeIndex, pub bounds: TypedRect<f32, T>, + visible_face: VisibleFace, } impl<F, T> SpaceMapper<F, T> where F: fmt::Debug { pub fn new( ref_spatial_node_index: SpatialNodeIndex, bounds: TypedRect<f32, T>, ) -> Self { SpaceMapper { kind: CoordinateSpaceMapping::Local, ref_spatial_node_index, current_target_spatial_node_index: ref_spatial_node_index, bounds, + visible_face: VisibleFace::Front, } } pub fn new_with_target( ref_spatial_node_index: SpatialNodeIndex, target_node_index: SpatialNodeIndex, bounds: TypedRect<f32, T>, clip_scroll_tree: &ClipScrollTree, @@ -220,21 +202,24 @@ impl<F, T> SpaceMapper<F, T> where F: fm pub fn set_target_spatial_node( &mut self, target_node_index: SpatialNodeIndex, clip_scroll_tree: &ClipScrollTree, ) { if target_node_index != self.current_target_spatial_node_index { self.current_target_spatial_node_index = target_node_index; - self.kind = CoordinateSpaceMapping::new( + let (kind, visible_face) = CoordinateSpaceMapping::new( self.ref_spatial_node_index, target_node_index, clip_scroll_tree, ).expect("bug: should have been culled by invalid node"); + + self.kind = kind; + self.visible_face = visible_face; } } pub fn get_transform(&self) -> TypedTransform3D<f32, F, T> { match self.kind { CoordinateSpaceMapping::Local => { TypedTransform3D::identity() } @@ -279,27 +264,17 @@ impl<F, T> SpaceMapper<F, T> where F: fm None } } } } } pub fn visible_face(&self) -> VisibleFace { - match self.kind { - CoordinateSpaceMapping::Local => VisibleFace::Front, - CoordinateSpaceMapping::ScaleOffset(_) => VisibleFace::Front, - CoordinateSpaceMapping::Transform(ref transform) => { - if transform.is_backface_visible() { - VisibleFace::Back - } else { - VisibleFace::Front - } - } - } + self.visible_face } } /// For external images, it's not possible to know the /// UV coords of the image (or the image data itself) /// until the render thread receives the frame and issues /// callbacks to the client application. For external /// images that are visible, a DeferredResolve is created
--- a/gfx/wr/webrender/src/spatial_node.rs +++ b/gfx/wr/webrender/src/spatial_node.rs @@ -341,16 +341,17 @@ impl SpatialNode { let transform = state.coordinate_system_relative_scale_offset .to_transform() .pre_mul(&relative_transform); // Push that new coordinate system and record the new id. let coord_system = CoordinateSystem { transform, + is_flatten_root: !state.preserves_3d && info.transform_style == TransformStyle::Preserve3D, parent: Some(state.current_coordinate_system_id), }; state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32); coord_systems.push(coord_system); } } } @@ -521,32 +522,35 @@ impl SpatialNode { SpatialNodeType::StickyFrame(ref info) => { // We don't translate the combined rect by the sticky offset, because sticky // offsets actually adjust the node position itself, whereas scroll offsets // only apply to contents inside the node. state.parent_accumulated_scroll_offset += info.current_offset; // We want nested sticky items to take into account the shift // we applied as well. state.nearest_scrolling_ancestor_offset += info.current_offset; + state.preserves_3d = false; } SpatialNodeType::ScrollFrame(ref scrolling) => { state.parent_accumulated_scroll_offset += scrolling.offset; state.nearest_scrolling_ancestor_offset = scrolling.offset; state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect; + state.preserves_3d = false; } SpatialNodeType::ReferenceFrame(ref info) => { state.parent_reference_frame_transform = self.world_viewport_transform; let should_flatten = info.kind == ReferenceFrameKind::Transform && info.transform_style == TransformStyle::Flat; if should_flatten { state.parent_reference_frame_transform = state.parent_reference_frame_transform.project_to_2d(); } + state.preserves_3d = info.transform_style == TransformStyle::Preserve3D; state.parent_accumulated_scroll_offset = LayoutVector2D::zero(); state.coordinate_system_relative_scale_offset = self.coordinate_system_relative_scale_offset; let translation = -info.origin_in_parent_reference_frame; state.nearest_scrolling_ancestor_viewport = state.nearest_scrolling_ancestor_viewport .translate(&translation); }
--- a/gfx/wr/webrender/src/util.rs +++ b/gfx/wr/webrender/src/util.rs @@ -230,30 +230,38 @@ impl ScaleOffset { 0.0, 1.0, ) } } // TODO: Implement these in euclid! pub trait MatrixHelpers<Src, Dst> { + /// A port of the preserves2dAxisAlignment function in Skia. + /// Defined in the SkMatrix44 class. fn preserves_2d_axis_alignment(&self) -> bool; fn has_perspective_component(&self) -> bool; fn has_2d_inverse(&self) -> bool; + /// Check if the matrix post-scaling on either the X or Y axes could cause geometry + /// transformed by this matrix to have scaling exceeding the supplied limit. fn exceeds_2d_scale(&self, limit: f64) -> bool; fn inverse_project(&self, target: &TypedPoint2D<f32, Dst>) -> Option<TypedPoint2D<f32, Src>>; fn inverse_rect_footprint(&self, rect: &TypedRect<f32, Dst>) -> Option<TypedRect<f32, Src>>; fn transform_kind(&self) -> TransformedRectKind; fn is_simple_translation(&self) -> bool; fn is_simple_2d_translation(&self) -> bool; + /// Return the determinant of the 2D part of the matrix. + fn determinant_2d(&self) -> f32; + /// This function returns a point in the `Src` space that projects into zero XY. + /// It ignores the Z coordinate and is usable for "flattened" transformations, + /// since they are not generally inversible. + fn inverse_project_2d_origin(&self) -> Option<TypedPoint2D<f32, Src>>; } impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> { - // A port of the preserves2dAxisAlignment function in Skia. - // Defined in the SkMatrix44 class. fn preserves_2d_axis_alignment(&self) -> bool { if self.m14 != 0.0 || self.m24 != 0.0 { return false; } let mut col0 = 0; let mut col1 = 0; let mut row0 = 0; @@ -282,21 +290,19 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> f fn has_perspective_component(&self) -> bool { self.m14.abs() > NEARLY_ZERO || self.m24.abs() > NEARLY_ZERO || self.m34.abs() > NEARLY_ZERO || (self.m44 - 1.0).abs() > NEARLY_ZERO } fn has_2d_inverse(&self) -> bool { - self.m11 * self.m22 - self.m12 * self.m21 != 0.0 + self.determinant_2d() != 0.0 } - // Check if the matrix post-scaling on either the X or Y axes could cause geometry - // transformed by this matrix to have scaling exceeding the supplied limit. fn exceeds_2d_scale(&self, limit: f64) -> bool { let limit2 = (limit * limit) as f32; self.m11 * self.m11 + self.m12 * self.m12 > limit2 || self.m21 * self.m21 + self.m22 * self.m22 > limit2 } fn inverse_project(&self, target: &TypedPoint2D<f32, Dst>) -> Option<TypedPoint2D<f32, Src>> { let m: TypedTransform2D<f32, Src, Dst>; @@ -344,16 +350,31 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> f fn is_simple_2d_translation(&self) -> bool { if !self.is_simple_translation() { return false; } self.m43.abs() < NEARLY_ZERO } + + fn determinant_2d(&self) -> f32 { + self.m11 * self.m22 - self.m12 * self.m21 + } + + fn inverse_project_2d_origin(&self) -> Option<TypedPoint2D<f32, Src>> { + let det = self.determinant_2d(); + if det != 0.0 { + let x = (self.m21 * self.m42 - self.m41 * self.m22) / det; + let y = (self.m12 * self.m41 - self.m11 * self.m42) / det; + Some(TypedPoint2D::new(x, y)) + } else { + None + } + } } pub trait RectHelpers<U> where Self: Sized, { fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self; fn is_well_formed_and_nonempty(&self) -> bool; @@ -521,16 +542,32 @@ pub mod test { #[test] fn scale_offset_accumulate() { let x0 = LayoutTransform::create_translation(130.0, 200.0, 0.0); let x1 = LayoutTransform::create_scale(7.0, 3.0, 1.0); validate_accumulate(&x0, &x1); } + + #[test] + fn inverse_project_2d_origin() { + let mut m = Transform3D::identity(); + assert_eq!(m.inverse_project_2d_origin(), Some(Point2D::zero())); + m.m11 = 0.0; + assert_eq!(m.inverse_project_2d_origin(), None); + m.m21 = -2.0; + m.m22 = 0.0; + m.m12 = -0.5; + m.m41 = 1.0; + m.m42 = 0.5; + let origin = m.inverse_project_2d_origin().unwrap(); + assert_eq!(origin, Point2D::new(1.0, 0.5)); + assert_eq!(m.transform_point2d(&origin), Some(Point2D::zero())); + } } pub trait MaxRect { fn max_rect() -> Self; } impl MaxRect for DeviceIntRect { fn max_rect() -> Self {
--- a/gfx/wr/wrench/reftests/transforms/content-offset.yaml +++ b/gfx/wr/wrench/reftests/transforms/content-offset.yaml @@ -1,15 +1,16 @@ --- root: items: - type: "stacking-context" perspective: 1000 perspective-origin: 0 0 + "transform-style": "preserve-3d" items: - type: "stacking-context" transform: rotate-x(45) translate(100, 100, 0) "transform-style": "preserve-3d" items: - bounds: [0, 0, 200, 200]
new file mode 100644 --- /dev/null +++ b/gfx/wr/wrench/reftests/transforms/flatten-preserve-3d-root-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - bounds: [100, 150, 150, 75] + type: rect + color: green
new file mode 100644 --- /dev/null +++ b/gfx/wr/wrench/reftests/transforms/flatten-preserve-3d-root.yaml @@ -0,0 +1,23 @@ +# This test ensures that we flatten the trasformations (i.e. zero out Z coordinates) +# at the boundaries of preserve-3d hierarchies. +# If the stacking context isn't flattened at the preserve-3d boundary here, +# it's non-zero Z component starts affecting the screen space position +# due to the "rotate-x" transform at the top level. +--- +root: + items: + - + bounds: [100, 100, 0, 0] + type: stacking-context + transform: rotate-x(60) + transform-style: flat + items: + - + type: "stacking-context" + transform: translate(0, 0, 200) + transform-style: preserve-3d + items: + - + bounds: [0, 0, 150, 150] + type: rect + color: green
--- a/gfx/wr/wrench/reftests/transforms/reftest.list +++ b/gfx/wr/wrench/reftests/transforms/reftest.list @@ -31,8 +31,9 @@ platform(linux,mac) fuzzy(1,2) == perspe platform(linux,mac) fuzzy(9,348) == perspective-border-radius.yaml perspective-border-radius.png == snapped-preserve-3d.yaml snapped-preserve-3d-ref.yaml platform(linux,mac) fuzzy(1,122) == border-scale.yaml border-scale.png platform(linux,mac) fuzzy(1,16) == border-scale-2.yaml border-scale-2.png platform(linux,mac) fuzzy(1,69) == border-scale-3.yaml border-scale-3.png platform(linux,mac) fuzzy(1,74) == border-scale-4.yaml border-scale-4.png # Just make sure we aren't crashing here != large-raster-root.yaml blank.yaml +== flatten-preserve-3d-root.yaml flatten-preserve-3d-root-ref.yaml