Simplify and extend trans-stacking-context subpixel accumulation

Simplify the situations in which subpixels are accumulated, so that it
is only done for translation or identity transformation matrices. Also,
apply accumulated subpixels to more operations in PaintContext. This
fixes several pre-existing reftests and hopefully will eliminate
off-by-one errors in flaky reftests.

Fixes #10881.
This commit is contained in:
Martin Robinson 2016-08-01 13:50:04 +02:00
parent 5dac1f64c1
commit 4aabbf33d4
5 changed files with 39 additions and 34 deletions

View file

@ -18,7 +18,7 @@ use app_units::Au;
use azure::azure::AzFloat; use azure::azure::AzFloat;
use azure::azure_hl::Color; use azure::azure_hl::Color;
use euclid::approxeq::ApproxEq; use euclid::approxeq::ApproxEq;
use euclid::num::Zero; use euclid::num::{One, Zero};
use euclid::rect::TypedRect; use euclid::rect::TypedRect;
use euclid::side_offsets::SideOffsets2D; use euclid::side_offsets::SideOffsets2D;
use euclid::{Matrix2D, Matrix4D, Point2D, Rect, Size2D}; use euclid::{Matrix2D, Matrix4D, Point2D, Rect, Size2D};
@ -457,20 +457,18 @@ impl DisplayList {
let transform = transform.translate(pixel_snapped_origin.x as AzFloat, let transform = transform.translate(pixel_snapped_origin.x as AzFloat,
pixel_snapped_origin.y as AzFloat, pixel_snapped_origin.y as AzFloat,
0.0).mul(&stacking_context.transform); 0.0).mul(&stacking_context.transform);
let inverse_transform = transform.invert();
// Here we are trying to accumulate any subpixel distances across transformed if transform.is_identity_or_simple_translation() {
// stacking contexts. This allows us transform stacking context with a let pixel_snapped_origin = Point2D::new(Au::from_f32_px(pixel_snapped_origin.x),
// pixel-snapped transform, but continue to propagate any subpixels from stacking Au::from_f32_px(pixel_snapped_origin.y));
// context origins to children. (transform, origin - pixel_snapped_origin)
let subpixel_offset = Point2D::new(origin.x.to_f32_px() - pixel_snapped_origin.x, } else {
origin.y.to_f32_px() - pixel_snapped_origin.y); // In the case of a more complicated transformation, don't attempt to
let subpixel_offset = inverse_transform.transform_point(&subpixel_offset) - // preserve subpixel offsets. This causes problems with reference tests
inverse_transform.transform_point(&Point2D::zero());; // that do scaling and rotation and it's unclear if we even want to be doing
let subpixel_offset = Point2D::new(Au::from_f32_px(subpixel_offset.x), // this.
Au::from_f32_px(subpixel_offset.y)); (transform, Point2D::zero())
}
(transform, subpixel_offset)
} }
}; };
@ -1462,3 +1460,18 @@ impl WebRenderImageInfo {
/// The type of the scroll offset list. This is only populated if WebRender is in use. /// The type of the scroll offset list. This is only populated if WebRender is in use.
pub type ScrollOffsetMap = HashMap<StackingContextId, Point2D<f32>>; pub type ScrollOffsetMap = HashMap<StackingContextId, Point2D<f32>>;
pub trait SimpleMatrixDetection {
fn is_identity_or_simple_translation(&self) -> bool;
}
impl SimpleMatrixDetection for Matrix4D<f32> {
#[inline]
fn is_identity_or_simple_translation(&self) -> bool {
let (_0, _1) = (Zero::zero(), One::one());
self.m11 == _1 && self.m12 == _0 && self.m13 == _0 && self.m14 == _0 &&
self.m21 == _0 && self.m22 == _1 && self.m23 == _0 && self.m24 == _0 &&
self.m31 == _0 && self.m32 == _0 && self.m33 == _1 && self.m34 == _0 &&
self.m44 == _1
}
}

View file

@ -122,6 +122,10 @@ struct CornerOrigin {
} }
impl<'a> PaintContext<'a> { impl<'a> PaintContext<'a> {
pub fn to_nearest_azure_rect(&self, rect: &Rect<Au>) -> Rect<AzFloat> {
rect.translate(&self.subpixel_offset).to_nearest_azure_rect(self.screen_pixels_per_px())
}
pub fn screen_pixels_per_px(&self) -> ScaleFactor<PagePx, ScreenPx, f32> { pub fn screen_pixels_per_px(&self) -> ScaleFactor<PagePx, ScreenPx, f32> {
self.screen_rect.as_f32().size.width / self.page_rect.size.width self.screen_rect.as_f32().size.width / self.page_rect.size.width
} }
@ -132,7 +136,7 @@ impl<'a> PaintContext<'a> {
pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) { pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) {
self.draw_target.make_current(); self.draw_target.make_current();
self.draw_target.fill_rect(&bounds.to_nearest_azure_rect(self.screen_pixels_per_px()), self.draw_target.fill_rect(&self.to_nearest_azure_rect(&bounds),
PatternRef::Color(&ColorPattern::new(color)), PatternRef::Color(&ColorPattern::new(color)),
None); None);
} }
@ -160,7 +164,7 @@ impl<'a> PaintContext<'a> {
} }
pub fn draw_push_clip(&self, bounds: &Rect<Au>) { pub fn draw_push_clip(&self, bounds: &Rect<Au>) {
let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let rect = self.to_nearest_azure_rect(bounds);
let path_builder = self.draw_target.create_path_builder(); let path_builder = self.draw_target.create_path_builder();
let left_top = Point2D::new(rect.origin.x, rect.origin.y); let left_top = Point2D::new(rect.origin.x, rect.origin.y);
@ -211,7 +215,7 @@ impl<'a> PaintContext<'a> {
let source_rect = Rect::new(Point2D::new(0.0, 0.0), let source_rect = Rect::new(Point2D::new(0.0, 0.0),
Size2D::new(image_info.width as AzFloat, Size2D::new(image_info.width as AzFloat,
image_info.height as AzFloat)); image_info.height as AzFloat));
let dest_rect = bounds.to_nearest_azure_rect(scale); let dest_rect = self.to_nearest_azure_rect(bounds);
// TODO(pcwalton): According to CSS-IMAGES-3 § 5.3, nearest-neighbor interpolation is a // TODO(pcwalton): According to CSS-IMAGES-3 § 5.3, nearest-neighbor interpolation is a
// conforming implementation of `crisp-edges`, but it is not the best we could do. // conforming implementation of `crisp-edges`, but it is not the best we could do.
@ -1127,7 +1131,7 @@ impl<'a> PaintContext<'a> {
radius: &BorderRadii<AzFloat>, radius: &BorderRadii<AzFloat>,
color: Color, color: Color,
dash_size: DashSize) { dash_size: DashSize) {
let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let rect = self.to_nearest_azure_rect(bounds);
let draw_opts = DrawOptions::new(1.0, CompositionOp::Over, AntialiasMode::None); let draw_opts = DrawOptions::new(1.0, CompositionOp::Over, AntialiasMode::None);
let border_width = match direction { let border_width = match direction {
Direction::Top => border.top, Direction::Top => border.top,
@ -1195,7 +1199,7 @@ impl<'a> PaintContext<'a> {
border: &SideOffsets2D<f32>, border: &SideOffsets2D<f32>,
radius: &BorderRadii<AzFloat>, radius: &BorderRadii<AzFloat>,
color: Color) { color: Color) {
let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let rect = self.to_nearest_azure_rect(bounds);
self.draw_border_path(&rect, direction, border, radius, color); self.draw_border_path(&rect, direction, border, radius, color);
} }
@ -1203,7 +1207,7 @@ impl<'a> PaintContext<'a> {
bounds: &Rect<Au>, bounds: &Rect<Au>,
border: &SideOffsets2D<f32>, border: &SideOffsets2D<f32>,
shrink_factor: f32) -> Rect<f32> { shrink_factor: f32) -> Rect<f32> {
let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let rect = self.to_nearest_azure_rect(bounds);
let scaled_border = SideOffsets2D::new(shrink_factor * border.top, let scaled_border = SideOffsets2D::new(shrink_factor * border.top,
shrink_factor * border.right, shrink_factor * border.right,
shrink_factor * border.bottom, shrink_factor * border.bottom,
@ -1406,7 +1410,7 @@ impl<'a> PaintContext<'a> {
&end_point.to_nearest_azure_point(scale), &end_point.to_nearest_azure_point(scale),
stops, stops,
&Matrix2D::identity()); &Matrix2D::identity());
self.draw_target.fill_rect(&bounds.to_nearest_azure_rect(scale), self.draw_target.fill_rect(&self.to_nearest_azure_rect(&bounds),
PatternRef::LinearGradient(&pattern), PatternRef::LinearGradient(&pattern),
None); None);
} }
@ -1632,7 +1636,7 @@ impl<'a> PaintContext<'a> {
self.draw_push_clip(&clip_region.main); self.draw_push_clip(&clip_region.main);
for complex_region in &clip_region.complex { for complex_region in &clip_region.complex {
// FIXME(pcwalton): Actually draw a rounded rect. // FIXME(pcwalton): Actually draw a rounded rect.
self.push_rounded_rect_clip(&complex_region.rect.to_nearest_azure_rect(scale), self.push_rounded_rect_clip(&self.to_nearest_azure_rect(&complex_region.rect),
&complex_region.radii.to_radii_pixels(scale)) &complex_region.radii.to_radii_pixels(scale))
} }
self.transient_clip = Some(clip_region) self.transient_clip = Some(clip_region)

View file

@ -1,4 +0,0 @@
[transform-input-017.htm]
type: reftest
expected: FAIL
bug: https://github.com/servo/servo/issues/10881

View file

@ -1,4 +0,0 @@
[transform-input-018.htm]
type: reftest
expected: FAIL
bug: https://github.com/servo/servo/issues/10881

View file

@ -1,4 +0,0 @@
[transform-input-019.htm]
type: reftest
expected:
if os == "linux": FAIL