Bug 1430829 - Update webrender to commit e9269c7e06e20363be0b2a2a1be98d292ff7acca. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 19 Jan 2018 12:32:00 -0500
changeset 722716 9f4eb4338e96a0a000e55acdd66e8ab1a6f43458
parent 722672 6ffbba9ce0ef9ec77a63445f068f2e218ed4830f
child 722717 50b4a163c366bae2da951e50353dd913000ce775
push id96212
push userkgupta@mozilla.com
push dateFri, 19 Jan 2018 17:35:25 +0000
reviewersjrmuizel
bugs1430829
milestone59.0a1
Bug 1430829 - Update webrender to commit e9269c7e06e20363be0b2a2a1be98d292ff7acca. r?jrmuizel MozReview-Commit-ID: CLfV8OtVG7Z
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/document.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_picture.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/capture.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/segment.rs
gfx/webrender/src/texture_allocator.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/image.rs
gfx/webrender_bindings/Cargo.toml
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -170,9 +170,9 @@ 2. Sometimes autoland tip has changed en
    has an env var you can set to do this). In theory you can get the same
    result by resolving the conflict manually but Cargo.lock files are usually not
    trivial to merge by hand. If it's just the third_party/rust dir that has conflicts
    you can delete it and run |mach vendor rust| again to repopulate it.
 
 -------------------------------------------------------------------------------
 
 The version of WebRender currently in the tree is:
-eb9e1702df4b6dc036b649b3dd32ccc4bfbe43bf
+e9269c7e06e20363be0b2a2a1be98d292ff7acca
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -14,17 +14,17 @@ debugger = ["ws", "serde_json", "serde",
 capture = ["webrender_api/debug-serialization", "ron", "serde"]
 
 [dependencies]
 app_units = "0.6"
 bincode = "0.9"
 byteorder = "1.0"
 euclid = "0.16"
 fxhash = "0.2.1"
-gleam = "0.4.15"
+gleam = "0.4.19"
 lazy_static = "1"
 log = "0.3"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "0.8"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -61,17 +61,17 @@ impl Example for App {
             Some(PropertyBinding::Binding(self.property_key)),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             filters,
         );
 
         // Fill it with a white rect
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
         match event {
             glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
                 let (offset_x, offset_y, angle, delta_opacity) = match key {
@@ -87,33 +87,35 @@ impl Example for App {
                 };
                 // Update the transform based on the keyboard input and push it to
                 // webrender using the generate_frame API. This will recomposite with
                 // the updated transform.
                 self.opacity += delta_opacity;
                 let new_transform = self.transform
                     .pre_rotate(0.0, 0.0, 1.0, Angle::radians(angle))
                     .post_translate(LayoutVector3D::new(offset_x, offset_y, 0.0));
-                api.generate_frame(
-                    document_id,
-                    Some(DynamicProperties {
+                let mut txn = Transaction::new();
+                txn.update_dynamic_properties(
+                    DynamicProperties {
                         transforms: vec![
                             PropertyValue {
                                 key: self.property_key,
                                 value: new_transform,
                             },
                         ],
                         floats: vec![
                             PropertyValue {
                                 key: self.opacity_key,
                                 value: self.opacity,
                             }
                         ],
-                    }),
+                    },
                 );
+                txn.generate_frame();
+                api.send_transaction(document_id, txn);
                 self.transform = new_transform;
             }
             _ => (),
         }
 
         false
     }
 }
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -350,26 +350,30 @@ impl Example for App {
                 box_shadow_type,
             );
         }
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+        let mut txn = Transaction::new();
         match event {
             glutin::Event::Touch(touch) => match self.touch_state.handle_event(touch) {
                 TouchResult::Pan(pan) => {
-                    api.set_pan(document_id, pan);
-                    api.generate_frame(document_id, None);
+                    txn.set_pan(pan);
                 }
                 TouchResult::Zoom(zoom) => {
-                    api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
-                    api.generate_frame(document_id, None);
+                    txn.set_pinch_zoom(ZoomFactor::new(zoom));
                 }
                 TouchResult::None => {}
             },
             _ => (),
         }
 
+        if !txn.is_empty() {
+            txn.generate_frame();
+            api.send_transaction(document_id, txn);
+        }
+
         false
     }
 }
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -172,34 +172,36 @@ pub fn main_wrapper<E: Example>(
     example.render(
         &api,
         &mut builder,
         &mut resources,
         framebuffer_size,
         pipeline_id,
         document_id,
     );
-    api.set_display_list(
-        document_id,
+    let mut txn = Transaction::new();
+    txn.set_display_list(
         epoch,
         None,
         layout_size,
         builder.finalize(),
         true,
-        resources,
     );
-    api.set_root_pipeline(document_id, pipeline_id);
-    api.generate_frame(document_id, None);
+    txn.update_resources(resources);
+    txn.set_root_pipeline(pipeline_id);
+    txn.generate_frame();
+    api.send_transaction(document_id, txn);
 
     println!("Entering event loop");
     'outer: for event in window.wait_events() {
         let mut events = Vec::new();
         events.push(event);
         events.extend(window.poll_events());
 
+        let mut txn = Transaction::new();
         for event in events {
             match event {
                 glutin::Event::Closed |
                 glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) => break 'outer,
 
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
@@ -243,30 +245,28 @@ pub fn main_wrapper<E: Example>(
                     renderer.toggle_debug_flags(webrender::DebugFlags::GPU_TIME_QUERIES
                         | webrender::DebugFlags::GPU_SAMPLE_QUERIES);
                 }
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
                     Some(glutin::VirtualKeyCode::Key1),
                 ) => {
-                    api.set_window_parameters(
-                        document_id,
+                    txn.set_window_parameters(
                         framebuffer_size,
                         DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size),
                         1.0
                     );
                 }
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
                     Some(glutin::VirtualKeyCode::Key2),
                 ) => {
-                    api.set_window_parameters(
-                        document_id,
+                    txn.set_window_parameters(
                         framebuffer_size,
                         DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size),
                         2.0
                     );
                 }
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
@@ -275,48 +275,47 @@ pub fn main_wrapper<E: Example>(
                     api.notify_memory_pressure();
                 }
                 #[cfg(feature = "capture")]
                 glutin::Event::KeyboardInput(
                     glutin::ElementState::Pressed,
                     _,
                     Some(glutin::VirtualKeyCode::C),
                 ) => {
-                    let path: PathBuf = "captures/example".into();
-                    if path.is_dir() {
-                        api.load_capture(path);
-                    } else {
-                        api.save_capture(path);
-                    }
+                    let path: PathBuf = "../captures/example".into();
+                    //TODO: switch between SCENE/FRAME capture types
+                    // based on "shift" modifier, when `glutin` is updated.
+                    let bits = CaptureBits::all();
+                    api.save_capture(path, bits);
                 }
                 _ => if example.on_event(event, &api, document_id) {
                     let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
                     let mut resources = ResourceUpdates::new();
 
                     example.render(
                         &api,
                         &mut builder,
                         &mut resources,
                         framebuffer_size,
                         pipeline_id,
                         document_id,
                     );
-                    api.set_display_list(
-                        document_id,
+                    txn.set_display_list(
                         epoch,
                         None,
                         layout_size,
                         builder.finalize(),
                         true,
-                        resources,
                     );
-                    api.generate_frame(document_id, None);
+                    txn.update_resources(resources);
+                    txn.generate_frame();
                 }
             }
         }
+        api.send_transaction(document_id, txn);
 
         renderer.update();
         renderer.render(framebuffer_size).unwrap();
         example.draw_custom(&*gl);
         window.swap_buffers().ok();
     }
 
     renderer.deinit();
--- a/gfx/webrender/examples/document.rs
+++ b/gfx/webrender/examples/document.rs
@@ -62,22 +62,20 @@ impl App {
             ),
         ];
 
         for (pipeline_id, layer, color, offset) in init_data {
             let size = DeviceUintSize::new(250, 250);
             let bounds = DeviceUintRect::new(offset, size);
 
             let document_id = api.add_document(size, layer);
-            api.set_window_parameters(document_id,
-                framebuffer_size,
-                bounds,
-                1.0
-            );
-            api.set_root_pipeline(document_id, pipeline_id);
+            let mut txn = Transaction::new();
+            txn.set_window_parameters(framebuffer_size, bounds, 1.0);
+            txn.set_root_pipeline(pipeline_id);
+            api.send_transaction(document_id, txn);
 
             self.documents.push(Document {
                 id: document_id,
                 pipeline_id,
                 content_rect: bounds.to_f32() / TypedScale::new(device_pixel_ratio),
                 color,
             });
         }
@@ -122,27 +120,26 @@ impl Example for App {
                 Vec::new(),
             );
             builder.push_rect(
                 &LayoutPrimitiveInfo::new(local_rect),
                 doc.color,
             );
             builder.pop_stacking_context();
 
-            api.set_display_list(
-                doc.id,
+            let mut txn = Transaction::new();
+            txn.set_display_list(
                 Epoch(0),
                 None,
                 doc.content_rect.size,
                 builder.finalize(),
                 true,
-                ResourceUpdates::new(),
             );
-
-            api.generate_frame(doc.id, None);
+            txn.generate_frame();
+            api.send_transaction(doc.id, txn);
         }
     }
 }
 
 fn main() {
     let mut app = App {
         documents: Vec::new(),
     };
--- a/gfx/webrender/examples/frame_output.rs
+++ b/gfx/webrender/examples/frame_output.rs
@@ -72,29 +72,27 @@ impl App {
         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),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(0),
                 channel_index: 0,
-                image_type: ExternalImageType::Texture2DHandle
+                image_type: ExternalImageType::Texture2DHandle,
             }),
             None,
         );
 
         let pipeline_id = PipelineId(1, 0);
         let layer = 1;
         let color = ColorF::new(1., 1., 0., 1.);
         let bounds = DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size);
         let document_id = api.add_document(framebuffer_size, layer);
 
-        api.set_root_pipeline(document_id, pipeline_id);
-
         let document = Document {
             id: document_id,
             pipeline_id,
             content_rect: bounds.to_f32() / TypedScale::new(device_pixel_ratio),
             color,
         };
 
         let info = LayoutPrimitiveInfo::new(document.content_rect);
@@ -111,28 +109,29 @@ impl App {
             None,
             MixBlendMode::Normal,
             Vec::new(),
         );
 
         builder.push_rect(&info, ColorF::new(1.0, 1.0, 0.0, 1.0));
         builder.pop_stacking_context();
 
