Backed out 2 changesets (bug 1519718) for performance regression (bug 1530611) on a CLOSED TREE.
☠☠ backed out by c1bd622a4093 ☠ ☠
authorGurzau Raul <rgurzau@mozilla.com>
Wed, 06 Mar 2019 11:47:53 +0200
changeset 520448 6878f6294d6834dba65171d5ec56248fabf36a5d
parent 520447 c15813f5f72f253dae60495b68e84ee71b16a65d
child 520449 475c7fe27919b7e336fada5c9733dfb13c0aea53
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1519718, 1530611
milestone67.0a1
backs out9bae35dc1471ff97f0efba49083bb165453ebf35
640e731ed52e5ea2428b329d7eb296b61163ee4b
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
Backed out 2 changesets (bug 1519718) for performance regression (bug 1530611) on a CLOSED TREE. Backed out changeset 9bae35dc1471 (bug 1519718) Backed out changeset 640e731ed52e (bug 1519718)
gfx/wr/webrender/res/brush_mix_blend.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/clip_scroll_tree.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/picture.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/tiling.rs
gfx/wr/wrench/reftests/blend/multiply-2-ref.yaml
gfx/wr/wrench/reftests/blend/multiply-2.yaml
gfx/wr/wrench/reftests/blend/multiply-3-ref.yaml
gfx/wr/wrench/reftests/blend/multiply-3.yaml
gfx/wr/wrench/reftests/blend/multiply-4.yaml
gfx/wr/wrench/reftests/blend/reftest.list
gfx/wr/wrench/reftests/blend/transform-source-ref.yaml
gfx/wr/wrench/reftests/blend/transform-source.yaml
gfx/wr/wrench/src/main.rs
layout/reftests/css-blending/reftest.list
--- a/gfx/wr/webrender/res/brush_mix_blend.glsl
+++ b/gfx/wr/webrender/res/brush_mix_blend.glsl
@@ -1,63 +1,53 @@
 /* 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/. */
 
 #define VECS_PER_SPECIFIC_BRUSH 3
 
 #include shared,prim_shared,brush
 
-varying vec4 vSourceAndBackdropUv;
-flat varying ivec4 vSourceUvBounds;
-flat varying ivec4 vBackdropUvBounds;
-flat varying ivec3 vOpAndLayers;
+varying vec3 vSrcUv;
+varying vec3 vBackdropUv;
+flat varying int vOp;
 
 #ifdef WR_VERTEX_SHADER
 
 //Note: this function is unsafe for `vi.world_pos.w <= 0.0`
 vec2 snap_device_pos(VertexInfo vi, float device_pixel_scale) {
     return vi.world_pos.xy * device_pixel_scale / max(0.0, vi.world_pos.w) + vi.snap_offset;
 }
 
-ivec4 rect_to_ivec(RectWithSize rect) {
-    return ivec4(rect.p0, rect.p0 + rect.size - 1.0);
-}
-
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
     ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
     vec2 snapped_device_pos = snap_device_pos(vi, pic_task.device_pixel_scale);
+    vec2 texture_size = vec2(textureSize(sPrevPassColor, 0));
+    vOp = user_data.x;
 
     PictureTask src_task = fetch_picture_task(user_data.z);
-    PictureTask backdrop_task = fetch_picture_task(user_data.y);
-
     vec2 src_uv = snapped_device_pos +
                   src_task.common_data.task_rect.p0 -
                   src_task.content_origin;
-    vec2 backdrop_uv = snapped_device_pos +
-                       backdrop_task.common_data.task_rect.p0 -
-                       backdrop_task.content_origin;
+    vSrcUv = vec3(src_uv / texture_size, src_task.common_data.texture_layer_index);
 
-    vSourceAndBackdropUv = vec4(src_uv, backdrop_uv);
-    vSourceUvBounds = rect_to_ivec(src_task.common_data.task_rect);
-    vBackdropUvBounds = rect_to_ivec(backdrop_task.common_data.task_rect);
-    vOpAndLayers = ivec3(
-        user_data.x,
-        int(src_task.common_data.texture_layer_index),
-        int(backdrop_task.common_data.texture_layer_index)
-    );
+    RenderTaskCommonData backdrop_task = fetch_render_task_common_data(user_data.y);
+    vec2 backdrop_uv = snapped_device_pos +
+                       backdrop_task.task_rect.p0 -
+                       src_task.content_origin;
+    vBackdropUv = vec3(backdrop_uv / texture_size, backdrop_task.texture_layer_index);
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec3 Multiply(vec3 Cb, vec3 Cs) {
     return Cb * Cs;
 }
 
@@ -210,94 +200,87 @@ 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() {
+    vec4 Cb = textureLod(sPrevPassColor, vBackdropUv, 0.0);
+    vec4 Cs = textureLod(sPrevPassColor, vSrcUv, 0.0);
+
+    // 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;
+    }
+
     // Return yellow if none of the branches match (shouldn't happen).
     vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
 
