Bug 1524797 - WR: rework the relative transform query on a clip-scroll tree r=gw

This change rewords get_relative_transform and assotiated pieces of logic,
so that we flatten the transforms at preserve-3d context boundaries.

It addresses a problem found by 1524797 but doesn't resolve the bug yet (!).
There is another issue likely contributing here, and we can treat this PR
as WIP and not merge until the case is completely resolved.

Differential Revision: https://phabricator.services.mozilla.com/D19254

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dzmitry Malyshau 2019-02-12 15:05:01 +00:00
Родитель 229c0633c7
Коммит 5c7daec3ac
11 изменённых файлов: 242 добавлений и 135 удалений

Просмотреть файл

@ -1306,7 +1306,9 @@ fn add_clip_node_to_current_chain(
ROOT_SPATIAL_NODE_INDEX, ROOT_SPATIAL_NODE_INDEX,
) { ) {
None => return true, None => return true,
Some(xf) => ClipSpaceConversion::Transform(xf.with_destination::<WorldPixel>()), Some(relative) => ClipSpaceConversion::Transform(
relative.flattened.with_destination::<WorldPixel>(),
),
} }
}; };

Просмотреть файл

@ -9,9 +9,9 @@ use gpu_types::TransformPalette;
use internal_types::{FastHashMap, FastHashSet}; use internal_types::{FastHashMap, FastHashSet};
use print_tree::{PrintableTree, PrintTree, PrintTreePrinter}; use print_tree::{PrintableTree, PrintTree, PrintTreePrinter};
use scene::SceneProperties; use scene::SceneProperties;
use smallvec::SmallVec;
use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind}; 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>; pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
@ -29,6 +29,9 @@ pub struct CoordinateSystemId(pub u32);
#[derive(Debug)] #[derive(Debug)]
pub struct CoordinateSystem { pub struct CoordinateSystem {
pub transform: LayoutTransform, 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>, pub parent: Option<CoordinateSystemId>,
} }
@ -36,6 +39,7 @@ impl CoordinateSystem {
fn root() -> Self { fn root() -> Self {
CoordinateSystem { CoordinateSystem {
transform: LayoutTransform::identity(), transform: LayoutTransform::identity(),
is_flatten_root: true,
parent: None, parent: None,
} }
} }
@ -63,6 +67,28 @@ impl CoordinateSystemId {
} }
} }
#[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 { pub struct ClipScrollTree {
/// Nodes which determine the positions (offsets and transforms) for primitives /// Nodes which determine the positions (offsets and transforms) for primitives
/// and clips. /// and clips.
@ -103,6 +129,20 @@ pub struct TransformUpdateState {
/// transformed by this node will not be displayed and display items not transformed by this /// 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. /// node will not be clipped by clips that are transformed by this node.
pub invertible: bool, 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 { impl ClipScrollTree {
@ -116,53 +156,59 @@ impl ClipScrollTree {
} }
} }
/// Calculate the relative transform from `from_node_index` /// Calculate the relative transform from `child_index` to `parent_index`.
/// to `to_node_index`. It's assumed that `from_node_index` /// This method will panic if the nodes are not connected!
/// is an ancestor or a descendant of `to_node_index`. This method will
/// panic if that invariant isn't true!
pub fn get_relative_transform( pub fn get_relative_transform(
&self, &self,
from_node_index: SpatialNodeIndex, child_index: SpatialNodeIndex,
to_node_index: SpatialNodeIndex, parent_index: SpatialNodeIndex,
) -> Option<LayoutTransform> { ) -> Option<RelativeTransform> {
let from_node = &self.spatial_nodes[from_node_index.0 as usize]; assert!(child_index.0 >= parent_index.0);
let to_node = &self.spatial_nodes[to_node_index.0 as usize]; let child = &self.spatial_nodes[child_index.0 as usize];
let parent = &self.spatial_nodes[parent_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)
};
let mut coordinate_system_id = child.coordinate_system_id; 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 { 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]; let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
coordinate_system_id = coord_system.parent.expect("invalid parent!"); 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(); transform = transform.post_mul(
&parent.coordinate_system_relative_scale_offset
let mut transform = parent.coordinate_system_relative_scale_offset .inverse()
.inverse() .to_transform()
.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(),
); );
if inverse { Some(RelativeTransform {
transform.inverse() flattened: transform,
} else { visible_face,
Some(transform) is_perspective,
} })
} }
/// Returns true if the spatial node is the same as the parent, or is /// Returns true if the spatial node is the same as the parent, or is
@ -304,6 +350,7 @@ impl ClipScrollTree {
current_coordinate_system_id: CoordinateSystemId::root(), current_coordinate_system_id: CoordinateSystemId::root(),
coordinate_system_relative_scale_offset: ScaleOffset::identity(), coordinate_system_relative_scale_offset: ScaleOffset::identity(),
invertible: true, invertible: true,
preserves_3d: false,
}; };
debug_assert!(self.nodes_to_update.is_empty()); debug_assert!(self.nodes_to_update.is_empty());
self.nodes_to_update.push((root_node_index, state)); self.nodes_to_update.push((root_node_index, state));
@ -538,8 +585,8 @@ fn test_pt(
px: f32, px: f32,
py: f32, py: f32,
cst: &ClipScrollTree, cst: &ClipScrollTree,
from: SpatialNodeIndex, child: SpatialNodeIndex,
to: SpatialNodeIndex, parent: SpatialNodeIndex,
expected_x: f32, expected_x: f32,
expected_y: f32, expected_y: f32,
) { ) {
@ -547,7 +594,7 @@ fn test_pt(
const EPSILON: f32 = 0.0001; const EPSILON: f32 = 0.0001;
let p = LayoutPoint::new(px, py); 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(); let pt = m.transform_point2d(&p).unwrap();
assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) && assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) &&
pt.y.approx_eq_eps(&expected_y, &EPSILON), pt.y.approx_eq_eps(&expected_y, &EPSILON),
@ -593,11 +640,8 @@ fn test_cst_simple_translation() {
cst.update_tree(WorldPoint::zero(), &SceneProperties::new(), None); 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, 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, 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, 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_pt(100.0, 100.0, &cst, child3, root, 400.0, 350.0);
} }
@ -638,14 +682,10 @@ fn test_cst_simple_scale() {
cst.update_tree(WorldPoint::zero(), &SceneProperties::new(), None); 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, 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, 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, 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, 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, child3, child1, 200.0, 400.0);
test_pt(100.0, 100.0, &cst, child1, child3, 50.0, 25.0);
} }
#[test] #[test]
@ -693,18 +733,13 @@ fn test_cst_scale_translation() {
test_pt(100.0, 100.0, &cst, child1, root, 200.0, 150.0); 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, 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(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(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, child4, child1, 1000.0, 400.0);
test_pt(100.0, 100.0, &cst, child2, child1, 200.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(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] #[test]

Просмотреть файл

@ -4,7 +4,7 @@
use api::{ use api::{
DeviceHomogeneousVector, DevicePoint, DeviceSize, DeviceRect, DeviceHomogeneousVector, DevicePoint, DeviceSize, DeviceRect,
LayoutRect, LayoutToWorldTransform, LayoutTransform, LayoutRect, LayoutToWorldTransform,
PremultipliedColorF, LayoutToPictureTransform, PictureToLayoutTransform, PicturePixel, PremultipliedColorF, LayoutToPictureTransform, PictureToLayoutTransform, PicturePixel,
WorldPixel, WorldToLayoutTransform, LayoutPoint, DeviceVector2D WorldPixel, WorldToLayoutTransform, LayoutPoint, DeviceVector2D
}; };
@ -472,18 +472,18 @@ impl TransformPalette {
fn get_index( fn get_index(
&mut self, &mut self,
from_index: SpatialNodeIndex, child_index: SpatialNodeIndex,
to_index: SpatialNodeIndex, parent_index: SpatialNodeIndex,
clip_scroll_tree: &ClipScrollTree, clip_scroll_tree: &ClipScrollTree,
) -> usize { ) -> usize {
if to_index == ROOT_SPATIAL_NODE_INDEX { if parent_index == ROOT_SPATIAL_NODE_INDEX {
from_index.0 as usize child_index.0 as usize
} else if from_index == to_index { } else if child_index == parent_index {
0 0
} else { } else {
let key = RelativeTransformKey { let key = RelativeTransformKey {
from_index, from_index: child_index,
to_index, to_index: parent_index,
}; };
let metadata = &mut self.metadata; let metadata = &mut self.metadata;
@ -493,17 +493,18 @@ impl TransformPalette {
.entry(key) .entry(key)
.or_insert_with(|| { .or_insert_with(|| {
let transform = clip_scroll_tree.get_relative_transform( let transform = clip_scroll_tree.get_relative_transform(
from_index, child_index,
to_index, parent_index,
) )
.unwrap_or(LayoutTransform::identity()) .unwrap_or_default()
.flattened
.with_destination::<PicturePixel>(); .with_destination::<PicturePixel>();
register_transform( register_transform(
metadata, metadata,
transforms, transforms,
from_index, child_index,
to_index, parent_index,
transform, transform,
) )
}) })

Просмотреть файл

@ -10,7 +10,7 @@ use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDe
use api::{DebugFlags, DeviceHomogeneousVector, DeviceVector2D}; use api::{DebugFlags, DeviceHomogeneousVector, DeviceVector2D};
use box_shadow::{BLUR_SAMPLE_SCALE}; use box_shadow::{BLUR_SAMPLE_SCALE};
use clip::{ClipChainId, ClipChainNode, ClipItem, ClipStore, ClipDataStore, ClipChainStack}; 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 debug_colors;
use device::TextureFilter; use device::TextureFilter;
use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D}; use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D};
@ -22,7 +22,7 @@ use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, Pict
use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
use gpu_types::{TransformPalette, UvRectKind}; use gpu_types::{TransformPalette, UvRectKind};
use plane_split::{Clipper, Polygon, Splitter}; 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::{get_raster_rects, PrimitiveScratchBuffer, VectorKey, PointKey};
use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey}; use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey};
use print_tree::PrintTreePrinter; use print_tree::PrintTreePrinter;
@ -786,14 +786,17 @@ impl TileCache {
self.tile_dimensions(frame_context.config.testing); self.tile_dimensions(frame_context.config.testing);
// Work out the scroll offset to apply to the world reference point. // Work out the scroll offset to apply to the world reference point.
let scroll_transform = frame_context.clip_scroll_tree.get_relative_transform( let scroll_offset_point = frame_context.clip_scroll_tree
ROOT_SPATIAL_NODE_INDEX, .get_relative_transform(
self.spatial_node_index, self.spatial_node_index,
).expect("bug: unable to get scroll transform"); ROOT_SPATIAL_NODE_INDEX,
let scroll_offset = WorldVector2D::new( )
scroll_transform.m41, .expect("bug: unable to get scroll transform")
scroll_transform.m42, .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 { let scroll_delta = match self.scroll_offset {
Some(prev) => prev - scroll_offset, Some(prev) => prev - scroll_offset,
None => WorldVector2D::zero(), None => WorldVector2D::zero(),
@ -1430,15 +1433,34 @@ impl TileCache {
let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect(); let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect();
transform_spatial_nodes.sort(); transform_spatial_nodes.sort();
for spatial_node_index in transform_spatial_nodes { for spatial_node_index in transform_spatial_nodes {
let xf = frame_context.clip_scroll_tree.get_relative_transform( // Note: this is the only place where we don't know beforehand if the tile-affecting
self.spatial_node_index, // spatial node is below or above the current picture.
spatial_node_index, let inverse_origin = if self.spatial_node_index >= spatial_node_index {
).expect("BUG: unable to get relative transform"); 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 // Store the result of transforming a fixed point by this
// transform. // transform.
// TODO(gw): This could in theory give incorrect results for a // TODO(gw): This could in theory give incorrect results for a
// primitive behind the near plane. // 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()); tile.descriptor.transforms.push(key.into());
} }
@ -2670,9 +2692,9 @@ impl PicturePrimitive {
// Check if there is perspective, and thus whether a new // Check if there is perspective, and thus whether a new
// rasterization root should be established. // rasterization root should be established.
let establishes_raster_root = frame_context.clip_scroll_tree 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") .expect("BUG: unable to get relative transform")
.has_perspective_component(); .is_perspective;
let surface = SurfaceInfo::new( let surface = SurfaceInfo::new(
surface_spatial_node_index, surface_spatial_node_index,

Просмотреть файл

@ -14,7 +14,7 @@ use api::DevicePoint;
use border::{get_max_scale_for_border, build_border_instances}; use border::{get_max_scale_for_border, build_border_instances};
use border::BorderSegmentCacheKey; use border::BorderSegmentCacheKey;
use clip::{ClipStore}; 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 clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
use debug_colors; use debug_colors;
use debug_render::DebugItem; use debug_render::DebugItem;
@ -127,21 +127,6 @@ impl PrimitiveOpacity {
} }
} }
#[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)] #[derive(Debug, Clone)]
pub enum CoordinateSpaceMapping<F, T> { pub enum CoordinateSpaceMapping<F, T> {
@ -155,32 +140,27 @@ impl<F, T> CoordinateSpaceMapping<F, T> {
ref_spatial_node_index: SpatialNodeIndex, ref_spatial_node_index: SpatialNodeIndex,
target_node_index: SpatialNodeIndex, target_node_index: SpatialNodeIndex,
clip_scroll_tree: &ClipScrollTree, clip_scroll_tree: &ClipScrollTree,
) -> Option<Self> { ) -> Option<(Self, VisibleFace)> {
let spatial_nodes = &clip_scroll_tree.spatial_nodes; let spatial_nodes = &clip_scroll_tree.spatial_nodes;
let ref_spatial_node = &spatial_nodes[ref_spatial_node_index.0 as usize]; 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]; let target_spatial_node = &spatial_nodes[target_node_index.0 as usize];
if ref_spatial_node_index == target_node_index { 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 { } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id {
Some(CoordinateSpaceMapping::ScaleOffset( let scale_offset = ref_spatial_node.coordinate_system_relative_scale_offset
ref_spatial_node.coordinate_system_relative_scale_offset .inverse()
.inverse() .accumulate(&target_spatial_node.coordinate_system_relative_scale_offset);
.accumulate( Some((CoordinateSpaceMapping::ScaleOffset(scale_offset), VisibleFace::Front))
&target_spatial_node.coordinate_system_relative_scale_offset
)
))
} else { } else {
let transform = clip_scroll_tree.get_relative_transform( clip_scroll_tree
target_node_index, .get_relative_transform(target_node_index, ref_spatial_node_index)
ref_spatial_node_index, .map(|relative| (
); CoordinateSpaceMapping::Transform(
relative.flattened.with_source::<F>().with_destination::<T>()
transform.map(|transform| { ),
CoordinateSpaceMapping::Transform( relative.visible_face,
transform.with_source::<F>().with_destination::<T>() ))
)
})
} }
} }
} }
@ -191,6 +171,7 @@ pub struct SpaceMapper<F, T> {
pub ref_spatial_node_index: SpatialNodeIndex, pub ref_spatial_node_index: SpatialNodeIndex,
pub current_target_spatial_node_index: SpatialNodeIndex, pub current_target_spatial_node_index: SpatialNodeIndex,
pub bounds: TypedRect<f32, T>, pub bounds: TypedRect<f32, T>,
visible_face: VisibleFace,
} }
impl<F, T> SpaceMapper<F, T> where F: fmt::Debug { impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
@ -203,6 +184,7 @@ impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
ref_spatial_node_index, ref_spatial_node_index,
current_target_spatial_node_index: ref_spatial_node_index, current_target_spatial_node_index: ref_spatial_node_index,
bounds, bounds,
visible_face: VisibleFace::Front,
} }
} }
@ -225,11 +207,14 @@ impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
if target_node_index != self.current_target_spatial_node_index { if target_node_index != self.current_target_spatial_node_index {
self.current_target_spatial_node_index = target_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, self.ref_spatial_node_index,
target_node_index, target_node_index,
clip_scroll_tree, clip_scroll_tree,
).expect("bug: should have been culled by invalid node"); ).expect("bug: should have been culled by invalid node");
self.kind = kind;
self.visible_face = visible_face;
} }
} }
@ -284,17 +269,7 @@ impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
} }
pub fn visible_face(&self) -> VisibleFace { pub fn visible_face(&self) -> VisibleFace {
match self.kind { self.visible_face
CoordinateSpaceMapping::Local => VisibleFace::Front,
CoordinateSpaceMapping::ScaleOffset(_) => VisibleFace::Front,
CoordinateSpaceMapping::Transform(ref transform) => {
if transform.is_backface_visible() {
VisibleFace::Back
} else {
VisibleFace::Front
}
}
}
} }
} }