-        api.enable_frame_output(document.id, document.pipeline_id, true);
-        api.set_display_list(
-            document.id,
+        let mut txn = Transaction::new();
+        txn.set_root_pipeline(pipeline_id);
+        txn.enable_frame_output(document.pipeline_id, true);
+        txn.update_resources(resources);
+        txn.set_display_list(
             Epoch(0),
             Some(document.color),
             document.content_rect.size,
             builder.finalize(),
             true,
-            resources,
         );
-        
-        api.generate_frame(document.id, None);
+        txn.generate_frame();
+        api.send_transaction(document.id, txn);
         self.output_document = Some(document);
     }
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
@@ -169,17 +168,17 @@ impl Example for App {
         );
 
         builder.pop_stacking_context();
     }
 
     fn get_image_handlers(
         &mut self,
         gl: &gl::Gl,
-    ) -> (Option<Box<webrender::ExternalImageHandler>>, 
+    ) -> (Option<Box<webrender::ExternalImageHandler>>,
           Option<Box<webrender::OutputImageHandler>>) {
         let texture_id = gl.gen_textures(1)[0];
 
         gl.bind_texture(gl::TEXTURE_2D, texture_id);
         gl.tex_parameter_i(
             gl::TEXTURE_2D,
             gl::TEXTURE_MAG_FILTER,
             gl::LINEAR as gl::GLint,
@@ -207,18 +206,18 @@ impl Example for App {
             100,
             0,
             gl::BGRA,
             gl::UNSIGNED_BYTE,
             None,
         );
         gl.bind_texture(gl::TEXTURE_2D, 0);
 
-        (   
-            Some(Box::new(ExternalHandler { texture_id })), 
+        (
+            Some(Box::new(ExternalHandler { texture_id })),
             Some(Box::new(OutputHandler { texture_id }))
         )
     }
 }
 
 fn main() {
     let mut app = App {
         external_image_key: None,
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -45,25 +45,25 @@ impl Example for App {
             MixBlendMode::Normal,
             Vec::new(),
         );
 
         // green rect visible == success
         sub_builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
         sub_builder.pop_stacking_context();
 
-        api.set_display_list(
-            document_id,
+        let mut txn = Transaction::new();
+        txn.set_display_list(
             Epoch(0),
             None,
             sub_bounds.size,
             sub_builder.finalize(),
             true,
-            ResourceUpdates::new(),
         );
+        api.send_transaction(document_id, txn);
 
         // And this is for the root pipeline
         builder.push_stacking_context(
             &info,
             ScrollPolicy::Scrollable,
             Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
             TransformStyle::Flat,
             None,
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -95,18 +95,20 @@ impl Example for App {
 
                         let mut updates = ResourceUpdates::new();
                         updates.update_image(
                             self.image_key,
                             ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true),
                             ImageData::new(image_data),
                             None,
                         );
-                        api.update_resources(updates);
-                        api.generate_frame(document_id, None);
+                        let mut txn = Transaction::new();
+                        txn.update_resources(updates);
+                        txn.generate_frame();
+                        api.send_transaction(document_id, txn);
                     }
                     _ => {}
                 }
             }
             _ => {}
         }
 
         false
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -132,28 +132,28 @@ impl Example for App {
             builder.pop_clip_id(); // clip_id
             builder.pop_stacking_context();
         }
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+        let mut txn = Transaction::new();
         match event {
             glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
                 let offset = match key {
                     glutin::VirtualKeyCode::Down => (0.0, -10.0),
                     glutin::VirtualKeyCode::Up => (0.0, 10.0),
                     glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                     glutin::VirtualKeyCode::Left => (10.0, 0.0),
                     _ => return false,
                 };
 
-                api.scroll(
-                    document_id,
+                txn.scroll(
                     ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                     self.cursor_position,
                     ScrollEventPhase::Start,
                 );
             }
             glutin::Event::MouseMoved(x, y) => {
                 self.cursor_position = WorldPoint::new(x as f32, y as f32);
             }
@@ -163,26 +163,27 @@ impl Example for App {
                 }
 
                 const LINE_HEIGHT: f32 = 38.0;
                 let (dx, dy) = match delta {
                     glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                     glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
                 };
 
-                api.scroll(
-                    document_id,
+                txn.scroll(
                     ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                     self.cursor_position,
                     ScrollEventPhase::Start,
                 );
             }
             _ => (),
         }
 
+        api.send_transaction(document_id, txn);
+
         false
     }
 }
 
 fn main() {
     let mut app = App {
         cursor_position: WorldPoint::zero(),
     };
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -236,31 +236,35 @@ impl Example for App {
             YuvColorSpace::Rec601,
             ImageRendering::Auto,
         );
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+        let mut txn = Transaction::new();
         match event {
             glutin::Event::Touch(touch) => match self.touch_state.handle_event(touch) {
                 TouchResult::Pan(pan) => {
-                    api.set_pan(document_id, pan);
-                    api.generate_frame(document_id, None);
+                    txn.set_pan(pan);
                 }
                 TouchResult::Zoom(zoom) => {
-                    api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
-                    api.generate_frame(document_id, None);
+                    txn.set_pinch_zoom(ZoomFactor::new(zoom));
                 }
                 TouchResult::None => {}
             },
             _ => (),
         }
 
+        if !txn.is_empty() {
+            txn.generate_frame();
+            api.send_transaction(document_id, txn);
+        }
+
         false
     }
 }
 
 fn main() {
     let mut app = App {
         touch_state: TouchState::new(),
     };
deleted file mode 100644
--- a/gfx/webrender/res/brush_image.glsl
+++ /dev/null
@@ -1,157 +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/. */
-
-#define VECS_PER_SPECIFIC_BRUSH 1
-
-#include shared,prim_shared,brush
-
-#ifdef WR_FEATURE_ALPHA_PASS
-varying vec2 vLocalPos;
-#endif
-
-varying vec3 vUv;
-flat varying int vImageKind;
-flat varying vec4 vUvBounds;
-flat varying vec4 vUvBounds_NoClamp;
-flat varying vec4 vParams;
-
-#if defined WR_FEATURE_ALPHA_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-flat varying vec4 vColor;
-#endif
-
-#define BRUSH_IMAGE_SIMPLE      0
-#define BRUSH_IMAGE_NINEPATCH   1
-#define BRUSH_IMAGE_MIRROR      2
-
-#ifdef WR_VERTEX_SHADER
-
-struct Picture {
-    vec4 color;
-};
-
-Picture fetch_picture(int address) {
-    vec4 data = fetch_from_resource_cache_1(address);
-    return Picture(data);
-}
-
-void brush_vs(
-    int prim_address,
-    vec2 local_pos,
-    RectWithSize local_rect,
-    ivec2 user_data,
-    PictureTask pic_task
-) {
-    vImageKind = user_data.y;
-
-    // TODO(gw): There's quite a bit of code duplication here,
-    //           depending on which variation of brush image
-    //           this is being used for. This is because only
-    //           box-shadow pictures are currently supported
-    //           as texture cacheable items. Once we port the
-    //           drop-shadows and text-shadows to be cacheable,
-    //           most of this code can be merged together.
-#if defined WR_FEATURE_COLOR_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-    BlurTask blur_task = fetch_blur_task(user_data.x);
-    vUv.z = blur_task.common_data.texture_layer_index;
-    vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
-#if defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-    vColor = blur_task.color;
-#endif
-    vec2 uv0 = blur_task.common_data.task_rect.p0;
-    vec2 src_size = blur_task.common_data.task_rect.size * blur_task.scale_factor;
-    vec2 uv1 = uv0 + blur_task.common_data.task_rect.size;
-#else
-    Picture pic = fetch_picture(prim_address);
-    ImageResource uv_rect = fetch_image_resource(user_data.x);
-    vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
-    vColor = pic.color;
-    vec2 uv0 = uv_rect.uv_rect.xy;
-    vec2 uv1 = uv_rect.uv_rect.zw;
-    vec2 src_size = (uv1 - uv0) * uv_rect.user_data.x;
-    vUv.z = uv_rect.layer;
-#endif
-
-    // TODO(gw): In the future we'll probably draw these as segments
-    //           with the brush shader. When that occurs, we can
-    //           modify the UVs for each segment in the VS, and the
-    //           FS can become a simple shader that doesn't need
-    //           to adjust the UVs.
-
-    switch (vImageKind) {
-        case BRUSH_IMAGE_SIMPLE: {
-            vec2 f = (local_pos - local_rect.p0) / local_rect.size;
-            vUv.xy = mix(uv0, uv1, f);
-            vUv.xy /= texture_size;
-            break;
-        }
-        case BRUSH_IMAGE_NINEPATCH: {
-            vec2 local_src_size = src_size / uDevicePixelRatio;
-            vUv.xy = (local_pos - local_rect.p0) / local_src_size;
-            vParams.xy = vec2(0.5);
-            vParams.zw = (local_rect.size / local_src_size - 0.5);
-            break;
-        }
-        case BRUSH_IMAGE_MIRROR: {
-            vec2 local_src_size = src_size / uDevicePixelRatio;
-            vUv.xy = (local_pos - local_rect.p0) / local_src_size;
-            vParams.xy = 0.5 * local_rect.size / local_src_size;
-            break;
-        }
-    }
-
-    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
-    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
-
-#ifdef WR_FEATURE_ALPHA_PASS
-    vLocalPos = local_pos;
-#endif
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-vec4 brush_fs() {
-    vec2 uv;
-
-    switch (vImageKind) {
-        case BRUSH_IMAGE_SIMPLE: {
-            uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-        case BRUSH_IMAGE_NINEPATCH: {
-            uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
-            uv += max(vec2(0.0), vUv.xy - vParams.zw);
-            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
-            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-        case BRUSH_IMAGE_MIRROR: {
-            // Mirror and stretch the box shadow corner over the entire
-            // primitives.
-            uv = vParams.xy - abs(vUv.xy - vParams.xy);
-
-            // Ensure that we don't fetch texels outside the box
-            // shadow corner. This can happen, for example, when
-            // drawing the outer parts of an inset box shadow.
-            uv = clamp(uv, vec2(0.0), vec2(1.0));
-            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
-            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
-    }
-
-#if defined WR_FEATURE_COLOR_TARGET
-    vec4 color = texture(sColor0, vec3(uv, vUv.z));
-#elif defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-    vec4 color = vColor * texture(sColor0, vec3(uv, vUv.z)).a;
-#else
-    vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
-#endif
-
-#ifdef WR_FEATURE_ALPHA_PASS
-    color *= init_transform_fs(vLocalPos);
-#endif
-
-    return color;
-}
-#endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/brush_picture.glsl
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 1
+
+#include shared,prim_shared,brush
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 vLocalPos;
+#endif
+
+varying vec3 vUv;
+flat varying int vImageKind;
+flat varying vec4 vUvBounds;
+flat varying vec4 vUvBounds_NoClamp;
+flat varying vec4 vParams;
+
+#if defined WR_FEATURE_ALPHA_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
+flat varying vec4 vColor;
+#endif
+
+#define BRUSH_PICTURE_SIMPLE      0
+#define BRUSH_PICTURE_NINEPATCH   1
+#define BRUSH_PICTURE_MIRROR      2
+
+#ifdef WR_VERTEX_SHADER
+
+struct Picture {
+    vec4 color;
+};
+
+Picture fetch_picture(int address) {
+    vec4 data = fetch_from_resource_cache_1(address);
+    return Picture(data);
+}
+
+void brush_vs(
+    int prim_address,
+    vec2 local_pos,
+    RectWithSize local_rect,
+    ivec2 user_data,
+    PictureTask pic_task
+) {
+    vImageKind = user_data.y;
+
+    // TODO(gw): There's quite a bit of code duplication here,
+    //           depending on which variation of brush image
+    //           this is being used for. This is because only
+    //           box-shadow pictures are currently supported
+    //           as texture cacheable items. Once we port the
+    //           drop-shadows and text-shadows to be cacheable,
+    //           most of this code can be merged together.
+#if defined WR_FEATURE_COLOR_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
+    BlurTask blur_task = fetch_blur_task(user_data.x);
+    vUv.z = blur_task.common_data.texture_layer_index;
+    vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+#if defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
+    vColor = blur_task.color;
+#endif
+    vec2 uv0 = blur_task.common_data.task_rect.p0;
+    vec2 src_size = blur_task.common_data.task_rect.size * blur_task.scale_factor;
+    vec2 uv1 = uv0 + blur_task.common_data.task_rect.size;
+#else
+    Picture pic = fetch_picture(prim_address);
+    ImageResource uv_rect = fetch_image_resource(user_data.x);
+    vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
+    vColor = pic.color;
+    vec2 uv0 = uv_rect.uv_rect.xy;
+    vec2 uv1 = uv_rect.uv_rect.zw;
+    vec2 src_size = (uv1 - uv0) * uv_rect.user_data.x;
+    vUv.z = uv_rect.layer;
+#endif
+
+    // TODO(gw): In the future we'll probably draw these as segments
+    //           with the brush shader. When that occurs, we can
+    //           modify the UVs for each segment in the VS, and the
+    //           FS can become a simple shader that doesn't need
+    //           to adjust the UVs.
+
+    switch (vImageKind) {
+        case BRUSH_PICTURE_SIMPLE: {
+            vec2 f = (local_pos - local_rect.p0) / local_rect.size;
+            vUv.xy = mix(uv0, uv1, f);
+            vUv.xy /= texture_size;
+            break;
+        }
+        case BRUSH_PICTURE_NINEPATCH: {
+            vec2 local_src_size = src_size / uDevicePixelRatio;
+            vUv.xy = (local_pos - local_rect.p0) / local_src_size;
+            vParams.xy = vec2(0.5);
+            vParams.zw = (local_rect.size / local_src_size - 0.5);
+            break;
+        }
+        case BRUSH_PICTURE_MIRROR: {
+            vec2 local_src_size = src_size / uDevicePixelRatio;
+            vUv.xy = (local_pos - local_rect.p0) / local_src_size;
+            vParams.xy = 0.5 * local_rect.size / local_src_size;
+            break;
+        }
+    }
+
+    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
+    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    vLocalPos = local_pos;
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec4 brush_fs() {
+    vec2 uv;
+
+    switch (vImageKind) {
+        case BRUSH_PICTURE_SIMPLE: {
+            uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
+            break;
+        }
+        case BRUSH_PICTURE_NINEPATCH: {
+            uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
+            uv += max(vec2(0.0), vUv.xy - vParams.zw);
+            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
+            break;
+        }
+        case BRUSH_PICTURE_MIRROR: {
+            // Mirror and stretch the box shadow corner over the entire
+            // primitives.
+            uv = vParams.xy - abs(vUv.xy - vParams.xy);
+
+            // Ensure that we don't fetch texels outside the box
+            // shadow corner. This can happen, for example, when
+            // drawing the outer parts of an inset box shadow.
+            uv = clamp(uv, vec2(0.0), vec2(1.0));
+            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
+            break;
+        }
+    }
+
+#if defined WR_FEATURE_COLOR_TARGET
+    vec4 color = texture(sColor0, vec3(uv, vUv.z));
+#elif defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
+    vec4 color = vColor * texture(sColor0, vec3(uv, vUv.z)).a;
+#else
+    vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
+#endif
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    color *= init_transform_fs(vLocalPos);
+#endif
+
+    return color;
+}
+#endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -629,19 +629,22 @@ VertexInfo write_transform_vertex(RectWi
     // As this is a transform shader, extrude by 2 (local space) pixels
     // in each direction. This gives enough space around the edge to
     // apply distance anti-aliasing. Technically, it:
     // (a) slightly over-estimates the number of required pixels in the simple case.
     // (b) might not provide enough edge in edge case perspective projections.
     // However, it's fast and simple. If / when we ever run into issues, we
     // can do some math on the projection matrix to work out a variable
     // amount to extrude.
-    float extrude_distance = 2.0;
-    local_segment_rect.p0 -= vec2(extrude_distance);
-    local_segment_rect.size += vec2(2.0 * extrude_distance);
+
+    // Only extrude along edges where we are going to apply AA.
+    float extrude_amount = 2.0;
+    vec4 extrude_distance = vec4(extrude_amount) * clip_edge_mask;
+    local_segment_rect.p0 -= extrude_distance.xy;
+    local_segment_rect.size += extrude_distance.xy + extrude_distance.zw;
 
     // Select the corner of the local rect that we are processing.
     vec2 local_pos = local_segment_rect.p0 + local_segment_rect.size * aPosition.xy;
 
     // Transform the current vertex to the world cpace.
     vec4 world_pos = scroll_node.transform * vec4(local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
@@ -667,17 +670,17 @@ VertexInfo write_transform_vertex(RectWi
     return vi;
 }
 
 VertexInfo write_transform_vertex_primitive(Primitive prim) {
     return write_transform_vertex(
         prim.local_rect,
         prim.local_rect,
         prim.local_clip_rect,
-        vec4(0.0),
+        vec4(1.0),
         prim.z,
         prim.scroll_node,
         prim.task
     );
 }
 
 struct GlyphResource {
     vec4 uv_rect;
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -30,67 +30,72 @@ use std::{usize, f32, i32};
 use tiling::{RenderTargetContext, RenderTargetKind};
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(i32::MAX as u32);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum TransformBatchKind {
     TextRun(GlyphFormat),
     Image(ImageBufferKind),
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BorderCorner,
     BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum BrushImageSourceKind {
     Alpha,
     Color,
     ColorAlphaMask,
 }
 
 impl BrushImageSourceKind {
     pub fn from_render_target_kind(render_target_kind: RenderTargetKind) -> BrushImageSourceKind {
         match render_target_kind {
             RenderTargetKind::Color => BrushImageSourceKind::Color,
             RenderTargetKind::Alpha => BrushImageSourceKind::Alpha,
         }
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum BrushBatchKind {
-    Image(BrushImageSourceKind),
+    Picture(BrushImageSourceKind),
     Solid,
     Line,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum BatchKind {
     Composite {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
     HardwareComposite,
     SplitComposite,
     Blend,
     Transformable(TransformedRectKind, TransformBatchKind),
     Brush(BrushBatchKind),
 }
 
 /// Optional textures that can be used as a source in the shaders.
 /// Textures that are not used by the batch are equal to TextureId::invalid().
 #[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct BatchTextures {
     pub colors: [SourceTexture; 3],
 }
 
 impl BatchTextures {
     pub fn no_texture() -> Self {
         BatchTextures {
             colors: [SourceTexture::Invalid; 3],
@@ -110,16 +115,17 @@ impl BatchTextures {
     pub fn color(texture: SourceTexture) -> Self {
         BatchTextures {
             colors: [texture, texture, SourceTexture::Invalid],
         }
     }
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct AlphaPrimitiveBatch {
     pub key: BatchKey,
     pub instances: Vec<PrimitiveInstance>,
     pub item_rects: Vec<DeviceIntRect>,
 }
 
 impl AlphaPrimitiveBatch {
     pub fn new(key: BatchKey) -> AlphaPrimitiveBatch {
@@ -127,31 +133,33 @@ impl AlphaPrimitiveBatch {
             key,
             instances: Vec::new(),
             item_rects: Vec::new(),
         }
     }
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct OpaquePrimitiveBatch {
     pub key: BatchKey,
     pub instances: Vec<PrimitiveInstance>,
 }
 
 impl OpaquePrimitiveBatch {
     pub fn new(key: BatchKey) -> OpaquePrimitiveBatch {
         OpaquePrimitiveBatch {
             key,
             instances: Vec::new(),
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct BatchKey {
     pub kind: BatchKind,
     pub blend_mode: BlendMode,
     pub textures: BatchTextures,
 }
 
 impl BatchKey {
     pub fn new(kind: BatchKind, blend_mode: BlendMode, textures: BatchTextures) -> Self {
@@ -170,16 +178,17 @@ impl BatchKey {
     }
 }
 
 #[inline]
 fn textures_compatible(t1: SourceTexture, t2: SourceTexture) -> bool {
     t1 == SourceTexture::Invalid || t2 == SourceTexture::Invalid || t1 == t2
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct AlphaBatchList {
     pub batches: Vec<AlphaPrimitiveBatch>,
 }
 
 impl AlphaBatchList {
     fn new() -> Self {
         AlphaBatchList {
             batches: Vec::new(),
@@ -247,16 +256,17 @@ impl AlphaBatchList {
 
         let batch = &mut self.batches[selected_batch_index.unwrap()];
         batch.item_rects.push(*item_bounding_rect);
 
         &mut batch.instances
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct OpaqueBatchList {
     pub pixel_area_threshold_for_new_batch: i32,
     pub batches: Vec<OpaquePrimitiveBatch>,
 }
 
 impl OpaqueBatchList {
     fn new(pixel_area_threshold_for_new_batch: i32) -> Self {
         OpaqueBatchList {
@@ -312,16 +322,17 @@ impl OpaqueBatchList {
         //           build these in reverse and avoid having
         //           to reverse the instance array here.
         for batch in &mut self.batches {
             batch.instances.reverse();
         }
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct BatchList {
     pub alpha_batch_list: AlphaBatchList,
     pub opaque_batch_list: OpaqueBatchList,
 }
 
 impl BatchList {
     pub fn new(screen_size: DeviceIntSize) -> Self {
         // The threshold for creating a new batch is
@@ -358,16 +369,17 @@ impl BatchList {
     }
 
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct AlphaBatcher {
     pub batch_list: BatchList,
     pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
 impl AlphaBatcher {
     pub fn new(screen_size: DeviceIntSize) -> Self {
@@ -581,30 +593,33 @@ impl AlphaBatcher {
             z,
         );
 
         let blend_mode = ctx.prim_store.get_blend_mode(prim_metadata, transform_kind);
 
         match prim_metadata.prim_kind {
             PrimitiveKind::Brush => {
                 let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
+                let batch_key = brush.get_batch_key(blend_mode);
 
                 self.add_brush_to_batch(
                     brush,
                     prim_metadata,
-                    blend_mode,
+                    batch_key,
                     clip_chain_rect_index,
                     clip_task_address,
                     item_bounding_rect,
                     prim_cache_address,
                     scroll_id,
                     task_address,
                     transform_kind,
                     z,
                     render_tasks,
+                    0,
+                    0,
                 );
             }
             PrimitiveKind::Border => {
                 let border_cpu =
                     &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
                 // TODO(gw): Select correct blend mode for edges and corners!!
                 let corner_kind = BatchKind::Transformable(
                     transform_kind,
@@ -818,46 +833,52 @@ impl AlphaBatcher {
                         match picture.kind {
                             PictureKind::TextShadow { .. } |
                             PictureKind::Image { .. } => {
                                 panic!("BUG: only supported as render tasks for now");
                             }
                             PictureKind::BoxShadow { image_kind, .. } => {
                                 let textures = BatchTextures::color(cache_item.texture_id);
                                 let kind = BatchKind::Brush(
-                                    BrushBatchKind::Image(
+                                    BrushBatchKind::Picture(
                                         BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
                                 );
-                                let key = BatchKey::new(kind, blend_mode, textures);
-                                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                                let alpha_batch_key = BatchKey::new(
+                                    kind,
+                                    blend_mode,
+                                    textures,
+                                );
 
-                                let instance = BrushInstance {
-                                    picture_address: task_address,
-                                    prim_address: prim_cache_address,
+                                self.add_brush_to_batch(
+                                    &picture.brush,
+                                    prim_metadata,
+                                    alpha_batch_key,
                                     clip_chain_rect_index,
+                                    clip_task_address,
+                                    item_bounding_rect,
+                                    prim_cache_address,
                                     scroll_id,
-                                    clip_task_address,
+                                    task_address,
+                                    transform_kind,
                                     z,
-                                    segment_index: 0,
-                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                    user_data0: cache_item.uv_rect_handle.as_int(gpu_cache),
-                                    user_data1: image_kind as i32,
-                                };
-                                batch.push(PrimitiveInstance::from(instance));
+                                    render_tasks,
+                                    cache_item.uv_rect_handle.as_int(gpu_cache),
+                                    image_kind as i32,
+                                );
                             }
                         }
                     }
                     Some(PictureSurface::RenderTask(cache_task_id)) => {
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
 
                         match picture.kind {
                             PictureKind::TextShadow { .. } => {
                                 let kind = BatchKind::Brush(
-                                    BrushBatchKind::Image(
+                                    BrushBatchKind::Picture(
                                         BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
                                 );
                                 let key = BatchKey::new(kind, blend_mode, textures);
                                 let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
 
                                 let instance = BrushInstance {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
@@ -931,17 +952,17 @@ impl AlphaBatcher {
                                                     item_bounding_rect.size.width,
                                                     item_bounding_rect.size.height,
                                                 );
 
                                                 batch.push(PrimitiveInstance::from(instance));
                                             }
                                             FilterOp::DropShadow(offset, _, _) => {
                                                 let kind = BatchKind::Brush(
-                                                    BrushBatchKind::Image(BrushImageSourceKind::ColorAlphaMask),
+                                                    BrushBatchKind::Picture(BrushImageSourceKind::ColorAlphaMask),
                                                 );
                                                 let key = BatchKey::new(kind, blend_mode, textures);
 
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
@@ -1215,52 +1236,61 @@ impl AlphaBatcher {
             }
         }
     }
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
         prim_metadata: &PrimitiveMetadata,
-        blend_mode: BlendMode,
+        batch_key: BatchKey,
         clip_chain_rect_index: ClipChainRectIndex,
         clip_task_address: RenderTaskAddress,
         item_bounding_rect: &DeviceIntRect,
         prim_cache_address: GpuCacheAddress,
         scroll_id: ClipScrollNodeIndex,
         task_address: RenderTaskAddress,
         transform_kind: TransformedRectKind,
         z: i32,
         render_tasks: &RenderTaskTree,
+        user_data0: i32,
+        user_data1: i32,
     ) {
         let base_instance = BrushInstance {
             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(),
-            user_data0: 0,
-            user_data1: 0,
+            edge_flags: EdgeAaSegmentMask::all(),
+            user_data0,
+            user_data1,
         };
 
         match brush.segment_desc {
             Some(ref segment_desc) => {
-                let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
-                    brush.get_batch_key(
-                        BlendMode::None
-                    ),
+                let alpha_batch_key = BatchKey {
+                    blend_mode: BlendMode::PremultipliedAlpha,
+                    ..batch_key
+                };
+
+                let alpha_batch = self.batch_list.alpha_batch_list.get_suitable_batch(
+                    alpha_batch_key,
                     item_bounding_rect
                 );
-                let alpha_batch = self.batch_list.alpha_batch_list.get_suitable_batch(
-                    brush.get_batch_key(
-                        BlendMode::PremultipliedAlpha
-                    ),
+
+                let opaque_batch_key = BatchKey {
+                    blend_mode: BlendMode::None,
+                    ..batch_key
+                };
+
+                let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
+                    opaque_batch_key,
                     item_bounding_rect
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
                     let needs_blending = !prim_metadata.opacity.is_opaque ||
                                          segment.clip_task_id.is_some() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
@@ -1279,33 +1309,36 @@ impl AlphaBatcher {
                     if needs_blending {
                         alpha_batch.push(instance);
                     } else {
                         opaque_batch.push(instance);
                     }
                 }
             }
             None => {
-                let batch = self.batch_list.get_suitable_batch(brush.get_batch_key(blend_mode), item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(batch_key, item_bounding_rect);
                 batch.push(PrimitiveInstance::from(base_instance));
             }
         }
     }
 }
 
 impl BrushPrimitive {
     fn get_batch_key(&self, blend_mode: BlendMode) -> BatchKey {
         match self.kind {
             BrushKind::Line { .. } => {
                 BatchKey::new(
                     BatchKind::Brush(BrushBatchKind::Line),
                     blend_mode,
                     BatchTextures::no_texture(),
                 )
             }
+            BrushKind::Picture => {
+                panic!("bug: get_batch_key is handled at higher level for pictures");
+            }
             BrushKind::Solid { .. } => {
                 BatchKey::new(
                     BatchKind::Brush(BrushBatchKind::Solid),
                     blend_mode,
                     BatchTextures::no_texture(),
                 )
             }
             BrushKind::Clear => {
@@ -1430,16 +1463,17 @@ fn make_polygon(
         transform.m42 as f64,
         transform.m43 as f64,
         transform.m44 as f64);
     Polygon::from_transformed_rect(rect.cast().unwrap(), mat, anchor)
 }
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
     pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
     pub border_clears: Vec<ClipMaskInstance>,
     pub borders: Vec<ClipMaskInstance>,
 }
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -24,16 +24,17 @@ pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 pub const MAX_BLUR_RADIUS : f32 = 300.;
 
 // The amount of padding added to the border corner drawn in the box shadow
 // mask. This ensures that we get a few pixels past the corner that can be
 // blurred without being affected by the border radius.
 pub const MASK_CORNER_PADDING: f32 = 4.0;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct BoxShadowCacheKey {
     pub width: Au,
     pub height: Au,
     pub blur_radius: Au,
     pub spread_radius: Au,
     pub offset_x: Au,
     pub offset_y: Au,
     pub br_top_left_w: Au,
@@ -209,22 +210,24 @@ impl FrameBuilder {
                         width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
                         height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
 
                         // If the width or height ends up being bigger than the original
                         // primitive shadow rect, just blur the entire rect and draw that
                         // as a simple blit.
                         if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
                             image_kind = BrushImageKind::Simple;
-                            width = prim_info.rect.size.width;
-                            height = prim_info.rect.size.height;
+                            width = prim_info.rect.size.width + spread_amount * 2.0;
+                            height = prim_info.rect.size.height + spread_amount * 2.0;
                         }
 
-                        let clip_rect = LayerRect::new(LayerPoint::zero(),
-                                                       LayerSize::new(width, height));
+                        let clip_rect = LayerRect::new(
+                            LayerPoint::zero(),
+                            LayerSize::new(width, height)
+                        );
 
                         brush_prim = BrushPrimitive::new(
                             BrushKind::Mask {
                                 clip_mode: brush_clip_mode,
                                 kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
                             },
                             None,
                         );
@@ -250,22 +253,16 @@ impl FrameBuilder {
                         cache_key,
                         pipeline_id,
                     );
                     pic_prim.add_primitive(
                         brush_prim_index,
                         clip_and_scroll
                     );
 
-                    // TODO(gw): Right now, we always use a clip out
-                    //           mask for outset shadows. We can make this
-                    //           much more efficient when we have proper
-                    //           segment logic, by avoiding drawing
-                    //           most of the pixels inside and just
-                    //           clipping out along the edges.
                     extra_clips.push(ClipSource::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut,
                     ));
 
                     let pic_info = LayerPrimitiveInfo::new(pic_rect);
                     self.add_primitive(
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/capture.rs
@@ -0,0 +1,68 @@
+/* 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 std::fs::File;
+use std::io::{Read, Write};
+use std::path::{Path, PathBuf};
+
+use api::{CaptureBits, ExternalImageData, ImageDescriptor};
+use ron::{de, ser};
+use serde::{Deserialize, Serialize};
+
+
+pub struct CaptureConfig {
+    pub root: PathBuf,
+    pub bits: CaptureBits,
+    pretty: ser::PrettyConfig,
+}
+
+impl CaptureConfig {
+    pub fn new(root: PathBuf, bits: CaptureBits) -> Self {
+        CaptureConfig {
+            root,
+            bits,
+            pretty: ser::PrettyConfig::default(),
+        }
+    }
+
+    pub fn serialize<T, P>(&self, data: &T, name: P)
+    where
+        T: Serialize,
+        P: AsRef<Path>,
+    {
+        let ron = ser::to_string_pretty(data, self.pretty.clone())
+            .unwrap();
+        let path = self.root
+            .join(name)
+            .with_extension("ron");
+        let mut file = File::create(path)
+            .unwrap();
+        write!(file, "{}\n", ron)
+            .unwrap();
+    }
+
+    pub fn deserialize<T, P>(root: &PathBuf, name: P) -> Option<T>
+    where
+        T: for<'a> Deserialize<'a>,
+        P: AsRef<Path>,
+    {
+        let mut string = String::new();
+        let path = root
+            .join(name)
+            .with_extension("ron");
+        File::open(path)
+            .ok()?
+            .read_to_string(&mut string)
+            .unwrap();
+        Some(de::from_str(&string)
+            .unwrap())
+    }
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct ExternalCaptureImage {
+    pub short_path: String,
+    pub descriptor: ImageDescriptor,
+    pub external: ExternalImageData,
+}
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -18,16 +18,17 @@ use util::TransformOrOffset;
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct CoordinateSystemId(pub u32);
 
 impl CoordinateSystemId {
     pub fn root() -> Self {
         CoordinateSystemId(0)
     }
 
     pub fn next(&self) -> Self {
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -111,22 +111,21 @@ impl DebugRenderer {
         let color_program = device
             .create_program("debug_color", "", &DESC_COLOR)
             .unwrap();
 
         let font_vao = device.create_vao(&DESC_FONT);
         let line_vao = device.create_vao(&DESC_COLOR);
         let tri_vao = device.create_vao(&DESC_COLOR);
 
-        let mut font_texture = device.create_texture(TextureTarget::Array);
+        let mut font_texture = device.create_texture(TextureTarget::Array, ImageFormat::R8);
         device.init_texture(
             &mut font_texture,
             debug_font_data::BMP_WIDTH,
             debug_font_data::BMP_HEIGHT,
-            ImageFormat::R8,
             TextureFilter::Linear,
             None,
             1,
             Some(&debug_font_data::FONT_BITMAP),
         );
 
         DebugRenderer {
             font_vertices: Vec::new(),
@@ -256,70 +255,76 @@ impl DebugRenderer {
         let p0 = rect.origin;
         let p1 = p0 + rect.size;
         self.add_line(p0.x, p0.y, color, p1.x, p0.y, color);
         self.add_line(p1.x, p0.y, color, p1.x, p1.y, color);
         self.add_line(p1.x, p1.y, color, p0.x, p1.y, color);
         self.add_line(p0.x, p1.y, color, p0.x, p0.y, color);
     }
 
-    pub fn render(&mut self, device: &mut Device, viewport_size: &DeviceUintSize) {
-        device.disable_depth();
-        device.set_blend(true);
-        device.set_blend_mode_premultiplied_alpha();
+    pub fn render(
+        &mut self,
+        device: &mut Device,
+        viewport_size: Option<DeviceUintSize>,
+    ) {
+        if let Some(viewport_size) = viewport_size {
+            device.disable_depth();
+            device.set_blend(true);
+            device.set_blend_mode_premultiplied_alpha();
 
-        let projection = Transform3D::ortho(
-            0.0,
-            viewport_size.width as f32,
-            viewport_size.height as f32,
-            0.0,
-            ORTHO_NEAR_PLANE,
-            ORTHO_FAR_PLANE,
-        );
-
-        // Triangles
-        if !self.tri_vertices.is_empty() {
-            device.bind_program(&self.color_program);
-            device.set_uniforms(&self.color_program, &projection, 0);
-            device.bind_vao(&self.tri_vao);
-            device.update_vao_indices(&self.tri_vao, &self.tri_indices, VertexUsageHint::Dynamic);
-            device.update_vao_main_vertices(
-                &self.tri_vao,
-                &self.tri_vertices,
-                VertexUsageHint::Dynamic,
+            let projection = Transform3D::ortho(
+                0.0,
+                viewport_size.width as f32,
+                viewport_size.height as f32,
+                0.0,
+                ORTHO_NEAR_PLANE,
+                ORTHO_FAR_PLANE,
             );
-            device.draw_triangles_u32(0, self.tri_indices.len() as i32);
-        }
+
+            // Triangles
+            if !self.tri_vertices.is_empty() {
+                device.bind_program(&self.color_program);
+                device.set_uniforms(&self.color_program, &projection, 0);
+                device.bind_vao(&self.tri_vao);
+                device.update_vao_indices(&self.tri_vao, &self.tri_indices, VertexUsageHint::Dynamic);
+                device.update_vao_main_vertices(
+                    &self.tri_vao,
+                    &self.tri_vertices,
+                    VertexUsageHint::Dynamic,
+                );
+                device.draw_triangles_u32(0, self.tri_indices.len() as i32);
+            }
 
-        // Lines
-        if !self.line_vertices.is_empty() {
-            device.bind_program(&self.color_program);
-            device.set_uniforms(&self.color_program, &projection, 0);
-            device.bind_vao(&self.line_vao);
-            device.update_vao_main_vertices(
-                &self.line_vao,
-                &self.line_vertices,
-                VertexUsageHint::Dynamic,
-            );
-            device.draw_nonindexed_lines(0, self.line_vertices.len() as i32);
-        }
+            // Lines
+            if !self.line_vertices.is_empty() {
+                device.bind_program(&self.color_program);
+                device.set_uniforms(&self.color_program, &projection, 0);
+                device.bind_vao(&self.line_vao);
+                device.update_vao_main_vertices(
+                    &self.line_vao,
+                    &self.line_vertices,
+                    VertexUsageHint::Dynamic,
+                );
+                device.draw_nonindexed_lines(0, self.line_vertices.len() as i32);
+            }
 
-        // Glyph
-        if !self.font_indices.is_empty() {
-            device.bind_program(&self.font_program);
-            device.set_uniforms(&self.font_program, &projection, 0);
-            device.bind_texture(DebugSampler::Font, &self.font_texture);
-            device.bind_vao(&self.font_vao);
-            device.update_vao_indices(&self.font_vao, &self.font_indices, VertexUsageHint::Dynamic);
-            device.update_vao_main_vertices(
-                &self.font_vao,
-                &self.font_vertices,
-                VertexUsageHint::Dynamic,
-            );
-            device.draw_triangles_u32(0, self.font_indices.len() as i32);
+            // Glyph
+            if !self.font_indices.is_empty() {
+                device.bind_program(&self.font_program);
+                device.set_uniforms(&self.font_program, &projection, 0);
+                device.bind_texture(DebugSampler::Font, &self.font_texture);
+                device.bind_vao(&self.font_vao);
+                device.update_vao_indices(&self.font_vao, &self.font_indices, VertexUsageHint::Dynamic);
+                device.update_vao_main_vertices(
+                    &self.font_vao,
+                    &self.font_vertices,
+                    VertexUsageHint::Dynamic,
+                );
+                device.draw_triangles_u32(0, self.font_indices.len() as i32);
+            }
         }
 
         self.font_indices.clear();
         self.font_vertices.clear();
         self.line_vertices.clear();
         self.tri_vertices.clear();
         self.tri_indices.clear();
     }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -15,22 +15,19 @@ use std::io::Read;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
 use std::thread;
 
-// Apparently, in some cases calling `glTexImage3D` with
-// similar parameters that the texture already has confuses
-// Angle when running with optimizations.
-const WORK_AROUND_TEX_IMAGE: bool = cfg!(windows);
 
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct FrameId(usize);
 
 impl FrameId {
     pub fn new(value: usize) -> FrameId {
         FrameId(value)
     }
 }
 
@@ -80,16 +77,17 @@ impl TextureTarget {
             TextureTarget::Array => gl::TEXTURE_2D_ARRAY,
             TextureTarget::Rect => gl::TEXTURE_RECTANGLE,
             TextureTarget::External => gl::TEXTURE_EXTERNAL_OES,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum TextureFilter {
     Nearest,
     Linear,
 }
 
 #[derive(Debug)]
 pub enum VertexAttributeKind {
     F32,
@@ -121,16 +119,22 @@ enum FBOTarget {
 #[derive(Debug, Clone)]
 pub enum UploadMethod {
     /// Just call `glTexSubImage` directly with the CPU data pointer
     Immediate,
     /// Accumulate the changes in PBO first before transferring to a texture.
     PixelBuffer(VertexUsageHint),
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ReadPixelsFormat {
+    Standard(ImageFormat),
+    Rgba8,
+}
+
 pub fn get_gl_format_bgra(gl: &gl::Gl) -> gl::GLuint {
     match gl.get_type() {
         gl::GlType::Gl => GL_FORMAT_BGRA_GL,
         gl::GlType::Gles => GL_FORMAT_BGRA_GLES,
     }
 }
 
 fn get_shader_version(gl: &gl::Gl) -> &'static str {
@@ -404,23 +408,24 @@ impl<V> VBO<V> {
 }
 
 impl<T> Drop for VBO<T> {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Clone))]
 pub struct ExternalTexture {
     id: gl::GLuint,
     target: gl::GLuint,
 }
 
 impl ExternalTexture {
-    pub fn new(id: u32, target: TextureTarget) -> ExternalTexture {
+    pub fn new(id: u32, target: TextureTarget) -> Self {
         ExternalTexture {
             id,
             target: target.to_gl_target(),
         }
     }
 }
 
 pub struct Texture {
@@ -448,33 +453,41 @@ impl Texture {
     pub fn get_layer_count(&self) -> i32 {
         self.layer_count
     }
 
     pub fn get_format(&self) -> ImageFormat {
         self.format
     }
 
-    pub fn get_bpp(&self) -> u32 {
-        match self.format {
-            ImageFormat::R8 => 1,
-            ImageFormat::BGRA8 => 4,
-            ImageFormat::RG8 => 2,
-            ImageFormat::RGBAF32 => 16,
-            ImageFormat::Invalid => unreachable!(),
-        }
+    pub fn get_filter(&self) -> TextureFilter {
+        self.filter
+    }
+
+    pub fn get_render_target(&self) -> Option<RenderTargetInfo> {
+        self.render_target.clone()
     }
 
     pub fn has_depth(&self) -> bool {
         self.depth_rb.is_some()
     }
 
     pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
         self.render_target.as_ref()
     }
+
+    #[cfg(feature = "capture")]
+    pub fn into_external(mut self) -> ExternalTexture {
+        let ext = ExternalTexture {
+            id: self.id,
+            target: self.target,
+        };
+        self.id = 0; // don't complain, moved out
+        ext
+    }
 }
 
 impl Drop for Texture {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
@@ -824,17 +837,17 @@ impl Device {
 
     pub fn bind_external_texture<S>(&mut self, sampler: S, external_texture: &ExternalTexture)
     where
         S: Into<TextureSlot>,
     {
         self.bind_texture_impl(sampler.into(), external_texture.id, external_texture.target);
     }
 
-    fn bind_read_target_impl(&mut self, fbo_id: FBOId) {
+    pub fn bind_read_target_impl(&mut self, fbo_id: FBOId) {
         debug_assert!(self.inside_frame);
 
         if self.bound_read_fbo != fbo_id {
             self.bound_read_fbo = fbo_id;
             fbo_id.bind(self.gl(), FBOTarget::Read);
         }
     }
 
@@ -873,24 +886,25 @@ impl Device {
                 dimensions.width as _,
                 dimensions.height as _,
             );
         }
     }
 
     pub fn create_fbo_for_external_texture(&mut self, texture_id: u32) -> FBOId {
         let fbo = FBOId(self.gl.gen_framebuffers(1)[0]);
-        self.bind_external_draw_target(fbo);
+        fbo.bind(self.gl(), FBOTarget::Draw);
         self.gl.framebuffer_texture_2d(
             gl::DRAW_FRAMEBUFFER,
             gl::COLOR_ATTACHMENT0,
             gl::TEXTURE_2D,
             texture_id,
             0,
         );
+        self.bound_draw_fbo.bind(self.gl(), FBOTarget::Draw);
         fbo
     }
 
     pub fn delete_fbo(&mut self, fbo: FBOId) {
         self.gl.delete_framebuffers(&[fbo.0]);
     }
 
     pub fn bind_external_draw_target(&mut self, fbo_id: FBOId) {
@@ -906,24 +920,26 @@ impl Device {
         debug_assert!(self.inside_frame);
 
         if self.bound_program != program.id {
             self.gl.use_program(program.id);
             self.bound_program = program.id;
         }
     }
 
-    pub fn create_texture(&mut self, target: TextureTarget) -> Texture {
+    pub fn create_texture(
+        &mut self, target: TextureTarget, format: ImageFormat,
+    ) -> Texture {
         Texture {
             id: self.gl.gen_textures(1)[0],
             target: target.to_gl_target(),
             width: 0,
             height: 0,
             layer_count: 0,
-            format: ImageFormat::Invalid,
+            format,
             filter: TextureFilter::Nearest,
             render_target: None,
             fbo_ids: vec![],
             depth_rb: None,
         }
     }
 
     fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
@@ -959,17 +975,17 @@ impl Device {
         texture.width = new_size.width;
         texture.height = new_size.height;
         let rt_info = texture.render_target
             .clone()
             .expect("Only renderable textures are expected for resize here");
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, texture.filter);
-        self.update_texture_storage(texture, &rt_info, true, false);
+        self.update_target_storage(texture, &rt_info, true, None);
 
         let rect = DeviceIntRect::new(DeviceIntPoint::zero(), old_size.to_i32());
         for (read_fbo, &draw_fbo) in old_fbos.into_iter().zip(&texture.fbo_ids) {
             self.bind_read_target_impl(read_fbo);
             self.bind_draw_target_impl(draw_fbo);
             self.blit_render_target(rect, rect);
             self.delete_fbo(read_fbo);
         }
@@ -977,139 +993,86 @@ impl Device {
         self.bind_read_target(None);
     }
 
     pub fn init_texture(
         &mut self,
         texture: &mut Texture,
         width: u32,
         height: u32,
-        format: ImageFormat,
         filter: TextureFilter,
         render_target: Option<RenderTargetInfo>,
         layer_count: i32,
-
         pixels: Option<&[u8]>,
     ) {
         debug_assert!(self.inside_frame);
 
         let is_resized = texture.width != width || texture.height != height;
-        let is_format_changed = texture.format != format;
 
-        texture.format = format;
         texture.width = width;
         texture.height = height;
         texture.filter = filter;
         texture.layer_count = layer_count;
         texture.render_target = render_target;
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, filter);
 
         match render_target {
             Some(info) => {
-                assert!(pixels.is_none());
-                self.update_texture_storage(texture, &info, is_resized, is_format_changed);
+                self.update_target_storage(texture, &info, is_resized, pixels);
             }
             None => {
-                let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format);
-                let type_ = gl_type_for_texture_format(format);
-
-                match texture.target {
-                    gl::TEXTURE_2D_ARRAY => {
-                        self.gl.tex_image_3d(
-                            gl::TEXTURE_2D_ARRAY,
-                            0,
-                            internal_format as gl::GLint,
-                            width as gl::GLint,
-                            height as gl::GLint,
-                            layer_count,
-                            0,
-                            gl_format,
-                            type_,
-                            pixels,
-                        );
-                    }
-                    gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
-                        self.gl.tex_image_2d(
-                            texture.target,
-                            0,
-                            internal_format as gl::GLint,
-                            width as gl::GLint,
-                            height as gl::GLint,
-                            0,
-                            gl_format,
-                            type_,
-                            pixels,
-                        );
-                    }
-                    _ => panic!("BUG: Unexpected texture target!"),
-                }
+                self.update_texture_storage(texture, pixels);
             }
         }
     }
 
-    /// Updates the texture storage for the texture, creating FBOs as required.
-    fn update_texture_storage(
+    /// Updates the render target storage for the texture, creating FBOs as required.
+    fn update_target_storage(
         &mut self,
         texture: &mut Texture,
         rt_info: &RenderTargetInfo,
         is_resized: bool,
-        is_format_changed: bool,
+        pixels: Option<&[u8]>,
     ) {
-        assert!(texture.layer_count > 0);
+        assert!(texture.layer_count > 0 || texture.width + texture.height == 0);
 
         let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
-        let allocate_color = needed_layer_count != 0 || is_resized || is_format_changed;
+        let allocate_color = needed_layer_count != 0 || is_resized || pixels.is_some();
 
         if allocate_color {
-            let (internal_format, gl_format) =
-                gl_texture_formats_for_image_format(&*self.gl, texture.format);
-            let type_ = gl_type_for_texture_format(texture.format);
-
+            let desc = gl_describe_format(self.gl(), texture.format);
             match texture.target {
                 gl::TEXTURE_2D_ARRAY => {
-                    if WORK_AROUND_TEX_IMAGE {
-                        // reset the contents before resizing
-                        self.gl.tex_image_3d(
-                            texture.target,
-                            0,
-                            gl::RGBA32F as _,
-                            2, 2, 1,
-                            0,
-                            gl::RGBA,
-                            gl::FLOAT,
-                            None,
-                        )
-                    }
                     self.gl.tex_image_3d(
                         texture.target,
                         0,
-                        internal_format as _,
+                        desc.internal,
                         texture.width as _,
                         texture.height as _,
                         texture.layer_count,
                         0,
-                        gl_format,
-                        type_,
-                        None,
+                        desc.external,
+                        desc.pixel_type,
+                        pixels,
                     )
                 }
                 _ => {
                     assert_eq!(texture.layer_count, 1);
                     self.gl.tex_image_2d(
                         texture.target,
                         0,
-                        internal_format as _,
+                        desc.internal,
                         texture.width as _,
                         texture.height as _,
                         0,
-                        gl_format,
-                        type_,
-                        None,
+                        desc.external,
+                        desc.pixel_type,
+                        pixels,
                     )
                 }
             }
         }
 
         if needed_layer_count > 0 {
             // Create more framebuffers to fill the gap
             let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
@@ -1182,16 +1145,50 @@ impl Device {
                     gl::RENDERBUFFER,
                     depth_rb,
                 );
             }
             self.bind_external_draw_target(original_bound_fbo);
         }
     }
 
+    fn update_texture_storage(&mut self, texture: &Texture, pixels: Option<&[u8]>) {
+        let desc = gl_describe_format(self.gl(), texture.format);
+        match texture.target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_image_3d(
+                    gl::TEXTURE_2D_ARRAY,
+                    0,
+                    desc.internal,
+                    texture.width as _,
+                    texture.height as _,
+                    texture.layer_count,
+                    0,
+                    desc.external,
+                    desc.pixel_type,
+                    pixels,
+                );
+            }
+            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
+                self.gl.tex_image_2d(
+                    texture.target,
+                    0,
+                    desc.internal,
+                    texture.width as _,
+                    texture.height as _,
+                    0,
+                    desc.external,
+                    desc.pixel_type,
+                    pixels,
+                );
+            }
+            _ => panic!("BUG: Unexpected texture target!"),
+        }
+    }
+
     pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
         debug_assert!(self.inside_frame);
 
         self.gl.blit_framebuffer(
             src_rect.origin.x,
             src_rect.origin.y,
             src_rect.origin.x + src_rect.size.width,
             src_rect.origin.y + src_rect.size.height,
@@ -1199,84 +1196,97 @@ impl Device {
             dest_rect.origin.y,
             dest_rect.origin.x + dest_rect.size.width,
             dest_rect.origin.y + dest_rect.size.height,
             gl::COLOR_BUFFER_BIT,
             gl::LINEAR,
         );
     }
 
-    pub fn free_texture_storage(&mut self, texture: &mut Texture) {
-        debug_assert!(self.inside_frame);
-
-        if texture.format == ImageFormat::Invalid {
-            return;
-        }
-
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-
-        let (internal_format, gl_format) =
-            gl_texture_formats_for_image_format(&*self.gl, texture.format);
-        let type_ = gl_type_for_texture_format(texture.format);
-
-        match texture.target {
+    fn free_texture_storage_impl(&mut self, target: gl::GLenum, desc: FormatDesc) {
+        match target {
             gl::TEXTURE_2D_ARRAY => {
                 self.gl.tex_image_3d(
                     gl::TEXTURE_2D_ARRAY,
                     0,
-                    internal_format as gl::GLint,
+                    desc.internal,
                     0,
                     0,
                     0,
                     0,
-                    gl_format,
-                    type_,
+                    desc.external,
+                    desc.pixel_type,
                     None,
                 );
             }
             _ => {
                 self.gl.tex_image_2d(
-                    texture.target,
+                    target,
                     0,
-                    internal_format,
+                    desc.internal,
                     0,
                     0,
                     0,
-                    gl_format,
-                    type_,
+                    desc.external,
+                    desc.pixel_type,
                     None,
                 );
             }
         }
+    }
+
+    pub fn free_texture_storage(&mut self, texture: &mut Texture) {
+        debug_assert!(self.inside_frame);
+
+        if texture.width + texture.height == 0 {
+            return;
+        }
+
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+        let desc = gl_describe_format(self.gl(), texture.format);
+
+        self.free_texture_storage_impl(texture.target, desc);
 
         if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
             self.gl.delete_renderbuffers(&[depth_rb]);
         }
 
         if !texture.fbo_ids.is_empty() {
             let fbo_ids: Vec<_> = texture
                 .fbo_ids
                 .drain(..)
                 .map(|FBOId(fbo_id)| fbo_id)
                 .collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
 
-        texture.format = ImageFormat::Invalid;
         texture.width = 0;
         texture.height = 0;
         texture.layer_count = 0;
     }
 
     pub fn delete_texture(&mut self, mut texture: Texture) {
         self.free_texture_storage(&mut texture);
         self.gl.delete_textures(&[texture.id]);
         texture.id = 0;
     }
 
+    #[cfg(feature = "capture")]
+    pub fn delete_external_texture(&mut self, mut external: ExternalTexture) {
+        self.bind_external_texture(DEFAULT_TEXTURE, &external);
+        //Note: the format descriptor here doesn't really matter
+        self.free_texture_storage_impl(external.target, FormatDesc {
+            internal: gl::R8 as _,
+            external: gl::RED,
+            pixel_type: gl::UNSIGNED_BYTE,
+        });
+        self.gl.delete_textures(&[external.id]);
+        external.id = 0;
+    }
+
     pub fn delete_program(&mut self, mut program: Program) {
         self.gl.delete_program(program.id);
         program.id = 0;
     }
 
     pub fn create_program(
         &mut self,
         base_filename: &str,
@@ -1485,29 +1495,98 @@ impl Device {
                 gl: &*self.gl,
                 texture,
             },
             buffer,
             marker: PhantomData,
         }
     }
 
-    pub fn read_pixels(&mut self, desc: &ImageDescriptor) -> Vec<u8> {
-        let (_, gl_format) = gl_texture_formats_for_image_format(self.gl(), desc.format);
-        let type_ = gl_type_for_texture_format(desc.format);
-
+    pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec<u8> {
+        let desc = gl_describe_format(self.gl(), img_desc.format);
         self.gl.read_pixels(
             0, 0,
-            desc.width as i32,
-            desc.height as i32,
-            gl_format,
-            type_,
+            img_desc.width as i32,
+            img_desc.height as i32,
+            desc.external,
+            desc.pixel_type,
         )
     }
 
+    /// Read rectangle of RGBA8 or BGRA8 pixels into the specified output slice.
+    pub fn read_pixels_into(
+        &mut self,
+        rect: DeviceUintRect,
+        format: ReadPixelsFormat,
+        output: &mut [u8],
+    ) {
+        let (bytes_per_pixel, desc) = match format {
+            ReadPixelsFormat::Standard(imf) => {
+                (imf.bytes_per_pixel(), gl_describe_format(self.gl(), imf))
+            }
+            ReadPixelsFormat::Rgba8 => {
+                (4, FormatDesc {
+                    external: gl::RGBA,
+                    internal: gl::RGBA8 as _,
+                    pixel_type: gl::UNSIGNED_BYTE,
+                })
+            }
+        };
+        let size_in_bytes = (bytes_per_pixel * rect.size.width * rect.size.height) as usize;
+        assert_eq!(output.len(), size_in_bytes);
+
+        self.gl.flush();
+        self.gl.read_pixels_into_buffer(
+            rect.origin.x as _,
+            rect.origin.y as _,
+            rect.size.width as _,
+            rect.size.height as _,
+            desc.external,
+            desc.pixel_type,
+            output,
+        );
+    }
+
+    /// Attaches the provided texture to the current Read FBO binding.
+    fn attach_read_texture_raw(
+        &mut self, texture_id: gl::GLuint, target: gl::GLuint, layer_id: i32
+    ) {
+        match target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.framebuffer_texture_layer(
+                    gl::READ_FRAMEBUFFER,
+                    gl::COLOR_ATTACHMENT0,
+                    texture_id,
+                    0,
+                    layer_id,
+                )
+            }
+            _ => {
+                assert_eq!(layer_id, 0);
+                self.gl.framebuffer_texture_2d(
+                    gl::READ_FRAMEBUFFER,
+                    gl::COLOR_ATTACHMENT0,
+                    target,
+                    texture_id,
+                    0,
+                )
+            }
+        }
+    }
+
+    pub fn attach_read_texture_external(
+        &mut self, texture_id: gl::GLuint, target: TextureTarget, layer_id: i32
+    ) {
+        self.attach_read_texture_raw(texture_id, target.to_gl_target(), layer_id)
+    }
+
+    pub fn attach_read_texture(&mut self, texture: &Texture, layer_id: i32) {
+        self.attach_read_texture_raw(texture.id, texture.target, layer_id)
+    }
+
     fn bind_vao_impl(&mut self, id: gl::GLuint) {
         debug_assert!(self.inside_frame);
 
         if self.bound_vao != id {
             self.bound_vao = id;
             self.gl.bind_vertex_array(id);
         }
     }
@@ -1920,41 +1999,53 @@ impl Device {
         self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC1_COLOR);
     }
 
     pub fn supports_extension(&self, extension: &str) -> bool {
         self.extensions.iter().any(|s| s == extension)
     }
 }
 
-/// return (gl_internal_format, gl_format)
-fn gl_texture_formats_for_image_format(
-    gl: &gl::Gl,
-    format: ImageFormat,
-) -> (gl::GLint, gl::GLuint) {
+struct FormatDesc {
+    internal: gl::GLint,
+    external: gl::GLuint,
+    pixel_type: gl::GLuint,
+}
+
+fn gl_describe_format(gl: &gl::Gl, format: ImageFormat) -> FormatDesc {
     match format {
-        ImageFormat::R8 => (gl::RED as gl::GLint, gl::RED),
-        ImageFormat::BGRA8 => match gl.get_type() {
-            gl::GlType::Gl => (gl::RGBA as gl::GLint, get_gl_format_bgra(gl)),
-            gl::GlType::Gles => (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl)),
+        ImageFormat::R8 => FormatDesc {
+            internal: gl::RED as _,
+            external: gl::RED,
+            pixel_type: gl::UNSIGNED_BYTE,
         },
-        ImageFormat::RGBAF32 => (gl::RGBA32F as gl::GLint, gl::RGBA),
-        ImageFormat::RG8 => (gl::RG8 as gl::GLint, gl::RG),
-        ImageFormat::Invalid => unreachable!(),
+        ImageFormat::BGRA8 => {
+            let external = get_gl_format_bgra(gl);
+            FormatDesc {
+                internal: match gl.get_type() {
+                    gl::GlType::Gl => gl::RGBA as _,
+                    gl::GlType::Gles => external as _,
+                },
+                external,
+                pixel_type: gl::UNSIGNED_BYTE,
+            }
+        },
+        ImageFormat::RGBAF32 => FormatDesc {
+            internal: gl::RGBA32F as _,
+            external: gl::RGBA,
+            pixel_type: gl::FLOAT,
+        },
+        ImageFormat::RG8 => FormatDesc {
+            internal: gl::RG8 as _,
+            external: gl::RG,
+            pixel_type: gl::UNSIGNED_BYTE,
+        },
     }
 }
 
-fn gl_type_for_texture_format(format: ImageFormat) -> gl::GLuint {
-    match format {
-        ImageFormat::RGBAF32 => gl::FLOAT,
-        _ => gl::UNSIGNED_BYTE,
-    }
-}
-
-
 struct UploadChunk {
     rect: DeviceUintRect,
     layer_index: i32,
     stride: Option<u32>,
     offset: usize,
 }
 
 struct PixelBuffer {
@@ -2055,17 +2146,16 @@ impl<'a, T> TextureUploader<'a, T> {
 
 impl<'a> UploadTarget<'a> {
     fn update_impl(&mut self, chunk: UploadChunk) {
         let (gl_format, bpp, data_type) = match self.texture.format {
             ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
             ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl), 4, gl::UNSIGNED_BYTE),
             ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
             ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
-            ImageFormat::Invalid => unreachable!(),
         };
 
         let row_length = match chunk.stride {
             Some(value) => value / bpp,
             None => self.texture.width,
         };
 
         if chunk.stride.is_some() {
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -16,19 +16,20 @@ use clip_scroll_node::StickyFrameInfo;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
 use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
-use tiling::CompositeOps;
+use tiling::{CompositeOps, Frame};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
@@ -1090,16 +1091,21 @@ impl FrameContext {
             .finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
         frame_builder
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         self.pipeline_epoch_map.insert(pipeline_id, epoch);
     }
 
+    pub fn make_rendered_document(&self, frame: Frame) -> RenderedDocument {
+        let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
+        RenderedDocument::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
+    }
+
     pub fn build_rendered_document(
         &mut self,
         frame_builder: &mut FrameBuilder,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         device_pixel_scale: DevicePixelScale,
         layer: DocumentLayer,
@@ -1117,13 +1123,11 @@ impl FrameContext {
             self.window_size,
             device_pixel_scale,
             layer,
             pan,
             texture_cache_profile,
             gpu_cache_profile,
             scene_properties,
         );
-
-        let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
-        RenderedDocument::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
+        self.make_rendered_document(frame)
     }
 }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -29,17 +29,17 @@ use prim_store::{PrimitiveContainer, Pri
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
 use prim_store::{BrushSegmentDescriptor, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::ResourceCache;
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize, f32};
 use tiling::{CompositeOps, Frame, RenderPass, RenderTargetKind};
-use tiling::{RenderTargetContext, ScrollbarPrimitive};
+use tiling::{RenderPassKind, RenderTargetContext, ScrollbarPrimitive};
 use util::{self, MaxRect, pack_as_float, RectHelpers, recycle_vec};
 
 #[derive(Debug)]
 pub struct ScrollbarInfo(pub ClipId, pub LayerRect);
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
@@ -847,16 +847,18 @@ impl FrameBuilder {
                     color: shadow_color.premultiplied(),
                     style,
                     orientation,
                 },
                 None,
             );
             let mut info = info.clone();
             info.rect = info.rect.translate(&shadow_offset);
+            info.local_clip =
+              LocalClip::from(info.local_clip.clip_rect().translate(&shadow_offset));
             let prim_index = self.create_primitive(
                 &info,
                 Vec::new(),
                 PrimitiveContainer::Brush(line),
             );
             self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
         }
 
@@ -1357,16 +1359,18 @@ impl FrameBuilder {
                 _ => {}
             }
         }
 
         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.local_clip =
+              LocalClip::from(info.local_clip.clip_rect().translate(&text_prim.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));
         }
 
@@ -1752,16 +1756,17 @@ impl FrameBuilder {
             render_tasks.assign_to_passes(
                 main_render_task_id,
                 required_pass_count - 1,
                 &mut passes,
             );
         }
 
         let mut deferred_resolves = vec![];
+        let mut has_texture_cache_tasks = false;
         let use_dual_source_blending = self.config.dual_source_blending_is_enabled &&
                                        self.config.dual_source_blending_is_supported;
 
         for (pass_index, pass) in passes.iter_mut().enumerate() {
             let ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
@@ -1773,16 +1778,20 @@ impl FrameBuilder {
             pass.build(
                 &ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
                 RenderPassIndex(pass_index),
             );
+
+            if let RenderPassKind::OffScreen { ref texture_cache, .. } = pass.kind {
+                has_texture_cache_tasks |= !texture_cache.is_empty();
+            }
         }
 
         let gpu_cache_updates = gpu_cache.end_frame(gpu_cache_profile);
 
         render_tasks.build();
 
         resource_cache.end_frame();
 
@@ -1794,11 +1803,13 @@ impl FrameBuilder {
             layer,
             profile_counters,
             passes,
             node_data,
             clip_chain_local_clip_rects,
             render_tasks,
             deferred_resolves,
             gpu_cache_updates: Some(gpu_cache_updates),
+            has_been_rendered: false,
+            has_texture_cache_tasks,
         }
     }
 }
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -5,19 +5,21 @@
 use std::marker::PhantomData;
 use util::recycle_vec;
 
 // TODO(gw): Add an occupied list head, for fast
 //           iteration of the occupied list to implement
 //           retain() style functionality.
 
 #[derive(Debug, Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct Epoch(u32);
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct FreeListHandle<T> {
     index: u32,
     epoch: Epoch,
     _marker: PhantomData<T>,
 }
 
 impl<T> FreeListHandle<T> {
     pub fn weak(&self) -> WeakFreeListHandle<T> {
@@ -25,50 +27,53 @@ impl<T> FreeListHandle<T> {
             index: self.index,
             epoch: self.epoch,
             _marker: PhantomData,
         }
     }
 }
 
 impl<T> Clone for WeakFreeListHandle<T> {
-    fn clone(&self) -> WeakFreeListHandle<T> {
+    fn clone(&self) -> Self {
         WeakFreeListHandle {
             index: self.index,
             epoch: self.epoch,
             _marker: PhantomData,
         }
     }
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct WeakFreeListHandle<T> {
     index: u32,
     epoch: Epoch,
     _marker: PhantomData<T>,
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct Slot<T> {
     next: Option<u32>,
     epoch: Epoch,
     value: Option<T>,
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct FreeList<T> {
     slots: Vec<Slot<T>>,
     free_list_head: Option<u32>,
 }
 
 pub enum UpsertResult<T> {
     Updated(T),
     Inserted(FreeListHandle<T>),
 }
 
 impl<T> FreeList<T> {
-    pub fn new() -> FreeList<T> {
+    pub fn new() -> Self {
         FreeList {
             slots: Vec::new(),
             free_list_head: None,
         }
     }
 
     pub fn recycle(self) -> FreeList<T> {
         FreeList {
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -4,33 +4,44 @@
 
 use api::{DevicePoint, DeviceUintSize, GlyphKey};
 use glyph_rasterizer::{FontInstance, GlyphFormat};
 use internal_types::FastHashMap;
 use resource_cache::ResourceClassCache;
 use std::sync::Arc;
 use texture_cache::TextureCacheHandle;
 
-pub struct CachedGlyphInfo {
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+pub struct GenericCachedGlyphInfo<D> {
     pub texture_cache_handle: TextureCacheHandle,
-    pub glyph_bytes: Arc<Vec<u8>>,
+    pub glyph_bytes: D,
     pub size: DeviceUintSize,
     pub offset: DevicePoint,
     pub scale: f32,
     pub format: GlyphFormat,
 }
 
+pub type CachedGlyphInfo = GenericCachedGlyphInfo<Arc<Vec<u8>>>;
 pub type GlyphKeyCache = ResourceClassCache<GlyphKey, Option<CachedGlyphInfo>>;
 
+#[cfg(feature = "capture")]
+pub type PlainCachedGlyphInfo = GenericCachedGlyphInfo<String>;
+#[cfg(feature = "capture")]
+pub type PlainGlyphKeyCache = ResourceClassCache<GlyphKey, Option<PlainCachedGlyphInfo>>;
+#[cfg(feature = "capture")]
+pub type PlainGlyphCacheRef<'a> = FastHashMap<&'a FontInstance, PlainGlyphKeyCache>;
+#[cfg(feature = "capture")]
+pub type PlainGlyphCacheOwn = FastHashMap<FontInstance, PlainGlyphKeyCache>;
+
 pub struct GlyphCache {
     pub glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
 }
 
 impl GlyphCache {
-    pub fn new() -> GlyphCache {
+    pub fn new() -> Self {
         GlyphCache {
             glyph_key_caches: FastHashMap::default(),
         }
     }
 
     pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache {
         self.glyph_key_caches
             .entry(font)
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -214,16 +214,17 @@ impl FontInstance {
             (bold_offset * x_scale).max(1.0).round() as usize
         } else {
             0
         }
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 #[allow(dead_code)]
 pub enum GlyphFormat {
     Alpha,
     TransformedAlpha,
     Subpixel,
     TransformedSubpixel,
     Bitmap,
     ColorBitmap,
@@ -591,16 +592,17 @@ impl FontContext {
             &FontTemplate::Native(ref native_font_handle) => {
                 self.add_native_font(&font_key, (*native_font_handle).clone());
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct GlyphRequest {
     pub key: GlyphKey,
     pub font: FontInstance,
 }
 
 impl GlyphRequest {
     pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
         GlyphRequest {
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -33,38 +33,41 @@ use std::{mem, u16, u32};
 use std::ops::Add;
 
 
 pub const GPU_CACHE_INITIAL_HEIGHT: u32 = 512;
 const FRAMES_BEFORE_EVICTION: usize = 10;
 const NEW_ROWS_PER_RESIZE: u32 = 512;
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct Epoch(u32);
 
 impl Epoch {
     fn next(&mut self) {
         *self = Epoch(self.0.wrapping_add(1));
     }
 }
 
 #[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct CacheLocation {
     block_index: BlockIndex,
     epoch: Epoch,
 }
 
 /// A single texel in RGBAF32 texture - 16 bytes.
 #[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct GpuBlockData {
     pub data: [f32; 4],
 }
 
 impl GpuBlockData {
-    pub fn empty() -> GpuBlockData {
+    pub fn empty() -> Self {
         GpuBlockData { data: [0.0; 4] }
     }
 }
 
 /// Conversion helpers for GpuBlockData
 impl Into<GpuBlockData> for PremultipliedColorF {
     fn into(self) -> GpuBlockData {
         GpuBlockData {
@@ -104,44 +107,46 @@ impl Into<GpuBlockData> for UvRect {
 // implement this trait.
 pub trait ToGpuBlocks {
     // Request an arbitrary number of GPU data blocks.
     fn write_gpu_blocks(&self, GpuDataRequest);
 }
 
 // A handle to a GPU resource.
 #[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct GpuCacheHandle {
     location: Option<CacheLocation>,
 }
 
 impl GpuCacheHandle {
     pub fn new() -> Self {
         GpuCacheHandle { location: None }
     }
 }
 
 // A unique address in the GPU cache. These are uploaded
 // as part of the primitive instances, to allow the vertex
 // shader to fetch the specific data.
 #[derive(Copy, Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct GpuCacheAddress {
     pub u: u16,
     pub v: u16,
 }
 
 impl GpuCacheAddress {
-    fn new(u: usize, v: usize) -> GpuCacheAddress {
+    fn new(u: usize, v: usize) -> Self {
         GpuCacheAddress {
             u: u as u16,
             v: v as u16,
         }
     }
 
-    pub fn invalid() -> GpuCacheAddress {
+    pub fn invalid() -> Self {
         GpuCacheAddress {
             u: u16::MAX,
             v: u16::MAX,
         }
     }
 }
 
 impl Add<usize> for GpuCacheAddress {
@@ -152,16 +157,17 @@ impl Add<usize> for GpuCacheAddress {
             u: self.u + other as u16,
             v: self.v,
         }
     }
 }
 
 // An entry in a free-list of blocks in the GPU cache.
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct Block {
     // The location in the cache of this block.
     address: GpuCacheAddress,
     // Index of the next free block in the list it
     // belongs to (either a free-list or the
     // occupied list).
     next: Option<BlockIndex>,
     // The current epoch (generation) of this block.
@@ -177,19 +183,21 @@ impl Block {
             next,
             last_access_time: frame_id,
             epoch: Epoch(0),
         }
     }
 }
 
 #[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct BlockIndex(usize);
 
 // A row in the cache texture.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct Row {
     // The fixed size of blocks that this row supports.
     // Each row becomes a slab allocator for a fixed block size.
     // This means no dealing with fragmentation within a cache
     // row as items are allocated and freed.
     block_count_per_item: usize,
 }
 
@@ -200,16 +208,17 @@ impl Row {
         }
     }
 }
 
 // A list of update operations that can be applied on the cache
 // this frame. The list of updates is created by the render backend
 // during frame construction. It's passed to the render thread
 // where GL commands can be applied.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum GpuCacheUpdate {
     Copy {
         block_index: usize,
         block_count: usize,
         address: GpuCacheAddress,
     },
 }
 
@@ -221,30 +230,31 @@ pub struct GpuCacheUpdateList {
     pub updates: Vec<GpuCacheUpdate>,
     // A flat list of GPU blocks that are pending upload
     // to GPU memory.
     pub blocks: Vec<GpuBlockData>,
 }
 
 // Holds the free lists of fixed size blocks. Mostly
 // just serves to work around the borrow checker.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct FreeBlockLists {
     free_list_1: Option<BlockIndex>,
     free_list_2: Option<BlockIndex>,
     free_list_4: Option<BlockIndex>,
     free_list_8: Option<BlockIndex>,
     free_list_16: Option<BlockIndex>,
     free_list_32: Option<BlockIndex>,
     free_list_64: Option<BlockIndex>,
     free_list_128: Option<BlockIndex>,
     free_list_large: Option<BlockIndex>,
 }
 
 impl FreeBlockLists {
-    fn new() -> FreeBlockLists {
+    fn new() -> Self {
         FreeBlockLists {
             free_list_1: None,
             free_list_2: None,
             free_list_4: None,
             free_list_8: None,
             free_list_16: None,
             free_list_32: None,
             free_list_64: None,
@@ -271,16 +281,17 @@ impl FreeBlockLists {
             65...128 => (128, &mut self.free_list_128),
             129...MAX_VERTEX_TEXTURE_WIDTH => (MAX_VERTEX_TEXTURE_WIDTH, &mut self.free_list_large),
             _ => panic!("Can't allocate > MAX_VERTEX_TEXTURE_WIDTH per resource!"),
         }
     }
 }
 
 // CPU-side representation of the GPU resource cache texture.
+#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
 struct Texture {
     // Current texture height
     height: u32,
     // All blocks that have been created for this texture
     blocks: Vec<Block>,
     // Metadata about each allocated row.
     rows: Vec<Row>,
     // Free lists of available blocks for each supported
@@ -485,16 +496,17 @@ impl<'a> Drop for GpuDataRequest<'a> {
         let location = self.texture
             .push_data(Some(self.start_index), block_count, self.frame_id);
         self.handle.location = Some(location);
     }
 }
 
 
 /// The main LRU cache interface.
+#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))]
 pub struct GpuCache {
     /// Current frame ID.
     frame_id: FrameId,
     /// CPU-side texture allocator.
     texture: Texture,
     /// Number of blocks requested this frame that don't
     /// need to be re-uploaded.
     saved_block_count: usize,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -6,44 +6,48 @@ use api::{LayerToWorldTransform};
 use gpu_cache::GpuCacheAddress;
 use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum BlurDirection {
     Horizontal = 0,
     Vertical,
 }
 
 #[derive(Debug)]
 #[repr(C)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct BlurInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub blur_direction: BlurDirection,
 }
 
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 #[repr(C)]
 pub struct ClipMaskInstance {
     pub render_task_address: RenderTaskAddress,
     pub scroll_node_data_index: ClipScrollNodeIndex,
     pub segment: i32,
     pub clip_data_address: GpuCacheAddress,
     pub resource_address: GpuCacheAddress,
 }
 
 // 32 bytes per instance should be enough for anyone!
 #[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct PrimitiveInstance {
     data: [i32; 8],
 }
 
 pub struct SimplePrimitiveInstance {
     pub specific_prim_address: GpuCacheAddress,
     pub task_address: RenderTaskAddress,
     pub clip_task_address: RenderTaskAddress,
@@ -55,17 +59,17 @@ pub struct SimplePrimitiveInstance {
 impl SimplePrimitiveInstance {
     pub fn new(
         specific_prim_address: GpuCacheAddress,
         task_address: RenderTaskAddress,
         clip_task_address: RenderTaskAddress,
         clip_chain_rect_index: ClipChainRectIndex,
         scroll_id: ClipScrollNodeIndex,
         z_sort_index: i32,
-    ) -> SimplePrimitiveInstance {
+    ) -> Self {
         SimplePrimitiveInstance {
             specific_prim_address,
             task_address,
             clip_task_address,
             clip_chain_rect_index,
             scroll_id,
             z_sort_index,
         }
@@ -103,32 +107,32 @@ impl CompositePrimitiveInstance {
         task_address: RenderTaskAddress,
         src_task_address: RenderTaskAddress,
         backdrop_task_address: RenderTaskAddress,
         data0: i32,
         data1: i32,
         z: i32,
         data2: i32,
         data3: i32,
-    ) -> CompositePrimitiveInstance {
+    ) -> Self {
         CompositePrimitiveInstance {
             task_address,
             src_task_address,
             backdrop_task_address,
             data0,
             data1,
             z,
             data2,
             data3,
         }
     }
 }
 
 impl From<CompositePrimitiveInstance> for PrimitiveInstance {
-    fn from(instance: CompositePrimitiveInstance) -> PrimitiveInstance {
+    fn from(instance: CompositePrimitiveInstance) -> Self {
         PrimitiveInstance {
             data: [
                 instance.task_address.0 as i32,
                 instance.src_task_address.0 as i32,
                 instance.backdrop_task_address.0 as i32,
                 instance.z,
                 instance.data0,
                 instance.data1,
@@ -156,17 +160,17 @@ pub struct BrushInstance {
     pub z: i32,
     pub segment_index: i32,
     pub edge_flags: EdgeAaSegmentMask,
     pub user_data0: i32,
     pub user_data1: i32,
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
-    fn from(instance: BrushInstance) -> PrimitiveInstance {
+    fn from(instance: BrushInstance) -> Self {
         PrimitiveInstance {
             data: [
                 instance.picture_address.0 as i32,
                 instance.prim_address.as_int(),
                 ((instance.clip_chain_rect_index.0 as i32) << 16) | instance.scroll_id.0 as i32,
                 instance.clip_task_address.0 as i32,
                 instance.z,
                 instance.segment_index | ((instance.edge_flags.bits() as i32) << 16),
@@ -184,40 +188,43 @@ impl From<BrushInstance> for PrimitiveIn
 #[derive(Debug, Copy, Clone)]
 pub enum BrushImageKind {
     Simple = 0,     // A normal rect
     NinePatch = 1,  // A nine-patch image (stretch inside segments)
     Mirror = 2,     // A top left corner only (mirror across x/y axes)
 }
 
 #[derive(Copy, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 #[repr(C)]
 pub struct ClipScrollNodeIndex(pub u32);
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 #[repr(C)]
 pub struct ClipScrollNodeData {
     pub transform: LayerToWorldTransform,
     pub transform_kind: f32,
     pub padding: [f32; 3],
 }
 
 impl ClipScrollNodeData {
-    pub fn invalid() -> ClipScrollNodeData {
+    pub fn invalid() -> Self {
         ClipScrollNodeData {
             transform: LayerToWorldTransform::identity(),
             transform_kind: 0.0,
             padding: [0.0; 3],
         }
     }
 }
 
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[repr(C)]
 pub struct ClipChainRectIndex(pub usize);
 
 #[derive(Copy, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 #[repr(C)]
 pub enum PictureType {
     Image = 1,
     TextShadow = 2,
     BoxShadow = 3,
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,70 +1,75 @@
 /* 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::{ClipId, DevicePoint, DeviceUintRect, DocumentId, Epoch};
 use api::{ExternalImageData, ExternalImageId};
 use api::{ImageFormat, PipelineId};
-#[cfg(feature = "capture")]
-use api::ImageDescriptor;
 use api::DebugCommand;
 use device::TextureFilter;
 use fxhash::FxHasher;
 use profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
 use std::sync::Arc;
+
+#[cfg(feature = "capture")]
+use capture::{CaptureConfig, ExternalCaptureImage};
 use tiling;
 
 pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
 pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
 // to be added to an atlas). The texture cache
 // manages the allocation and freeing of these
 // IDs, and the rendering thread maintains a
 // map from cache texture ID to native texture.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct CacheTextureId(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderPassIndex(pub usize);
 
 // Represents the source for a texture.
 // These are passed from throughout the
 // pipeline until they reach the rendering
 // thread, where they are resolved to a
 // native texture ID.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum SourceTexture {
     Invalid,
     TextureCache(CacheTextureId),
     External(ExternalImageData),
     CacheA8,
     CacheRGBA8,
     // XXX Remove this once RenderTaskCacheA8 is used.
     #[allow(dead_code)]
     RenderTaskCacheA8(RenderPassIndex),
     RenderTaskCacheRGBA8(RenderPassIndex),
 }
 
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Copy, Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTargetInfo {
     pub has_depth: bool,
 }
 
 #[derive(Debug)]
 pub enum TextureUpdateSource {
     External {
         id: ExternalImageId,
@@ -94,22 +99,23 @@ pub enum TextureUpdateOp {
 }
 
 #[derive(Debug)]
 pub struct TextureUpdate {
     pub id: CacheTextureId,
     pub op: TextureUpdateOp,
 }
 
+#[derive(Default)]
 pub struct TextureUpdateList {
     pub updates: Vec<TextureUpdate>,
 }
 
 impl TextureUpdateList {
-    pub fn new() -> TextureUpdateList {
+    pub fn new() -> Self {
         TextureUpdateList {
             updates: Vec::new(),
         }
     }
 
     #[inline]
     pub fn push(&mut self, update: TextureUpdate) {
         self.updates.push(update);
@@ -137,30 +143,23 @@ impl RenderedDocument {
         RenderedDocument {
             pipeline_epoch_map,
             layers_bouncing_back,
             frame,
         }
     }
 }
 
-#[cfg(feature = "capture")]
-pub struct ExternalCaptureImage {
-    pub short_path: String,
-    pub descriptor: ImageDescriptor,
-    pub external: ExternalImageData,
-}
-
 pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
-    SaveCapture(PathBuf, Vec<ExternalCaptureImage>),
+    SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
     #[cfg(feature = "capture")]
-    LoadCapture,
+    LoadCapture(PathBuf),
 }
 
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
     DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     PublishDocument(
         DocumentId,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -50,16 +50,18 @@ extern crate log;
 extern crate thread_profiler;
 #[cfg(any(feature = "debugger", feature = "capture"))]
 #[macro_use]
 extern crate serde;
 
 mod batch;
 mod border;
 mod box_shadow;
+#[cfg(feature = "capture")]
+mod capture;
 mod clip;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
@@ -155,15 +157,15 @@ extern crate ws;
 #[cfg(feature = "debugger")]
 extern crate image;
 #[cfg(feature = "debugger")]
 extern crate base64;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
-pub use device::{build_shader_strings, ProgramCache, UploadMethod, VertexUsageHint};
+pub use device::{build_shader_strings, ProgramCache, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use renderer::{CpuProfile, DebugFlags, GpuProfile, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource};
-pub use renderer::{GraphicsApi, GraphicsApiInfo, ReadPixelsFormat, Renderer, RendererOptions};
+pub use renderer::{GraphicsApi, GraphicsApiInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, ThreadListener};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -5,17 +5,17 @@
 use api::{ColorF, ClipAndScrollInfo, FilterOp, MixBlendMode};
 use api::{DeviceIntPoint, DeviceIntRect, LayerToWorldScale, PipelineId};
 use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerVector2D, Shadow};
 use api::{ClipId, PremultipliedColorF};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
 use frame_builder::PrimitiveContext;
 use gpu_cache::{GpuCache, GpuDataRequest};
 use gpu_types::{BrushImageKind, PictureType};
-use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
+use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskTree};
 use resource_cache::{CacheItem, ResourceCache};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  A picture represents a dynamically rendered image. It consists of:
@@ -38,16 +38,17 @@ pub enum PictureCompositeMode {
     /// 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(Deserialize, Serialize))]
 pub enum ContentOrigin {
     Local(LayerPoint),
     Screen(DeviceIntPoint),
 }
 
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
@@ -113,31 +114,44 @@ pub struct PicturePrimitive {
 
     // 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 brush primitive that will be used to draw this
+    // picture.
+    // TODO(gw): Having a brush primitive embedded here
+    //           makes the code complex in a few places.
+    //           Consider a better way to structure this.
+    //           Maybe embed the PicturePrimitive inside
+    //           the BrushKind enum instead?
+    pub brush: BrushPrimitive,
 }
 
 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,
+            brush: BrushPrimitive::new(
+                BrushKind::Picture,
+                None,
+            ),
         }
     }
 
     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)) => {
@@ -173,16 +187,20 @@ impl PicturePrimitive {
                 color,
                 clip_mode,
                 image_kind,
                 content_rect: LayerRect::zero(),
                 cache_key,
             },
             pipeline_id,
             cull_children: false,
+            brush: BrushPrimitive::new(
+                BrushKind::Picture,
+                None,
+            ),
         }
     }
 
     pub fn new_image(
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         reference_frame_id: ClipId,
@@ -196,16 +214,20 @@ impl PicturePrimitive {
                 composite_mode,
                 is_in_3d_context,
                 frame_output_pipeline_id,
                 reference_frame_id,
                 real_local_rect: LayerRect::zero(),
             },
             pipeline_id,
             cull_children: true,
+            brush: BrushPrimitive::new(
+                BrushKind::Picture,
+                None,
+            ),
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
         clip_and_scroll: ClipAndScrollInfo
     ) {
@@ -555,17 +577,17 @@ impl PicturePrimitive {
                         );
 
                         let root_task_id = render_tasks.add(blur_render_task);
                         parent_tasks.push(root_task_id);
 
                         // TODO(gw): Remove the nastiness with having to pass
                         //           the scale factor through the texture cache
                         //           item user data. This will disappear once
-                        //           the brush_image shader is updated to draw
+                        //           the brush_picture shader is updated to draw
                         //           segments, since the scale factor will not
                         //           be used at all then during drawing.
                         (root_task_id, [scale_factor, 0.0, 0.0])
                     }
                 );
 
                 self.surface = Some(PictureSurface::TextureCache(cache_item));
             }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -135,16 +135,17 @@ pub struct DeferredResolve {
     pub address: GpuCacheAddress,
     pub image_properties: ImageProperties,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct SpecificPrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct PrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
     TextRun,
     Image,
     YuvImage,
     Border,
@@ -211,23 +212,25 @@ pub enum BrushKind {
         color: ColorF,
     },
     Clear,
     Line {
         color: PremultipliedColorF,
         wavy_line_thickness: f32,
         style: LineStyle,
         orientation: LineOrientation,
-    }
+    },
+    Picture,
 }
 
 impl BrushKind {
-    fn is_solid(&self) -> bool {
+    fn supports_segments(&self) -> bool {
         match *self {
-            BrushKind::Solid { .. } => true,
+            BrushKind::Solid { .. } |
+            BrushKind::Picture => true,
             _ => false,
         }
     }
 }
 
 bitflags! {
     /// Each bit of the edge AA mask is:
     /// 0, when the edge of the primitive needs to be considered for AA
@@ -295,16 +298,18 @@ impl BrushPrimitive {
             kind,
             segment_desc,
         }
     }
 
     fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // has to match VECS_PER_SPECIFIC_BRUSH
         match self.kind {
+            BrushKind::Picture => {
+            }
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
             BrushKind::Mask { clip_mode, kind: BrushMaskKind::Corner(radius) } => {
@@ -1015,16 +1020,21 @@ impl PrimitiveStore {
 
         let metadata = match container {
             PrimitiveContainer::Brush(brush) => {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a),
                     BrushKind::Mask { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Line { .. } => PrimitiveOpacity::translucent(),
+                    BrushKind::Picture => {
+                        // TODO(gw): This is not currently used. In the future
+                        //           we should detect opaque pictures.
+                        unreachable!();
+                    }
                 };
 
                 let metadata = PrimitiveMetadata {
                     opacity,
                     prim_kind: PrimitiveKind::Brush,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()),
                     ..base_metadata
                 };
@@ -1258,27 +1268,32 @@ impl PrimitiveStore {
                     let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
                     gradient.build_gpu_blocks_for_angle_radial(prim_context.display_list, request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Picture => {
-                    self.cpu_pictures[metadata.cpu_prim_index.0]
-                        .write_gpu_blocks(&mut request);
+                    let pic = &self.cpu_pictures[metadata.cpu_prim_index.0];
+                    pic.write_gpu_blocks(&mut request);
 
-                    // TODO(gw): This is a bit of a hack. The Picture type
-                    //           is drawn by the brush_image shader, so the
-                    //           layout here needs to conform to the same
-                    //           BrushPrimitive layout. We should tidy this
-                    //           up in the future so it's enforced that these
-                    //           types use a shared function to write out the
-                    //           GPU blocks...
-                    request.write_segment(metadata.local_rect);
+                    let brush = &pic.brush;
+                    brush.write_gpu_blocks(&mut request);
+                    match brush.segment_desc {
+                        Some(ref segment_desc) => {
+                            for segment in &segment_desc.segments {
+                                // has to match VECS_PER_SEGMENT
+                                request.write_segment(segment.local_rect);
+                            }
+                        }
+                        None => {
+                            request.write_segment(metadata.local_rect);
+                        }
+                    }
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                     brush.write_gpu_blocks(&mut request);
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
                                 // has to match VECS_PER_SEGMENT
@@ -1290,68 +1305,64 @@ impl PrimitiveStore {
                         }
                     }
                 }
             }
         }
     }
 
     fn write_brush_segment_description(
-        &mut self,
-        prim_index: PrimitiveIndex,
+        brush: &mut BrushPrimitive,
+        metadata: &PrimitiveMetadata,
         prim_context: &PrimitiveContext,
         clip_store: &mut ClipStore,
         node_data: &[ClipScrollNodeData],
         clips: &Vec<ClipWorkItem>,
+        has_clips_from_other_coordinate_systems: bool,
     ) {
-        debug_assert!(self.cpu_metadata[prim_index.0].prim_kind == PrimitiveKind::Brush);
-
-        let metadata = &self.cpu_metadata[prim_index.0];
-        let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
-
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 // If we already have a segment descriptor, only run through the
                 // clips list if we haven't already determined the mask kind.
                 if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
                     return;
                 }
             }
             None => {
                 // If no segment descriptor built yet, see if it is a brush
                 // type that wants to be segmented.
-                if !brush.kind.is_solid() {
+                if !brush.kind.supports_segments() {
                     return;
                 }
                 if metadata.local_rect.size.area() <= MIN_BRUSH_SPLIT_AREA {
                     return;
                 }
             }
         }
 
         let mut segment_builder = SegmentBuilder::new(
             metadata.local_rect,
+            None,
             metadata.local_clip_rect
         );
 
-        // If true, we need a clip mask for the entire primitive. This
-        // is either because we don't handle segmenting this clip source,
-        // or we have a clip source from a different coordinate system.
-        let mut clip_mask_kind = BrushClipMaskKind::Individual;
+        // If this primitive is clipped by clips from a different coordinate system, then we
+        // need to apply a clip mask for the entire primitive.
+        let mut clip_mask_kind = match has_clips_from_other_coordinate_systems {
+            true => BrushClipMaskKind::Global,
+            false => BrushClipMaskKind::Individual,
+        };
 
-        // Segment the primitive on all the local-space clip sources
-        // that we can.
+        // Segment the primitive on all the local-space clip sources that we can.
         for clip_item in clips {
             if clip_item.coordinate_system_id != prim_context.scroll_node.coordinate_system_id {
-                clip_mask_kind = BrushClipMaskKind::Global;
                 continue;
             }
 
             let local_clips = clip_store.get_opt(&clip_item.clip_sources).expect("bug");
-
             for &(ref clip, _) in &local_clips.clips {
                 let (local_clip_rect, radius, mode) = match *clip {
                     ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
                         (rect, Some(radii), clip_mode)
                     }
                     ClipSource::Rectangle(rect) => {
                         (rect, None, ClipMode::Clip)
                     }
@@ -1378,21 +1389,17 @@ impl PrimitiveStore {
                     let relative_transform = prim_transform
                         .inverse()
                         .unwrap_or(WorldToLayerTransform::identity())
                         .pre_mul(&clip_transform_data.transform);
 
                     relative_transform.transform_rect(&local_clip_rect)
                 };
 
-                segment_builder.push_rect(
-                    local_clip_rect,
-                    radius,
-                    mode
-                );
+                segment_builder.push_rect(local_clip_rect, radius, mode);
             }
         }
 
         match brush.segment_desc {
             Some(ref mut segment_desc) => {
                 segment_desc.clip_mask_kind = clip_mask_kind;
             }
             None => {
@@ -1426,60 +1433,72 @@ impl PrimitiveStore {
         prim_context: &PrimitiveContext,
         prim_index: PrimitiveIndex,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
         tasks: &mut Vec<RenderTaskId>,
         node_data: &[ClipScrollNodeData],
         clips: &Vec<ClipWorkItem>,
         combined_outer_rect: &DeviceIntRect,
+        has_clips_from_other_coordinate_systems: bool,
     ) -> bool {
-        if self.cpu_metadata[prim_index.0].prim_kind != PrimitiveKind::Brush {
-            return false;
-        }
+        let metadata = &self.cpu_metadata[prim_index.0];
+        let brush = match metadata.prim_kind {
+            PrimitiveKind::Brush => {
+                &mut self.cpu_brushes[metadata.cpu_prim_index.0]
+            }
+            PrimitiveKind::Picture => {
+                &mut self.cpu_pictures[metadata.cpu_prim_index.0].brush
+            }
+            _ => {
+                return false;
+            }
+        };
 
-        self.write_brush_segment_description(
-            prim_index,
+        PrimitiveStore::write_brush_segment_description(
+            brush,
+            metadata,
             prim_context,
             clip_store,
             node_data,
-            clips
+            clips,
+            has_clips_from_other_coordinate_systems,
         );
 
-        let metadata = &self.cpu_metadata[prim_index.0];
-        let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
         let segment_desc = match brush.segment_desc {
             Some(ref mut description) => description,
             None => return false,
         };
         let clip_mask_kind = segment_desc.clip_mask_kind;
 
         for segment in &mut segment_desc.segments {
-            segment.clip_task_id = if segment.may_need_clip_mask || clip_mask_kind == BrushClipMaskKind::Global {
-                let segment_screen_rect = calculate_screen_bounding_rect(
-                    &prim_context.scroll_node.world_content_transform,
-                    &segment.local_rect,
-                    prim_context.device_pixel_scale,
+            if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
+                segment.clip_task_id = None;
+                continue;
+            }
+
+            let segment_screen_rect = calculate_screen_bounding_rect(
+                &prim_context.scroll_node.world_content_transform,
+                &segment.local_rect,
+                prim_context.device_pixel_scale,
+            );
+
+            let intersected_rect = combined_outer_rect.intersection(&segment_screen_rect);
+            segment.clip_task_id = intersected_rect.map(|bounds| {
+                let clip_task = RenderTask::new_mask(
+                    bounds,
+                    clips.clone(),
+                    prim_context.scroll_node.coordinate_system_id,
                 );
 
-                combined_outer_rect.intersection(&segment_screen_rect).map(|bounds| {
-                    let clip_task = RenderTask::new_mask(
-                        bounds,
-                        clips.clone(),
-                        prim_context.scroll_node.coordinate_system_id,
-                    );
+                let clip_task_id = render_tasks.add(clip_task);
+                tasks.push(clip_task_id);
 
-                    let clip_task_id = render_tasks.add(clip_task);
-                    tasks.push(clip_task_id);
-
-                    clip_task_id
-                })
-            } else {
-                None
-            };
+                clip_task_id
+            })
         }
 
         true
     }
 
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
@@ -1548,30 +1567,34 @@ impl PrimitiveStore {
         let combined_outer_rect = match combined_outer_rect {
             Some(rect) if !rect.is_empty() => rect,
             _ => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
             }
         };
 
+        let mut has_clips_from_other_coordinate_systems = false;
         let mut combined_inner_rect = *screen_rect;
         let clips = convert_clip_chain_to_clip_vector(
             clip_chain,
             extra_clip,
             &combined_outer_rect,
-            &mut combined_inner_rect
+            &mut combined_inner_rect,
+            prim_context.scroll_node.coordinate_system_id,
+            &mut has_clips_from_other_coordinate_systems
         );
 
+        // This can happen if we had no clips or if all the clips were optimized away. In
+        // some cases we still need to create a clip mask in order to create a rectangular
+        // clip in screen space coordinates.
         if clips.is_empty() {
-            // If this item is in the root coordinate system, then
-            // we know that the local_clip_rect in the clip node
-            // will take care of applying this clip, so no need
-            // for a mask.
-            if prim_coordinate_system_id == CoordinateSystemId::root() {
+            // If we don't have any clips from other coordinate systems, the local clip
+            // calculated from the clip chain should be sufficient to ensure proper clipping.
+            if !has_clips_from_other_coordinate_systems {
                 return true;
             }
 
             // If we have filtered all clips and the screen rect isn't any smaller, we can just
             // skip masking entirely.
             if combined_outer_rect == prim_screen_rect {
                 return true;
             }
@@ -1589,16 +1612,17 @@ impl PrimitiveStore {
             prim_context,
             prim_index,
             render_tasks,
             clip_store,
             tasks,
             node_data,
             &clips,
             &combined_outer_rect,
+            has_clips_from_other_coordinate_systems,
         ) {
             return true;
         }
 
         let clip_task = RenderTask::new_mask(
             combined_outer_rect,
             clips,
             prim_coordinate_system_id,
@@ -1949,21 +1973,26 @@ impl InsideTest<ComplexClipRegion> for C
     }
 }
 
 fn convert_clip_chain_to_clip_vector(
     clip_chain_nodes: ClipChainNodeRef,
     extra_clip: ClipChainNodeRef,
     combined_outer_rect: &DeviceIntRect,
     combined_inner_rect: &mut DeviceIntRect,
+    prim_coordinate_system: CoordinateSystemId,
+    has_clips_from_other_coordinate_systems: &mut bool,
 ) -> Vec<ClipWorkItem> {
     // Filter out all the clip instances that don't contribute to the result.
     ClipChainNodeIter { current: extra_clip }
         .chain(ClipChainNodeIter { current: clip_chain_nodes })
         .filter_map(|node| {
+            *has_clips_from_other_coordinate_systems |=
+                prim_coordinate_system != node.work_item.coordinate_system_id;
+
             *combined_inner_rect = if !node.screen_inner_rect.is_empty() {
                 // If this clip's inner area contains the area of the primitive clipped
                 // by previous clips, then it's not going to affect rendering in any way.
                 if node.screen_inner_rect.contains_rect(&combined_outer_rect) {
                     return None;
                 }
                 combined_inner_rect.intersection(&node.screen_inner_rect)
                     .unwrap_or_else(DeviceIntRect::zero)
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -5,33 +5,35 @@
 use api::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DebugCommand, DeviceIntPoint};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentId, DocumentLayer, DocumentMsg};
 use api::{IdNamespace, PipelineId, RenderNotifier};
 use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
 use api::channel::{PayloadSender, PayloadSenderHelperMethods};
+#[cfg(feature = "capture")]
+use api::CapturedDocument;
+#[cfg(feature = "capture")]
+use capture::{CaptureConfig, ExternalCaptureImage};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame::FrameContext;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
-#[cfg(feature = "capture")]
-use internal_types::ExternalCaptureImage;
 use profiler::{BackendProfileCounters, ResourceProfileCounters};
 use rayon::ThreadPool;
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 #[cfg(feature = "capture")]
-use resource_cache::PlainResources;
+use resource_cache::{PlainCacheOwn, PlainResources};
 use scene::Scene;
 #[cfg(feature = "serialize")]
-use serde::{Serialize, Serializer};
+use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
 #[cfg(feature = "capture")]
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
 use std::sync::Arc;
 use std::sync::mpsc::Sender;
 use std::u32;
@@ -291,24 +293,17 @@ impl RenderBackend {
             DocumentMsg::SetDisplayList {
                 epoch,
                 pipeline_id,
                 background,
                 viewport_size,
                 content_size,
                 list_descriptor,
                 preserve_frame_state,
-                resources,
             } => {
-                // TODO: this will be removed from the SetDisplayList message soon.
-                self.resource_cache.update_resources(
-                    resources,
-                    &mut profile_counters.resources
-                );
-
                 profile_scope!("SetDisplayList");
 
                 let mut data;
                 while {
                     data = self.payload_rx.recv_payload().unwrap();
                     data.epoch != epoch || data.pipeline_id != pipeline_id
                 } {
                     self.payload_tx.send_payload(data).unwrap()
@@ -372,27 +367,16 @@ impl RenderBackend {
 
                 DocumentOps::nop()
             }
             DocumentMsg::UpdateEpoch(pipeline_id, epoch) => {
                 doc.scene.update_epoch(pipeline_id, epoch);
                 doc.frame_ctx.update_epoch(pipeline_id, epoch);
                 DocumentOps::nop()
             }
-            DocumentMsg::UpdatePipelineResources { resources, pipeline_id, epoch } => {
-                profile_scope!("UpdateResources");
-
-                self.resource_cache
-                    .update_resources(resources, &mut profile_counters.resources);
-
-                doc.scene.update_epoch(pipeline_id, epoch);
-                doc.frame_ctx.update_epoch(pipeline_id, epoch);
-
-                DocumentOps::nop()
-            }
             DocumentMsg::SetRootPipeline(pipeline_id) => {
                 profile_scope!("SetRootPipeline");
 
                 doc.scene.set_root_pipeline_id(pipeline_id);
                 if doc.scene.pipelines.get(&pipeline_id).is_some() {
                     DocumentOps::build()
                 } else {
                     DocumentOps::nop()
@@ -582,26 +566,41 @@ impl RenderBackend {
                             let json = self.get_docs_for_debugger();
                             ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
                         }
                         DebugCommand::FetchClipScrollTree => {
                             let json = self.get_clip_scroll_tree_for_debugger();
                             ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
                         }
                         #[cfg(feature = "capture")]
-                        DebugCommand::SaveCapture(root) => {
-                            let deferred = self.save_capture(&root);
-                            ResultMsg::DebugOutput(DebugOutput::SaveCapture(root, deferred))
+                        DebugCommand::SaveCapture(root, bits) => {
+                            let config = CaptureConfig::new(root, bits);
+                            let deferred = self.save_capture(&config, &mut profile_counters);
+                            ResultMsg::DebugOutput(DebugOutput::SaveCapture(config, deferred))
                         },
                         #[cfg(feature = "capture")]
-                        DebugCommand::LoadCapture(root) => {
+                        DebugCommand::LoadCapture(root, tx) => {
                             NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
                             frame_counter += 1;
+                            let msg = ResultMsg::DebugOutput(
+                                DebugOutput::LoadCapture(root.clone())
+                            );
+                            self.result_tx.send(msg).unwrap();
                             self.load_capture(&root, &mut profile_counters);
-                            ResultMsg::DebugOutput(DebugOutput::LoadCapture)
+
+                            for (id, doc) in &self.documents {
+                                let captured = CapturedDocument {
+                                    document_id: *id,
+                                    root_pipeline_id: doc.scene.root_pipeline_id,
+                                };
+                                tx.send(captured).unwrap();
+                            }
+                            // Note: we can't pass `LoadCapture` here since it needs to arrive
+                            // before the `PublishDocument` messages sent by `load_capture`.
+                            continue
                         },
                         DebugCommand::EnableDualSourceBlending(enable) => {
                             // Set in the config used for any future documents
                             // that are created.
                             self.frame_config
                                 .dual_source_blending_is_enabled = enable;
 
                             // Set for any existing documents.
@@ -666,16 +665,19 @@ impl RenderBackend {
 
             *frame_counter += 1;
             let rendered_document = doc.render(
                 &mut self.resource_cache,
                 &mut self.gpu_cache,
                 &mut profile_counters.resources,
             );
 
+            info!("generated frame for document {:?} with {} passes",
+                document_id, rendered_document.frame.passes.len());
+
             // Publish the frame
             let pending_update = self.resource_cache.pending_updates();
             let msg = ResultMsg::PublishDocument(
                 document_id,
                 rendered_document,
                 pending_update,
                 profile_counters.clone()
             );
@@ -807,123 +809,139 @@ impl ToDebugString for SpecificDisplayIt
             SpecificDisplayItem::SetGradientStops => String::from("set_gradient_stops"),
             SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
             SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
             SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
         }
     }
 }
 
-
 #[cfg(feature = "capture")]
 impl RenderBackend {
     // Note: the mutable `self` is only needed here for resolving blob images
-    fn save_capture(&mut self, root: &PathBuf) -> Vec<ExternalCaptureImage> {
-        use ron::ser::{to_string_pretty, PrettyConfig};
-        use std::fs;
-        use std::io::Write;
+    fn save_capture(
+        &mut self,
+        config: &CaptureConfig,
+        profile_counters: &mut BackendProfileCounters,
+    ) -> Vec<ExternalCaptureImage> {
+        use api::CaptureBits;
+
+        info!("capture: saving {:?}", config.root);
+        let (resources, deferred) = self.resource_cache.save_capture(&config.root);
 
-        info!("capture: saving {}", root.to_string_lossy());
-        let ron_config = PrettyConfig::default();
-        let (resources, deferred) = self.resource_cache.save_capture(root);
-
-        for (&id, doc) in &self.documents {
+        for (&id, doc) in &mut self.documents {
             info!("\tdocument {:?}", id);
-            let ron = to_string_pretty(&doc.scene, ron_config.clone()).unwrap();
-            let file_name = format!("scene-{}-{}.ron", (id.0).0, id.1);
-            let ron_path = root.clone().join(file_name);
-            let mut file = fs::File::create(ron_path).unwrap();
-            write!(file, "{}\n", ron).unwrap();
+            if config.bits.contains(CaptureBits::SCENE) {
+                let file_name = format!("scene-{}-{}", (id.0).0, id.1);
+                config.serialize(&doc.scene, file_name);
+            }
+            if config.bits.contains(CaptureBits::FRAME) {
+                let rendered_document = doc.render(
+                    &mut self.resource_cache,
+                    &mut self.gpu_cache,
+                    &mut profile_counters.resources,
+                );
+                //TODO: write down full `RenderedDocument`?
+                // it has `pipeline_epoch_map` and `layers_bouncing_back`,
+                // which may capture necessary details for some cases.
+                let file_name = format!("frame-{}-{}", (id.0).0, id.1);
+                config.serialize(&rendered_document.frame, file_name);
+            }
         }
 
         info!("\tbackend");
-        let serial = PlainRenderBackend {
+        let backend = PlainRenderBackend {
             default_device_pixel_ratio: self.default_device_pixel_ratio,
             enable_render_on_scroll: self.enable_render_on_scroll,
             frame_config: self.frame_config.clone(),
             documents: self.documents
                 .iter()
                 .map(|(id, doc)| (*id, doc.view.clone()))
                 .collect(),
             resources,
         };
 
-        let ron = to_string_pretty(&serial, ron_config).unwrap();
-        let ron_path = root.clone().join("backend.ron");
-        let mut file = fs::File::create(ron_path).unwrap();
-        write!(file, "{}\n", ron).unwrap();
+        config.serialize(&backend, "backend");
+
+        if config.bits.contains(CaptureBits::FRAME) {
+            info!("\tresource cache");
+            let caches = self.resource_cache.save_caches(&config.root);
+            config.serialize(&caches, "resource_cache");
+            info!("\tgpu cache");
+            config.serialize(&self.gpu_cache, "gpu_cache");
+        }
 
         deferred
     }
 
     fn load_capture(
         &mut self,
         root: &PathBuf,
         profile_counters: &mut BackendProfileCounters,
     ) {
-        use ron::de;
-        use std::fs::File;
-        use std::io::Read;
-
-        let mut string = String::new();
-        info!("capture: loading {}", root.to_string_lossy());
+        use tiling::Frame;
 
-        File::open(root.join("backend.ron"))
-            .unwrap()
-            .read_to_string(&mut string)
-            .unwrap();
-        let backend: PlainRenderBackend = de::from_str(&string)
-            .unwrap();
+        info!("capture: loading {:?}", root);
+        let backend = CaptureConfig::deserialize::<PlainRenderBackend, _>(root, "backend")
+            .expect("Unable to open backend.ron");
+        let caches_maybe = CaptureConfig::deserialize::<PlainCacheOwn, _>(root, "resource_cache");
 
         // Note: it would be great to have RenderBackend to be split
         // rather explicitly on what's used before and after scene building
         // so that, for example, we never miss anything in the code below:
 
-        self.resource_cache.load_capture(backend.resources, root);
-        self.gpu_cache = GpuCache::new();
+        self.resource_cache.load_capture(backend.resources, caches_maybe,root);
+
+        self.gpu_cache = match CaptureConfig::deserialize::<GpuCache, _>(root, "gpu_cache") {
+            Some(gpu_cache) => gpu_cache,
+            None => GpuCache::new(),
+        };
+
         self.documents.clear();
         self.default_device_pixel_ratio = backend.default_device_pixel_ratio;
         self.frame_config = backend.frame_config;
         self.enable_render_on_scroll = backend.enable_render_on_scroll;
 
         for (id, view) in backend.documents {
             info!("\tdocument {:?}", id);
-            string.clear();
-            let file_name = format!("scene-{}-{}.ron", (id.0).0, id.1);
-            File::open(root.join(file_name))
-                .expect(&format!("Unable to open scene {:?}", id))
-                .read_to_string(&mut string)
-                .unwrap();
-            let scene: Scene = de::from_str(&string)
-                .unwrap();
+            let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
+            let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
+                .expect(&format!("Unable to open {}.ron", scene_name));
 
             let mut doc = Document {
                 scene,
                 view,
                 frame_ctx: FrameContext::new(self.frame_config.clone()),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 render_on_scroll: None,
             };
 
-            doc.build_scene(&mut self.resource_cache);
-            let render_doc = doc.render(
-                &mut self.resource_cache,
-                &mut self.gpu_cache,
-                &mut profile_counters.resources,
-            );
+            let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
+            let render_doc = match CaptureConfig::deserialize::<Frame, _>(root, frame_name) {
+                Some(frame) => {
+                    info!("\tloaded a built frame with {} passes", frame.passes.len());
+                    doc.frame_ctx.make_rendered_document(frame)
+                }
+                None => {
+                    doc.build_scene(&mut self.resource_cache);
+                    doc.render(
+                        &mut self.resource_cache,
+                        &mut self.gpu_cache,
+                        &mut profile_counters.resources,
+                    )
+                }
+            };
 
-            let pending_update = self.resource_cache.pending_updates();
             let msg = ResultMsg::PublishDocument(
                 id,
                 render_doc,
-                pending_update,
-                profile_counters.clone()
+                self.resource_cache.pending_updates(),
+                profile_counters.clone(),
             );
             self.result_tx.send(msg).unwrap();
             profile_counters.reset();
 
             self.notifier.new_document_ready(id, false, true);
-
             self.documents.insert(id, doc);
         }
     }
 }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -22,23 +22,26 @@ use texture_cache::{TextureCache, Textur
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTaskId(pub u32); // TODO(gw): Make private when using GPU cache!
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTaskTree {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
 }
 
 pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
 
 #[derive(Debug, Clone)]
@@ -204,46 +207,51 @@ impl ops::Index<RenderTaskId> for Render
 
 impl ops::IndexMut<RenderTaskId> for RenderTaskTree {
     fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask {
         &mut self.tasks[id.0 as usize]
     }
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
     TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
 #[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct ClipWorkItem {
     pub scroll_node_data_index: ClipScrollNodeIndex,
     pub clip_sources: ClipSourcesWeakHandle,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
     pub target_kind: RenderTargetKind,
     pub content_origin: ContentOrigin,
     pub color: PremultipliedColorF,
     pub pic_type: PictureType,
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
     pub target_kind: RenderTargetKind,
     pub color: PremultipliedColorF,
     pub scale_factor: f32,
 }
 
 impl BlurTask {
@@ -251,41 +259,45 @@ impl BlurTask {
     fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
         pt.add_item(format!("scale: {}", self.scale_factor));
     }
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
     CacheMask(CacheMaskTask),
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     Readback(DeviceIntRect),
     Scaling(RenderTargetKind),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
     One,
 
     // Applicable to color targets only.
     Transparent,
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTask {
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
     pub pass_index: Option<RenderPassIndex>,
 }
 
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -15,37 +15,39 @@ use api::{ExternalImageType, FontRenderM
 use api::{RenderNotifier, YUV_COLOR_SPACES, YUV_FORMATS, YuvColorSpace, YuvFormat, channel};
 #[cfg(not(feature = "debugger"))]
 use api::ApiMsg;
 use api::DebugCommand;
 #[cfg(not(feature = "debugger"))]
 use api::channel::MsgSender;
 use batch::{BatchKey, BatchKind, BatchTextures, BrushBatchKind};
 use batch::{BrushImageSourceKind, TransformBatchKind};
+#[cfg(feature = "capture")]
+use capture::{CaptureConfig, ExternalCaptureImage};
 use debug_colors;
 use debug_render::DebugRenderer;
 #[cfg(feature = "debugger")]
 use debug_server::{self, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture,
              VertexDescriptor, PBO};
-use device::{get_gl_format_bgra, ExternalTexture, FBOId, TextureSlot, VertexAttribute,
-             VertexAttributeKind};
+use device::{ExternalTexture, FBOId, TextureSlot, VertexAttribute, VertexAttributeKind};
 use device::{FileWatcherHandler, ShaderError, TextureFilter, TextureTarget,
              VertexUsageHint, VAO, VBO, CustomVAO};
-use device::ProgramCache;
+use device::{ProgramCache, ReadPixelsFormat};
 use euclid::{rect, Transform3D};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use gpu_types::PrimitiveInstance;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE};
 use internal_types::{CacheTextureId, FastHashMap, RenderedDocument, ResultMsg, TextureUpdateOp};
 use internal_types::{DebugOutput, RenderPassIndex, RenderTargetInfo, TextureUpdateList, TextureUpdateSource};
 use picture::ContentOrigin;
+use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, Profiler};
 use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use query::{GpuProfiler, GpuTimer};
 use rayon::Configuration as ThreadPoolConfig;
 use rayon::ThreadPool;
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::{RenderTaskKind, RenderTaskTree};
@@ -82,18 +84,18 @@ pub const BLOCKS_PER_UV_RECT: usize = 2;
 const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag {
     label: "B_Solid",
     color: debug_colors::RED,
 };
 const GPU_TAG_BRUSH_MASK: GpuProfileTag = GpuProfileTag {
     label: "B_Mask",
     color: debug_colors::BLACK,
 };
-const GPU_TAG_BRUSH_IMAGE: GpuProfileTag = GpuProfileTag {
-    label: "B_Image",
+const GPU_TAG_BRUSH_PICTURE: GpuProfileTag = GpuProfileTag {
+    label: "B_Picture",
     color: debug_colors::SILVER,
 };
 const GPU_TAG_BRUSH_LINE: GpuProfileTag = GpuProfileTag {
     label: "Line",
     color: debug_colors::DARKRED,
 };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
     label: "C_Clip",
@@ -216,34 +218,34 @@ impl BatchKind {
     fn debug_name(&self) -> &'static str {
         match *self {
             BatchKind::Composite { .. } => "Composite",
             BatchKind::HardwareComposite => "HardwareComposite",
             BatchKind::SplitComposite => "SplitComposite",
             BatchKind::Blend => "Blend",
             BatchKind::Brush(kind) => {
                 match kind {
-                    BrushBatchKind::Image(..) => "Brush (Image)",
+                    BrushBatchKind::Picture(..) => "Brush (Picture)",
                     BrushBatchKind::Solid => "Brush (Solid)",
                     BrushBatchKind::Line => "Brush (Line)",
                 }
             }
             BatchKind::Transformable(_, batch_kind) => batch_kind.debug_name(),
         }
     }
 
     fn gpu_sampler_tag(&self) -> GpuProfileTag {
         match *self {
             BatchKind::Composite { .. } => GPU_TAG_PRIM_COMPOSITE,
             BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE,
             BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
             BatchKind::Blend => GPU_TAG_PRIM_BLEND,
             BatchKind::Brush(kind) => {
                 match kind {
-                    BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
+                    BrushBatchKind::Picture(..) => GPU_TAG_BRUSH_PICTURE,
                     BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
                     BrushBatchKind::Line => GPU_TAG_BRUSH_LINE,
                 }
             }
             BatchKind::Transformable(_, batch_kind) => batch_kind.gpu_sampler_tag(),
         }
     }
 }
@@ -475,16 +477,17 @@ pub enum GraphicsApi {
 #[derive(Clone, Debug)]
 pub struct GraphicsApiInfo {
     pub kind: GraphicsApi,
     pub renderer: String,
     pub version: String,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum ImageBufferKind {
     Texture2D = 0,
     TextureRect = 1,
     TextureExternal = 2,
     Texture2DArray = 3,
 }
 
 pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [
@@ -599,22 +602,22 @@ struct SourceTextureResolver {
     pass_rgba8_textures: FastHashMap<RenderPassIndex, RenderTargetPoolId>,
     pass_a8_textures: FastHashMap<RenderPassIndex, RenderTargetPoolId>,
 
     render_target_pool: Vec<Texture>,
 }
 
 impl SourceTextureResolver {
     fn new(device: &mut Device) -> SourceTextureResolver {
-        let mut dummy_cache_texture = device.create_texture(TextureTarget::Array);
+        let mut dummy_cache_texture = device
+            .create_texture(TextureTarget::Array, ImageFormat::BGRA8);
         device.init_texture(
             &mut dummy_cache_texture,
             1,
             1,
-            ImageFormat::BGRA8,
             TextureFilter::Linear,
             None,
             1,
             None,
         );
 
         SourceTextureResolver {
             cache_texture_map: Vec::new(),
@@ -693,17 +696,17 @@ impl SourceTextureResolver {
                 let texture = self.cache_rgba8_texture
                     .as_ref()
                     .unwrap_or(&self.dummy_cache_texture);
                 device.bind_texture(sampler, texture);
             }
             SourceTexture::External(external_image) => {
                 let texture = self.external_images
                     .get(&(external_image.id, external_image.channel_index))
-                    .expect("BUG: External image should be resolved by now!");
+                    .expect(&format!("BUG: External image should be resolved by now: {:?}", external_image));
                 device.bind_external_texture(sampler, texture);
             }
             SourceTexture::TextureCache(index) => {
                 let texture = &self.cache_texture_map[index.0];
                 device.bind_texture(sampler, texture);
             }
             SourceTexture::RenderTaskCacheRGBA8(pass_index) => {
                 let pool_index = self.pass_rgba8_textures
@@ -752,16 +755,17 @@ impl SourceTextureResolver {
                     .expect("BUG: pass_index doesn't map to pool_index");
                 Some(&self.render_target_pool[pool_index.0])
             },
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 #[allow(dead_code)] // SubpixelVariableTextColor is not used at the moment.
 pub enum BlendMode {
     None,
     Alpha,
     PremultipliedAlpha,
     PremultipliedDestOut,
     SubpixelDualSource,
     SubpixelConstantTextColor(ColorF),
@@ -770,17 +774,17 @@ pub enum BlendMode {
 }
 
 // Tracks the state of each row in the GPU cache texture.
 struct CacheRow {
     is_dirty: bool,
 }
 
 impl CacheRow {
-    fn new() -> CacheRow {
+    fn new() -> Self {
         CacheRow { is_dirty: false }
     }
 }
 
 /// The bus over which CPU and GPU versions of the cache
 /// get synchronized.
 enum CacheBus {
     /// PBO-based updates, currently operate on a row granularity.
@@ -812,17 +816,17 @@ enum CacheBus {
 /// The device-specific representation of the cache texture in gpu_cache.rs
 struct CacheTexture {
     texture: Texture,
     bus: CacheBus,
 }
 
 impl CacheTexture {
     fn new(device: &mut Device, use_scatter: bool) -> Result<Self, RendererError> {
-        let texture = device.create_texture(TextureTarget::Default);
+        let texture = device.create_texture(TextureTarget::Default, ImageFormat::RGBAF32);
 
         let bus = if use_scatter {
             let program = device
                 .create_program("gpu_cache_update", "", &DESC_GPU_CACHE_UPDATE)?;
             let buf_position = device.create_vbo();
             let buf_value = device.create_vbo();
             //Note: the vertex attributes have to be supplied in the same order
             // as for program creation, but each assigned to a different stream.
@@ -885,17 +889,16 @@ impl CacheTexture {
             CacheBus::PixelBuffer { ref mut rows, .. } => {
                 if max_height > old_size.height {
                     // Create a f32 texture that can be used for the vertex shader
                     // to fetch data from.
                     device.init_texture(
                         &mut self.texture,
                         new_size.width,
                         new_size.height,
-                        ImageFormat::RGBAF32,
                         TextureFilter::Nearest,
                         None,
                         1,
                         None,
                     );
 
                     // If we had to resize the texture, just mark all rows
                     // as dirty so they will be uploaded to the texture
@@ -920,17 +923,16 @@ impl CacheTexture {
                 if new_size.height > old_size.height || GPU_CACHE_RESIZE_TEST {
                     if old_size.height > 0 {
                         device.resize_renderable_texture(&mut self.texture, new_size);
                     } else {
                         device.init_texture(
                             &mut self.texture,
                             new_size.width,
                             new_size.height,
-                            ImageFormat::RGBAF32,
                             TextureFilter::Nearest,
                             Some(RenderTargetInfo {
                                 has_depth: false,
                             }),
                             1,
                             None,
                         );
                     }
@@ -1065,17 +1067,17 @@ impl CacheTexture {
 
 struct VertexDataTexture {
     texture: Texture,
     pbo: PBO,
 }
 
 impl VertexDataTexture {
     fn new(device: &mut Device) -> VertexDataTexture {
-        let texture = device.create_texture(TextureTarget::Default);
+        let texture = device.create_texture(TextureTarget::Default, ImageFormat::RGBAF32);
         let pbo = device.create_pbo();
 
         VertexDataTexture { texture, pbo }
     }
 
     fn update<T>(&mut self, device: &mut Device, data: &mut Vec<T>) {
         if data.is_empty() {
             return;
@@ -1103,17 +1105,16 @@ impl VertexDataTexture {
 
         if needed_height > texture_size.height {
             let new_height = (needed_height + 127) & !127;
 
             device.init_texture(
                 &mut self.texture,
                 width,
                 new_height,
-                ImageFormat::RGBAF32,
                 TextureFilter::Nearest,
                 None,
                 1,
                 None,
             );
         }
 
         let rect = DeviceUintRect::new(
@@ -1251,17 +1252,17 @@ struct BrushShader {
 }
 
 impl BrushShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
         precache: bool,
-    ) -> Result<BrushShader, ShaderError> {
+    ) -> Result<Self, ShaderError> {
         let opaque = try!{
             LazilyCompiledShader::new(ShaderKind::Brush,
                                       name,
                                       features,
                                       device,
                                       precache)
         };
 
@@ -1315,17 +1316,17 @@ struct PrimitiveShader {
 }
 
 impl PrimitiveShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
         precache: bool,
-    ) -> Result<PrimitiveShader, ShaderError> {
+    ) -> Result<Self, ShaderError> {
         let simple = try!{
             LazilyCompiledShader::new(ShaderKind::Primitive,
                                       name,
                                       features,
                                       device,
                                       precache)
         };
 
@@ -1374,17 +1375,17 @@ struct TextShader {
 }
 
 impl TextShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
         precache: bool,
-    ) -> Result<TextShader, ShaderError> {
+    ) -> Result<Self, ShaderError> {
         let simple = try!{
             LazilyCompiledShader::new(ShaderKind::Text,
                                       name,
                                       features,
                                       device,
                                       precache)
         };
 
@@ -1520,46 +1521,61 @@ fn create_clip_shader(name: &'static str
                 ("sLocalClipRects", TextureSampler::LocalClipRects),
             ],
         );
     }
 
     program
 }
 
+fn get_external_image_target(ext_type: ExternalImageType) -> Option<TextureTarget> {
+    Some(match ext_type {
+        ExternalImageType::Texture2DHandle => TextureTarget::Default,
+        ExternalImageType::Texture2DArrayHandle => TextureTarget::Array,
+        ExternalImageType::TextureRectHandle => TextureTarget::Rect,
+        ExternalImageType::TextureExternalHandle => TextureTarget::External,
+        ExternalImageType::ExternalBuffer => return None,
+    })
+}
+
 struct FileWatcher {
     notifier: Box<RenderNotifier>,
     result_tx: Sender<ResultMsg>,
 }
 
 impl FileWatcherHandler for FileWatcher {
     fn file_changed(&self, path: PathBuf) {
         self.result_tx.send(ResultMsg::RefreshShader(path)).ok();
         self.notifier.wake_up();
     }
 }
 
-#[derive(Clone, Debug, PartialEq)]
-pub enum ReadPixelsFormat {
-    Rgba8,
-    Bgra8,
-}
-
 struct FrameOutput {
     last_access: FrameId,
     fbo_id: FBOId,
 }
 
 #[derive(PartialEq)]
 struct TargetSelector {
     size: DeviceUintSize,
     num_layers: usize,
     format: ImageFormat,
 }
 
+#[cfg(feature = "capture")]
+struct RendererCapture {
+    read_fbo: FBOId,
+    owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>,
+}
+
+// Note: we can't just feature-gate the fields because `cbindgen` fails on those.
+// see https://github.com/eqrion/cbindgen/issues/116
+#[cfg(not(feature = "capture"))]
+struct RendererCapture;
+
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
@@ -1571,19 +1587,19 @@ pub struct Renderer {
     // of these shaders are then used by the primitive shaders.
     cs_text_run: LazilyCompiledShader,
     cs_blur_a8: LazilyCompiledShader,
     cs_blur_rgba8: LazilyCompiledShader,
 
     // Brush shaders
     brush_mask_corner: LazilyCompiledShader,
     brush_mask_rounded_rect: LazilyCompiledShader,
-    brush_image_rgba8: BrushShader,
-    brush_image_rgba8_alpha_mask: BrushShader,
-    brush_image_a8: BrushShader,
+    brush_picture_rgba8: BrushShader,
+    brush_picture_rgba8_alpha_mask: BrushShader,
+    brush_picture_a8: BrushShader,
     brush_solid: BrushShader,
     brush_line: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     cs_clip_rectangle: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
@@ -1656,16 +1672,19 @@ pub struct Renderer {
     output_targets: FastHashMap<u32, FrameOutput>,
 
     renderer_errors: Vec<RendererError>,
 
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
+
+    #[cfg_attr(not(feature = "capture"), allow(dead_code))]
+    capture: RendererCapture,
 }
 
 #[derive(Debug)]
 pub enum RendererError {
     Shader(ShaderError),
     Thread(std::io::Error),
     MaxTextureSize,
 }
@@ -1785,32 +1804,32 @@ impl Renderer {
 
         let brush_line = try!{
             BrushShader::new("brush_line",
                              &mut device,
                              &[],
                              options.precache_shaders)
         };
 
-        let brush_image_a8 = try!{
-            BrushShader::new("brush_image",
+        let brush_picture_a8 = try!{
+            BrushShader::new("brush_picture",
                              &mut device,
                              &["ALPHA_TARGET"],
                              options.precache_shaders)
         };
 
-        let brush_image_rgba8 = try!{
-            BrushShader::new("brush_image",
+        let brush_picture_rgba8 = try!{
+            BrushShader::new("brush_picture",
                              &mut device,
                              &["COLOR_TARGET"],
                              options.precache_shaders)
         };
 
-        let brush_image_rgba8_alpha_mask = try!{
-            BrushShader::new("brush_image",
+        let brush_picture_rgba8_alpha_mask = try!{
+            BrushShader::new("brush_picture",
                              &mut device,
                              &["COLOR_TARGET_ALPHA_MASK"],
                              options.precache_shaders)
         };
 
         let cs_blur_a8 = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexArrayKind::Blur),
                                      "cs_blur",
@@ -2082,22 +2101,22 @@ impl Renderer {
                 38,
                 22,
                 41,
                 25,
                 37,
                 21,
             ];
 
-            let mut texture = device.create_texture(TextureTarget::Default);
+            let mut texture = device
+                .create_texture(TextureTarget::Default, ImageFormat::R8);
             device.init_texture(
                 &mut texture,
                 8,
                 8,
-                ImageFormat::R8,
                 TextureFilter::Nearest,
                 None,
                 1,
                 Some(&dither_matrix),
             );
 
             Some(texture)
         } else {
@@ -2213,34 +2232,42 @@ impl Renderer {
                 );
                 backend.run(backend_profile_counters);
                 if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                     thread_listener.thread_stopped(&thread_name);
                 }
             })
         };
 
+        #[cfg(feature = "capture")]
+        let capture = RendererCapture {
+            read_fbo: device.create_fbo_for_external_texture(0),
+            owned_external_images: FastHashMap::default(),
+        };
+        #[cfg(not(feature = "capture"))]
+        let capture = RendererCapture;
+
         let gpu_profile = GpuProfiler::new(Rc::clone(device.rc_gl()));
 
         let mut renderer = Renderer {
             result_rx,
             debug_server,
             device,
             active_documents: Vec::new(),
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_text_run,
             cs_blur_a8,
             cs_blur_rgba8,
             brush_mask_corner,
             brush_mask_rounded_rect,
-            brush_image_rgba8,
-            brush_image_rgba8_alpha_mask,
-            brush_image_a8,
+            brush_picture_rgba8,
+            brush_picture_rgba8_alpha_mask,
+            brush_picture_a8,
             brush_solid,
             brush_line,
             cs_clip_rectangle,
             cs_clip_border,
             cs_clip_image,
             ps_text_run,
             ps_text_run_dual_source,
             ps_image,
@@ -2277,16 +2304,17 @@ impl Renderer {
             output_image_handler: None,
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             texture_cache_upload_pbo,
             texture_resolver,
             renderer_errors: Vec::new(),
+            capture,
         };
 
         renderer.set_debug_flags(options.debug_flags);
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
@@ -2352,17 +2380,25 @@ impl Renderer {
                     // This is a workaround for https://github.com/servo/servo/issues/13149.
                     for (pipeline_id, epoch) in &doc.pipeline_epoch_map {
                         self.pipeline_epoch_map.insert(*pipeline_id, *epoch);
                     }
 
                     // Add a new document to the active set, expressed as a `Vec` in order
                     // to re-order based on `DocumentLayer` during rendering.
                     match self.active_documents.iter().position(|&(id, _)| id == document_id) {
-                        Some(pos) => self.active_documents[pos].1 = doc,
+                        Some(pos) => {
+                            // If the document we are replacing must be drawn
+                            // (in order to update the texture cache), issue
+                            // a render just to off-screen targets.
+                            if self.active_documents[pos].1.frame.must_be_drawn() {
+                                self.render_impl(None).ok();
+                            }
+                            self.active_documents[pos].1 = doc;
+                        }
                         None => self.active_documents.push((document_id, doc)),
                     }
                 }
                 ResultMsg::UpdateResources {
                     updates,
                     cancel_rendering,
                 } => {
                     self.pending_texture_updates.push(updates);
@@ -2379,57 +2415,24 @@ impl Renderer {
                     self.pending_shader_updates.push(path);
                 }
                 ResultMsg::DebugOutput(output) => match output {
                     DebugOutput::FetchDocuments(string) |
                     DebugOutput::FetchClipScrollTree(string) => {
                         self.debug_server.send(string);
                     }
                     #[cfg(feature = "capture")]
-                    DebugOutput::SaveCapture(path, deferred)=> {
-                        use std::fs::File;
-                        use std::io::Write;
-                        use api::ExternalImageData;
-
-                        if deferred.is_empty() {
-                            continue
-                        }
-
-                        info!("saving external images");
-                        let handler = self.external_image_handler
-                            .as_mut()
-                            .expect("Unable to lock the external image handler!");
-                        for def in deferred {
-                            let ExternalImageData { id, channel_index, .. } = def.external;
-                            let data = match handler.lock(id, channel_index).source {
-                                ExternalImageSource::RawData(data) => data.to_vec(),
-                                ExternalImageSource::NativeTexture(_gl_id) => {
-                                    //TODO: make a read FBO with this GL texture
-                                    //self.device.read_pixels(&def.descriptor);
-                                    unimplemented!()
-                                }
-                                ExternalImageSource::Invalid => {
-                                    // Create a dummy buffer...
-                                    let stride = def.descriptor.compute_stride();
-                                    let total_size = def.descriptor.height * stride;
-                                    vec![0xFF; total_size as usize]
-                                }
-                            };
-                            handler.unlock(id, channel_index);
-
-                            let full_path = format!("{}/{}",
-                                path.to_string_lossy(), def.short_path);
-                            File::create(full_path)
-                                .expect(&format!("Unable to create {}", def.short_path))
-                                .write_all(&data)
-                                .unwrap();
-                        }
+                    DebugOutput::SaveCapture(config, deferred) => {
+                        self.save_capture(config, deferred);
                     }
                     #[cfg(feature = "capture")]
-                    DebugOutput::LoadCapture => {}
+                    DebugOutput::LoadCapture(root) => {
+                        self.active_documents.clear();
+                        self.load_capture(root);
+                    }
                 },
                 ResultMsg::DebugCommand(command) => {
                     self.handle_debug_command(command);
                 }
             }
         }
     }
 
@@ -2665,19 +2668,19 @@ impl Renderer {
             DebugCommand::FetchPasses => {
                 let json = self.get_passes_for_debugger();
                 self.debug_server.send(json);
             }
             DebugCommand::FetchScreenshot => {
                 let json = self.get_screenshot_for_debugger();
                 self.debug_server.send(json);
             }
-            DebugCommand::SaveCapture(_) |
-            DebugCommand::LoadCapture(_) => {
-                panic!("Capture commands are not welcome here!")
+            DebugCommand::SaveCapture(..) |
+            DebugCommand::LoadCapture(..) => {
+                panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
             }
             DebugCommand::EnableDualSourceBlending(_) => {
                 panic!("Should be handled by render backend");
             }
         }
     }
 
     /// Set a callback for handling external images.
@@ -2724,17 +2727,28 @@ impl Renderer {
     }
 
     /// Renders the current frame.
     ///
     /// A Frame is supplied by calling [`generate_frame()`][genframe].
     /// [genframe]: ../../webrender_api/struct.DocumentApi.html#method.generate_frame
     pub fn render(
         &mut self,
-        framebuffer_size: DeviceUintSize
+        framebuffer_size: DeviceUintSize,
+    ) -> Result<RendererStats, Vec<RendererError>> {
+        self.render_impl(Some(framebuffer_size))
+    }
+
+    // If framebuffer_size is None, don't render
+    // to the main frame buffer. This is useful
+    // to update texture cache render tasks but
+    // avoid doing a full frame render.
+    fn render_impl(
+        &mut self,
+        framebuffer_size: Option<DeviceUintSize>
     ) -> Result<RendererStats, Vec<RendererError>> {
         profile_scope!("render");
 
         if self.active_documents.is_empty() {
             self.last_time = precise_time_ns();
             return Ok(RendererStats::empty());
         }
 
@@ -2783,42 +2797,49 @@ impl Renderer {
             };
 
             //Note: another borrowck dance
             let mut active_documents = mem::replace(&mut self.active_documents, Vec::default());
             // sort by the document layer id
             active_documents.sort_by_key(|&(_, ref render_doc)| render_doc.frame.layer);
 
             // don't clear the framebuffer if one of the rendered documents will overwrite it
-            let needs_color_clear = !active_documents
-                .iter()
-                .any(|&(_, RenderedDocument { ref frame, .. })| {
-                    frame.background_color.is_some() &&
-                    frame.inner_rect.origin == DeviceUintPoint::zero() &&
-                    frame.inner_rect.size == framebuffer_size
-                });
-
-            if needs_color_clear || clear_depth_value.is_some() {
-                let clear_color = if needs_color_clear {
-                    self.clear_color.map(|color| color.to_array())
-                } else {
-                    None
-                };
-                self.device.bind_draw_target(None, None);
-                self.device.enable_depth_write();
-                self.device.clear_target(clear_color, clear_depth_value, None);
-                self.device.disable_depth_write();
+            if let Some(framebuffer_size) = framebuffer_size {
+                let needs_color_clear = !active_documents
+                    .iter()
+                    .any(|&(_, RenderedDocument { ref frame, .. })| {
+                        frame.background_color.is_some() &&
+                        frame.inner_rect.origin == DeviceUintPoint::zero() &&
+                        frame.inner_rect.size == framebuffer_size
+                    });
+
+                if needs_color_clear || clear_depth_value.is_some() {
+                    let clear_color = if needs_color_clear {
+                        self.clear_color.map(|color| color.to_array())
+                    } else {
+                        None
+                    };
+                    self.device.bind_draw_target(None, None);
+                    self.device.enable_depth_write();
+                    self.device.clear_target(clear_color, clear_depth_value, None);
+                    self.device.disable_depth_write();
+                }
             }
 
             // Re-use whatever targets possible from the pool, before
             // they get changed/re-allocated by the rendered frames.
             for doc_with_id in &mut active_documents {
                 self.prepare_tile_frame(&mut doc_with_id.1.frame);
             }
 
+            #[cfg(feature = "capture")]
+            self.texture_resolver.external_images.extend(
+                self.capture.owned_external_images.iter().map(|(key, value)| (*key, value.clone()))
+            );
+
             for &mut (_, RenderedDocument { ref mut frame, .. }) in &mut active_documents {
                 self.prepare_gpu_cache(frame);
 
                 self.draw_tile_frame(
                     frame,
                     framebuffer_size,
                     clear_depth_value.is_some(),
                     cpu_frame_id,
@@ -2847,37 +2868,39 @@ impl Renderer {
                 self.backend_profile_counters.total_time.get(),
                 profile_timers.cpu_time.get(),
                 self.profile_counters.draw_calls.get(),
             );
             self.cpu_profiles.push_back(cpu_profile);
         }
 
         if self.debug_flags.contains(DebugFlags::PROFILER_DBG) {
-            //TODO: take device/pixel ratio into equation?
-            let screen_fraction = 1.0 / framebuffer_size.to_f32().area();
-            self.profiler.draw_profile(
-                &frame_profiles,
-                &self.backend_profile_counters,
-                &self.profile_counters,
-                &mut profile_timers,
-                &profile_samplers,
-                screen_fraction,
-                &mut self.debug,
-                self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
-            );
+            if let Some(framebuffer_size) = framebuffer_size {
+                //TODO: take device/pixel ratio into equation?
+                let screen_fraction = 1.0 / framebuffer_size.to_f32().area();
+                self.profiler.draw_profile(
+                    &frame_profiles,
+                    &self.backend_profile_counters,
+                    &self.profile_counters,
+                    &mut profile_timers,
+                    &profile_samplers,
+                    screen_fraction,
+                    &mut self.debug,
+                    self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
+                );
+            }
         }
 
         self.profile_counters.reset();
         self.profile_counters.frame_counter.inc();
 
-        self.debug.render(&mut self.device, &framebuffer_size);
         profile_timers.cpu_time.profile(|| {
             let _gm = self.gpu_profile.start_marker("end frame");
             self.gpu_profile.end_frame();
+            self.debug.render(&mut self.device, framebuffer_size);
             self.device.end_frame();
         });
         self.last_time = current_time;
 
         if self.renderer_errors.is_empty() {
             Ok(stats)
         } else {
             Err(mem::replace(&mut self.renderer_errors, Vec::new()))
@@ -2888,17 +2911,17 @@ impl Renderer {
         self.active_documents
             .iter()
             .any(|&(_, ref render_doc)| !render_doc.layers_bouncing_back.is_empty())
     }
 
     fn prepare_gpu_cache(&mut self, frame: &Frame) {
         let _gm = self.gpu_profile.start_marker("gpu cache update");
 
-        let deferred_update_list = self.update_deferred_resolves(frame);
+        let deferred_update_list = self.update_deferred_resolves(&frame.deferred_resolves);
         self.pending_gpu_cache_updates.extend(deferred_update_list);
 
         // For an artificial stress test of GPU cache resizing,
         // always pass an extra update list with at least one block in it.
         let gpu_cache_height = self.gpu_cache_texture.get_height();
         if gpu_cache_height != 0 &&  GPU_CACHE_RESIZE_TEST {
             self.pending_gpu_cache_updates.push(GpuCacheUpdateList {
                 height: gpu_cache_height,
@@ -2956,29 +2979,29 @@ impl Renderer {
                         layer_count,
                         format,
                         filter,
                         render_target,
                     } => {
                         let CacheTextureId(cache_texture_index) = update.id;
                         if self.texture_resolver.cache_texture_map.len() == cache_texture_index {
                             // Create a new native texture, as requested by the texture cache.
-                            let texture = self.device.create_texture(TextureTarget::Array);
+                            let texture = self.device.create_texture(TextureTarget::Array, format);
                             self.texture_resolver.cache_texture_map.push(texture);
                         }
                         let texture =
                             &mut self.texture_resolver.cache_texture_map[cache_texture_index];
+                        assert_eq!(texture.get_format(), format);
 
                         // Ensure no PBO is bound when creating the texture storage,
                         // or GL will attempt to read data from there.
                         self.device.init_texture(
                             texture,
                             width,
                             height,
-                            format,
                             filter,
                             render_target,
                             layer_count,
                             None,
                         );
                     }
                     TextureUpdateOp::Update {
                         rect,
@@ -3009,17 +3032,17 @@ impl Renderer {
                                     ExternalImageSource::RawData(data) => {
                                         uploader.upload(
                                             rect, layer_index, stride,
                                             &data[offset as usize ..],
                                         );
                                     }
                                     ExternalImageSource::Invalid => {
                                         // Create a local buffer to fill the pbo.
-                                        let bpp = texture.get_bpp();
+                                        let bpp = texture.get_format().bytes_per_pixel();
                                         let width = stride.unwrap_or(rect.size.width * bpp);
                                         let total_size = width * rect.size.height;
                                         // WR haven't support RGBAF32 format in texture_cache, so
                                         // we use u8 type here.
                                         let dummy_data: Vec<u8> = vec![255; total_size as usize];
                                         uploader.upload(rect, layer_index, stride, &dummy_data);
                                     }
                                     _ => panic!("No external buffer found"),
@@ -3122,21 +3145,21 @@ impl Renderer {
                         self.brush_solid.bind(
                             &mut self.device,
                             key.blend_mode,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
                     }
-                    BrushBatchKind::Image(target_kind) => {
+                    BrushBatchKind::Picture(target_kind) => {
                         let shader = match target_kind {
-                            BrushImageSourceKind::Alpha => &mut self.brush_image_a8,
-                            BrushImageSourceKind::Color => &mut self.brush_image_rgba8,
-                            BrushImageSourceKind::ColorAlphaMask => &mut self.brush_image_rgba8_alpha_mask,
+                            BrushImageSourceKind::Alpha => &mut self.brush_picture_a8,
+                            BrushImageSourceKind::Color => &mut self.brush_picture_rgba8,
+                            BrushImageSourceKind::ColorAlphaMask => &mut self.brush_picture_rgba8_alpha_mask,
                         };
                         shader.bind(
                             &mut self.device,
                             key.blend_mode,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
@@ -3731,37 +3754,36 @@ impl Renderer {
 
         // For any registered image outputs on this render target,
         // get the texture from caller and blit it.
         for output in &target.outputs {
             let handler = self.output_image_handler
                 .as_mut()
                 .expect("Found output image, but no handler set!");
             if let Some((texture_id, output_size)) = handler.lock(output.pipeline_id) {
-                let device = &mut self.device;
                 let fbo_id = match self.output_targets.entry(texture_id) {
                     Entry::Vacant(entry) => {
-                        let fbo_id = device.create_fbo_for_external_texture(texture_id);
+                        let fbo_id = self.device.create_fbo_for_external_texture(texture_id);
                         entry.insert(FrameOutput {
                             fbo_id,
                             last_access: frame_id,
                         });
                         fbo_id
                     }
                     Entry::Occupied(mut entry) => {
                         let target = entry.get_mut();
                         target.last_access = frame_id;
                         target.fbo_id
                     }
                 };
                 let (src_rect, _) = render_tasks[output.task_id].get_target_rect();
                 let dest_rect = DeviceIntRect::new(DeviceIntPoint::zero(), output_size);
-                device.bind_read_target(render_target);
-                device.bind_external_draw_target(fbo_id);
-                device.blit_render_target(src_rect, dest_rect);
+                self.device.bind_read_target(render_target);
+                self.device.bind_external_draw_target(fbo_id);
+                self.device.blit_render_target(src_rect, dest_rect);
                 handler.unlock(output.pipeline_id);
             }
         }
     }
 
     fn draw_alpha_target(
         &mut self,
         render_target: (&Texture, i32),
@@ -3990,54 +4012,44 @@ impl Renderer {
                 &target.horizontal_blurs,
                 VertexArrayKind::Blur,
                 &BatchTextures::no_texture(),
                 stats,
             );
         }
     }
 
-    fn update_deferred_resolves(&mut self, frame: &Frame) -> Option<GpuCacheUpdateList> {
+    fn update_deferred_resolves(&mut self, deferred_resolves: &[DeferredResolve]) -> Option<GpuCacheUpdateList> {
         // The first thing we do is run through any pending deferred
         // resolves, and use a callback to get the UV rect for this
         // custom item. Then we patch the resource_rects structure
         // here before it's uploaded to the GPU.
-        if frame.deferred_resolves.is_empty() {
+        if deferred_resolves.is_empty() {
             return None;
         }
 
         let handler = self.external_image_handler
             .as_mut()
             .expect("Found external image, but no handler set!");
 
         let mut list = GpuCacheUpdateList {
             height: self.gpu_cache_texture.get_height(),
             blocks: Vec::new(),
             updates: Vec::new(),
         };
 
-        for deferred_resolve in &frame.deferred_resolves {
+        for deferred_resolve in deferred_resolves {
             self.gpu_profile.place_marker("deferred resolve");
             let props = &deferred_resolve.image_properties;
             let ext_image = props
                 .external_image
                 .expect("BUG: Deferred resolves must be external images!");
             let image = handler.lock(ext_image.id, ext_image.channel_index);
-            let texture_target = match ext_image.image_type {
-                ExternalImageType::Texture2DHandle => TextureTarget::Default,
-                ExternalImageType::Texture2DArrayHandle => TextureTarget::Array,
-                ExternalImageType::TextureRectHandle => TextureTarget::Rect,
-                ExternalImageType::TextureExternalHandle => TextureTarget::External,
-                ExternalImageType::ExternalBuffer => {
-                    panic!(
-                        "{:?} is not a suitable image type in update_deferred_resolves().",
-                        ext_image.image_type
-                    );
-                }
-            };
+            let texture_target = get_external_image_target(ext_image.image_type)
+                .expect(&format!("{:?} is not a suitable image type in update_deferred_resolves()", ext_image.image_type));
 
             // In order to produce the handle, the external image handler may call into
             // the GL context and change some states.
             self.device.reset_state();
 
             let texture = match image.source {
                 ExternalImageSource::NativeTexture(texture_id) => {
                     ExternalTexture::new(texture_id, texture_target)
@@ -4112,27 +4124,29 @@ impl Renderer {
                 Some(pos) => self.texture_resolver.render_target_pool.swap_remove(pos),
                 None => return,
             }
         } else {
             if list.texture.is_some() {
                 list.check_ready();
                 return
             }
-            match self.texture_resolver.render_target_pool.pop() {
-                Some(texture) => texture,
-                None => self.device.create_texture(TextureTarget::Array),
+            let index = self.texture_resolver.render_target_pool
+                .iter()
+                .position(|texture| texture.get_format() == list.format);
+            match index {
+                Some(pos) => self.texture_resolver.render_target_pool.swap_remove(pos),
+                None => self.device.create_texture(TextureTarget::Array, list.format),
             }
         };
 
         self.device.init_texture(
             &mut texture,
             list.max_size.width,
             list.max_size.height,
-            list.format,
             TextureFilter::Linear,
             Some(RenderTargetInfo {
                 has_depth: list.needs_depth(),
             }),
             list.targets.len() as _,
             None,
         );
         list.texture = Some(texture);
@@ -4184,22 +4198,23 @@ impl Renderer {
 
         debug_assert!(self.texture_resolver.cache_a8_texture.is_none());
         debug_assert!(self.texture_resolver.cache_rgba8_texture.is_none());
     }
 
     fn draw_tile_frame(
         &mut self,
         frame: &mut Frame,
-        framebuffer_size: DeviceUintSize,
+        framebuffer_size: Option<DeviceUintSize>,
         framebuffer_depth_is_ready: bool,
         frame_id: FrameId,
         stats: &mut RendererStats,
     ) {
         let _gm = self.gpu_profile.start_marker("tile frame draw");
+        frame.has_been_rendered = true;
 
         if frame.passes.is_empty() {
             return;
         }
 
         self.device.disable_depth_write();
         self.device.disable_stencil();
         self.device.set_blend(false);
@@ -4218,40 +4233,42 @@ impl Renderer {
             self.texture_resolver.bind(
                 &SourceTexture::CacheRGBA8,
                 TextureSampler::CacheRGBA8,
                 &mut self.device,
             );
 
             let (cur_alpha, cur_color) = match pass.kind {
                 RenderPassKind::MainFramebuffer(ref target) => {
-                    stats.color_target_count += 1;
-
-                    let clear_color = frame.background_color.map(|color| color.to_array());
-                    let projection = Transform3D::ortho(
-                        0.0,
-                        framebuffer_size.width as f32,
-                        framebuffer_size.height as f32,
-                        0.0,
-                        ORTHO_NEAR_PLANE,
-                        ORTHO_FAR_PLANE,
-                    );
-
-                    self.draw_color_target(
-                        None,
-                        target,
-                        frame.inner_rect,
-                        framebuffer_size,
-                        framebuffer_depth_is_ready,
-                        clear_color,
-                        &frame.render_tasks,
-                        &projection,
-                        frame_id,
-                        stats,
-                    );
+                    if let Some(framebuffer_size) = framebuffer_size {
+                        stats.color_target_count += 1;
+
+                        let clear_color = frame.background_color.map(|color| color.to_array());
+                        let projection = Transform3D::ortho(
+                            0.0,
+                            framebuffer_size.width as f32,
+                            framebuffer_size.height as f32,
+                            0.0,
+                            ORTHO_NEAR_PLANE,
+                            ORTHO_FAR_PLANE,
+                        );
+
+                        self.draw_color_target(
+                            None,
+                            target,
+                            frame.inner_rect,
+                            framebuffer_size,
+                            framebuffer_depth_is_ready,
+                            clear_color,
+                            &frame.render_tasks,
+                            &projection,
+                            frame_id,
+                            stats,
+                        );
+                    }
 
                     (None, None)
                 }
                 RenderPassKind::OffScreen { ref mut alpha, ref mut color, ref mut texture_cache } => {
                     alpha.check_ready();
                     color.check_ready();
                     for (&(texture_id, target_index), target) in texture_cache {
                         self.draw_texture_cache_target(
@@ -4328,18 +4345,20 @@ impl Renderer {
                 {
                     self.device
                         .bind_texture(TextureSampler::SharedCacheA8, shared_alpha_texture);
                 }
             }
         }
 
         self.texture_resolver.end_frame(RenderPassIndex(frame.passes.len()));
-        self.draw_render_target_debug(framebuffer_size);
-        self.draw_texture_cache_debug(framebuffer_size);
+        if let Some(framebuffer_size) = framebuffer_size {
+            self.draw_render_target_debug(framebuffer_size);
+            self.draw_texture_cache_debug(framebuffer_size);
+        }
         self.draw_epoch_debug();
 
         // Garbage collect any frame outputs that weren't used this frame.
         let device = &mut self.device;
         self.output_targets
             .retain(|_, target| if target.last_access != frame_id {
                 device.delete_fbo(target.fbo_id);
                 false
@@ -4506,63 +4525,42 @@ impl Renderer {
             y0 - margin,
             x0 + text_width + margin,
             y + margin,
             ColorU::new(25, 25, 25, 200),
             ColorU::new(51, 51, 51, 200),
         );
     }
 
-    pub fn read_pixels_rgba8(&self, rect: DeviceUintRect) -> Vec<u8> {
-        let mut pixels = vec![0u8; (4 * rect.size.width * rect.size.height) as usize];
-        self.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels);
+    /// Pass-through to `Device::read_pixels_into`, used by Gecko's WR bindings.
+    pub fn read_pixels_into(&mut self, rect: DeviceUintRect, format: ReadPixelsFormat, output: &mut [u8]) {
+        self.device.read_pixels_into(rect, format, output);
+    }
+
+    pub fn read_pixels_rgba8(&mut self, rect: DeviceUintRect) -> Vec<u8> {
+        let mut pixels = vec![0; (rect.size.width * rect.size.height * 4) as usize];
+        self.device.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels);
         pixels
     }
 
     pub fn read_gpu_cache(&mut self) -> (DeviceUintSize, Vec<u8>) {
         let size = self.gpu_cache_texture.texture.get_dimensions();
-        let mut texels = vec![0u8; 4 * (size.width * size.height) as usize];
+        let mut texels = vec![0; (size.width * size.height * 16) as usize];
         self.device.begin_frame();
         self.device.bind_read_target(Some((&self.gpu_cache_texture.texture, 0)));
-        self.read_pixels_into(
+        self.device.read_pixels_into(
             DeviceUintRect::new(DeviceUintPoint::zero(), size),
-            ReadPixelsFormat::Rgba8,
+            ReadPixelsFormat::Standard(ImageFormat::RGBAF32),
             &mut texels,
         );
         self.device.bind_read_target(None);
         self.device.end_frame();
         (size, texels)
     }
 
-    pub fn read_pixels_into(
-        &self,
-        rect: DeviceUintRect,
-        format: ReadPixelsFormat,
-        output: &mut [u8],
-    ) {
-        let (gl_format, gl_type, size) = match format {
-            ReadPixelsFormat::Rgba8 => (gl::RGBA, gl::UNSIGNED_BYTE, 4),
-            ReadPixelsFormat::Bgra8 => (get_gl_format_bgra(self.device.gl()), gl::UNSIGNED_BYTE, 4),
-        };
-        assert_eq!(
-            output.len(),
-            (size * rect.size.width * rect.size.height) as usize
-        );
-        self.device.gl().flush();
-        self.device.gl().read_pixels_into_buffer(
-            rect.origin.x as gl::GLint,
-            rect.origin.y as gl::GLint,
-            rect.size.width as gl::GLsizei,
-            rect.size.height as gl::GLsizei,
-            gl_format,
-            gl_type,
-            output,
-        );
-    }
-
     // De-initialize the Renderer safely, assuming the GL is still alive and active.
     pub fn deinit(mut self) {
         //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame
         self.device.begin_frame();
         self.gpu_cache_texture.deinit(&mut self.device);
         if let Some(dither_matrix_texture) = self.dither_matrix_texture {
             self.device.delete_texture(dither_matrix_texture);
         }
@@ -4575,19 +4573,19 @@ impl Renderer {
         self.device.delete_vao(self.clip_vao);
         self.device.delete_vao(self.blur_vao);
         self.debug.deinit(&mut self.device);
         self.cs_text_run.deinit(&mut self.device);
         self.cs_blur_a8.deinit(&mut self.device);
         self.cs_blur_rgba8.deinit(&mut self.device);
         self.brush_mask_rounded_rect.deinit(&mut self.device);
         self.brush_mask_corner.deinit(&mut self.device);
-        self.brush_image_rgba8.deinit(&mut self.device);
-        self.brush_image_rgba8_alpha_mask.deinit(&mut self.device);
-        self.brush_image_a8.deinit(&mut self.device);
+        self.brush_picture_rgba8.deinit(&mut self.device);
+        self.brush_picture_rgba8_alpha_mask.deinit(&mut self.device);
+        self.brush_picture_a8.deinit(&mut self.device);
         self.brush_solid.deinit(&mut self.device);
         self.brush_line.deinit(&mut self.device);
         self.cs_clip_rectangle.deinit(&mut self.device);
         self.cs_clip_image.deinit(&mut self.device);
         self.cs_clip_border.deinit(&mut self.device);
         self.ps_text_run.deinit(&mut self.device);
         self.ps_text_run_dual_source.deinit(&mut self.device);
         for shader in self.ps_image {
@@ -4607,16 +4605,22 @@ impl Renderer {
         self.ps_border_edge.deinit(&mut self.device);
         self.ps_gradient.deinit(&mut self.device);
         self.ps_angle_gradient.deinit(&mut self.device);
         self.ps_radial_gradient.deinit(&mut self.device);
         self.ps_blend.deinit(&mut self.device);
         self.ps_hw_composite.deinit(&mut self.device);
         self.ps_split_composite.deinit(&mut self.device);
         self.ps_composite.deinit(&mut self.device);
+        #[cfg(feature = "capture")]
+        self.device.delete_fbo(self.capture.read_fbo);
+        #[cfg(feature = "capture")]
+        for (_, ext) in self.capture.owned_external_images {
+            self.device.delete_external_texture(ext);
+        }
         self.device.end_frame();
     }
 }
 
 pub enum ExternalImageSource<'a> {
     RawData(&'a [u8]),  // raw buffers.
     NativeTexture(u32), // It's a gl::GLuint texture handle
     Invalid,
@@ -4693,17 +4697,17 @@ pub struct RendererOptions {
     pub enable_render_on_scroll: bool,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
 }
 
 impl Default for RendererOptions {
-    fn default() -> 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,
@@ -4731,17 +4735,17 @@ impl Default for RendererOptions {
     }
 }
 
 #[cfg(not(feature = "debugger"))]
 pub struct DebugServer;
 
 #[cfg(not(feature = "debugger"))]
 impl DebugServer {
-    pub fn new(_: MsgSender<ApiMsg>) -> DebugServer {
+    pub fn new(_: MsgSender<ApiMsg>) -> Self {
         DebugServer
     }
 
     pub fn send(&mut self, _: String) {}
 }
 
 // Some basic statistics about the rendered scene
 // that we can use in wrench reftests to ensure that
@@ -4749,16 +4753,262 @@ impl DebugServer {
 // targets as we expect them to.
 pub struct RendererStats {
     pub total_draw_calls: usize,
     pub alpha_target_count: usize,
     pub color_target_count: usize,
 }
 
 impl RendererStats {
-    pub fn empty() -> RendererStats {
+    pub fn empty() -> Self {
         RendererStats {
             total_draw_calls: 0,
             alpha_target_count: 0,
             color_target_count: 0,
         }
     }
 }
+
+
+#[cfg(feature = "capture")]
+#[derive(Deserialize, Serialize)]
+struct PlainTexture {
+    data: String,
+    size: (u32, u32, i32),
+    format: ImageFormat,
+    filter: TextureFilter,
+    render_target: Option<RenderTargetInfo>,
+}
+
+#[cfg(feature = "capture")]
+#[derive(Deserialize, Serialize)]
+struct PlainRenderer {
+    gpu_cache: PlainTexture,
+    textures: Vec<PlainTexture>,
+    external_images: Vec<ExternalCaptureImage>
+}
+
+#[cfg(feature = "capture")]
+struct DummyExternalImageHandler {
+    data: FastHashMap<(ExternalImageId, u8), Vec<u8>>,
+}
+
+#[cfg(feature = "capture")]
+impl ExternalImageHandler for DummyExternalImageHandler {
+    fn lock(&mut self, key: ExternalImageId, channel_index: u8) -> ExternalImage {
+        let slice = &self.data[&(key, channel_index)];
+        ExternalImage {
+            u0: 0.0,
+            v0: 0.0,
+            u1: 1.0,
+            v1: 1.0,
+            source: ExternalImageSource::RawData(slice),
+        }
+    }
+    fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
+}
+
+#[cfg(feature = "capture")]
+impl OutputImageHandler for () {
+    fn lock(&mut self, _: PipelineId) -> Option<(u32, DeviceIntSize)> {
+        None
+    }
+    fn unlock(&mut self, _: PipelineId) {
+        unreachable!()
+    }
+}
+
+#[cfg(feature = "capture")]
+impl Renderer {
+    fn save_texture(
+        texture: &Texture, name: &str, root: &PathBuf, device: &mut Device
+    ) -> PlainTexture {
+        use std::fs;
+        use std::io::Write;
+
+        let short_path = format!("textures/{}.raw", name);
+
+        let bytes_per_pixel = texture.get_format().bytes_per_pixel();
+        let read_format = ReadPixelsFormat::Standard(texture.get_format());
+        let rect = DeviceUintRect::new(
+            DeviceUintPoint::zero(),
+            texture.get_dimensions(),
+        );
+
+        let mut file = fs::File::create(root.join(&short_path))
+            .expect(&format!("Unable to create {}", short_path));
+        let bytes_per_layer = (rect.size.width * rect.size.height * bytes_per_pixel) as usize;
+        let mut data = vec![0; bytes_per_layer];
+
+        for layer_id in 0 .. texture.get_layer_count() {
+            device.attach_read_texture(texture, layer_id);
+            device.read_pixels_into(rect, read_format, &mut data);
+            file.write_all(&data)
+                .unwrap();
+        }
+
+        PlainTexture {
+            data: short_path,
+            size: (rect.size.width, rect.size.height, texture.get_layer_count()),
+            format: texture.get_format(),
+            filter: texture.get_filter(),
+            render_target: texture.get_render_target(),
+        }
+    }
+
+    fn load_texture(texture: &mut Texture, plain: &PlainTexture, root: &PathBuf, device: &mut Device) -> Vec<u8> {
+        use std::fs::File;
+        use std::io::Read;
+
+        let mut texels = Vec::new();
+        assert_eq!(plain.format, texture.get_format());
+        File::open(root.join(&plain.data))
+            .unwrap()
+            .read_to_end(&mut texels)
+            .unwrap();
+
+        device.init_texture(
+            texture, plain.size.0, plain.size.1,
+            plain.filter, plain.render_target,
+            plain.size.2, Some(texels.as_slice()),
+        );
+
+        texels
+    }
+
+    fn save_capture(&mut self, config: CaptureConfig, deferred_images: Vec<ExternalCaptureImage>) {
+        use std::fs;
+        use std::io::Write;
+        use api::{CaptureBits, ExternalImageData};
+
+        self.device.begin_frame();
+        self.device.bind_read_target_impl(self.capture.read_fbo);
+
+        if !deferred_images.is_empty() {
+            info!("saving external images");
+            let handler = self.external_image_handler
+                .as_mut()
+                .expect("Unable to lock the external image handler!");
+            for def in &deferred_images {
+                let ExternalImageData { id, channel_index, image_type } = def.external;
+                let data = match handler.lock(id, channel_index).source {
+                    ExternalImageSource::RawData(data) => data.to_vec(),
+                    ExternalImageSource::NativeTexture(gl_id) => {
+                        let target = get_external_image_target(image_type).unwrap();
+                        self.device.attach_read_texture_external(gl_id, target, 0);
+                        self.device.read_pixels(&def.descriptor)
+                    }
+                    ExternalImageSource::Invalid => {
+                        // Create a dummy buffer...
+                        let stride = def.descriptor.compute_stride();
+                        let total_size = def.descriptor.height * stride;
+                        vec![0xFF; total_size as usize]
+                    }
+                };
+                handler.unlock(id, channel_index);
+
+                fs::File::create(config.root.join(&def.short_path))
+                    .expect(&format!("Unable to create {}", def.short_path))
+                    .write_all(&data)
+                    .unwrap();
+            }
+        }
+
+        if config.bits.contains(CaptureBits::FRAME) {
+            let path_textures = config.root.join("textures");
+            if !path_textures.is_dir() {
+                fs::create_dir(&path_textures).unwrap();
+            }
+
+            info!("saving GPU cache");
+            let mut plain_self = PlainRenderer {
+                gpu_cache: Self::save_texture(
+                    &self.gpu_cache_texture.texture,
+                    "gpu", &config.root, &mut self.device,
+                ),
+                textures: Vec::new(),
+                external_images: deferred_images,
+            };
+
+            info!("saving cached textures");
+            for texture in &self.texture_resolver.cache_texture_map {
+                let file_name = format!("cache-{}", plain_self.textures.len() + 1);
+                info!("\t{}", file_name);
+                let plain = Self::save_texture(texture, &file_name, &config.root, &mut self.device);
+                plain_self.textures.push(plain);
+            }
+
+            config.serialize(&plain_self, "renderer");
+        }
+
+        self.device.bind_read_target(None);
+        self.device.end_frame();
+        info!("done.");
+    }
+
+    fn load_capture(&mut self, root: PathBuf) {
+        let renderer = match CaptureConfig::deserialize::<PlainRenderer, _>(&root, "renderer") {
+            Some(r) => r,
+            None => return,
+        };
+
+        self.device.begin_frame();
+        info!("loading cached textures");
+
+        for texture in self.texture_resolver.cache_texture_map.drain(..) {
+            self.device.delete_texture(texture);
+        }
+        for texture in renderer.textures {
+            info!("\t{}", texture.data);
+            let mut t = self.device.create_texture(TextureTarget::Array, texture.format);
+            Self::load_texture(&mut t, &texture, &root, &mut self.device);
+            self.texture_resolver.cache_texture_map.push(t);
+        }
+
+        info!("loading gpu cache");
+        Self::load_texture(
+            &mut self.gpu_cache_texture.texture,
+            &renderer.gpu_cache,
+            &root,
+            &mut self.device,
+        );
+        match self.gpu_cache_texture.bus {
+            CacheBus::PixelBuffer { ref mut rows, ref mut cpu_blocks, .. } => {
+                rows.clear();
+                cpu_blocks.clear();
+            }
+            CacheBus::Scatter { .. } => {}
+        }
+
+        info!("loading external images");
+        assert!(self.texture_resolver.external_images.is_empty());
+        let mut image_handler = DummyExternalImageHandler {
+            data: FastHashMap::default(),
+        };
+
+        for ExternalCaptureImage { short_path, external, descriptor } in renderer.external_images {
+            let target = match get_external_image_target(external.image_type) {
+                Some(target) => target,
+                None => continue,
+            };
+            //TODO: provide a way to query both the layer count and the filter from external images
+            let (layer_count, filter) = (1, TextureFilter::Linear);
+            let plain = PlainTexture {
+                data: short_path,
+                size: (descriptor.width, descriptor.height, layer_count),
+                format: descriptor.format,
+                filter,
+                render_target: None,
+            };
+
+            let mut t = self.device.create_texture(target, plain.format);
+            let data = Self::load_texture(&mut t, &plain, &root, &mut self.device);
+            let key = (external.id, external.channel_index);
+            self.capture.owned_external_images.insert(key, t.into_external());
+            image_handler.data.insert(key, data);
+        }
+
+        self.device.end_frame();
+        self.external_image_handler = Some(Box::new(image_handler) as Box<_>);
+        self.output_image_handler = Some(Box::new(()) as Box<_>);
+        info!("done.");
+    }
+}
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -9,40 +9,43 @@ use api::{Epoch, FontInstanceKey, FontKe
 use api::{ExternalImageData, ExternalImageType};
 use api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, GlyphKey, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 #[cfg(feature = "capture")]
 use api::{NativeFontHandle};
 use app_units::Au;
+#[cfg(feature = "capture")]
+use capture::{ExternalCaptureImage};
 use device::TextureFilter;
 use frame::FrameId;
 use glyph_cache::GlyphCache;
+#[cfg(feature = "capture")]
+use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn, PlainGlyphCacheRef, PlainCachedGlyphInfo};
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
-#[cfg(feature = "capture")]
-use internal_types::ExternalCaptureImage;
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use rayon::ThreadPool;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::cmp;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 #[cfg(feature = "capture")]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use texture_cache::{TextureCache, TextureCacheHandle};
 
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct GlyphFetchResult {
     pub index_in_text_run: i32,
     pub uv_rect_address: GpuCacheAddress,
 }
 
 // These coordinates are always in texels.
 // They are converted to normalized ST
 // values in the vertex shader. The reason
@@ -112,29 +115,32 @@ impl ImageTemplates {
         self.images.get(&key)
     }
 
     fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> {
         self.images.get_mut(&key)
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct CachedImageInfo {
     texture_cache_handle: TextureCacheHandle,
     epoch: Epoch,
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Clone, Deserialize, Serialize))]
 pub enum ResourceClassCacheError {
     OverLimitSize,
 }
 
 pub type ResourceCacheResult<V> = Result<V, ResourceClassCacheError>;
 
-pub struct ResourceClassCache<K, V> {
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+pub struct ResourceClassCache<K: Hash + Eq, V> {
     resources: FastHashMap<K, ResourceCacheResult<V>>,
 }
 
 impl<K, V> ResourceClassCache<K, V>
 where
     K: Clone + Hash + Eq + Debug,
 {
     pub fn new() -> ResourceClassCache<K, V> {
@@ -177,16 +183,17 @@ where
         for key in resources_to_destroy {
             let _ = self.resources.remove(&key).unwrap();
         }
     }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct ImageRequest {
     key: ImageKey,
     rendering: ImageRendering,
     tile: Option<TileOffset>,
 }
 
 impl Into<BlobImageRequest> for ImageRequest {
     fn into(self) -> BlobImageRequest {
@@ -212,29 +219,31 @@ impl BlobImageResources for Resources {
     }
     fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)> {
         self.image_templates
             .get(key)
             .map(|resource| (&resource.data, &resource.descriptor))
     }
 }
 
+pub type GlyphDimensionsCache = FastHashMap<GlyphRequest, Option<GlyphDimensions>>;
+
 pub struct ResourceCache {
     cached_glyphs: GlyphCache,
     cached_images: ImageCache,
     cached_render_tasks: RenderTaskCache,
 
     resources: Resources,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
-    cached_glyph_dimensions: FastHashMap<GlyphRequest, Option<GlyphDimensions>>,
+    cached_glyph_dimensions: GlyphDimensionsCache,
     glyph_rasterizer: GlyphRasterizer,
 
     // The set of images that aren't present or valid in the texture cache,
     // and need to be rasterized and/or uploaded this frame. This includes
     // both blobs and regular images.
     pending_image_requests: FastHashSet<ImageRequest>,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
@@ -988,94 +997,113 @@ struct PlainImageTemplate {
 #[derive(Serialize, Deserialize)]
 pub struct PlainResources {
     font_templates: FastHashMap<FontKey, PlainFontTemplate>,
     font_instances: FastHashMap<FontInstanceKey, FontInstance>,
     image_templates: FastHashMap<ImageKey, PlainImageTemplate>,
 }
 
 #[cfg(feature = "capture")]
+#[derive(Serialize)]
+pub struct PlainCacheRef<'a> {
+    current_frame_id: FrameId,
+    glyphs: PlainGlyphCacheRef<'a>,
+    glyph_dimensions: &'a GlyphDimensionsCache,
+    images: &'a ImageCache,
+    textures: &'a TextureCache,
+}
+
+#[cfg(feature = "capture")]
+#[derive(Deserialize)]
+pub struct PlainCacheOwn {
+    current_frame_id: FrameId,
+    glyphs: PlainGlyphCacheOwn,
+    glyph_dimensions: GlyphDimensionsCache,
+    images: ImageCache,
+    textures: TextureCache,
+}
+
+#[cfg(feature = "capture")]
 impl ResourceCache {
     pub fn save_capture(
         &mut self, root: &PathBuf
     ) -> (PlainResources, Vec<ExternalCaptureImage>) {
         use std::fs;
         use std::io::Write;
 
         info!("saving resource cache");
         let res = &self.resources;
         if !root.is_dir() {
-            fs::create_dir_all(&root).unwrap()
+            fs::create_dir_all(root).unwrap()
         }
-        let path_fonts = root.clone().join("fonts");
+        let path_fonts = root.join("fonts");
         if !path_fonts.is_dir() {
             fs::create_dir(&path_fonts).unwrap();
         }
-        let path_images = root.clone().join("images");
+        let path_images = root.join("images");
         if !path_images.is_dir() {
             fs::create_dir(&path_images).unwrap();
         }
-        let path_blobs = root.clone().join("blobs");
+        let path_blobs = root.join("blobs");
         if !path_blobs.is_dir() {
             fs::create_dir(&path_blobs).unwrap();
         }
 
         info!("\tfont templates");
         let mut font_paths = FastHashMap::default();
-        let mut num_fonts = 0;
         for template in res.font_templates.values() {
             let data: &[u8] = match *template {
                 FontTemplate::Raw(ref arc, _) => arc,
                 FontTemplate::Native(_) => continue,
             };
+            let font_id = res.font_templates.len() + 1;
             let entry = match font_paths.entry(data.as_ptr()) {
                 Entry::Occupied(_) => continue,
                 Entry::Vacant(e) => e,
             };
-            num_fonts += 1;
-            let file_name = format!("{}.raw", num_fonts);
+            let file_name = format!("{}.raw", font_id);
             let short_path = format!("fonts/{}", file_name);
-            let full_path = path_fonts.clone().join(&file_name);
-            fs::File::create(full_path)
+            fs::File::create(path_fonts.join(file_name))
                 .expect(&format!("Unable to create {}", short_path))
                 .write_all(data)
                 .unwrap();
             entry.insert(short_path);
         }
 
         info!("\timage templates");
         let mut image_paths = FastHashMap::default();
         let mut other_paths = FastHashMap::default();
-        let mut num_images = 0;
         let mut external_images = Vec::new();
         for (&key, template) in res.image_templates.images.iter() {
             let desc = &template.descriptor;
             match template.data {
                 ImageData::Raw(ref arc) => {
+                    let image_id = image_paths.len() + 1;
                     let entry = match image_paths.entry(arc.as_ptr()) {
                         Entry::Occupied(_) => continue,
                         Entry::Vacant(e) => e,
                     };
 
-                    //TODO: option to save as PNG
-                    num_images += 1;
-                    let file_name = format!("{}.raw", num_images);
+                    //TODO: option to save as PNG:
+                    // https://github.com/servo/webrender/issues/2234
+                    let file_name = format!("{}.raw", image_id);
                     let short_path = format!("images/{}", file_name);
-                    let full_path = path_images.clone().join(&file_name);
-                    fs::File::create(full_path)
+                    fs::File::create(path_images.join(file_name))
                         .expect(&format!("Unable to create {}", short_path))
                         .write_all(&*arc)
                         .unwrap();
                     entry.insert(short_path);
                 }
                 ImageData::Blob(_) => {
                     assert_eq!(template.tiling, None);
                     let request = BlobImageRequest {
                         key,
-                        tile: None, //TODO: tiled blob images
+                        //TODO: support tiled blob images
+                        // https://github.com/servo/webrender/issues/2236
+                        tile: None,
                     };
 
                     let renderer = self.blob_image_renderer.as_mut().unwrap();
                     renderer.request(
                         &self.resources,
                         request,
                         &BlobImageDescriptor {
                             width: desc.width,
@@ -1141,53 +1169,171 @@ impl ResourceCache {
                     })
                 })
                 .collect(),
         };
 
         (resources, external_images)
     }
 
-    pub fn load_capture(&mut self, resources: PlainResources, root: &PathBuf) {
+    pub fn save_caches(&self, root: &PathBuf) -> PlainCacheRef {
+        use std::io::Write;
+        use std::fs;
+
+        let path_glyphs = root.join("glyphs");
+        if !path_glyphs.is_dir() {
+            fs::create_dir(&path_glyphs).unwrap();
+        }
+
+        info!("\tcached glyphs");
+        let mut glyph_paths = FastHashMap::default();
+        for cache in self.cached_glyphs.glyph_key_caches.values() {
+            for result in cache.resources.values() {
+                let arc = match *result {
+                    Ok(Some(ref info)) => &info.glyph_bytes,
+                    Ok(None) | Err(_) => continue,
+                };
+                let glyph_id = glyph_paths.len() + 1;
+                let entry = match glyph_paths.entry(arc.as_ptr()) {
+                    Entry::Occupied(_) => continue,
+                    Entry::Vacant(e) => e,
+                };
+
+                let file_name = format!("{}.raw", glyph_id);
+                let short_path = format!("glyphs/{}", file_name);
+                fs::File::create(path_glyphs.join(&file_name))
+                    .expect(&format!("Unable to create {}", short_path))
+                    .write_all(&*arc)
+                    .unwrap();
+                entry.insert(short_path);
+            }
+        }
+
+        PlainCacheRef {
+            current_frame_id: self.current_frame_id,
+            glyphs: self.cached_glyphs.glyph_key_caches
+                .iter()
+                .map(|(font_instance, cache)| {
+                    let resources = cache.resources
+                        .iter()
+                        .map(|(key, result)| {
+                            (key.clone(), match *result {
+                                Ok(Some(ref info)) => Ok(Some(PlainCachedGlyphInfo {
+                                    texture_cache_handle: info.texture_cache_handle.clone(),
+                                    glyph_bytes: glyph_paths[&info.glyph_bytes.as_ptr()].clone(),
+                                    size: info.size,
+                                    offset: info.offset,
+                                    scale: info.scale,
+                                    format: info.format,
+                                })),
+                                Ok(None) => Ok(None),
+                                Err(ref e) => Err(e.clone()),
+                            })
+                        })
+                        .collect();
+                    (font_instance, ResourceClassCache { resources })
+                })
+                .collect(),
+            glyph_dimensions: &self.cached_glyph_dimensions,
+            images: &self.cached_images,
+            textures: &self.texture_cache,
+        }
+    }
+
+    pub fn load_capture(
+        &mut self,
+        resources: PlainResources,
+        caches: Option<PlainCacheOwn>,
+        root: &PathBuf,
+    ) {
         use std::fs::File;
         use std::io::Read;
 
         info!("loading resource cache");
-        self.cached_glyphs.clear();
-        self.cached_images.clear();
-        self.cached_render_tasks.clear();
+        //TODO: instead of filling the local path to Arc<data> map as we process
+        // each of the resource types, we could go through all of the local paths
+        // and fill out the map as the first step.
+        let mut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
+
+        match caches {
+            Some(cached) => {
+                let glyph_key_caches = cached.glyphs
+                    .into_iter()
+                    .map(|(font_instance, rcc)| {
+                        let resources = rcc.resources
+                            .into_iter()
+                            .map(|(key, result)| {
+                                (key, match result {
+                                    Ok(Some(info)) => {
+                                        let glyph_bytes = match raw_map.entry(info.glyph_bytes) {
+                                            Entry::Occupied(e) => {
+                                                e.get().clone()
+                                            }
+                                            Entry::Vacant(e) => {
+                                                let mut buffer = Vec::new();
+                                                File::open(root.join(e.key()))
+                                                    .expect(&format!("Unable to open {}", e.key()))
+                                                    .read_to_end(&mut buffer)
+                                                    .unwrap();
+                                                e.insert(Arc::new(buffer))
+                                                    .clone()
+                                            }
+                                        };
+                                        Ok(Some(CachedGlyphInfo {
+                                            texture_cache_handle: info.texture_cache_handle,
+                                            glyph_bytes,
+                                            size: info.size,
+                                            offset: info.offset,
+                                            scale: info.scale,
+                                            format: info.format,
+                                        }))
+                                    },
+                                    Ok(None) => Ok(None),
+                                    Err(e) => Err(e),
+                                })
+                            })
+                            .collect();
+                        (font_instance, ResourceClassCache { resources })
+                    })
+                    .collect();
+                self.current_frame_id = cached.current_frame_id;
+                self.cached_glyphs = GlyphCache { glyph_key_caches };
+                self.cached_glyph_dimensions = cached.glyph_dimensions;
+                self.texture_cache = cached.textures;
+            }
+            None => {
+                self.current_frame_id = FrameId(0);
+                self.cached_glyphs.clear();
+                self.cached_glyph_dimensions.clear();
+                self.cached_images.clear();
+                let max_texture_size = self.texture_cache.max_texture_size();
+                self.texture_cache = TextureCache::new(max_texture_size);
+            }
+        }
 
         self.state = State::Idle;
-        self.current_frame_id = FrameId(0);
-
-        let max_texture_size = self.texture_cache.max_texture_size();
-        self.texture_cache = TextureCache::new(max_texture_size);
-
-        self.cached_glyph_dimensions.clear();
         self.glyph_rasterizer.reset();
         self.pending_image_requests.clear();
 
         let res = &mut self.resources;
         res.font_templates.clear();
         *res.font_instances.write().unwrap() = resources.font_instances;
         res.image_templates.images.clear();
-        let mut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
 
         info!("\tfont templates...");
         for (key, plain_template) in resources.font_templates {
             let template = match plain_template {
                 PlainFontTemplate::Raw { data, index } => {
                     let arc = match raw_map.entry(data) {
                         Entry::Occupied(e) => {
                             e.get().clone()
                         }
                         Entry::Vacant(e) => {
-                            let path = format!("{}/{}", root.to_string_lossy(), e.key());
                             let mut buffer = Vec::new();
-                            File::open(path)
+                            File::open(root.join(e.key()))
                                 .expect(&format!("Unable to open {}", e.key()))
                                 .read_to_end(&mut buffer)
                                 .unwrap();
                             e.insert(Arc::new(buffer))
                                 .clone()
                         }
                     };
                     FontTemplate::Raw(arc, index)
@@ -1203,20 +1349,18 @@ impl ResourceCache {
 
         info!("\timage templates...");
         for (key, template) in resources.image_templates {
             let arc = match raw_map.entry(template.data) {
                 Entry::Occupied(e) => {
                     e.get().clone()
                 }
                 Entry::Vacant(e) => {
-                    //TODO: consider merging the code path with font loading
-                    let path = format!("{}/{}", root.to_string_lossy(), e.key());
                     let mut buffer = Vec::new();
-                    File::open(path)
+                    File::open(root.join(e.key()))
                         .expect(&format!("Unable to open {}", e.key()))
                         .read_to_end(&mut buffer)
                         .unwrap();
                     e.insert(Arc::new(buffer))
                         .clone()
                 }
             };
 
--- a/gfx/webrender/src/segment.rs
+++ b/gfx/webrender/src/segment.rs
@@ -1,57 +1,82 @@
 /* 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::{BorderRadius, ClipMode, LayerPoint, LayerPointAu, LayerRect, LayerSize};
 use app_units::Au;
 use prim_store::EdgeAaSegmentMask;
-use std::cmp;
+use std::{cmp, usize};
 use util::extract_inner_rect_safe;
 
+bitflags! {
+    pub struct ItemFlags: u8 {
+        const X_ACTIVE = 0x1;
+        const Y_ACTIVE = 0x2;
+        const HAS_MASK = 0x4;
+    }
+}
+
 // The segment builder outputs a list of these segments.
 #[derive(Debug, PartialEq)]
 pub struct Segment {
     pub rect: LayerRect,
     pub has_mask: bool,
     pub edge_flags: EdgeAaSegmentMask,
+    pub region_x: usize,
+    pub region_y: usize,
 }
 
 // The segment builder creates a list of x/y axis events
 // that are used to build a segment list. Right now, we
 // don't bother providing a list of *which* clip regions
 // are active for a given segment. Instead, if there is
 // any clip mask present in a segment, we will just end
 // up drawing each of the masks to that segment clip.
 // This is a fairly rare case, but we can detect this
 // in the future and only apply clip masks that are
 // relevant to each segment region.
 // TODO(gw): Provide clip region info with each segment.
 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
 enum EventKind {
-    Begin,
-    End,
+    // Beginning of a clip (rounded) rect.
+    BeginClip,
+    // End of a clip (rounded) rect.
+    EndClip,
+    // Begin the next region in the primitive.
+    BeginRegion,
 }
 
 // Events must be ordered such that when the coordinates
 // of two events are the same, the end events are processed
 // before the begin events. This ensures that we're able
 // to detect which regions are active for a given segment.
 impl Ord for EventKind {
     fn cmp(&self, other: &EventKind) -> cmp::Ordering {
         match (*self, *other) {
-            (EventKind::Begin, EventKind::Begin) |
-            (EventKind::End, EventKind::End) => {
+            (EventKind::BeginRegion, EventKind::BeginRegion) => {
+                panic!("bug: regions must be non-overlapping")
+            }
+            (EventKind::EndClip, EventKind::BeginRegion) |
+            (EventKind::BeginRegion, EventKind::BeginClip) => {
+                cmp::Ordering::Less
+            }
+            (EventKind::BeginClip, EventKind::BeginRegion) |
+            (EventKind::BeginRegion, EventKind::EndClip) => {
+                cmp::Ordering::Greater
+            }
+            (EventKind::BeginClip, EventKind::BeginClip) |
+            (EventKind::EndClip, EventKind::EndClip) => {
                 cmp::Ordering::Equal
             }
-            (EventKind::Begin, EventKind::End) => {
+            (EventKind::BeginClip, EventKind::EndClip) => {
                 cmp::Ordering::Greater
             }
-            (EventKind::End, EventKind::Begin) => {
+            (EventKind::EndClip, EventKind::BeginClip) => {
                 cmp::Ordering::Less
             }
         }
     }
 }
 
 // A x/y event where we will create a vertex in the
 // segment builder.
@@ -70,82 +95,106 @@ impl Ord for Event {
     }
 }
 
 impl Event {
     fn begin(value: f32, index: usize) -> Event {
         Event {
             value: Au::from_f32_px(value),
             item_index: ItemIndex(index),
-            kind: EventKind::Begin,
+            kind: EventKind::BeginClip,
         }
     }
 
     fn end(value: f32, index: usize) -> Event {
         Event {
             value: Au::from_f32_px(value),
             item_index: ItemIndex(index),
-            kind: EventKind::End,
+            kind: EventKind::EndClip,
+        }
+    }
+
+    fn region(value: f32) -> Event {
+        Event {
+            value: Au::from_f32_px(value),
+            kind: EventKind::BeginRegion,
+            item_index: ItemIndex(usize::MAX),
         }
     }
 
-    fn is_active(&self) -> bool {
-        match self.kind {
-            EventKind::Begin => true,
-            EventKind::End => false,
-        }
+    fn update(
+        &self,
+        flag: ItemFlags,
+        items: &mut [Item],
+        region: &mut usize,
+    ) {
+        let is_active = match self.kind {
+            EventKind::BeginClip => true,
+            EventKind::EndClip => false,
+            EventKind::BeginRegion => {
+                *region += 1;
+                return;
+            }
+        };
+
+        items[self.item_index.0].flags.set(flag, is_active);
     }
 }
 
 // An item that provides some kind of clip region (either
 // a clip in/out rect, or a mask region).
 #[derive(Debug)]
 struct Item {
     rect: LayerRect,
     mode: ClipMode,
-    has_mask: bool,
-    active_x: bool,
-    active_y: bool,
+    flags: ItemFlags,
 }
 
 impl Item {
     fn new(
         rect: LayerRect,
         mode: ClipMode,
         has_mask: bool,
     ) -> Item {
+        let flags = if has_mask {
+            ItemFlags::HAS_MASK
+        } else {
+            ItemFlags::empty()
+        };
+
         Item {
             rect,
             mode,
-            has_mask,
-            active_x: false,
-            active_y: false,
+            flags,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
 struct ItemIndex(usize);
 
 // The main public interface to the segment module.
 pub struct SegmentBuilder {
     items: Vec<Item>,
+    inner_rect: Option<LayerRect>,
     bounding_rect: Option<LayerRect>,
 }
 
 impl SegmentBuilder {
     // Create a new segment builder, supplying the primitive
     // local rect and associated local clip rect.
     pub fn new(
         local_rect: LayerRect,
+        inner_rect: Option<LayerRect>,
         local_clip_rect: LayerRect,
     ) -> SegmentBuilder {
         let mut builder = SegmentBuilder {
             items: Vec::new(),
             bounding_rect: Some(local_rect),
+            inner_rect,
         };
 
         builder.push_rect(local_rect, None, ClipMode::Clip);
         builder.push_rect(local_clip_rect, None, ClipMode::Clip);
 
         builder
     }
 
@@ -255,17 +304,17 @@ impl SegmentBuilder {
                     mode,
                     false,
                 ))
             }
         }
     }
 
     // Consume this segment builder and produce a list of segments.
-    pub fn build<F>(self, mut f: F) where F: FnMut(Segment) {
+    pub fn build<F>(self, mut f: F) where F: FnMut(&Segment) {
         let bounding_rect = match self.bounding_rect {
             Some(bounding_rect) => bounding_rect,
             None => return,
         };
 
         // First, filter out any items that don't intersect
         // with the visible bounding rect.
         let mut items: Vec<Item> = self.items
@@ -282,16 +331,25 @@ impl SegmentBuilder {
             let p1 = item.rect.bottom_right();
 
             x_events.push(Event::begin(p0.x, item_index));
             x_events.push(Event::end(p1.x, item_index));
             y_events.push(Event::begin(p0.y, item_index));
             y_events.push(Event::end(p1.y, item_index));
         }
 
+        // Add the region events, if provided.
+        if let Some(inner_rect) = self.inner_rect {
+            x_events.push(Event::region(inner_rect.origin.x));
+            x_events.push(Event::region(inner_rect.origin.x + inner_rect.size.width));
+
+            y_events.push(Event::region(inner_rect.origin.y));
+            y_events.push(Event::region(inner_rect.origin.y + inner_rect.size.height));
+        }
+
         // Get the minimal bounding rect in app units. We will
         // work in fixed point in order to avoid float precision
         // error while handling events.
         let p0 = LayerPointAu::new(
             Au::from_f32_px(bounding_rect.origin.x),
             Au::from_f32_px(bounding_rect.origin.y),
         );
 
@@ -313,118 +371,143 @@ impl SegmentBuilder {
 
         // Each coordinate is clamped to the bounds of the minimal
         // bounding rect. This ensures that we don't generate segments
         // outside that bounding rect, but does allow correctly handling
         // clips where the clip region starts outside the minimal
         // rect but still intersects with it.
 
         let mut prev_y = clamp(p0.y, y_events[0].value, p1.y);
+        let mut region_y = 0;
+        let mut segments = Vec::new();
+        let mut x_count = 0;
+        let mut y_count = 0;
 
         for ey in &y_events {
             let cur_y = clamp(p0.y, ey.value, p1.y);
 
             if cur_y != prev_y {
                 let mut prev_x = clamp(p0.x, x_events[0].value, p1.x);
+                let mut region_x = 0;
 
                 for ex in &x_events {
                     let cur_x = clamp(p0.x, ex.value, p1.x);
 
                     if cur_x != prev_x {
-                        if let Some(segment) = emit_segment_if_needed(
+                        segments.push(emit_segment_if_needed(
                             prev_x,
                             prev_y,
                             cur_x,
                             cur_y,
+                            region_x,
+                            region_y,
                             &items,
-                            &p0,
-                            &p1,
-                        ) {
-                            f(segment);
-                        }
+                        ));
 
                         prev_x = cur_x;
+                        if y_count == 0 {
+                            x_count += 1;
+                        }
                     }
 
-                    items[ex.item_index.0].active_x = ex.is_active();
+                    ex.update(
+                        ItemFlags::X_ACTIVE,
+                        &mut items,
+                        &mut region_x,
+                    );
                 }
 
                 prev_y = cur_y;
+                y_count += 1;
             }
 
-            items[ey.item_index.0].active_y = ey.is_active();
+            ey.update(
+                ItemFlags::Y_ACTIVE,
+                &mut items,
+                &mut region_y,
+            );
+        }
+
+        // Run user supplied closure for each valid segment.
+        debug_assert_eq!(segments.len(), x_count * y_count);
+        for y in 0 .. y_count {
+            for x in 0 .. x_count {
+                let mut edge_flags = EdgeAaSegmentMask::empty();
+
+                if x == 0 || segments[y * x_count + x - 1].is_none() {
+                    edge_flags |= EdgeAaSegmentMask::LEFT;
+                }
+                if x == x_count-1 || segments[y * x_count + x + 1].is_none() {
+                    edge_flags |= EdgeAaSegmentMask::RIGHT;
+                }
+                if y == 0 || segments[(y-1) * x_count + x].is_none() {
+                    edge_flags |= EdgeAaSegmentMask::TOP;
+                }
+                if y == y_count-1 || segments[(y+1) * x_count + x].is_none() {
+                    edge_flags |= EdgeAaSegmentMask::BOTTOM;
+                }
+
+                if let Some(ref mut segment) = segments[y * x_count + x] {
+                    segment.edge_flags = edge_flags;
+                    f(segment);
+                }
+            }
         }
     }
 }
 
 fn clamp(low: Au, value: Au, high: Au) -> Au {
     value.max(low).min(high)
 }
 
 fn emit_segment_if_needed(
     x0: Au,
     y0: Au,
     x1: Au,
     y1: Au,
+    region_x: usize,
+    region_y: usize,
     items: &[Item],
-    bounds_p0: &LayerPointAu,
-    bounds_p1: &LayerPointAu,
 ) -> Option<Segment> {
     debug_assert!(x1 > x0);
     debug_assert!(y1 > y0);
 
     // TODO(gw): Don't scan the whole list of items for
     //           each segment rect. Store active list
     //           in a hash set or similar if this ever
     //           shows up in a profile.
     let mut has_clip_mask = false;
 
     for item in items {
-        if item.active_x && item.active_y {
-            has_clip_mask |= item.has_mask;
+        if item.flags.contains(ItemFlags::X_ACTIVE | ItemFlags::Y_ACTIVE) {
+            has_clip_mask |= item.flags.contains(ItemFlags::HAS_MASK);
 
-            if item.mode == ClipMode::ClipOut && !item.has_mask {
+            if item.mode == ClipMode::ClipOut && !item.flags.contains(ItemFlags::HAS_MASK) {
                 return None;
             }
         }
     }
 
     let segment_rect = LayerRect::new(
         LayerPoint::new(
             x0.to_f32_px(),
             y0.to_f32_px(),
         ),
         LayerSize::new(
             (x1 - x0).to_f32_px(),
             (y1 - y0).to_f32_px(),
         ),
     );
 
-    // Determine which edges touch the bounding rect. This allows
-    // the shaders to apply AA correctly along those edges. It also
-    // allows the batching code to determine which are inner segments
-    // without edges, and push those through the opaque pass.
-    let mut edge_flags = EdgeAaSegmentMask::empty();
-    if x0 == bounds_p0.x {
-        edge_flags |= EdgeAaSegmentMask::LEFT;
-    }
-    if x1 == bounds_p1.x {
-        edge_flags |= EdgeAaSegmentMask::RIGHT;
-    }
-    if y0 == bounds_p0.y {
-        edge_flags |= EdgeAaSegmentMask::TOP;
-    }
-    if y1 == bounds_p1.y {
-        edge_flags |= EdgeAaSegmentMask::BOTTOM;
-    }
-
     Some(Segment {
         rect: segment_rect,
         has_mask: has_clip_mask,
-        edge_flags,
+        edge_flags: EdgeAaSegmentMask::empty(),
+        region_x,
+        region_y,
     })
 }
 
 #[cfg(test)]
 mod test {
     use api::{BorderRadius, ClipMode, LayerPoint, LayerRect, LayerSize};
     use prim_store::EdgeAaSegmentMask;
     use super::{Segment, SegmentBuilder};
@@ -440,53 +523,72 @@ mod test {
     fn seg(
         x0: f32,
         y0: f32,
         x1: f32,
         y1: f32,
         has_mask: bool,
         edge_flags: Option<EdgeAaSegmentMask>,
     ) -> Segment {
+        seg_region(x0, y0, x1, y1, 0, 0, has_mask, edge_flags)
+    }
+
+    fn seg_region(
+        x0: f32,
+        y0: f32,
+        x1: f32,
+        y1: f32,
+        region_x: usize,
+        region_y: usize,
+        has_mask: bool,
+        edge_flags: Option<EdgeAaSegmentMask>,
+    ) -> Segment {
         Segment {
             rect: LayerRect::new(
                 LayerPoint::new(x0, y0),
                 LayerSize::new(x1-x0, y1-y0),
             ),
             has_mask,
             edge_flags: edge_flags.unwrap_or(EdgeAaSegmentMask::empty()),
+            region_x,
+            region_y,
         }
     }
 
     fn segment_sorter(s0: &Segment, s1: &Segment) -> cmp::Ordering {
         let r0 = &s0.rect;
         let r1 = &s1.rect;
 
         (
             (r0.origin.x, r0.origin.y, r0.size.width, r0.size.height)
         ).partial_cmp(&
             (r1.origin.x, r1.origin.y, r1.size.width, r1.size.height)
         ).unwrap()
     }
 
     fn seg_test(
         local_rect: LayerRect,
+        inner_rect: Option<LayerRect>,
         local_clip_rect: LayerRect,
         clips: &[(LayerRect, Option<BorderRadius>, ClipMode)],
         expected_segments: &mut [Segment]
     ) {
         let mut sb = SegmentBuilder::new(
             local_rect,
+            inner_rect,
             local_clip_rect,
         );
         let mut segments = Vec::new();
         for &(rect, radius, mode) in clips {
             sb.push_rect(rect, radius, mode);
         }
-        sb.build(|rect| {
-            segments.push(rect);
+        sb.build(|segment| {
+            segments.push(Segment {
+                ..*segment
+            });
         });
         segments.sort_by(segment_sorter);
         expected_segments.sort_by(segment_sorter);
         assert_eq!(
             segments.len(),
             expected_segments.len(),
             "segments\n{:?}\nexpected\n{:?}\n",
             segments,
@@ -496,26 +598,28 @@ mod test {
             assert_eq!(segment, expected);
         }
     }
 
     #[test]
     fn segment_empty() {
         seg_test(
             rect(0.0, 0.0, 0.0, 0.0),
+            None,
             rect(0.0, 0.0, 0.0, 0.0),
             &[],
             &mut [],
         );
     }
 
     #[test]
     fn segment_single() {
         seg_test(
             rect(10.0, 20.0, 30.0, 40.0),
+            None,
             rect(10.0, 20.0, 30.0, 40.0),
             &[],
             &mut [
                 seg(10.0, 20.0, 30.0, 40.0, false,
                     Some(EdgeAaSegmentMask::LEFT |
                          EdgeAaSegmentMask::TOP |
                          EdgeAaSegmentMask::RIGHT |
                          EdgeAaSegmentMask::BOTTOM
@@ -524,16 +628,17 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_single_clip() {
         seg_test(
             rect(10.0, 20.0, 30.0, 40.0),
+            None,
             rect(10.0, 20.0, 25.0, 35.0),
             &[],
             &mut [
                 seg(10.0, 20.0, 25.0, 35.0, false,
                     Some(EdgeAaSegmentMask::LEFT |
                          EdgeAaSegmentMask::TOP |
                          EdgeAaSegmentMask::RIGHT |
                          EdgeAaSegmentMask::BOTTOM
@@ -542,16 +647,17 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_inner_clip() {
         seg_test(
             rect(10.0, 20.0, 30.0, 40.0),
+            None,
             rect(15.0, 25.0, 25.0, 35.0),
             &[],
             &mut [
                 seg(15.0, 25.0, 25.0, 35.0, false,
                     Some(EdgeAaSegmentMask::LEFT |
                          EdgeAaSegmentMask::TOP |
                          EdgeAaSegmentMask::RIGHT |
                          EdgeAaSegmentMask::BOTTOM
@@ -560,16 +666,17 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_outer_clip() {
         seg_test(
             rect(15.0, 25.0, 25.0, 35.0),
+            None,
             rect(10.0, 20.0, 30.0, 40.0),
             &[],
             &mut [
                 seg(15.0, 25.0, 25.0, 35.0, false,
                     Some(EdgeAaSegmentMask::LEFT |
                          EdgeAaSegmentMask::TOP |
                          EdgeAaSegmentMask::RIGHT |
                          EdgeAaSegmentMask::BOTTOM
@@ -578,16 +685,17 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_clip_int() {
         seg_test(
             rect(10.0, 20.0, 30.0, 40.0),
+            None,
             rect(20.0, 10.0, 40.0, 30.0),
             &[],
             &mut [
                 seg(20.0, 20.0, 30.0, 30.0, false,
                     Some(EdgeAaSegmentMask::LEFT |
                          EdgeAaSegmentMask::TOP |
                          EdgeAaSegmentMask::RIGHT |
                          EdgeAaSegmentMask::BOTTOM
@@ -596,40 +704,43 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_clip_disjoint() {
         seg_test(
             rect(10.0, 20.0, 30.0, 40.0),
+            None,
             rect(30.0, 20.0, 50.0, 40.0),
             &[],
             &mut [],
         );
     }
 
     #[test]
     fn segment_clips() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(-1000.0, -1000.0, 1000.0, 1000.0),
             &[
                 (rect(20.0, 20.0, 40.0, 40.0), None, ClipMode::Clip),
                 (rect(40.0, 20.0, 60.0, 40.0), None, ClipMode::Clip),
             ],
             &mut [
             ],
         );
     }
 
     #[test]
     fn segment_rounded_clip() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(-1000.0, -1000.0, 1000.0, 1000.0),
             &[
                 (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
             ],
             &mut [
                 // corners
                 seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
                 seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
@@ -647,125 +758,130 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_clip_out() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(-1000.0, -1000.0, 2000.0, 2000.0),
             &[
                 (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::ClipOut),
             ],
             &mut [
                 seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)),
-                seg(20.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)),
+                seg(20.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)),
                 seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
 
-                seg(0.0, 20.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT)),
-                seg(60.0, 20.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT)),
+                seg(0.0, 20.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)),
+                seg(60.0, 20.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)),
 
                 seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
-                seg(20.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
+                seg(20.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)),
                 seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)),
             ],
         );
     }
 
     #[test]
     fn segment_rounded_clip_out() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(-1000.0, -1000.0, 2000.0, 2000.0),
             &[
                 (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::ClipOut),
             ],
             &mut [
                 // top row
                 seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)),
                 seg(20.0, 0.0, 30.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)),
-                seg(30.0, 0.0, 50.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)),
+                seg(30.0, 0.0, 50.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)),
                 seg(50.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)),
                 seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
 
                 // left
                 seg(0.0, 20.0, 20.0, 30.0, false, Some(EdgeAaSegmentMask::LEFT)),
-                seg(0.0, 30.0, 20.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT)),
+                seg(0.0, 30.0, 20.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)),
                 seg(0.0, 50.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT)),
 
                 // right
                 seg(60.0, 20.0, 100.0, 30.0, false, Some(EdgeAaSegmentMask::RIGHT)),
-                seg(60.0, 30.0, 100.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT)),
+                seg(60.0, 30.0, 100.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)),
                 seg(60.0, 50.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT)),
 
                 // bottom row
                 seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
                 seg(20.0, 60.0, 30.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
-                seg(30.0, 60.0, 50.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
+                seg(30.0, 60.0, 50.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)),
                 seg(50.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
                 seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
 
                 // inner corners
-                seg(20.0, 20.0, 30.0, 30.0, true, None),
-                seg(20.0, 50.0, 30.0, 60.0, true, None),
-                seg(50.0, 20.0, 60.0, 30.0, true, None),
-                seg(50.0, 50.0, 60.0, 60.0, true, None),
+                seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
+                seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
+                seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
+                seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
             ],
         );
     }
 
     #[test]
     fn segment_clip_in_clip_out() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(-1000.0, -1000.0, 2000.0, 2000.0),
             &[
                 (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::Clip),
                 (rect(50.0, 50.0, 80.0, 80.0), None, ClipMode::ClipOut),
             ],
             &mut [
                 seg(20.0, 20.0, 50.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
-                seg(50.0, 20.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
-                seg(20.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
+                seg(50.0, 20.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
+                seg(20.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)),
             ],
         );
     }
 
     #[test]
     fn segment_rounded_clip_overlap() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(0.0, 0.0, 100.0, 100.0),
             &[
                 (rect(0.0, 0.0, 10.0, 10.0), None, ClipMode::ClipOut),
                 (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
             ],
             &mut [
                 // corners
                 seg(0.0, 90.0, 10.0, 100.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
                 seg(90.0, 0.0, 100.0, 10.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)),
                 seg(90.0, 90.0, 100.0, 100.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
 
                 // inner
                 seg(10.0, 10.0, 90.0, 90.0, false, None),
 
                 // edges
-                seg(10.0, 0.0, 90.0, 10.0, false, Some(EdgeAaSegmentMask::TOP)),
+                seg(10.0, 0.0, 90.0, 10.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)),
                 seg(10.0, 90.0, 90.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
-                seg(0.0, 10.0, 10.0, 90.0, false, Some(EdgeAaSegmentMask::LEFT)),
+                seg(0.0, 10.0, 10.0, 90.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
                 seg(90.0, 10.0, 100.0, 90.0, false, Some(EdgeAaSegmentMask::RIGHT)),
             ],
         );
     }
 
     #[test]
     fn segment_rounded_clip_overlap_reverse() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(0.0, 0.0, 100.0, 100.0),
             &[
                 (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip),
                 (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
             ],
             &mut [
                 seg(10.0, 10.0, 90.0, 90.0, false,
                     Some(EdgeAaSegmentMask::LEFT |
@@ -777,32 +893,260 @@ mod test {
             ],
         );
     }
 
     #[test]
     fn segment_clip_in_clip_out_overlap() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
             rect(0.0, 0.0, 100.0, 100.0),
             &[
                 (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip),
                 (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::ClipOut),
             ],
             &mut [
             ],
         );
     }
 
     #[test]
     fn segment_event_order() {
         seg_test(
             rect(0.0, 0.0, 100.0, 100.0),
+            None,
+            rect(0.0, 0.0, 100.0, 100.0),
+            &[
+                (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut),
+            ],
+            &mut [
+                seg(0.0, 90.0, 100.0, 100.0, false, Some(
+                    EdgeAaSegmentMask::LEFT |
+                    EdgeAaSegmentMask::RIGHT |
+                    EdgeAaSegmentMask::BOTTOM |
+                    EdgeAaSegmentMask::TOP
+                )),
+            ],
+        );
+    }
+
+    #[test]
+    fn segment_region_simple() {
+        seg_test(
+            rect(0.0, 0.0, 100.0, 100.0),
+            Some(rect(20.0, 40.0, 60.0, 80.0)),
+            rect(0.0, 0.0, 100.0, 100.0),
+            &[
+            ],
+            &mut [
+                seg_region(
+                    0.0, 0.0,
+                    20.0, 40.0,
+                    0, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)
+                ),
+
+                seg_region(
+                    20.0, 0.0,
+                    60.0, 40.0,
+                    1, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::TOP)
+                ),
+
+                seg_region(
+                    60.0, 0.0,
+                    100.0, 40.0,
+                    2, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)
+                ),
+
+                seg_region(
+                    0.0, 40.0,
+                    20.0, 80.0,
+                    0, 1,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT)
+                ),
+
+                seg_region(
+                    20.0, 40.0,
+                    60.0, 80.0,
+                    1, 1,
+                    false,
+                    None,
+                ),
+
+                seg_region(
+                    60.0, 40.0,
+                    100.0, 80.0,
+                    2, 1,
+                    false,
+                    Some(EdgeAaSegmentMask::RIGHT)
+                ),
+
+                seg_region(
+                    0.0, 80.0,
+                    20.0, 100.0,
+                    0, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)
+                ),
+
+                seg_region(
+                    20.0, 80.0,
+                    60.0, 100.0,
+                    1, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::BOTTOM),
+                ),
+
+                seg_region(
+                    60.0, 80.0,
+                    100.0, 100.0,
+                    2, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)
+                ),
+
+            ],
+        );
+    }
+
+    #[test]
+    fn segment_region_clip() {
+        seg_test(
+            rect(0.0, 0.0, 100.0, 100.0),
+            Some(rect(20.0, 40.0, 60.0, 80.0)),
             rect(0.0, 0.0, 100.0, 100.0),
             &[
                 (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut),
             ],
             &mut [
-                seg(0.0, 90.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
+                seg_region(
+                    0.0, 90.0,
+                    20.0, 100.0,
+                    0, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)
+                ),
+
+                seg_region(
+                    20.0, 90.0,
+                    60.0, 100.0,
+                    1, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP),
+                ),
+
+                seg_region(
+                    60.0, 90.0,
+                    100.0, 100.0,
+                    2, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)
+                ),
+
+            ],
+        );
+    }
+
+    #[test]
+    fn segment_region_clip2() {
+        seg_test(
+            rect(0.0, 0.0, 100.0, 100.0),
+            Some(rect(20.0, 20.0, 80.0, 80.0)),
+            rect(0.0, 0.0, 100.0, 100.0),
+            &[
+                (rect(20.0, 20.0, 100.0, 100.0), None, ClipMode::ClipOut),
+            ],
+            &mut [
+                seg_region(
+                    0.0, 0.0,
+                    20.0, 20.0,
+                    0, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)
+                ),
+
+                seg_region(
+                    20.0, 0.0,
+                    80.0, 20.0,
+                    1, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM),
+                ),
+
+                seg_region(
+                    80.0, 0.0,
+                    100.0, 20.0,
+                    2, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)
+                ),
+
+                seg_region(
+                    0.0, 20.0,
+                    20.0, 80.0,
+                    0, 1,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)
+                ),
+
+                seg_region(
+                    0.0, 80.0,
+                    20.0, 100.0,
+                    0, 2,
+                    false,
+                    Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)
+                ),
+            ],
+        );
+    }
+
+    #[test]
+    fn segment_region_clip3() {
+        seg_test(
+            rect(0.0, 0.0, 100.0, 100.0),
+            Some(rect(20.0, 20.0, 80.0, 80.0)),
+            rect(0.0, 0.0, 100.0, 100.0),
+            &[
+                (rect(10.0, 10.0, 30.0, 30.0), None, ClipMode::Clip),
+            ],
+            &mut [
+                seg_region(
+                    10.0, 10.0,
+                    20.0, 20.0,
+                    0, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT),
+                ),
+
+                seg_region(
+                    20.0, 10.0,
+                    30.0, 20.0,
+                    1, 0,
+                    false,
+                    Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT),
+                ),
+
+                seg_region(
+                    10.0, 20.0,
+                    20.0, 30.0,
+                    0, 1,
+                    false,
+                    Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT),
+                ),
+
+                seg_region(
+                    20.0, 20.0,
+                    30.0, 30.0,
+                    1, 1,
+                    false,
+                    Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT),
+                ),
             ],
         );
     }
 }
--- a/gfx/webrender/src/texture_allocator.rs
+++ b/gfx/webrender/src/texture_allocator.rs
@@ -17,16 +17,17 @@ const MINIMUM_LARGE_RECT_SIZE: u32 = 32;
 /// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See
 /// sections 2.2 and 2.2.5 in "A Thousand Ways to Pack the Bin - A Practical Approach to Two-
 /// Dimensional Rectangle Bin Packing":
 ///
 ///    http://clb.demon.fi/files/RectangleBinPack.pdf
 ///
 /// This approach was chosen because of its simplicity, good performance, and easy support for
 /// dynamic texture deallocation.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct GuillotineAllocator {
     texture_size: DeviceUintSize,
     free_list: FreeRectList,
     allocations: u32,
     dirty: bool,
 }
 
 impl GuillotineAllocator {
@@ -165,24 +166,25 @@ impl GuillotineAllocator {
         ));
         self.allocations = 0;
         self.dirty = false;
     }
 }
 
 /// A binning free list. Binning is important to avoid sifting through lots of small strips when
 /// allocating many texture items.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct FreeRectList {
     small: Vec<DeviceUintRect>,
     medium: Vec<DeviceUintRect>,
     large: Vec<DeviceUintRect>,
 }
 
 impl FreeRectList {
-    fn new() -> FreeRectList {
+    fn new() -> Self {
         FreeRectList {
             small: vec![],
             medium: vec![],
             large: vec![],
         }
     }
 
     fn push(&mut self, rect: &DeviceUintRect) {
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -4,17 +4,17 @@
 
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{ExternalImageType, ImageData, ImageFormat};
 use api::ImageDescriptor;
 use device::TextureFilter;
 use frame::FrameId;
 use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle};
-use internal_types::{CacheTextureId, TextureUpdateList, TextureUpdateSource};
+use internal_types::{CacheTextureId, FastHashMap, TextureUpdateList, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SourceTexture, TextureUpdate, TextureUpdateOp};
 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use resource_cache::CacheItem;
 use std::cmp;
 use std::mem;
 
 // The fixed number of layers for the shared texture cache.
 // There is one array texture per image format, allocated lazily.
@@ -24,66 +24,70 @@ const TEXTURE_ARRAY_LAYERS_NEAREST: usiz
 // The dimensions of each layer in the texture cache.
 const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
 
 // The size of each region (page) in a texture layer.
 const TEXTURE_REGION_DIMENSIONS: u32 = 512;
 
 // Maintains a simple freelist of texture IDs that are mapped
 // to real API-specific texture IDs in the renderer.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct CacheTextureIdList {
-    free_list: Vec<CacheTextureId>,
+    free_lists: FastHashMap<ImageFormat, Vec<CacheTextureId>>,
     next_id: usize,
 }
 
 impl CacheTextureIdList {
-    fn new() -> CacheTextureIdList {
+    fn new() -> Self {
         CacheTextureIdList {
             next_id: 0,
-            free_list: Vec::new(),
+            free_lists: FastHashMap::default(),
         }
     }
 
-    fn allocate(&mut self) -> CacheTextureId {
+    fn allocate(&mut self, format: ImageFormat) -> CacheTextureId {
         // If nothing on the free list of texture IDs,
         // allocate a new one.
-        match self.free_list.pop() {
-            Some(id) => id,
-            None => {
-                let id = CacheTextureId(self.next_id);
+        self.free_lists.get_mut(&format)
+            .and_then(|fl| fl.pop())
+            .unwrap_or_else(|| {
                 self.next_id += 1;
-                id
-            }
-        }
+                CacheTextureId(self.next_id - 1)
+            })
     }
 
-    fn free(&mut self, id: CacheTextureId) {
-        self.free_list.push(id);
+    fn free(&mut self, id: CacheTextureId, format: ImageFormat) {
+        self.free_lists
+            .entry(format)
+            .or_insert(Vec::new())
+            .push(id);
     }
 }
 
 // Items in the texture cache can either be standalone textures,
 // or a sub-rect inside the shared cache.
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 enum EntryKind {
     Standalone,
     Cache {
         // Origin within the texture layer where this item exists.
         origin: DeviceUintPoint,
         // The layer index of the texture array.
         layer_index: u16,
         // The region that this entry belongs to in the layer.
         region_index: u16,
     },
 }
 
 // Stores information related to a single entry in the texture
 // cache. This is stored for each item whether it's in the shared
 // cache or a standalone texture.
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct CacheEntry {
     // Size the requested item, in device pixels.
     size: DeviceUintSize,
     // Details specific to standalone or shared items.
     kind: EntryKind,
     // Arbitrary user data associated with this item.
     user_data: [f32; 3],
     // The last frame this item was requested for rendering.
@@ -148,26 +152,28 @@ type WeakCacheEntryHandle = WeakFreeList
 
 // A texture cache handle is a weak reference to a cache entry.
 // If the handle has not been inserted into the cache yet, the
 // value will be None. Even when the value is Some(), the location
 // may not actually be valid if it has been evicted by the cache.
 // In this case, the cache handle needs to re-upload this item
 // to the texture cache (see request() below).
 #[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Clone, Deserialize, Serialize))]
 pub struct TextureCacheHandle {
     entry: Option<WeakCacheEntryHandle>,
 }
 
 impl TextureCacheHandle {
-    pub fn new() -> TextureCacheHandle {
+    pub fn new() -> Self {
         TextureCacheHandle { entry: None }
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct TextureCache {
     // A lazily allocated, fixed size, texture array for
     // each format the texture cache supports.
     array_rgba8_nearest: TextureArray,
     array_a8_linear: TextureArray,
     array_rgba8_linear: TextureArray,
 
     // Maximum texture size supported by hardware.
@@ -176,16 +182,17 @@ pub struct TextureCache {
     // A list of texture IDs that represent native
     // texture handles. This indirection allows the texture
     // cache to create / destroy / reuse texture handles
     // without knowing anything about the device code.
     cache_textures: CacheTextureIdList,
 
     // A list of updates that need to be applied to the
     // texture cache in the rendering thread this frame.
+    #[cfg_attr(feature = "capture", serde(skip))]
     pending_updates: TextureUpdateList,
 
     // The current frame ID. Used for cache eviction policies.
     frame_id: FrameId,
 
     // Maintains the list of all current items in
     // the texture cache.
     entries: FreeList<CacheEntry>,
@@ -213,17 +220,17 @@ impl TextureCache {
             array_rgba8_linear: TextureArray::new(
                 ImageFormat::BGRA8,
                 TextureFilter::Linear,
                 TEXTURE_ARRAY_LAYERS_LINEAR,
             ),
             array_rgba8_nearest: TextureArray::new(
                 ImageFormat::BGRA8,
                 TextureFilter::Nearest,
-                TEXTURE_ARRAY_LAYERS_NEAREST
+                TEXTURE_ARRAY_LAYERS_NEAREST,
             ),
             cache_textures: CacheTextureIdList::new(),
             pending_updates: TextureUpdateList::new(),
             frame_id: FrameId(0),
             entries: FreeList::new(),
             standalone_entry_handles: Vec::new(),
             shared_entry_handles: Vec::new(),
         }
@@ -365,17 +372,16 @@ impl TextureCache {
         format: ImageFormat,
         filter: TextureFilter,
         region_index: u16
     ) -> &mut TextureRegion {
         let texture_array = match (format, filter) {
             (ImageFormat::R8, TextureFilter::Linear) => &mut self.array_a8_linear,
             (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear,
             (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
-            (ImageFormat::Invalid, _) |
             (ImageFormat::RGBAF32, _) |
             (ImageFormat::RG8, _) |
             (ImageFormat::R8, TextureFilter::Nearest) => unreachable!(),
         };
 
         &mut texture_array.regions[region_index as usize]
     }
 
@@ -543,17 +549,17 @@ impl TextureCache {
         match entry.kind {
             EntryKind::Standalone { .. } => {
                 // This is a standalone texture allocation. Just push it back onto the free
                 // list.
                 self.pending_updates.push(TextureUpdate {
                     id: entry.texture_id,
                     op: TextureUpdateOp::Free,
                 });
-                self.cache_textures.free(entry.texture_id);
+                self.cache_textures.free(entry.texture_id, entry.format);
                 None
             }
             EntryKind::Cache {
                 origin,
                 region_index,
                 ..
             } => {
                 // Free the block in the given region.
@@ -575,25 +581,24 @@ impl TextureCache {
         filter: TextureFilter,
         user_data: [f32; 3],
     ) -> Option<CacheEntry> {
         // Work out which cache it goes in, based on format.
         let texture_array = match (descriptor.format, filter) {
             (ImageFormat::R8, TextureFilter::Linear) => &mut self.array_a8_linear,
             (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear,
             (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest,
-            (ImageFormat::Invalid, _) |
             (ImageFormat::RGBAF32, _) |
             (ImageFormat::R8, TextureFilter::Nearest) |
             (ImageFormat::RG8, _) => unreachable!(),
         };
 
         // Lazy initialize this texture array if required.
         if texture_array.texture_id.is_none() {
-            let texture_id = self.cache_textures.allocate();
+            let texture_id = self.cache_textures.allocate(descriptor.format);
 
             let update_op = TextureUpdate {
                 id: texture_id,
                 op: TextureUpdateOp::Create {
                     width: TEXTURE_LAYER_DIMENSIONS,
                     height: TEXTURE_LAYER_DIMENSIONS,
                     format: descriptor.format,
                     filter: texture_array.filter,
@@ -677,17 +682,17 @@ impl TextureCache {
                 );
             }
         }
 
         // If not allowed in the cache, or if the shared cache is full, then it
         // will just have to be in a unique texture. This hurts batching but should
         // only occur on a small number of images (or pathological test cases!).
         if new_cache_entry.is_none() {
-            let texture_id = self.cache_textures.allocate();
+            let texture_id = self.cache_textures.allocate(descriptor.format);
 
             // Create an update operation to allocate device storage
             // of the right size / format.
             let update_op = TextureUpdate {
                 id: texture_id,
                 op: TextureUpdateOp::Create {
                     width: descriptor.width,
                     height: descriptor.height,
@@ -796,45 +801,41 @@ impl SlabSize {
             SlabSize::Size128x128 => 128,
             SlabSize::Size256x256 => 256,
             SlabSize::Size512x512 => 512,
         }
     }
 }
 
 // The x/y location within a texture region of an allocation.
-struct TextureLocation {
-    x: u8,
-    y: u8,
-}
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
+struct TextureLocation(u8, u8);
 
 impl TextureLocation {
-    fn new(x: u32, y: u32) -> TextureLocation {
-        debug_assert!(x < 256 && y < 256);
-        TextureLocation {
-            x: x as u8,
-            y: y as u8,
-        }
+    fn new(x: u32, y: u32) -> Self {
+        debug_assert!(x < 0x100 && y < 0x100);
+        TextureLocation(x as u8, y as u8)
     }
 }
 
 // A region is a sub-rect of a texture array layer.
 // All allocations within a region are of the same size.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct TextureRegion {
     layer_index: i32,
     region_size: u32,
     slab_size: u32,
     free_slots: Vec<TextureLocation>,
     slots_per_axis: u32,
     total_slot_count: usize,
     origin: DeviceUintPoint,
 }
 
 impl TextureRegion {
-    fn new(region_size: u32, layer_index: i32, origin: DeviceUintPoint) -> TextureRegion {
+    fn new(region_size: u32, layer_index: i32, origin: DeviceUintPoint) -> Self {
         TextureRegion {
             layer_index,
             region_size,
             slab_size: 0,
             free_slots: Vec::new(),
             slots_per_axis: 0,
             total_slot_count: 0,
             origin,
@@ -871,18 +872,18 @@ impl TextureRegion {
     fn is_empty(&self) -> bool {
         self.slab_size == 0
     }
 
     // Attempt to allocate a fixed size block from this region.
     fn alloc(&mut self) -> Option<DeviceUintPoint> {
         self.free_slots.pop().map(|location| {
             DeviceUintPoint::new(
-                self.origin.x + self.slab_size * location.x as u32,
-                self.origin.y + self.slab_size * location.y as u32,
+                self.origin.x + self.slab_size * location.0 as u32,
+                self.origin.y + self.slab_size * location.1 as u32,
             )
         })
     }
 
     // Free a block in this region.
     fn free(&mut self, point: DeviceUintPoint) {
         let x = (point.x - self.origin.x) / self.slab_size;
         let y = (point.y - self.origin.y) / self.slab_size;
@@ -895,31 +896,32 @@ impl TextureRegion {
             self.deinit();
         }
     }
 }
 
 // A texture array contains a number of texture layers, where
 // each layer contains one or more regions that can act
 // as slab allocators.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct TextureArray {
     filter: TextureFilter,
     layer_count: usize,
     format: ImageFormat,
     is_allocated: bool,
     regions: Vec<TextureRegion>,
     texture_id: Option<CacheTextureId>,
 }
 
 impl TextureArray {
     fn new(
         format: ImageFormat,
         filter: TextureFilter,
         layer_count: usize
-    ) -> TextureArray {
+    ) -> Self {
         TextureArray {
             format,
             filter,
             layer_count,
             is_allocated: false,
             regions: Vec::new(),
             texture_id: None,
         }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -30,27 +30,29 @@ const MIN_TARGET_SIZE: u32 = 2048;
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub clip_id: ClipId,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayerRect,
 }
 
 #[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
     pub node_data: &'a [ClipScrollNodeData],
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub use_dual_source_blending: bool,
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
     // cache allocator requires.
     allocator: GuillotineAllocator,
 
     // Track the used rect of the render target, so that
@@ -106,26 +108,29 @@ pub trait RenderTarget {
         render_tasks: &RenderTaskTree,
         clip_store: &ClipStore,
     );
     fn used_rect(&self) -> DeviceIntRect;
     fn needs_depth(&self) -> bool;
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum RenderTargetKind {
     Color, // RGBA32
     Alpha, // R8
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderTargetList<T> {
     screen_size: DeviceIntSize,
     pub format: ImageFormat,
     pub max_size: DeviceUintSize,
     pub targets: Vec<T>,
+    #[cfg_attr(feature = "capture", serde(skip))]
     pub texture: Option<Texture>,
 }
 
 impl<T: RenderTarget> RenderTargetList<T> {
     fn new(
         screen_size: DeviceIntSize,
         format: ImageFormat,
     ) -> Self {
@@ -211,27 +216,30 @@ impl<T: RenderTarget> RenderTargetList<T
         }
     }
 }
 
 /// Frame output information for a given pipeline ID.
 /// Storing the task ID allows the renderer to find
 /// the target rect within the render target that this
 /// pipeline exists at.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct FrameOutput {
     pub task_id: RenderTaskId,
     pub pipeline_id: PipelineId,
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct ScalingInfo {
     pub src_task_id: RenderTaskId,
     pub dest_task_id: RenderTaskId,
 }
 
 /// A render target represents a number of rendering operations on a surface.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct ColorRenderTarget {
     pub alpha_batcher: AlphaBatcher,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInfo>,
     // List of frame buffer outputs for this render target.
@@ -356,16 +364,17 @@ impl RenderTarget for ColorRenderTarget 
             .used_rect
     }
 
     fn needs_depth(&self) -> bool {
         !self.alpha_batcher.batch_list.opaque_batch_list.batches.is_empty()
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
     pub brush_mask_corners: Vec<PrimitiveInstance>,
     pub brush_mask_rounded_rects: Vec<PrimitiveInstance>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub scalings: Vec<ScalingInfo>,
@@ -471,16 +480,17 @@ impl RenderTarget for AlphaRenderTarget 
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             user_data0: 0,
                                             user_data1: 0,
                                         };
                                         let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
                                         let batch = match brush.kind {
                                             BrushKind::Solid { .. } |
                                             BrushKind::Clear |
+                                            BrushKind::Picture |
                                             BrushKind::Line { .. } => {
                                                 unreachable!("bug: unexpected brush here");
                                             }
                                             BrushKind::Mask { ref kind, .. } => {
                                                 match *kind {
                                                     BrushMaskKind::Corner(..) => &mut self.brush_mask_corners,
                                                     BrushMaskKind::RoundedRect(..) => &mut self.brush_mask_rounded_rects,
                                                 }
@@ -525,16 +535,17 @@ impl RenderTarget for AlphaRenderTarget 
         self.allocator.used_rect
     }
 
     fn needs_depth(&self) -> bool {
         false
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct TextureCacheRenderTarget {
     pub horizontal_blurs: Vec<BlurInstance>,
 }
 
 impl TextureCacheRenderTarget {
     fn new(
         _size: Option<DeviceUintSize>,
         _screen_size: DeviceIntSize,
@@ -567,30 +578,32 @@ impl TextureCacheRenderTarget {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum RenderPassKind {
     MainFramebuffer(ColorRenderTarget),
     OffScreen {
         alpha: RenderTargetList<AlphaRenderTarget>,
         color: RenderTargetList<ColorRenderTarget>,
         texture_cache: FastHashMap<(SourceTexture, i32), TextureCacheRenderTarget>,
     },
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
 /// A render pass can have several render targets if there wasn't enough space in one
 /// target to do all of the rendering for that pass.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct RenderPass {
     pub kind: RenderPassKind,
     tasks: Vec<RenderTaskId>,
 }
 
 impl RenderPass {
     pub fn new_main_framebuffer(screen_size: DeviceIntSize) -> Self {
         let target = ColorRenderTarget::new(None, screen_size);
@@ -726,38 +739,58 @@ impl CompositeOps {
 
     pub fn count(&self) -> usize {
         self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
     }
 }
 
 /// A rendering-oriented representation of frame::Frame built by the render backend
 /// and presented to the renderer.
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub struct Frame {
     pub window_size: DeviceUintSize,
     pub inner_rect: DeviceUintRect,
     pub background_color: Option<ColorF>,
     pub layer: DocumentLayer,
     pub device_pixel_ratio: f32,
     pub passes: Vec<RenderPass>,
+    #[cfg_attr(feature = "capture", serde(default = "FrameProfileCounters::new", skip))]
     pub profile_counters: FrameProfileCounters,
 
     pub node_data: Vec<ClipScrollNodeData>,
     pub clip_chain_local_clip_rects: Vec<LayerRect>,
     pub render_tasks: RenderTaskTree,
 
     // List of updates that need to be pushed to the
     // gpu resource cache.
+    #[cfg_attr(feature = "capture", serde(skip))]
     pub gpu_cache_updates: Option<GpuCacheUpdateList>,
 
     // List of textures that we don't know about yet
     // from the backend thread. The render thread
     // will use a callback to resolve these and
     // patch the data structures.
+    #[cfg_attr(feature = "capture", serde(skip))]
     pub deferred_resolves: Vec<DeferredResolve>,
+
+    // True if this frame contains any render tasks
+    // that write to the texture cache.
+    pub has_texture_cache_tasks: bool,
+
+    // True if this frame has been drawn by the
+    // renderer.
+    pub has_been_rendered: bool,
+}
+
+impl Frame {
+    // This frame must be flushed if it writes to the
+    // texture cache, and hasn't been drawn yet.
+    pub fn must_be_drawn(&self) -> bool {
+        self.has_texture_cache_tasks && !self.has_been_rendered
+    }
 }
 
 impl BlurTask {
     fn add_instances(
         &self,
         instances: &mut Vec<BlurInstance>,
         task_id: RenderTaskId,
         source_task_id: RenderTaskId,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -209,18 +209,19 @@ pub fn _subtract_rect<U>(
             }
         }
         None => {
             results.push(*rect);
         }
     }
 }
 
+#[repr(u32)]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[repr(u32)]
+#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))]
 pub enum TransformedRectKind {
     AxisAligned = 0,
     Complex = 1,
 }
 
 #[inline(always)]
 pub fn pack_as_float(value: u32) -> f32 {
     value as f32 + 0.5
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -95,17 +95,17 @@ const SHADERS: &[Shader] = &[
         name: "brush_mask",
         features: &[],
     },
     Shader {
         name: "brush_solid",
         features: &[],
     },
     Shader {
-        name: "brush_image",
+        name: "brush_picture",
         features: &["COLOR_TARGET", "ALPHA_TARGET"],
     },
     Shader {
         name: "brush_line",
         features: &[],
     },
 ];
 
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -188,18 +188,16 @@ impl Transaction {
     /// * `background`: The background color of this pipeline.
     /// * `viewport_size`: The size of the viewport for this frame.
     /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
     /// * `content_size`: The total screen space size of this display list's display items.
     /// * `display_list`: The root Display list used in this frame.
     /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline
     ///                           id, this setting determines if frame state (such as scrolling
     ///                           position) should be preserved for this new display list.
-    /// * `resources`: A set of resource updates that must be applied at the same time as the
-    ///                display list.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn set_display_list(
         &mut self,
         epoch: Epoch,
         background: Option<ColorF>,
         viewport_size: LayoutSize,
         (pipeline_id, content_size, display_list): (PipelineId, LayoutSize, BuiltDisplayList),
@@ -210,17 +208,16 @@ impl Transaction {
             DocumentMsg::SetDisplayList {
                 epoch,
                 pipeline_id,
                 background,
                 viewport_size,
                 content_size,
                 list_descriptor,
                 preserve_frame_state,
-                resources: ResourceUpdates::new(),
             }
         );
         self.payloads.push(Payload { epoch, pipeline_id, display_list_data });
     }
 
     pub fn update_resources(&mut self, resources: ResourceUpdates) {
         self.ops.push(DocumentMsg::UpdateResources(resources));
     }
@@ -283,16 +280,22 @@ impl Transaction {
         self.ops.push(DocumentMsg::GenerateFrame);
     }
 
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
     pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
         self.ops.push(DocumentMsg::UpdateDynamicProperties(properties));
     }
+
+    /// Enable copying of the output of this pipeline id to
+    /// an external texture for callers to consume.
+    pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) {
+        self.ops.push(DocumentMsg::EnableFrameOutput(pipeline_id, enable));
+    }
 }
 
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddImage {
     pub key: ImageKey,
     pub descriptor: ImageDescriptor,
     pub data: ImageData,
@@ -360,25 +363,18 @@ pub enum DocumentMsg {
     SetDisplayList {
         list_descriptor: BuiltDisplayListDescriptor,
         epoch: Epoch,
         pipeline_id: PipelineId,
         background: Option<ColorF>,
         viewport_size: LayoutSize,
         content_size: LayoutSize,
         preserve_frame_state: bool,
-        resources: ResourceUpdates,
     },
     UpdateResources(ResourceUpdates),
-    // TODO(nical): Remove this once gecko doesn't use it anymore.
-    UpdatePipelineResources {
-        resources: ResourceUpdates,
-        pipeline_id: PipelineId,
-        epoch: Epoch,
-    },
     UpdateEpoch(PipelineId, Epoch),
     SetPageZoom(ZoomFactor),
     SetPinchZoom(ZoomFactor),
     SetPan(DeviceIntPoint),
     SetRootPipeline(PipelineId),
     RemovePipeline(PipelineId),
     EnableFrameOutput(PipelineId, bool),
     SetWindowParameters {
@@ -393,17 +389,16 @@ pub enum DocumentMsg {
     GenerateFrame,
     UpdateDynamicProperties(DynamicProperties),
 }
 
 impl fmt::Debug for DocumentMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList",
-            DocumentMsg::UpdatePipelineResources { .. } => "DocumentMsg::UpdatePipelineResources",
             DocumentMsg::HitTest(..) => "DocumentMsg::HitTest",
             DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
             DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
             DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
             DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
             DocumentMsg::RemovePipeline(..) => "DocumentMsg::RemovePipeline",
             DocumentMsg::SetWindowParameters { .. } => "DocumentMsg::SetWindowParameters",
             DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
@@ -414,17 +409,35 @@ impl fmt::Debug for DocumentMsg {
             DocumentMsg::EnableFrameOutput(..) => "DocumentMsg::EnableFrameOutput",
             DocumentMsg::UpdateResources(..) => "DocumentMsg::UpdateResources",
             DocumentMsg::UpdateEpoch(..) => "DocumentMsg::UpdateEpoch",
             DocumentMsg::UpdateDynamicProperties(..) => "DocumentMsg::UpdateDynamicProperties",
         })
     }
 }
 
-#[derive(Debug, Clone, Deserialize, Serialize)]
+bitflags!{
+    /// Bit flags for WR stages to store in a capture.
+    // Note: capturing `FRAME` without `SCENE` is not currently supported.
+    #[derive(Deserialize, Serialize)]
+    pub struct CaptureBits: u8 {
+        const SCENE = 0x1;
+        const FRAME = 0x2;
+    }
+}
+
+/// 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,
+    pub root_pipeline_id: Option<PipelineId>,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
 pub enum DebugCommand {
     /// Display the frame profiler on screen.
     EnableProfiler(bool),
     /// Display all texture cache pages on screen.
     EnableTextureCacheDebug(bool),
     /// Display intermediate render targets on screen.
     EnableRenderTargetDebug(bool),
     /// Display alpha primitive rects.
@@ -439,19 +452,19 @@ pub enum DebugCommand {
     FetchPasses,
     /// Fetch clip-scroll tree.
     FetchClipScrollTree,
     /// Fetch render tasks.
     FetchRenderTasks,
     /// Fetch screenshot.
     FetchScreenshot,
     /// Save a capture of all the documents state.
-    SaveCapture(PathBuf),
+    SaveCapture(PathBuf, CaptureBits),
     /// Load a capture of all the documents state.
-    LoadCapture(PathBuf),
+    LoadCapture(PathBuf, MsgSender<CapturedDocument>),
     /// Configure if dual-source blending is used, if available.
     EnableDualSourceBlending(bool),
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     /// Add/remove/update images and fonts.
     UpdateResources(ResourceUpdates),
@@ -669,34 +682,16 @@ impl RenderApi {
         if resources.updates.is_empty() {
             return;
         }
         self.api_sender
             .send(ApiMsg::UpdateResources(resources))
             .unwrap();
     }
 
-    /// Add/remove/update resources such as images and fonts.
-    ///
-    /// This is similar to update_resources with the addition that it allows updating
-    /// a pipeline's epoch.
-    pub fn update_pipeline_resources(
-        &self,
-        resources: ResourceUpdates,
-        document_id: DocumentId,
-        pipeline_id: PipelineId,
-        epoch: Epoch,
-    ) {
-        self.send(document_id, DocumentMsg::UpdatePipelineResources {
-            resources,
-            pipeline_id,
-            epoch,
-        });
-    }
-
     pub fn send_external_event(&self, evt: ExternalEvent) {
         let msg = ApiMsg::ExternalEvent(evt);
         self.api_sender.send(msg).unwrap();
     }
 
     pub fn notify_memory_pressure(&self) {
         self.api_sender.send(ApiMsg::MemoryPressure).unwrap();
     }
@@ -749,141 +744,23 @@ impl RenderApi {
             .unwrap()
     }
 
     // TODO(nical) - decide what to do with the methods that are duplicated in Transaction.
     // I think that we should remove them from RenderApi but we could also leave them here if
     // it makes things easier for servo.
     // They are all equivalent to creating a transaction with a single command.
 
-    /// Sets the root pipeline.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use webrender_api::{DeviceUintSize, PipelineId, RenderApiSender};
-    /// # fn example(sender: RenderApiSender) {
-    /// let api = sender.create_api();
-    /// let document_id = api.add_document(DeviceUintSize::zero(), 0);
-    /// let pipeline_id = PipelineId(0, 0);
-    /// api.set_root_pipeline(document_id, pipeline_id);
-    /// # }
-    /// ```
-    pub fn set_root_pipeline(&self, document_id: DocumentId, pipeline_id: PipelineId) {
-        self.send(document_id, DocumentMsg::SetRootPipeline(pipeline_id));
-    }
-
-    /// Removes data associated with a pipeline from the internal data structures.
-    /// If the specified `pipeline_id` is for the root pipeline, the root pipeline
-    /// is reset back to `None`.
-    pub fn remove_pipeline(&self, document_id: DocumentId, pipeline_id: PipelineId) {
-        self.send(document_id, DocumentMsg::RemovePipeline(pipeline_id));
-    }
-
-    /// Supplies a new frame to WebRender.
-    ///
-    /// Non-blocking, it notifies a worker process which processes the display list.
-    /// When it's done and a `RenderNotifier` has been set in `webrender::Renderer`,
-    /// [new_document_ready()][notifier] gets called.
-    ///
-    /// Note: Scrolling doesn't require an own Frame.
-    ///
-    /// Arguments:
-    ///
-    /// * `document_id`: Target Document ID.
-    /// * `epoch`: The unique Frame ID, monotonically increasing.
-    /// * `background`: The background color of this pipeline.
-    /// * `viewport_size`: The size of the viewport for this frame.
-    /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
-    /// * `content_size`: The total screen space size of this display list's display items.
-    /// * `display_list`: The root Display list used in this frame.
-    /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline
-    ///                           id, this setting determines if frame state (such as scrolling
-    ///                           position) should be preserved for this new display list.
-    /// * `resources`: A set of resource updates that must be applied at the same time as the
-    ///                display list.
-    ///
-    /// [notifier]: trait.RenderNotifier.html#tymethod.new_document_ready
-    pub fn set_display_list(
-        &self,
-        document_id: DocumentId,
-        epoch: Epoch,
-        background: Option<ColorF>,
-        viewport_size: LayoutSize,
-        (pipeline_id, content_size, display_list): (PipelineId, LayoutSize, BuiltDisplayList),
-        preserve_frame_state: bool,
-        resources: ResourceUpdates, // TODO: this will be removed soon.
-    ) {
-        // TODO(nical) set_display_list uses the epoch to match the displaylist and the payload
-        // coming from different channels when receiving in the render backend.
-        // It would be cleaner to use a separate id that is implicitly generated for the displaylist-payload
-        // matching so that the semantics of epochs is really up to the api user and so that the latter can't
-        // introduce bugs by accidently using the same epoch twice.
-
-        let (display_list_data, list_descriptor) = display_list.into_data();
-        self.send(
-            document_id,
-            DocumentMsg::SetDisplayList {
-                epoch,
-                pipeline_id,
-                background,
-                viewport_size,
-                content_size,
-                list_descriptor,
-                preserve_frame_state,
-                resources,
-            },
-        );
-
-        self.payload_sender
-            .send_payload(Payload {
-                epoch,
-                pipeline_id,
-                display_list_data,
-            })
-            .unwrap();
-    }
-
-    pub fn send_transaction(&mut self, document_id: DocumentId, transaction: Transaction) {
+    pub fn send_transaction(&self, document_id: DocumentId, transaction: Transaction) {
         for payload in transaction.payloads {
             self.payload_sender.send_payload(payload).unwrap();
         }
         self.api_sender.send(ApiMsg::UpdateDocument(document_id, transaction.ops)).unwrap();
     }
 
-    /// Scrolls the scrolling layer under the `cursor`
-    ///
-    /// WebRender looks for the layer closest to the user
-    /// which has `ScrollPolicy::Scrollable` set.
-    pub fn scroll(
-        &self,
-        document_id: DocumentId,
-        scroll_location: ScrollLocation,
-        cursor: WorldPoint,
-        phase: ScrollEventPhase,
-    ) {
-        self.send(
-            document_id,
-            DocumentMsg::Scroll(scroll_location, cursor, phase),
-        );
-    }
-
-    pub fn scroll_node_with_id(
-        &self,
-        document_id: DocumentId,
-        origin: LayoutPoint,
-        id: ClipId,
-        clamp: ScrollClamping,
-    ) {
-        self.send(
-            document_id,
-            DocumentMsg::ScrollNodeWithId(origin, id, clamp),
-        );
-    }
-
     /// Does a hit test on display items in the specified document, at the given
     /// point. If a pipeline_id is specified, it is used to further restrict the
     /// hit results so that only items inside that pipeline are matched. If the
     /// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit
     /// results will contain all display items that match, ordered from front
     /// to back.
     pub fn hit_test(&self,
                     document_id: DocumentId,
@@ -891,28 +768,16 @@ impl RenderApi {
                     point: WorldPoint,
                     flags: HitTestFlags)
                     -> HitTestResult {
         let (tx, rx) = channel::msg_channel().unwrap();
         self.send(document_id, DocumentMsg::HitTest(pipeline_id, point, flags, tx));
         rx.recv().unwrap()
     }
 
-    pub fn set_page_zoom(&self, document_id: DocumentId, page_zoom: ZoomFactor) {
-        self.send(document_id, DocumentMsg::SetPageZoom(page_zoom));
-    }
-
-    pub fn set_pinch_zoom(&self, document_id: DocumentId, pinch_zoom: ZoomFactor) {
-        self.send(document_id, DocumentMsg::SetPinchZoom(pinch_zoom));
-    }
-
-    pub fn set_pan(&self, document_id: DocumentId, pan: DeviceIntPoint) {
-        self.send(document_id, DocumentMsg::SetPan(pan));
-    }
-
     pub fn set_window_parameters(
         &self,
         document_id: DocumentId,
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
         device_pixel_ratio: f32,
     ) {
         self.send(
@@ -920,64 +785,39 @@ impl RenderApi {
             DocumentMsg::SetWindowParameters {
                 window_size,
                 inner_rect,
                 device_pixel_ratio,
             },
         );
     }
 
-    pub fn tick_scrolling_bounce_animations(&self, document_id: DocumentId) {
-        self.send(document_id, DocumentMsg::TickScrollingBounce);
-    }
-
     pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollLayerState> {
         let (tx, rx) = channel::msg_channel().unwrap();
         self.send(document_id, DocumentMsg::GetScrollNodeState(tx));
         rx.recv().unwrap()
     }
 
-    /// Enable copying of the output of this pipeline id to
-    /// an external texture for callers to consume.
-    pub fn enable_frame_output(
-        &self,
-        document_id: DocumentId,
-        pipeline_id: PipelineId,
-        enable: bool,
-    ) {
-        self.send(
-            document_id,
-            DocumentMsg::EnableFrameOutput(pipeline_id, enable),
-        );
-    }
-
-    /// Generate a new frame. Optionally, supply a list of animated
-    /// property bindings that should be used to resolve bindings
-    /// in the current display list.
-    pub fn generate_frame(
-        &self,
-        document_id: DocumentId,
-        property_bindings: Option<DynamicProperties>,
-    ) {
-        if let Some(properties) = property_bindings {
-            self.send(document_id, DocumentMsg::UpdateDynamicProperties(properties));
-        }
-        self.send(document_id, DocumentMsg::GenerateFrame);
-    }
-
     /// Save a capture of the current frame state for debugging.
-    pub fn save_capture(&self, path: PathBuf) {
-        let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path));
+    pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) {
+        let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits));
         self.send_message(msg);
     }
 
     /// Load a capture of the current frame state for debugging.
-    pub fn load_capture(&self, path: PathBuf) {
-        let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path));
+    pub fn load_capture(&self, path: PathBuf) -> Vec<CapturedDocument> {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path, tx));
         self.send_message(msg);
+
+        let mut documents = Vec::new();
+        while let Ok(captured_doc) = rx.recv() {
+            documents.push(captured_doc);
+        }
+        documents
     }
 
     pub fn send_debug_cmd(&self, cmd: DebugCommand) {
         let msg = ApiMsg::DebugCommand(cmd);
         self.send_message(msg);
     }
 }
 
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -486,70 +486,59 @@ impl Serialize for BuiltDisplayList {
 impl<'de> Deserialize<'de> for BuiltDisplayList {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     where
         D: Deserializer<'de>,
     {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::{CompletelySpecificDisplayItem, GenericDisplayItem};
 
-        // Push a vector of things into the DL according to the
-        // convention used by `skip_iter` and `push_iter`
-        fn push_vec<T: Clone + Serialize>(data: &mut Vec<u8>, vec: Vec<T>) {
-            let vec_len = vec.len();
-            let byte_size = mem::size_of::<T>() * vec_len;
-            serialize_fast(data, &byte_size);
-            serialize_fast(data, &vec_len);
-            let count = serialize_iter_fast(data, vec.into_iter());
-            assert_eq!(count, vec_len);
-        }
-
         let list = Vec::<GenericDisplayItem<CompletelySpecificDisplayItem>>
             ::deserialize(deserializer)?;
 
         let mut data = Vec::new();
         let mut temp = Vec::new();
         for complete in list {
             let item = DisplayItem {
                 item: match complete.item {
                     Clip(specific_item, complex_clips) => {
-                        push_vec(&mut temp, complex_clips);
+                        DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
                         SpecificDisplayItem::Clip(specific_item)
                     },
                     ClipChain(specific_item, clip_chain_ids) => {
-                        push_vec(&mut temp, clip_chain_ids);
+                        DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids);
                         SpecificDisplayItem::ClipChain(specific_item)
                     }
                     ScrollFrame(specific_item, complex_clips) => {
-                        push_vec(&mut temp, complex_clips);
+                        DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
                         SpecificDisplayItem::ScrollFrame(specific_item)
                     },
                     StickyFrame(specific_item) => SpecificDisplayItem::StickyFrame(specific_item),
                     Rectangle(specific_item) => SpecificDisplayItem::Rectangle(specific_item),
                     ClearRectangle => SpecificDisplayItem::ClearRectangle,
                     Line(specific_item) => SpecificDisplayItem::Line(specific_item),
                     Text(specific_item, glyphs) => {
-                        push_vec(&mut temp, glyphs);
+                        DisplayListBuilder::push_iter_impl(&mut temp, glyphs);
                         SpecificDisplayItem::Text(specific_item)
                     },
                     Image(specific_item) => SpecificDisplayItem::Image(specific_item),
                     YuvImage(specific_item) => SpecificDisplayItem::YuvImage(specific_item),
                     Border(specific_item) => SpecificDisplayItem::Border(specific_item),
                     BoxShadow(specific_item) => SpecificDisplayItem::BoxShadow(specific_item),
                     Gradient(specific_item) => SpecificDisplayItem::Gradient(specific_item),
                     RadialGradient(specific_item) =>
                         SpecificDisplayItem::RadialGradient(specific_item),
                     Iframe(specific_item) => SpecificDisplayItem::Iframe(specific_item),
                     PushStackingContext(specific_item, filters) => {
-                        push_vec(&mut temp, filters);
+                        DisplayListBuilder::push_iter_impl(&mut temp, filters);
                         SpecificDisplayItem::PushStackingContext(specific_item)
                     },
                     PopStackingContext => SpecificDisplayItem::PopStackingContext,
                     SetGradientStops(stops) => {
-                        push_vec(&mut temp, stops);
+                        DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         SpecificDisplayItem::SetGradientStops
                     },
                     PushShadow(specific_item) => SpecificDisplayItem::PushShadow(specific_item),
                     PopAllShadows => SpecificDisplayItem::PopAllShadows,
                 },
                 clip_and_scroll: complete.clip_and_scroll,
                 info: complete.info,
             };
@@ -922,46 +911,56 @@ impl DisplayListBuilder {
             &DisplayItem {
                 item,
                 clip_and_scroll: *self.clip_stack.last().unwrap(),
                 info,
             }
         )
     }
 
+    fn push_iter_impl<I>(data: &mut Vec<u8>, iter_source: I)
+    where
+        I: IntoIterator,
+        I::IntoIter: ExactSizeIterator + Clone,
+        I::Item: Serialize,
+    {
+        let iter = iter_source.into_iter();
+        let len = iter.len();
+        // Format:
+        // payload_byte_size: usize, item_count: usize, [I; item_count]
+
+        // We write a dummy value so there's room for later
+        let byte_size_offset = data.len();
+        serialize_fast(data, &0usize);
+        serialize_fast(data, &len);
+        let payload_offset = data.len();
+
+        let count = serialize_iter_fast(data, iter);
+
+        // 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,
         I::IntoIter: ExactSizeIterator + Clone,
         I::Item: Serialize,
     {
-        let iter = iter.into_iter();
-        let len = iter.len();
-
-        // Format:
-        // payload_byte_size: usize, item_count: usize, [I; item_count]
-
-        // We write a dummy value so there's room for later
-        let byte_size_offset = self.data.len();
-        serialize_fast(&mut self.data, &0usize);
-        serialize_fast(&mut self.data, &len);
-        let payload_offset = self.data.len();
-
-        let count = serialize_iter_fast(&mut self.data, iter.into_iter());
-
-        // Now write the actual byte_size
-        let final_offset = self.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 self.data[byte_size_offset..],
-                                &byte_size,
-                                bincode::Infinite).unwrap();
-
-        debug_assert_eq!(len, count);
+        Self::push_iter_impl(&mut self.data, iter);
     }
 
     pub fn push_rect(&mut self, info: &LayoutPrimitiveInfo, color: ColorF) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem { color });
         self.push_item(item, info);
     }
 
     pub fn push_clear_rect(&mut self, info: &LayoutPrimitiveInfo) {
@@ -1249,27 +1248,27 @@ impl DisplayListBuilder {
             border_radius,
             clip_mode,
         });
 
         self.push_item(item, info);
     }
 
     /// Pushes a linear gradient to be displayed.
-    /// 
+    ///
     /// The gradient itself is described in the
     /// `gradient` parameter. It is drawn on
     /// a "tile" with the dimensions from `tile_size`.
     /// These tiles are now repeated to the right and
     /// to the bottom infinitly. If `tile_spacing`
     /// is not zero spacers with the given dimensions
     /// are inserted between the tiles as seams.
-    /// 
+    ///
     /// The origin of the tiles is given in `info.rect.origin`.
-    /// If the gradient should only be displayed once limit 
+    /// If the gradient should only be displayed once limit
     /// the `info.rect.size` to a single tile.
     /// The gradient is only visible within the local clip.
     pub fn push_gradient(
         &mut self,
         info: &LayoutPrimitiveInfo,
         gradient: Gradient,
         tile_size: LayoutSize,
         tile_spacing: LayoutSize,
@@ -1279,17 +1278,17 @@ impl DisplayListBuilder {
             tile_size,
             tile_spacing,
         });
 
         self.push_item(item, info);
     }
 
     /// Pushes a radial gradient to be displayed.
-    /// 
+    ///
     /// See [`push_gradient`](#method.push_gradient) for explanation.
     pub fn push_radial_gradient(
         &mut self,
         info: &LayoutPrimitiveInfo,
         gradient: RadialGradient,
         tile_size: LayoutSize,
         tile_spacing: LayoutSize,
     ) {
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -45,31 +45,29 @@ pub struct ExternalImageData {
     pub id: ExternalImageId,
     pub channel_index: u8,
     pub image_type: ExternalImageType,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ImageFormat {
-    Invalid = 0,
     R8 = 1,
     BGRA8 = 3,
     RGBAF32 = 4,
     RG8 = 5,
 }
 
 impl ImageFormat {
     pub fn bytes_per_pixel(self) -> u32 {
         match self {
             ImageFormat::R8 => 1,
             ImageFormat::BGRA8 => 4,
             ImageFormat::RGBAF32 => 16,
             ImageFormat::RG8 => 2,
-            ImageFormat::Invalid => 0,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDescriptor {
     pub format: ImageFormat,
     pub width: u32,
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -4,17 +4,17 @@ version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
 rayon = "0.8"
 thread_profiler = "0.1.1"
 euclid = "0.16"
 app_units = "0.6"
-gleam = "0.4.15"
+gleam = "0.4.19"
 log = "0.3"
 
 [dependencies.webrender]
 path = "../webrender"
 version = "0.56.1"
 default-features = false
 
 [target.'cfg(target_os = "windows")'.dependencies]