Bug 1575258 - Make text rasterize, render and snap glyphs consistently. r=lsalzman
authorAndrew Osmond <aosmond@mozilla.com>
Wed, 06 Nov 2019 12:17:43 +0000
changeset 500831 1e12f9b1812772fee75f80a38fd27b5fff8cffbc
parent 500830 b4ef304c1c6ba4ae9dcce9eddb14cdfe98ccf871
child 500832 8189d25586fb6dd2d8e4f5bafbc43a1e31f4988e
push id114166
push userapavel@mozilla.com
push dateThu, 07 Nov 2019 10:04:01 +0000
treeherdermozilla-inbound@d271c572a9bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1575258
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1575258 - Make text rasterize, render and snap glyphs consistently. r=lsalzman The glyph pixel space in which we rasterized glyphs differed from how we rendered the rasterized glyphs in the shader. They need to be in agreement because the glyph subpixel offset selected during rasterization depends on it. This patch should make the paths consistent with each other. Additionally, during animations, we now snap the reference frame relative offset ignoring the impact of any animated transforms. This helps with minimizing glyph wiggling during the transition. Differential Revision: https://phabricator.services.mozilla.com/D51305
gfx/wr/webrender/res/ps_text_run.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/prim_store/text_run.rs
gfx/wr/webrender/src/scene_building.rs
gfx/wr/webrender/src/util.rs
gfx/wr/wrench/reftests/text/reftest.list
layout/reftests/bidi/reftest.list
layout/reftests/bugs/reftest.list
layout/reftests/svg/smil/reftest.list
layout/reftests/svg/svg-integration/clip-path/reftest.list
layout/reftests/text-overflow/reftest.list
layout/reftests/text/reftest.list
layout/reftests/w3c-css/submitted/counter-styles-3/reftest.list
layout/reftests/w3c-css/submitted/selectors4/reftest.list
layout/reftests/webkit-box/reftest.list
layout/reftests/writing-mode/reftest.list
testing/web-platform/meta/css/css-lists/list-style-type-string-002.html.ini
testing/web-platform/meta/css/css-overflow/webkit-line-clamp-024.html.ini
testing/web-platform/meta/css/css-overflow/webkit-line-clamp-027.html.ini
testing/web-platform/meta/css/css-overflow/webkit-line-clamp-029.html.ini
testing/web-platform/meta/css/css-overflow/webkit-line-clamp-030.html.ini
testing/web-platform/meta/css/css-ui/text-overflow-026.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-01a.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-01b.html.ini
--- a/gfx/wr/webrender/res/ps_text_run.glsl
+++ b/gfx/wr/webrender/res/ps_text_run.glsl
@@ -70,108 +70,107 @@ struct TextRun {
 
 TextRun fetch_text_run(int address) {
     vec4 data[2] = fetch_from_gpu_cache_2(address);
     return TextRun(data[0], data[1]);
 }
 
 VertexInfo write_text_vertex(RectWithSize local_clip_rect,
                              float z,
-                             int raster_space,
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+                             mat2 glyph_transform,
+#else
+                             float glyph_scale,
+#endif
                              Transform transform,
                              PictureTask task,
                              vec2 text_offset,
                              vec2 glyph_offset,
                              RectWithSize glyph_rect,
                              vec2 snap_bias) {
-    // The offset to snap the glyph rect to a device pixel
-    vec2 snap_offset = vec2(0.0);
-    // Transform from glyph space to local space
-    mat2 glyph_transform_inv = mat2(1.0);
-
+    // Glyph space refers to the pixel space used by glyph rasterization during frame
+    // building. If a non-identity transform was used, WR_FEATURE_GLYPH_TRANSFORM will
+    // be set. Otherwise, regardless of whether the raster space is LOCAL or SCREEN,
+    // we ignored the transform during glyph rasterization, and need to snap just using
+    // the device pixel scale and the raster scale.
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
-    bool remove_subpx_offset = true;
-#else
-    bool remove_subpx_offset = transform.is_axis_aligned;
-#endif
-    // Compute the snapping offset only if the scroll node transform is axis-aligned.
-    if (remove_subpx_offset) {
-        // Be careful to only snap with the transform when in screen raster space.
-        switch (raster_space) {
-            case RASTER_SCREEN: {
-                // Transform from local space to glyph space.
-                float device_scale = task.device_pixel_scale / transform.m[3].w;
-                mat2 glyph_transform = mat2(transform.m) * device_scale;
-
-                // Ensure the transformed text offset does not contain a subpixel translation
-                // such that glyph snapping is stable for equivalent glyph subpixel positions.
-                vec2 device_text_pos = glyph_transform * text_offset + transform.m[3].xy * device_scale;
-                snap_offset = floor(device_text_pos + 0.5) - device_text_pos;
+    // Transform from glyph space back to local space.
+    mat2 glyph_transform_inv = inverse(glyph_transform);
 
-                // Snap the glyph offset to a device pixel, using an appropriate bias depending
-                // on whether subpixel positioning is required.
-                vec2 device_glyph_offset = glyph_transform * glyph_offset;
-                snap_offset += floor(device_glyph_offset + snap_bias) - device_glyph_offset;
-
-                // Transform from glyph space back to local space.
-                glyph_transform_inv = inverse(glyph_transform);
+    // Glyph raster pixels include the impact of the transform. This path can only be
+    // entered for 3d transforms that can be coerced into a 2d transform; they have no
+    // perspective, and have a 2d inverse. This is a looser condition than axis aligned
+    // transforms because it also allows 2d rotations.
+    vec2 raster_glyph_offset = glyph_transform * glyph_offset;
+    vec2 raster_snap_offset = floor(raster_glyph_offset + snap_bias) - raster_glyph_offset;
+    vec2 local_snap_offset = glyph_transform_inv * raster_snap_offset;
 
-#ifndef WR_FEATURE_GLYPH_TRANSFORM
-                // If not using transformed subpixels, the glyph rect is actually in local space.
-                // So convert the snap offset back to local space.
-                snap_offset = glyph_transform_inv * snap_offset;
-#endif
-                break;
-            }
-            default: {
-                // Otherwise, when in local raster space, the transform may be animated, so avoid
-                // snapping with the transform to avoid oscillation.
-                snap_offset = floor(text_offset + 0.5) - text_offset;
-                snap_offset += floor(glyph_offset + snap_bias) - glyph_offset;
-                break;
-            }
-        }
-    }
+    // We want to eliminate any subpixel translation in device space to ensure glyph
+    // snapping is stable for equivalent glyph subpixel positions. Note that we must use
+    // device pixels, and not glyph raster pixels for this purpose.
+    vec2 device_text_pos = (transform.m * vec4(text_offset, 0.0, 1.0)).xy * task.device_pixel_scale;
+    vec2 device_snap_offset = floor(device_text_pos + 0.5) - device_text_pos;
 
-    // Actually translate the glyph rect to a device pixel using the snap offset.
-    glyph_rect.p0 += snap_offset;
-
-#ifdef WR_FEATURE_GLYPH_TRANSFORM
     // The glyph rect is in device space, so transform it back to local space.
     RectWithSize local_rect = transform_rect(glyph_rect, glyph_transform_inv);
 
     // Select the corner of the glyph's local space rect that we are processing.
     vec2 local_pos = local_rect.p0 + local_rect.size * aPosition.xy;
 
     // If the glyph's local rect would fit inside the local clip rect, then select a corner from
     // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
     // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
     if (rect_inside_rect(local_rect, local_clip_rect)) {
         local_pos = glyph_transform_inv * (glyph_rect.p0 + glyph_rect.size * aPosition.xy);
     }
 #else
+    // Glyph raster pixels do not include the impact of the transform. Instead it was
+    // replaced with an identity transform during glyph rasterization. As such only the
+    // impact of the raster scale (if in local space) and the device pixel scale (for both
+    // local and screen space) are included.
+    //
+    // This implies one or more of the following conditions:
+    // - The transform is an identity. In that case, setting WR_FEATURE_GLYPH_TRANSFORM
+    //   should have the same output result as not. We just distingush which path to use
+    //   based on the transform used during glyph rasterization. (Screen space).
+    // - The transform contains an animation. We will imply local raster space in such
+    //   cases to avoid constantly rerasterizing the glyphs.
+    // - The transform has perspective or does not have a 2d inverse (Screen or local space).
+    // - The transform's scale will result in result in very large rasterized glyphs and
+    //   we clamped the size. This will imply local raster space.
+    vec2 raster_glyph_offset = glyph_offset * glyph_scale;
+    vec2 raster_snap_offset = floor(raster_glyph_offset + snap_bias) - raster_glyph_offset;
+    vec2 local_snap_offset = raster_snap_offset / glyph_scale;
+
+    // The transform may be animated, so we don't want to do any snapping here for the
+    // text offset to avoid glyphs wiggling. The text offset should have been snapped
+    // already for axis aligned transforms excluding any animations during frame building.
+    vec2 device_snap_offset = vec2(0.0);
+
     // Select the corner of the glyph rect that we are processing.
     vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
 #endif
 
     // Clamp to the local clip rect.
     local_pos = clamp_rect(local_pos, local_clip_rect);
 
     // Map the clamped local space corner into device space.
     vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
     vec2 device_pos = world_pos.xy * task.device_pixel_scale;
+    vec4 snapped_world_pos = transform.m * vec4(local_pos + local_snap_offset, 0.0, 1.0);
+    vec2 snapped_device_pos = snapped_world_pos.xy * task.device_pixel_scale + device_snap_offset * snapped_world_pos.w;
 
     // Apply offsets for the render task to get correct screen location.
     vec2 final_offset = -task.content_origin + task.common_data.task_rect.p0;
 
-    gl_Position = uTransform * vec4(device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w);
+    gl_Position = uTransform * vec4(snapped_device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w);
 
     VertexInfo vi = VertexInfo(
         local_pos,
-        snap_offset,
+        snapped_device_pos - device_pos,
         world_pos
     );
 
     return vi;
 }
 
 void main(void) {
     Instance instance = decode_instance_attributes();
@@ -181,18 +180,16 @@ void main(void) {
     int color_mode = (instance.flags >> 16) & 0xff;
     int resource_address = instance.user_data;
 
     PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address);
     Transform transform = fetch_transform(ph.transform_id);
     ClipArea clip_area = fetch_clip_area(instance.clip_address);
     PictureTask task = fetch_picture_task(instance.picture_task_address);
 
-    int raster_space = ph.user_data.w;
-
     TextRun text = fetch_text_run(ph.specific_prim_address);
     vec2 text_offset = vec2(ph.user_data.xy) / 256.0;
 
     if (color_mode == COLOR_MODE_FROM_PASS) {
         color_mode = uMode;
     }
 
     Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
@@ -206,21 +203,22 @@ void main(void) {
 
     // Compute the glyph rect in glyph space.
     RectWithSize glyph_rect = RectWithSize(res.offset + glyph_transform * (text_offset + glyph.offset),
                                            res.uv_rect.zw - res.uv_rect.xy);
 #else
     float raster_scale = float(ph.user_data.z) / 65535.0;
 
     // Scale from glyph space to local space.
-    float scale = res.scale / (raster_scale * task.device_pixel_scale);
+    float glyph_scale_inv = res.scale / (raster_scale * task.device_pixel_scale);
+    float glyph_scale = 1.0 / glyph_scale_inv;
 
     // Compute the glyph rect in local space.
-    RectWithSize glyph_rect = RectWithSize(scale * res.offset + text_offset + glyph.offset,
-                                           scale * (res.uv_rect.zw - res.uv_rect.xy));
+    RectWithSize glyph_rect = RectWithSize(glyph_scale_inv * res.offset + text_offset + glyph.offset,
+                                           glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy));
 #endif
 
     vec2 snap_bias;
     // In subpixel mode, the subpixel offset has already been
     // accounted for while rasterizing the glyph. However, we
     // must still round with a subpixel bias rather than rounding
     // to the nearest whole pixel, depending on subpixel direciton.
     switch (subpx_dir) {
@@ -240,24 +238,27 @@ void main(void) {
             break;
         case SUBPX_DIR_MIXED:
             snap_bias = vec2(0.125);
             break;
     }
 
     VertexInfo vi = write_text_vertex(ph.local_clip_rect,
                                       ph.z,
-                                      raster_space,
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+                                      glyph_transform,
+#else
+                                      glyph_scale,
+#endif
                                       transform,
                                       task,
                                       text_offset,
                                       glyph.offset,
                                       glyph_rect,
                                       snap_bias);
-    glyph_rect.p0 += vi.snap_offset;
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
     vUvClip = vec4(f, 1.0 - f);
 #else
     vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size;
 #endif
 
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, ClipMode, ExternalImageType, ImageRendering};
-use api::{YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF, RasterSpace};
+use api::{YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF};
 use api::units::*;
 use crate::clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItemKind, ClipStore};
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex, CoordinateSystemId};
 use crate::composite::{CompositeState, CompositeTile, CompositeTileSurface};
 use crate::glyph_rasterizer::GlyphFormat;
 use crate::gpu_cache::{GpuBlockData, GpuCache, GpuCacheHandle, GpuCacheAddress};
 use crate::gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use crate::gpu_types::{ClipMaskInstance, SplitCompositeInstance};