-    ivec2 source_uv = ivec2(floor(vSourceAndBackdropUv.xy));
-    vec4 Cs = source_uv == clamp(source_uv, vSourceUvBounds.xy, vSourceUvBounds.zw) ?
-        texelFetch(sPrevPassColor, ivec3(source_uv, vOpAndLayers.y), 0) :
-        vec4(0.0);
-    ivec2 backdrop_uv = ivec2(floor(vSourceAndBackdropUv.zw));
-    vec4 Cb = backdrop_uv == clamp(backdrop_uv, vBackdropUvBounds.xy, vBackdropUvBounds.zw) ?
-        texelFetch(sPrevPassColor, ivec3(backdrop_uv, vOpAndLayers.z), 0) :
-        vec4(0.0);
-
-    if (Cs.a == 0.0) {
-        result = Cb;
-    } else if (Cb.a == 0.0) {
-        result = Cs;
-    } else {
-        vec3 original_backdrop = Cb.rgb;
-        // The mix-blend-mode functions assume no premultiplied alpha
-        Cs.rgb /= Cs.a;
-        Cb.rgb /= Cb.a;
+    switch (vOp) {
+        case MixBlendMode_Multiply:
+            result.rgb = Multiply(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Screen:
+            result.rgb = Screen(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Overlay:
+            // Overlay is inverse of Hardlight
+            result.rgb = HardLight(Cs.rgb, Cb.rgb);
+            break;
+        case MixBlendMode_Darken:
+            result.rgb = min(Cs.rgb, Cb.rgb);
+            break;
+        case MixBlendMode_Lighten:
+            result.rgb = max(Cs.rgb, Cb.rgb);
+            break;
+        case MixBlendMode_ColorDodge:
+            result.r = ColorDodge(Cb.r, Cs.r);
+            result.g = ColorDodge(Cb.g, Cs.g);
+            result.b = ColorDodge(Cb.b, Cs.b);
+            break;
+        case MixBlendMode_ColorBurn:
+            result.r = ColorBurn(Cb.r, Cs.r);
+            result.g = ColorBurn(Cb.g, Cs.g);
+            result.b = ColorBurn(Cb.b, Cs.b);
+            break;
+        case MixBlendMode_HardLight:
+            result.rgb = HardLight(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_SoftLight:
+            result.r = SoftLight(Cb.r, Cs.r);
+            result.g = SoftLight(Cb.g, Cs.g);
+            result.b = SoftLight(Cb.b, Cs.b);
+            break;
+        case MixBlendMode_Difference:
+            result.rgb = Difference(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Exclusion:
+            result.rgb = Exclusion(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Hue:
+            result.rgb = Hue(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Saturation:
+            result.rgb = Saturation(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Color:
+            result.rgb = Color(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Luminosity:
+            result.rgb = Luminosity(Cb.rgb, Cs.rgb);
+            break;
+        default: break;
+    }
 
-        switch (vOpAndLayers.x) {
-            case MixBlendMode_Multiply:
-                result.rgb = Multiply(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Screen:
-                result.rgb = Screen(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Overlay:
-                // Overlay is inverse of Hardlight
-                result.rgb = HardLight(Cs.rgb, Cb.rgb);
-                break;
-            case MixBlendMode_Darken:
-                result.rgb = min(Cs.rgb, Cb.rgb);
-                break;
-            case MixBlendMode_Lighten:
-                result.rgb = max(Cs.rgb, Cb.rgb);
-                break;
-            case MixBlendMode_ColorDodge:
-                result.r = ColorDodge(Cb.r, Cs.r);
-                result.g = ColorDodge(Cb.g, Cs.g);
-                result.b = ColorDodge(Cb.b, Cs.b);
-                break;
-            case MixBlendMode_ColorBurn:
-                result.r = ColorBurn(Cb.r, Cs.r);
-                result.g = ColorBurn(Cb.g, Cs.g);
-                result.b = ColorBurn(Cb.b, Cs.b);
-                break;
-            case MixBlendMode_HardLight:
-                result.rgb = HardLight(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_SoftLight:
-                result.r = SoftLight(Cb.r, Cs.r);
-                result.g = SoftLight(Cb.g, Cs.g);
-                result.b = SoftLight(Cb.b, Cs.b);
-                break;
-            case MixBlendMode_Difference:
-                result.rgb = Difference(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Exclusion:
-                result.rgb = Exclusion(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Hue:
-                result.rgb = Hue(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Saturation:
-                result.rgb = Saturation(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Color:
-                result.rgb = Color(Cb.rgb, Cs.rgb);
-                break;
-            case MixBlendMode_Luminosity:
-                result.rgb = Luminosity(Cb.rgb, Cs.rgb);
-                break;
-            default: break;
-        }
+    result.rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
+    result.a = Cs.a;
 
-        vec3 rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
-        // simulate alpha-blending with the backdrop
-        result = mix(vec4(original_backdrop, Cb.a), vec4(rgb, 1.0), Cs.a);
-    }
+    result.rgb *= result.a;
 
     return Fragment(result);
 }
 #endif
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -24,17 +24,17 @@ use render_backend::DataStores;
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
-use util::{project_rect, MaxRect, TransformedRectKind};
+use util::{project_rect, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 /// Used to signal there are no segments provided with this primitive.
 const INVALID_SEGMENT_INDEX: i32 = 0xffff;
 
@@ -974,20 +974,20 @@ impl AlphaBatchBuilder {
                     clip_task_address,
                     transform_id,
                 };
 
                 match picture.context_3d {
                     // Convert all children of the 3D hierarchy root into batches.
                     Picture3DContext::In { root_data: Some(ref list), .. } => {
                         for child in list {
-                            let child_prim_instance = &picture.prim_list.prim_instances[child.anchor];
-                            let child_prim_info = &ctx.scratch.prim_info[child_prim_instance.visibility_info.0 as usize];
+                            let prim_instance = &picture.prim_list.prim_instances[child.anchor];
+                            let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize];
 
-                            let child_pic_index = match child_prim_instance.kind {
+                            let child_pic_index = match prim_instance.kind {
                                 PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
                                 PrimitiveInstanceKind::LineDecoration { .. } |
                                 PrimitiveInstanceKind::TextRun { .. } |
                                 PrimitiveInstanceKind::NormalBorder { .. } |
                                 PrimitiveInstanceKind::ImageBorder { .. } |
                                 PrimitiveInstanceKind::Rectangle { .. } |
                                 PrimitiveInstanceKind::YuvImage { .. } |
                                 PrimitiveInstanceKind::Image { .. } |
@@ -998,24 +998,24 @@ impl AlphaBatchBuilder {
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[child_pic_index.0];
 
 
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
                                 &ctx.scratch.clip_mask_instances,
-                                child_prim_info.clip_task_index,
+                                prim_info.clip_task_index,
                                 0,
                                 render_tasks,
                             ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-                            let child_header = PrimitiveHeader {
+                            let prim_header = PrimitiveHeader {
                                 local_rect: pic.local_rect,
-                                local_clip_rect: child_prim_info.combined_local_clip_rect,
+                                local_clip_rect: prim_info.combined_local_clip_rect,
                                 task_address,
                                 specific_prim_address: GpuCacheAddress::invalid(),
                                 clip_task_address,
                                 transform_id: transforms
                                     .get_id(
                                         child.spatial_node_index,
                                         root_spatial_node_index,
                                         ctx.clip_scroll_tree,
@@ -1032,17 +1032,17 @@ impl AlphaBatchBuilder {
                                 .as_ref()
                                 .expect("BUG: no surface")
                                 .resolve(
                                     render_tasks,
                                     ctx.resource_cache,
                                     gpu_cache,
                                 );
 
-                            let prim_header_index = prim_headers.push(&child_header, z_id, [
+                            let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                 uv_rect_address.as_int(),
                                 if raster_config.establishes_raster_root { 1 } else { 0 },
                                 0,
                             ]);
 
                             let key = BatchKey::new(
                                 BatchKind::SplitComposite,
                                 BlendMode::PremultipliedAlpha,
@@ -1052,17 +1052,17 @@ impl AlphaBatchBuilder {
                             let instance = SplitCompositeInstance::new(
                                 prim_header_index,
                                 child.gpu_address,
                                 z_id,
                             );
 
                             self.current_batch_list().push_single_instance(
                                 key,
-                                &child_prim_info.clip_chain.pic_clip_rect,
+                                &prim_info.clip_chain.pic_clip_rect,
                                 z_id,
                                 PrimitiveInstanceData::from(instance),
                             );
                         }
                     }
                     // Ignore the 3D pictures that are not in the root of preserve-3D
                     // hierarchy, since we process them with the root.
                     Picture3DContext::In { root_data: None, .. } => return,
@@ -1129,26 +1129,26 @@ impl AlphaBatchBuilder {
                                     );
 
                                     for tile_index in &tile_cache.tiles_to_draw {
                                         let tile = &tile_cache.tiles[tile_index.0];
 
                                         // Get the local rect of the tile.
                                         let tile_rect = tile.local_rect;
 
-                                        let tile_header = PrimitiveHeader {
+                                        let prim_header = PrimitiveHeader {
                                             local_rect: tile_rect,
                                             local_clip_rect,
                                             task_address,
                                             specific_prim_address: prim_cache_address,
                                             clip_task_address,
                                             transform_id,
                                         };
 
-                                        let prim_header_index = prim_headers.push(&tile_header, z_id, [
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Local as i32,
                                             get_shader_opacity(1.0),
                                         ]);
 
                                         let cache_item = ctx
                                             .resource_cache
                                             .get_texture_cache_item(&tile.handle);
@@ -1510,108 +1510,59 @@ impl AlphaBatchBuilder {
 
                                 self.current_batch_list().push_single_instance(
                                     key,
                                     bounding_rect,
                                     z_id,
                                     PrimitiveInstanceData::from(instance),
                                 );
                             }
-                            PictureCompositeMode::Puppet { master: Some(source) } if ctx.is_picture_surface_visible(source) => return,
-                            PictureCompositeMode::MixBlend { mode, backdrop } if ctx.is_picture_surface_visible(backdrop) => {
-                                let backdrop_picture = &ctx.prim_store.pictures[backdrop.0];
-
-                                let source_id = ctx
-                                    .surfaces[raster_config.surface_index.0]
+                            PictureCompositeMode::MixBlend(mode) => {
+                                let surface = ctx.surfaces[raster_config.surface_index.0]
                                     .surface
                                     .as_ref()
-                                    .expect("bug: source surface must be allocated by now")
-                                    .resolve_render_task_id();
-                                let backdrop_surface_id = backdrop_picture.raster_config
-                                    .as_ref()
-                                    .unwrap()
-                                    .surface_index;
-                                let backdrop_id = ctx.surfaces[backdrop_surface_id.0]
-                                    .surface
-                                    .as_ref()
-                                    .expect("bug: backdrop surface must be allocated by now")
-                                    .resolve_render_task_id();
+                                    .expect("bug: surface must be allocated by now");
+                                let cache_task_id = surface.resolve_render_task_id();
+                                let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
 
                                 let key = BatchKey::new(
                                     BatchKind::Brush(
                                         BrushBatchKind::MixBlend {
                                             task_id,
-                                            source_id,
+                                            source_id: cache_task_id,
                                             backdrop_id,
                                         },
                                     ),
-                                    non_segmented_blend_mode,
+                                    BlendMode::PremultipliedAlpha,
                                     BatchTextures::no_texture(),
                                 );
-
-                                // The trick here is to draw the picture in the space of the backdrop,
-                                // since the source can be attached to a child spatial node.
-                                let expanded_header = PrimitiveHeader {
-                                    local_rect: ctx.clip_scroll_tree
-                                        .map_rect_to_parent_space(
-                                            prim_header.local_rect,
-                                            picture.spatial_node_index,
-                                            backdrop_picture.spatial_node_index,
-                                            &backdrop_picture.local_rect, //Note: this shouldn't be used
-                                        )
-                                        .unwrap_or_else(LayoutRect::zero)
-                                        .union(&backdrop_picture.local_rect),
-                                    local_clip_rect: ctx.clip_scroll_tree
-                                        .map_rect_to_parent_space(
-                                            prim_header.local_clip_rect,
-                                            picture.spatial_node_index,
-                                            backdrop_picture.spatial_node_index,
-                                            &backdrop_picture.local_clip_rect, //Note: this shouldn't be used
-                                        )
-                                        .unwrap_or_else(LayoutRect::zero)
-                                        .union(&backdrop_picture.local_clip_rect),
-                                    transform_id: transforms
-                                        .get_id(
-                                            backdrop_picture.spatial_node_index,
-                                            root_spatial_node_index,
-                                            ctx.clip_scroll_tree,
-                                        ),
-                                    ..prim_header
-                                };
-
                                 let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
-                                let source_task_address = render_tasks.get_task_address(source_id);
-                                let prim_header_index = prim_headers.push(&expanded_header, z_id, [
+                                let source_task_address = render_tasks.get_task_address(cache_task_id);
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                     mode as u32 as i32,
                                     backdrop_task_address.0 as i32,
                                     source_task_address.0 as i32,
                                 ]);
 
                                 let instance = BrushInstance {
                                     prim_header_index,
                                     clip_task_address,
                                     segment_index: INVALID_SEGMENT_INDEX,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags,
                                     user_data: 0,
                                 };
-                                //TODO: investigate if we can do better. We can't use the `bounding_rect`
-                                // here because we effectively merge the call with the backdrop,
-                                // and the instance for the backdrop isn't available here.
-                                let conservative_bounding_rect = PictureRect::max_rect();
 
                                 self.current_batch_list().push_single_instance(
                                     key,
-                                    &conservative_bounding_rect,
+                                    bounding_rect,
                                     z_id,
                                     PrimitiveInstanceData::from(instance),
                                 );
                             }
-                            PictureCompositeMode::Puppet { .. } |
-                            PictureCompositeMode::MixBlend { .. } |
                             PictureCompositeMode::Blit(_) => {
                                 let surface = ctx.surfaces[raster_config.surface_index.0]
                                     .surface
                                     .as_ref()
                                     .expect("bug: surface must be allocated by now");
                                 let cache_task_id = surface.resolve_render_task_id();
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -6,17 +6,17 @@ use api::{ExternalScrollId, LayoutPoint,
 use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation, ScrollSensitivity};
 use api::{LayoutSize, LayoutTransform, PropertyBinding, TransformStyle, WorldPoint};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintableTree, PrintTree, PrintTreePrinter};
 use scene::SceneProperties;
 use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind};
 use std::ops;
-use util::{project_rect, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset};
+use util::{LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset};
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
@@ -206,45 +206,16 @@ impl ClipScrollTree {
 
         Some(RelativeTransform {
             flattened: transform,
             visible_face,
             is_perspective,
         })
     }
 
-    /// Map a rectangle in some child space to a parent.
-    /// Doesn't handle preserve-3d islands.
-    pub fn map_rect_to_parent_space(
-        &self,
-        mut rect: LayoutRect,
-        child_index: SpatialNodeIndex,
-        parent_index: SpatialNodeIndex,
-        parent_bounds: &LayoutRect,
-    ) -> Option<LayoutRect> {
-        if child_index == parent_index {
-            return Some(rect);
-        }
-        assert!(child_index.0 > parent_index.0);
-
-        let child = &self.spatial_nodes[child_index.0 as usize];
-        let parent = &self.spatial_nodes[parent_index.0 as usize];
-
-        let mut coordinate_system_id = child.coordinate_system_id;
-        rect = child.coordinate_system_relative_scale_offset.map_rect(&rect);
-
-        while coordinate_system_id != parent.coordinate_system_id {
-            let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
-            coordinate_system_id = coord_system.parent.expect("invalid parent!");
-            rect = project_rect(&coord_system.transform, &rect, parent_bounds)?;
-        }
-
-        Some(parent.coordinate_system_relative_scale_offset.unmap_rect(&rect))
-    }
-
     /// Returns true if the spatial node is the same as the parent, or is
     /// a child of the parent.
     pub fn is_same_or_child_of(
         &self,
         spatial_node_index: SpatialNodeIndex,
         parent_spatial_node_index: SpatialNodeIndex,
     ) -> bool {
         let mut index = spatial_node_index;
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -1321,54 +1321,33 @@ impl<'a> DisplayListFlattener<'a> {
         let is_pipeline_root =
             self.sc_stack.last().map_or(true, |sc| sc.pipeline_id != pipeline_id);
         let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
             Some(pipeline_id)
         } else {
             None
         };
 
-        // Figure out if the parent is in 3D context,
+        // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
-        let (parent_is_3d, extra_3d_picture, backdrop_picture) = match self.sc_stack.last_mut() {
-            Some(ref mut sc) if composite_ops.mix_blend_mode.is_some() => {
-                // Cut the sequence of children before starting a mix-blend stacking context,
-                // so that we have a source picture for applying the blending operator.
-                let backdrop_picture = sc.cut_item_sequence(
+        let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() {
+            Some(sc) => {
+                // Cut the sequence of flat children before starting a child stacking context,
+                // so that the relative order between them and our current SC is preserved.
+                let extra_instance = sc.cut_flat_item_sequence(
                     &mut self.prim_store,
                     &mut self.interners,
-                    PictureCompositeMode::Puppet { master: None },
-                    Picture3DContext::Out,
                 );
-                (false, None, backdrop_picture)
-            }
-            Some(ref mut sc) if sc.is_3d() => {
-                let flat_items_context_3d = match sc.context_3d {
-                    Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
-                        root_data: None,
-                        ancestor_index,
-                    },
-                    Picture3DContext::Out => panic!("Unexpected out of 3D context"),
-                };
-                // Cut the sequence of flat children before starting a child stacking context,
-                // so that the relative order between them and our current SC is preserved.
-                let extra_picture = sc.cut_item_sequence(
-                    &mut self.prim_store,
-                    &mut self.interners,
-                    PictureCompositeMode::Blit(BlitReason::PRESERVE3D),
-                    flat_items_context_3d,
-                );
-
-                (true, extra_picture, None)
-            }
-            Some(_) | None => (false, None, None),
+                (sc.is_3d(), extra_instance)
+            },
+            None => (false, None),
         };
 
-        if let Some((_picture_index, instance)) = extra_3d_picture {
+        if let Some(instance) = extra_3d_instance {
             self.add_primitive_instance_to_3d_root(instance);
         }
 
         // If this is preserve-3d *or* the parent is, then this stacking
         // context is participating in the 3d rendering context. In that
         // case, hoist the picture up to the 3d rendering context
         // container, so that it's rendered as a sibling with other
         // elements in this context.
@@ -1396,36 +1375,33 @@ impl<'a> DisplayListFlattener<'a> {
         } else {
             Picture3DContext::Out
         };
 
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        let mut blit_reason = BlitReason::empty();
-        if clip_chain_id != ClipChainId::NONE {
-            blit_reason |= BlitReason::CLIP
-        }
-        if participating_in_3d_context {
-            blit_reason |= BlitReason::PRESERVE3D;
-        }
+        let blit_reason = if clip_chain_id == ClipChainId::NONE {
+            BlitReason::empty()
+        } else {
+            BlitReason::CLIP
+        };
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
             primitives: Vec::new(),
             pipeline_id,
             is_backface_visible,
             requested_raster_space,
             spatial_node_index,
             clip_chain_id,
             frame_output_pipeline_id,
             composite_ops,
-            backdrop_picture,
             blit_reason,
             transform_style,
             context_3d,
             create_tile_cache,
         });
     }
 
     pub fn pop_stacking_context(&mut self) {
@@ -1435,86 +1411,92 @@ impl<'a> DisplayListFlattener<'a> {
         // of creating a picture, just append the primitive list to the parent stacking
         // context as a short cut. This serves two purposes:
         // (a) It's an optimization to reduce picture count and allocations, as display lists
         //     often contain a lot of these stacking contexts that don't require pictures or
         //     off-screen surfaces.
         // (b) It's useful for the initial version of picture caching in gecko, by enabling
         //     is to just look for interesting scroll roots on the root stacking context,
         //     without having to consider cuts at stacking context boundaries.
-        if let Some(parent_sc) = self.sc_stack.last_mut() {
-            if stacking_context.is_redundant(
-                parent_sc,
-                self.clip_scroll_tree,
-            ) {
-                // If the parent context primitives list is empty, it's faster
-                // to assign the storage of the popped context instead of paying
-                // the copying cost for extend.
-                if parent_sc.primitives.is_empty() {
-                    parent_sc.primitives = stacking_context.primitives;
-                } else {
-                    parent_sc.primitives.extend(stacking_context.primitives);
+        let parent_is_empty = match self.sc_stack.last_mut() {
+            Some(parent_sc) => {
+                if stacking_context.is_redundant(
+                    parent_sc,
+                    self.clip_scroll_tree,
+                ) {
+                    // If the parent context primitives list is empty, it's faster
+                    // to assign the storage of the popped context instead of paying
+                    // the copying cost for extend.
+                    if parent_sc.primitives.is_empty() {
+                        parent_sc.primitives = stacking_context.primitives;
+                    } else {
+                        parent_sc.primitives.extend(stacking_context.primitives);
+                    }
+                    return;
                 }
-                return;
-            }
-        }
+                parent_sc.primitives.is_empty()
+            },
+            None => true,
+        };
 
         if stacking_context.create_tile_cache {
             self.setup_picture_caching(
                 &mut stacking_context.primitives,
             );
         }
 
         // An arbitrary large clip rect. For now, we don't
         // specify a clip specific to the stacking context.
         // However, now that they are represented as Picture
         // primitives, we can apply any kind of clip mask
         // to them, as for a normal primitive. This is needed
         // to correctly handle some CSS cases (see #1957).
         let max_clip = LayoutRect::max_rect();
 
-        let leaf_composite_mode = if stacking_context.blit_reason.is_empty() {
-            // By default, this picture will be collapsed into
-            // the owning target.
-            None
-        } else {
-            // Add a dummy composite filter if the SC has to be isolated.
-            Some(PictureCompositeMode::Blit(stacking_context.blit_reason))
-        };
-
-        let leaf_context_3d = match stacking_context.context_3d {
+        let (leaf_context_3d, leaf_composite_mode, leaf_output_pipeline_id) = match stacking_context.context_3d {
             // TODO(gw): For now, as soon as this picture is in
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
-            Picture3DContext::In { ancestor_index, .. } => {
-                assert!(!leaf_composite_mode.is_none());
-                Picture3DContext::In { root_data: None, ancestor_index }
-            }
-            Picture3DContext::Out => Picture3DContext::Out,
+            Picture3DContext::In { ancestor_index, .. } => (
+                Picture3DContext::In { root_data: None, ancestor_index },
+                Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason)),
+                None,
+            ),
+            Picture3DContext::Out => (
+                Picture3DContext::Out,
+                if stacking_context.blit_reason.is_empty() {
+                    // By default, this picture will be collapsed into
+                    // the owning target.
+                    None
+                } else {
+                    // Add a dummy composite filter if the SC has to be isolated.
+                    Some(PictureCompositeMode::Blit(stacking_context.blit_reason))
+                },
+                stacking_context.frame_output_pipeline_id
+            ),
         };
 
-        let leaf_prim_list = PrimitiveList::new(
-            stacking_context.primitives,
-            &self.interners,
-        );
         // Add picture for this actual stacking context contents to render into.
         let leaf_pic_index = PictureIndex(self.prim_store.pictures
             .alloc()
             .init(PicturePrimitive::new_image(
                 leaf_composite_mode,
                 leaf_context_3d,
                 stacking_context.pipeline_id,
-                stacking_context.frame_output_pipeline_id,
+                leaf_output_pipeline_id,
                 true,
                 stacking_context.requested_raster_space,
-                leaf_prim_list,
+                PrimitiveList::new(
+                    stacking_context.primitives,
+                    &self.interners,
+                ),
                 stacking_context.spatial_node_index,
                 max_clip,
                 None,
                 PictureOptions::default(),
             ))
         );
 
         // Create a chain of pictures based on presence of filters,
@@ -1645,36 +1627,30 @@ impl<'a> DisplayListFlattener<'a> {
                 println!("\tis a composite picture for a stacking context with {:?}", filter);
             }
 
             // Run the optimize pass on this picture, to see if we can
             // collapse opacity and avoid drawing to an off-screen surface.
             self.prim_store.optimize_picture_if_possible(current_pic_index);
         }
 
-        // Same for mix-blend-mode, except we can skip if the backdrop doesn't have any primitives.
+        // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent
+        // stacking context.
         // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is:
         // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs)
         // where
         // Cs = Source color
         // ab = Backdrop alpha
         // Cb = Backdrop color
         //
         // If we're the first primitive within a stacking context, then we can guarantee that the
         // backdrop alpha will be 0, and then the blend equation collapses to just
         // Cs = Cs, and the blend mode isn't taken into account at all.
-        if let (Some(mode), Some((backdrop, backdrop_instance))) = (stacking_context.composite_ops.mix_blend_mode, stacking_context.backdrop_picture.take()) {
-            let composite_mode = Some(PictureCompositeMode::MixBlend { mode, backdrop });
-
-            // We need to make the backdrop picture to be at the same level as the content,
-            // to be available as a source for composition...
-            if let Some(parent_sc) = self.sc_stack.last_mut() {
-                // Not actually rendered, due to `PictureCompositeMode::Puppet`, unless the blend picture is culled.
-                parent_sc.primitives.push(backdrop_instance);
-            }
+        let has_mix_blend = if let (Some(mix_blend_mode), false) = (stacking_context.composite_ops.mix_blend_mode, parent_is_empty) {
+            let composite_mode = Some(PictureCompositeMode::MixBlend(mix_blend_mode));
 
             let blend_pic_index = PictureIndex(self.prim_store.pictures
                 .alloc()
                 .init(PicturePrimitive::new_image(
                     composite_mode,
                     Picture3DContext::Out,
                     stacking_context.pipeline_id,
                     None,
@@ -1686,49 +1662,54 @@ impl<'a> DisplayListFlattener<'a> {
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
                     None,
                     PictureOptions::default(),
                 ))
             );
 
-            // Assoiate the backdrop picture with the blend.
-            self.prim_store.pictures[backdrop.0].requested_composite_mode = Some(PictureCompositeMode::Puppet {
-                master: Some(blend_pic_index),
-            });
-
             current_pic_index = blend_pic_index;
             cur_instance = create_prim_instance(
-                current_pic_index,
+                blend_pic_index,
                 composite_mode.into(),
                 stacking_context.is_backface_visible,
                 ClipChainId::NONE,
                 stacking_context.spatial_node_index,
                 &mut self.interners,
             );
 
             if cur_instance.is_chased() {
-                println!("\tis a mix-blend picture for a stacking context with {:?}", mode);
+                println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode);
             }
-        }
+            true
+        } else {
+            false
+        };
 
         // Set the stacking context clip on the outermost picture in the chain,
         // unless we already set it on the leaf picture.
         cur_instance.clip_chain_id = stacking_context.clip_chain_id;
 
         // The primitive instance for the remainder of flat children of this SC
         // if it's a part of 3D hierarchy but not the root of it.
         let trailing_children_instance = match self.sc_stack.last_mut() {
             // Preserve3D path (only relevant if there are no filters/mix-blend modes)
             Some(ref parent_sc) if parent_sc.is_3d() => {
                 Some(cur_instance)
             }
             // Regular parenting path
             Some(ref mut parent_sc) => {
+                // If we have a mix-blend-mode, the stacking context needs to be isolated
+                // to blend correctly as per the CSS spec.
+                // If not already isolated for some other reason,
+                // make this picture as isolated.
+                if has_mix_blend {
+                    parent_sc.blit_reason |= BlitReason::ISOLATE;
+                }
                 parent_sc.primitives.push(cur_instance);
                 None
             }
             // This must be the root stacking context
             None => {
                 self.root_pic_index = current_pic_index;
                 None
             }
@@ -2708,19 +2689,16 @@ struct FlattenedStackingContext {
     /// If set, this should be provided to caller
     /// as an output texture.
     frame_output_pipeline_id: Option<PipelineId>,
 
     /// The list of filters / mix-blend-mode for this
     /// stacking context.
     composite_ops: CompositeOps,
 
-    /// For a mix-blend stacking context, specify the picture index for backdrop.
-    backdrop_picture: Option<(PictureIndex, PrimitiveInstance)>,
-
     /// Bitfield of reasons this stacking context needs to
     /// be an offscreen surface.
     blit_reason: BlitReason,
 
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// CSS transform-style property.
@@ -2753,18 +2731,17 @@ impl FlattenedStackingContext {
         // If there are filters / mix-blend-mode
         if !self.composite_ops.filters.is_empty() {
             return false;
         }
 
         // We can skip mix-blend modes if they are the first primitive in a stacking context,
         // see pop_stacking_context for a full explanation.
         if !self.composite_ops.mix_blend_mode.is_none() &&
-           !self.backdrop_picture.is_none()
-        {
+           !parent.primitives.is_empty() {
             return false;
         }
 
         // If backface visibility is different
         if self.is_backface_visible != parent.is_backface_visible {
             return false;
         }
 
@@ -2792,34 +2769,39 @@ impl FlattenedStackingContext {
         if self.create_tile_cache {
             return false;
         }
 
         // It is redundant!
         true
     }
 
-    /// Cut the sequence of the immediate children of a stacking context
+    /// For a Preserve3D context, cut the sequence of the immediate flat children
     /// recorded so far and generate a picture from them.
-    fn cut_item_sequence(
+    pub fn cut_flat_item_sequence(
         &mut self,
         prim_store: &mut PrimitiveStore,
         interners: &mut Interners,
-        composite_mode: PictureCompositeMode,
-        context_3d: Picture3DContext<OrderedPictureChild>,
-    ) -> Option<(PictureIndex, PrimitiveInstance)> {
-        if self.primitives.is_empty() {
+    ) -> Option<PrimitiveInstance> {
+        if !self.is_3d() || self.primitives.is_empty() {
             return None
         }
+        let flat_items_context_3d = match self.context_3d {
+            Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
+                root_data: None,
+                ancestor_index,
+            },
+            Picture3DContext::Out => panic!("Unexpected out of 3D context"),
+        };
 
         let pic_index = PictureIndex(prim_store.pictures
             .alloc()
             .init(PicturePrimitive::new_image(
-                Some(composite_mode),
-                context_3d,
+                Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)),
+                flat_items_context_3d,
                 self.pipeline_id,
                 None,
                 true,
                 self.requested_raster_space,
                 PrimitiveList::new(
                     mem::replace(&mut self.primitives, Vec::new()),
                     interners,
                 ),
@@ -2834,17 +2816,17 @@ impl FlattenedStackingContext {
             pic_index,
             PictureCompositeKey::Identity,
             self.is_backface_visible,
             self.clip_chain_id,
             self.spatial_node_index,
             interners,
         );
 
-        Some((pic_index, prim_instance))
+        Some(prim_instance)
     }
 }
 
 /// A primitive that is added while a shadow context is
 /// active is stored as a pending primitive and only
 /// added to pictures during pop_all_shadows.
 pub struct PendingPrimitive<T> {
     clip_and_scroll: ScrollNodeAndClipChain,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1868,43 +1868,33 @@ pub struct RasterConfig {
     /// Whether this picture establishes a rasterization root.
     pub establishes_raster_root: bool,
 }
 
 bitflags! {
     /// A set of flags describing why a picture may need a backing surface.
     #[cfg_attr(feature = "capture", derive(Serialize))]
     pub struct BlitReason: u32 {
+        /// Mix-blend-mode on a child that requires isolation.
+        const ISOLATE = 1;
         /// Clip node that _might_ require a surface.
-        const CLIP = 1;
+        const CLIP = 2;
         /// Preserve-3D requires a surface for plane-splitting.
-        const PRESERVE3D = 2;
+        const PRESERVE3D = 4;
     }
 }
 
 /// Specifies how this Picture should be composited
 /// onto the target it belongs to.
 #[allow(dead_code)]
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub enum PictureCompositeMode {
-    /// Don't composite this picture in a standard way,
-    /// can be used for pictures that need to be isolated but used
-    /// manually, e.g. for the backdrop of mix-blend pictures.
-    Puppet {
-        /// The master picture that actually handles compositing
-        /// of this one. If that picture turns out to be invisible,
-        /// the puppet mode becomes a regular blit.
-        master: Option<PictureIndex>,
-    },
     /// Apply CSS mix-blend-mode effect.
-    MixBlend {
-        mode: MixBlendMode,
-        backdrop: PictureIndex,
-    },
+    MixBlend(MixBlendMode),
     /// Apply a CSS filter (except component transfer).
     Filter(FilterOp),
     /// Apply a component transfer filter.
     ComponentTransferFilter(FilterDataHandle),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit(BlitReason),
     /// Used to cache a picture as a series of tiles.
@@ -3076,16 +3066,47 @@ impl PicturePrimitive {
 
                     // segment rect / extra data
                     request.push(shadow_rect);
                     request.push([0.0, 0.0, 0.0, 0.0]);
                 }
 
                 PictureSurface::RenderTask(render_task_id)
             }
+            PictureCompositeMode::MixBlend(..) => {
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    device_pixel_scale,
+                    true,
+                );
+
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    child_tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                    device_pixel_scale,
+                );
+
+                let readback_task_id = frame_state.render_tasks.add(
+                    RenderTask::new_readback(clipped)
+                );
+
+                self.secondary_render_task_id = Some(readback_task_id);
+                surfaces[surface_index.0].tasks.push(readback_task_id);
+
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                surfaces[surface_index.0].tasks.push(render_task_id);
+                PictureSurface::RenderTask(render_task_id)
+            }
             PictureCompositeMode::Filter(filter) => {
                 if let FilterOp::ColorMatrix(m) = filter {
                     if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
                         for i in 0..5 {
                             request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
                 }
@@ -3135,23 +3156,21 @@ impl PicturePrimitive {
                     pic_context.raster_spatial_node_index,
                     device_pixel_scale,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
-            PictureCompositeMode::Puppet { .. } |
-            PictureCompositeMode::MixBlend { .. } |
             PictureCompositeMode::Blit(_) => {
                 // The SplitComposite shader used for 3d contexts doesn't snap
                 // to pixels, so we shouldn't snap our uv coordinates either.
                 let supports_snapping = match self.context_3d {
-                    Picture3DContext::In { .. } => false,
+                    Picture3DContext::In{ .. } => false,
                     _ => true,
                 };
 
                 let uv_rect_kind = calculate_uv_rect_kind(
                     &pic_rect,
                     &transform,
                     &clipped,
                     device_pixel_scale,
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -59,17 +59,17 @@ pub enum PictureCompositeKey {
     Saturation,
     Color,
     Luminosity,
 }
 
 impl From<Option<PictureCompositeMode>> for PictureCompositeKey {
     fn from(mode: Option<PictureCompositeMode>) -> Self {
         match mode {
-            Some(PictureCompositeMode::MixBlend { mode, .. }) => {
+            Some(PictureCompositeMode::MixBlend(mode)) => {
                 match mode {
                     MixBlendMode::Normal => PictureCompositeKey::Identity,
                     MixBlendMode::Multiply => PictureCompositeKey::Multiply,
                     MixBlendMode::Screen => PictureCompositeKey::Screen,
                     MixBlendMode::Overlay => PictureCompositeKey::Overlay,
                     MixBlendMode::Darken => PictureCompositeKey::Darken,
                     MixBlendMode::Lighten => PictureCompositeKey::Lighten,
                     MixBlendMode::ColorDodge => PictureCompositeKey::ColorDodge,
@@ -118,17 +118,16 @@ impl From<Option<PictureCompositeMode>> 
                         PictureCompositeKey::ColorMatrix(quantized_values)
                     }
                     FilterOp::ComponentTransfer => unreachable!(),
                 }
             }
             Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
                 PictureCompositeKey::ComponentTransfer(handle.uid())
             }
-            Some(PictureCompositeMode::Puppet { .. }) |
             Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
                 PictureCompositeKey::Identity
             }
         }
     }
 }
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -402,16 +402,17 @@ pub struct RenderTaskData {
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
     ClipRegion(ClipRegionTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     #[allow(dead_code)]
     Glyph(GlyphTask),
+    Readback(DeviceIntRect),
     Scaling(ScalingTask),
     Blit(BlitTask),
     Border(BorderTask),
     LineDecoration(LineDecorationTask),
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -490,16 +491,25 @@ impl RenderTask {
                 root_spatial_node_index,
                 device_pixel_scale,
             }),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
+    pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
+        RenderTask::with_dynamic_location(
+            screen_rect.size,
+            Vec::new(),
+            RenderTaskKind::Readback(screen_rect),
+            ClearMode::Transparent,
+        )
+    }
+
     pub fn new_blit(
         size: DeviceIntSize,
         source: BlitSource,
     ) -> Self {
         RenderTask::new_blit_with_padding(size, &DeviceIntSideOffsets::zero(), source)
     }
 
     pub fn new_blit_with_padding(
@@ -840,17 +850,18 @@ impl RenderTask {
             }),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     fn uv_rect_kind(&self) -> UvRectKind {
         match self.kind {
-            RenderTaskKind::CacheMask(..) => {
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Readback(..) => {
                 unreachable!("bug: unexpected render task");
             }
 
             RenderTaskKind::Picture(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::VerticalBlur(ref task) |
@@ -913,16 +924,17 @@ impl RenderTask {
                     task.blur_std_deviation,
                     0.0,
                     0.0,
                 ]
             }
             RenderTaskKind::Glyph(_) => {
                 [0.0, 1.0, 0.0]
             }
+            RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
         };
 
@@ -953,16 +965,17 @@ impl RenderTask {
             RenderTaskKind::Picture(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::ClipRegion(..) |
+            RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("texture handle not supported for this task kind");
             }
@@ -1005,16 +1018,18 @@ impl RenderTask {
             RenderTaskLocation::TextureCache {layer, rect, .. } => {
                 (rect, RenderTargetIndex(layer as usize))
             }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
+            RenderTaskKind::Readback(..) => RenderTargetKind::Color,
+
             RenderTaskKind::LineDecoration(..) => RenderTargetKind::Color,
 
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
@@ -1057,16 +1072,17 @@ impl RenderTask {
         let (cache_handle, uv_rect_kind) = match self.kind {
             RenderTaskKind::HorizontalBlur(ref mut info) |
             RenderTaskKind::VerticalBlur(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::Picture(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::LineDecoration(..) |
             RenderTaskKind::Glyph(..) => {
                 return;
@@ -1107,16 +1123,20 @@ 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(ref rect) => {
+                pt.new_level("Readback".to_owned());
+                pt.add_item(format!("rect: {:?}", rect));
+            }
             RenderTaskKind::Scaling(ref kind) => {
                 pt.new_level("Scaling".to_owned());
                 pt.add_item(format!("kind: {:?}", kind));
             }
             RenderTaskKind::Border(..) => {
                 pt.new_level("Border".to_owned());
             }
             RenderTaskKind::Blit(ref task) => {
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -77,17 +77,17 @@ use profiler::{BackendProfileCounters, F
 use profiler::{Profiler, ChangeIndicator};
 use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::{FrameId, RenderBackend};
 use scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
 use shade::{Shaders, WrShaders};
 use smallvec::SmallVec;
-use render_task::RenderTaskTree;
+use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 use util::drain_filter;
 
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
@@ -2336,16 +2336,21 @@ impl Renderer {
 
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Scalings",
             target.scalings.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
+            "Readbacks",
+            target.readbacks.len(),
+        );
+        debug_target.add(
+            debug_server::BatchKind::Cache,
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
@@ -3075,16 +3080,84 @@ impl Renderer {
                 self.profile_counters.draw_calls.inc();
                 stats.total_draw_calls += 1;
             }
         }
 
         self.profile_counters.vertices.add(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 cache_texture = self.texture_resolver
+            .resolve(&TextureSource::PrevPassColor)
+            .unwrap();
+
+        // 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 {
+            RenderTaskKind::Picture(ref task_info) => task_info.content_origin,
+            _ => panic!("bug: composite on non-picture?"),
+        };
+        let source_screen_origin = match source.kind {
+            RenderTaskKind::Picture(ref task_info) => task_info.content_origin,
+            _ => 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::Texture {
+            texture: cache_texture,
+            layer: readback_layer.0 as usize,
+            with_depth: false,
+        };
+        self.device.bind_draw_target(cache_draw_target);
+
+        let mut src = DeviceIntRect::new(
+            source_screen_origin + (backdrop_rect.origin - backdrop_screen_origin),
+            readback_rect.size,
+        );
+        let mut dest = readback_rect.to_i32();
+
+        // 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.bind_read_target(draw_target.into());
+        self.device.blit_render_target(src, dest, 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();
+
+        if uses_scissor {
+            self.device.enable_scissor();
+        }
+    }
+
     //TODO: make this nicer. Currently we can't accept `&mut self` because the `DrawTarget` parameter
     // needs to borrow self.texture_resolver
     fn handle_blits(
         gpu_profile: &mut GpuProfiler<GpuProfileTag>,
         device: &mut Device,
         texture_resolver: &TextureResolver,
         blits: &[BlitJob],
         render_tasks: &RenderTaskTree,
@@ -3406,16 +3479,30 @@ impl Renderer {
                                 //
                                 self.device.set_blend_mode_subpixel_with_bg_color_pass0();
                                 self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass0 as _);
                             }
                         }
                         prev_blend_mode = batch.key.blend_mode;
                     }
 
+                    // Handle special case readback for composites.
+                    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_profile.start_timer(batch.key.kind.sampler_tag());
 
                     iterate_regions(
                         &alpha_batch_container.regions,
                         |region| {
                             if let Some(region) = region {
                                 let scissor_rect = draw_target.build_scissor_rect(
                                     Some(region),
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -15,17 +15,17 @@ use euclid::{TypedPoint2D, TypedVector2D
 use frame_builder::FrameGlobalResources;
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::{RecordedDirtyRegion, SurfaceInfo};
-use prim_store::{PictureIndex, PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
+use prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
 use profiler::FrameProfileCounters;
 use render_backend::{DataStores, FrameId};
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::{ArrayAllocationTracker, FreeRectSlice};
 #[cfg(feature = "pathfinder")]
@@ -57,28 +57,16 @@ pub struct RenderTargetContext<'a, 'rc> 
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub data_stores: &'a DataStores,
     pub surfaces: &'a [SurfaceInfo],
     pub scratch: &'a PrimitiveScratchBuffer,
     pub screen_world_rect: WorldRect,
     pub globals: &'a FrameGlobalResources,
 }
 
-impl<'a, 'rc> RenderTargetContext<'a, 'rc> {
-    /// Returns true if a picture has a surface that is visible.
-    pub fn is_picture_surface_visible(&self, index: PictureIndex) -> bool {
-        match self.prim_store.pictures[index.0].raster_config {
-            Some(ref raster_config) => {
-                self.surfaces[raster_config.surface_index.0].surface.is_some()
-            }
-            None => false,
-        }
-    }
-}
-
 /// Represents a number of rendering operations on a surface.
 ///
 /// In graphics parlance, a "render target" usually means "a surface (texture or
 /// framebuffer) bound to the output of a shader". This trait has a slightly
 /// different meaning, in that it represents the operations on that surface
 /// _before_ it's actually bound and rendered. So a `RenderTarget` is built by
 /// the `RenderBackend` by inserting tasks, and then shipped over to the
 /// `Renderer` where a device surface is resolved and the tasks are transformed
@@ -355,16 +343,17 @@ pub struct GlyphJob;
 /// See `RenderTarget`.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ColorRenderTarget {
     pub alpha_batch_containers: Vec<AlphaBatchContainer>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
+    pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInstance>,
     pub blits: Vec<BlitJob>,
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
     alpha_tasks: Vec<RenderTaskId>,
     screen_size: DeviceIntSize,
     // Track the used rect of the render target, so that
     // we can set a scissor rect and only clear to the
@@ -376,16 +365,17 @@ impl RenderTarget for ColorRenderTarget 
     fn new(
         screen_size: DeviceIntSize,
         _: bool,
     ) -> Self {
         ColorRenderTarget {
             alpha_batch_containers: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
+            readbacks: Vec::new(),
             scalings: Vec::new(),
             blits: Vec::new(),
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
             screen_size,
             used_rect: DeviceIntRect::zero(),
         }
     }
@@ -507,16 +497,19 @@ impl RenderTarget for ColorRenderTarget 
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::LineDecoration(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Glyph(..) => {
                 // FIXME(pcwalton): Support color glyphs.
                 panic!("Glyphs should not be added to color target!");
             }
+            RenderTaskKind::Readback(device_rect) => {
+                self.readbacks.push(device_rect);
+            }
             RenderTaskKind::Scaling(..) => {
                 self.scalings.push(ScalingInstance {
                     task_address: render_tasks.get_task_address(task_id),
                     src_task_address: render_tasks.get_task_address(task.children[0]),
                 });
             }
             RenderTaskKind::Blit(ref task_info) => {
                 match task_info.source {
@@ -639,16 +632,17 @@ impl RenderTarget for AlphaRenderTarget 
             }
             ClearMode::DontCare => {}
             ClearMode::Transparent => {
                 panic!("bug: invalid clear mode for alpha task");
             }
         }
 
         match task.kind {
+            RenderTaskKind::Readback(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
@@ -820,16 +814,17 @@ impl TextureCacheRenderTarget {
             }
             RenderTaskKind::Glyph(ref mut task_info) => {
                 self.add_glyph_task(task_info, target_rect.0)
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
     }
 
     #[cfg(feature = "pathfinder")]
     fn add_glyph_task(&mut self, task_info: &mut GlyphTask, target_rect: DeviceIntRect) {
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/blend/multiply-2-ref.yaml
@@ -0,0 +1,6 @@
+---
+root:
+  items:
+        - type: rect
+          bounds: [0, 0, 100, 100]
+          color: [0, 128, 0]
--- a/gfx/wr/wrench/reftests/blend/multiply-2.yaml
+++ b/gfx/wr/wrench/reftests/blend/multiply-2.yaml
@@ -1,14 +1,13 @@
-# test that we handle the backdrop size to be smaller than the source
 ---
 root:
   items:
         - type: rect
-          bounds: [25, 25, 50, 50]
-          color: green
+          bounds: [0, 0, 100, 100]
+          color: [0, 255, 0]
         - type: stacking-context
           bounds: [0, 0, 100, 100]
           mix-blend-mode: multiply
           items:
             - type: rect
               bounds: [0, 0, 100, 100]
-              color: green
+              color: [255, 128, 0]
deleted file mode 100644
--- a/gfx/wr/wrench/reftests/blend/multiply-3-ref.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-root:
-  items:
-        - type: rect
-          bounds: [0, 0, 100, 100]
-          color: [0, 128, 0]
--- a/gfx/wr/wrench/reftests/blend/multiply-3.yaml
+++ b/gfx/wr/wrench/reftests/blend/multiply-3.yaml
@@ -3,11 +3,15 @@ root:
   items:
         - type: rect
           bounds: [0, 0, 100, 100]
           color: [0, 255, 0]
         - type: stacking-context
           bounds: [0, 0, 100, 100]
           mix-blend-mode: multiply
           items:
-            - type: rect
+            - type: stacking-context
               bounds: [0, 0, 100, 100]
-              color: [255, 128, 0]
+              mix-blend-mode: multiply
+              items:
+                - type: rect
+                  bounds: [0, 0, 100, 100]
+                  color: [255, 128, 0]
deleted file mode 100644
--- a/gfx/wr/wrench/reftests/blend/multiply-4.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
----
-root:
-  items:
-        - type: rect
-          bounds: [0, 0, 100, 100]
-          color: [0, 255, 0]
-        - type: stacking-context
-          bounds: [0, 0, 100, 100]
-          mix-blend-mode: multiply
-          items:
-            - type: stacking-context
-              bounds: [0, 0, 100, 100]
-              mix-blend-mode: multiply
-              items:
-                - type: rect
-                  bounds: [0, 0, 100, 100]
-                  color: [255, 128, 0]
--- a/gfx/wr/wrench/reftests/blend/reftest.list
+++ b/gfx/wr/wrench/reftests/blend/reftest.list
@@ -1,12 +1,11 @@
 == multiply.yaml multiply-ref.yaml
-== multiply-2.yaml multiply-ref.yaml
-== multiply-3.yaml multiply-3-ref.yaml
-== color_targets(2) alpha_targets(0) multiply-4.yaml multiply-3-ref.yaml
+== multiply-2.yaml multiply-2-ref.yaml
+== color_targets(3) alpha_targets(0) multiply-3.yaml multiply-2-ref.yaml
 == difference.yaml difference-ref.yaml
 fuzzy(1,10000) == difference-transparent.yaml difference-transparent-ref.yaml
 == darken.yaml darken-ref.yaml
 == lighten.yaml lighten-ref.yaml
 
 == repeated-difference.yaml repeated-difference-ref.yaml
 
 == isolated.yaml isolated-ref.yaml
@@ -18,9 +17,8 @@ fuzzy(1,10000) == difference-transparent
 == large.yaml large-ref.yaml
 
 # fuzzy because dithering is different for gradients
 # drawn in different render targets
 fuzzy(1,2502) == transparent-composite-1.yaml transparent-composite-1-ref.yaml
 fuzzy(1,2502) == transparent-composite-2.yaml transparent-composite-2-ref.yaml
 
 == multi-mix-blend-mode.yaml multi-mix-blend-mode-ref.yaml
-fuzzy(50,8) == transform-source.yaml transform-source-ref.yaml
deleted file mode 100644
--- a/gfx/wr/wrench/reftests/blend/transform-source-ref.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-root:
-  items:
-    - type: stacking-context
-      bounds: [0, 0, 0, 0]
-      transform: rotate-z(60)
-      items:
-        - type: rect
-          bounds: [25, -100, 150, 150]
-          color: blue
-    - type: rect
-      bounds: [25, 25, 100, 100]
-      color: black
deleted file mode 100644
--- a/gfx/wr/wrench/reftests/blend/transform-source.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-# test that we handle the source stacking context being transformed
----
-root:
-  items:
-    - type: rect
-      bounds: [25, 25, 100, 100]
-      color: green
-    - type: stacking-context
-      bounds: [0, 0, 0, 0]
-      mix-blend-mode: multiply
-      transform: rotate-z(60)
-      items:
-        - type: rect
-          bounds: [25, -100, 150, 150]
-          color: blue
--- a/gfx/wr/wrench/src/main.rs
+++ b/gfx/wr/wrench/src/main.rs
@@ -544,35 +544,33 @@ fn main() {
 fn render<'a>(
     wrench: &mut Wrench,
     window: &mut WindowWrapper,
     size: FramebufferIntSize,
     events_loop: &mut Option<winit::EventsLoop>,
     subargs: &clap::ArgMatches<'a>,
 ) {
     let input_path = subargs.value_of("INPUT").map(PathBuf::from).unwrap();
-    let mut show_stats = false;
 
     // If the input is a directory, we are looking at a capture.
-    let mut thing = if input_path.is_dir() {
+    let mut thing = if input_path.as_path().is_dir() {
         let mut documents = wrench.api.load_capture(input_path);
         println!("loaded {:?}", documents.iter().map(|cd| cd.document_id).collect::<Vec<_>>());
         let captured = documents.swap_remove(0);
         if let Some(fb_size) = wrench.renderer.framebuffer_size() {
             window.resize(fb_size);
         }
         wrench.document_id = captured.document_id;
         Box::new(captured) as Box<WrenchThing>
     } else {
         let extension = input_path
             .extension()
             .expect("Tried to render with an unknown file type.")
             .to_str()
             .expect("Tried to render with an unknown file type.");
-        show_stats = true; // show when invoked on single files
 
         match extension {
             "yaml" => Box::new(YamlFrameReader::new_from_args(subargs)) as Box<WrenchThing>,
             "bin" => Box::new(BinaryFrameReader::new_from_args(subargs)) as Box<WrenchThing>,
             _ => panic!("Tried to render with an unknown file type."),
         }
     };
 
@@ -769,24 +767,19 @@ fn render<'a>(
             }
         }
 
         if do_render {
             if show_help {
                 wrench.show_onscreen_help();
             }
 
-            let results = wrench.render();
+            wrench.render();
             window.swap_buffers();
 
-            if show_stats {
-                show_stats = false;
-                println!("{:#?}", results.stats);
-            }
-
             if do_loop {
                 thing.next_frame();
             }
         }
 
         winit::ControlFlow::Continue
     };
 
--- a/layout/reftests/css-blending/reftest.list
+++ b/layout/reftests/css-blending/reftest.list
@@ -1,16 +1,16 @@
 == blend-canvas.html blend-canvas-ref.html
 == blend-constant-background-color.html blend-constant-background-color-ref.html
-fuzzy-if(webrender,1-3,1291-7888) == blend-gradient-background-color.html blend-gradient-background-color-ref.html
+fuzzy-if(webrender,1-3,1313-7888) == blend-gradient-background-color.html blend-gradient-background-color-ref.html
 == blend-image.html blend-image-ref.html
 == blend-difference-stacking.html blend-difference-stacking-ref.html
 
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-1,0-10000) fuzzy-if(skiaContent,0-1,0-30000) == background-blending-alpha.html background-blending-alpha-ref.html
-fuzzy-if(webrender,1-3,1291-7888) == background-blending-gradient-color.html background-blending-gradient-color-ref.html
+fuzzy-if(webrender,1-3,1313-7888) == background-blending-gradient-color.html background-blending-gradient-color-ref.html
 fuzzy-if(azureSkiaGL,0-3,0-7597) fuzzy-if(cocoaWidget,0-3,0-7597) fuzzy-if(d2d,0-1,0-3800) fuzzy-if(d3d11,0-1,0-4200) fuzzy-if(skiaContent,0-2,0-9450) fuzzy-if(webrender,1-5,3938-23925) == background-blending-gradient-gradient.html background-blending-gradient-gradient-ref.html
 fuzzy-if(azureSkiaGL,0-2,0-7174) fuzzy-if(webrender,1-3,1288-7888) == background-blending-gradient-image.html background-blending-gradient-color-ref.html
 fuzzy-if(azureSkia||d2d||gtkWidget,0-1,0-10000) == background-blending-image-color-jpg.html background-blending-image-color-ref.html
 == background-blending-image-color-png.html background-blending-image-color-ref.html
 == background-blending-image-color-svg.html background-blending-image-color-ref.html
 fuzzy-if(azureSkiaGL,0-2,0-7174) fuzzy-if(webrender,1-3,1288-7888) == background-blending-image-gradient.html background-blending-gradient-color-ref.html
 == background-blending-image-image.html background-blending-image-color-ref.html
 == background-blending-isolation.html background-blending-isolation-ref.html