Backed out changeset 0788e77d2d62 (bug 1690396) for causing crashes in Bug 1690846. a=backout
authorCsoregi Natalia <ncsoregi@mozilla.com>
Thu, 04 Feb 2021 21:21:32 +0200
changeset 565986 f259675c88bf6bb78eed73238ba942804417c750
parent 565985 c71d2b6ffa9def41acb4ff403331897448f81357
child 566073 65a5081f9afeff9b911a00eef46486d42199a734
push id38172
push userncsoregi@mozilla.com
push dateThu, 04 Feb 2021 19:23:09 +0000
treeherdermozilla-central@f259675c88bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1690396, 1690846
milestone87.0a1
backs out0788e77d2d62496dd9f43902546bd1acd181d8b5
first release with
nightly linux32
f259675c88bf / 87.0a1 / 20210204192309 / files
nightly linux64
f259675c88bf / 87.0a1 / 20210204192309 / files
nightly mac
f259675c88bf / 87.0a1 / 20210204192309 / files
nightly win32
f259675c88bf / 87.0a1 / 20210204192309 / files
nightly win64
f259675c88bf / 87.0a1 / 20210204192309 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 0788e77d2d62 (bug 1690396) for causing crashes in Bug 1690846. a=backout
gfx/wr/webrender/res/brush_mix_blend.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/render_target.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer/mod.rs
gfx/wr/wrench/src/wrench.rs
gfx/wr/wrench/src/yaml_helper.rs
--- a/gfx/wr/webrender/res/brush_mix_blend.glsl
+++ b/gfx/wr/webrender/res/brush_mix_blend.glsl
@@ -2,85 +2,57 @@
  * 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/. */
 
 #define VECS_PER_SPECIFIC_BRUSH 3
 #define WR_FEATURE_TEXTURE_2D
 
 #include shared,prim_shared,brush
 
-// UV and bounds for the source image
+// xy: uv coorinates.
 varying vec2 v_src_uv;
-flat varying vec4 v_src_uv_sample_bounds;
 
-// UV and bounds for the backdrop image
+// xy: uv coorinates.
 varying vec2 v_backdrop_uv;
-flat varying vec4 v_backdrop_uv_sample_bounds;
 
-// mix-blend op, and perspective interpolation control
-flat varying float v_perspective;
 flat varying int v_op;
 
 #ifdef WR_VERTEX_SHADER
 
-void get_uv(
-    int res_address,
-    vec2 f,
-    ivec2 texture_size,
-    float perspective_f,
-    out vec2 out_uv,
-    out vec4 out_uv_sample_bounds
-) {
-    ImageResource res = fetch_image_resource(res_address);
-    vec2 uv0 = res.uv_rect.p0;
-    vec2 uv1 = res.uv_rect.p1;
-
-    vec2 inv_texture_size = vec2(1.0) / vec2(texture_size);
-    f = get_image_quad_uv(res_address, f);
-    vec2 uv = mix(uv0, uv1, f);
-
-    out_uv = uv * inv_texture_size * perspective_f;
-    out_uv_sample_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) * inv_texture_size.xyxy;
-}
-
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
     ivec4 prim_user_data,
     int specific_resource_address,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
-    vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
-    float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;
-    float perspective_f = mix(vi.world_pos.w, 1.0, perspective_interpolate);
-    v_perspective = perspective_interpolate;
+    //Note: this is unsafe for `vi.world_pos.w <= 0.0`
+    vec2 device_pos = vi.world_pos.xy * pic_task.device_pixel_scale / max(0.0, vi.world_pos.w);
+    vec2 backdrop_texture_size = vec2(textureSize(sColor0, 0));
+    vec2 src_texture_size = vec2(textureSize(sColor1, 0));
     v_op = prim_user_data.x;
 
-    get_uv(
-        prim_user_data.y,
-        f,
-        textureSize(sColor0, 0).xy,
-        1.0,
-        v_backdrop_uv,
-        v_backdrop_uv_sample_bounds
-    );
+    PictureTask src_task = fetch_picture_task(prim_user_data.z);
+    vec2 src_device_pos = vi.world_pos.xy * (src_task.device_pixel_scale / max(0.0, vi.world_pos.w));
+    vec2 src_uv = src_device_pos +
+                  src_task.common_data.task_rect.p0 -
+                  src_task.content_origin;
+    v_src_uv = src_uv / src_texture_size;
 
