Bug 1522218 - Incorporate layout to world scaling for borders. r=gw,mattwoodrow a=lizzard
authorDan Glastonbury <dan.glastonbury@gmail.com>
Wed, 30 Jan 2019 04:08:23 +0000
changeset 516006 05f992aafb09ea89c1681eab913e1bc3b823452c
parent 516005 e0e4b872648840352e81400e823a8b146f8780e3
child 516007 a6605042490a902cea77646ddd0f1cb8196ad2f1
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw, mattwoodrow, lizzard
bugs1522218
milestone66.0
Bug 1522218 - Incorporate layout to world scaling for borders. r=gw,mattwoodrow a=lizzard Implement scaling of borders using the same scale extraction and clamping to nearest power of two that gecko uses in FrameLayerBuilder::ChooseScale. Differential Revision: https://phabricator.services.mozilla.com/D17456
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/util.rs
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -46,16 +46,17 @@ use resource_cache::{ImageProperties, Im
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
 use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
+use util::{scale_factors, clamp_to_scale_factor};
 use smallvec::SmallVec;
 
 pub mod borders;
 pub mod gradient;
 pub mod image;
 pub mod line_dec;
 pub mod picture;
 pub mod text_run;
@@ -2549,17 +2550,31 @@ impl PrimitiveStore {
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 border_data.update(common_data, frame_state);
 
                 // TODO(gw): When drawing in screen raster mode, we should also incorporate a
                 //           scale factor from the world transform to get an appropriately
                 //           sized border task.
-                let world_scale = LayoutToWorldScale::new(1.0);
+                let transform = prim_context.spatial_node.world_content_transform.to_transform();
+
+                // Scale factors are normalized to a power of 2 to reduce the number of
+                // resolution changes
+                let scale = scale_factors(&transform);
+                // For frames with a changing scale transform round scale factors up to
+                // nearest power-of-2 boundary so that we don't keep having to redraw
+                // the content as it scales up and down. Rounding up to nearest
+                // power-of-2 boundary ensures we never scale up, only down --- avoiding
+                // jaggies. It also ensures we never scale down by more than a factor of
+                // 2, avoiding bad downscaling quality.
+                let scale_width = clamp_to_scale_factor(scale.0, false);
+                let scale_height = clamp_to_scale_factor(scale.1, false);
+                // Pick the maximum dimension as scale
+                let world_scale = LayoutToWorldScale::new(scale_width.max(scale_height));
                 let mut scale = world_scale * frame_context.device_pixel_scale;
                 let max_scale = get_max_scale_for_border(&border_data.border.radius,
                                                          &border_data.widths);
                 scale.0 = scale.0.min(max_scale.0);
 
                 // For each edge and corner, request the render task by content key
                 // from the render task cache. This ensures that the render task for
                 // this segment will be available for batching later in the frame.
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -1048,8 +1048,81 @@ impl<T> MallocShallowSizeOf for PrimaryA
     }
 }
 
 impl<T: MallocSizeOf> MallocSizeOf for PrimaryArc<T> {
     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
         self.shallow_size_of(ops) + (**self).size_of(ops)
     }
 }
+
+/// Computes the scale factors of this matrix; that is,
+/// the amounts each basis vector is scaled by.
+///
+/// This code comes from gecko gfx/2d/Matrix.h with the following
+/// modifications:
+///
+/// * Removed `xMajor` parameter.
+pub fn scale_factors<Src, Dst>(
+    mat: &TypedTransform3D<f32, Src, Dst>
+) -> (f32, f32) {
+    // Determinant is just of the 2D component.
+    let det = mat.m11 * mat.m22 - mat.m12 * mat.m21;
+    if det == 0.0 {
+        return (0.0, 0.0);
+    }
+
+    // ignore mirroring
+    let det = det.abs();
+
+    let major = (mat.m11 * mat.m11 + mat.m12 * mat.m12).sqrt();
+    let minor = if major != 0.0 { det / major } else { 0.0 };
+
+    (major, minor)
+}
+
+/// Clamp scaling factor to a power of two.
+///
+/// This code comes from gecko gfx/thebes/gfxUtils.cpp with the following
+/// modification:
+///
+/// * logs are taken in base 2 instead of base e.
+pub fn clamp_to_scale_factor(val: f32, round_down: bool) -> f32 {
+    // Arbitary scale factor limitation. We can increase this
+    // for better scaling performance at the cost of worse
+    // quality.
+    const SCALE_RESOLUTION: f32 = 2.0;
+
+    // Negative scaling is just a flip and irrelevant to
+    // our resolution calculation.
+    let val = val.abs();
+
+    let (val, inverse) = if val < 1.0 {
+        (1.0 / val, true)
+    } else {
+        (val, false)
+    };
+
+    let power = val.log2() / SCALE_RESOLUTION.log2();
+
+    // If power is within 1e-5 of an integer, round to nearest to
+    // prevent floating point errors, otherwise round up to the
+    // next integer value.
+    let power = if (power - power.round()).abs() < 1e-5 {
+        power.round()
+    } else if inverse != round_down {
+        // Use floor when we are either inverted or rounding down, but
+        // not both.
+        power.floor()
+    } else {
+        // Otherwise, ceil when we are not inverted and not rounding
+        // down, or we are inverted and rounding down.
+        power.ceil()
+    };
+
+    let scale = SCALE_RESOLUTION.powf(power);
+
+    if inverse {
+        1.0 / scale
+    } else {
+        scale
+    }
+}