Просмотреть файл

@ -346,6 +346,7 @@ impl SpatialNode {
// Push that new coordinate system and record the new id. // Push that new coordinate system and record the new id.
let coord_system = CoordinateSystem { let coord_system = CoordinateSystem {
transform, transform,
is_flatten_root: !state.preserves_3d && info.transform_style == TransformStyle::Preserve3D,
parent: Some(state.current_coordinate_system_id), parent: Some(state.current_coordinate_system_id),
}; };
state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32); state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32);
@ -526,11 +527,13 @@ impl SpatialNode {
// We want nested sticky items to take into account the shift // We want nested sticky items to take into account the shift
// we applied as well. // we applied as well.
state.nearest_scrolling_ancestor_offset += info.current_offset; state.nearest_scrolling_ancestor_offset += info.current_offset;
state.preserves_3d = false;
} }
SpatialNodeType::ScrollFrame(ref scrolling) => { SpatialNodeType::ScrollFrame(ref scrolling) => {
state.parent_accumulated_scroll_offset += scrolling.offset; state.parent_accumulated_scroll_offset += scrolling.offset;
state.nearest_scrolling_ancestor_offset = scrolling.offset; state.nearest_scrolling_ancestor_offset = scrolling.offset;
state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect; state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
state.preserves_3d = false;
} }
SpatialNodeType::ReferenceFrame(ref info) => { SpatialNodeType::ReferenceFrame(ref info) => {
state.parent_reference_frame_transform = self.world_viewport_transform; state.parent_reference_frame_transform = self.world_viewport_transform;
@ -542,6 +545,7 @@ impl SpatialNode {
if should_flatten { if should_flatten {
state.parent_reference_frame_transform = state.parent_reference_frame_transform.project_to_2d(); 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.parent_accumulated_scroll_offset = LayoutVector2D::zero();
state.coordinate_system_relative_scale_offset = self.coordinate_system_relative_scale_offset; state.coordinate_system_relative_scale_offset = self.coordinate_system_relative_scale_offset;

Просмотреть файл

@ -235,20 +235,28 @@ impl ScaleOffset {
// TODO: Implement these in euclid! // TODO: Implement these in euclid!
pub trait MatrixHelpers<Src, Dst> { 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 preserves_2d_axis_alignment(&self) -> bool;
fn has_perspective_component(&self) -> bool; fn has_perspective_component(&self) -> bool;
fn has_2d_inverse(&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 exceeds_2d_scale(&self, limit: f64) -> bool;
fn inverse_project(&self, target: &TypedPoint2D<f32, Dst>) -> Option<TypedPoint2D<f32, Src>>; 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 inverse_rect_footprint(&self, rect: &TypedRect<f32, Dst>) -> Option<TypedRect<f32, Src>>;
fn transform_kind(&self) -> TransformedRectKind; fn transform_kind(&self) -> TransformedRectKind;
fn is_simple_translation(&self) -> bool; fn is_simple_translation(&self) -> bool;
fn is_simple_2d_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> { 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 { fn preserves_2d_axis_alignment(&self) -> bool {
if self.m14 != 0.0 || self.m24 != 0.0 { if self.m14 != 0.0 || self.m24 != 0.0 {
return false; return false;
@ -287,11 +295,9 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
} }
fn has_2d_inverse(&self) -> bool { 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 { fn exceeds_2d_scale(&self, limit: f64) -> bool {
let limit2 = (limit * limit) as f32; let limit2 = (limit * limit) as f32;
self.m11 * self.m11 + self.m12 * self.m12 > limit2 || self.m11 * self.m11 + self.m12 * self.m12 > limit2 ||
@ -349,6 +355,21 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
self.m43.abs() < NEARLY_ZERO 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> pub trait RectHelpers<U>
@ -526,6 +547,22 @@ pub mod test {
validate_accumulate(&x0, &x1); 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 { pub trait MaxRect {

Просмотреть файл

@ -5,6 +5,7 @@ root:
type: "stacking-context" type: "stacking-context"
perspective: 1000 perspective: 1000
perspective-origin: 0 0 perspective-origin: 0 0
"transform-style": "preserve-3d"
items: items:
- -
type: "stacking-context" type: "stacking-context"

Просмотреть файл

@ -0,0 +1,6 @@
---
root:
items:
- bounds: [100, 150, 150, 75]
type: rect
color: green

Просмотреть файл

@ -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

Просмотреть файл

@ -36,3 +36,4 @@ 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 platform(linux,mac) fuzzy(1,74) == border-scale-4.yaml border-scale-4.png
# Just make sure we aren't crashing here # Just make sure we aren't crashing here
!= large-raster-root.yaml blank.yaml != large-raster-root.yaml blank.yaml
== flatten-preserve-3d-root.yaml flatten-preserve-3d-root-ref.yaml