-    get_uv(
-        prim_user_data.z,
-        f,
-        textureSize(sColor1, 0).xy,
-        perspective_f,
-        v_src_uv,
-        v_src_uv_sample_bounds
-    );
+    RenderTaskCommonData backdrop_task = fetch_render_task_common_data(prim_user_data.y);
+    float src_to_backdrop_scale = pic_task.device_pixel_scale / src_task.device_pixel_scale;
+    vec2 backdrop_uv = device_pos +
+                       backdrop_task.task_rect.p0 -
+                       src_task.content_origin * src_to_backdrop_scale;
+    v_backdrop_uv = backdrop_uv / backdrop_texture_size;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec3 Multiply(vec3 Cb, vec3 Cs) {
     return Cb * Cs;
 }
 
@@ -233,25 +205,18 @@ const int MixBlendMode_SoftLight   = 9;
 const int MixBlendMode_Difference  = 10;
 const int MixBlendMode_Exclusion   = 11;
 const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 Fragment brush_fs() {
-    float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective);
-
-    vec2 src_uv = v_src_uv * perspective_divisor;
-    src_uv = clamp(src_uv, v_src_uv_sample_bounds.xy, v_src_uv_sample_bounds.zw);
-
-    vec2 backdrop_uv = clamp(v_backdrop_uv, v_backdrop_uv_sample_bounds.xy, v_backdrop_uv_sample_bounds.zw);
-
-    vec4 Cb = texture(sColor0, backdrop_uv);
-    vec4 Cs = texture(sColor1, src_uv);
+    vec4 Cb = texture(sColor0, v_backdrop_uv);
+    vec4 Cs = texture(sColor1, v_src_uv);
 
     // The mix-blend-mode functions assume no premultiplied alpha
     if (Cb.a != 0.0) {
         Cb.rgb /= Cb.a;
     }
 
     if (Cs.a != 0.0) {
         Cs.rgb /= Cs.a;
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -54,16 +54,17 @@ const CLIP_RECTANGLE_AREA_THRESHOLD: f32
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
     Solid,
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
+        source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
     YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange),
     ConicGradient,
     RadialGradient,
     LinearGradient,
     Opacity,
 }
