Bug 1540200 - Part 2. Calculate snapped primitive rect and offsets for WebRender on the CPU. r=kvark
authorAndrew Osmond <aosmond@mozilla.com>
Wed, 10 Apr 2019 14:20:34 -0400
changeset 531547 eefb300b7640986be7d1a4a23c133c4ebe73261d
parent 531546 403ea69a03b06c297aad8938b40feffee4f5a5bc
child 531548 34510ca46cc61984ca775f30a7c4f288371cf329
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1540200
milestone68.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 1540200 - Part 2. Calculate snapped primitive rect and offsets for WebRender on the CPU. r=kvark We currently do most snapping on the GPU in the shader. However the picture's local rect needs to take into account the snapping done there, so we need to calculate this earlier in the pipeline. Instead of using the clipped primitive local rects to create the picture's own local rect, we now snap the child local rects first. If no snapping is required, there should be no functional change. If snapping is required, there should be fewer visual distortions caused by an inaccurate picture local rect. Differential Revision: https://phabricator.services.mozilla.com/D28882
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/wrench/reftests/filters/filter-drop-shadow-clip.png
gfx/wr/wrench/reftests/transforms/nested-preserve-3d.png
gfx/wr/wrench/reftests/transforms/nested-rotate-x.png
testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -12,17 +12,17 @@ use border::{get_max_scale_for_border, b
 use border::BorderSegmentCacheKey;
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::{ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, VisibleFace};
 use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use debug_colors;
 use debug_render::DebugItem;
 use display_list_flattener::{CreateShadow, IsVisible};
-use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
+use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D, TypedPoint2D};
 use euclid::approxeq::ApproxEq;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::{BrushFlags, SnapOffsets};
 use image::{Repetition};
 use intern;
@@ -45,17 +45,17 @@ use renderer::{MAX_VERTEX_TEXTURE_WIDTH}
 use resource_cache::{ImageProperties, ImageRequest};
 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 texture_cache::TEXTURE_REGION_DIMENSIONS;
-use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler, TransformedRectKind};
+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 internal_types::LayoutPrimitiveInfo;
 use smallvec::SmallVec;
 
 pub mod borders;
 pub mod gradient;
 pub mod image;
@@ -1366,16 +1366,28 @@ pub struct PrimitiveVisibility {
     /// has no clip mask. Otherwise, it may store the offset of the
     /// global clip mask task for this primitive, or the first of
     /// a list of clip task ids (one per segment).
     pub clip_task_index: ClipTaskIndex,
 
     /// The current combined local clip for this primitive, from
     /// the primitive local clip above and the current clip chain.
     pub combined_local_clip_rect: LayoutRect,
+
+    /// The snap offsets in device space for this primitive. They are
+    /// generated based on the visible rect, which is the local rect
+    /// clipped by the combined local clip for most primitives, or
+    /// just the local rect for pictures.
+    pub snap_offsets: SnapOffsets,
+
+    /// The snap offsets in device space for the drop shadow for
+    /// picture primitives, if applicable. Similar to snap offsets,
+    /// they are generated based on the local rect translated by the
+    /// drop shadow offset.
+    pub shadow_snap_offsets: SnapOffsets,
 }
 
 #[derive(Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
     /// the relevant information for the primitive
