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,
) {
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 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>;
@ -29,6 +29,9 @@ pub struct CoordinateSystemId(pub u32);
#[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>,
}
@ -36,6 +39,7 @@ impl CoordinateSystem {
fn root() -> Self {
CoordinateSystem {
transform: LayoutTransform::identity(),
is_flatten_root: true,
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 {
/// Nodes which determine the positions (offsets and transforms) for primitives
/// and clips.
@ -103,6 +129,20 @@ pub struct TransformUpdateState {
/// 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 {
@ -116,53 +156,59 @@ impl ClipScrollTree {
}
}
/// 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
@ -304,6 +350,7 @@ impl ClipScrollTree {
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));
@ -538,8 +585,8 @@ fn test_pt(
px: f32,
py: f32,
cst: &ClipScrollTree,
from: SpatialNodeIndex,
to: SpatialNodeIndex,
child: SpatialNodeIndex,
parent: SpatialNodeIndex,
expected_x: f32,
expected_y: f32,
) {
@ -547,7 +594,7 @@ fn test_pt(
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),
@ -593,11 +640,8 @@ fn test_cst_simple_translation() {
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);
}
@ -638,14 +682,10 @@ fn test_cst_simple_scale() {
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]
@ -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, 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]

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

@ -4,7 +4,7 @@
use api::{
DeviceHomogeneousVector, DevicePoint, DeviceSize, DeviceRect,
LayoutRect, LayoutToWorldTransform, LayoutTransform,
LayoutRect, LayoutToWorldTransform,
PremultipliedColorF, LayoutToPictureTransform, PictureToLayoutTransform, PicturePixel,
WorldPixel, WorldToLayoutTransform, LayoutPoint, DeviceVector2D
};
@ -472,18 +472,18 @@ impl TransformPalette {
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;
@ -493,17 +493,18 @@ impl TransformPalette {
.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,
)
})

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

@ -10,7 +10,7 @@ use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDe
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};
@ -22,7 +22,7 @@ use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, Pict
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;
@ -786,14 +786,17 @@ impl TileCache {
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(),
@ -1430,15 +1433,34 @@ impl TileCache {
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());
}
@ -2670,9 +2692,9 @@ impl PicturePrimitive {
// 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,

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

@ -14,7 +14,7 @@ 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;
@ -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)]
pub enum CoordinateSpaceMapping<F, T> {
@ -155,32 +140,27 @@ impl<F, T> CoordinateSpaceMapping<F, T> {
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,
))
}
}
}
@ -191,6 +171,7 @@ pub struct SpaceMapper<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 {
@ -203,6 +184,7 @@ impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
ref_spatial_node_index,
current_target_spatial_node_index: ref_spatial_node_index,
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 {
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;
}
}
@ -284,17 +269,7 @@ impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
}
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
}
}

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

@ -346,6 +346,7 @@ impl SpatialNode {
// 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);
@ -526,11 +527,13 @@ impl SpatialNode {
// 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;
@ -542,6 +545,7 @@ impl SpatialNode {
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;

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

@ -235,20 +235,28 @@ impl ScaleOffset {
// 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;
@ -287,11 +295,9 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
}
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 ||
@ -349,6 +355,21 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
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>
@ -526,6 +547,22 @@ pub mod test {
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 {

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

@ -5,6 +5,7 @@ root:
type: "stacking-context"
perspective: 1000
perspective-origin: 0 0
"transform-style": "preserve-3d"
items:
-
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
# 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