@@ -1973,16 +1974,17 @@ impl BatchBuilder {
 
                                 let color0 = render_tasks[backdrop_id].get_target_texture();
                                 let color1 = render_tasks[cache_task_id].get_target_texture();
 
                                 let key = BatchKey::new(
                                     BatchKind::Brush(
                                         BrushBatchKind::MixBlend {
                                             task_id: self.batchers[0].render_task_id,
+                                            source_id: cache_task_id,
                                             backdrop_id,
                                         },
                                     ),
                                     BlendMode::PremultipliedAlpha,
                                     BatchTextures {
                                         input: TextureSet {
                                             colors: [
                                                 TextureSource::TextureCache(
@@ -1994,22 +1996,22 @@ impl BatchBuilder {
                                                     Swizzle::default(),
                                                 ),
                                                 TextureSource::Invalid,
                                             ],
                                         },
                                         clip_mask: clip_mask_texture_id,
                                     },
                                 );
-                                let src_uv_address = render_tasks[cache_task_id].get_texture_address(gpu_cache);
-                                let readback_uv_address = render_tasks[backdrop_id].get_texture_address(gpu_cache);
+                                let backdrop_task_address: RenderTaskAddress = backdrop_id.into();
+                                let source_task_address: RenderTaskAddress = cache_task_id.into();
                                 let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                     mode as u32 as i32,
-                                    readback_uv_address.as_int(),
-                                    src_uv_address.as_int(),
+                                    backdrop_task_address.0 as i32,
+                                    source_task_address.0 as i32,
                                     0,
                                 ]);
 
                                 self.add_brush_instance_to_batches(
                                     key,
                                     batch_features,
                                     bounding_rect,
                                     z_id,
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -205,59 +205,53 @@ impl<'a> FrameBuildingState<'a> {
     }
 
     /// Initialize render tasks for a surface that is tiled (currently applies
     /// only to picture cache surfaces).
     pub fn init_surface_tiled(
         &mut self,
         surface_index: SurfaceIndex,
         tasks: Vec<RenderTaskId>,
-        device_rect: DeviceRect,
     ) {
         let surface = &mut self.surfaces[surface_index.0];
         assert!(surface.render_tasks.is_none());
         surface.render_tasks = Some(SurfaceRenderTasks::Tiled(tasks));
-        surface.device_rect = Some(device_rect);
     }
 
     /// Initialize render tasks for a simple surface, that contains only a
     /// single render task.
     pub fn init_surface(
         &mut self,
         surface_index: SurfaceIndex,
         task_id: RenderTaskId,
         parent_surface_index: SurfaceIndex,
-        device_rect: DeviceRect,
     ) {
         let surface = &mut self.surfaces[surface_index.0];
         assert!(surface.render_tasks.is_none());
         surface.render_tasks = Some(SurfaceRenderTasks::Simple(task_id));
-        surface.device_rect = Some(device_rect);
 
         self.add_child_render_task(
             parent_surface_index,
             task_id,
         );
     }
 
     /// Initialize render tasks for a surface that is made up of a chain of
     /// render tasks, where the final output render task is different than the
     /// input render task (for example, a blur pass on a picture).
     pub fn init_surface_chain(
         &mut self,
         surface_index: SurfaceIndex,
         root_task_id: RenderTaskId,
         port_task_id: RenderTaskId,
         parent_surface_index: SurfaceIndex,
-        device_rect: DeviceRect,
     ) {
         let surface = &mut self.surfaces[surface_index.0];
         assert!(surface.render_tasks.is_none());
         surface.render_tasks = Some(SurfaceRenderTasks::Chained { root_task_id, port_task_id });
-        surface.device_rect = Some(device_rect);
 
         self.add_child_render_task(
             parent_surface_index,
             root_task_id,
         );
     }
 
     /// Add a render task as a dependency of a given surface.
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -4062,18 +4062,16 @@ pub struct SurfaceInfo {
     /// This is set when the render task is created.
     pub render_tasks: Option<SurfaceRenderTasks>,
     /// How much the local surface rect should be inflated (for blur radii).
     pub inflation_factor: f32,
     /// The device pixel ratio specific to this surface.
     pub device_pixel_scale: DevicePixelScale,
     /// The scale factors of the surface to raster transform.
     pub scale_factors: (f32, f32),
-    /// The allocated device rect for this surface
-    pub device_rect: Option<DeviceRect>,
 }
 
 impl SurfaceInfo {
     pub fn new(
         surface_spatial_node_index: SpatialNodeIndex,
         raster_spatial_node_index: SpatialNodeIndex,
         inflation_factor: f32,
         world_rect: WorldRect,
@@ -4102,22 +4100,17 @@ impl SurfaceInfo {
             opaque_rect: PictureRect::zero(),
             map_local_to_surface,
             render_tasks: None,
             raster_spatial_node_index,
             surface_spatial_node_index,
             inflation_factor,
             device_pixel_scale,
             scale_factors,
-            device_rect: None,
-        }
-    }
-
-    pub fn get_device_rect(&self) -> DeviceRect {
-        self.device_rect.expect("bug: queried before surface was initialized")
+        }
     }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct RasterConfig {
     /// How this picture should be composited into
     /// the parent surface.
@@ -5094,17 +5087,16 @@ impl PicturePrimitive {
                             tile_cache.slice,
                             debug_info,
                         );
                 }
 
                 frame_state.init_surface_tiled(
                     surface_index,
                     surface_tasks,
-                    device_clip_rect,
                 );
             }
             Some(ref mut raster_config) => {
                 let pic_rect = self.precise_local_rect.cast_unit();
 
                 let mut device_pixel_scale = frame_state
                     .surfaces[raster_config.surface_index.0]
                     .device_pixel_scale;
@@ -5302,17 +5294,16 @@ impl PicturePrimitive {
                             original_size.to_i32(),
                         );
 
                         frame_state.init_surface_chain(
                             raster_config.surface_index,
                             blur_render_task_id,
                             picture_task_id,
                             parent_surface_index,
-                            device_rect,
                         );
                     }
                     PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
                         let mut max_std_deviation = 0.0;
                         for shadow in shadows {
                             max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius);
                         }
                         max_std_deviation = clamp_blur_radius(max_std_deviation, scale_factors) * device_pixel_scale.0;
