Bug 1447286 - Update webrender to commit 30cfecc343e407ce277d07cf09f27ad9dd1917a1. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 22 Mar 2018 10:27:18 -0400
changeset 771133 525be13e782cddd7619a97c1fe303678742acb27
parent 771132 30d6a5d1373477f0c55c9f0926f5bdc53bc6a205
child 771134 f708a8ce461d54056a06c43c698341ab5eb11fc6
push id103574
push userkgupta@mozilla.com
push dateThu, 22 Mar 2018 14:29:33 +0000
reviewersjrmuizel
bugs1447286
milestone61.0a1
Bug 1447286 - Update webrender to commit 30cfecc343e407ce277d07cf09f27ad9dd1917a1. r?jrmuizel MozReview-Commit-ID: BLqZiOeNLt
gfx/webrender/Cargo.toml
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/common/image_helper.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/multiwindow.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/cs_text_run.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/record.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/image.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/args.yaml
gfx/wrench/src/main.rs
gfx/wrench/src/rawtest.rs
gfx/wrench/src/reftest.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -11,18 +11,18 @@ default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
 debugger = ["ws", "serde_json", "serde", "image", "base64"]
 capture = ["webrender_api/serialize", "ron", "serde"]
 replay = ["webrender_api/deserialize", "ron", "serde"]
 
 [dependencies]
 app_units = "0.6"
-bincode = "0.9"
 byteorder = "1.0"
+bincode = "1.0"
 euclid = "0.17"
 fxhash = "0.2.1"
 gleam = "0.4.20"
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "1"
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -197,17 +197,17 @@ impl Example for App {
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
         let image_mask_key = api.generate_image_key();
         resources.add_image(
             image_mask_key,
-            ImageDescriptor::new(2, 2, ImageFormat::R8, true),
+            ImageDescriptor::new(2, 2, ImageFormat::R8, true, false),
             ImageData::new(vec![0, 80, 180, 255]),
             None,
         );
         let mask = ImageMask {
             image: image_mask_key,
             rect: (75, 75).by(100, 100),
             repeat: false,
         };
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -227,25 +227,25 @@ impl Example for App {
         resources: &mut ResourceUpdates,
         _framebuffer_size: api::DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let blob_img1 = api.generate_image_key();
         resources.add_image(
             blob_img1,
-            api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true),
+            api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
             api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 50, 150, 255))),
             Some(128),
         );
 
         let blob_img2 = api.generate_image_key();
         resources.add_image(
             blob_img2,
-            api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true),
+            api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true, false),
             api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
         let info = api::LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -133,17 +133,16 @@ pub fn main_wrapper<E: Example>(
     println!("OpenGL version {}", gl.get_string(gl::VERSION));
     println!("Shader resource path: {:?}", res_path);
     let device_pixel_ratio = window.hidpi_factor();
     println!("Device pixel ratio: {}", device_pixel_ratio);
 
     println!("Loading shaders...");
     let opts = webrender::RendererOptions {
         resource_override_path: res_path,
-        debug: true,
         precache_shaders: E::PRECACHE_SHADERS,
         device_pixel_ratio,
         clear_color: Some(ColorF::new(0.3, 0.0, 0.0, 1.0)),
         //scatter_gpu_cache_updates: false,
         ..options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let framebuffer_size = {
--- a/gfx/webrender/examples/common/image_helper.rs
+++ b/gfx/webrender/examples/common/image_helper.rs
@@ -7,10 +7,10 @@ use webrender::api::{ImageData, ImageDes
 pub fn make_checkerboard(width: u32, height: u32) -> (ImageDescriptor, ImageData) {
     let mut image_data = Vec::new();
     for y in 0 .. height {
         for x in 0 .. width {
             let lum = 255 * (((x & 8) == 0) ^ ((y & 8) == 0)) as u8;
             image_data.extend_from_slice(&[lum, lum, lum, 0xff]);
         }
     }
-    (ImageDescriptor::new(width, height, ImageFormat::BGRA8, true), ImageData::new(image_data))
+    (ImageDescriptor::new(width, height, ImageFormat::BGRA8, true, false), ImageData::new(image_data))
 }
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -65,17 +65,17 @@ impl App {
         framebuffer_size: DeviceUintSize,
         device_pixel_ratio: f32,
     ) {
         // Generate the external image key that will be used to render the output document to the root document.
         self.external_image_key = Some(api.generate_image_key());
         let mut resources = ResourceUpdates::new();
         resources.add_image(
             self.external_image_key.unwrap(),
-            ImageDescriptor::new(100, 100, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::BGRA8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(0),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(TextureTarget::Default),
             }),
             None,
         );
 
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -96,17 +96,17 @@ impl Example for App {
                         let g = 255 * ((x & 32) == 0) as u8;
                         image_data.extend_from_slice(&[0, g, r, 0xff]);
                     }
                 }
 
                 let mut updates = ResourceUpdates::new();
                 updates.update_image(
                     self.image_key,
-                    ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true),
+                    ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true, false),
                     ImageData::new(image_data),
                     None,
                 );
                 let mut txn = Transaction::new();
                 txn.update_resources(updates);
                 txn.generate_frame();
                 api.send_transaction(document_id, txn);
             }
--- a/gfx/webrender/examples/multiwindow.rs
+++ b/gfx/webrender/examples/multiwindow.rs
@@ -81,17 +81,16 @@ impl Window {
                 gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
             },
             glutin::Api::WebGl => unimplemented!(),
         };
 
         let device_pixel_ratio = window.hidpi_factor();
 
         let opts = webrender::RendererOptions {
-            debug: true,
             device_pixel_ratio,
             clear_color: Some(clear_color),
             ..webrender::RendererOptions::default()
         };
 
         let framebuffer_size = {
             let (width, height) = window.get_inner_size().unwrap();
             DeviceUintSize::new(width, height)
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -106,25 +106,25 @@ impl Example for App {
 
         if self.swap_keys.is_empty() {
             let key0 = api.generate_image_key();
             let key1 = api.generate_image_key();
 
             self.image_generator.generate_image(128);
             resources.add_image(
                 key0,
-                ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true),
+                ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true, false),
                 ImageData::new(self.image_generator.take()),
                 None,
             );
 
             self.image_generator.generate_image(128);
             resources.add_image(
                 key1,
-                ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true),
+                ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true, false),
                 ImageData::new(self.image_generator.take()),
                 None,
             );
 
             self.swap_keys.push(key0);
             self.swap_keys.push(key1);
         }
 
@@ -210,17 +210,17 @@ impl Example for App {
                                 let size = 4;
 
                                 let image_key = api.generate_image_key();
 
                                 self.image_generator.generate_image(size);
 
                                 updates.add_image(
                                     image_key,
-                                    ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                                    ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                                     ImageData::new(self.image_generator.take()),
                                     None,
                                 );
 
                                 self.stress_keys.push(image_key);
                             }
                         }
                     }
