From 4c1689f4adc005fcb1215783180a1675f2f6ae28 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 20 Aug 2018 15:51:07 +1000 Subject: [PATCH] Use get_relative_transform for clip <-> prim space conversions. This expands the functionality in get_relative_transform to work for both primitive and clip space conversions. This code path is not super efficient (it could be improved), but on the majority of sites it never runs, and even on cases where it does run, the coordinate system tree is typically very shallow. We can optimize this function if it ever shows up in a profile. --- webrender/src/clip.rs | 36 +-- webrender/src/clip_scroll_tree.rs | 282 ++++++++++++++++++-- webrender/src/prim_store.rs | 6 +- wrench/reftests/transforms/blank.yaml | 2 + wrench/reftests/transforms/coord-system.png | Bin 4767 -> 0 bytes wrench/reftests/transforms/reftest.list | 2 +- 6 files changed, 278 insertions(+), 50 deletions(-) create mode 100644 wrench/reftests/transforms/blank.yaml delete mode 100644 wrench/reftests/transforms/coord-system.png 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 cc6a9c2955baa35c8e64d90cc165cc889a01810b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4767 zcmeHL`&Uxw8r~W;(V3c*T22?WNdwJE?EsN?r=dth$S#m2YGdPNv~m^FHsp zzXkUYm9%!v<~0BSto1ucJPZKlLI5y3yLuJ$%L9+n4FF&n;Ya-HXiD+)i2t=Mih{0F ztSn+(N!dZ}`9tx!Q^y9i8ck3t>W$6R!xV8)s{Q3uGn?6U=fCsY5c)&XvLvOaqV91W zJ3VW~A9MSQ4{po*^}Q9r_I-mfG!_7EUezFgo5dd=A2s-|eNc8+x%iw={_`? zZ()Ia+QDl+EFno9>$VxFK)XlBpxw`1(@=P5YE2c-xd!!*EMB# zpJ-bBF5<9a9F|UQD8=0|m}M40nEw2hH99Vnm4c-&NHIK4()sV_y|LoAFYBoLtQS8rMSDrW07B_Q?YnvaQIw{ffMb$KlKx_Vb);1>2T1 z?@OV)gBjxd`gLyZ=i-f{OW=c4t3W}^H0dguiYgZsbLtL6(;b>JxNHf1Vfd5U1}R+RsA=OMYGh z5G;hh>~LRr;%Fd7+uZaK<8}*NL2a>|c28AvK&Nc$NL!IPOl8ThYo3KRu^kD!nYr@d zBZBE{`Lj<8QdW+=h6J<}Ufz|P`XpRrS63IIpuNV)|ECNl^UoQP@RMP60q z(d@{Q!|paEB1K`~dEN3nB;zQ|rA*SPf{J0xfCyKX1?>VS7pMGrs=PzX{YYPy{D$VV zNT~D*Wsno>j+nEE(e-?2Ibwf*hr3zB1sM1j{!!??s1jsfR#Ze*^z4KrE2i3Mi66#H!n$kZO$m z(a1ihz~)wJg-TNVmlQ~V5r-AV3!~`%sFJ3i9KR53lv|WH865A-N)8LcGaFz(UFeM` zrHgUN&yB}<5#?`ZW?taQdbejuuwU*;sk-87g6l*L5hX-Im}rF#%z8U=!b|VUpA9jG z_nwBg#XWxGyBN%XYGIPjj;iPUD1UKT`~7l1Hyr~bZwT6st>tSdX%XK-!qdq$q-Z+* z7S`lh)EF4=?tNzh!n8#&;J~E&DPkaHG5$PfrwXq8lVsc2-dFPsOwtFwDajx$JJaAr5@=+5h-Ws||IL8L+Y;xP_6>U>v#(Q~vlS z`=|yqbN{b>Q022cpIa2VJXP4^l=NW?L${)-uUYZ|hH;@^HEg9k+s>W=RW4Fvok3+F zFhbOl-O9Gbv-+hT9YNVK&(TlyOWu7BtQV$dQ`hYi>kXCam^iez-aCIAXnz=06<*|E zU885KTC9|3O4`$!+4Cz9NrtZwjs6|B3{ZPe7X)7%oeX#4w@iv=k_R@6Ej^M7J zwSPAVDLE_0M0V~A>xF$s_pz#)(IKd6uBaK4ln)AA}b1al%Gwmn~`BD^{2ya|~7he;oE0yzu4 z2aA*vqv#=$49a%=Z_RDs@@_ZpTY~ky=T2`_>{#K8H=#vvEbVYuwh#azisf3*4C@;H z-8e9bnaB)Ld+O_gd?27P#{p7MF|CaQ?V}{;f}OAG z#ut!di`V^Ejk&_8}8^5&1VavtZQ5%-JH8HM-|HB=nnNY zGtGX}j5He!q%&L?%t8$%GomhI?Ndd56tls{ z;>WZ)FIbjpzdR-IO@DeNybhduv$WVNv333npXYxL76KqVepks=;P)2#W#;(tLOMnh zf1eMk_y0wem?QmRI$O~hM^DhZ;bGVS#~j&Mn}kZV;`yU!@XDEqAm*coPQXP mfIie_fCJ{^<9|(qIY#~cs2hkGg#r5G3GmxbC06eXyZCS6S-kB4 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