@@ -5400,17 +5391,16 @@ impl PicturePrimitive {
                         }
 
                         // TODO(nical) the second one should to be the blur's task id but we have several blurs now
                         frame_state.init_surface_chain(
                             raster_config.surface_index,
                             blur_render_task_id,
                             picture_task_id,
                             parent_surface_index,
-                            device_rect,
                         );
                     }
                     PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {
                         if let Some(scale) = adjust_scale_for_max_surface_size(
                             raster_config, frame_context.fb_config.max_target_size,
                             pic_rect, &map_pic_to_raster, &map_raster_to_world,
                             raster_config.clipped_bounding_rect,
                             &mut device_pixel_scale, &mut clipped, &mut unclipped,
@@ -5420,65 +5410,20 @@ impl PicturePrimitive {
 
                         let uv_rect_kind = calculate_uv_rect_kind(
                             &pic_rect,
                             &transform,
                             &clipped,
                             device_pixel_scale,
                         );
 
-                        let parent_surface = &frame_state.surfaces[parent_surface_index.0];
-                        let parent_raster_spatial_node_index = parent_surface.raster_spatial_node_index;
-                        let parent_device_pixel_scale = parent_surface.device_pixel_scale;
-
-                        // Create a space mapper that will allow mapping from the local rect
-                        // of the mix-blend primitive into the space of the surface that we
-                        // need to read back from. Note that we use the parent's raster spatial
-                        // node here, so that we are in the correct device space of the parent
-                        // surface, whether it establishes a raster root or not.
-                        let map_pic_to_parent = SpaceMapper::new_with_target(
-                            parent_raster_spatial_node_index,
-                            self.spatial_node_index,
-                            RasterRect::max_rect(),         // TODO(gw): May need a conservative estimate?
-                            frame_context.spatial_tree,
-                        );
-                        let pic_in_raster_space = map_pic_to_parent
-                            .map(&pic_rect)
-                            .expect("bug: unable to map mix-blend content into parent");
-
-                        // Apply device pixel ratio for parent surface to get into device
-                        // pixels for that surface.
-                        let backdrop_rect = raster_rect_to_device_pixels(
-                            pic_in_raster_space,
-                            parent_device_pixel_scale,
-                        );
-
-                        let parent_surface_rect = parent_surface.get_device_rect();
-                        let backdrop_rect = backdrop_rect
-                            .intersection(&parent_surface_rect)
-                            .expect("bug: readback rect outside parent rect clip");
-
-                        // Calculate the UV coords necessary for the shader to sampler
-                        // from the primitive rect within the readback region. This is
-                        // 0..1 for aligned surfaces, but doing it this way allows
-                        // accurate sampling if the primitive bounds have fractional values.
-                        let backdrop_uv = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &map_pic_to_parent.get_transform(),
-                            &backdrop_rect,
-                            parent_device_pixel_scale,
-                        );
-
                         let readback_task_id = frame_state.rg_builder.add().init(
                             RenderTask::new_dynamic(
-                                backdrop_rect.size.to_i32(),
-                                RenderTaskKind::new_readback(
-                                    backdrop_rect.origin,
-                                    backdrop_uv,
-                                ),
+                                clipped.size.to_i32(),
+                                RenderTaskKind::new_readback(),
                             )
                         );
 
                         frame_state.add_child_render_task(
                             parent_surface_index,
                             readback_task_id,
                         );
 
@@ -5503,17 +5448,16 @@ impl PicturePrimitive {
                                 )
                             )
                         );
 
                         frame_state.init_surface(
                             raster_config.surface_index,
                             render_task_id,
                             parent_surface_index,
-                            clipped,
                         );
                     }
                     PictureCompositeMode::Filter(..) => {
 
                         if let Some(scale) = adjust_scale_for_max_surface_size(
                             raster_config, frame_context.fb_config.max_target_size,
                             pic_rect, &map_pic_to_raster, &map_raster_to_world,
                             raster_config.clipped_bounding_rect,
@@ -5548,17 +5492,16 @@ impl PicturePrimitive {
                                 )
                             )
                         );
 
                         frame_state.init_surface(
                             raster_config.surface_index,
                             render_task_id,
                             parent_surface_index,
-                            clipped,
                         );
                     }
                     PictureCompositeMode::ComponentTransferFilter(..) => {
                         if let Some(scale) = adjust_scale_for_max_surface_size(
                             raster_config, frame_context.fb_config.max_target_size,
                             pic_rect, &map_pic_to_raster, &map_raster_to_world,
                             raster_config.clipped_bounding_rect,
                             &mut device_pixel_scale, &mut clipped, &mut unclipped,
@@ -5592,17 +5535,16 @@ impl PicturePrimitive {
                                 )
                             )
                         );
 
                         frame_state.init_surface(
                             raster_config.surface_index,
                             render_task_id,
                             parent_surface_index,
-                            clipped,
                         );
                     }
                     PictureCompositeMode::MixBlend(..) |
                     PictureCompositeMode::Blit(_) => {
                         if let Some(scale) = adjust_scale_for_max_surface_size(
                             raster_config, frame_context.fb_config.max_target_size,
                             pic_rect, &map_pic_to_raster, &map_raster_to_world,
                             raster_config.clipped_bounding_rect,
@@ -5637,17 +5579,16 @@ impl PicturePrimitive {
                                 )
                             )
                         );
 
                         frame_state.init_surface(
                             raster_config.surface_index,
                             render_task_id,
                             parent_surface_index,
-                            clipped,
                         );
                     }
                     PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
 
                         if let Some(scale) = adjust_scale_for_max_surface_size(
                             raster_config, frame_context.fb_config.max_target_size,
                             pic_rect, &map_pic_to_raster, &map_raster_to_world,
                             raster_config.clipped_bounding_rect,
@@ -5693,27 +5634,19 @@ impl PicturePrimitive {
                             device_pixel_scale,
                         );
 
                         frame_state.init_surface_chain(
                             raster_config.surface_index,
                             filter_task_id,
                             picture_task_id,
                             parent_surface_index,
-                            clipped,
                         );
                     }
                 }