@@ -228,17 +228,17 @@ impl Example for App {
                         updates.delete_image(image_key);
                     },
                     glutin::VirtualKeyCode::U => if let Some(image_key) = self.image_key {
                         let size = 128;
                         self.image_generator.generate_image(size);
 
                         updates.update_image(
                             image_key,
-                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::new(self.image_generator.take()),
                             None,
                         );
                     },
                     glutin::VirtualKeyCode::E => {
                         if let Some(image_key) = self.image_key.take() {
                             updates.delete_image(image_key);
                         }
@@ -249,17 +249,17 @@ impl Example for App {
                         let image_data = ExternalImageData {
                             id: ExternalImageId(0),
                             channel_index: size as u8,
                             image_type: ExternalImageType::Buffer,
                         };
 
                         updates.add_image(
                             image_key,
-                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::External(image_data),
                             None,
                         );
 
                         self.image_key = Some(image_key);
                     }
                     glutin::VirtualKeyCode::R => {
                         if let Some(image_key) = self.image_key.take() {
@@ -267,17 +267,17 @@ impl Example for App {
                         }
 
                         let image_key = api.generate_image_key();
                         let size = 32;
                         self.image_generator.generate_image(size);
 
                         updates.add_image(
                             image_key,
-                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::new(self.image_generator.take()),
                             None,
                         );
 
                         self.image_key = Some(image_key);
                     }
                     _ => {}
                 }
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -96,53 +96,53 @@ impl Example for App {
         );
 
         let yuv_chanel1 = api.generate_image_key();
         let yuv_chanel2 = api.generate_image_key();
         let yuv_chanel2_1 = api.generate_image_key();
         let yuv_chanel3 = api.generate_image_key();
         resources.add_image(
             yuv_chanel1,
-            ImageDescriptor::new(100, 100, ImageFormat::R8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::R8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(0),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
         );
         resources.add_image(
             yuv_chanel2,
-            ImageDescriptor::new(100, 100, ImageFormat::RG8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::RG8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(1),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
         );
         resources.add_image(
             yuv_chanel2_1,
-            ImageDescriptor::new(100, 100, ImageFormat::R8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::R8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(2),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
         );
         resources.add_image(
             yuv_chanel3,
-            ImageDescriptor::new(100, 100, ImageFormat::R8, true),
+            ImageDescriptor::new(100, 100, ImageFormat::R8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(3),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(
                     TextureTarget::Default,
                 ),
             }),
             None,
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -84,94 +84,66 @@ void main(void) {
     RectWithSize local_segment_rect = RectWithSize(segment_data[0].xy, segment_data[0].zw);
 
     VertexInfo vi;
 
     // Fetch the dynamic picture that we are drawing on.
     PictureTask pic_task = fetch_picture_task(brush.picture_address);
     ClipArea clip_area = fetch_clip_area(brush.clip_address);
 
-    if (pic_task.pic_kind_and_raster_mode > 0.0) {
-        vec2 local_pos = local_segment_rect.p0 + aPosition.xy * local_segment_rect.size;
-        vec2 clamped_local_pos = clamp_rect(local_pos, brush_prim.local_clip_rect);
-
-        vec2 device_pos = uDevicePixelRatio * clamped_local_pos;
-
-        vec2 final_pos = device_pos +
-                         pic_task.common_data.task_rect.p0 -
-                         uDevicePixelRatio * pic_task.content_origin;
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(brush.scroll_node_id);
 
-#ifdef WR_FEATURE_ALPHA_PASS
-        write_clip(
-            vec2(0.0),
-            clip_area
-        );
-#endif
-
-        vi = VertexInfo(
-            local_pos,
-            device_pos,
-            1.0,
-            device_pos
+    // Write the normal vertex information out.
+    if (scroll_node.is_axis_aligned) {
+        vi = write_vertex(
+            local_segment_rect,
+            brush_prim.local_clip_rect,
+            float(brush.z),
+            scroll_node,
+            pic_task,
+            brush_prim.local_rect
         );
 
-        // Write the final position transformed by the orthographic device-pixel projection.
-        gl_Position = uTransform * vec4(final_pos, 0.0, 1.0);
-    } else {
-        ClipScrollNode scroll_node = fetch_clip_scroll_node(brush.scroll_node_id);
-
-        // Write the normal vertex information out.
-        if (scroll_node.is_axis_aligned) {
-            vi = write_vertex(
-                local_segment_rect,
-                brush_prim.local_clip_rect,
-                float(brush.z),
-                scroll_node,
-                pic_task,
-                brush_prim.local_rect
-            );
-
-            // TODO(gw): vLocalBounds may be referenced by
-            //           the fragment shader when running in
-            //           the alpha pass, even on non-transformed
-            //           items. For now, just ensure it has no
-            //           effect. We can tidy this up as we move
-            //           more items to be brush shaders.
+        // TODO(gw): vLocalBounds may be referenced by
+        //           the fragment shader when running in
+        //           the alpha pass, even on non-transformed
+        //           items. For now, just ensure it has no
+        //           effect. We can tidy this up as we move
+        //           more items to be brush shaders.
 #ifdef WR_FEATURE_ALPHA_PASS
-            vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0));
+        vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0));
 #endif
-        } else {
-            bvec4 edge_mask = notEqual(brush.edge_mask & ivec4(1, 2, 4, 8), ivec4(0));
-            bool do_perspective_interpolation = (brush.flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0;
+    } else {
+        bvec4 edge_mask = notEqual(brush.edge_mask & ivec4(1, 2, 4, 8), ivec4(0));
+        bool do_perspective_interpolation = (brush.flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0;
 
-            vi = write_transform_vertex(
-                local_segment_rect,
-                brush_prim.local_rect,
-                brush_prim.local_clip_rect,
-                mix(vec4(0.0), vec4(1.0), edge_mask),
-                float(brush.z),
-                scroll_node,
-                pic_task,
-                do_perspective_interpolation
-            );
-        }
+        vi = write_transform_vertex(
+            local_segment_rect,
+            brush_prim.local_rect,
+            brush_prim.local_clip_rect,
+            mix(vec4(0.0), vec4(1.0), edge_mask),
+            float(brush.z),
+            scroll_node,
+            pic_task,
+            do_perspective_interpolation
+        );
+    }
 
-        // For brush instances in the alpha pass, always write
-        // out clip information.
-        // TODO(gw): It's possible that we might want alpha
-        //           shaders that don't clip in the future,
-        //           but it's reasonable to assume that one
-        //           implies the other, for now.
+    // For brush instances in the alpha pass, always write
+    // out clip information.
+    // TODO(gw): It's possible that we might want alpha
+    //           shaders that don't clip in the future,
+    //           but it's reasonable to assume that one
+    //           implies the other, for now.
 #ifdef WR_FEATURE_ALPHA_PASS
-        write_clip(
-            vi.screen_pos,
-            clip_area
-        );
+    write_clip(
+        vi.screen_pos,
+        clip_area
+    );
 #endif
-    }
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
         brush.prim_address + VECS_PER_BRUSH_PRIM,
         brush_prim.local_rect,
         brush.user_data,
         pic_task
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -89,17 +89,21 @@ void brush_vs(
             ) / texture_size.xyxy;
             break;
         case RASTER_LOCAL:
         default: {
             f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
             // Set the clip bounds to a value that won't have any
             // effect for local space images.
+#ifdef WR_FEATURE_TEXTURE_RECT
+            vUvClipBounds = vec4(0.0, 0.0, vec2(textureSize(sColor0)));
+#else
             vUvClipBounds = vec4(0.0, 0.0, 1.0, 1.0);
+#endif
             break;
         }
     }
 #else
     f = (vi.local_pos - local_rect.p0) / local_rect.size;
 #endif
 
     vUv.xy = mix(uv0, uv1, f);
deleted file mode 100644
--- a/gfx/webrender/res/cs_text_run.glsl
+++ /dev/null
@@ -1,68 +0,0 @@
-/* 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/. */
-
-#include shared,prim_shared
-
-varying vec3 vUv;
-flat varying vec4 vColor;
-flat varying vec4 vStRect;
-
-#ifdef WR_VERTEX_SHADER
-// Draw a text run to a cache target. These are always
-// drawn un-transformed. These are used for effects such
-// as text-shadow.
-
-void main(void) {
-    Primitive prim = load_primitive();
-    TextRun text = fetch_text_run(prim.specific_prim_address);
-
-    int glyph_index = prim.user_data0;
-    int resource_address = prim.user_data1;
-    int subpx_dir = prim.user_data2;
-
-    Glyph glyph = fetch_glyph(prim.specific_prim_address,
-                              glyph_index,
-                              subpx_dir);
-
-    GlyphResource res = fetch_glyph_resource(resource_address);
-
-    // Scale from glyph space to local space.
-    float scale = res.scale / uDevicePixelRatio;
-
-    // Compute the glyph rect in local space.
-    RectWithSize glyph_rect = RectWithSize(scale * res.offset + text.offset + glyph.offset,
-                                           scale * (res.uv_rect.zw - res.uv_rect.xy));
-
-    // Select the corner of the glyph rect that we are processing.
-    vec2 local_pos = (glyph_rect.p0 + glyph_rect.size * aPosition.xy);
-
-    // Clamp the local position to the text run's local clipping rectangle.
-    local_pos = clamp_rect(local_pos, prim.local_clip_rect);
-
-    // Move the point into device pixel space.
-    local_pos = (local_pos - prim.task.content_origin) * uDevicePixelRatio;
-    local_pos += prim.task.common_data.task_rect.p0;
-    gl_Position = uTransform * vec4(local_pos, 0.0, 1.0);
-
-    vec2 texture_size = vec2(textureSize(sColor0, 0));
-    vec2 st0 = res.uv_rect.xy / texture_size;
-    vec2 st1 = res.uv_rect.zw / texture_size;
-
-    vUv = vec3(mix(st0, st1, aPosition.xy), res.layer);
-    vColor = prim.task.color;
-
-    // We clamp the texture coordinates to the half-pixel offset from the borders
-    // in order to avoid sampling outside of the texture area.
-    vec2 half_texel = vec2(0.5) / texture_size;
-    vStRect = vec4(min(st0, st1) + half_texel, max(st0, st1) - half_texel);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    vec2 uv = clamp(vUv.xy, vStRect.xy, vStRect.zw);
-    float a = texture(sColor0, vec3(uv, vUv.z)).a;
-    oFragColor = vColor * a;
-}
-#endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,27 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, DeviceIntRect, DeviceIntSize, LayerToWorldScale};
 use api::{DeviceUintRect, DeviceUintPoint, DeviceUintSize, ExternalImageType, FilterOp, ImageRendering, LayerRect};
-use api::{DeviceIntPoint, LayerPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
+use api::{DeviceIntPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, RasterizationSpace};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive};
+use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PictureIndex, PrimitiveRun};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use std::{usize, f32, i32};
@@ -351,40 +351,31 @@ impl PrimitiveBatch {
             instances: Vec::new(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaBatchContainer {
-    pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
     pub opaque_batches: Vec<PrimitiveBatch>,
     pub alpha_batches: Vec<PrimitiveBatch>,
     pub target_rect: Option<DeviceIntRect>,
 }
 
 impl AlphaBatchContainer {
     pub fn new(target_rect: Option<DeviceIntRect>) -> AlphaBatchContainer {
         AlphaBatchContainer {
-            text_run_cache_prims: FastHashMap::default(),
             opaque_batches: Vec::new(),
             alpha_batches: Vec::new(),
             target_rect,
         }
     }
 
     fn merge(&mut self, builder: AlphaBatchBuilder) {
-        for (key, value) in builder.text_run_cache_prims {
-            self.text_run_cache_prims
-                .entry(key)
-                .or_insert(vec![])
-                .extend(value);
-        }
-
         for other_batch in builder.batch_list.opaque_batch_list.batches {
             let batch_index = self.opaque_batches.iter().position(|batch| {
                 batch.key.is_compatible_with(&other_batch.key)
             });
 
             match batch_index {
                 Some(batch_index) => {
                     self.opaque_batches[batch_index].instances.extend(other_batch.instances);
@@ -415,30 +406,28 @@ impl AlphaBatchContainer {
             }
         }
     }
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatchBuilder {
     pub batch_list: BatchList,
-    pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
     target_rect: DeviceIntRect,
 }
 
 impl AlphaBatchBuilder {
     pub fn new(
         screen_size: DeviceIntSize,
         target_rect: DeviceIntRect,
     ) -> Self {
         AlphaBatchBuilder {
             batch_list: BatchList::new(screen_size),
             glyph_fetch_buffer: Vec::new(),
-            text_run_cache_prims: FastHashMap::default(),
             target_rect,
         }
     }
 
     pub fn build(mut self, merged_batches: &mut AlphaBatchContainer) -> Option<AlphaBatchContainer> {
         self.batch_list.finalize();
 
         let task_relative_target_rect = DeviceIntRect::new(
@@ -451,17 +440,16 @@ impl AlphaBatchBuilder {
         if can_merge {
             merged_batches.merge(self);
             None
         } else {
             Some(AlphaBatchContainer {
                 alpha_batches: self.batch_list.alpha_batch_list.batches,
                 opaque_batches: self.batch_list.opaque_batch_list.batches,
                 target_rect: Some(self.target_rect),
-                text_run_cache_prims: self.text_run_cache_prims,
             })
         }
     }
 
     pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
         task_id: RenderTaskId,
@@ -495,17 +483,16 @@ impl AlphaBatchBuilder {
                 scroll_id,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
                 &mut splitter,
-                pic,
                 content_origin,
             );
         }
 
         // Flush the accumulated plane splits onto the task tree.
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_index = PrimitiveIndex(poly.anchor);
@@ -555,49 +542,35 @@ impl AlphaBatchBuilder {
         scroll_id: ClipScrollNodeIndex,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
-        pic: &PicturePrimitive,
-        content_origin: ContentOrigin,
+        content_origin: DeviceIntPoint,
     ) {
         for i in 0 .. run.count {
             let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-
             let metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
 
-            // Now that we walk the primitive runs in order to add
-            // items to batches, we need to check if they are
-            // visible here.
-            // We currently only support culling on normal (Image)
-            // picture types.
-            // TODO(gw): Support culling on shadow image types.
-            let is_image = match pic.kind {
-                PictureKind::Image { .. } => true,
-                PictureKind::TextShadow { .. } => false,
-            };
-
-            if !is_image || metadata.screen_rect.is_some() {
+            if metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
                     metadata.clip_chain_rect_index,
                     scroll_id,
                     prim_index,
                     ctx,
                     gpu_cache,
                     render_tasks,
                     task_id,
                     task_address,
                     deferred_resolves,
                     splitter,
                     content_origin,
-                    pic,
                 );
             }
         }
     }
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
@@ -609,51 +582,34 @@ impl AlphaBatchBuilder {
         prim_index: PrimitiveIndex,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
-        content_origin: ContentOrigin,
-        pic: &PicturePrimitive,
+        content_origin: DeviceIntPoint,
     ) {
         let z = prim_index.0 as i32;
         let prim_metadata = ctx.prim_store.get_metadata(prim_index);
         let scroll_node = &ctx.node_data[scroll_id.0 as usize];
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = scroll_node.transform.transform_kind();
 
-        let task_relative_bounding_rect = match content_origin {
-            ContentOrigin::Screen(point) => {
-                // translate by content-origin
-                let screen_rect = prim_metadata.screen_rect.expect("bug");
-                DeviceIntRect::new(
-                    DeviceIntPoint::new(
-                        screen_rect.unclipped.origin.x - point.x,
-                        screen_rect.unclipped.origin.y - point.y,
-                    ),
-                    screen_rect.unclipped.size,
-                )
-            }
-            ContentOrigin::Local(point) => {
-                // scale local rect by device pixel ratio
-                let content_rect = LayerRect::new(
-                    LayerPoint::new(
-                        prim_metadata.local_rect.origin.x - point.x,
-                        prim_metadata.local_rect.origin.y - point.y,
-                    ),
-                    prim_metadata.local_rect.size,
-                );
-                (content_rect * LayerToWorldScale::new(1.0) * ctx.device_pixel_scale).round().to_i32()
-            }
-        };
+        let screen_rect = prim_metadata.screen_rect.expect("bug");
+        let task_relative_bounding_rect = DeviceIntRect::new(
+            DeviceIntPoint::new(
+                screen_rect.unclipped.origin.x - content_origin.x,
+                screen_rect.unclipped.origin.y - content_origin.y,
+            ),
+            screen_rect.unclipped.size,
+        );
 
         let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
         let no_textures = BatchTextures::no_texture();
         let clip_task_address = prim_metadata
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
         let base_instance = SimplePrimitiveInstance::new(
             prim_cache_address,
@@ -704,21 +660,21 @@ impl AlphaBatchBuilder {
                                             picture_address: task_address,
                                             prim_address: prim_cache_address,
                                             clip_chain_rect_index,
                                             scroll_id,
                                             clip_task_address,
                                             z,
                                             segment_index: 0,
                                             edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+                                            brush_flags: BrushFlags::empty(),
                                             user_data: [
                                                 uv_rect_address,
                                                 BrushImageSourceKind::Color as i32,
-                                                RasterizationSpace::Local as i32,
+                                                RasterizationSpace::Screen as i32,
                                             ],
                                         };
                                         batch.push(PrimitiveInstance::from(instance));
                                     }
                                     PictureKind::Image {
                                         composite_mode,
                                         secondary_render_task_id,
                                         is_in_3d_context,
@@ -1131,37 +1087,24 @@ impl AlphaBatchBuilder {
                     },
                 );
                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0));
             }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                let is_shadow = match pic.kind {
-                    PictureKind::TextShadow { .. } => true,
-                    PictureKind::Image { .. } => false,
-                };
-
-                // TODO(gw): It probably makes sense to base this decision on the content
-                //           origin field in the future (once that's configurable).
-                let font_transform = if is_shadow {
-                    None
-                } else {
-                    Some(scroll_node.transform)
-                };
 
                 let font = text_cpu.get_font(
                     ctx.device_pixel_scale,
-                    font_transform,
+                    Some(scroll_node.transform),
                 );
 
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let batch_list = &mut self.batch_list;
-                let text_run_cache_prims = &mut self.text_run_cache_prims;
 
                 ctx.resource_cache.fetch_glyphs(
                     font,
                     &text_cpu.glyph_keys,
                     glyph_fetch_buffer,
                     gpu_cache,
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, SourceTexture::Invalid);
@@ -1172,54 +1115,48 @@ impl AlphaBatchBuilder {
                         }
 
                         let subpx_dir = match glyph_format {
                             GlyphFormat::Bitmap |
                             GlyphFormat::ColorBitmap => SubpixelDirection::None,
                             _ => text_cpu.font.subpx_dir.limit_by(text_cpu.font.render_mode),
                         };
 
-                        let batch = if is_shadow {
-                            text_run_cache_prims
-                                .entry(texture_id)
-                                .or_insert(Vec::new())
-                        } else {
-                            let textures = BatchTextures {
-                                colors: [
-                                    texture_id,
-                                    SourceTexture::Invalid,
-                                    SourceTexture::Invalid,
-                                ],
-                            };
+                        let textures = BatchTextures {
+                            colors: [
+                                texture_id,
+                                SourceTexture::Invalid,
+                                SourceTexture::Invalid,
+                            ],
+                        };
 
-                            let kind = BatchKind::Transformable(
-                                transform_kind,
-                                TransformBatchKind::TextRun(glyph_format),
-                            );
+                        let kind = BatchKind::Transformable(
+                            transform_kind,
+                            TransformBatchKind::TextRun(glyph_format),
+                        );
 
-                            let blend_mode = match glyph_format {
-                                GlyphFormat::Subpixel |
-                                GlyphFormat::TransformedSubpixel => {
-                                    if text_cpu.font.bg_color.a != 0 {
-                                        BlendMode::SubpixelWithBgColor
-                                    } else if ctx.use_dual_source_blending {
-                                        BlendMode::SubpixelDualSource
-                                    } else {
-                                        BlendMode::SubpixelConstantTextColor(text_cpu.font.color.into())
-                                    }
+                        let blend_mode = match glyph_format {
+                            GlyphFormat::Subpixel |
+                            GlyphFormat::TransformedSubpixel => {
+                                if text_cpu.font.bg_color.a != 0 {
+                                    BlendMode::SubpixelWithBgColor
+                                } else if ctx.use_dual_source_blending {
+                                    BlendMode::SubpixelDualSource
+                                } else {
+                                    BlendMode::SubpixelConstantTextColor(text_cpu.font.color.into())
                                 }
-                                GlyphFormat::Alpha |
-                                GlyphFormat::TransformedAlpha |
-                                GlyphFormat::Bitmap |
-                                GlyphFormat::ColorBitmap => BlendMode::PremultipliedAlpha,
-                            };
+                            }
+                            GlyphFormat::Alpha |
+                            GlyphFormat::TransformedAlpha |
+                            GlyphFormat::Bitmap |
+                            GlyphFormat::ColorBitmap => BlendMode::PremultipliedAlpha,
+                        };
 
-                            let key = BatchKey::new(kind, blend_mode, textures);
-                            batch_list.get_suitable_batch(key, &task_relative_bounding_rect)
-                        };
+                        let key = BatchKey::new(kind, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
                         for glyph in glyphs {
                             batch.push(base_instance.build(
                                 glyph.index_in_text_run,
                                 glyph.uv_rect_address.as_int(),
                                 subpx_dir as u32 as i32,
                             ));
                         }
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -10,29 +10,30 @@ use api::{FilterOp, FontInstanceKey, Fon
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo};
 use api::{LayerRect, LayerSize, LayerVector2D, LayoutRect, LayoutSize, LayoutTransform};
 use api::{LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
 use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use border::ImageBorderSegment;
+use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::{decompose_image, TiledImageInfo};
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{PictureCompositeMode, PictureKind};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
 use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
-use prim_store::{PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
+use prim_store::{PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
@@ -734,18 +735,18 @@ impl<'a> DisplayListFlattener<'a> {
                     info.tile_spacing,
                 );
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 self.add_radial_gradient(
                     clip_and_scroll,
                     &prim_info,
                     info.gradient.center,
-                    info.gradient.start_radius,
-                    info.gradient.end_radius,
+                    info.gradient.start_offset * info.gradient.radius.width,
+                    info.gradient.end_offset * info.gradient.radius.width,
                     info.gradient.radius.width / info.gradient.radius.height,
                     item.gradient_stops(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                 );
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
@@ -1514,23 +1515,29 @@ impl<'a> DisplayListFlattener<'a> {
                 color: line_color.premultiplied(),
                 style,
                 orientation,
             },
             None,
         );
 
         let mut fast_shadow_prims = Vec::new();
+        let mut slow_shadow_prims = Vec::new();
         for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let brush = &self.prim_store.cpu_brushes[shadow_metadata.cpu_prim_index.0];
-            let picture = &self.prim_store.pictures[brush.get_picture_index().0];
+            let pic_index = brush.get_picture_index();
+            let picture = &self.prim_store.pictures[pic_index.0];
             match picture.kind {
-                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
-                    fast_shadow_prims.push((idx, offset, color));
+                PictureKind::TextShadow { offset, color, blur_radius, .. } => {
+                    if blur_radius == 0.0 {
+                        fast_shadow_prims.push((idx, offset, color));
+                    } else {
+                        slow_shadow_prims.push((pic_index, offset, color));
+                    }
                 }
                 _ => {}
             }
         }
 
         for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
             let line = BrushPrimitive::new(
                 BrushKind::Line {
@@ -1547,47 +1554,56 @@ impl<'a> DisplayListFlattener<'a> {
             let prim_index = self.create_primitive(
                 &info,
                 Vec::new(),
                 PrimitiveContainer::Brush(line),
             );
             self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
         }
 
-        let prim_index = self.create_primitive(
-            &info,
-            Vec::new(),
-            PrimitiveContainer::Brush(line),
-        );
+        if line_color.a > 0.0 {
+            let prim_index = self.create_primitive(
+                &info,
+                Vec::new(),
+                PrimitiveContainer::Brush(line),
+            );
 
-        if line_color.a > 0.0 {
             if self.shadow_prim_stack.is_empty() {
                 self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
                 self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
             } else {
                 self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
             }
         }
 
-        for &(shadow_prim_index, _) in &self.shadow_prim_stack {
-            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
-            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Brush);
-            let brush = &self.prim_store.cpu_brushes[shadow_metadata.cpu_prim_index.0];
-            let picture = &mut self.prim_store.pictures[brush.get_picture_index().0];
+        for (pic_index, shadow_offset, shadow_color) in slow_shadow_prims {
+            let line = BrushPrimitive::new(
+                BrushKind::Line {
+                    wavy_line_thickness,
+                    color: shadow_color.premultiplied(),
+                    style,
+                    orientation,
+                },
+                None,
+            );
+            let mut info = info.clone();
+            info.rect = info.rect.translate(&shadow_offset);
+            info.clip_rect = info.clip_rect.translate(&shadow_offset);
+            let prim_index = self.create_primitive(
+                &info,
+                Vec::new(),
+                PrimitiveContainer::Brush(line),
+            );
 
-            match picture.kind {
-                // Only run real blurs here (fast path zero blurs are handled above).
-                PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
-                    picture.add_primitive(
-                        prim_index,
-                        clip_and_scroll,
-                    );
-                }
-                _ => {}
-            }
+            let picture = &mut self.prim_store.pictures[pic_index.0];
+
+            picture.add_primitive(
+                prim_index,
+                clip_and_scroll,
+            );
         }
     }
 
     pub fn add_border(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         border_item: &BorderDisplayItem,
@@ -1819,18 +1835,18 @@ impl<'a> DisplayListFlattener<'a> {
                     let segment_rel = segment.origin - rect.origin;
                     let mut info = info.clone();
                     info.rect = segment;
 
                     self.add_radial_gradient(
                         clip_and_scroll,
                         &info,
                         border.gradient.center - segment_rel,
-                        border.gradient.start_radius,
-                        border.gradient.end_radius,
+                        border.gradient.start_offset * border.gradient.radius.width,
+                        border.gradient.end_offset * border.gradient.radius.width,
                         border.gradient.radius.width / border.gradient.radius.height,
                         gradient_stops,
                         border.gradient.extend_mode,
                         segment.size,
                         LayerSize::zero(),
                     );
                 }
             }
@@ -2015,17 +2031,17 @@ impl<'a> DisplayListFlattener<'a> {
             }
         }
     }
 
     pub fn add_text(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         run_offset: LayoutVector2D,
-        info: &LayerPrimitiveInfo,
+        prim_info: &LayerPrimitiveInfo,
         font_instance_key: &FontInstanceKey,
         text_color: &ColorF,
         glyph_range: ItemRange<GlyphInstance>,
         glyph_count: usize,
         glyph_options: Option<GlyphOptions>,
     ) {
         let prim = {
             let instance_map = self.font_instances.read().unwrap();
@@ -2106,86 +2122,102 @@ impl<'a> DisplayListFlattener<'a> {
         // text elements to get pixel perfect results for reftests. It's also a big
         // performance win to avoid blurs and render target allocations where
         // possible. For any text shadows that have zero blur, create a normal text
         // primitive with the shadow's color and offset. These need to be added
         // *before* the visual text primitive in order to get the correct paint
         // order. Store them in a Vec first to work around borrowck issues.
         // TODO(gw): Refactor to avoid having to store them in a Vec first.
         let mut fast_shadow_prims = Vec::new();
+        let mut slow_shadow_prims = Vec::new();
         for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let brush = &self.prim_store.cpu_brushes[shadow_metadata.cpu_prim_index.0];
-            let picture_prim = &self.prim_store.pictures[brush.get_picture_index().0];
+            let pic_index = brush.get_picture_index();
+            let picture_prim = &self.prim_store.pictures[pic_index.0];
             match picture_prim.kind {
-                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
+                PictureKind::TextShadow { offset, color, blur_radius, .. } => {
                     let mut text_prim = prim.clone();
                     text_prim.font.color = color.into();
                     text_prim.shadow = true;
                     text_prim.offset += offset;
-                    fast_shadow_prims.push((idx, text_prim));
+
+                    if blur_radius == 0.0 {
+                        fast_shadow_prims.push((idx, text_prim, offset));
+                    } else {
+                        text_prim.font.render_mode = text_prim
+                            .font
+                            .render_mode
+                            .limit_by(FontRenderMode::Alpha);
+
+                        slow_shadow_prims.push((pic_index, text_prim, offset, blur_radius));
+                    }
                 }
                 _ => {}
             }
         }
 
-        for (idx, text_prim) in fast_shadow_prims {
-            let rect = info.rect;
-            let mut info = info.clone();
-            info.rect = rect.translate(&text_prim.offset);
-            info.clip_rect = info.clip_rect.translate(&text_prim.offset);
+        for (idx, text_prim, offset) in fast_shadow_prims {
+            let rect = prim_info.rect;
+            let mut info = prim_info.clone();
+            info.rect = rect.translate(&offset);
+            info.clip_rect = info.clip_rect.translate(&offset);
             let prim_index = self.create_primitive(
                 &info,
                 Vec::new(),
                 PrimitiveContainer::TextRun(text_prim),
             );
             self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
         }
 
-        // Create (and add to primitive store) the primitive that will be
-        // used for both the visual element and also the shadow(s).
-        let prim_index = self.create_primitive(
-            info,
-            Vec::new(),
-            PrimitiveContainer::TextRun(prim),
-        );
-
         // Only add a visual element if it can contribute to the scene.
         if text_color.a > 0.0 {
+            // Create (and add to primitive store) the primitive that will be
+            // used for both the visual element and also the shadow(s).
+            let prim_index = self.create_primitive(
+                prim_info,
+                Vec::new(),
+                PrimitiveContainer::TextRun(prim),
+            );
+
             if self.shadow_prim_stack.is_empty() {
-                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+                self.add_primitive_to_hit_testing_list(prim_info, clip_and_scroll);
                 self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
             } else {
-                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
+                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *prim_info));
             }
         }
 
         // Now add this primitive index to all the currently active text shadow
         // primitives. Although we're adding the indices *after* the visual
         // primitive here, they will still draw before the visual text, since
         // the shadow primitive itself has been added to the draw cmd
         // list *before* the visual element, during push_shadow. We need
         // the primitive index of the visual element here before we can add
         // the indices as sub-primitives to the shadow primitives.
-        for &(shadow_prim_index, _) in &self.shadow_prim_stack {
-            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
-            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Brush);
-            let brush = &self.prim_store.cpu_brushes[shadow_metadata.cpu_prim_index.0];
-            let picture = &mut self.prim_store.pictures[brush.get_picture_index().0];
+        for (pic_index, shadow_prim, offset, blur_radius) in slow_shadow_prims {
+            let blur_region = blur_radius * BLUR_SAMPLE_SCALE;
+
+            let rect = prim_info.rect;
+            let mut info = prim_info.clone();
+            info.rect = rect.translate(&offset).inflate(blur_region, blur_region);
+            info.clip_rect = info.clip_rect.translate(&offset);
 
-            match picture.kind {
-                // Only run real blurs here (fast path zero blurs are handled above).
-                PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
-                    picture.add_primitive(
-                        prim_index,
-                        clip_and_scroll,
-                    );
-                }
-                _ => {}
-            }
+            let prim_index = self.create_primitive(
+                &info,
+                Vec::new(),
+                PrimitiveContainer::TextRun(shadow_prim),
+            );
+
+            let picture = &mut self.prim_store.pictures[pic_index.0];
+
+            picture.add_primitive(
+                prim_index,
+                clip_and_scroll,
+            );
         }
     }
 
     pub fn add_image(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         stretch_size: LayerSize,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -8,17 +8,16 @@ use api::{LayerRect, LayerSize, Pipeline
 use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
-use picture::{ContentOrigin};
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
@@ -27,17 +26,16 @@ use tiling::ScrollbarPrimitive;
 use util::{self, MaxRect, WorldToLayerFastTransform};
 
 #[derive(Clone, Copy)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
-    pub debug: bool,
     pub dual_source_blending_is_supported: bool,
     pub dual_source_blending_is_enabled: bool,
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
@@ -66,22 +64,21 @@ pub struct FrameBuildingState<'a> {
     pub local_clip_rects: &'a mut Vec<LayerRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub cached_gradients: &'a mut [CachedGradient],
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
-    pub perform_culling: bool,
     pub prim_runs: Vec<PrimitiveRun>,
     pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
-    pub draw_text_transformed: bool,
     pub inv_world_transform: Option<WorldToLayerFastTransform>,
+    pub apply_local_clip_rect: bool,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
 }
 
 impl PictureState {
     pub fn new() -> PictureState {
@@ -120,17 +117,16 @@ impl FrameBuilder {
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
-                debug: false,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
             },
         }
     }
 
     pub fn with_display_list_flattener(
         screen_rect: DeviceUintRect,
@@ -197,22 +193,21 @@ impl FrameBuilder {
             local_clip_rects,
             resource_cache,
             gpu_cache,
             cached_gradients: &mut self.cached_gradients,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
-            perform_culling: true,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
             original_reference_frame_index: None,
             display_list,
-            draw_text_transformed: true,
             inv_world_transform: None,
+            apply_local_clip_rect: true,
         };
 
         let mut pic_state = PictureState::new();
 
         self.prim_store.reset_prim_visibility();
         self.prim_store.prepare_prim_runs(
             &pic_context,
             &mut pic_state,
@@ -222,17 +217,17 @@ impl FrameBuilder {
 
         let pic = &mut self.prim_store.pictures[0];
         pic.runs = pic_context.prim_runs;
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(frame_context.screen_rect),
             PrimitiveIndex(0),
             RenderTargetKind::Color,
-            ContentOrigin::Screen(DeviceIntPoint::zero()),
+            DeviceIntPoint::zero(),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
             pic_state.tasks,
             PictureType::Image,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.surface = Some(render_task_id);
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -60,35 +60,38 @@ struct Slot<T> {
     value: Option<T>,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FreeList<T> {
     slots: Vec<Slot<T>>,
     free_list_head: Option<u32>,
+    active_count: usize,
 }
 
 pub enum UpsertResult<T> {
     Updated(T),
     Inserted(FreeListHandle<T>),
 }
 
 impl<T> FreeList<T> {
     pub fn new() -> Self {
         FreeList {
             slots: Vec::new(),
             free_list_head: None,
+            active_count: 0,
         }
     }
 
     pub fn recycle(self) -> FreeList<T> {
         FreeList {
             slots: recycle_vec(self.slots),
             free_list_head: None,
+            active_count: 0,
         }
     }
 
     #[allow(dead_code)]
     pub fn get(&self, id: &FreeListHandle<T>) -> &T {
         self.slots[id.index as usize].value.as_ref().unwrap()
     }
 
@@ -126,16 +129,18 @@ impl<T> FreeList<T> {
             slot.value = Some(data);
             result
         } else {
             UpsertResult::Inserted(self.insert(data))
         }
     }
 
     pub fn insert(&mut self, item: T) -> FreeListHandle<T> {
+        self.active_count += 1;
+
         match self.free_list_head {
             Some(free_index) => {
                 let slot = &mut self.slots[free_index as usize];
 
                 // Remove from free list.
                 self.free_list_head = slot.next;
                 slot.next = None;
                 slot.value = Some(item);
@@ -161,15 +166,20 @@ impl<T> FreeList<T> {
                     epoch,
                     _marker: PhantomData,
                 }
             }
         }
     }
 
     pub fn free(&mut self, id: FreeListHandle<T>) -> T {
+        self.active_count -= 1;
         let slot = &mut self.slots[id.index as usize];
         slot.next = self.free_list_head;
         slot.epoch = Epoch(slot.epoch.0 + 1);
         self.free_list_head = Some(id.index);
         slot.value.take().unwrap()
     }
+
+    pub fn len(&self) -> usize {
+        self.active_count
+    }
 }
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -516,16 +516,17 @@ impl GlyphRasterizer {
                         texture_cache.update(
                             &mut texture_cache_handle,
                             ImageDescriptor {
                                 width: glyph.width,
                                 height: glyph.height,
                                 stride: None,
                                 format: ImageFormat::BGRA8,
                                 is_opaque: false,
+                                allow_mipmaps: false,
                                 offset: 0,
                             },
                             TextureFilter::Linear,
                             Some(ImageData::Raw(Arc::new(glyph.bytes))),
                             [glyph.left, -glyph.top, glyph.scale],
                             None,
                             gpu_cache,
                             Some(glyph_key_cache.eviction_notice()),
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,25 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DeviceIntPoint, DeviceIntRect};
-use api::{LayerPoint, LayerRect, LayerToWorldScale, LayerVector2D};
 use api::{ColorF, FilterOp, MixBlendMode, PipelineId};
+use api::{DeviceIntRect, LayerRect, LayerToWorldScale, LayerVector2D};
 use api::{PremultipliedColorF, Shadow};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip_scroll_tree::ClipScrollNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
 use gpu_types::{PictureType};
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::{PrimitiveMetadata, ScrollNodeAndClipChain};
 use render_task::{ClearMode, RenderTask};
-use render_task::{RenderTaskId, RenderTaskLocation, to_cache_size};
+use render_task::{RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
@@ -36,33 +35,22 @@ pub enum PictureCompositeMode {
     MixBlend(MixBlendMode),
     /// Apply a CSS filter.
     Filter(FilterOp),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit,
 }
 
-/// Configure whether the content to be drawn by a picture
-/// in local space rasterization or the screen space.
-#[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum ContentOrigin {
-    Local(LayerPoint),
-    Screen(DeviceIntPoint),
-}
-
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
         offset: LayerVector2D,
         color: ColorF,
         blur_radius: f32,
-        content_rect: LayerRect,
     },
     Image {
         // If a mix-blend-mode, contains the render task for
         // the readback of the framebuffer that we use to sample
         // from in the mix-blend-mode shader.
         // For drop-shadow filter, this will store the original
         // picture task which would be rendered on screen after
         // blur pass.
@@ -80,19 +68,16 @@ pub enum PictureKind {
         // It is only different if this is part of a 3D
         // rendering context.
         reference_frame_index: ClipScrollNodeIndex,
         real_local_rect: LayerRect,
         // An optional cache handle for storing extra data
         // in the GPU cache, depending on the type of
         // picture.
         extra_gpu_data_handle: GpuCacheHandle,
-        // The current screen-space rect of the rendered
-        // portion of this picture.
-        task_rect: DeviceIntRect,
     },
 }
 
 #[derive(Debug)]
 pub struct PicturePrimitive {
     // If this picture is drawn to an intermediate surface,
     // the associated target information.
     pub surface: Option<RenderTaskId>,
@@ -101,35 +86,33 @@ pub struct PicturePrimitive {
     pub kind: PictureKind,
 
     // List of primitive runs that make up this picture.
     pub runs: Vec<PrimitiveRun>,
 
     // The pipeline that the primitives on this picture belong to.
     pub pipeline_id: PipelineId,
 
-    // If true, apply visibility culling to primitives on this
-    // picture. For text shadows and box shadows, we want to
-    // unconditionally draw them.
-    pub cull_children: bool,
+    // The current screen-space rect of the rendered
+    // portion of this picture.
+    task_rect: DeviceIntRect,
 }
 
 impl PicturePrimitive {
     pub fn new_text_shadow(shadow: Shadow, pipeline_id: PipelineId) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
             surface: None,
             kind: PictureKind::TextShadow {
                 offset: shadow.offset,
                 color: shadow.color,
                 blur_radius: shadow.blur_radius,
-                content_rect: LayerRect::zero(),
             },
             pipeline_id,
-            cull_children: false,
+            task_rect: DeviceIntRect::zero(),
         }
     }
 
     pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.kind {
             PictureKind::Image { ref mut composite_mode, .. } => {
                 match composite_mode {
                     &mut Some(PictureCompositeMode::Filter(ref mut filter)) => {
@@ -162,20 +145,19 @@ impl PicturePrimitive {
             kind: PictureKind::Image {
                 secondary_render_task_id: None,
                 composite_mode,
                 is_in_3d_context,
                 frame_output_pipeline_id,
                 reference_frame_index,
                 real_local_rect: LayerRect::zero(),
                 extra_gpu_data_handle: GpuCacheHandle::new(),
-                task_rect: DeviceIntRect::zero(),
             },
             pipeline_id,
-            cull_children: true,
+            task_rect: DeviceIntRect::zero(),
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
         clip_and_scroll: ScrollNodeAndClipChain
     ) {
@@ -214,25 +196,23 @@ impl PicturePrimitive {
                         local_content_rect.inflate(inflate_size, inflate_size)
                                           .translate(&offset)
                     }
                     _ => {
                         local_content_rect
                     }
                 }
             }
-            PictureKind::TextShadow { offset, blur_radius, ref mut content_rect, .. } => {
+            PictureKind::TextShadow { blur_radius, .. } => {
                 let blur_offset = blur_radius * BLUR_SAMPLE_SCALE;
 
-                *content_rect = local_content_rect.inflate(
+                local_content_rect.inflate(
                     blur_offset,
                     blur_offset,
-                );
-
-                content_rect.translate(&offset)
+                )
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_metadata: &mut PrimitiveMetadata,
@@ -241,26 +221,26 @@ impl PicturePrimitive {
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
         let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
         let prim_screen_rect = prim_metadata
                                 .screen_rect
                                 .as_ref()
                                 .expect("bug: trying to draw an off-screen picture!?");
+        let device_rect;
 
         match self.kind {
             PictureKind::Image {
                 ref mut secondary_render_task_id,
                 ref mut extra_gpu_data_handle,
-                ref mut task_rect,
                 composite_mode,
                 ..
             } => {
-                let device_rect = match composite_mode {
+                device_rect = match composite_mode {
                     Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                         // If blur radius is 0, we can skip drawing this an an
                         // intermediate surface.
                         if blur_radius == 0.0 {
                             pic_state.tasks.extend(pic_state_for_children.tasks);
                             self.surface = None;
 
                             DeviceIntRect::zero()
@@ -277,23 +257,21 @@ impl PicturePrimitive {
                             // then intersect with the total screen rect, to minimize the
                             // allocation size.
                             let device_rect = prim_screen_rect
                                 .clipped
                                 .inflate(blur_range, blur_range)
                                 .intersection(&prim_screen_rect.unclipped)
                                 .unwrap();
 
-                            let content_origin = ContentOrigin::Screen(device_rect.origin);
-
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 prim_index,
                                 RenderTargetKind::Color,
-                                content_origin,
+                                device_rect.origin,
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
                                 pic_state_for_children.tasks,
                                 PictureType::Image,
                             );
 
                             let picture_task_id = frame_state.render_tasks.add(picture_task);
 
@@ -315,17 +293,17 @@ impl PicturePrimitive {
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
                         // TODO(gw): This is totally wrong and can never work with
                         //           transformed drop-shadow elements. Fix me!
                         let rect = (prim_metadata.local_rect.translate(&-offset) * content_scale).round().to_i32();
                         let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, rect.size),
                             prim_index,
                             RenderTargetKind::Color,
-                            ContentOrigin::Screen(rect.origin),
+                            rect.origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
                         picture_task.mark_for_saving();
 
                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
@@ -343,23 +321,21 @@ impl PicturePrimitive {
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
                         self.surface = Some(render_task_id);
 
                         rect
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
-                        let content_origin = ContentOrigin::Screen(prim_screen_rect.clipped.origin);
-
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                             prim_index,
                             RenderTargetKind::Color,
-                            content_origin,
+                            prim_screen_rect.clipped.origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let readback_task_id = frame_state.render_tasks.add(
                             RenderTask::new_readback(prim_screen_rect.clipped)
@@ -370,18 +346,16 @@ impl PicturePrimitive {
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
                         self.surface = Some(render_task_id);
 
                         prim_screen_rect.clipped
                     }
                     Some(PictureCompositeMode::Filter(filter)) => {
-                        let content_origin = ContentOrigin::Screen(prim_screen_rect.clipped.origin);
-
                         // If this filter is not currently going to affect
                         // the picture, just collapse this picture into the
                         // current render task. This most commonly occurs
                         // when opacity == 1.0, but can also occur on other
                         // filters and be a significant performance win.
                         if filter.is_noop() {
                             pic_state.tasks.extend(pic_state_for_children.tasks);
                             self.surface = None;
@@ -394,38 +368,36 @@ impl PicturePrimitive {
                                     }
                                 }
                             }
 
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                                 prim_index,
                                 RenderTargetKind::Color,
-                                content_origin,
+                                prim_screen_rect.clipped.origin,
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
                                 pic_state_for_children.tasks,
                                 PictureType::Image,
                             );
 
                             let render_task_id = frame_state.render_tasks.add(picture_task);
                             pic_state.tasks.push(render_task_id);
                             self.surface = Some(render_task_id);
                         }
 
                         prim_screen_rect.clipped
                     }
                     Some(PictureCompositeMode::Blit) => {
-                        let content_origin = ContentOrigin::Screen(prim_screen_rect.clipped.origin);
-
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                             prim_index,
                             RenderTargetKind::Color,
-                            content_origin,
+                            prim_screen_rect.clipped.origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
@@ -435,44 +407,42 @@ impl PicturePrimitive {
                     }
                     None => {
                         pic_state.tasks.extend(pic_state_for_children.tasks);
                         self.surface = None;
 
                         DeviceIntRect::zero()
                     }
                 };
-
-                // If scrolling or property animation has resulted in the task
-                // rect being different than last time, invalidate the GPU
-                // cache entry for this picture to ensure that the correct
-                // task rect is provided to the image shader.
-                if *task_rect != device_rect {
-                    frame_state.gpu_cache.invalidate(&prim_metadata.gpu_location);
-                    *task_rect = device_rect;
-                }
             }
-            PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
+            PictureKind::TextShadow { blur_radius, color, .. } => {
                 // This is a shadow element. Create a render task that will
                 // render the text run to a target, and then apply a gaussian
                 // blur to that text run in order to build the actual primitive
                 // which will be blitted to the framebuffer.
-                let cache_size = to_cache_size(content_rect.size * content_scale);
 
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
                 let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
                 let blur_std_deviation = device_radius * 0.5;
 
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+
+                device_rect = prim_screen_rect
+                    .clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&prim_screen_rect.unclipped)
+                    .unwrap();
+
                 let picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, cache_size),
+                    RenderTaskLocation::Dynamic(None, device_rect.size),
                     prim_index,
                     RenderTargetKind::Color,
-                    ContentOrigin::Local(content_rect.origin),
+                    device_rect.origin,
                     color.premultiplied(),
                     ClearMode::Transparent,
                     Vec::new(),
                     PictureType::TextShadow,
                 );
 
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
@@ -484,32 +454,41 @@ impl PicturePrimitive {
                     ClearMode::Transparent,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
             }
         }
+
+        // If scrolling or property animation has resulted in the task
+        // rect being different than last time, invalidate the GPU
+        // cache entry for this picture to ensure that the correct
+        // task rect is provided to the image shader.
+        if self.task_rect != device_rect {
+            frame_state.gpu_cache.invalidate(&prim_metadata.gpu_location);
+            self.task_rect = device_rect;
+        }
     }
 
     pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
+        request.push(self.task_rect.to_f32());
+
         match self.kind {
             PictureKind::TextShadow { .. } => {
-                request.push([0.0; 4]);
                 request.push(PremultipliedColorF::WHITE);
             }
-            PictureKind::Image { task_rect, composite_mode, .. } => {
+            PictureKind::Image { composite_mode, .. } => {
                 let color = match composite_mode {
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, _, color))) => {
                         color.premultiplied()
                     }
                     _ => {
                         PremultipliedColorF::WHITE
                     }
                 };
 
-                request.push(task_rect.to_f32());
                 request.push(color);
             }
         }
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1102,21 +1102,17 @@ impl PrimitiveStore {
         frame_state: &mut FrameBuildingState,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
-                let transform = if pic_context.draw_text_transformed {
-                    Some(prim_run_context.scroll_node.world_content_transform.into())
-                } else {
-                    None
-                };
+                let transform = Some(prim_run_context.scroll_node.world_content_transform.into());
                 text.prepare_for_render(
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                     transform,
                     pic_context.display_list,
                     frame_state.gpu_cache,
                 );
             }
@@ -1710,18 +1706,17 @@ impl PrimitiveStore {
         let mut may_need_clip_mask = true;
         let mut pic_state_for_children = PictureState::new();
 
         // Do some basic checks first, that can early out
         // without even knowing the local rect.
         let (prim_kind, cpu_prim_index) = {
             let metadata = &self.cpu_metadata[prim_index.0];
 
-            if pic_context.perform_culling &&
-               !metadata.is_backface_visible &&
+            if !metadata.is_backface_visible &&
                prim_run_context.scroll_node.world_content_transform.is_backface_visible() {
                 return None;
             }
 
             (metadata.prim_kind, metadata.cpu_prim_index)
         };
 
         // If we have dependencies, we need to prepare them first, in order
@@ -1733,17 +1728,17 @@ impl PrimitiveStore {
             if let BrushKind::Picture { pic_index } = self.cpu_brushes[cpu_prim_index.0].kind {
                 let pic_context_for_children = {
                     let pic = &mut self.pictures[pic_index.0];
 
                     if !pic.resolve_scene_properties(frame_context.scene_properties) {
                         return None;
                     }
 
-                    let (draw_text_transformed, original_reference_frame_index) = match pic.kind {
+                    let (apply_local_clip_rect, original_reference_frame_index) = match pic.kind {
                         PictureKind::Image { reference_frame_index, composite_mode, .. } => {
                             may_need_clip_mask = composite_mode.is_some();
                             (true, Some(reference_frame_index))
                         }
                         PictureKind::TextShadow { .. } => {
                             (false, None)
                         }
                     };
@@ -1756,22 +1751,21 @@ impl PrimitiveStore {
 
                     let inv_world_transform = prim_run_context
                         .scroll_node
                         .world_content_transform
                         .inverse();
 
                     PictureContext {
                         pipeline_id: pic.pipeline_id,
-                        perform_culling: pic.cull_children,
                         prim_runs: mem::replace(&mut pic.runs, Vec::new()),
                         original_reference_frame_index,
                         display_list,
-                        draw_text_transformed,
                         inv_world_transform,
+                        apply_local_clip_rect,
                     }
                 };
 
                 let result = self.prepare_prim_runs(
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
@@ -1792,18 +1786,17 @@ impl PrimitiveStore {
                metadata.local_rect.size.height <= 0.0 {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
             let local_rect = metadata.local_clip_rect.intersection(&metadata.local_rect);
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
-                None if pic_context.perform_culling => return None,
-                None => LayerRect::zero(),
+                None => return None,
             };
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
                 frame_context.device_pixel_scale,
             );
 
@@ -1811,26 +1804,26 @@ impl PrimitiveStore {
                 .intersection(&prim_run_context.clip_chain.combined_outer_screen_rect)
                 .map(|clipped| {
                     ScreenRect {
                         clipped,
                         unclipped: screen_bounding_rect,
                     }
                 });
 
-            if metadata.screen_rect.is_none() && pic_context.perform_culling {
+            if metadata.screen_rect.is_none() {
                 return None;
             }
 
             metadata.clip_chain_rect_index = prim_run_context.clip_chain_rect_index;
 
             (local_rect, screen_bounding_rect)
         };
 
-        if pic_context.perform_culling && may_need_clip_mask && !self.update_clip_task(
+        if may_need_clip_mask && !self.update_clip_task(
             prim_index,
             prim_run_context,
             &unclipped_device_rect,
             pic_state,
             frame_context,
             frame_state,
         ) {
             return None;
@@ -1875,26 +1868,24 @@ impl PrimitiveStore {
             //           lookups ever show up in a profile).
             let scroll_node = &frame_context
                 .clip_scroll_tree
                 .nodes[run.clip_and_scroll.scroll_node_id.0];
             let clip_chain = frame_context
                 .clip_scroll_tree
                 .get_clip_chain(run.clip_and_scroll.clip_chain_index);
 
-            if pic_context.perform_culling {
-                if !scroll_node.invertible {
-                    debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
-                    continue;
-                }
+            if !scroll_node.invertible {
+                debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
+                continue;
+            }
 
-                if clip_chain.combined_outer_screen_rect.is_empty() {
-                    debug!("{:?} {:?}: clipped out", run.base_prim_index, pic_context.pipeline_id);
-                    continue;
-                }
+            if clip_chain.combined_outer_screen_rect.is_empty() {
+                debug!("{:?} {:?}: clipped out", run.base_prim_index, pic_context.pipeline_id);
+                continue;
             }
 
             let parent_relative_transform = pic_context
                 .inv_world_transform
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
@@ -1905,19 +1896,20 @@ impl PrimitiveStore {
                         .nodes[original_reference_frame_index.0]
                         .world_content_transform;
                     parent.inverse()
                         .map(|inv_parent| {
                             inv_parent.pre_mul(&scroll_node.world_content_transform)
                         })
                 });
 
-            let clip_chain_rect = match pic_context.perform_culling {
-                true => get_local_clip_rect_for_nodes(scroll_node, clip_chain),
-                false => None,
+            let clip_chain_rect = if pic_context.apply_local_clip_rect {
+                get_local_clip_rect_for_nodes(scroll_node, clip_chain)
+            } else {
+                None
             };
 
             let clip_chain_rect_index = match clip_chain_rect {
                 Some(rect) if rect.is_empty() => continue,
                 Some(rect) => {
                     frame_state.local_clip_rects.push(rect);
                     ClipChainRectIndex(frame_state.local_clip_rects.len() - 1)
                 }
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ApiMsg, FrameMsg, SceneMsg};
-use bincode::{serialize, Infinite};
+use bincode::serialize;
 use byteorder::{LittleEndian, WriteBytesExt};
 use std::any::TypeId;
 use std::fmt::Debug;
 use std::fs::File;
 use std::io::Write;
 use std::mem;
 use std::path::PathBuf;
 
@@ -44,17 +44,17 @@ impl BinaryRecorder {
         self.file.write_u32::<LittleEndian>(data.len() as u32).ok();
         self.file.write(data).ok();
     }
 }
 
 impl ApiRecordingReceiver for BinaryRecorder {
     fn write_msg(&mut self, _: u32, msg: &ApiMsg) {
         if should_record_msg(msg) {
-            let buf = serialize(msg, Infinite).unwrap();
+            let buf = serialize(msg).unwrap();
             self.write_length_and_data(&buf);
         }
     }
 
     fn write_payload(&mut self, _: u32, data: &[u8]) {
         // signal payload with a 0 length
         self.file.write_u32::<LittleEndian>(0).ok();
         self.write_length_and_data(data);
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -6,17 +6,16 @@ use api::{DeviceIntPoint, DeviceIntRect,
 use api::{DeviceSize, PremultipliedColorF};
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{ImageSource, PictureType, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
-use picture::ContentOrigin;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
@@ -162,17 +161,17 @@ pub struct ClipRegionTask {
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
     pub target_kind: RenderTargetKind,
-    pub content_origin: ContentOrigin,
+    pub content_origin: DeviceIntPoint,
     pub color: PremultipliedColorF,
     pub pic_type: PictureType,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -254,17 +253,17 @@ pub struct RenderTask {
     pub saved_index: Option<SavedTargetIndex>,
 }
 
 impl RenderTask {
     pub fn new_picture(
         location: RenderTaskLocation,
         prim_index: PrimitiveIndex,
         target_kind: RenderTargetKind,
-        content_origin: ContentOrigin,
+        content_origin: DeviceIntPoint,
         color: PremultipliedColorF,
         clear_mode: ClearMode,
         children: Vec<RenderTaskId>,
         pic_type: PictureType,
     ) -> Self {
         RenderTask {
             children,
             location,
@@ -523,29 +522,21 @@ impl RenderTask {
         // TODO(gw): Maybe there's a way to make this stuff a bit
         //           more type-safe. Although, it will always need
         //           to be kept in sync with the GLSL code anyway.
 
         let (data1, data2) = match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 (
                     // Note: has to match `PICTURE_TYPE_*` in shaders
-                    // TODO(gw): Instead of using the sign of the picture
-                    //           type here, we should consider encoding it
-                    //           as a set of flags that get casted here
-                    //           and in the shader. This is a bit tidier
-                    //           and allows for future expansion of flags.
-                    match task.content_origin {
-                        ContentOrigin::Local(point) => [
-                            point.x, point.y, task.pic_type as u32 as f32,
-                        ],
-                        ContentOrigin::Screen(point) => [
-                            point.x as f32, point.y as f32, -(task.pic_type as u32 as f32),
-                        ],
-                    },
+                    [
+                        task.content_origin.x as f32,
+                        task.content_origin.y as f32,
+                        task.pic_type as u32 as f32,
+                    ],
                     task.color.to_array()
                 )
             }
             RenderTaskKind::CacheMask(ref task) => {
                 (
                     [
                         task.actual_rect.origin.x as f32,
                         task.actual_rect.origin.y as f32,
@@ -916,16 +907,17 @@ impl RenderTaskCache {
             // TODO(gw): Support color tasks in the texture cache,
             //           and perhaps consider if we can determine
             //           if some tasks are opaque as an optimization.
             let descriptor = ImageDescriptor::new(
                 size.width as u32,
                 size.height as u32,
                 image_format,
                 is_opaque,
+                false,
             );
 
             // Allocate space in the texture cache, but don't supply
             // and CPU-side data to be uploaded.
             texture_cache.update(
                 &mut cache_entry.handle,
                 descriptor,
                 TextureFilter::Linear,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -37,17 +37,16 @@ use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use gpu_types::PrimitiveInstance;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
 use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
-use picture::ContentOrigin;
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters, Profiler};
 use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use query::{GpuProfiler, GpuTimer};
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use scene_builder::SceneBuilder;
@@ -116,20 +115,16 @@ const GPU_TAG_BRUSH_SOLID: GpuProfileTag
 const GPU_TAG_BRUSH_LINE: GpuProfileTag = GpuProfileTag {
     label: "Line",
     color: debug_colors::DARKRED,
 };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
     label: "C_Clip",
     color: debug_colors::PURPLE,
 };
-const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag {
-    label: "C_TextRun",
-    color: debug_colors::MISTYROSE,
-};
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
     label: "target init",
     color: debug_colors::SLATEGREY,
 };
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
     label: "data init",
     color: debug_colors::LIGHTGREY,
 };
@@ -1440,17 +1435,16 @@ impl Renderer {
             (true, true) => FontRenderMode::Subpixel,
             (true, false) => FontRenderMode::Alpha,
             (false, _) => FontRenderMode::Mono,
         };
 
         let config = FrameBuilderConfig {
             enable_scrollbars: options.enable_scrollbars,
             default_font_render_mode,
-            debug: options.debug,
             dual_source_blending_is_enabled: true,
             dual_source_blending_is_supported: ext_dual_source_blending,
         };
 
         let device_pixel_ratio = options.device_pixel_ratio;
         // First set the flags to default and later call set_debug_flags to ensure any
         // potential transition when enabling a flag is run.
         let debug_flags = DebugFlags::default();
@@ -1479,16 +1473,17 @@ impl Renderer {
                     .build();
                 Arc::new(worker.unwrap())
             });
         let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
+        let renderer_id_for_render_backend = options.renderer_id.clone();
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
         let glyph_rasterizer = GlyphRasterizer::new(workers)?;
 
         let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(config, api_tx.clone());
         thread::Builder::new().name(scene_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(scene_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
@@ -1501,16 +1496,17 @@ impl Renderer {
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
                 thread_listener.thread_stopped(&scene_thread_name);
             }
         })?;
 
         thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(rb_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
+                thread_listener.new_render_backend_thread(renderer_id_for_render_backend);
                 thread_listener.thread_started(&rb_thread_name);
             }
 
             let texture_cache = TextureCache::new(max_device_size);
             let resource_cache = ResourceCache::new(
                 texture_cache,
                 glyph_rasterizer,
                 blob_image_renderer,
@@ -1718,17 +1714,17 @@ impl Renderer {
         String::new()
     }
 
 
     #[cfg(feature = "debugger")]
     fn get_screenshot_for_debugger(&mut self) -> String {
         use api::ImageDescriptor;
 
-        let desc = ImageDescriptor::new(1024, 768, ImageFormat::BGRA8, true);
+        let desc = ImageDescriptor::new(1024, 768, ImageFormat::BGRA8, true, false);
         let data = self.device.read_pixels(&desc);
         let screenshot = debug_server::Screenshot::new(desc.width, desc.height, data);
 
         serde_json::to_string(&screenshot).unwrap()
     }
 
     #[cfg(not(feature = "debugger"))]
     fn get_passes_for_debugger(&self) -> String {
@@ -1809,24 +1805,16 @@ impl Renderer {
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
 
         for alpha_batch_container in &target.alpha_batch_containers {
-            for (_, batch) in &alpha_batch_container.text_run_cache_prims {
-                debug_target.add(
-                    debug_server::BatchKind::Cache,
-                    "Text Shadow",
-                    batch.len(),
-                );
-            }
-
             for batch in alpha_batch_container
                 .opaque_batches
                 .iter()
                 .rev() {
                 debug_target.add(
                     debug_server::BatchKind::Opaque,
                     batch.key.kind.debug_name(),
                     batch.instances.len(),
@@ -2420,27 +2408,21 @@ impl Renderer {
             .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) => match task_info.content_origin {
-                ContentOrigin::Local(_) => panic!("bug: composite from a local-space rasterized picture?"),
-                ContentOrigin::Screen(p) => p,
-            },
+            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) => match task_info.content_origin {
-                ContentOrigin::Local(_) => panic!("bug: composite from a local-space rasterized picture?"),
-                ContentOrigin::Screen(p) => p,
-            },
+            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 = (cache_texture, readback_layer.0 as i32);
@@ -2679,41 +2661,16 @@ impl Renderer {
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
         }
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheRGBA8);
 
-        // Draw any textrun caches for this target. For now, this
-        // is only used to cache text runs that are to be blurred
-        // for shadow support. In the future it may be worth
-        // considering using this for (some) other text runs, since
-        // it removes the overhead of submitting many small glyphs
-        // to multiple tiles in the normal text run case.
-        for alpha_batch_container in &target.alpha_batch_containers {
-            if !alpha_batch_container.text_run_cache_prims.is_empty() {
-                self.device.set_blend(true);
-                self.device.set_blend_mode_premultiplied_alpha();
-
-                let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_TEXT_RUN);
-                self.shaders.cs_text_run
-                    .bind(&mut self.device, projection, &mut self.renderer_errors);
-                for (texture_id, instances) in &alpha_batch_container.text_run_cache_prims {
-                    self.draw_instanced_batch(
-                        instances,
-                        VertexArrayKind::Primitive,
-                        &BatchTextures::color(*texture_id),
-                        stats,
-                    );
-                }
-            }
-        }
-
         //TODO: record the pixel count for cached primitives
 
         if target.needs_depth() {
             let _gl = self.gpu_profile.start_marker("opaque batches");
             let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
             self.device.set_blend(false);
             //Note: depth equality is needed for split planes
             self.device.set_depth_func(DepthFunction::LessEqual);
@@ -3816,25 +3773,25 @@ pub trait ExternalImageHandler {
 pub trait OutputImageHandler {
     fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, DeviceIntSize)>;
     fn unlock(&mut self, pipeline_id: PipelineId);
 }
 
 pub trait ThreadListener {
     fn thread_started(&self, thread_name: &str);
     fn thread_stopped(&self, thread_name: &str);
+    fn new_render_backend_thread(&self, renderer_id: Option<u64>);
 }
 
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
     pub max_recorded_profiles: usize,
-    pub debug: bool,
     pub enable_scrollbars: bool,
     pub precache_shaders: bool,
     pub renderer_kind: RendererKind,
     pub enable_subpixel_aa: bool,
     pub clear_color: Option<ColorF>,
     pub enable_clear_scissor: bool,
     pub max_texture_size: Option<u32>,
     pub scatter_gpu_cache_updates: bool,
@@ -3854,17 +3811,16 @@ impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
             enable_dithering: true,
             debug_flags: DebugFlags::empty(),
             max_recorded_profiles: 0,
-            debug: false,
             enable_scrollbars: false,
             precache_shaders: false,
             renderer_kind: RendererKind::Native,
             enable_subpixel_aa: false,
             clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
             enable_clear_scissor: true,
             max_texture_size: None,
             // Scattered GPU cache updates haven't met a test that would show their superiority yet.
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -884,18 +884,17 @@ impl ResourceCache {
                     (Some(stride), offset)
                 };
 
                 ImageDescriptor {
                     width: actual_width,
                     height: actual_height,
                     stride,
                     offset,
-                    format: image_descriptor.format,
-                    is_opaque: image_descriptor.is_opaque,
+                    ..*image_descriptor
                 }
             } else {
                 image_template.descriptor.clone()
             };
 
             let filter = match request.rendering {
                 ImageRendering::Pixelated => {
                     TextureFilter::Nearest
@@ -903,17 +902,18 @@ impl ResourceCache {
                 ImageRendering::Auto | ImageRendering::CrispEdges => {
                     // If the texture uses linear filtering, enable mipmaps and
                     // trilinear filtering, for better image quality. We only
                     // support this for now on textures that are not placed
                     // into the shared cache. This accounts for any image
                     // that is > 512 in either dimension, so it should cover
                     // the most important use cases. We may want to support
                     // mip-maps on shared cache items in the future.
-                    if descriptor.width > 512 &&
+                    if descriptor.allow_mipmaps &&
+                       descriptor.width > 512 &&
                        descriptor.height > 512 &&
                        !self.texture_cache.is_allowed_in_shared_cache(
                         TextureFilter::Linear,
                         &descriptor,
                     ) {
                         TextureFilter::Trilinear
                     } else {
                         TextureFilter::Linear
@@ -949,16 +949,19 @@ impl ResourceCache {
             self.cached_glyphs.clear();
         }
         if what.contains(ClearCache::GLYPH_DIMENSIONS) {
             self.cached_glyph_dimensions.clear();
         }
         if what.contains(ClearCache::RENDER_TASKS) {
             self.cached_render_tasks.clear();
         }
+        if what.contains(ClearCache::TEXTURE_CACHE) {
+            self.texture_cache.clear();
+        }
     }
 
     pub fn clear_namespace(&mut self, namespace: IdNamespace) {
         self.resources
             .image_templates
             .images
             .retain(|key, _| key.0 != namespace);
         self.cached_images
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -408,17 +408,16 @@ fn create_clip_shader(name: &'static str
     program
 }
 
 
 pub struct Shaders {
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
-    pub cs_text_run: LazilyCompiledShader,
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_line: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
@@ -453,24 +452,16 @@ pub struct Shaders {
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
         options: &RendererOptions,
     ) -> Result<Self, ShaderError> {
-        let cs_text_run = LazilyCompiledShader::new(
-            ShaderKind::Cache(VertexArrayKind::Primitive),
-            "cs_text_run",
-            &[],
-            device,
-            options.precache_shaders,
-        )?;
-
         let brush_solid = BrushShader::new(
             "brush_solid",
             device,
             &[],
             options.precache_shaders,
         )?;
 
         let brush_line = BrushShader::new(
@@ -677,17 +668,16 @@ impl Shaders {
             ShaderKind::Primitive,
             "ps_split_composite",
             &[],
             device,
             options.precache_shaders,
         )?;
 
         Ok(Shaders {
-            cs_text_run,
             cs_blur_a8,
             cs_blur_rgba8,
             brush_solid,
             brush_line,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
@@ -777,17 +767,16 @@ impl Shaders {
                     }
                 };
                 prim_shader.get(transform_kind)
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
-        self.cs_text_run.deinit(device);
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_line.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -276,16 +276,66 @@ impl TextureCache {
             pending_updates: TextureUpdateList::new(),
             frame_id: FrameId(0),
             entries: FreeList::new(),
             standalone_entry_handles: Vec::new(),
             shared_entry_handles: Vec::new(),
         }
     }
 
+    pub fn clear(&mut self) {
+        let standalone_entry_handles = mem::replace(
+            &mut self.standalone_entry_handles,
+            Vec::new(),
+        );
+
+        for handle in standalone_entry_handles {
+            let entry = self.entries.free(handle);
+            entry.evict();
+            self.free(entry);
+        }
+
+        let shared_entry_handles = mem::replace(
+            &mut self.shared_entry_handles,
+            Vec::new(),
+        );
+
+        for handle in shared_entry_handles {
+            let entry = self.entries.free(handle);
+            entry.evict();
+            self.free(entry);
+        }
+
+        assert!(self.entries.len() == 0);
+
+        if let Some(texture_id) = self.array_a8_linear.clear() {
+            self.pending_updates.push(TextureUpdate {
+                id: texture_id,
+                op: TextureUpdateOp::Free,
+            });
+            self.cache_textures.free(texture_id, self.array_a8_linear.format);
+        }
+
+        if let Some(texture_id) = self.array_rgba8_linear.clear() {
+            self.pending_updates.push(TextureUpdate {
+                id: texture_id,
+                op: TextureUpdateOp::Free,
+            });
+            self.cache_textures.free(texture_id, self.array_rgba8_linear.format);
+        }
+
+        if let Some(texture_id) = self.array_rgba8_nearest.clear() {
+            self.pending_updates.push(TextureUpdate {
+                id: texture_id,
+                op: TextureUpdateOp::Free,
+            });
+            self.cache_textures.free(texture_id, self.array_rgba8_nearest.format);
+        }
+    }
+
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         self.frame_id = frame_id;
     }
 
     pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
         self.expire_old_standalone_entries();
 
         self.array_a8_linear
@@ -1008,16 +1058,22 @@ impl TextureArray {
             filter,
             layer_count,
             is_allocated: false,
             regions: Vec::new(),
             texture_id: None,
         }
     }
 
+    fn clear(&mut self) -> Option<CacheTextureId> {
+        self.is_allocated = false;
+        self.regions.clear();
+        self.texture_id.take()
+    }
+
     fn update_profile(&self, counter: &mut ResourceProfileCounter) {
         if self.is_allocated {
             let size = self.layer_count as u32 * TEXTURE_LAYER_DIMENSIONS *
                 TEXTURE_LAYER_DIMENSIONS * self.format.bytes_per_pixel();
             counter.set(self.layer_count as usize, size as usize);
         } else {
             counter.set(0, 0);
         }
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -40,20 +40,16 @@ const SHADERS: &[Shader] = &[
         name: "cs_clip_border",
         features: CLIP_FEATURES,
     },
     // Cache shaders
     Shader {
         name: "cs_blur",
         features: CACHE_FEATURES,
     },
-    Shader {
-        name: "cs_text_run",
-        features: CACHE_FEATURES,
-    },
     // Prim shaders
     Shader {
         name: "ps_border_corner",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_border_edge",
         features: PRIM_FEATURES,
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -8,21 +8,21 @@ repository = "https://github.com/servo/w
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 serialize = []
 deserialize = []
 
 [dependencies]
 app_units = "0.6"
+bincode = "1.0"
 bitflags = "1.0"
-bincode = "0.9"
 byteorder = "1.2.1"
+ipc-channel = {version = "0.10.0", optional = true}
 euclid = { version = "0.17", features = ["serde"] }
-ipc-channel = {version = "0.9", optional = true}
 serde = { version = "=1.0.27", features = ["rc"] }
 serde_derive = { version = "=1.0.27", features = ["deserialize_in_place"] }
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
 core-graphics = "0.13"
 
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -531,16 +531,17 @@ bitflags!{
 bitflags!{
     /// Mask for clearing caches in debug commands.
     #[derive(Deserialize, Serialize)]
     pub struct ClearCache: u8 {
         const IMAGES = 0x1;
         const GLYPHS = 0x2;
         const GLYPH_DIMENSIONS = 0x4;
         const RENDER_TASKS = 0x8;
+        const TEXTURE_CACHE = 0x16;
     }
 }
 
 /// Information about a loaded capture of each document
 /// that is returned by `RenderBackend`.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct CapturedDocument {
     pub document_id: DocumentId,
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -415,18 +415,18 @@ pub struct GradientStop {
     pub offset: f32,
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradient {
     pub center: LayoutPoint,
     pub radius: LayoutSize,
-    pub start_radius: f32,
-    pub end_radius: f32,
+    pub start_offset: f32,
+    pub end_offset: f32,
     pub extend_mode: ExtendMode,
 } // IMPLICIT stops: Vec<GradientStop>
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipChainItem {
     pub id: ClipChainId,
     pub parent: Option<ClipChainId>,
 } // IMPLICIT stops: Vec<ClipId>
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -157,24 +157,24 @@ impl BuiltDisplayList {
         AuxIter::new(&self.data[range.start .. range.start + range.length])
     }
 }
 
 /// Returns the byte-range the slice occupied, and the number of elements
 /// in the slice.
 fn skip_slice<T: for<'de> Deserialize<'de>>(
     list: &BuiltDisplayList,
-    data: &mut &[u8],
+    mut data: &mut &[u8],
 ) -> (ItemRange<T>, usize) {
     let base = list.data.as_ptr() as usize;
 
-    let byte_size: usize = bincode::deserialize_from(data, bincode::Infinite)
+    let byte_size: usize = bincode::deserialize_from(&mut data)
                                     .expect("MEH: malicious input?");
     let start = data.as_ptr() as usize;
-    let item_count: usize = bincode::deserialize_from(data, bincode::Infinite)
+    let item_count: usize = bincode::deserialize_from(&mut data)
                                     .expect("MEH: malicious input?");
 
     // Remember how many bytes item_count occupied
     let item_count_size = data.as_ptr() as usize - start;
 
     let range = ItemRange {
         start: start - base,                      // byte offset to item_count
         length: byte_size + item_count_size,      // number of bytes for item_count + payload
@@ -236,19 +236,18 @@ impl<'a> BuiltDisplayListIter<'a> {
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
             if self.data.len() == 0 {
                 return None;
             }
 
             {
-                let reader = bincode::read_types::IoReader::new(UnsafeReader::new(&mut self.data));
-                let mut deserializer = bincode::Deserializer::new(reader, bincode::Infinite);
-                Deserialize::deserialize_in_place(&mut deserializer, &mut self.cur_item)
+                let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
+                bincode::deserialize_in_place(reader, &mut self.cur_item)
                     .expect("MEH: malicious process?");
             }
 
             match self.cur_item.item {
                 SetGradientStops => {
                     self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
 
                     // This is a dummy item, skip over it
@@ -388,18 +387,17 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     }
 }
 
 impl<'de, 'a, T: Deserialize<'de>> AuxIter<'a, T> {
     pub fn new(mut data: &'a [u8]) -> Self {
         let size: usize = if data.len() == 0 {
             0 // Accept empty ItemRanges pointing anywhere
         } else {
-            bincode::deserialize_from(&mut UnsafeReader::new(&mut data), bincode::Infinite)
-                .expect("MEH: malicious input?")
+            bincode::deserialize_from(&mut UnsafeReader::new(&mut data)).expect("MEH: malicious input?")
         };
 
         AuxIter {
             data,
             size,
             _boo: PhantomData,
         }
     }
@@ -409,17 +407,17 @@ impl<'a, T: for<'de> Deserialize<'de>> I
     type Item = T;
 
     fn next(&mut self) -> Option<T> {
         if self.size == 0 {
             None
         } else {
             self.size -= 1;
             Some(
-                bincode::deserialize_from(&mut UnsafeReader::new(&mut self.data), bincode::Infinite)
+                bincode::deserialize_from(&mut UnsafeReader::new(&mut self.data))
                     .expect("MEH: malicious input?"),
             )
         }
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
         (self.size, Some(self.size))
     }
@@ -660,23 +658,23 @@ impl<'a> Write for SizeCounter {
 /// invocations.
 ///
 /// If this assumption is incorrect, the result will be Undefined Behaviour. This
 /// assumption should hold for all derived Serialize impls, which is all we currently
 /// use.
 fn serialize_fast<T: Serialize>(vec: &mut Vec<u8>, e: &T) {
     // manually counting the size is faster than vec.reserve(bincode::serialized_size(&e) as usize) for some reason
     let mut size = SizeCounter(0);
-    bincode::serialize_into(&mut size, e, bincode::Infinite).unwrap();
+    bincode::serialize_into(&mut size, e).unwrap();
     vec.reserve(size.0);
 
     let old_len = vec.len();
     let ptr = unsafe { vec.as_mut_ptr().offset(old_len as isize) };
     let mut w = UnsafeVecWriter(ptr);
-    bincode::serialize_into(&mut w, e, bincode::Infinite).unwrap();
+    bincode::serialize_into(&mut w, e).unwrap();
 
     // fix up the length
     unsafe { vec.set_len(old_len + size.0); }
 
     // make sure we wrote the right amount
     debug_assert_eq!(((w.0 as usize) - (vec.as_ptr() as usize)), vec.len());
 }
 
@@ -697,29 +695,29 @@ fn serialize_iter_fast<I>(vec: &mut Vec<
 where I: ExactSizeIterator + Clone,
       I::Item: Serialize,
 {
     // manually counting the size is faster than vec.reserve(bincode::serialized_size(&e) as usize) for some reason
     let mut size = SizeCounter(0);
     let mut count1 = 0;
 
     for e in iter.clone() {
-        bincode::serialize_into(&mut size, &e, bincode::Infinite).unwrap();
+        bincode::serialize_into(&mut size, &e).unwrap();
         count1 += 1;
     }
 
     vec.reserve(size.0);
 
     let old_len = vec.len();
     let ptr = unsafe { vec.as_mut_ptr().offset(old_len as isize) };
     let mut w = UnsafeVecWriter(ptr);
     let mut count2 = 0;
 
     for e in iter {
-        bincode::serialize_into(&mut w, &e, bincode::Infinite).unwrap();
+        bincode::serialize_into(&mut w, &e).unwrap();
         count2 += 1;
     }
 
     // fix up the length
     unsafe { vec.set_len(old_len + size.0); }
 
     // make sure we wrote the right amount
     debug_assert_eq!(((w.0 as usize) - (vec.as_ptr() as usize)), vec.len());
@@ -957,17 +955,16 @@ impl DisplayListBuilder {
         // Now write the actual byte_size
         let final_offset = data.len();
         let byte_size = final_offset - payload_offset;
 
         // Note we don't use serialize_fast because we don't want to change the Vec's len
         bincode::serialize_into(
             &mut &mut data[byte_size_offset..],
             &byte_size,
-            bincode::Infinite,
         ).unwrap();
 
         debug_assert_eq!(len, count);
     }
 
     fn push_iter<I>(&mut self, iter: I)
     where
         I: IntoIterator,
@@ -1070,70 +1067,51 @@ impl DisplayListBuilder {
     fn normalize_stops(stops: &mut Vec<GradientStop>, extend_mode: ExtendMode) -> (f32, f32) {
         assert!(stops.len() >= 2);
 
         let first = *stops.first().unwrap();
         let last = *stops.last().unwrap();
 
         assert!(first.offset <= last.offset);
 
-        let stops_origin = first.offset;
         let stops_delta = last.offset - first.offset;
 
         if stops_delta > 0.000001 {
             for stop in stops {
-                stop.offset = (stop.offset - stops_origin) / stops_delta;
+                stop.offset = (stop.offset - first.offset) / stops_delta;
             }
 
             (first.offset, last.offset)
         } else {
             // We have a degenerate gradient and can't accurately transform the stops
             // what happens here depends on the repeat behavior, but in any case
             // we reconstruct the gradient stops to something simpler and equivalent
             stops.clear();
 
             match extend_mode {
                 ExtendMode::Clamp => {
                     // This gradient is two colors split at the offset of the stops,
                     // so create a gradient with two colors split at 0.5 and adjust
                     // the gradient line so 0.5 is at the offset of the stops
-                    stops.push(GradientStop {
-                        color: first.color,
-                        offset: 0.0,
-                    });
-                    stops.push(GradientStop {
-                        color: first.color,
-                        offset: 0.5,
-                    });
-                    stops.push(GradientStop {
-                        color: last.color,
-                        offset: 0.5,
-                    });
-                    stops.push(GradientStop {
-                        color: last.color,
-                        offset: 1.0,
-                    });
+                    stops.push(GradientStop { color: first.color, offset: 0.0, });
+                    stops.push(GradientStop { color: first.color, offset: 0.5, });
+                    stops.push(GradientStop { color: last.color, offset: 0.5, });
+                    stops.push(GradientStop { color: last.color, offset: 1.0, });
 
                     let offset = last.offset;
 
                     (offset - 0.5, offset + 0.5)
                 }
                 ExtendMode::Repeat => {
                     // A repeating gradient with stops that are all in the same
                     // position should just display the last color. I believe the
                     // spec says that it should be the average color of the gradient,
                     // but this matches what Gecko and Blink does
-                    stops.push(GradientStop {
-                        color: last.color,
-                        offset: 0.0,
-                    });
-                    stops.push(GradientStop {
-                        color: last.color,
-                        offset: 1.0,
-                    });
+                    stops.push(GradientStop { color: last.color, offset: 0.0, });
+                    stops.push(GradientStop { color: last.color, offset: 1.0, });
 
                     (0.0, 1.0)
                 }
             }
         }
     }
 
     // NOTE: gradients must be pushed in the order they're created
@@ -1170,69 +1148,41 @@ impl DisplayListBuilder {
     ) -> RadialGradient {
         if radius.width <= 0.0 || radius.height <= 0.0 {
             // The shader cannot handle a non positive radius. So
             // reuse the stops vector and construct an equivalent
             // gradient.
             let last_color = stops.last().unwrap().color;
 
             let stops = [
-                GradientStop {
-                    offset: 0.0,
-                    color: last_color,
-                },
-                GradientStop {
-                    offset: 1.0,
-                    color: last_color,
-                },
+                GradientStop { offset: 0.0, color: last_color, },
+                GradientStop { offset: 1.0, color: last_color, },
             ];
 
             self.push_stops(&stops);
 
             return RadialGradient {
                 center,
                 radius: LayoutSize::new(1.0, 1.0),
-                start_radius: 0.0,
-                end_radius: 1.0,
+                start_offset: 0.0,
+                end_offset: 1.0,
                 extend_mode,
             };
         }
 
         let (start_offset, end_offset) =
             DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
 
         self.push_stops(&stops);
 
         RadialGradient {
             center,
             radius,
-            start_radius: radius.width * start_offset,
-            end_radius: radius.width * end_offset,
-            extend_mode,
-        }
-    }
-
-    // NOTE: gradients must be pushed in the order they're created
-    // because create_gradient stores the stops in anticipation
-    pub fn create_complex_radial_gradient(
-        &mut self,
-        center: LayoutPoint,
-        radius: LayoutSize,
-        start_radius: f32,
-        end_radius: f32,
-        stops: Vec<GradientStop>,
-        extend_mode: ExtendMode,
-    ) -> RadialGradient {
-        self.push_stops(&stops);
-
-        RadialGradient {
-            center,
-            radius,
-            start_radius,
-            end_radius,
+            start_offset: start_offset,
+            end_offset: end_offset,
             extend_mode,
         }
     }
 
     pub fn push_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         widths: BorderWidths,
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -71,27 +71,35 @@ impl ImageFormat {
 #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDescriptor {
     pub format: ImageFormat,
     pub width: u32,
     pub height: u32,
     pub stride: Option<u32>,
     pub offset: u32,
     pub is_opaque: bool,
+    pub allow_mipmaps: bool,
 }
 
 impl ImageDescriptor {
-    pub fn new(width: u32, height: u32, format: ImageFormat, is_opaque: bool) -> Self {
+    pub fn new(
+        width: u32,
+        height: u32,
+        format: ImageFormat,
+        is_opaque: bool,
+        allow_mipmaps: bool,
+    ) -> Self {
         ImageDescriptor {
             width,
             height,
             format,
             stride: None,
             offset: 0,
             is_opaque,
+            allow_mipmaps,
         }
     }
 
     pub fn compute_stride(&self) -> u32 {
         self.stride
             .unwrap_or(self.width * self.format.bytes_per_pixel())
     }
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-2083e83d958dd4a230ccae5c518e4bc8fbf88009
+30cfecc343e407ce277d07cf09f27ad9dd1917a1
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -2,17 +2,17 @@
 name = "wrench"
 version = "0.3.0"
 authors = ["Vladimir Vukicevic <vladimir@pobox.com>"]
 build = "build.rs"
 license = "MPL-2.0"
 
 [dependencies]
 base64 = "0.6"
-bincode = "0.9"
+bincode = "1.0"
 byteorder = "1.0"
 env_logger = { version = "0.5", optional = true }
 euclid = "0.17"
 gleam = "0.4"
 glutin = "0.12"
 app_units = "0.6"
 image = "0.18"
 clap = { version = "2", features = ["yaml"] }
--- a/gfx/wrench/src/args.yaml
+++ b/gfx/wrench/src/args.yaml
@@ -12,20 +12,16 @@ args:
       short: v
       long: verbose
       help: Enable verbose display
   - zoom:
       short: z
       long: zoom
       help: Set zoom factor
       takes_value: true
-  - debug:
-      short: d
-      long: debug
-      help: Enable debug renderer
   - shaders:
       long: shaders
       help: Override path for shaders
       takes_value: true
   - rebuild:
       short: r
       long: rebuild
       help: Rebuild display list from scratch every frame
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -424,17 +424,16 @@ fn main() {
         &mut window,
         events_loop.as_mut().map(|el| el.create_proxy()),
         res_path,
         dp_ratio,
         save_type,
         dim,
         args.is_present("rebuild"),
         args.is_present("no_subpixel_aa"),
-        args.is_present("debug"),
         args.is_present("verbose"),
         args.is_present("no_scissor"),
         args.is_present("no_batch"),
         args.is_present("precache"),
         args.is_present("slow_subpixel"),
         zoom_factor.unwrap_or(1.0),
         notifier,
     );
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -83,17 +83,17 @@ impl<'a> RawtestHarness<'a> {
         println!("\ttile decomposition...");
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
         let mut resources = ResourceUpdates::new();
 
         let blob_img = self.wrench.api.generate_image_key();
         resources.add_image(
             blob_img,
-            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(128),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(448.899994, 74.0, 151.000031, 56.));
 
@@ -135,17 +135,17 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(400., 400.);
         let mut resources = ResourceUpdates::new();
         {
             let api = &self.wrench.api;
 
             blob_img = api.generate_image_key();
             resources.add_image(
                 blob_img,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
         }
 
         // draw the blob the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
@@ -215,24 +215,24 @@ impl<'a> RawtestHarness<'a> {
         let layout_size = LayoutSize::new(400., 400.);
         let mut resources = ResourceUpdates::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
 
             blob_img = api.generate_image_key();
             resources.add_image(
                 blob_img,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
             blob_img2 = api.generate_image_key();
             resources.add_image(
                 blob_img2,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(80, 50, 150, 255))),
                 None,
             );
             (blob_img, blob_img2)
         };
 
         // setup some counters to count how many times each image is requested
         let img1_requested = Arc::new(AtomicIsize::new(0));
@@ -280,38 +280,38 @@ impl<'a> RawtestHarness<'a> {
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let _pixels_first = self.render_and_get_pixels(window_rect);
 
 
         // update and redraw both images
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
         resources.update_image(
             blob_img2,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(59, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let _pixels_second = self.render_and_get_pixels(window_rect);
 
 
         // only update the first image
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
             Some(rect(200, 200, 100, 100)),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let _pixels_third = self.render_and_get_pixels(window_rect);
@@ -334,17 +334,17 @@ impl<'a> RawtestHarness<'a> {
         );
         let layout_size = LayoutSize::new(400., 400.);
         let mut resources = ResourceUpdates::new();
 
         let blob_img = {
             let img = self.wrench.api.generate_image_key();
             resources.add_image(
                 img,
-                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
                 ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
                 None,
             );
             img
         };
 
         // draw the blobs the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
@@ -363,17 +363,17 @@ impl<'a> RawtestHarness<'a> {
 
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let pixels_first = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a second time after updating it with the same color
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(rect(100, 100, 100, 100)),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
@@ -387,17 +387,17 @@ impl<'a> RawtestHarness<'a> {
 
         self.submit_dl(&mut epoch, layout_size, builder, Some(resources));
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a third time after updating it with a different color
         let mut resources = ResourceUpdates::new();
         resources.update_image(
             blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, true, false),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
             Some(rect(200, 200, 100, 100)),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
@@ -502,17 +502,17 @@ impl<'a> RawtestHarness<'a> {
         );
 
         // 1. render some scene
 
         let mut resources = ResourceUpdates::new();
         let image = self.wrench.api.generate_image_key();
         resources.add_image(
             image,
-            ImageDescriptor::new(1, 1, ImageFormat::BGRA8, true),
+            ImageDescriptor::new(1, 1, ImageFormat::BGRA8, true, false),
             ImageData::new(vec![0xFF, 0, 0, 0xFF]),
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         builder.push_image(
             &LayoutPrimitiveInfo::new(rect(300.0, 70.0, 150.0, 50.0)),
--- a/gfx/wrench/src/reftest.rs
+++ b/gfx/wrench/src/reftest.rs
@@ -27,16 +27,17 @@ const PLATFORM: &str = "linux";
 #[cfg(target_os = "macos")]
 const PLATFORM: &str = "mac";
 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
 const PLATFORM: &str = "other";
 
 const OPTION_DISABLE_SUBPX: &str = "disable-subpixel";
 const OPTION_DISABLE_AA: &str = "disable-aa";
 const OPTION_DISABLE_DUAL_SOURCE_BLENDING: &str = "disable-dual-source-blending";
+const OPTION_ALLOW_MIPMAPS: &str = "allow-mipmaps";
 
 pub struct ReftestOptions {
     // These override values that are lower.
     pub allow_max_difference: usize,
     pub allow_num_differences: usize,
 }
 
 impl ReftestOptions {
@@ -72,16 +73,17 @@ pub struct Reftest {
     reference: PathBuf,
     font_render_mode: Option<FontRenderMode>,
     max_difference: usize,
     num_differences: usize,
     expected_draw_calls: Option<usize>,
     expected_alpha_targets: Option<usize>,
     expected_color_targets: Option<usize>,
     disable_dual_source_blending: bool,
+    allow_mipmaps: bool,
     zoom_factor: f32,
 }
 
 impl Display for Reftest {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         write!(
             f,
             "{} {} {}",
@@ -191,16 +193,17 @@ impl ReftestManifest {
             let mut max_count = 0;
             let mut op = ReftestOp::Equal;
             let mut font_render_mode = None;
             let mut expected_color_targets = None;
             let mut expected_alpha_targets = None;
             let mut expected_draw_calls = None;
             let mut disable_dual_source_blending = false;
             let mut zoom_factor = 1.0;
+            let mut allow_mipmaps = false;
 
             for (i, token) in tokens.iter().enumerate() {
                 match *token {
                     "include" => {
                         assert!(i == 0, "include must be by itself");
                         let include = dir.join(tokens[1]);
 
                         reftests.append(
@@ -243,16 +246,19 @@ impl ReftestManifest {
                             font_render_mode = Some(FontRenderMode::Alpha);
                         }
                         if args.iter().any(|arg| arg == &OPTION_DISABLE_AA) {
                             font_render_mode = Some(FontRenderMode::Mono);
                         }
                         if args.iter().any(|arg| arg == &OPTION_DISABLE_DUAL_SOURCE_BLENDING) {
                             disable_dual_source_blending = true;
                         }
+                        if args.iter().any(|arg| arg == &OPTION_ALLOW_MIPMAPS) {
+                            allow_mipmaps = true;
+                        }
                     }
                     "==" => {
                         op = ReftestOp::Equal;
                     }
                     "!=" => {
                         op = ReftestOp::NotEqual;
                     }
                     _ => {
@@ -262,16 +268,17 @@ impl ReftestManifest {
                             reference: dir.join(tokens[i + 1]),
                             font_render_mode,
                             max_difference: cmp::max(max_difference, options.allow_max_difference),
                             num_differences: cmp::max(max_count, options.allow_num_differences),
                             expected_draw_calls,
                             expected_alpha_targets,
                             expected_color_targets,
                             disable_dual_source_blending,
+                            allow_mipmaps,
                             zoom_factor,
                         });
 
                         break;
                     }
                 }
             }
         }
@@ -329,16 +336,22 @@ impl<'a> ReftestHarness<'a> {
         }
 
         failing.len()
     }
 
     fn run_reftest(&mut self, t: &Reftest) -> bool {
         println!("REFTEST {}", t);
 
+        self.wrench
+            .api
+            .send_debug_cmd(
+                DebugCommand::ClearCaches(ClearCache::all())
+            );
+
         self.wrench.set_page_zoom(ZoomFactor::new(t.zoom_factor));
 
         if t.disable_dual_source_blending {
             self.wrench
                 .api
                 .send_debug_cmd(
                     DebugCommand::EnableDualSourceBlending(false)
                 );
@@ -346,31 +359,33 @@ impl<'a> ReftestHarness<'a> {
 
         let window_size = self.window.get_inner_size();
         let reference = match t.reference.extension().unwrap().to_str().unwrap() {
             "yaml" => {
                 let (reference, _) = self.render_yaml(
                     t.reference.as_path(),
                     window_size,
                     t.font_render_mode,
+                    t.allow_mipmaps,
                 );
                 reference
             }
             "png" => {
                 self.load_image(t.reference.as_path(), ImageFormat::PNG)
             }
             other => panic!("Unknown reftest extension: {}", other),
         };
 
         // the reference can be smaller than the window size,
         // in which case we only compare the intersection
         let (test, stats) = self.render_yaml(
             t.test.as_path(),
             reference.size,
             t.font_render_mode,
+            t.allow_mipmaps,
         );
 
         if t.disable_dual_source_blending {
             self.wrench
                 .api
                 .send_debug_cmd(
                     DebugCommand::EnableDualSourceBlending(true)
                 );
@@ -459,19 +474,21 @@ impl<'a> ReftestHarness<'a> {
         }
     }
 
     fn render_yaml(
         &mut self,
         filename: &Path,
         size: DeviceUintSize,
         font_render_mode: Option<FontRenderMode>,
+        allow_mipmaps: bool,
     ) -> (ReftestImage, RendererStats) {
         let mut reader = YamlFrameReader::new(filename);
         reader.set_font_render_mode(font_render_mode);
+        reader.allow_mipmaps(allow_mipmaps);
         reader.do_frame(self.wrench);
 
         // wait for the frame
         self.rx.recv().unwrap();
         let stats = self.wrench.render();
 
         let window_size = self.window.get_inner_size();
         assert!(size.width <= window_size.width && size.height <= window_size.height);
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -169,17 +169,16 @@ impl Wrench {
         window: &mut WindowWrapper,
         proxy: Option<EventsLoopProxy>,
         shader_override_path: Option<PathBuf>,
         dp_ratio: f32,
         save_type: Option<SaveType>,
         size: DeviceUintSize,
         do_rebuild: bool,
         no_subpixel_aa: bool,
-        debug: bool,
         verbose: bool,
         no_scissor: bool,
         no_batch: bool,
         precache_shaders: bool,
         disable_dual_source_blending: bool,
         zoom_factor: f32,
         notifier: Option<Box<RenderNotifier>>,
     ) -> Self {
@@ -202,17 +201,16 @@ impl Wrench {
         debug_flags.set(DebugFlags::DISABLE_BATCHING, no_batch);
         let callbacks = Arc::new(Mutex::new(blob::BlobCallbacks::new()));
 
         let opts = webrender::RendererOptions {
             device_pixel_ratio: dp_ratio,
             resource_override_path: shader_override_path,
             recorder,
             enable_subpixel_aa: !no_subpixel_aa,
-            debug,
             debug_flags,
             enable_clear_scissor: !no_scissor,
             max_recorded_profiles: 16,
             precache_shaders,
             blob_image_renderer: Some(Box::new(blob::CheckerboardRenderer::new(callbacks.clone()))),
             disable_dual_source_blending,
             ..Default::default()
         };
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -99,17 +99,17 @@ fn generate_checkerboard_image(
                 pixels.push(value);
                 pixels.push(value);
                 pixels.push(0xff);
             }
         }
     }
 
     (
-        ImageDescriptor::new(width, height, ImageFormat::BGRA8, true),
+        ImageDescriptor::new(width, height, ImageFormat::BGRA8, true, false),
         ImageData::new(pixels),
     )
 }
 
 fn generate_xy_gradient_image(w: u32, h: u32) -> (ImageDescriptor, ImageData) {
     let mut pixels = Vec::with_capacity((w * h * 4) as usize);
     for y in 0 .. h {
         for x in 0 .. w {
@@ -117,17 +117,17 @@ fn generate_xy_gradient_image(w: u32, h:
             pixels.push((y as f32 / h as f32 * 255.0 * grid) as u8);
             pixels.push(0);
             pixels.push((x as f32 / w as f32 * 255.0 * grid) as u8);
             pixels.push(255);
         }
     }
 
     (
-        ImageDescriptor::new(w, h, ImageFormat::BGRA8, true),
+        ImageDescriptor::new(w, h, ImageFormat::BGRA8, true, false),
         ImageData::new(pixels),
     )
 }
 
 fn generate_solid_color_image(
     r: u8,
     g: u8,
     b: u8,
@@ -147,17 +147,17 @@ fn generate_solid_color_image(
         let end = ptr.offset((w * h) as isize);
         while ptr < end {
             *ptr = color;
             ptr = ptr.offset(1);
         }
     }
 
     (
-        ImageDescriptor::new(w, h, ImageFormat::BGRA8, a == 255),
+        ImageDescriptor::new(w, h, ImageFormat::BGRA8, a == 255, false),
         ImageData::new(pixels),
     )
 }
 
 
 
 fn is_image_opaque(format: ImageFormat, bytes: &[u8]) -> bool {
     match format {
@@ -195,16 +195,17 @@ pub struct YamlFrameReader {
     /// scroll layers should be initialized with.
     scroll_offsets: HashMap<ExternalScrollId, LayerPoint>,
 
     image_map: HashMap<(PathBuf, Option<i64>), (ImageKey, LayoutSize)>,
 
     fonts: HashMap<FontDescriptor, FontKey>,
     font_instances: HashMap<(FontKey, Au, FontInstanceFlags), FontInstanceKey>,
     font_render_mode: Option<FontRenderMode>,
+    allow_mipmaps: bool,
 
     /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
     /// and having each of those ids correspond to a unique ClipId.
     clip_id_map: HashMap<u64, ClipId>,
 }
 
 impl YamlFrameReader {
     pub fn new(yaml_path: &Path) -> YamlFrameReader {
@@ -219,16 +220,17 @@ impl YamlFrameReader {
             queue_depth: 1,
             include_only: vec![],
             scroll_offsets: HashMap::new(),
             fonts: HashMap::new(),
             font_instances: HashMap::new(),
             font_render_mode: None,
             image_map: HashMap::new(),
             clip_id_map: HashMap::new(),
+            allow_mipmaps: false,
         }
     }
 
     pub fn deinit(mut self, wrench: &mut Wrench) {
         let mut updates = ResourceUpdates::new();
 
         for (_, font_instance) in self.font_instances.drain() {
             updates.delete_font_instance(font_instance);
@@ -410,16 +412,17 @@ impl YamlFrameReader {
                     }
                     _ => panic!("We don't support whatever your crazy image type is, come on"),
                 };
                 let descriptor = ImageDescriptor::new(
                     image_dims.0,
                     image_dims.1,
                     format,
                     is_image_opaque(format, &bytes[..]),
+                    self.allow_mipmaps,
                 );
                 let data = ImageData::new(bytes);
                 (descriptor, data)
             }
             _ => {
                 // This is a hack but it is convenient when generating test cases and avoids
                 // bloating the repository.
                 match parse_function(
@@ -511,16 +514,20 @@ impl YamlFrameReader {
                     ref family,
                     weight,
                     style,
                     stretch,
                 } => wrench.font_key_from_properties(family, weight, style, stretch),
             })
     }
 
+    pub fn allow_mipmaps(&mut self, allow_mipmaps: bool) {
+        self.allow_mipmaps = allow_mipmaps;
+    }
+
     pub fn set_font_render_mode(&mut self, render_mode: Option<FontRenderMode>) {
         self.font_render_mode = render_mode;
     }
 
     fn get_or_create_font_instance(
         &mut self,
         font_key: FontKey,
         size: Au,
@@ -596,77 +603,38 @@ impl YamlFrameReader {
         };
 
         dl.create_gradient(start, end, stops, extend_mode)
     }
 
     fn to_radial_gradient(&mut self, dl: &mut DisplayListBuilder, item: &Yaml) -> RadialGradient {
         let center = item["center"].as_point().expect("radial gradient must have center");
         let radius = item["radius"].as_size().expect("radial gradient must have a radius");
-
-        if item["start-radius"].is_badvalue() {
-            let stops = item["stops"]
-                .as_vec()
-                .expect("radial gradient must have stops")
-                .chunks(2)
-                .map(|chunk| {
-                    GradientStop {
-                        offset: chunk[0]
-                            .as_force_f32()
-                            .expect("gradient stop offset is not f32"),
-                        color: chunk[1]
-                            .as_colorf()
-                            .expect("gradient stop color is not color"),
-                    }
-                })
-                .collect::<Vec<_>>();
-            let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
-                ExtendMode::Repeat
-            } else {
-                ExtendMode::Clamp
-            };
-
-            dl.create_radial_gradient(center, radius, stops, extend_mode)
+        let stops = item["stops"]
+            .as_vec()
+            .expect("radial gradient must have stops")
+            .chunks(2)
+            .map(|chunk| {
+                GradientStop {
+                    offset: chunk[0]
+                        .as_force_f32()
+                        .expect("gradient stop offset is not f32"),
+                    color: chunk[1]
+                        .as_colorf()
+                        .expect("gradient stop color is not color"),
+                }
+            })
+            .collect::<Vec<_>>();
+        let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
+            ExtendMode::Repeat
         } else {
-            let start_radius = item["start-radius"]
-                .as_force_f32()
-                .expect("radial gradient must have start radius");
-            let end_radius = item["end-radius"]
-                .as_force_f32()
-                .expect("radial gradient must have end radius");
-            let stops = item["stops"]
-                .as_vec()
-                .expect("radial gradient must have stops")
-                .chunks(2)
-                .map(|chunk| {
-                    GradientStop {
-                        offset: chunk[0]
-                            .as_force_f32()
-                            .expect("gradient stop offset is not f32"),
-                        color: chunk[1]
-                            .as_colorf()
-                            .expect("gradient stop color is not color"),
-                    }
-                })
-                .collect::<Vec<_>>();
-            let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
-                ExtendMode::Repeat
-            } else {
-                ExtendMode::Clamp
-            };
+            ExtendMode::Clamp
+        };
 
-            dl.create_complex_radial_gradient(
-                center,
-                radius,
-                start_radius,
-                end_radius,
-                stops,
-                extend_mode,
-            )
-        }
+        dl.create_radial_gradient(center, radius, stops, extend_mode)
     }
 
     fn handle_rect(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
@@ -1434,21 +1402,16 @@ impl YamlFrameReader {
     }
 
     pub fn handle_push_shadow(
         &mut self,
         dl: &mut DisplayListBuilder,
         yaml: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
-        let rect = yaml["bounds"]
-            .as_rect()
-            .expect("Text shadows require bounds");
-        info.rect = rect;
-        info.clip_rect = rect;
         let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0);
         let offset = yaml["offset"].as_vector().unwrap_or(LayoutVector2D::zero());
         let color = yaml["color"].as_colorf().unwrap_or(*BLACK_COLOR);
 
         dl.push_shadow(
             &info,
             Shadow {
                 blur_radius,
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -268,16 +268,40 @@ fn native_font_handle_to_yaml(
     _rsrc: &mut ResourceGenerator,
     handle: &NativeFontHandle,
     parent: &mut yaml_rust::yaml::Hash,
     _: &mut Option<PathBuf>,
 ) {
     str_node(parent, "font", &handle.pathname);
 }
 
+fn radial_gradient_to_yaml(
+    table: &mut Table,
+    gradient: &webrender::api::RadialGradient,
+    stops_range: ItemRange<GradientStop>,
+    display_list: &BuiltDisplayList
+) {
+    point_node(table, "center", &gradient.center);
+    size_node(table, "radius", &gradient.radius);
+
+    let first_offset = gradient.start_offset;
+    let last_offset = gradient.end_offset;
+    let stops_delta = last_offset - first_offset;
+    assert!(first_offset <= last_offset);
+
+    let mut denormalized_stops = vec![];
+    for stop in display_list.get(stops_range) {
+        let denormalized_stop = (stop.offset * stops_delta) + first_offset;
+        denormalized_stops.push(Yaml::Real(denormalized_stop.to_string()));
+        denormalized_stops.push(Yaml::String(color_to_string(stop.color)));
+    }
+    yaml_node(table, "stops", Yaml::Array(denormalized_stops));
+    bool_node(table, "repeat", gradient.extend_mode == ExtendMode::Repeat);
+}
+
 enum CachedFont {
     Native(NativeFontHandle, Option<PathBuf>),
     Raw(Option<Vec<u8>>, u32, Option<PathBuf>),
 }
 
 struct CachedFontInstance {
     font_key: FontKey,
     glyph_size: Au,
@@ -900,32 +924,23 @@ impl YamlFrameWriter {
                             let outset: Vec<f32> = vec![
                                 details.outset.top,
                                 details.outset.right,
                                 details.outset.bottom,
                                 details.outset.left,
                             ];
                             yaml_node(&mut v, "width", f32_vec_yaml(&widths, true));
                             str_node(&mut v, "border-type", "radial-gradient");
-                            point_node(&mut v, "center", &details.gradient.center);
-                            size_node(&mut v, "radius", &details.gradient.radius);
-                            f32_node(&mut v, "start-radius", details.gradient.start_radius);
-                            f32_node(&mut v, "end-radius", details.gradient.end_radius);
-                            let mut stops = vec![];
-                            for stop in display_list.get(base.gradient_stops()) {
-                                stops.push(Yaml::Real(stop.offset.to_string()));
-                                stops.push(Yaml::String(color_to_string(stop.color)));
-                            }
-                            yaml_node(&mut v, "stops", Yaml::Array(stops));
-                            bool_node(
+                            yaml_node(&mut v, "outset", f32_vec_yaml(&outset, true));
+                            radial_gradient_to_yaml(
                                 &mut v,
-                                "repeat",
-                                details.gradient.extend_mode == ExtendMode::Repeat,
+                                &details.gradient,
+                                base.gradient_stops(),
+                                display_list
                             );
-                            yaml_node(&mut v, "outset", f32_vec_yaml(&outset, true));
                         }
                     }
                 }
                 BoxShadow(item) => {
                     str_node(&mut v, "type", "box-shadow");
                     rect_node(&mut v, "box-bounds", &item.box_bounds);
                     vector_node(&mut v, "offset", &item.offset);
                     color_node(&mut v, "color", item.color);
@@ -955,32 +970,23 @@ impl YamlFrameWriter {
                     bool_node(
                         &mut v,
                         "repeat",
                         item.gradient.extend_mode == ExtendMode::Repeat,
                     );
                 }
                 RadialGradient(item) => {
                     str_node(&mut v, "type", "radial-gradient");
-                    point_node(&mut v, "center", &item.gradient.center);
-                    size_node(&mut v, "center", &item.gradient.radius);
-                    f32_node(&mut v, "start-radius", item.gradient.start_radius);
-                    f32_node(&mut v, "end-radius", item.gradient.end_radius);
                     size_node(&mut v, "tile-size", &item.tile_size);
                     size_node(&mut v, "tile-spacing", &item.tile_spacing);
-                    let mut stops = vec![];
-                    for stop in display_list.get(base.gradient_stops()) {
-                        stops.push(Yaml::Real(stop.offset.to_string()));
-                        stops.push(Yaml::String(color_to_string(stop.color)));
-                    }
-                    yaml_node(&mut v, "stops", Yaml::Array(stops));
-                    bool_node(
+                    radial_gradient_to_yaml(
                         &mut v,
-                        "repeat",
-                        item.gradient.extend_mode == ExtendMode::Repeat,
+                        &item.gradient,
+                        base.gradient_stops(),
+                        display_list
                     );
                 }
                 Iframe(item) => {
                     str_node(&mut v, "type", "iframe");
                     u32_vec_node(&mut v, "id", &[item.pipeline_id.0, item.pipeline_id.1]);
                 }
                 PushStackingContext(item) => {
                     str_node(&mut v, "type", "stacking-context");