diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs index d52faa9b0fab..a6a25023dc4c 100644 --- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -7109,12 +7109,14 @@ pub fn get_raster_rects( ); let unclipped_world_rect = map_to_world.map(&unclipped_raster_rect)?; - let clipped_world_rect = unclipped_world_rect.intersection(&prim_bounding_rect)?; - let clipped_raster_rect = map_to_world.unmap(&clipped_world_rect)?; - - let clipped_raster_rect = clipped_raster_rect.intersection(&unclipped_raster_rect)?; + // We don't have to be able to do the back-projection from world into raster. + // Rendering only cares one way, so if that fails, we fall back to the full rect. + let clipped_raster_rect = match map_to_world.unmap(&clipped_world_rect) { + Some(rect) => rect.intersection(&unclipped_raster_rect)?, + None => return Some((unclipped, unclipped)), + }; let clipped = raster_rect_to_device_pixels( clipped_raster_rect, diff --git a/gfx/wr/webrender/src/util.rs b/gfx/wr/webrender/src/util.rs index 77975194c7c3..7e10254c9048 100644 --- a/gfx/wr/webrender/src/util.rs +++ b/gfx/wr/webrender/src/util.rs @@ -385,14 +385,21 @@ impl MatrixHelpers for Transform3D { self.m21 * self.m21 + self.m22 * self.m22 > limit2 } + /// Find out a point in `Src` that would be projected into the `target`. fn inverse_project(&self, target: &Point2D) -> Option> { - let m: Transform2D; - m = Transform2D::new( + // form the linear equation for the hyperplane intersection + let m = Transform2D::::new( self.m11 - target.x * self.m14, self.m12 - target.y * self.m14, self.m21 - target.x * self.m24, self.m22 - target.y * self.m24, self.m41 - target.x * self.m44, self.m42 - target.y * self.m44, ); - m.inverse().map(|inv| Point2D::new(inv.m31, inv.m32)) + let inv = m.inverse()?; + // we found the point, now check if it maps to the positive hemisphere + if inv.m31 * self.m14 + inv.m32 * self.m24 + self.m44 > 0.0 { + Some(Point2D::new(inv.m31, inv.m32)) + } else { + None + } } fn inverse_rect_footprint(&self, rect: &Rect) -> Option> { @@ -609,8 +616,8 @@ use euclid::vec3; #[cfg(test)] pub mod test { use super::*; - use euclid::default::{Point2D, Transform3D}; - use euclid::Angle; + use euclid::default::{Point2D, Rect, Size2D, Transform3D}; + use euclid::{Angle, approxeq::ApproxEq}; use std::f32::consts::PI; #[test] @@ -624,6 +631,50 @@ pub mod test { assert_eq!(m1.inverse_project(&p0), Some(Point2D::new(2.0, 2.0))); } + #[test] + fn inverse_project_footprint() { + let m = Transform3D::new( + 0.477499992, 0.135000005, -1.0, 0.000624999986, + -0.642787635, 0.766044438, 0.0, 0.0, + 0.766044438, 0.642787635, 0.0, 0.0, + 1137.10986, 113.71286, 402.0, 0.748749971, + ); + let r = Rect::new(Point2D::zero(), Size2D::new(804.0, 804.0)); + { + let points = &[ + r.origin, + r.top_right(), + r.bottom_left(), + r.bottom_right(), + ]; + let mi = m.inverse().unwrap(); + // In this section, we do the forward and backward transformation + // to confirm that its bijective. + // We also do the inverse projection path, and confirm it functions the same way. + println!("Points:"); + for p in points { + let pp = m.transform_point2d_homogeneous(*p); + let p3 = pp.to_point3d().unwrap(); + let pi = mi.transform_point3d_homogeneous(p3); + let px = pi.to_point2d().unwrap(); + let py = m.inverse_project(&pp.to_point2d().unwrap()).unwrap(); + println!("\t{:?} -> {:?} -> {:?} -> ({:?} -> {:?}, {:?})", p, pp, p3, pi, px, py); + assert!(px.approx_eq_eps(p, &Point2D::new(0.001, 0.001))); + assert!(py.approx_eq_eps(p, &Point2D::new(0.001, 0.001))); + } + } + // project + let rp = project_rect(&m, &r, &Rect::new(Point2D::zero(), Size2D::new(1000.0, 1000.0))).unwrap(); + println!("Projected {:?}", rp); + // one of the points ends up in the negative hemisphere + assert_eq!(m.inverse_project(&rp.origin), None); + // inverse + if let Some(ri) = m.inverse_rect_footprint(&rp) { + // inverse footprint should be larger, since it doesn't know the original Z + assert!(ri.contains_rect(&r), "Inverse {:?}", ri); + } + } + fn validate_convert(xref: &LayoutTransform) { let so = ScaleOffset::from_transform(xref).unwrap(); let xf = so.to_transform(); diff --git a/gfx/wr/wrench/reftests/transforms/non-inversible-world-rect.yaml b/gfx/wr/wrench/reftests/transforms/non-inversible-world-rect.yaml new file mode 100644 index 000000000000..f94404f58ed5 --- /dev/null +++ b/gfx/wr/wrench/reftests/transforms/non-inversible-world-rect.yaml @@ -0,0 +1,23 @@ +# Tests that `get_raster_rects` raster -> world transform is inversible in general, +# but one of the vertices of the world rectangles can't map back to the raster. +--- +root: + items: + - type: stacking-context + bounds: 0 0 400 400 + perspective: 800 + perspective-origin: 50% 200 + items: + - type: stacking-context + bounds: 0 0 400 400 + transform-style: preserve-3d + transform: rotate-z(40) translate(400, 200, 0) + margin: 100 + items: + - type: stacking-context + bounds: 0 0 1000 1000 + transform: rotate-y(-75) translate(0, 0, -500) + items: + - type: rect + bounds: [0, 0, 200, 200] + color: red diff --git a/gfx/wr/wrench/reftests/transforms/reftest.list b/gfx/wr/wrench/reftests/transforms/reftest.list index 5931b7d6e522..b5f82c39e0a2 100644 --- a/gfx/wr/wrench/reftests/transforms/reftest.list +++ b/gfx/wr/wrench/reftests/transforms/reftest.list @@ -52,3 +52,4 @@ skip_on(android) == raster-root-scaling.yaml raster-root-scaling-ref.yaml skip_on(android) == raster-root-scaling-2.yaml raster-root-scaling-2-ref.yaml # Make sure we don't panic != raster-root-huge-scale.yaml blank.yaml +!= non-inversible-world-rect.yaml blank.yaml