-
-                // Update the device pixel ratio in the surface, in case it was adjusted due
-                // to the surface being too large. This ensures the correct scale is available
-                // in case it's used as input to a parent mix-blend-mode readback.
-                frame_state
-                    .surfaces[raster_config.surface_index.0]
-                    .device_pixel_scale = device_pixel_scale;
             }
             None => {}
         };
 
         #[cfg(feature = "capture")]
         {
             if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
                 if let Some(PictureCompositeMode::TileCache { slice_id }) = self.requested_composite_mode {
--- a/gfx/wr/webrender/src/render_target.rs
+++ b/gfx/wr/webrender/src/render_target.rs
@@ -403,17 +403,17 @@ impl RenderTarget for ColorRenderTarget 
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) => {
                 panic!("Should not be added to color target!");
             }
-            RenderTaskKind::Readback(..) => {}
+            RenderTaskKind::Readback => {}
             RenderTaskKind::Scaling(ref info) => {
                 add_scaling_instances(
                     info,
                     &mut self.scalings,
                     task,
                     task.children.first().map(|&child| &render_tasks[child]),
                     ctx.resource_cache,
                     gpu_cache,
@@ -525,17 +525,17 @@ impl RenderTarget for AlphaRenderTarget 
         transforms: &mut TransformPalette,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         profile_scope!("add_task");
         let task = &render_tasks[task_id];
         let (target_rect, _) = task.get_target_rect();
 
         match task.kind {
-            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Readback |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::SvgFilter(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
@@ -747,17 +747,17 @@ impl TextureCacheRenderTarget {
                     colors,
                     start_stop: [task_info.start_point, task_info.end_point],
                 });
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
-            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Readback |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::SvgFilter(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
             #[cfg(test)]
             RenderTaskKind::Test(..) => {}
         }
     }
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -292,44 +292,32 @@ pub enum SvgFilterInfo {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SvgFilterTask {
     pub info: SvgFilterInfo,
     pub extra_gpu_cache_handle: Option<GpuCacheHandle>,
     pub uv_rect_handle: GpuCacheHandle,
     pub uv_rect_kind: UvRectKind,
 }
 
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ReadbackTask {
-    // The offset of the rect that needs to be read back, in the
-    // device space of the surface that will be read back from
-    pub readback_origin: DevicePoint,
-    // UV rect of the readback rect within the readback task area
-    pub uv_rect_kind: UvRectKind,
-    // GPU cache handle that provides uv_rect_kind to shaders
-    pub uv_rect_handle: GpuCacheHandle,
-}
-
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
     ClipRegion(ClipRegionTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
-    Readback(ReadbackTask),
+    Readback,
     Scaling(ScalingTask),
     Blit(BlitTask),
     Border(BorderTask),
     LineDecoration(LineDecorationTask),
     Gradient(GradientTask),
     SvgFilter(SvgFilterTask),
     #[cfg(test)]
     Test(RenderTargetKind),
@@ -338,32 +326,32 @@ pub enum RenderTaskKind {
 impl RenderTaskKind {
     pub fn as_str(&self) -> &'static str {
         match *self {
             RenderTaskKind::Picture(..) => "Picture",
             RenderTaskKind::CacheMask(..) => "CacheMask",
             RenderTaskKind::ClipRegion(..) => "ClipRegion",
             RenderTaskKind::VerticalBlur(..) => "VerticalBlur",
             RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur",
-            RenderTaskKind::Readback(..) => "Readback",
+            RenderTaskKind::Readback => "Readback",
             RenderTaskKind::Scaling(..) => "Scaling",
             RenderTaskKind::Blit(..) => "Blit",
             RenderTaskKind::Border(..) => "Border",
             RenderTaskKind::LineDecoration(..) => "LineDecoration",
             RenderTaskKind::Gradient(..) => "Gradient",
             RenderTaskKind::SvgFilter(..) => "SvgFilter",
             #[cfg(test)]
             RenderTaskKind::Test(..) => "Test",
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match *self {
             RenderTaskKind::LineDecoration(..) |
-            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Readback |
             RenderTaskKind::Border(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::SvgFilter(..) => {
                 RenderTargetKind::Color
             }
 
@@ -426,27 +414,18 @@ impl RenderTaskKind {
         RenderTaskKind::Gradient(GradientTask {
             stops,
             orientation,
             start_point,
             end_point,
         })
     }
 
-    pub fn new_readback(
-        readback_origin: DevicePoint,
-        uv_rect_kind: UvRectKind,
-    ) -> Self {
-        RenderTaskKind::Readback(
-            ReadbackTask {
-                readback_origin,
-                uv_rect_kind,
-                uv_rect_handle: GpuCacheHandle::new(),
-            }
-        )
+    pub fn new_readback() -> Self {
+        RenderTaskKind::Readback
     }
 
     pub fn new_line_decoration(
         style: LineStyle,
         orientation: LineOrientation,
         wavy_line_thickness: f32,
         local_size: LayoutSize,
     ) -> Self {
@@ -644,17 +623,17 @@ impl RenderTaskKind {
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 [
                     task.blur_std_deviation,
                     task.blur_region.width as f32,
                     task.blur_region.height as f32,
                 ]
             }
-            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Readback |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
 
@@ -701,19 +680,17 @@ impl RenderTaskKind {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::SvgFilter(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
-            RenderTaskKind::Readback(ref mut info) => {
-                (&mut info.uv_rect_handle, info.uv_rect_kind)
-            }
+            RenderTaskKind::Readback |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) => {
                 return;
@@ -1377,17 +1354,17 @@ impl RenderTask {
         }
 
         task_id
     }
 
     pub fn uv_rect_kind(&self) -> UvRectKind {
         match self.kind {
             RenderTaskKind::CacheMask(..) |
-            RenderTaskKind::Readback(..) => {
+            RenderTaskKind::Readback => {
                 unreachable!("bug: unexpected render task");
             }
 
             RenderTaskKind::Picture(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::VerticalBlur(ref task) |
@@ -1425,20 +1402,18 @@ impl RenderTask {
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::SvgFilter(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
-            RenderTaskKind::Readback(ref info) => {
-                gpu_cache.get_address(&info.uv_rect_handle)
-            }
             RenderTaskKind::ClipRegion(..) |
+            RenderTaskKind::Readback |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) => {
                 panic!("texture handle not supported for this task kind");
             }
@@ -1528,17 +1503,17 @@ impl RenderTask {
             RenderTaskKind::VerticalBlur(ref task) => {
                 pt.new_level("VerticalBlur".to_owned());
                 task.print_with(pt);
             }
             RenderTaskKind::HorizontalBlur(ref task) => {
                 pt.new_level("HorizontalBlur".to_owned());
                 task.print_with(pt);
             }
-            RenderTaskKind::Readback(..) => {
+            RenderTaskKind::Readback => {
                 pt.new_level("Readback".to_owned());
             }
             RenderTaskKind::Scaling(ref kind) => {
                 pt.new_level("Scaling".to_owned());
                 pt.add_item(format!("kind: {:?}", kind));
             }
             RenderTaskKind::Border(..) => {
                 pt.new_level("Border".to_owned());
--- a/gfx/wr/webrender/src/renderer/mod.rs
+++ b/gfx/wr/webrender/src/renderer/mod.rs
@@ -2484,75 +2484,77 @@ impl Renderer {
 
         self.profile.add(profiler::VERTICES, 6 * data.len());
     }
 
     fn handle_readback_composite(
         &mut self,
         draw_target: DrawTarget,
         uses_scissor: bool,
+        source: &RenderTask,
         backdrop: &RenderTask,
         readback: &RenderTask,
     ) {
         if uses_scissor {
             self.device.disable_scissor();
         }
 
         let texture_source = TextureSource::TextureCache(
             readback.get_target_texture(),
             Swizzle::default(),
         );
         let (cache_texture, _) = self.texture_resolver
             .resolve(&texture_source).expect("bug: no source texture");
 
-        // Extract the rectangle in the backdrop surface's device space of where
-        // we need to read from.
-        let readback_origin = match readback.kind {
-            RenderTaskKind::Readback(ref info) => info.readback_origin,
-            _ => unreachable!(),
-        };
-
         // Before submitting the composite batch, do the
         // framebuffer readbacks that are needed for each
         // composite operation in this batch.
         let (readback_rect, readback_layer) = readback.get_target_rect();
         let (backdrop_rect, _) = backdrop.get_target_rect();
-        let (backdrop_screen_origin, _) = match backdrop.kind {
+        let (backdrop_screen_origin, backdrop_scale) = match backdrop.kind {
+            RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale),
+            _ => panic!("bug: composite on non-picture?"),
+        };
+        let (source_screen_origin, source_scale) = match source.kind {
             RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale),
             _ => panic!("bug: composite on non-picture?"),
         };
 
         // Bind the FBO to blit the backdrop to.
         // Called per-instance in case the layer (and therefore FBO)
         // changes. The device will skip the GL call if the requested
         // target is already bound.
         let cache_draw_target = DrawTarget::from_texture(
             cache_texture,
             readback_layer.0 as usize,
             false,
         );
 
-        let src_origin = readback_origin +
-            backdrop_rect.origin.to_f32().to_vector() -
-            backdrop_screen_origin.to_vector();
-
-        let src = DeviceIntRect::new(
-            src_origin.to_i32(),
+        let source_in_backdrop_space = source_screen_origin * (backdrop_scale.0 / source_scale.0);
+
+        let mut src = DeviceIntRect::new(
+            (source_in_backdrop_space + (backdrop_rect.origin.to_f32() - backdrop_screen_origin)).to_i32(),
             readback_rect.size,
         );
-
-        // Should always be drawing to picture cache tiles or off-screen surface!
-        debug_assert!(!draw_target.is_default());
+        let mut dest = readback_rect.to_i32();
         let device_to_framebuffer = Scale::new(1i32);
 
+        // Need to invert the y coordinates and flip the image vertically when
+        // reading back from the framebuffer.
+        if draw_target.is_default() {
+            src.origin.y = draw_target.dimensions().height as i32 - src.size.height - src.origin.y;
+            dest.origin.y += dest.size.height;
+            dest.size.height = -dest.size.height;
+        }
+
         self.device.blit_render_target(
             draw_target.into(),
             src * device_to_framebuffer,
             cache_draw_target,
-            readback_rect * device_to_framebuffer,
+            dest * device_to_framebuffer,
             TextureFilter::Linear,
         );
 
         // Restore draw target to current pass render target + layer, and reset
         // the read target.
         self.device.bind_draw_target(draw_target);
         self.device.reset_read_target();
 
@@ -2892,23 +2894,24 @@ impl Renderer {
                             }
                             self.device.set_blend_mode_advanced(mode);
                         }
                     }
                     prev_blend_mode = batch.key.blend_mode;
                 }
 
                 // Handle special case readback for composites.
-                if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, backdrop_id }) = batch.key.kind {
+                if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind {
                     // composites can't be grouped together because
                     // they may overlap and affect each other.
                     debug_assert_eq!(batch.instances.len(), 1);
                     self.handle_readback_composite(
                         draw_target,
                         uses_scissor,
+                        &render_tasks[source_id],
                         &render_tasks[task_id],
                         &render_tasks[backdrop_id],
                     );
                 }
 
                 let _timer = self.gpu_profiler.start_timer(batch.key.kind.sampler_tag());
                 shader.bind(
                     &mut self.device,
--- a/gfx/wr/wrench/src/wrench.rs
+++ b/gfx/wr/wrench/src/wrench.rs
@@ -261,16 +261,17 @@ impl Wrench {
             enable_clear_scissor: !no_scissor,
             max_recorded_profiles: 16,
             precache_flags,
             blob_image_handler: Some(Box::new(blob::CheckerboardRenderer::new(callbacks.clone()))),
             chase_primitive,
             testing: true,
             max_texture_size: Some(8196), // Needed for rawtest::test_resize_image.
             allow_dual_source_blending: !disable_dual_source_blending,
+            allow_advanced_blend_equation: true,
             dump_shader_source,
             // SWGL doesn't support the GL_ALWAYS depth comparison function used by
             // `clear_caches_with_quads`, but scissored clears work well.
             clear_caches_with_quads: !window.is_software(),
             ..Default::default()
         };
 
         // put an Awakened event into the queue to kick off the first frame
--- a/gfx/wr/wrench/src/yaml_helper.rs
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -47,18 +47,16 @@ pub trait YamlHelper {
 fn string_to_color(color: &str) -> Option<ColorF> {
     match color {
         "red" => Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
         "green" => Some(ColorF::new(0.0, 1.0, 0.0, 1.0)),
         "blue" => Some(ColorF::new(0.0, 0.0, 1.0, 1.0)),
         "white" => Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
         "black" => Some(ColorF::new(0.0, 0.0, 0.0, 1.0)),
         "yellow" => Some(ColorF::new(1.0, 1.0, 0.0, 1.0)),
-        "cyan" => Some(ColorF::new(0.0, 1.0, 1.0, 1.0)),
-        "magenta" => Some(ColorF::new(1.0, 0.0, 1.0, 1.0)),
         "transparent" => Some(ColorF::new(1.0, 1.0, 1.0, 0.0)),
         s => {
             let items: Vec<f32> = s.split_whitespace()
                 .map(|s| f32::from_str(s).unwrap())
                 .collect();
             if items.len() == 3 {
                 Some(ColorF::new(
                     items[0] / 255.0,