@@ -874,29 +874,25 @@ impl BatchBuilder {
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range];
-                let rasterization_space = match run.raster_space {
-                    RasterSpace::Screen => RasterizationSpace::Screen,
-                    RasterSpace::Local(..) => RasterizationSpace::Local,
-                };
                 let raster_scale = run.raster_space.local_scale().unwrap_or(1.0).max(0.001);
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     [
-                        (run.reference_frame_relative_offset.x * 256.0) as i32,
-                        (run.reference_frame_relative_offset.y * 256.0) as i32,
+                        (run.snapped_reference_frame_relative_offset.x * 256.0) as i32,
+                        (run.snapped_reference_frame_relative_offset.y * 256.0) as i32,
                         (raster_scale * 65535.0).round() as i32,
-                        (rasterization_space as i32),
+                        0,
                     ],
                 );
                 let base_instance = GlyphInstance::new(
                     prim_header_index,
                 );
                 let batchers = &mut self.batchers;
 
                 ctx.resource_cache.fetch_glyphs(
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -11,17 +11,17 @@ use api::units::*;
 use crate::border::{get_max_scale_for_border, build_border_instances};
 use crate::border::BorderSegmentCacheKey;
 use crate::clip::{ClipStore};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
 use crate::clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItemKind};
 use crate::debug_colors;
 use crate::debug_render::DebugItem;
 use crate::scene_building::{CreateShadow, IsVisible};
