diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs index 933d95588e..e4584abad8 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -7,14 +7,13 @@ use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, L use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, LayoutTransform}; use border::{ensure_no_corner_overlap}; use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey}; -use clip_scroll_tree::{CoordinateSystemId, SpatialNodeIndex}; +use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex}; use ellipse::Ellipse; use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks}; use gpu_types::BoxShadowStretchMode; use prim_store::{BrushClipMaskKind, ClipData, ImageMaskData}; use render_task::to_cache_size; use resource_cache::{ImageRequest, ResourceCache}; -use spatial_node::SpatialNode; use std::u32; use util::{extract_inner_rect_safe, pack_as_float, recycle_vec, MatrixHelpers}; @@ -449,7 +448,7 @@ impl ClipStore { local_prim_rect: LayoutRect, local_prim_clip_rect: LayoutRect, spatial_node_index: SpatialNodeIndex, - spatial_nodes: &[SpatialNode], + clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache, resource_cache: &mut ResourceCache, device_pixel_scale: DevicePixelScale, @@ -461,6 +460,7 @@ impl ClipStore { None => return None, }; let mut current_local_clip_rect = local_prim_clip_rect; + let spatial_nodes = &clip_scroll_tree.spatial_nodes; // Walk the clip chain to build local rects, and collect the // smallest possible local clip area. @@ -489,26 +489,16 @@ impl ClipStore { ref_spatial_node.coordinate_system_relative_offset; Some(ClipSpaceConversion::Offset(offset)) } else { - // TODO(gw): We still have issues with clip nodes and primitives where - // there is a perspective transform. We intend to fix these - // cases as a follow up. - let relative_transform = ref_spatial_node - .world_content_transform - .to_transform() - .inverse() - .map(|inv| { - inv.pre_mul(&clip_spatial_node.world_content_transform.to_transform()) - }); - let inv_relative_transform = relative_transform - .and_then(|rt| rt.inverse()); - match (relative_transform, inv_relative_transform) { - (Some(relative_transform), Some(inv_relative_transform)) => { - Some(ClipSpaceConversion::Transform(relative_transform, inv_relative_transform)) - } - _ => { - None - } - } + let xf = clip_scroll_tree.get_relative_transform( + clip_node.spatial_node_index, + spatial_node_index, + ); + + xf.and_then(|xf| { + xf.inverse().map(|inv| { + ClipSpaceConversion::Transform(xf, inv) + }) + }) }; // If we can convert spaces, try to reduce the size of the region diff --git a/webrender/src/clip_scroll_tree.rs b/webrender/src/clip_scroll_tree.rs index 2063d0ff5b..2aa2229091 100644 --- a/webrender/src/clip_scroll_tree.rs +++ b/webrender/src/clip_scroll_tree.rs @@ -10,6 +10,7 @@ use gpu_types::TransformPalette; use internal_types::{FastHashMap, FastHashSet}; use print_tree::{PrintTree, PrintTreePrinter}; use scene::SceneProperties; +use smallvec::SmallVec; use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo}; use util::LayoutToWorldFastTransform; @@ -112,35 +113,54 @@ impl ClipScrollTree { /// panic if that invariant isn't true! pub fn get_relative_transform( &self, - ref_node_index: SpatialNodeIndex, - target_node_index: SpatialNodeIndex, - ) -> LayoutTransform { - let ref_node = &self.spatial_nodes[ref_node_index.0]; - let target_node = &self.spatial_nodes[target_node_index.0]; - - let mut offset = LayoutVector3D::new( - target_node.coordinate_system_relative_offset.x, - target_node.coordinate_system_relative_offset.y, + from_node_index: SpatialNodeIndex, + to_node_index: SpatialNodeIndex, + ) -> Option { + let from_node = &self.spatial_nodes[from_node_index.0]; + let to_node = &self.spatial_nodes[to_node_index.0]; + + 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 nodes: SmallVec<[_; 16]> = SmallVec::new(); + + 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!"); + } + + nodes.reverse(); + + let mut transform = LayoutTransform::create_translation( + -parent.coordinate_system_relative_offset.x, + -parent.coordinate_system_relative_offset.y, 0.0, ); - let mut transform = LayoutTransform::identity(); - // Walk up the tree of coordinate systems, accumulating each - // relative transform. - let mut current_coordinate_system_id = target_node.coordinate_system_id; - while current_coordinate_system_id != ref_node.coordinate_system_id { - let coord_system = &self.coord_systems[current_coordinate_system_id.0 as usize]; + for node in nodes { + let coord_system = &self.coord_systems[node.0 as usize]; + transform = transform.pre_translate(coord_system.offset) + .pre_mul(&coord_system.transform); + } - let relative_transform = coord_system - .transform - .post_translate(offset); - transform = transform.pre_mul(&relative_transform); + let transform = transform.post_translate( + LayoutVector3D::new( + child.coordinate_system_relative_offset.x, + child.coordinate_system_relative_offset.y, + 0.0, + ) + ); - offset = coord_system.offset; - current_coordinate_system_id = coord_system.parent.expect("invalid parent!"); + if inverse { + transform.inverse() + } else { + Some(transform) } - - transform } /// The root reference frame, which is the true root of the ClipScrollTree. Initially @@ -442,3 +462,219 @@ impl ClipScrollTree { } } } + +#[cfg(test)] +fn add_reference_frame( + cst: &mut ClipScrollTree, + parent: Option, + transform: LayoutTransform, + origin_in_parent_reference_frame: LayoutVector2D, +) -> SpatialNodeIndex { + cst.add_reference_frame( + parent, + Some(PropertyBinding::Value(transform)), + None, + origin_in_parent_reference_frame, + PipelineId::dummy(), + ) +} + +#[cfg(test)] +fn test_pt( + px: f32, + py: f32, + cst: &ClipScrollTree, + from: SpatialNodeIndex, + to: 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 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, + ); +} + +#[test] +fn test_cst_simple_translation() { + // Basic translations only + + let mut cst = ClipScrollTree::new(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::create_translation(100.0, 0.0, 0.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), + LayoutTransform::create_translation(0.0, 50.0, 0.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), + LayoutTransform::create_translation(200.0, 200.0, 0.0), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), &SceneProperties::new()); + + 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(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::create_scale(4.0, 1.0, 1.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), + LayoutTransform::create_scale(1.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), + LayoutTransform::create_scale(2.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), &SceneProperties::new()); + + 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(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::create_translation(100.0, 50.0, 0.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), + LayoutTransform::create_scale(2.0, 4.0, 1.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), + LayoutTransform::create_translation(200.0, -100.0, 0.0), + LayoutVector2D::zero(), + ); + + let child4 = add_reference_frame( + &mut cst, + Some(child3), + LayoutTransform::create_scale(3.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), &SceneProperties::new()); + + 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, 400.0, 300.0); + test_pt(400.0, 300.0, &cst, child1, child3, 100.0, 100.0); +} + +#[test] +fn test_cst_translation_rotate() { + // Rotation + translation + use euclid::Angle; + + let mut cst = ClipScrollTree::new(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::degrees(90.0)), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), &SceneProperties::new()); + + test_pt(100.0, 0.0, &cst, child1, root, 0.0, -100.0); +} diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index f0021b2be1..c5826e081e 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -162,9 +162,9 @@ impl LocalRectBuilder { CoordinateSpaceMapping::Offset(offset) } else { let transform = clip_scroll_tree.get_relative_transform( - self.ref_spatial_node_index, target_node_index, - ); + self.ref_spatial_node_index, + ).expect("bug: should have already been culled"); CoordinateSpaceMapping::Transform(transform) }; } @@ -1620,7 +1620,7 @@ impl PrimitiveStore { local_rect, prim.metadata.local_clip_rect, prim_context.spatial_node_index, - &frame_context.clip_scroll_tree.spatial_nodes, + &frame_context.clip_scroll_tree, frame_state.gpu_cache, frame_state.resource_cache, frame_context.device_pixel_scale, diff --git a/wrench/reftests/transforms/blank.yaml b/wrench/reftests/transforms/blank.yaml new file mode 100644 index 0000000000..c4eb3ab673 --- /dev/null +++ b/wrench/reftests/transforms/blank.yaml @@ -0,0 +1,2 @@ +--- +root: diff --git a/wrench/reftests/transforms/coord-system.png b/wrench/reftests/transforms/coord-system.png deleted file mode 100644 index cc6a9c2955..0000000000 Binary files a/wrench/reftests/transforms/coord-system.png and /dev/null differ diff --git a/wrench/reftests/transforms/reftest.list b/wrench/reftests/transforms/reftest.list index 3d8f356df0..dfeeb6d81c 100644 --- a/wrench/reftests/transforms/reftest.list +++ b/wrench/reftests/transforms/reftest.list @@ -10,7 +10,7 @@ platform(linux) fuzzy(1,630) == perspective.yaml perspective.png platform(linux,mac) fuzzy(1,156) == prim-suite.yaml prim-suite.png == segments-bug.yaml segments-bug-ref.yaml platform(linux,mac) == content-offset.yaml content-offset.png -platform(linux,mac) == coord-system.yaml coord-system.png +platform(linux,mac) == coord-system.yaml blank.yaml platform(linux,mac) zoom(4) == border-zoom.yaml border-zoom.png platform(linux) fuzzy(1,520) == perspective-origin.yaml perspective-origin.png platform(linux,mac) color_targets(1) alpha_targets(0) fuzzy(1,180) == screen-space-blit.yaml screen-space-blit.png