Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 1bc435d

Browse files
committed
Bug 1659418 - Handle inverse projection negative hemisphere r=nical
Our inverse projection logic was correct, just not fully complete. The problem is that it didn't take into account that the projected point can be in negative hemisphere. Now we are checking for this and bailing out of building a tight rect if this is the case. Another small change is in `get_raster_rects`: if we are unable to find a tighter bound by inverse projection, we return the same unclipped rect. The actual clipping should happen at the picture level anyway. Differential Revision: https://phabricator.services.mozilla.com/D93087
1 parent 0babc1c commit 1bc435d

4 files changed

Lines changed: 86 additions & 9 deletions

File tree

gfx/wr/webrender/src/picture.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7109,12 +7109,14 @@ pub fn get_raster_rects(
71097109
);
71107110

71117111
let unclipped_world_rect = map_to_world.map(&unclipped_raster_rect)?;
7112-
71137112
let clipped_world_rect = unclipped_world_rect.intersection(&prim_bounding_rect)?;
71147113

7115-
let clipped_raster_rect = map_to_world.unmap(&clipped_world_rect)?;
7116-
7117-
let clipped_raster_rect = clipped_raster_rect.intersection(&unclipped_raster_rect)?;
7114+
// We don't have to be able to do the back-projection from world into raster.
7115+
// Rendering only cares one way, so if that fails, we fall back to the full rect.
7116+
let clipped_raster_rect = match map_to_world.unmap(&clipped_world_rect) {
7117+
Some(rect) => rect.intersection(&unclipped_raster_rect)?,
7118+
None => return Some((unclipped, unclipped)),
7119+
};
71187120

71197121
let clipped = raster_rect_to_device_pixels(
71207122
clipped_raster_rect,

gfx/wr/webrender/src/util.rs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -385,14 +385,21 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for Transform3D<f32, Src, Dst> {
385385
self.m21 * self.m21 + self.m22 * self.m22 > limit2
386386
}
387387

388+
/// Find out a point in `Src` that would be projected into the `target`.
388389
fn inverse_project(&self, target: &Point2D<f32, Dst>) -> Option<Point2D<f32, Src>> {
389-
let m: Transform2D<f32, Src, Dst>;
390-
m = Transform2D::new(
390+
// form the linear equation for the hyperplane intersection
391+
let m = Transform2D::<f32, Src, Dst>::new(
391392
self.m11 - target.x * self.m14, self.m12 - target.y * self.m14,
392393
self.m21 - target.x * self.m24, self.m22 - target.y * self.m24,
393394
self.m41 - target.x * self.m44, self.m42 - target.y * self.m44,
394395
);
395-
m.inverse().map(|inv| Point2D::new(inv.m31, inv.m32))
396+
let inv = m.inverse()?;
397+
// we found the point, now check if it maps to the positive hemisphere
398+
if inv.m31 * self.m14 + inv.m32 * self.m24 + self.m44 > 0.0 {
399+
Some(Point2D::new(inv.m31, inv.m32))
400+
} else {
401+
None
402+
}
396403
}
397404

398405
fn inverse_rect_footprint(&self, rect: &Rect<f32, Dst>) -> Option<Rect<f32, Src>> {
@@ -609,8 +616,8 @@ use euclid::vec3;
609616
#[cfg(test)]
610617
pub mod test {
611618
use super::*;
612-
use euclid::default::{Point2D, Transform3D};
613-
use euclid::Angle;
619+
use euclid::default::{Point2D, Rect, Size2D, Transform3D};
620+
use euclid::{Angle, approxeq::ApproxEq};
614621
use std::f32::consts::PI;
615622

616623
#[test]
@@ -624,6 +631,50 @@ pub mod test {
624631
assert_eq!(m1.inverse_project(&p0), Some(Point2D::new(2.0, 2.0)));
625632
}
626633

634+
#[test]
635+
fn inverse_project_footprint() {
636+
let m = Transform3D::new(
637+
0.477499992, 0.135000005, -1.0, 0.000624999986,
638+
-0.642787635, 0.766044438, 0.0, 0.0,
639+
0.766044438, 0.642787635, 0.0, 0.0,
640+
1137.10986, 113.71286, 402.0, 0.748749971,
641+
);
642+
let r = Rect::new(Point2D::zero(), Size2D::new(804.0, 804.0));
643+
{
644+
let points = &[
645+
r.origin,
646+
r.top_right(),
647+
r.bottom_left(),
648+
r.bottom_right(),
649+
];
650+
let mi = m.inverse().unwrap();
651+
// In this section, we do the forward and backward transformation
652+
// to confirm that its bijective.
653+
// We also do the inverse projection path, and confirm it functions the same way.
654+
println!("Points:");
655+
for p in points {
656+
let pp = m.transform_point2d_homogeneous(*p);
657+
let p3 = pp.to_point3d().unwrap();
658+
let pi = mi.transform_point3d_homogeneous(p3);
659+
let px = pi.to_point2d().unwrap();
660+
let py = m.inverse_project(&pp.to_point2d().unwrap()).unwrap();
661+
println!("\t{:?} -> {:?} -> {:?} -> ({:?} -> {:?}, {:?})", p, pp, p3, pi, px, py);
662+
assert!(px.approx_eq_eps(p, &Point2D::new(0.001, 0.001)));
663+
assert!(py.approx_eq_eps(p, &Point2D::new(0.001, 0.001)));
664+
}
665+
}
666+
// project
667+
let rp = project_rect(&m, &r, &Rect::new(Point2D::zero(), Size2D::new(1000.0, 1000.0))).unwrap();
668+
println!("Projected {:?}", rp);
669+
// one of the points ends up in the negative hemisphere
670+
assert_eq!(m.inverse_project(&rp.origin), None);
671+
// inverse
672+
if let Some(ri) = m.inverse_rect_footprint(&rp) {
673+
// inverse footprint should be larger, since it doesn't know the original Z
674+
assert!(ri.contains_rect(&r), "Inverse {:?}", ri);
675+
}
676+
}
677+
627678
fn validate_convert(xref: &LayoutTransform) {
628679
let so = ScaleOffset::from_transform(xref).unwrap();
629680
let xf = so.to_transform();
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Tests that `get_raster_rects` raster -> world transform is inversible in general,
2+
# but one of the vertices of the world rectangles can't map back to the raster.
3+
---
4+
root:
5+
items:
6+
- type: stacking-context
7+
bounds: 0 0 400 400
8+
perspective: 800
9+
perspective-origin: 50% 200
10+
items:
11+
- type: stacking-context
12+
bounds: 0 0 400 400
13+
transform-style: preserve-3d
14+
transform: rotate-z(40) translate(400, 200, 0)
15+
margin: 100
16+
items:
17+
- type: stacking-context
18+
bounds: 0 0 1000 1000
19+
transform: rotate-y(-75) translate(0, 0, -500)
20+
items:
21+
- type: rect
22+
bounds: [0, 0, 200, 200]
23+
color: red

gfx/wr/wrench/reftests/transforms/reftest.list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ skip_on(android) == raster-root-scaling.yaml raster-root-scaling-ref.yaml
5252
skip_on(android) == raster-root-scaling-2.yaml raster-root-scaling-2-ref.yaml
5353
# Make sure we don't panic
5454
!= raster-root-huge-scale.yaml blank.yaml
55+
!= non-inversible-world-rect.yaml blank.yaml

0 commit comments

Comments
 (0)