-use euclid::{SideOffsets2D, Transform3D, Rect, Scale, Size2D, Point2D};
+use euclid::{SideOffsets2D, Transform3D, Rect, Scale, Size2D, Point2D, Vector2D};
 use euclid::approxeq::ApproxEq;
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use crate::glyph_rasterizer::GlyphKey;
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use crate::gpu_types::{BrushFlags};
 use crate::image::{Repetition};
 use crate::intern;
@@ -47,17 +47,17 @@ use crate::renderer::{MAX_VERTEX_TEXTURE
 use crate::resource_cache::{ImageProperties, ImageRequest};
 use crate::scene::SceneProperties;
 use crate::segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use crate::storage;
 use crate::texture_cache::TEXTURE_REGION_DIMENSIONS;
-use crate::util::{MatrixHelpers, MaxRect, Recycler, ScaleOffset, RectHelpers};
+use crate::util::{MatrixHelpers, MaxRect, Recycler, ScaleOffset, RectHelpers, VectorHelpers};
 use crate::util::{clamp_to_scale_factor, pack_as_float, project_rect, raster_rect_to_device_pixels};
 use crate::internal_types::{LayoutPrimitiveInfo, Filter};
 use smallvec::SmallVec;
 
 pub mod backdrop;
 pub mod borders;
 pub mod gradient;
 pub mod image;
@@ -200,16 +200,27 @@ impl SpaceSnapper {
             Some(ref scale_offset) => {
                 let snapped_device_rect : DeviceRect = scale_offset.map_rect(rect).snap();
                 scale_offset.unmap_rect(&snapped_device_rect)
             }
             None => *rect,
         }
     }
 
+    pub fn snap_vector<F>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, F> where F: fmt::Debug {
+        debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID);
+        match self.snapping_transform {
+            Some(ref scale_offset) => {
+                let snapped_device_vector : DeviceVector2D = scale_offset.map_vector(&vector).snap();
+                scale_offset.unmap_vector(&snapped_device_vector)
+            }
+            None => *vector,
+        }
+    }
+
     pub fn snap_size<F>(&self, size: &Size2D<f32, F>) -> Size2D<f32, F> where F: fmt::Debug {
         debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID);
         match self.snapping_transform {
             Some(ref scale_offset) => {
                 let rect = Rect::<f32, F>::new(Point2D::<f32, F>::zero(), *size);
                 let snapped_device_rect : DeviceRect = scale_offset.map_rect(&rect).snap();
                 scale_offset.unmap_rect(&snapped_device_rect).size
             }
@@ -2906,21 +2917,23 @@ impl PrimitiveStore {
                 let surface = &frame_state.surfaces[pic_context.surface_index.0];
 
                 run.request_resources(
                     prim_offset,
                     &prim_data.font,
                     &prim_data.glyphs,
                     &transform.to_transform().with_destination::<_>(),
                     surface,
+                    prim_spatial_node_index,
                     raster_space,
                     pic_context.subpixel_mode,
                     frame_state.resource_cache,
                     frame_state.gpu_cache,
                     frame_state.render_tasks,
+                    frame_context.clip_scroll_tree,
                     scratch,
                 );
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
             }
             PrimitiveInstanceKind::Clear { data_handle, .. } => {
--- a/gfx/wr/webrender/src/prim_store/text_run.rs
+++ b/gfx/wr/webrender/src/prim_store/text_run.rs
@@ -1,28 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, GlyphInstance, RasterSpace, Shadow};
-use api::units::{DevicePixelScale, LayoutToWorldTransform, LayoutVector2D};
+use api::units::{LayoutToWorldTransform, LayoutVector2D};
 use crate::scene_building::{CreateShadow, IsVisible};
 use crate::frame_builder::FrameBuildingState;
 use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use crate::gpu_cache::GpuCache;
 use crate::intern;
 use crate::internal_types::LayoutPrimitiveInfo;
 use crate::picture::{SubpixelMode, SurfaceInfo};
 use crate::prim_store::{PrimitiveOpacity, PrimitiveSceneData,  PrimitiveScratchBuffer};
 use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData};
 use crate::render_task_graph::RenderTaskGraph;
 use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use crate::resource_cache::{ResourceCache};
 use crate::util::{MatrixHelpers};
-use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind};
+use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind, SpaceSnapper};
+use crate::clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use std::ops;
 use std::sync::Arc;
 use crate::storage;
 use crate::util::PrimaryArc;
 
 /// A run of glyphs, with associated font information.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -169,16 +170,17 @@ impl InternablePrimitive for TextRun {
         data_handle: TextRunDataHandle,
         prim_store: &mut PrimitiveStore,
         reference_frame_relative_offset: LayoutVector2D,
     ) -> PrimitiveInstanceKind {
         let run_index = prim_store.text_runs.push(TextRunPrimitive {
             used_font: key.font.clone(),
             glyph_keys_range: storage::Range::empty(),
             reference_frame_relative_offset,
+            snapped_reference_frame_relative_offset: reference_frame_relative_offset,
             shadow: key.shadow,
             raster_space: RasterSpace::Screen,
         });
 
         PrimitiveInstanceKind::TextRun{ data_handle, run_index }
     }
 }
 