@@ -1760,16 +1772,21 @@ impl PrimitiveStore {
 
         let map_surface_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface.surface_spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
+        let mut map_local_to_raster = SpaceMapper::new(
+            surface.raster_spatial_node_index,
+            RasterRect::max_rect(),
+        );
+
         let mut surface_rect = PictureRect::zero();
 
         for prim_instance in &mut prim_list.prim_instances {
             prim_instance.reset();
 
             if prim_instance.is_chased() {
                 #[cfg(debug_assertions)] // needed for ".id" part
                 println!("\tpreparing {:?} in {:?}", prim_instance.id, pic_index);
@@ -1783,17 +1800,22 @@ impl PrimitiveStore {
                 continue;
             }
 
             map_local_to_surface.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
-            let (is_passthrough, prim_local_rect) = match prim_instance.kind {
+            map_local_to_raster.set_target_spatial_node(
+                prim_instance.spatial_node_index,
+                frame_context.clip_scroll_tree,
+            );
+
+            let (is_passthrough, snap_to_visible, prim_local_rect, prim_shadow_rect) = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
                     let is_composite = {
                         let pic = &self.pictures[pic_index.0];
                         if !pic.is_visible() {
                             continue;
                         }
 
                         // If this picture has a surface, we will handle any active clips from parents
@@ -1836,72 +1858,62 @@ impl PrimitiveStore {
 
                     // Similar to above, pop either the clip chain or root entry off the current clip stack.
                     if is_composite {
                         frame_state.clip_chain_stack.pop_surface();
                     } else {
                         frame_state.clip_chain_stack.pop_clip();
                     }
 
-                    let pic_visible_rect = match pic.raster_config {
-                        Some(ref rc) => {
-                            let visible_rect = match rc.composite_mode {
-                                // If we have a drop shadow filter, we also need to include the shadow in
-                                // our local rect for the purpose of calculating the size of the picture.
-                                PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) => {
-                                    pic.snapped_local_rect.translate(&offset).union(&pic.snapped_local_rect)
-                                }
-                                _ => pic.snapped_local_rect,
-                            };
-
-                            map_local_to_surface.map(&visible_rect)
+                    let shadow_rect = match pic.raster_config {
+                        Some(ref rc) => match rc.composite_mode {
+                            // If we have a drop shadow filter, we also need to include the shadow in
+                            // our local rect for the purpose of calculating the size of the picture.
+                            PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) => pic.snapped_local_rect.translate(&offset),
+                            _ => LayoutRect::zero(),
                         }
-                        None => pic_surface_rect,
+                        None => {
+                            if let Some(ref rect) = pic_surface_rect {
+                                surface_rect = surface_rect.union(rect);
+                            }
+                            LayoutRect::zero()
+                        }
                     };
 
-                    if let Some(ref rect) = pic_visible_rect {
-                        surface_rect = surface_rect.union(rect);
-                    }
-
                     if prim_instance.is_chased() {
                         if pic.unsnapped_local_rect != pic.snapped_local_rect {
                             println!("\tsnapped from {:?} to {:?}", pic.unsnapped_local_rect, pic.snapped_local_rect);
                         }
                     }
 
-                    (pic.raster_config.is_none(), pic.snapped_local_rect)
+                    (pic.raster_config.is_none(), false, pic.snapped_local_rect, shadow_rect)
                 }
                 _ => {
                     let prim_data = &frame_state.data_stores.as_common_data(&prim_instance);
 
                     let prim_rect = LayoutRect::new(
                         prim_instance.prim_origin,
                         prim_data.prim_size,
                     );
 
-                    let visible_rect = prim_instance.local_clip_rect
-                        .intersection(&prim_rect)
-                        .unwrap_or(LayoutRect::zero());
-                    if let Some(rect) = map_local_to_surface.map(&visible_rect) {
-                        surface_rect = surface_rect.union(&rect);
-                    }
-
-                    (false, prim_rect)
+                    (false, true, prim_rect, LayoutRect::zero())
                 }
             };
 
             if is_passthrough {
                 let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
 
                 frame_state.scratch.prim_info.push(
                     PrimitiveVisibility {
                         clipped_world_rect: WorldRect::max_rect(),
                         clip_chain: ClipChainInstance::empty(),
                         clip_task_index: ClipTaskIndex::INVALID,
                         combined_local_clip_rect: LayoutRect::zero(),
+                        snap_offsets: SnapOffsets::empty(),
+                        shadow_snap_offsets: SnapOffsets::empty(),
                     }
                 );
 
                 prim_instance.visibility_info = vis_index;
             } else {
                 if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 {
                     if prim_instance.is_chased() {
                         println!("\tculled for zero local rectangle");
@@ -1986,38 +1998,81 @@ impl PrimitiveStore {
 
                 if prim_instance.is_chased() {
                     println!("\teffective clip chain from {:?} {}",
                         clip_chain.clips_range,
                         if apply_local_clip_rect { "(applied)" } else { "" },
                     );
                 }
 
-                let combined_local_clip_rect = if apply_local_clip_rect {
-                    clip_chain.local_clip_rect
-                } else {
-                    prim_instance.local_clip_rect
-                };
-
                 // Check if the clip bounding rect (in pic space) is visible on screen
                 // This includes both the prim bounding rect + local prim clip rect!
                 let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
                     Some(world_rect) => world_rect,
                     None => {
                         continue;
                     }
                 };
 
                 let clipped_world_rect = match world_rect.intersection(&frame_context.screen_world_rect) {
                     Some(rect) => rect,
                     None => {
                         continue;
                     }
                 };
 
+                let combined_local_clip_rect = if apply_local_clip_rect {
+                    clip_chain.local_clip_rect
+                } else {
+                    prim_instance.local_clip_rect
+                };
+
+                // All pictures must snap to their primitive rect instead of the
+                // visible rect like most primitives. This is because the picture's
+                // visible rect includes the effect of the picture's clip rect,
+                // which was not considered by the picture's children. The primitive
+                // rect however is simply the union of the visible rect of the
+                // children, which they snapped to, which is precisely what we also
+                // need to snap to in order to be consistent.
+                let visible_rect = if snap_to_visible {
+                    combined_local_clip_rect
+                        .intersection(&prim_local_rect)
+                        .unwrap_or(LayoutRect::zero())
+                } else {
+                    prim_local_rect
+                };
+
+                // This is how primitives get snapped. In general, snapping a picture's
+                // visible rect here will have no effect, but if it is rasterized in its
+                // own space, or it has a blur or drop shadow effect applied, it may
+                // provide a snapping offset.
+                let (snapped_visible_rect, snap_offsets) = get_snapped_rect(
+                    visible_rect,
+                    &map_local_to_raster,
+                    surface.device_pixel_scale,
+                ).unwrap_or((visible_rect, SnapOffsets::empty()));
+
+                let (combined_visible_rect, shadow_snap_offsets) = if !prim_shadow_rect.is_empty() {
+                    let (snapped_shadow_rect, shadow_snap_offsets) = get_snapped_rect(
+                        prim_shadow_rect,
+                        &map_local_to_raster,
+                        surface.device_pixel_scale,
+                    ).unwrap_or((prim_shadow_rect, SnapOffsets::empty()));
+
+                    (snapped_visible_rect.union(&snapped_shadow_rect), shadow_snap_offsets)
+                } else {
+                    (snapped_visible_rect, SnapOffsets::empty())
+                };
+
+                // Include the snapped primitive/picture local rect, including any shadows,
+                // in the area affected by the surface.
+                if let Some(rect) = map_local_to_surface.map(&combined_visible_rect) {
+                    surface_rect = surface_rect.union(&rect);
+                }
+
                 // When the debug display is enabled, paint a colored rectangle around each
                 // primitive.
                 if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) {
                     let debug_color = match prim_instance.kind {
                         PrimitiveInstanceKind::Picture { .. } => ColorF::TRANSPARENT,
                         PrimitiveInstanceKind::TextRun { .. } => debug_colors::RED,
                         PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE,
                         PrimitiveInstanceKind::NormalBorder { .. } |
@@ -2038,16 +2093,18 @@ impl PrimitiveStore {
                 let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
 
                 frame_state.scratch.prim_info.push(
                     PrimitiveVisibility {
                         clipped_world_rect,
                         clip_chain,
                         clip_task_index: ClipTaskIndex::INVALID,
                         combined_local_clip_rect,
+                        snap_offsets,
+                        shadow_snap_offsets,
                     }
                 );
 
                 prim_instance.visibility_info = vis_index;
 
                 self.request_resources_for_prim(
                     prim_instance,
                     surface,
@@ -3706,23 +3763,23 @@ impl PrimitiveInstance {
 /// Mimics the GLSL mix() function.
 fn mix(x: f32, y: f32, a: f32) -> f32 {
     x * (1.0 - a) + y * a
 }
 
 /// Given a point within a local rectangle, and the device space corners
 /// of a snapped primitive, return the snap offsets. This *must* exactly
 /// match the logic in the GLSL compute_snap_offset_impl function.
-fn compute_snap_offset_impl(
-    reference_pos: PicturePoint,
-    reference_rect: PictureRect,
+fn compute_snap_offset_impl<PixelSpace>(
+    reference_pos: TypedPoint2D<f32, PixelSpace>,
+    reference_rect: TypedRect<f32, PixelSpace>,
     prim_top_left: DevicePoint,
     prim_bottom_right: DevicePoint,
 ) -> DeviceVector2D {
-    let normalized_snap_pos = PicturePoint::new(
+    let normalized_snap_pos = TypedPoint2D::<f32, PixelSpace>::new(
         (reference_pos.x - reference_rect.origin.x) / reference_rect.size.width,
         (reference_pos.y - reference_rect.origin.y) / reference_rect.size.height,
     );
 
     let top_left = DeviceVector2D::new(
         (prim_top_left.x + 0.5).floor() - prim_top_left.x,
         (prim_top_left.y + 0.5).floor() - prim_top_left.y,
     );
@@ -3883,16 +3940,71 @@ pub fn get_raster_rects(
     // Ensure that we won't try to allocate a zero-sized clip render task.
     if clipped.is_empty() {
         return None;
     }
 
     Some((clipped.to_i32(), unclipped))
 }
 
+/// Snap the given rect in raster space if the transform is
+/// axis-aligned. It return the snapped rect transformed back into the
+/// given pixel space, and the snap offsets in device space.
+pub fn get_snapped_rect<PixelSpace>(
+    prim_rect: TypedRect<f32, PixelSpace>,
+    map_to_raster: &SpaceMapper<PixelSpace, RasterPixel>,
+    device_pixel_scale: DevicePixelScale,
+) -> Option<(TypedRect<f32, PixelSpace>, SnapOffsets)> where PixelSpace: fmt::Debug {
+    let is_axis_aligned = match map_to_raster.kind {
+        CoordinateSpaceMapping::Local |
+        CoordinateSpaceMapping::ScaleOffset(..) => true,
+        CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(),
+    };
+
+    if is_axis_aligned {
+       let raster_rect = map_to_raster.map(&prim_rect)?;
+
+       let device_rect = {
+            let world_rect = raster_rect * TypedScale::new(1.0);
+            world_rect * device_pixel_scale
+        };
+
+        let top_left = compute_snap_offset_impl(
+            prim_rect.origin,
+            prim_rect,
+            device_rect.origin,
+            device_rect.bottom_right(),
+        );
+
+        let bottom_right = compute_snap_offset_impl(
+            prim_rect.bottom_right(),
+            prim_rect,
+            device_rect.origin,
+            device_rect.bottom_right(),
+        );
+
+        let snap_offsets = SnapOffsets {
+            top_left,
+            bottom_right,
+        };
+
+        let snapped_device_rect = DeviceRect::new(
+            device_rect.origin + top_left,
+            device_rect.size + (bottom_right - top_left).to_size()
+        );
+
+        let snapped_world_rect = snapped_device_rect / device_pixel_scale;
+        let snapped_raster_rect = snapped_world_rect * TypedScale::new(1.0);
+        let snapped_prim_rect = map_to_raster.unmap(&snapped_raster_rect)?;
+        Some((snapped_prim_rect, snap_offsets))
+    } else {
+        None
+    }
+}
+
 /// Get the inline (horizontal) and block (vertical) sizes
 /// for a given line decoration.
 pub fn get_line_decoration_sizes(
     rect_size: &LayoutSize,
     orientation: LineOrientation,
     style: LineStyle,
     wavy_line_thickness: f32,
 ) -> Option<(f32, f32)> {
index 543ffe227fc5cb9a6d592215e14d4763f9dad1f7..7e36607b04bcceffac9372814d976f1f9f969ba4
GIT binary patch
literal 11151
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
index 4ce47d9b6980e76bdfa3cadca09402b0cb9f1c24..102ed077b6862077b415096aa92e79714acb2265
GIT binary patch
literal 3830
zc%1E5e^8Ql7=Mil>-<$$rmdjtnw@pnvKqG(QYlw0qv@#=OB|;z-(6yDDpNvpS7|2m
zig|7&b!^KeO0EdaRCb<&ITvdcBo38Cxl%weKtyC;&}!{knz{e{gZn=3=bjfn&+~k~
z&-0K^3=eZ%xO^c5L9V;rCwvG&PG<12I?o4)x9>bX1i97kA_PZfp4TWI<+DhhA|-u)
z0CDr&lO8KVsLsK6ZVOk>J?RpA$MJaSdI)hdf~7q`{UYMCv?A(9mNLPZnkH;Ea?sL|
z)k{*X26mMbltw^7A^CyOmy2nT^IARRv0LS|>YNy{eO)m!hv?%DU7B^6b(nRSb#OQc
z2W0Vd?w~abzP!&QnlG#38<n99?w~#q7Ni0|QpPuS1v9v5LrE|%9sm|g<$;<%(C~Te
z9l3=4t*su3Igp|mQjf)SYucq%@j9j}Dkw$s>sbJ371A<10C4maO$!SE{cX~QV;UwN
zn^DD2?Hr+;VWLTO*|qqsH5okqGJgP^bSGtv*5J2hnyUD3y$gWT%Si3*eN0!kmd_tY
z!i@}5BX_W853H5|E`q~+WA9$T_Q@gGqyqr&AU|Cl4%iw}U|lBw@W-yF_kI9?0iAUl
z0p+!I$37Kvv6({{1Nrbwh<IW*@Vfy<BQJ6blO*P7t~>}UMxH3(%(++opH?1v%*@7H
zzgoF5x*1n6dA2vM81<~RneV3lKBsr$`z4>u+2Uv1ugbmjupFAMrMzE(i#pt?MLhVX
zfWBn%ijy;qTqet6vOqU}_!zwd<>iOtEO<qnQ?*Efkr<lwraKw7)0$L~xe1I$CTmDN
z(=^WdT4KzGiU}II%7HD|(MIPkc62p!3RlCW1Y@ZFNgzXB-LllOPA{YmSHxlvpE@eM
zF>=Myi#w-j-O7@QS`2-3h!pXZ2zKX~!nU2yFyKq=4}>yr0*dT3>G8mci855{httrf
z*XUcXwu+<sIbuX;bMj{OVp>v6l4|{gK3do6TUY6YZsl0NqxpdWrvk4c{EAxQd?YSD
z?)^ChoJ}R-fIKl`+r^i<Y88Ey9`g{+GfY*QP+H8hC%0%(aq@}8oejsu2+=9W%Nug0
zdTi^udl?sniPuunKp0<po9$vHmk$^A+8O;;m_3m&R_#QTOT!W|#mV!Fk?Gr;_m}Qb
zl?JPFR}#}AdqI>QG&1s~uEN@*bZ)j)E_!$>FknM~-ciJNe8%(SLF@8bWPdqlp2zDQ
z;McMfV9a>*<7#bS1%doEZCVp=ucynj*#5(H;86s6eTl!R8v8sO)WV4%UPm6!RASNs
zm5kE<UzMPO9#eV-yWd<Tkf>0MXsXMgbK@F2p58v<p!eQs-?-;i+|7HUmX3pHp)}H-
zgh}UQlM<#5!z3Y|=O}e}SwE#m#>3@Q{^;m1O*RtMn_TzCjr(5>aMF?ObyxwCZRmSO
zWQOKP+cjL2wo^Ct`C+)iWVH)sx^crYFh6V!Q=T765fc{`&VD-g?5*T&B4?aY(ZW|)
zZfKUxWVNsv!|E<U5`7pfGPknZ<eGMC_0QLmD}yot@Yk(2d7lgbW5?iSm?xs;zTwx6
zL+W0JE_Y8!tYx$oFU_>v;P=Tal)NxzY?l5zytF1uz*kEEu#X*EcjSA#6q|K}&x^No
zyWhS5YuW{1_vsc%K?h{IY89}i`7{8OO;Qvd03G$P=9e=7c-Sm88b>I3c}Fh1cVDjS
zwEDx@=0D};A;6`QR9R2Ei-rT~a=Fz4fQsl!MJ)j0>POU_J{bd{>B+YNSEQ|@F%;<Q
l#m(l0wFhdMnFVshe?jmep36CvFZe$T?b;Des0tw;{~ID<5^Vqg
index a057fddec5c0ac561376e79436b41cc6fae447d7..dba46d8899047ff1a09ca3b7ab8cd17ded292138
GIT binary patch
literal 3832
zc%1E5YfO_@82+Hx7J|0Mg`q<~FUfRs(`IIhQND_@Fc-Bb#UjXB)<iZd!dz@YdZCrT
zoWm~J6t7g{CIUhMTP_x8tIlTYv|EtUpwu!aj6$U<z0m7U+c9*SP|=_KXwG+@JbB;e
zocD6x9!lDhI6Y!c1O!3TH-4P34T3^k;OD`j0g`{RH5!6u$Tud$lk-lSdIl<w?ub(L
zW{^|R-xM40f(_Ui3zl@xjx92L7r%6le;ZqL_H*>RJ<h8Gr6UhwdTXSEtxX;cYFQ)f
zuG8XrAM5XVIzEI9@Md$!{6Yh29i+q*LINBV5(Yz|SOf~gvmxw!JA_|1MVKN?5vB-!
zLZsZSRYg(TCCs5z-catN&lqB=O%+8dmoQCfydkqWlhKp|07|XII<x}-kLZjF5&-00
zeRori%-e>!Ri15fJ3YETe?F{TmSGgmeW=eidE5ZFQL3F62LOG0w#hXLfa`~}2*)U-
z#^yIjH6kbd@T#1&HLO}9vpN5=R3!w!8djQ&B_=ZSMjNE+G61Z|N-MAdM(mO<5wFpD
zKhdj>f)NSRNCt8(yBQi05QCd1VGeEw>PEg`bcq4*<}!)Zm;wL`gP{@v0OQrmKQZ$&
zDbO@8K}p<X#?4~W_A{p?fe+WUFo0pU_xD3rBI~G~&Lwt55{Fj!pvtz@%s=N)K<bp?
z^9Ixg3qRyowLVdaDLx3tI7C!;;XDGxf<#Pw|AZJ<qs%+nnPUs>tMXeglJY~JGiyYl
z-lMb$L0CF5=+xQ+@H9BO3Kv<o)8~XhhNEdMM$Q{-+C_w**yq;?P7J@*Rci?u+8msa
zwQQa0Mol^gDo74E5<L!^wD<8_>NR#EE6d{sQMyw;&V<QGv)k*1rO6cz$a*2*i1;9U
z(%#2obPeW>)IV|;zzRLtzt?mb`n?$tPad4C*n&o3DQoyYSzOP_GBxB(d39mxGR~fz
zS8ikKR{1zS75qhi-E-r}PKx)5X0cr{bD{k=AaCiSiI#Q8Wh|$znI#r>&SdXO%`#U9
zTqT!V!bG#Rvt46yeA_S>o6MmZ!;uwO1b@MyxIK%#3(uav$-=K(957}?C6ml2;>~<Q
zC+0%AVZ}iMDs!)Xh<^W%^T_r@^NGweO0h3Lj^LG9#rx6nlLl1c*M8ciZ-ZSj_3bA5
z{ktvC81&VEcGlA;2vS`YZd05x3$4UlCBw%L2sDubT+!<M24N{l<Vc+xQ0WU(?E1ic
z>=xi+7Wip(AH3vJROxA;#EqMbu9Ej;ke)t6koqcL5RN|69c1OLGlTT~<8d=!|BY!l
zl3RrJ(G<T1yWso!ZXzo|%YE7)?V{(0tY=OTq`DU-%ZoB^Q9|H8-hPQ3h7`o2=ZZp+
z<bH<~R5eY|GjElHZ0nIBg>98S&V(;vu}G}cP)9>O+(1)R!JIv8uYOFH`H6SoPP9(^
z>cS8%sH@wR!3(RVICXi}OE_9i)mV{F-urmh`rj)s@t8moS?j+vrH!@K4VH?v{7HKs
z@0rbc;1!^V$J*_LvrKe57erQ9KX!q^KcifP;2l<%QN4!Wl{vNp?kmK`yvILE8C?!~
zNeM1p#*q;loA>=B&C&os2&b>%0EXRd)3$T&xQF9%t$P^5_rM^r{8I^YU>le{Y<n5D
zVF0Y!E3y8)6^t6zY(~EZ0E@RstOLnB*VSLXui<#^!s)H~9o;Qox$CdpJXI6N1;E{w
zj_$Ss0I17qGTH%fdUZ#)j0J%B7LV()6ec>Tjdt@nDR>|0CDoMl0#7TDYd$MYZvsFC
zDP6{G19EeFyX2;a2F%D2cv$%U3{M;HD5$AgD8iE|;2k%>pdgJy5odiLCE)<WL9XoA
dH&NP`KfIoO>6o#j5B#HrHh#1vp>{n@@DE?_?`Hr2
--- a/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini
+++ b/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini
@@ -1,3 +1,3 @@
 [mix-blend-mode-both-parent-and-blended-with-3D-transform.html]
   expected:
-    if os == "android" and not e10s: FAIL
+    if webrender or (os == "android" and not e10s): FAIL