@@ -207,40 +209,43 @@ impl IsVisible for TextRun {
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct TextRunPrimitive {
     pub used_font: FontInstance,
     pub glyph_keys_range: storage::Range<GlyphKey>,
     pub reference_frame_relative_offset: LayoutVector2D,
+    pub snapped_reference_frame_relative_offset: LayoutVector2D,
     pub shadow: bool,
     pub raster_space: RasterSpace,
 }
 
 impl TextRunPrimitive {
     pub fn update_font_instance(
         &mut self,
         specified_font: &FontInstance,
-        device_pixel_scale: DevicePixelScale,
+        surface: &SurfaceInfo,
+        spatial_node_index: SpatialNodeIndex,
         transform: &LayoutToWorldTransform,
         subpixel_mode: SubpixelMode,
         raster_space: RasterSpace,
+        clip_scroll_tree: &ClipScrollTree,
     ) -> bool {
         // If local raster space is specified, include that in the scale
         // of the glyphs that get rasterized.
         // TODO(gw): Once we support proper local space raster modes, this
         //           will implicitly be part of the device pixel ratio for
         //           the (cached) local space surface, and so this code
         //           will no longer be required.
         let mut raster_space = raster_space;
         let raster_scale = raster_space.local_scale().unwrap_or(1.0).max(0.001);
 
         // Get the current font size in device pixels
-        let mut device_font_size = specified_font.size.scale_by(device_pixel_scale.0 * raster_scale);
+        let mut device_font_size = specified_font.size.scale_by(surface.device_pixel_scale.0 * raster_scale);
 
         // Determine if rasterizing glyphs in local or screen space.
         let transform_glyphs = if raster_space != RasterSpace::Screen {
             // Ensure the font is supposed to be rasterized in screen-space.
             false
         } else if transform.has_perspective_component() || !transform.has_2d_inverse() {
             // Only support transforms that can be coerced to simple 2D transforms.
             false
@@ -261,16 +266,40 @@ impl TextRunPrimitive {
             FontTransform::from(transform)
         } else {
             FontTransform::identity()
         };
 
         // Record the raster space the text needs to be snapped in.
         self.raster_space = raster_space;
 
+        // TODO(aosmond): Snapping really ought to happen during scene building
+        // as much as possible. This will allow clips to be already adjusted
+        // based on the snapping requirements of the primitive. This may affect
+        // complex clips that create a different task, and when we rasterize
+        // glyphs without the transform (because the shader doesn't have the
+        // snap offsets to adjust its clip). These rects are fairly conservative
+        // to begin with and do not appear to be causing significant issues at
+        // this time.
+        self.snapped_reference_frame_relative_offset = if !font_transform.is_identity() {
+            // Don't touch the reference frame relative offset. We'll let the
+            // shader do the snapping in device pixels.
+            self.reference_frame_relative_offset
+        } else {
+            // There may be an animation, so snap the reference frame relative
+            // offset such that it excludes the impact, if any.
+            let snap_to_device = SpaceSnapper::new_with_target(
+                surface.raster_spatial_node_index,
+                spatial_node_index,
+                surface.device_pixel_scale,
+                clip_scroll_tree,
+            );
+            snap_to_device.snap_vector(&self.reference_frame_relative_offset)
+        };
+
         // If the transform or device size is different, then the caller of
         // this method needs to know to rebuild the glyphs.
         let cache_dirty =
             self.used_font.transform != font_transform ||
             self.used_font.size != device_font_size;
 
         // Construct used font instance from the specified font instance
         self.used_font = FontInstance {
@@ -294,41 +323,43 @@ impl TextRunPrimitive {
 
     pub fn request_resources(
         &mut self,
         prim_offset: LayoutVector2D,
         specified_font: &FontInstance,
         glyphs: &[GlyphInstance],
         transform: &LayoutToWorldTransform,
         surface: &SurfaceInfo,
+        spatial_node_index: SpatialNodeIndex,
         raster_space: RasterSpace,
         subpixel_mode: SubpixelMode,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskGraph,
+        clip_scroll_tree: &ClipScrollTree,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
-        let device_pixel_scale = surface.device_pixel_scale;
-
         let cache_dirty = self.update_font_instance(
             specified_font,
-            device_pixel_scale,
+            surface,
+            spatial_node_index,
             transform,
             subpixel_mode,
             raster_space,
+            clip_scroll_tree,
         );
 
         if self.glyph_keys_range.is_empty() || cache_dirty {
             let subpx_dir = self.used_font.get_subpx_dir();
 
             self.glyph_keys_range = scratch.glyph_keys.extend(
                 glyphs.iter().map(|src| {
                     let src_point = src.point + prim_offset;
                     let world_offset = self.used_font.transform.transform(&src_point);
-                    let device_offset = device_pixel_scale.transform_point(world_offset);
+                    let device_offset = surface.device_pixel_scale.transform_point(world_offset);
                     GlyphKey::new(src.index, device_offset, subpx_dir)
                 }));
         }
 
         resource_cache.request_glyphs(
             self.used_font.clone(),
             &scratch.glyph_keys[self.glyph_keys_range],
             gpu_cache,
@@ -346,10 +377,10 @@ fn test_struct_sizes() {
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<TextRun>(), 56, "TextRun size changed");
     assert_eq!(mem::size_of::<TextRunTemplate>(), 72, "TextRunTemplate size changed");
     assert_eq!(mem::size_of::<TextRunKey>(), 64, "TextRunKey size changed");
-    assert_eq!(mem::size_of::<TextRunPrimitive>(), 72, "TextRunPrimitive size changed");
+    assert_eq!(mem::size_of::<TextRunPrimitive>(), 80, "TextRunPrimitive size changed");
 }
--- a/gfx/wr/webrender/src/scene_building.rs
+++ b/gfx/wr/webrender/src/scene_building.rs
@@ -1109,16 +1109,22 @@ impl<'a> SceneBuilder<'a> {
                     info.yuv_data,
                     info.color_depth,
                     info.color_space,
                     info.color_range,
                     info.image_rendering,
                 );
             }
             DisplayItem::Text(ref info) => {
+                // TODO(aosmond): Snapping text primitives does not make much sense, given the
+                // primitive bounds and clip are supposed to be conservative, not definitive.
+                // E.g. they should be able to grow and not impact the output. However there
+                // are subtle interactions between the primitive origin and the glyph offset
+                // which appear to be significant (presumably due to some sort of accumulated
+                // error throughout the layers). We should fix this at some point.
                 let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
                     &info.common,
                     &info.bounds,
                     apply_pipeline_clip,
                 );
 
                 self.add_text(
                     clip_and_scroll,
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -241,16 +241,30 @@ impl ScaleOffset {
             ),
             Size2D::new(
                 rect.size.width / self.scale.x,
                 rect.size.height / self.scale.y,
             )
         )
     }
 
+    pub fn map_vector<F, T>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, T> {
+        Vector2D::new(
+            vector.x * self.scale.x + self.offset.x,
+            vector.y * self.scale.y + self.offset.y,
+        )
+    }
+
+    pub fn unmap_vector<F, T>(&self, vector: &Vector2D<f32, F>) -> Vector2D<f32, T> {
+        Vector2D::new(
+            (vector.x - self.offset.x) / self.scale.x,
+            (vector.y - self.offset.y) / self.scale.y,
+        )
+    }
+
     pub fn to_transform<F, T>(&self) -> Transform3D<f32, F, T> {
         Transform3D::row_major(
             self.scale.x,
             0.0,
             0.0,
             0.0,
 
             0.0,
--- a/gfx/wr/wrench/reftests/text/reftest.list
+++ b/gfx/wr/wrench/reftests/text/reftest.list
@@ -60,15 +60,15 @@ fuzzy(1,1) platform(linux) == two-shadow
 == shadow-clip.yaml shadow-clip-ref.yaml
 == shadow-fast-clip.yaml shadow-fast-clip-ref.yaml
 skip_on(android,device) == shadow-partial-glyph.yaml shadow-partial-glyph-ref.yaml  # Fails on Pixel2
 fuzzy(1,107) platform(linux) == shadow-transforms.yaml shadow-transforms.png
 fuzzy(1,113) platform(linux) == raster-space.yaml raster-space.png
 skip_on(android) skip_on(mac,>=10.14) != allow-subpixel.yaml allow-subpixel-ref.yaml  # Android: we don't enable sub-px aa on this platform.
 skip_on(android,device) == bg-color.yaml bg-color-ref.yaml  # Fails on Pixel2
 != large-glyphs.yaml blank.yaml
-== snap-text-offset.yaml snap-text-offset-ref.yaml
+skip_on(android,device) == snap-text-offset.yaml snap-text-offset-ref.yaml
 fuzzy(5,4435) == shadow-border.yaml shadow-solid-ref.yaml
 fuzzy(5,4435) == shadow-image.yaml shadow-solid-ref.yaml
 options(disable-aa) == snap-clip.yaml snap-clip-ref.yaml
 platform(linux) == perspective-clip.yaml perspective-clip.png
 fuzzy(1,39) options(disable-subpixel) == raster-space-snap.yaml raster-space-snap-ref.yaml
 # == intermediate-transform.yaml intermediate-transform-ref.yaml # fails because of AA inavailable with an intermediate surface
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -93,18 +93,18 @@ fuzzy-if(webrender,0-52,0-14) random-if(
 == 489517-1.html 489517-1-ref.html
 == 489887-1.html 489887-1-ref.html
 == 492231-1.html 492231-1-ref.html
 == 496006-1.html 496006-1-ref.html
 == 503269-1.html 503269-1-ref.html
 == 503957-1.html 503957-1-ref.html
 == 525740-1.html 525740-1-ref.html
 == 536963-1.html 536963-1-ref.html
-fuzzy-if(webrender,0-122,0-1) == 562169-1.html 562169-1-ref.html
-fuzzy-if(webrender,0-122,0-1) == 562169-1a.html 562169-1-ref.html
+fuzzy-if(webrender,0-137,0-1) == 562169-1.html 562169-1-ref.html
+fuzzy-if(webrender,0-137,0-1) == 562169-1a.html 562169-1-ref.html
 == 562169-2.html 562169-2-ref.html
 == 562169-2a.html 562169-2-ref.html
 == 562169-3.html 562169-3-ref.html
 == 562169-3a.html 562169-3-ref.html
 == 562169-4.html 562169-4-ref.html
 == 588739-1.html 588739-ref.html
 == 588739-2.html 588739-ref.html
 == 588739-3.html 588739-ref.html
@@ -153,17 +153,17 @@ random-if(/^Windows\x20NT\x206\.1/.test(
 != chrome://reftest/content/bidi/1155359-1.xul chrome://reftest/content/bidi/1155359-1-ref.xul
 == 1157726-1.html 1157726-1-ref.html
 == 1161752.html 1161752-ref.html
 == 1161752-5-embed.html 1161752-5-embed-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1a-ltr.html brackets-1a-ltr-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1a-rtl.html brackets-1a-rtl-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1b-ltr.html brackets-1b-ltr-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1b-rtl.html brackets-1b-rtl-ref.html # Bug 1392106
-random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1c-ltr.html brackets-1c-ltr-ref.html # Bug 1392106
+fuzzy-if(geckoview&&webrender,22-22,44-46) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1c-ltr.html brackets-1c-ltr-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-1c-rtl.html brackets-1c-rtl-ref.html # Bug 1392106
 fuzzy-if(Android,0-1,0-6) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-2a-ltr.html brackets-2a-ltr-ref.html # Bug 1392106
 fuzzy-if(cocoaWidget,0-1,0-2) fuzzy-if(Android,0-254,0-557) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-2a-rtl.html brackets-2a-rtl-ref.html # Bug 1392106
 fuzzy-if(Android,0-1,0-8) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-2b-ltr.html brackets-2b-ltr-ref.html # Bug 1392106
 fuzzy-if(cocoaWidget,0-1,0-2) fuzzy-if(Android,0-1,0-6) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-2b-rtl.html brackets-2b-rtl-ref.html # Bug 1392106
 fuzzy-if(cocoaWidget,0-1,0-2) fuzzy-if(Android,0-1,0-6) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-2c-ltr.html brackets-2c-ltr-ref.html # Bug 1392106
 fuzzy-if(Android,0-254,0-231) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-2c-rtl.html brackets-2c-rtl-ref.html # Bug 1392106
 fuzzy-if(cocoaWidget,0-1,0-3) fuzzy-if(Android,0-1,0-8) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == brackets-3a-ltr.html brackets-3a-ltr-ref.html # Bug 1392106
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1784,17 +1784,17 @@ fuzzy-if(Android,0-1,0-1) fuzzy-if(skiaC
 fuzzy-if(skiaContent,0-1,0-5) == 956513-1.svg 956513-1-ref.svg
 == 944291-1.html 944291-1-ref.html
 == 950436-1.html 950436-1-ref.html
 == 957770-1.svg 957770-1-ref.svg
 == 960277-1.html 960277-1-ref.html
 fuzzy-if(skiaContent,0-1,0-80) == 961887-1.html 961887-1-ref.html
 == 961887-2.html 961887-2-ref.html
 == 961887-3.html 961887-3-ref.html
-pref(layout.css.overflow-clip-box.enabled,true) fuzzy(0-50,0-145) fuzzy-if(asyncPan&&!layersGPUAccelerated,0-102,0-3712) fuzzy-if(webrender,0-255,0-51) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 966992-1.html 966992-1-ref.html # Bug 1392106
+pref(layout.css.overflow-clip-box.enabled,true) fuzzy(0-50,0-145) fuzzy-if(asyncPan&&!layersGPUAccelerated,0-102,0-3712) fuzzy-if(webrender,0-255,0-180) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 966992-1.html 966992-1-ref.html # Bug 1392106
 == 966510-1.html 966510-1-ref.html
 == 966510-2.html 966510-2-ref.html
 fuzzy-if(skiaContent,0-1,0-123) == 978911-1.svg 978911-1-ref.svg
 == 983084-1.html 983084-1-ref.html
 == 983084-2.html 983084-2-ref.html
 == 983084-3.html 983084-1-ref.html
 == 983691-1.html 983691-ref.html
 == 983985-1.html 983985-1-ref.html
@@ -2076,14 +2076,14 @@ fuzzy-if(!webrender,0-6,0-34) fuzzy-if(A
 skip-if(!asyncPan) == 1544895.html 1544895-ref.html
 == 1547759-1.html 1547759-1-ref.html
 == 1548809.html 1548809-ref.html
 != 1552789-1.html 1552789-ref-1.html
 pref(image.downscale-during-decode.enabled,true) == 1553571-1.html 1553571-1-ref.html
 == 1558937-1.html 1558937-1-ref.html
 != 1563484.html 1563484-notref.html
 == 1563484.html 1563484-ref.html
-fuzzy-if(!webrender||!winWidget,251-255,464-1613) fuzzy-if(geckoview&&webrender,251-253,1392-1401) == 1562733-rotated-nastaliq-1.html 1562733-rotated-nastaliq-1-ref.html
+fuzzy-if(!webrender||!winWidget,251-255,464-1613) fuzzy-if(webrender&&winWidget,255-255,6-6) fuzzy-if(geckoview&&webrender,251-255,1392-1401) == 1562733-rotated-nastaliq-1.html 1562733-rotated-nastaliq-1-ref.html
 fuzzy-if(winWidget&&webrender,0-31,0-3) fuzzy-if(geckoview&&webrender,0-93,0-87) == 1562733-rotated-nastaliq-2.html 1562733-rotated-nastaliq-2-ref.html
 test-pref(plain_text.wrap_long_lines,false) != 1565129.txt 1565129.txt
 fuzzy(0-32,0-8) fuzzy-if(Android&&webrender,0-32,0-1458) == 1576553-1.html 1576553-1-ref.html
 fuzzy(0-1,0-10000) == 1577566-1.html 1577566-1-ref.html
 == 1579953-2.html 1579953-2-ref.html
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -283,12 +283,12 @@ pref(privacy.reduceTimerPrecision,false)
 pref(privacy.reduceTimerPrecision,false) == anim-display-in-g-element.svg lime.svg
 
 # Test animation that change 'display' style value to 'none'
 == anim-change-display-none-for-ancestor-elem.html lime.html
 == anim-change-display-none-for-target-elem.html lime.html
 == anim-change-display-none-for-dynamically-appended-elem.html lime.html
 == anim-change-display-block-for-dynamically-appended-elem.html anim-standard-ref.html
 
-fuzzy(0-63,0-146) fuzzy-if(skiaContent,0-31,0-308) fuzzy-if(webrender,0-7,0-240) == anim-clipPath-viewBox.svg anim-clipPath-viewBox-ref.svg
+fuzzy(0-63,0-146) fuzzy-if(skiaContent,0-31,0-308) fuzzy-if(webrender,0-7,0-241) == anim-clipPath-viewBox.svg anim-clipPath-viewBox-ref.svg
 
 # Test animations for overflow.
 == anim-overflow-shorthand.svg anim-overflow-shorthand-ref.svg
--- a/layout/reftests/svg/svg-integration/clip-path/reftest.list
+++ b/layout/reftests/svg/svg-integration/clip-path/reftest.list
@@ -23,24 +23,24 @@ fuzzy-if(webrender,35-70,699-715) == cli
 fuzzy-if(webrender,35-70,699-715) == clip-path-circle-005.html clip-path-circle-002-ref.html
 fuzzy-if(webrender,35-70,699-715) == clip-path-circle-006.html clip-path-circle-001-ref.html
 fuzzy-if(webrender,35-70,699-715) == clip-path-circle-007.html clip-path-circle-002-ref.html
 fuzzy-if(webrender,35-70,699-715) == clip-path-circle-008.html clip-path-circle-002-ref.html
 fuzzy-if(webrender,35-70,699-715) == clip-path-circle-009.html clip-path-circle-003-ref.html
 fuzzy-if(webrender,35-70,699-735) == clip-path-circle-010.html clip-path-circle-004-ref.html
 fuzzy-if(webrender,35-70,699-725) == clip-path-circle-011.html clip-path-circle-005-ref.html
 fuzzy-if(webrender,35-70,699-715) == clip-path-circle-012.html clip-path-circle-006-ref.html
-fuzzy-if(webrender,35-70,699-715) == clip-path-circle-013.html clip-path-circle-002-ref.html
+fuzzy-if(webrender,35-70,699-733) == clip-path-circle-013.html clip-path-circle-002-ref.html
 fuzzy-if(webrender,34-70,699-885) == clip-path-circle-014.html clip-path-circle-007-ref.html
 fuzzy-if(webrender,34-70,699-905) == clip-path-circle-015.html clip-path-circle-008-ref.html
 fuzzy-if(webrender,34-70,699-840) == clip-path-circle-016.html clip-path-circle-009-ref.html
-fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-16,0-9) fuzzy-if(webrender,34-70,699-885) == clip-path-circle-017.html clip-path-circle-007-ref.html
+fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-16,0-9) fuzzy-if(webrender,34-70,699-893) == clip-path-circle-017.html clip-path-circle-007-ref.html
 fuzzy-if(webrender,35-70,699-725) == clip-path-circle-018.html clip-path-circle-010-ref.html
-fuzzy-if(webrender,35-70,699-715) == clip-path-circle-019.html clip-path-circle-002-ref.html
-fuzzy-if(webrender,35-70,699-715) == clip-path-circle-020.html clip-path-circle-002-ref.html
+fuzzy-if(webrender,35-70,699-733) == clip-path-circle-019.html clip-path-circle-002-ref.html
+fuzzy-if(webrender,35-70,699-733) == clip-path-circle-020.html clip-path-circle-002-ref.html
 fuzzy-if(webrender&&(winWidget||cocoaWidget),0-1,0-5) == clip-path-circle-021.html clip-path-circle-021-ref.html
 
 fuzzy-if(webrender,36-36,1099-1100) == clip-path-ellipse-001.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,36-36,1099-1100) == clip-path-ellipse-002.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,36-36,1099-1100) == clip-path-ellipse-003.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,36-36,1099-1100) == clip-path-ellipse-004.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,36-36,1099-1100) == clip-path-ellipse-005.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,36-36,1099-1100) == clip-path-ellipse-006.html clip-path-ellipse-001-ref.html
--- a/layout/reftests/text-overflow/reftest.list
+++ b/layout/reftests/text-overflow/reftest.list
@@ -1,16 +1,16 @@
 == ellipsis-font-fallback.html ellipsis-font-fallback-ref.html
 == line-clipping.html line-clipping-ref.html
 fuzzy-if(Android,0-16,0-244) fuzzy-if(webrender,0-47,0-6) == marker-basic.html marker-basic-ref.html  # Bug 1128229
 == marker-string.html marker-string-ref.html
 fuzzy-if(webrender,0-47,0-18) == bidi-simple.html bidi-simple-ref.html
 skip-if(!gtkWidget) fuzzy-if(gtkWidget,0-124,0-289) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
 fuzzy-if(Android,0-24,0-4000) fuzzy-if(cocoaWidget,0-1,0-40) fuzzy-if(asyncPan&&!layersGPUAccelerated,0-149,0-1836) == scroll-rounding.html scroll-rounding-ref.html # bug 760264
-fuzzy(0-16,0-454) fails-if(gtkWidget) fuzzy-if(webrender&&winWidget,50-85,454-499) fuzzy-if(webrender&&OSX,7-7,143-143) skip-if(OSX&&!isDebugBuild&&verify) == anonymous-block.html anonymous-block-ref.html # gtkWidget:bug 1309103, fuzzy: subpixel aa
+fuzzy(0-16,0-454) fails-if(gtkWidget) fuzzy-if(webrender&&winWidget,50-85,454-499) fuzzy-if(webrender&&OSX,15-15,145-145) skip-if(OSX&&!isDebugBuild&&verify) == anonymous-block.html anonymous-block-ref.html # gtkWidget:bug 1309103, fuzzy: subpixel aa
 fuzzy-if(webrender,0-47,0-3) == false-marker-overlap.html false-marker-overlap-ref.html
 == visibility-hidden.html visibility-hidden-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,0-102,0-1724) fuzzy-if(gtkWidget,0-10,0-8) fuzzy-if(webrender,0-47,0-24) == block-padding.html block-padding-ref.html
 fuzzy-if(webrender,0-3,0-825) == quirks-decorations.html quirks-decorations-ref.html
 == quirks-line-height.html quirks-line-height-ref.html
 == standards-decorations.html standards-decorations-ref.html
 == standards-line-height.html standards-line-height-ref.html
 fuzzy-if(skiaContent,0-1,0-4200) fuzzy-if(webrender,0-47,0-6) == selection.html selection-ref.html
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -181,17 +181,17 @@ random-if(!winWidget) == arial-bold-lam-
 == 726392-3.html 726392-3-ref.html
 == 745555-1.html 745555-1-ref.html
 == 745555-2.html 745555-2-ref.html
 == 820255.html 820255-ref.html
 != 1170688.html 1170688-ref.html
 fails-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 1320665-cmap-format-13.html 1320665-cmap-format-13-ref.html # see bug 1320665 comments 8-9
 == 1331339-script-extensions-shaping-1.html 1331339-script-extensions-shaping-1-ref.html
 skip-if(!cocoaWidget) != 1349308-1.html 1349308-notref.html # macOS-specific test for -apple-system glyph metrics
-fuzzy-if(Android,0-128,0-230) fails-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 1463020-letter-spacing-text-transform-1.html 1463020-letter-spacing-text-transform-1-ref.html # Win10: regional indicators not supported by system emoji font
+fuzzy-if(Android,0-128,0-233) fails-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 1463020-letter-spacing-text-transform-1.html 1463020-letter-spacing-text-transform-1-ref.html # Win10: regional indicators not supported by system emoji font
 fails-if(Android) == 1463020-letter-spacing-text-transform-2.html 1463020-letter-spacing-text-transform-2-ref.html # missing font coverage on Android
 == 1507661-spurious-hyphenation-after-explicit.html 1507661-spurious-hyphenation-after-explicit-ref.html
 fuzzy-if(!webrender,12-66,288-1660) fails-if(gtkWidget&&!webrender) == 1522857-1.html 1522857-1-ref.html # antialiasing fuzz in non-webrender cases
 
 # ensure emoji chars don't render blank (bug 715798, bug 779042);
 # should at least render hexboxes if there's no font support
 random-if(geckoview&&webrender) != emoji-01.html emoji-01-notref.html
 != emoji-02.html emoji-02-notref.html
--- a/layout/reftests/w3c-css/submitted/counter-styles-3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/counter-styles-3/reftest.list
@@ -11,17 +11,17 @@ fails-if(webrender&&cocoaWidget) == syst
 == system-alphabetic-invalid.html system-common-invalid2-ref.html
 == system-numeric-invalid.html    system-common-invalid2-ref.html
 == system-additive-invalid.html   system-common-invalid-ref.html
 == system-extends-invalid.html    system-extends-invalid-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == descriptor-negative.html descriptor-negative-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == descriptor-prefix.html   descriptor-prefix-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == descriptor-suffix.html   descriptor-suffix-ref.html # Bug 1392106
 == descriptor-range.html    descriptor-range-ref.html
-fuzzy-if(webrender,0-22,0-3) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == descriptor-pad.html descriptor-pad-ref.html # Bug 1392106
+fuzzy-if(webrender,0-92,0-3) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == descriptor-pad.html descriptor-pad-ref.html # Bug 1392106
 == descriptor-fallback.html descriptor-fallback-ref.html
 == descriptor-symbols.html  descriptor-symbols-ref.html
 == descriptor-negative-invalid.html descriptor-negative-invalid-ref.html
 == descriptor-prefix-invalid.html   descriptor-prefix-invalid-ref.html
 fuzzy-if(webrender,0-15,0-8) == descriptor-suffix-invalid.html   descriptor-suffix-invalid-ref.html
 == descriptor-range-invalid.html    descriptor-range-invalid-ref.html
 == descriptor-pad-invalid.html      descriptor-pad-invalid-ref.html
 == descriptor-fallback-invalid.html descriptor-fallback-invalid-ref.html
--- a/layout/reftests/w3c-css/submitted/selectors4/reftest.list
+++ b/layout/reftests/w3c-css/submitted/selectors4/reftest.list
@@ -1,12 +1,12 @@
 needs-focus == focus-within-1.html focus-within-1-ref.html
 needs-focus == focus-within-2.html focus-within-2-ref.html
 needs-focus == focus-within-3.html focus-within-3-ref.html
-fuzzy-if(webrender,0-122,0-1) == dir-style-01a.html dir-style-01-ref.html
-fuzzy-if(webrender,0-122,0-1) == dir-style-01b.html dir-style-01-ref.html
+fuzzy-if(webrender,0-137,0-1) == dir-style-01a.html dir-style-01-ref.html
+fuzzy-if(webrender,0-137,0-1) == dir-style-01b.html dir-style-01-ref.html
 == dir-style-02a.html dir-style-02-ref.html
 == dir-style-02b.html dir-style-02-ref.html
 == dir-style-03a.html dir-style-03-ref.html
 == dir-style-03b.html dir-style-03-ref.html
 == dir-style-04.html dir-style-04-ref.html
 == child-index-no-parent-01.html child-index-no-parent-01-ref.html
 == class-id-attr-selector-invalidation-01.html class-id-attr-selector-invalidation-01-ref.html
--- a/layout/reftests/webkit-box/reftest.list
+++ b/layout/reftests/webkit-box/reftest.list
@@ -5,17 +5,17 @@
 == webkit-box-abspos-children-1.html webkit-box-abspos-children-1-ref.html
 
 # Tests for anonymous flex item formation inside of a "-webkit-box":
 # Note: some of these tests are marked as failing, because we don't match
 # WebKit/Blink on them.  (The reference case represents the WebKit/Blink
 # rendering.) We could probably make them pass by implementing some quirks, if
 # it turns out that the web depends on WebKit/Blink's behavior in these cases.
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == webkit-box-anon-flex-items-1a.html webkit-box-anon-flex-items-1-ref.html # Bug 1392106
-random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == webkit-box-anon-flex-items-1b.html webkit-box-anon-flex-items-1-ref.html # Bug 1392106
+random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fuzzy-if(geckoview&&webrender,150-150,7-7) == webkit-box-anon-flex-items-1b.html webkit-box-anon-flex-items-1-ref.html # Bug 1392106
 fails == webkit-box-anon-flex-items-2.html webkit-box-anon-flex-items-2-ref.html
 fails == webkit-box-anon-flex-items-3.html webkit-box-anon-flex-items-3-ref.html
 
 # Tests for "-webkit-box" & "-webkit-inline-box" as display values:
 == webkit-display-values-1.html webkit-display-values-1-ref.html
 
 # Tests for "-webkit-box-align" (cross-axis alignment):
 == webkit-box-align-horiz-1a.html webkit-box-align-horiz-1-ref.html
--- a/layout/reftests/writing-mode/reftest.list
+++ b/layout/reftests/writing-mode/reftest.list
@@ -26,17 +26,17 @@ random-if(/^Windows\x20NT\x206\.1/.test(
 == 1096224-1b.html 1096224-1-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 1102175-1a.html 1102175-1-ref.html # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 1102175-1b.html 1102175-1-ref.html # Bug 1392106
 == 1103613-1.html 1103613-1-ref.html
 == 1105268-1-min-max-dimensions.html 1105268-1-min-max-dimensions-ref.html
 == 1105268-2-min-max-dimensions.html 1105268-2-min-max-dimensions-ref.html
 == 1106669-1-intrinsic-for-container.html 1106669-1-intrinsic-for-container-ref.html
 == 1108923-1-percentage-margins.html 1108923-1-percentage-margins-ref.html
-fuzzy-if(Android,0-128,0-78) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 1111944-1-list-marker.html 1111944-1-list-marker-ref.html # Bug 1392106
+fuzzy-if(Android,0-128,0-94) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 1111944-1-list-marker.html 1111944-1-list-marker-ref.html # Bug 1392106
 fuzzy(0-116,0-94) fuzzy-if(winWidget,0-135,0-124) == 1115916-1-vertical-metrics.html 1115916-1-vertical-metrics-ref.html
 == 1117210-1-vertical-baseline-snap.html 1117210-1-vertical-baseline-snap-ref.html
 == 1117227-1-text-overflow.html 1117227-1-text-overflow-ref.html
 == 1122366-1-margin-collapse.html 1122366-1-margin-collapse-ref.html
 == 1124636-1-fieldset-max-height.html 1124636-1-fieldset-max-height-ref.html
 == 1124636-2-fieldset-min-height.html 1124636-2-fieldset-min-height-ref.html
 
 == ua-style-sheet-margin-1.html ua-style-sheet-margin-1-ref.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-lists/list-style-type-string-002.html.ini
@@ -0,0 +1,3 @@
+[list-style-type-string-002.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=137;totalPixels=2
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-overflow/webkit-line-clamp-024.html.ini
@@ -0,0 +1,3 @@
+[webkit-line-clamp-024.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=47;totalPixels=1
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-overflow/webkit-line-clamp-027.html.ini
@@ -0,0 +1,3 @@
+[webkit-line-clamp-027.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=47;totalPixels=1
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-overflow/webkit-line-clamp-029.html.ini
@@ -0,0 +1,3 @@
+[webkit-line-clamp-029.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=47;totalPixels=1
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-overflow/webkit-line-clamp-030.html.ini
@@ -0,0 +1,3 @@
+[webkit-line-clamp-030.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=47;totalPixels=1
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-ui/text-overflow-026.html.ini
@@ -0,0 +1,3 @@
+[text-overflow-026.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=47;totalPixels=3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad.html.ini
@@ -0,0 +1,3 @@
+[descriptor-pad.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=92;totalPixels=3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-01a.html.ini
@@ -0,0 +1,3 @@
+[dir-style-01a.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=137;totalPixels=1
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-01b.html.ini
@@ -0,0 +1,3 @@
+[dir-style-01b.html]
+  fuzzy:
+    if webrender and (os == "win"): maxDifference=137;totalPixels=1