Bug 1398241 - Update webrender to commit 7d9444a24fb98bcc41afdca2a5bf145d514500f1. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 14 Sep 2017 08:14:53 -0400
changeset 430383 3f7072c64b51c52081c3a2782a4e84abf1127456
parent 430382 62517cf482ae592f7d71afb6d2819c7d7801615c
child 430384 091818dd34247eaaa318ee8dc0ab9101a3270606
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1398241
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1398241 - Update webrender to commit 7d9444a24fb98bcc41afdca2a5bf145d514500f1. r=jrmuizel MozReview-Commit-ID: 7faAn2asOOw
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/frame_output.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/nested_display_list.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_blur.glsl
gfx/webrender/res/cs_clip_border.vs.glsl
gfx/webrender/res/cs_clip_image.vs.glsl
gfx/webrender/res/cs_clip_rectangle.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_split_composite.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/src/border.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/debug_server.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/gpu_types.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.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/tiling.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/units.rs
gfx/webrender_bindings/Cargo.toml
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 6cf9cd4075efdf7467bad71b372170f626a8fce4
+Latest Commit: 7d9444a24fb98bcc41afdca2a5bf145d514500f1
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -8,21 +8,21 @@ build = "build.rs"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
 debugger = ["ws", "serde_json", "serde", "serde_derive"]
 
 [dependencies]
-app_units = "0.5"
+app_units = "0.5.6"
 bincode = "0.8"
 bit-set = "0.4"
 byteorder = "1.0"
-euclid = "0.15.1"
+euclid = "0.15.2"
 fxhash = "0.2.1"
 gleam = "0.4.8"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "0.8"
 webrender_api = {path = "../webrender_api"}
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -26,26 +26,32 @@ impl Example for App {
               _resources: &mut ResourceUpdates,
               _layout_size: LayoutSize,
               _pipeline_id: PipelineId,
               _document_id: DocumentId) {
         // Create a 100x100 stacking context with an animatable transform property.
         // Note the magic "42" we use as the animation key. That is used to update
         // the transform in the keyboard event handler code.
         let bounds = (0,0).to(100, 100);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         // Fill it with a white rect
-        builder.push_rect(bounds, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self,
                 event: glutin::Event,
                 api: &RenderApi,
                 document_id: DocumentId) -> bool {
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -180,18 +180,23 @@ impl Example for App {
     fn render(&mut self,
               api: &RenderApi,
               builder: &mut DisplayListBuilder,
               resources: &mut ResourceUpdates,
               layout_size: LayoutSize,
               _pipeline_id: PipelineId,
               _document_id: DocumentId) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         let image_mask_key = api.generate_image_key();
         resources.add_image(
@@ -204,21 +209,29 @@ impl Example for App {
             image: image_mask_key,
             rect: (75, 75).by(100, 100),
             repeat: false,
         };
         let complex = ComplexClipRegion::new((50, 50).to(150, 150), BorderRadius::uniform(20.0));
         let id = builder.define_clip(None, bounds, vec![complex], Some(mask));
         builder.push_clip_id(id);
 
-        let bounds = (100, 100).to(200, 200);
-        builder.push_rect(bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        let info = LayoutPrimitiveInfo {
+            rect: (100, 100).to(200, 200),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
-        let bounds = (250, 100).to(350, 200);
-        builder.push_rect(bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        let info = LayoutPrimitiveInfo {
+            rect: (250, 100).to(350, 200),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
         let border_side = BorderSide {
             color: ColorF::new(0.0, 0.0, 1.0, 1.0),
             style: BorderStyle::Groove,
         };
         let border_widths = BorderWidths {
             top: 10.0,
             left: 10.0,
             bottom: 10.0,
@@ -227,18 +240,22 @@ impl Example for App {
         let border_details = BorderDetails::Normal(NormalBorder {
             top: border_side,
             right: border_side,
             bottom: border_side,
             left: border_side,
             radius: BorderRadius::uniform(20.0),
         });
 
-        let bounds = (100, 100).to(200, 200);
-        builder.push_border(bounds, None, border_widths, border_details);
+        let info = LayoutPrimitiveInfo {
+            rect: (100, 100).to(200, 200),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_border(&info, border_widths, border_details);
 
 
         if false { // draw text?
             let font_key = api.generate_font_key();
             let font_bytes = load_file("res/FreeSans.ttf");
             resources.add_raw_font(font_key, font_bytes, 0);
 
             let font_instance_key = api.generate_font_instance_key();
@@ -290,37 +307,45 @@ impl Example for App {
                     index: 87,
                     point: LayoutPoint::new(600.0, 100.0),
                 },
                 GlyphInstance {
                     index: 17,
                     point: LayoutPoint::new(650.0, 100.0),
                 },
             ];
+            let info = LayoutPrimitiveInfo {
+                rect: text_bounds,
+                local_clip: None,
+                is_backface_visible: true,
+            };
 
-            builder.push_text(text_bounds,
-                              None,
+            builder.push_text(&info,
                               &glyphs,
                               font_instance_key,
                               ColorF::new(1.0, 1.0, 0.0, 1.0),
                               None);
         }
 
         if false { // draw box shadow?
             let rect = LayoutRect::zero();
             let simple_box_bounds = (20, 200).by(50, 50);
             let offset = vec2(10.0, 10.0);
             let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
             let blur_radius = 0.0;
             let spread_radius = 0.0;
             let simple_border_radius = 8.0;
             let box_shadow_type = BoxShadowClipMode::Inset;
+            let info = LayoutPrimitiveInfo {
+                rect: rect,
+                local_clip: Some(LocalClip::from(bounds)),
+                is_backface_visible: true,
+            };
 
-            builder.push_box_shadow(rect,
-                                    Some(LocalClip::from(bounds)),
+            builder.push_box_shadow(&info,
                                     simple_box_bounds,
                                     offset,
                                     color,
                                     blur_radius,
                                     spread_radius,
                                     simple_border_radius,
                                     box_shadow_type);
         }
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -235,36 +235,49 @@ impl Example for App {
         resources.add_image(
             blob_img2,
             api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true),
             api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), layout_size);
-        builder.push_stacking_context(api::ScrollPolicy::Scrollable,
-                                      bounds,
+        let info = api::LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      api::ScrollPolicy::Scrollable,
                                       None,
                                       api::TransformStyle::Flat,
                                       None,
                                       api::MixBlendMode::Normal,
                                       Vec::new());
 
+        let info = api::LayoutPrimitiveInfo {
+            rect: (30, 30).by(500, 500),
+            local_clip: Some(api::LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
         builder.push_image(
-            (30, 30).by(500, 500),
-            Some(api::LocalClip::from(bounds)),
+            &info,
             api::LayoutSize::new(500.0, 500.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
             blob_img1,
         );
 
+        let info = api::LayoutPrimitiveInfo {
+            rect: (600, 600).by(200, 200),
+            local_clip: Some(api::LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
         builder.push_image(
-            (600, 600).by(200, 200),
-            Some(api::LocalClip::from(bounds)),
+            &info,
             api::LayoutSize::new(200.0, 200.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
             blob_img2,
         );
 
         builder.pop_stacking_context();
     }
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -61,16 +61,21 @@ pub trait Example {
               document_id: DocumentId);
     fn on_event(&mut self,
                 event: glutin::Event,
                 api: &RenderApi,
                 document_id: DocumentId) -> bool;
     fn get_external_image_handler(&self) -> Option<Box<webrender::ExternalImageHandler>> {
         None
     }
+    fn get_output_image_handler(&mut self, _gl: &gl::Gl) -> Option<Box<webrender::OutputImageHandler>> {
+        None
+    }
+    fn draw_custom(&self, _gl: &gl::Gl) {
+    }
 }
 
 pub fn main_wrapper(example: &mut Example,
                     options: Option<webrender::RendererOptions>)
 {
     let args: Vec<String> = env::args().collect();
     let res_path = if args.len() > 1 {
         Some(PathBuf::from(&args[1]))
@@ -106,26 +111,29 @@ pub fn main_wrapper(example: &mut Exampl
         resource_override_path: res_path,
         debug: true,
         precache_shaders: true,
         device_pixel_ratio: window.hidpi_factor(),
         .. options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let size = DeviceUintSize::new(width, height);
-    let (mut renderer, sender) = webrender::Renderer::new(gl, opts).unwrap();
+    let (mut renderer, sender) = webrender::Renderer::new(gl.clone(), opts).unwrap();
     let api = sender.create_api();
     let document_id = api.add_document(size);
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     if let Some(external_image_handler) = example.get_external_image_handler() {
         renderer.set_external_image_handler(external_image_handler);
     }
+    if let Some(output_image_handler) = example.get_output_image_handler(&*gl) {
+        renderer.set_output_image_handler(output_image_handler);
+    }
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
     let layout_size = LayoutSize::new(width as f32, height as f32);
     let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
     let mut resources = ResourceUpdates::new();
@@ -171,16 +179,22 @@ pub fn main_wrapper(example: &mut Exampl
                 }
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::I)) => {
                     let mut flags = renderer.get_debug_flags();
                     flags.toggle(webrender::TEXTURE_CACHE_DBG);
                     renderer.set_debug_flags(flags);
                 }
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
+                                             _, Some(glutin::VirtualKeyCode::B)) => {
+                    let mut flags = renderer.get_debug_flags();
+                    flags.toggle(webrender::ALPHA_PRIM_DBG);
+                    renderer.set_debug_flags(flags);
+                }
+                glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::M)) => {
                     api.notify_memory_pressure();
                 }
                 _ => {
                     if example.on_event(event, &api, document_id) {
                         let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
                         let mut resources = ResourceUpdates::new();
 
@@ -197,13 +211,14 @@ pub fn main_wrapper(example: &mut Exampl
                         api.generate_frame(document_id, None);
                     }
                 }
             }
         }
 
         renderer.update();
         renderer.render(DeviceUintSize::new(width, height)).unwrap();
+        example.draw_custom(&*gl);
         window.swap_buffers().ok();
     }
 
     renderer.deinit();
 }
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/frame_output.rs
@@ -0,0 +1,228 @@
+/* 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/. */
+
+extern crate gleam;
+extern crate glutin;
+extern crate webrender;
+
+#[path="common/boilerplate.rs"]
+mod boilerplate;
+
+use boilerplate::{Example, HandyDandyRectBuilder};
+use gleam::gl;
+use webrender::api::*;
+
+// This example demonstrates using the frame output feature to copy
+// the output of a WR framebuffer to a custom texture.
+
+const VS: &str = "#version 130\nin vec2 aPos;out vec2 vUv;\nvoid main() { vUv = aPos; gl_Position = vec4(aPos, 0.0, 1.0); }\n";
+const FS: &str = "#version 130\nout vec4 oFragColor;\nin vec2 vUv;\nuniform sampler2D s;\nvoid main() { oFragColor = texture(s, vUv); }\n";
+
+struct App {
+    iframe_pipeline_id: Option<PipelineId>,
+    texture_id: gl::GLuint,
+}
+
+struct OutputHandler {
+    texture_id: gl::GLuint,
+}
+
+impl OutputHandler {
+    fn new(texture_id: gl::GLuint) -> OutputHandler {
+        OutputHandler {
+            texture_id
+        }
+    }
+}
+
+impl webrender::OutputImageHandler for OutputHandler {
+    fn lock(&mut self, _id: PipelineId) -> Option<(u32, DeviceIntSize)> {
+        Some((self.texture_id, DeviceIntSize::new(100, 100)))
+    }
+
+    fn unlock(&mut self, _id: PipelineId) {
+    }
+}
+
+impl Example for App {
+    fn render(&mut self,
+              api: &RenderApi,
+              builder: &mut DisplayListBuilder,
+              _resources: &mut ResourceUpdates,
+              _layout_size: LayoutSize,
+              _pipeline_id: PipelineId,
+              document_id: DocumentId) {
+        // Build the iframe display list on first render.
+        if self.iframe_pipeline_id.is_none() {
+            let epoch = Epoch(0);
+            let root_background_color = ColorF::new(0.0, 1.0, 0.0, 1.0);
+
+            let iframe_pipeline_id = PipelineId(0, 1);
+            let layout_size = LayoutSize::new(100.0, 100.0);
+            let mut builder = DisplayListBuilder::new(iframe_pipeline_id, layout_size);
+            let resources = ResourceUpdates::new();
+
+            let bounds = (0,0).to(50, 50);
+            let info = LayoutPrimitiveInfo {
+                rect: bounds,
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_stacking_context(&info,
+                                          ScrollPolicy::Scrollable,
+                                          None,
+                                          TransformStyle::Flat,
+                                          None,
+                                          MixBlendMode::Normal,
+                                          Vec::new());
+
+            builder.push_rect(&info, ColorF::new(1.0, 1.0, 0.0, 1.0));
+            builder.pop_stacking_context();
+
+            api.set_display_list(
+                document_id,
+                epoch,
+                Some(root_background_color),
+                layout_size,
+                builder.finalize(),
+                true,
+                resources
+            );
+
+            self.iframe_pipeline_id = Some(iframe_pipeline_id);
+            api.enable_frame_output(document_id, iframe_pipeline_id, true);
+        }
+
+        let bounds = (100,100).to(200, 200);
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
+                                      None,
+                                      TransformStyle::Flat,
+                                      None,
+                                      MixBlendMode::Normal,
+                                      Vec::new());
+
+        builder.push_iframe(&info, self.iframe_pipeline_id.unwrap());
+
+        builder.pop_stacking_context();
+    }
+
+    fn draw_custom(&self, gl: &gl::Gl) {
+        let vbo = gl.gen_buffers(1)[0];
+        let vao = gl.gen_vertex_arrays(1)[0];
+
+        let pid = create_program(gl);
+
+        let vertices: [f32; 12] = [
+            0.0, 1.0,
+            1.0, 0.0,
+            0.0, 0.0,
+            0.0, 1.0,
+            1.0, 1.0,
+            1.0, 0.0
+        ];
+
+        gl.active_texture(gl::TEXTURE0);
+        gl.bind_texture(gl::TEXTURE_2D, self.texture_id);
+
+        gl.use_program(pid);
+        let sampler = gl.get_uniform_location(pid, "s");
+        debug_assert!(sampler != -1);
+        gl.uniform_1i(sampler, 0);
+
+        gl.bind_buffer(gl::ARRAY_BUFFER, vbo);
+        gl::buffer_data(gl, gl::ARRAY_BUFFER, &vertices, gl::STATIC_DRAW);
+
+        gl.bind_vertex_array(vao);
+        gl.enable_vertex_attrib_array(0);
+        gl.vertex_attrib_pointer(0, 2, gl::FLOAT, false, 8, 0);
+
+        gl.draw_arrays(gl::TRIANGLES, 0, 6);
+
+        gl.delete_vertex_arrays(&[vao]);
+        gl.delete_buffers(&[vbo]);
+        gl.delete_program(pid);
+    }
+
+    fn on_event(&mut self,
+                _event: glutin::Event,
+                _api: &RenderApi,
+                _document_id: DocumentId) -> bool {
+        false
+    }
+
+    fn get_output_image_handler(&mut self, gl: &gl::Gl) -> 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);
+        gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint);
+        gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
+        gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
+        gl.tex_image_2d(gl::TEXTURE_2D,
+                        0,
+                        gl::RGBA as gl::GLint,
+                        100,
+                        100,
+                        0,
+                        gl::BGRA,
+                        gl::UNSIGNED_BYTE,
+                        None);
+        gl.bind_texture(gl::TEXTURE_2D, 0);
+
+        self.texture_id = texture_id;
+        Some(Box::new(OutputHandler::new(texture_id)))
+    }
+}
+
+fn main() {
+    let mut app = App {
+        iframe_pipeline_id: None,
+        texture_id: 0,
+    };
+    boilerplate::main_wrapper(&mut app, None);
+}
+
+pub fn compile_shader(gl: &gl::Gl,
+                      shader_type: gl::GLenum,
+                      source: &str)
+                      -> gl::GLuint {
+    let id = gl.create_shader(shader_type);
+    gl.shader_source(id, &[source.as_bytes()]);
+    gl.compile_shader(id);
+    let log = gl.get_shader_info_log(id);
+    if gl.get_shader_iv(id, gl::COMPILE_STATUS) == (0 as gl::GLint) {
+        panic!("{:?} {}", source, log);
+    }
+    id
+}
+
+pub fn create_program(gl: &gl::Gl) -> gl::GLuint {
+    let vs_id = compile_shader(gl, gl::VERTEX_SHADER, VS);
+    let fs_id = compile_shader(gl, gl::FRAGMENT_SHADER, FS);
+
+    let pid = gl.create_program();
+    gl.attach_shader(pid, vs_id);
+    gl.attach_shader(pid, fs_id);
+
+    gl.bind_attrib_location(pid, 0, "aPos");
+    gl.link_program(pid);
+
+    gl.detach_shader(pid, vs_id);
+    gl.detach_shader(pid, fs_id);
+    gl.delete_shader(vs_id);
+    gl.delete_shader(fs_id);
+
+    if gl.get_program_iv(pid, gl::LINK_STATUS) == (0 as gl::GLint) {
+        let error_log = gl.get_program_info_log(pid);
+        panic!("{}", error_log);
+    }
+
+    pid
+}
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -29,50 +29,54 @@ impl Example for App {
               pipeline_id: PipelineId,
               document_id: DocumentId) {
         // All the sub_* things are for the nested pipeline
         let sub_size = DeviceUintSize::new(100, 100);
         let sub_bounds = (0,0).to(sub_size.width as i32, sub_size.height as i32);
 
         let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
         let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
+        let info = LayoutPrimitiveInfo {
+            rect: sub_bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
 
-        sub_builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                          sub_bounds,
+        sub_builder.push_stacking_context(&info,
+                                          ScrollPolicy::Scrollable,
                                           None,
                                           TransformStyle::Flat,
                                           None,
                                           MixBlendMode::Normal,
                                           Vec::new());
         // green rect visible == success
-        sub_builder.push_rect(sub_bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        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,
             Epoch(0),
             None,
             sub_bounds.size,
             sub_builder.finalize(),
             true,
             ResourceUpdates::new(),
         );
 
-        let bounds = sub_bounds;
         // And this is for the root pipeline
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
         // red rect under the iframe: if this is visible, things have gone wrong
-        builder.push_rect(bounds, None, ColorF::new(1.0, 0.0, 0.0, 1.0));
-        builder.push_iframe(bounds, None, sub_pipeline_id);
+        builder.push_rect(&info, ColorF::new(1.0, 0.0, 0.0, 1.0));
+        builder.push_iframe(&info, sub_pipeline_id);
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self,
                 _event: glutin::Event,
                 _api: &RenderApi,
                 _document_id: DocumentId) -> bool {
         false
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -34,39 +34,52 @@ impl Example for App {
 
         resources.add_image(
             self.image_key,
             ImageDescriptor::new(32, 32, ImageFormat::BGRA8, true),
             ImageData::new(image_data),
             None,
         );
 
-        let bounds = (0,0).to(512, 512);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let bounds = (0, 0).to(512, 512);
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         let image_size = LayoutSize::new(100.0, 100.0);
 
+        let info = LayoutPrimitiveInfo {
+            rect: LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
+            local_clip: Some(LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
         builder.push_image(
-            LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
-            Some(LocalClip::from(bounds)),
+            &info,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
             self.image_key
         );
 
+        let info = LayoutPrimitiveInfo {
+            rect: LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size),
+            local_clip: Some(LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
         builder.push_image(
-            LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size),
-            Some(LocalClip::from(bounds)),
+            &info,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Pixelated,
             self.image_key
         );
 
         builder.pop_stacking_context();
     }
--- a/gfx/webrender/examples/nested_display_list.rs
+++ b/gfx/webrender/examples/nested_display_list.rs
@@ -20,69 +20,101 @@ impl Example for App {
     fn render(&mut self,
               _api: &RenderApi,
               builder: &mut DisplayListBuilder,
               _resources: &mut ResourceUpdates,
               layout_size: LayoutSize,
               pipeline_id: PipelineId,
               _document_id: DocumentId) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         let outer_scroll_frame_rect = (100, 100).to(600, 400);
-        builder.push_rect(outer_scroll_frame_rect, None, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        let info = LayoutPrimitiveInfo {
+            rect: outer_scroll_frame_rect,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         let nested_clip_id = builder.define_scroll_frame(None,
                                                          (100, 100).to(1000, 1000),
                                                          outer_scroll_frame_rect,
                                                          vec![],
                                                          None,
                                                          ScrollSensitivity::ScriptAndInputEvents);
         builder.push_clip_id(nested_clip_id);
 
         let mut builder2 = DisplayListBuilder::new(pipeline_id, layout_size);
         let mut builder3 = DisplayListBuilder::new(pipeline_id, layout_size);
 
-        let rect = (110, 110).to(210, 210);
-        builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        let info = LayoutPrimitiveInfo {
+            rect: (110, 110).to(210, 210),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder3.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         // A fixed position rectangle should be fixed to the reference frame that starts
         // in the outer display list.
-        builder3.push_stacking_context(ScrollPolicy::Fixed,
-                                      (220, 110).to(320, 210),
-                                      None,
-                                      TransformStyle::Flat,
-                                      None,
-                                      MixBlendMode::Normal,
-                                      Vec::new());
-        let rect = (0, 0).to(100, 100);
-        builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        let info = LayoutPrimitiveInfo {
+            rect: (220, 110).to(320, 210),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder3.push_stacking_context(&info,
+                                       ScrollPolicy::Fixed,
+                                       None,
+                                       TransformStyle::Flat,
+                                       None,
+                                       MixBlendMode::Normal,
+                                       Vec::new());
+        let info = LayoutPrimitiveInfo {
+            rect: (0, 0).to(100, 100),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder3.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
         builder3.pop_stacking_context();
 
         // Now we push an inner scroll frame that should have the same id as the outer one,
         // but the WebRender nested display list replacement code should convert it into
         // a unique ClipId.
         let inner_scroll_frame_rect = (330, 110).to(530, 360);
-        builder3.push_rect(inner_scroll_frame_rect, None, ColorF::new(1.0, 0.0, 1.0, 0.5));
+        let info = LayoutPrimitiveInfo {
+            rect: inner_scroll_frame_rect,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder3.push_rect(&info, ColorF::new(1.0, 0.0, 1.0, 0.5));
         let inner_nested_clip_id =
             builder3.define_scroll_frame(None,
                                          (330, 110).to(2000, 2000),
                                          inner_scroll_frame_rect,
                                          vec![],
                                          None,
                                          ScrollSensitivity::ScriptAndInputEvents);
         builder3.push_clip_id(inner_nested_clip_id);
-        let rect = (340, 120).to(440, 220);
-        builder3.push_rect(rect, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        let info = LayoutPrimitiveInfo {
+            rect: (340, 120).to(440, 220),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder3.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
         builder3.pop_clip_id();
 
         let (_, _, built_list) = builder3.finalize();
         builder2.push_nested_display_list(&built_list);
         let (_, _, built_list) = builder2.finalize();
         builder.push_nested_display_list(&built_list);
 
         builder.pop_clip_id();
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -19,94 +19,137 @@ struct App {
 impl Example for App {
     fn render(&mut self,
               _api: &RenderApi,
               builder: &mut DisplayListBuilder,
               _resources: &mut ResourceUpdates,
               layout_size: LayoutSize,
               _pipeline_id: PipelineId,
               _document_id: DocumentId) {
-        let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let info = LayoutPrimitiveInfo {
+            rect: LayoutRect::new(LayoutPoint::zero(), layout_size),
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         if true {   // scrolling and clips stuff
             // let's make a scrollbox
             let scrollbox = (0, 0).to(300, 400);
-            builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                          LayoutRect::new(LayoutPoint::new(10.0, 10.0),
-                                                          LayoutSize::zero()),
+            let info = LayoutPrimitiveInfo {
+                rect: LayoutRect::new(LayoutPoint::new(10.0, 10.0),
+                                      LayoutSize::zero()),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_stacking_context(&info,
+                                          ScrollPolicy::Scrollable,
                                           None,
                                           TransformStyle::Flat,
                                           None,
                                           MixBlendMode::Normal,
                                           Vec::new());
             // set the scrolling clip
             let clip_id = builder.define_scroll_frame(None,
                                                       (0, 0).by(1000, 1000),
                                                       scrollbox,
                                                       vec![],
                                                       None,
                                                       ScrollSensitivity::ScriptAndInputEvents);
             builder.push_clip_id(clip_id);
 
             // now put some content into it.
             // start with a white background
-            builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));
+            let info = LayoutPrimitiveInfo {
+                rect: (0, 0).to(1000, 1000),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
             // let's make a 50x50 blue square as a visual reference
-            builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.0, 1.0));
+            let info = LayoutPrimitiveInfo {
+                rect: (0, 0).to(50, 50),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));
 
             // and a 50x50 green square next to it with an offset clip
             // to see what that looks like
-            builder.push_rect((50, 0).to(100, 50),
-                              Some(LocalClip::from((60, 10).to(110, 60))),
+            let info = LayoutPrimitiveInfo {
+                rect: (50, 0).to(100, 50),
+                local_clip: Some(LocalClip::from((60, 10).to(110, 60))),
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info,
                               ColorF::new(0.0, 1.0, 0.0, 1.0));
 
             // Below the above rectangles, set up a nested scrollbox. It's still in
             // the same stacking context, so note that the rects passed in need to
             // be relative to the stacking context.
             let nested_clip_id = builder.define_scroll_frame(None,
                                                              (0, 100).to(300, 400),
                                                              (0, 100).to(200, 300),
                                                              vec![],
                                                              None,
                                                              ScrollSensitivity::ScriptAndInputEvents);
             builder.push_clip_id(nested_clip_id);
 
             // give it a giant gray background just to distinguish it and to easily
             // visually identify the nested scrollbox
-            builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));
+            let info = LayoutPrimitiveInfo {
+                rect: (-1000, -1000).to(5000, 5000),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
             // add a teal square to visualize the scrolling/clipping behaviour
             // as you scroll the nested scrollbox
-            builder.push_rect((0, 200).to(50, 250), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
+            let info = LayoutPrimitiveInfo {
+                rect: (0, 200).to(50, 250),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             // Add a sticky frame. It will "stick" at a margin of 10px from the top, until
             // the scrollframe scrolls another 60px, at which point it will "unstick". This lines
             // it up with the above teal square as it scrolls out of the visible area of the
             // scrollframe
             let sticky_id = builder.define_sticky_frame(
                 None,
                 (50, 140).to(100, 190),
                 StickyFrameInfo::new(Some(StickySideConstraint{ margin: 10.0, max_offset: 60.0 }),
                                      None, None, None));
             builder.push_clip_id(sticky_id);
-            builder.push_rect((50, 140).to(100, 190), None, ColorF::new(0.5, 0.5, 1.0, 1.0));
+            let info = LayoutPrimitiveInfo {
+                rect: (50, 140).to(100, 190),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
             builder.pop_clip_id(); // sticky_id
 
             // just for good measure add another teal square in the bottom-right
             // corner of the nested scrollframe content, which can be scrolled into
             // view by the user
-            builder.push_rect((250, 350).to(300, 400), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
+            let info = LayoutPrimitiveInfo {
+                rect: (250, 350).to(300, 400),
+                local_clip: None,
+                is_backface_visible: true,
+            };
+            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             builder.pop_clip_id(); // nested_clip_id
 
             builder.pop_clip_id(); // clip_id
             builder.pop_stacking_context();
         }
 
         builder.pop_stacking_context();
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -82,19 +82,24 @@ struct App {
 impl Example for App {
     fn render(&mut self,
               api: &RenderApi,
               builder: &mut DisplayListBuilder,
               resources: &mut ResourceUpdates,
               _layout_size: LayoutSize,
               _pipeline_id: PipelineId,
               _document_id: DocumentId) {
-        let bounds = (0,0).to(512, 512);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let bounds = (0, 0).to(512, 512);
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         let x0 = 50.0;
         let y0 = 50.0;
@@ -122,46 +127,58 @@ impl Example for App {
 
             self.swap_keys.push(key0);
             self.swap_keys.push(key1);
         }
 
         for (i, key) in self.stress_keys.iter().enumerate() {
             let x = (i % 128) as f32;
             let y = (i / 128) as f32;
+            let info = LayoutPrimitiveInfo {
+                rect: LayoutRect::new(LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y), image_size),
+                local_clip: Some(LocalClip::from(bounds)),
+                is_backface_visible: true,
+            };
 
             builder.push_image(
-                LayoutRect::new(LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y), image_size),
-                Some(LocalClip::from(bounds)),
+                &info,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
                 *key
             );
         }
 
         if let Some(image_key) = self.image_key {
             let image_size = LayoutSize::new(100.0, 100.0);
+            let info = LayoutPrimitiveInfo {
+                rect: LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
+                local_clip: Some(LocalClip::from(bounds)),
+                is_backface_visible: true,
+            };
 
             builder.push_image(
-                LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
-                Some(LocalClip::from(bounds)),
+                &info,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
                 image_key
             );
         }
 
         let swap_key = self.swap_keys[self.swap_index];
         let image_size = LayoutSize::new(64.0, 64.0);
+        let info = LayoutPrimitiveInfo {
+            rect: LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size),
+            local_clip: Some(LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
 
         builder.push_image(
-            LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size),
-            Some(LocalClip::from(bounds)),
+            &info,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
             swap_key
         );
         self.swap_index = 1 - self.swap_index;
 
         builder.pop_stacking_context();
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -160,18 +160,23 @@ impl Example for App {
     fn render(&mut self,
               api: &RenderApi,
               builder: &mut DisplayListBuilder,
               resources: &mut ResourceUpdates,
               layout_size: LayoutSize,
               _pipeline_id: PipelineId,
               _document_id: DocumentId) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
-        builder.push_stacking_context(ScrollPolicy::Scrollable,
-                                      bounds,
+        let info = LayoutPrimitiveInfo {
+            rect: bounds,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+        builder.push_stacking_context(&info,
+                                      ScrollPolicy::Scrollable,
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       MixBlendMode::Normal,
                                       Vec::new());
 
         let yuv_chanel1 = api.generate_image_key();
         let yuv_chanel2 = api.generate_image_key();
@@ -197,27 +202,35 @@ impl Example for App {
         );
         resources.add_image(
             yuv_chanel3,
             ImageDescriptor::new(100, 100, ImageFormat::A8, true),
             ImageData::new(vec![127; 100 * 100]),
             None,
         );
 
+        let info = LayoutPrimitiveInfo {
+            rect: LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
+            local_clip: Some(LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
         builder.push_yuv_image(
-            LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
-            Some(LocalClip::from(bounds)),
+            &info,
             YuvData::NV12(yuv_chanel1, yuv_chanel2),
             YuvColorSpace::Rec601,
             ImageRendering::Auto,
         );
 
+        let info = LayoutPrimitiveInfo {
+            rect: LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
+            local_clip: Some(LocalClip::from(bounds)),
+            is_backface_visible: true,
+        };
         builder.push_yuv_image(
-            LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
-            Some(LocalClip::from(bounds)),
+            &info,
             YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
             YuvColorSpace::Rec601,
             ImageRendering::Auto,
         );
 
         builder.pop_stacking_context();
     }
 
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -5,39 +5,39 @@
 #ifdef WR_VERTEX_SHADER
 
 #define SEGMENT_ALL         0
 #define SEGMENT_CORNER_TL   1
 #define SEGMENT_CORNER_TR   2
 #define SEGMENT_CORNER_BL   3
 #define SEGMENT_CORNER_BR   4
 
-in int aClipRenderTaskIndex;
-in int aClipLayerIndex;
+in int aClipRenderTaskAddress;
+in int aClipLayerAddress;
 in int aClipSegment;
 in ivec4 aClipDataResourceAddress;
 
-struct CacheClipInstance {
-    int render_task_index;
-    int layer_index;
+struct ClipMaskInstance {
+    int render_task_address;
+    int layer_address;
     int segment;
     ivec2 clip_data_address;
     ivec2 resource_address;
 };
 
-CacheClipInstance fetch_clip_item(int index) {
-    CacheClipInstance cci;
+ClipMaskInstance fetch_clip_item() {
+    ClipMaskInstance cmi;
 
-    cci.render_task_index = aClipRenderTaskIndex;
-    cci.layer_index = aClipLayerIndex;
-    cci.segment = aClipSegment;
-    cci.clip_data_address = aClipDataResourceAddress.xy;
-    cci.resource_address = aClipDataResourceAddress.zw;
+    cmi.render_task_address = aClipRenderTaskAddress;
+    cmi.layer_address = aClipLayerAddress;
+    cmi.segment = aClipSegment;
+    cmi.clip_data_address = aClipDataResourceAddress.xy;
+    cmi.resource_address = aClipDataResourceAddress.zw;
 
-    return cci;
+    return cmi;
 }
 
 struct ClipVertexInfo {
     vec3 local_pos;
     vec2 screen_pos;
     RectWithSize clipped_local_rect;
 };
 
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -12,53 +12,36 @@ flat varying int vBlurRadius;
 
 #ifdef WR_VERTEX_SHADER
 // Applies a separable gaussian blur in one direction, as specified
 // by the dir field in the blur command.
 
 #define DIR_HORIZONTAL  0
 #define DIR_VERTICAL    1
 
-in int aBlurRenderTaskIndex;
-in int aBlurSourceTaskIndex;
+in int aBlurRenderTaskAddress;
+in int aBlurSourceTaskAddress;
 in int aBlurDirection;
 
-struct BlurCommand {
-    int task_id;
-    int src_task_id;
-    int dir;
-};
-
-BlurCommand fetch_blur() {
-    BlurCommand blur;
-
-    blur.task_id = aBlurRenderTaskIndex;
-    blur.src_task_id = aBlurSourceTaskIndex;
-    blur.dir = aBlurDirection;
-
-    return blur;
-}
-
 void main(void) {
-    BlurCommand cmd = fetch_blur();
-    RenderTaskData task = fetch_render_task(cmd.task_id);
-    RenderTaskData src_task = fetch_render_task(cmd.src_task_id);
+    RenderTaskData task = fetch_render_task(aBlurRenderTaskAddress);
+    RenderTaskData src_task = fetch_render_task(aBlurSourceTaskAddress);
 
     vec4 local_rect = task.data0;
 
     vec2 pos = mix(local_rect.xy,
                    local_rect.xy + local_rect.zw,
                    aPosition.xy);
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0).xy);
     vUv.z = src_task.data1.x;
     vBlurRadius = int(task.data1.y);
     vSigma = task.data1.y * 0.5;
 
-    switch (cmd.dir) {
+    switch (aBlurDirection) {
         case DIR_HORIZONTAL:
             vOffsetScale = vec2(1.0 / texture_size.x, 0.0);
             break;
         case DIR_VERTICAL:
             vOffsetScale = vec2(0.0, 1.0 / texture_size.y);
             break;
     }
 
--- a/gfx/webrender/res/cs_clip_border.vs.glsl
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -45,25 +45,25 @@ struct BorderClipDot {
 };
 
 BorderClipDot fetch_border_clip_dot(ivec2 address, int segment) {
     vec4 data = fetch_from_resource_cache_1_direct(address + ivec2(2 + (segment - 1), 0));
     return BorderClipDot(data.xyz);
 }
 
 void main(void) {
-    CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
-    ClipArea area = fetch_clip_area(cci.render_task_index);
-    Layer layer = fetch_layer(cci.layer_index);
+    ClipMaskInstance cmi = fetch_clip_item();
+    ClipArea area = fetch_clip_area(cmi.render_task_address);
+    Layer layer = fetch_layer(cmi.layer_address);
 
     // Fetch the header information for this corner clip.
-    BorderCorner corner = fetch_border_corner(cci.clip_data_address);
+    BorderCorner corner = fetch_border_corner(cmi.clip_data_address);
     vClipCenter = corner.clip_center;
 
-    if (cci.segment == 0) {
+    if (cmi.segment == 0) {
         // The first segment is used to zero out the border corner.
         vAlphaMask = vec2(0.0);
         vDotParams = vec3(0.0);
         vPoint_Tangent0 = vec4(1.0);
         vPoint_Tangent1 = vec4(1.0);
     } else {
         vec2 sign_modifier;
         switch (corner.corner) {
@@ -79,25 +79,25 @@ void main(void) {
             case CORNER_BOTTOM_LEFT:
                 sign_modifier = vec2(-1.0, 1.0);
                 break;
         };
 
         switch (corner.clip_mode) {
             case CLIP_MODE_DASH: {
                 // Fetch the information about this particular dash.
-                BorderClipDash dash = fetch_border_clip_dash(cci.clip_data_address, cci.segment);
+                BorderClipDash dash = fetch_border_clip_dash(cmi.clip_data_address, cmi.segment);
                 vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
                 vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
                 vDotParams = vec3(0.0);
                 vAlphaMask = vec2(0.0, 1.0);
                 break;
             }
             case CLIP_MODE_DOT: {
-                BorderClipDot cdot = fetch_border_clip_dot(cci.clip_data_address, cci.segment);
+                BorderClipDot cdot = fetch_border_clip_dot(cmi.clip_data_address, cmi.segment);
                 vPoint_Tangent0 = vec4(1.0);
                 vPoint_Tangent1 = vec4(1.0);
                 vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
                 vAlphaMask = vec2(1.0, 1.0);
                 break;
             }
         }
     }
--- a/gfx/webrender/res/cs_clip_image.vs.glsl
+++ b/gfx/webrender/res/cs_clip_image.vs.glsl
@@ -7,27 +7,27 @@ struct ImageMaskData {
 };
 
 ImageMaskData fetch_mask_data(ivec2 address) {
     vec4 data = fetch_from_resource_cache_1_direct(address);
     return ImageMaskData(RectWithSize(data.xy, data.zw));
 }
 
 void main(void) {
-    CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
-    ClipArea area = fetch_clip_area(cci.render_task_index);
-    Layer layer = fetch_layer(cci.layer_index);
-    ImageMaskData mask = fetch_mask_data(cci.clip_data_address);
+    ClipMaskInstance cmi = fetch_clip_item();
+    ClipArea area = fetch_clip_area(cmi.render_task_address);
+    Layer layer = fetch_layer(cmi.layer_address);
+    ImageMaskData mask = fetch_mask_data(cmi.clip_data_address);
     RectWithSize local_rect = mask.local_rect;
-    ImageResource res = fetch_image_resource_direct(cci.resource_address);
+    ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
-                                               cci.segment);
+                                               cmi.segment);
 
     vPos = vi.local_pos;
     vLayer = res.layer;
 
     vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(res.uv_rect.xy, res.uv_rect.zw - res.uv_rect.xy) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -49,26 +49,26 @@ ClipData fetch_clip(ivec2 address) {
     clip.top_right = fetch_clip_corner(address, 1);
     clip.bottom_left = fetch_clip_corner(address, 2);
     clip.bottom_right = fetch_clip_corner(address, 3);
 
     return clip;
 }
 
 void main(void) {
-    CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
-    ClipArea area = fetch_clip_area(cci.render_task_index);
-    Layer layer = fetch_layer(cci.layer_index);
-    ClipData clip = fetch_clip(cci.clip_data_address);
+    ClipMaskInstance cmi = fetch_clip_item();
+    ClipArea area = fetch_clip_area(cmi.render_task_address);
+    Layer layer = fetch_layer(cmi.layer_address);
+    ClipData clip = fetch_clip(cmi.clip_data_address);
     RectWithSize local_rect = clip.rect.rect;
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
-                                               cci.segment);
+                                               cmi.segment);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
 
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
 
     vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
                                  clip.top_left.outer_inner_radius.xy);
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -80,26 +80,26 @@ ivec2 get_resource_cache_uv(int address)
     return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
                  address / WR_MAX_VERTEX_TEXTURE_WIDTH);
 }
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sResourceCache;
 
 vec4[2] fetch_from_resource_cache_2_direct(ivec2 address) {
     return vec4[2](
-        texelFetchOffset(sResourceCache, address, 0, ivec2(0, 0)),
-        texelFetchOffset(sResourceCache, address, 0, ivec2(1, 0))
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0))
     );
 }
 
 vec4[2] fetch_from_resource_cache_2(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[2](
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0))
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
     );
 }
 
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_LAYER              9
 #define VECS_PER_RENDER_TASK        3
 #define VECS_PER_PRIM_HEADER        2
@@ -119,52 +119,52 @@ in ivec4 aData1;
 // https://github.com/servo/webrender/pull/623
 // https://github.com/servo/servo/issues/13953
 #define get_fetch_uv(i, vpi)  ivec2(vpi * (i % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)), i / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))
 
 
 vec4[8] fetch_from_resource_cache_8(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[8](
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(3, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(4, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(5, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(6, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(7, 0))
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(3, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(4, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(5, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(6, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(7, 0))
     );
 }
 
 vec4[3] fetch_from_resource_cache_3(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[3](
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0))
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0))
     );
 }
 
 vec4[4] fetch_from_resource_cache_4_direct(ivec2 address) {
     return vec4[4](
-        texelFetchOffset(sResourceCache, address, 0, ivec2(0, 0)),
-        texelFetchOffset(sResourceCache, address, 0, ivec2(1, 0)),
-        texelFetchOffset(sResourceCache, address, 0, ivec2(2, 0)),
-        texelFetchOffset(sResourceCache, address, 0, ivec2(3, 0))
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(3, 0))
     );
 }
 
 vec4[4] fetch_from_resource_cache_4(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[4](
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0)),
-        texelFetchOffset(sResourceCache, uv, 0, ivec2(3, 0))
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0)),
+        TEXEL_FETCH(sResourceCache, uv, 0, ivec2(3, 0))
     );
 }
 
 vec4 fetch_from_resource_cache_1_direct(ivec2 address) {
     return texelFetch(sResourceCache, address, 0);
 }
 
 vec4 fetch_from_resource_cache_1(int address) {
@@ -184,46 +184,46 @@ Layer fetch_layer(int index) {
     // Create a UV base coord for each 8 texels.
     // This is required because trying to use an offset
     // of more than 8 texels doesn't work on some versions
     // of OSX.
     ivec2 uv = get_fetch_uv(index, VECS_PER_LAYER);
     ivec2 uv0 = ivec2(uv.x + 0, uv.y);
     ivec2 uv1 = ivec2(uv.x + 8, uv.y);
 
-    layer.transform[0] = texelFetchOffset(sLayers, uv0, 0, ivec2(0, 0));
-    layer.transform[1] = texelFetchOffset(sLayers, uv0, 0, ivec2(1, 0));
-    layer.transform[2] = texelFetchOffset(sLayers, uv0, 0, ivec2(2, 0));
-    layer.transform[3] = texelFetchOffset(sLayers, uv0, 0, ivec2(3, 0));
+    layer.transform[0] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(0, 0));
+    layer.transform[1] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(1, 0));
+    layer.transform[2] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(2, 0));
+    layer.transform[3] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(3, 0));
 
-    layer.inv_transform[0] = texelFetchOffset(sLayers, uv0, 0, ivec2(4, 0));
-    layer.inv_transform[1] = texelFetchOffset(sLayers, uv0, 0, ivec2(5, 0));
-    layer.inv_transform[2] = texelFetchOffset(sLayers, uv0, 0, ivec2(6, 0));
-    layer.inv_transform[3] = texelFetchOffset(sLayers, uv0, 0, ivec2(7, 0));
+    layer.inv_transform[0] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(4, 0));
+    layer.inv_transform[1] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(5, 0));
+    layer.inv_transform[2] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(6, 0));
+    layer.inv_transform[3] = TEXEL_FETCH(sLayers, uv0, 0, ivec2(7, 0));
 
-    vec4 clip_rect = texelFetchOffset(sLayers, uv1, 0, ivec2(0, 0));
+    vec4 clip_rect = TEXEL_FETCH(sLayers, uv1, 0, ivec2(0, 0));
     layer.local_clip_rect = RectWithSize(clip_rect.xy, clip_rect.zw);
 
     return layer;
 }
 
 struct RenderTaskData {
     vec4 data0;
     vec4 data1;
     vec4 data2;
 };
 
 RenderTaskData fetch_render_task(int index) {
     RenderTaskData task;
 
     ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
 
-    task.data0 = texelFetchOffset(sRenderTasks, uv, 0, ivec2(0, 0));
-    task.data1 = texelFetchOffset(sRenderTasks, uv, 0, ivec2(1, 0));
-    task.data2 = texelFetchOffset(sRenderTasks, uv, 0, ivec2(2, 0));
+    task.data0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
+    task.data1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
+    task.data2 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(2, 0));
 
     return task;
 }
 
 struct AlphaBatchTask {
     vec2 screen_space_origin;
     vec2 render_target_origin;
     vec2 size;
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -11,19 +11,19 @@ flat varying vec4 vUvSampleBounds;
 #ifdef WR_VERTEX_SHADER
 struct SplitGeometry {
     vec3 points[4];
 };
 
 SplitGeometry fetch_split_geometry(int address) {
     ivec2 uv = get_resource_cache_uv(address);
 
-    vec4 data0 = texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0));
-    vec4 data1 = texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0));
-    vec4 data2 = texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0));
+    vec4 data0 = TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0));
+    vec4 data1 = TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0));
+    vec4 data2 = TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0));
 
     SplitGeometry geo;
     geo.points = vec3[4](
         data0.xyz, vec3(data0.w, data1.xy),
         vec3(data1.zw, data2.x), data2.yzw
     );
     return geo;
 }
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -16,16 +16,28 @@
 // Use texture() instead.
 #if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT) || defined(WR_FEATURE_TEXTURE_2D)
 #define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy)
 #else
 // In normal case, we use textureLod(). We haven't used the lod yet. So, we always pass 0.0 now.
 #define TEX_SAMPLE(sampler, tex_coord) textureLod(sampler, tex_coord, 0.0)
 #endif
 
+// texelFetchOffset is buggy on some Android GPUs (see issue #1694).
+// Fallback to texelFetch on mobile GPUs.
+#if defined(GL_ES) 
+    #if GL_ES == 1
+        #define TEXEL_FETCH(sampler, position, lod, offset) texelFetch(sampler, position + offset, lod)
+    #else
+        #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset)
+    #endif
+#else
+    #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset)
+#endif
+
 //======================================================================================
 // Vertex shader attributes and uniforms
 //======================================================================================
 #ifdef WR_VERTEX_SHADER
     #define varying out
 
     // Uniform inputs
     uniform mat4 uTransform;       // Orthographic projection
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ColorF, LayerPoint, LayerRect};
-use api::{LayerSize, LocalClip, NormalBorder};
+use api::{LayerPrimitiveInfo, LayerSize, NormalBorder};
 use clip::ClipSource;
 use ellipse::Ellipse;
 use gpu_cache::GpuDataRequest;
 use frame_builder::FrameBuilder;
 use prim_store::{BorderPrimitiveCpu, PrimitiveContainer};
 use tiling::PrimitiveFlags;
 use util::{lerp, pack_as_float};
 
@@ -217,21 +217,20 @@ impl NormalBorderHelpers for NormalBorde
             BorderStyle::Dashed |
             BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
         }
     }
 }
 
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
-                                   rect: &LayerRect,
+                                   info: &LayerPrimitiveInfo,
                                    border: &NormalBorder,
                                    widths: &BorderWidths,
                                    clip_and_scroll: ClipAndScrollInfo,
-                                   local_clip: &LocalClip,
                                    corner_instances: [BorderCornerInstance; 4],
                                    clip_sources: Vec<ClipSource>) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
@@ -267,32 +266,30 @@ impl FrameBuilder {
                 [ radius.bottom_right.width,
                   radius.bottom_right.height,
                   radius.bottom_left.width,
                   radius.bottom_left.height ].into(),
             ],
         };
 
         self.add_primitive(clip_and_scroll,
-                           &rect,
-                           local_clip,
+                           info,
                            clip_sources,
                            PrimitiveContainer::Border(prim_cpu));
     }
 
     // TODO(gw): This allows us to move border types over to the
     // simplified shader model one at a time. Once all borders
     // are converted, this can be removed, along with the complex
     // border code path.
     pub fn add_normal_border(&mut self,
-                             rect: &LayerRect,
+                             info: &LayerPrimitiveInfo,
                              border: &NormalBorder,
                              widths: &BorderWidths,
-                             clip_and_scroll: ClipAndScrollInfo,
-                             local_clip: &LocalClip) {
+                             clip_and_scroll: ClipAndScrollInfo) {
         // The border shader is quite expensive. For simple borders, we can just draw
         // the border with a few rectangles. This generally gives better batching, and
         // a GPU win in fragment shader time.
         // More importantly, the software (OSMesa) implementation we run tests on is
         // particularly slow at running our complex border shader, compared to the
         // rectangle shader. This has the effect of making some of our tests time
         // out more often on CI (the actual cause is simply too many Servo processes and
         // threads being run on CI at once).
@@ -305,38 +302,38 @@ impl FrameBuilder {
 
         let corners = [
             border.get_corner(left,
                               widths.left,
                               top,
                               widths.top,
                               &radius.top_left,
                               BorderCorner::TopLeft,
-                              rect),
+                              &info.rect),
             border.get_corner(right,
                               widths.right,
                               top,
                               widths.top,
                               &radius.top_right,
                               BorderCorner::TopRight,
-                              rect),
+                              &info.rect),
             border.get_corner(right,
                               widths.right,
                               bottom,
                               widths.bottom,
                               &radius.bottom_right,
                               BorderCorner::BottomRight,
-                              rect),
+                              &info.rect),
             border.get_corner(left,
                               widths.left,
                               bottom,
                               widths.bottom,
                               &radius.bottom_left,
                               BorderCorner::BottomLeft,
-                              rect),
+                              &info.rect),
         ];
 
         let (left_edge, left_len) = border.get_edge(left, widths.left);
         let (top_edge, top_len) = border.get_edge(top, widths.top);
         let (right_edge, right_len) = border.get_edge(right, widths.right);
         let (bottom_edge, bottom_len) = border.get_edge(bottom, widths.bottom);
 
         let edges = [
@@ -353,53 +350,57 @@ impl FrameBuilder {
         });
         let all_edges_simple = edges.iter().all(|e| {
             *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
         });
 
         let has_no_curve = radius.is_zero();
 
         if has_no_curve && all_corners_simple && all_edges_simple {
-            let p0 = rect.origin;
-            let p1 = rect.bottom_right();
-            let rect_width = rect.size.width;
-            let rect_height = rect.size.height;
+            let p0 = info.rect.origin;
+            let p1 = info.rect.bottom_right();
+            let rect_width = info.rect.size.width;
+            let rect_height = info.rect.size.height;
 
             // Add a solid rectangle for each visible edge/corner combination.
             if top_edge == BorderEdgeKind::Solid {
+                let mut info = info.clone();
+                info.rect = LayerRect::new(p0, LayerSize::new(rect_width, top_len));
                 self.add_solid_rectangle(clip_and_scroll,
-                                         &LayerRect::new(p0, LayerSize::new(rect_width, top_len)),
-                                         local_clip,
+                                         &info,
                                          &border.top.color,
                                          PrimitiveFlags::None);
             }
             if left_edge == BorderEdgeKind::Solid {
+                let mut info = info.clone();
+                info.rect = LayerRect::new(LayerPoint::new(p0.x, p0.y + top_len),
+                                           LayerSize::new(left_len,
+                                                          rect_height - top_len - bottom_len));
                 self.add_solid_rectangle(clip_and_scroll,
-                                         &LayerRect::new(LayerPoint::new(p0.x, p0.y + top_len),
-                                                         LayerSize::new(left_len,
-                                                                        rect_height - top_len - bottom_len)),
-                                         local_clip,
+                                         &info,
                                          &border.left.color,
                                          PrimitiveFlags::None);
             }
             if right_edge == BorderEdgeKind::Solid {
+                let mut info = info.clone();
+                info.rect = LayerRect::new(LayerPoint::new(p1.x - right_len,
+                                                           p0.y + top_len),
+                                           LayerSize::new(right_len,
+                                                          rect_height - top_len - bottom_len));
                 self.add_solid_rectangle(clip_and_scroll,
-                                         &LayerRect::new(LayerPoint::new(p1.x - right_len,
-                                                                         p0.y + top_len),
-                                                         LayerSize::new(right_len,
-                                                                        rect_height - top_len - bottom_len)),
-                                         local_clip,
+                                         &info,
                                          &border.right.color,
                                          PrimitiveFlags::None);
             }
             if bottom_edge == BorderEdgeKind::Solid {
+                let mut info = info.clone();
+                info.rect = LayerRect::new(LayerPoint::new(p0.x, p1.y - bottom_len),
+                                           LayerSize::new(rect_width, bottom_len));
                 self.add_solid_rectangle(clip_and_scroll,
-                                         &LayerRect::new(LayerPoint::new(p0.x, p1.y - bottom_len),
-                                                         LayerSize::new(rect_width, bottom_len)),
-                                         local_clip,
+                                         &info,
                                          &border.bottom.color,
                                          PrimitiveFlags::None);
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
             let mut corner_instances = [BorderCornerInstance::Single; 4];
 
@@ -414,21 +415,20 @@ impl FrameBuilder {
                     }
                     &BorderCornerKind::Clip(instance_kind) => {
                         corner_instances[i] = instance_kind;
                     }
                     _ => {}
                 }
             }
 
-            self.add_normal_border_primitive(rect,
+            self.add_normal_border_primitive(info,
                                              border,
                                              widths,
                                              clip_and_scroll,
-                                             local_clip,
                                              corner_instances,
                                              extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,19 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ComplexClipRegion, ImageMask, ImageRendering};
-use api::{LayerPoint, LayerRect, LayerToWorldTransform, LocalClip};
+use api::{DeviceIntRect, LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LocalClip};
 use border::BorderCornerClipSource;
-use gpu_cache::GpuCache;
-use mask_cache::MaskCacheInfo;
+use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
+use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
+use prim_store::{ClipData, ImageMaskData};
 use resource_cache::ResourceCache;
 use std::ops::Not;
+use util::{extract_inner_rect_safe, TransformedRect};
+
+const MAX_CLIP: f32 = 1000000.0;
+
+pub type ClipStore = FreeList<ClipSources>;
+pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
+pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
     pub origin: LayerPoint,
     pub main: LayerRect,
     pub image_mask: Option<ImageMask>,
     pub complex_clips: Vec<ComplexClipRegion>,
 }
@@ -96,63 +104,181 @@ impl From<ClipRegion> for ClipSources {
         }
 
         ClipSources::new(clips)
     }
 }
 
 #[derive(Debug)]
 pub struct ClipSources {
-    clips: Vec<ClipSource>,
-    mask_cache_info: MaskCacheInfo,
+    pub clips: Vec<(ClipSource, GpuCacheHandle)>,
+    pub bounds: MaskBounds,
 }
 
 impl ClipSources {
     pub fn new(clips: Vec<ClipSource>) -> ClipSources {
-        let mask_cache_info = MaskCacheInfo::new(&clips);
+        let clips = clips.into_iter()
+                         .map(|clip| (clip, GpuCacheHandle::new()))
+                         .collect();
 
         ClipSources {
             clips,
-            mask_cache_info,
+            bounds: MaskBounds {
+                inner: None,
+                outer: None,
+            },
         }
     }
 
-    pub fn clips(&self) -> &[ClipSource] {
+    pub fn clips(&self) -> &[(ClipSource, GpuCacheHandle)] {
         &self.clips
     }
 
     pub fn update(&mut self,
                   layer_transform: &LayerToWorldTransform,
                   gpu_cache: &mut GpuCache,
                   resource_cache: &mut ResourceCache,
                   device_pixel_ratio: f32) {
         if self.clips.is_empty() {
             return;
         }
 
-        self.mask_cache_info
-            .update(&self.clips,
-                    layer_transform,
-                    gpu_cache,
-                    device_pixel_ratio);
+        // compute the local bounds
+        if self.bounds.inner.is_none() {
+            let mut local_rect = Some(LayerRect::new(LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
+                                                     LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP)));
+            let mut local_inner = local_rect;
+            let mut has_clip_out = false;
+            let mut has_border_clip = false;
+
+            for &(ref source, _) in &self.clips {
+                match *source {
+                    ClipSource::Image(ref mask) => {
+                        if !mask.repeat {
+                            local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
+                        }
+                        local_inner = None;
+                    }
+                    ClipSource::Rectangle(rect) => {
+                        local_rect = local_rect.and_then(|r| r.intersection(&rect));
+                        local_inner = local_inner.and_then(|r| r.intersection(&rect));
+                    }
+                    ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
+                        // Once we encounter a clip-out, we just assume the worst
+                        // case clip mask size, for now.
+                        if mode == ClipMode::ClipOut {
+                            has_clip_out = true;
+                        }
+
+                        local_rect = local_rect.and_then(|r| r.intersection(rect));
+
+                        let inner_rect = extract_inner_rect_safe(rect, radius);
+                        local_inner = local_inner.and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
+                    }
+                    ClipSource::BorderCorner{..} => {
+                        has_border_clip = true;
+                    }
+                }
+            }
 
-        for clip in &self.clips {
+            // Work out the type of mask geometry we have, based on the
+            // list of clip sources above.
+            self.bounds = if has_clip_out || has_border_clip {
+                // For clip-out, the mask rect is not known.
+                MaskBounds {
+                    outer: None,
+                    inner: Some(LayerRect::zero().into()),
+                }
+            } else {
+                MaskBounds {
+                    outer: Some(local_rect.unwrap_or(LayerRect::zero()).into()),
+                    inner: Some(local_inner.unwrap_or(LayerRect::zero()).into()),
+                }
+            };
+        }
+
+        // update the screen bounds
+        self.bounds.update(layer_transform, device_pixel_ratio);
+
+        for &mut (ref mut source, ref mut handle) in &mut self.clips {
+            if let Some(mut request) = gpu_cache.request(handle) {
+                match *source {
+                    ClipSource::Image(ref mask) => {
+                        let data = ImageMaskData {
+                            local_rect: mask.rect,
+                        };
+                        data.write_gpu_blocks(request);
+                    }
+                    ClipSource::Rectangle(rect) => {
+                        let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
+                        data.write(&mut request);
+                    }
+                    ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
+                        let data = ClipData::rounded_rect(rect, radius, mode);
+                        data.write(&mut request);
+                    }
+                    ClipSource::BorderCorner(ref mut source) => {
+                        source.write(request);
+                    }
+                }
+            }
+        }
+
+        for &(ref clip, _) in &self.clips {
             if let ClipSource::Image(ref mask) = *clip {
                 resource_cache.request_image(mask.image,
                                              ImageRendering::Auto,
                                              None,
                                              gpu_cache);
             }
         }
     }
 
     pub fn is_masking(&self) -> bool {
-        self.mask_cache_info.is_masking()
+        !self.clips.is_empty()
     }
+}
 
-    pub fn clone_mask_cache_info(&self, keep_aligned: bool) -> MaskCacheInfo {
-        if keep_aligned {
-            self.mask_cache_info.clone()
-        } else {
-            self.mask_cache_info.strip_aligned()
+/// Represents a local rect and a device space
+/// rectangles that are either outside or inside bounds.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Geometry {
+    pub local_rect: LayerRect,
+    pub device_rect: DeviceIntRect,
+}
+
+impl From<LayerRect> for Geometry {
+    fn from(local_rect: LayerRect) -> Self {
+        Geometry {
+            local_rect,
+            device_rect: DeviceIntRect::zero(),
         }
     }
 }
+
+/// Depending on the complexity of the clip, we may either
+/// know the outer and/or inner rect, or neither or these.
+/// In the case of a clip-out, we currently set the mask
+/// bounds to be unknown. This is conservative, but ensures
+/// correctness. In the future we can make this a lot
+/// more clever with some proper region handling.
+#[derive(Clone, Debug, PartialEq)]
+pub struct MaskBounds {
+    pub outer: Option<Geometry>,
+    pub inner: Option<Geometry>,
+}
+
+impl MaskBounds {
+    pub fn update(&mut self, transform: &LayerToWorldTransform, device_pixel_ratio: f32) {
+        if let Some(ref mut outer) = self.outer {
+            let transformed = TransformedRect::new(&outer.local_rect,
+                                                   transform,
+                                                   device_pixel_ratio);
+            outer.device_rect = transformed.bounding_rect;
+        }
+        if let Some(ref mut inner) = self.inner {
+            let transformed = TransformedRect::new(&inner.local_rect,
+                                                   transform,
+                                                   device_pixel_ratio);
+            inner.device_rect = transformed.inner_rect;
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,53 +1,55 @@
 /* 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, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
 use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyFrameInfo};
 use api::WorldPoint;
-use clip::{ClipRegion, ClipSources};
+use clip::{ClipRegion, ClipSources, ClipSourcesHandle, ClipStore};
 use clip_scroll_tree::TransformUpdateState;
 use geometry::ray_intersects_rect;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::{MatrixHelpers, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Debug)]
 pub struct ClipInfo {
     /// The clips for this node.
-    pub clip_sources: ClipSources,
+    pub clip_sources: ClipSourcesHandle,
 
     /// The packed layer index for this node, which is used to render a clip mask
     /// for it, if necessary.
     pub packed_layer_index: PackedLayerIndex,
 
     /// The final transformed rectangle of this clipping region for this node,
     /// which depends on the screen rectangle and the transformation of all of
     /// the parents.
     pub screen_bounding_rect: Option<(TransformedRectKind, DeviceIntRect)>,
 
     /// A rectangle which defines the rough boundaries of this clip in reference
     /// frame relative coordinates (with no scroll offsets).
     pub clip_rect: LayerRect,
 }
 
 impl ClipInfo {
-    pub fn new(clip_region: ClipRegion, packed_layer_index: PackedLayerIndex) -> ClipInfo {
+    pub fn new(clip_region: ClipRegion,
+               packed_layer_index: PackedLayerIndex,
+               clip_store: &mut ClipStore) -> ClipInfo {
         let clip_rect = LayerRect::new(clip_region.origin, clip_region.main.size);
         ClipInfo {
-            clip_sources: ClipSources::from(clip_region),
+            clip_sources: clip_store.insert(ClipSources::from(clip_region)),
             packed_layer_index,
             screen_bounding_rect: None,
             clip_rect: clip_rect,
         }
     }
 }
 
 #[derive(Debug)]
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,12 +1,13 @@
 /* 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 clip::ClipStore;
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use internal_types::{FastHashSet, FastHashMap};
 use print_tree::{PrintTree, PrintTreePrinter};
 use api::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform, LayerToWorldTransform};
 use api::{LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
 use api::{ScrollLocation, StickyFrameInfo, WorldPoint};
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
@@ -365,25 +366,28 @@ impl ClipScrollTree {
         self.pipelines_to_discard.insert(pipeline_id);
 
         match self.currently_scrolling_node_id {
             Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
             _ => {}
         }
     }
 
-    fn print_node<T: PrintTreePrinter>(&self, id: &ClipId, pt: &mut T) {
+    fn print_node<T: PrintTreePrinter>(&self,
+                                       id: &ClipId,
+                                       pt: &mut T,
+                                       clip_store: &ClipStore) {
         let node = self.nodes.get(id).unwrap();
 
         match node.node_type {
             NodeType::Clip(ref info) => {
                 pt.new_level("Clip".to_owned());
                 pt.add_item(format!("screen_bounding_rect: {:?}", info.screen_bounding_rect));
 
-                let clips = info.clip_sources.clips();
+                let clips = clip_store.get(&info.clip_sources).clips();
                 pt.new_level(format!("Clip Sources [{}]", clips.len()));
                 for source in clips {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.transform));
@@ -401,29 +405,29 @@ impl ClipScrollTree {
         pt.add_item(format!("content_size: {:?}", node.content_size));
         pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
         pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
         pt.add_item(format!("combined_local_viewport_rect: {:?}", node.combined_local_viewport_rect));
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
 
         for child_id in &node.children {
-            self.print_node(child_id, pt);
+            self.print_node(child_id, pt, clip_store);
         }
 
         pt.end_level();
     }
 
     #[allow(dead_code)]
-    pub fn print(&self) {
+    pub fn print(&self, clip_store: &ClipStore) {
         if !self.nodes.is_empty() {
             let mut pt = PrintTree::new("clip_scroll tree");
-            self.print_with(&mut pt);
+            self.print_with(clip_store, &mut pt,);
         }
     }
 
-    pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
+    pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
         if !self.nodes.is_empty() {
-            self.print_node(&self.root_reference_frame_id, pt);
+            self.print_node(&self.root_reference_frame_id, pt, clip_store);
         }
     }
 }
 
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -4,17 +4,17 @@
 
 use debug_font_data;
 use device::{Device, GpuMarker, Program, VAO, Texture, TextureSlot, VertexDescriptor};
 use device::{TextureFilter, VertexAttribute, VertexUsageHint, VertexAttributeKind, TextureTarget};
 use euclid::{Transform3D, Point2D, Size2D, Rect};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE};
 use internal_types::RenderTargetMode;
 use std::f32;
-use api::{ColorU, ImageFormat, DeviceUintSize};
+use api::{ColorU, ImageFormat, DeviceUintSize, DeviceIntRect};
 
 #[derive(Debug, Copy, Clone)]
 enum DebugSampler {
     Font,
 }
 
 impl Into<TextureSlot> for DebugSampler {
     fn into(self) -> TextureSlot {
@@ -234,16 +234,26 @@ impl DebugRenderer {
                     color0: ColorU,
                     x1: i32,
                     y1: i32,
                     color1: ColorU) {
         self.line_vertices.push(DebugColorVertex::new(x0 as f32, y0 as f32, color0));
         self.line_vertices.push(DebugColorVertex::new(x1 as f32, y1 as f32, color1));
     }
 
+
+    pub fn add_rect(&mut self, rect: &DeviceIntRect, color: ColorU) {
+        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) {
         let _gm = GpuMarker::new(device.rc_gl(), "debug");
         device.disable_depth();
         device.set_blend(true);
         device.set_blend_mode_alpha();
 
--- a/gfx/webrender/src/debug_server.rs
+++ b/gfx/webrender/src/debug_server.rs
@@ -55,16 +55,22 @@ impl ws::Handler for Server {
                         DebugCommand::EnableTextureCacheDebug(false)
                     }
                     "enable_render_target_debug" => {
                         DebugCommand::EnableRenderTargetDebug(true)
                     }
                     "disable_render_target_debug" => {
                         DebugCommand::EnableRenderTargetDebug(false)
                     }
+                    "enable_alpha_rects_debug" => {
+                        DebugCommand::EnableAlphaRectsDebug(true)
+                    }
+                    "disable_alpha_rects_debug" => {
+                        DebugCommand::EnableAlphaRectsDebug(false)
+                    }
                     "fetch_passes" => {
                         DebugCommand::FetchPasses
                     }
                     "fetch_documents" => {
                         DebugCommand::FetchDocuments
                     }
                     "fetch_clipscrolltree" => {
                         DebugCommand::FetchClipScrollTree
@@ -104,17 +110,19 @@ impl DebugServer {
                 debug_tx: debug_tx.clone(),
                 api_tx: api_tx.clone(),
             }
         }).unwrap();
 
         let broadcaster = socket.broadcaster();
 
         let join_handle = Some(thread::spawn(move || {
-            if let Err(..) = socket.listen("127.0.0.1:3583") {
+            let address = "127.0.0.1:3583";
+            println!("WebRender debug server started: {}", address);
+            if let Err(..) = socket.listen(address) {
                 println!("ERROR: Unable to bind debugger websocket (port may be in use).");
             }
         }));
 
         DebugServer {
             join_handle,
             broadcaster,
             debug_rx,
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -11,17 +11,17 @@ use std::io::Read;
 use std::iter::repeat;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
 use std::thread;
 use api::{ColorF, ImageFormat};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintSize};
+use api::{DeviceIntRect, DeviceUintSize};
 
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
 pub struct FrameId(usize);
 
 impl FrameId {
     pub fn new(value: usize) -> FrameId {
         FrameId(value)
     }
@@ -973,16 +973,41 @@ impl Device {
             fbo_id.bind(self.gl(), FBOTarget::Draw);
         }
 
         if let Some(dimensions) = dimensions {
             self.gl.viewport(0, 0, dimensions.width as gl::GLint, dimensions.height as gl::GLint);
         }
     }
 
+    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);
+        self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo.0);
+        self.gl.framebuffer_texture_2d(gl::FRAMEBUFFER,
+                                       gl::COLOR_ATTACHMENT0,
+                                       gl::TEXTURE_2D,
+                                       texture_id,
+                                       0);
+        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) {
+        debug_assert!(self.inside_frame);
+
+        if self.bound_draw_fbo != fbo_id {
+            self.bound_draw_fbo = fbo_id;
+            fbo_id.bind(self.gl(), FBOTarget::Draw);
+        }
+    }
+
     pub fn bind_program(&mut self, program: &Program) {
         debug_assert!(self.inside_frame);
 
         if self.bound_program != program.id {
             self.gl.use_program(program.id);
             self.bound_program = program.id;
         }
     }
@@ -1161,30 +1186,20 @@ impl Device {
         }
 
         // TODO(gw): Hack! Modify the code above to use the normal binding interfaces the device exposes.
         self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.bound_read_fbo.0);
         self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.bound_draw_fbo.0);
     }
 
     pub fn blit_render_target(&mut self,
-                              src_texture: Option<(&Texture, i32)>,
-                              src_rect: Option<DeviceIntRect>,
+                              src_rect: DeviceIntRect,
                               dest_rect: DeviceIntRect) {
         debug_assert!(self.inside_frame);
 
-        let src_rect = src_rect.unwrap_or_else(|| {
-            let texture = src_texture.unwrap().0;
-            DeviceIntRect::new(DeviceIntPoint::zero(),
-                               DeviceIntSize::new(texture.width as gl::GLint,
-                                                  texture.height as gl::GLint))
-        });
-
-        self.bind_read_target(src_texture);
-
         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,
                                   dest_rect.origin.x,
                                   dest_rect.origin.y,
                                   dest_rect.origin.x + dest_rect.size.width,
                                   dest_rect.origin.y + dest_rect.size.height,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF};
 use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
-use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
+use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize, LayerToScrollTransform};
 use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, MixBlendMode, PipelineId};
 use api::{PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
 use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
 use api::{TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use gpu_cache::GpuCache;
-use internal_types::{FastHashMap, RendererFrame};
+use internal_types::{FastHashMap, FastHashSet, RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{ResourceCache, TiledImageMap};
 use scene::{Scene, SceneProperties};
 use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
@@ -181,17 +181,17 @@ impl<'a> FlattenContext<'a> {
 }
 
 // TODO: doc
 pub struct Frame {
     pub clip_scroll_tree: ClipScrollTree,
     pub pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
-    frame_builder: Option<FrameBuilder>,
+    pub frame_builder: Option<FrameBuilder>,
 }
 
 trait FilterOpHelpers {
     fn resolve(self, properties: &SceneProperties) -> FilterOp;
     fn is_noop(&self) -> bool;
 }
 
 impl FilterOpHelpers for FilterOp {
@@ -415,17 +415,18 @@ impl Frame {
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut BuiltDisplayListIter<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_node_id: ClipId,
                                     mut reference_frame_relative_offset: LayerVector2D,
                                     bounds: &LayerRect,
                                     stacking_context: &StackingContext,
-                                    filters: ItemRange<FilterOp>) {
+                                    filters: ItemRange<FilterOp>,
+                                    is_backface_visible: bool) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
             // TODO(optimization?): self.traversal.display_list()
@@ -471,17 +472,19 @@ impl Frame {
             reference_frame_relative_offset = LayerVector2D::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
 
         context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
                                               composition_operations,
-                                              stacking_context.transform_style);
+                                              stacking_context.transform_style,
+                                              is_backface_visible,
+                                              false);
 
         self.flatten_items(traversal,
                            pipeline_id,
                            context,
                            reference_frame_relative_offset);
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             context.replacements.pop();
@@ -557,152 +560,149 @@ impl Frame {
                             -> Option<BuiltDisplayListIter<'a>> {
         let mut clip_and_scroll = item.clip_and_scroll();
         context.convert_clip_scroll_info_to_nested(&mut clip_and_scroll);
 
         let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
         clip_and_scroll.scroll_node_id =
             context.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
 
-
-        let item_rect_with_offset = item.rect().translate(&reference_frame_relative_offset);
-        let clip_with_offset = item.local_clip_with_offset(&reference_frame_relative_offset);
+        let prim_info = item.get_layer_primitive_info(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::Image(ref info) => {
                 if let Some(tiling) = context.tiled_image_map.get(&info.image_key) {
                     // The image resource is tiled. We have to generate an image primitive
                     // for each tile.
                     self.decompose_image(clip_and_scroll,
                                          &mut context.builder,
-                                         &item_rect_with_offset,
-                                         &clip_with_offset,
+                                         &prim_info,
                                          info,
                                          tiling.image_size,
                                          tiling.tile_size as u32);
                 } else {
                     context.builder.add_image(clip_and_scroll,
-                                              item_rect_with_offset,
-                                              &clip_with_offset,
+                                              &prim_info,
                                               &info.stretch_size,
                                               &info.tile_spacing,
                                               None,
                                               info.image_key,
                                               info.image_rendering,
                                               None);
                 }
             }
             SpecificDisplayItem::YuvImage(ref info) => {
                 context.builder.add_yuv_image(clip_and_scroll,
-                                              item_rect_with_offset,
-                                              &clip_with_offset,
+                                              &prim_info,
                                               info.yuv_data,
                                               info.color_space,
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
                 match context.resource_cache.get_font_instance(text_info.font_key) {
                     Some(instance) => {
                         context.builder.add_text(clip_and_scroll,
                                                  reference_frame_relative_offset,
-                                                 item_rect_with_offset,
-                                                 &clip_with_offset,
+                                                 &prim_info,
                                                  instance,
                                                  &text_info.color,
                                                  item.glyphs(),
                                                  item.display_list().get(item.glyphs()).count(),
                                                  text_info.glyph_options);
                     }
                     None => {
                         warn!("Unknown font instance key: {:?}", text_info.font_key);
                     }
                 }
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
-                                                                &item_rect_with_offset,
-                                                                &clip_with_offset,
+                                                                &prim_info,
                                                                 &info.color,
                                                                 &clip_and_scroll) {
                     context.builder.add_solid_rectangle(clip_and_scroll,
-                                                        &item_rect_with_offset,
-                                                        &clip_with_offset,
+                                                        &prim_info,
                                                         &info.color,
                                                         PrimitiveFlags::None);
 
                 }
             }
             SpecificDisplayItem::Line(ref info) => {
+                let prim_info = LayerPrimitiveInfo {
+                    rect: LayerRect::zero(),
+                    local_clip: Some(*item.local_clip()),
+                    is_backface_visible: prim_info.is_backface_visible,
+                };
+
                 context.builder.add_line(clip_and_scroll,
-                                         item.local_clip(),
+                                         &prim_info,
                                          info.baseline,
                                          info.start,
                                          info.end,
                                          info.orientation,
                                          info.width,
                                          &info.color,
                                          info.style);
             }
             SpecificDisplayItem::Gradient(ref info) => {
                 context.builder.add_gradient(clip_and_scroll,
-                                             item_rect_with_offset,
-                                             &clip_with_offset,
+                                             &prim_info,
                                              info.gradient.start_point,
                                              info.gradient.end_point,
                                              item.gradient_stops(),
                                              item.display_list()
                                                  .get(item.gradient_stops()).count(),
                                              info.gradient.extend_mode,
                                              info.tile_size,
                                              info.tile_spacing);
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 context.builder.add_radial_gradient(clip_and_scroll,
-                                                    item_rect_with_offset,
-                                                    &clip_with_offset,
+                                                    &prim_info,
                                                     info.gradient.start_center,
                                                     info.gradient.start_radius,
                                                     info.gradient.end_center,
                                                     info.gradient.end_radius,
                                                     info.gradient.ratio_xy,
                                                     item.gradient_stops(),
                                                     info.gradient.extend_mode,
                                                     info.tile_size,
                                                     info.tile_spacing);
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                 let bounds = box_shadow_info.box_bounds.translate(&reference_frame_relative_offset);
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = bounds;
                 context.builder.add_box_shadow(clip_and_scroll,
-                                               &bounds,
-                                               &clip_with_offset,
+                                               &prim_info,
                                                &box_shadow_info.offset,
                                                &box_shadow_info.color,
                                                box_shadow_info.blur_radius,
                                                box_shadow_info.spread_radius,
                                                box_shadow_info.border_radius,
                                                box_shadow_info.clip_mode);
             }
             SpecificDisplayItem::Border(ref info) => {
                 context.builder.add_border(clip_and_scroll,
-                                           item_rect_with_offset,
-                                           &clip_with_offset,
+                                           &prim_info,
                                            info,
                                            item.gradient_stops(),
                                            item.display_list()
                                                .get(item.gradient_stops()).count());
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(&mut subtraversal,
                                               pipeline_id,
                                               context,
                                               unreplaced_scroll_id,
                                               reference_frame_relative_offset,
                                               &item.rect(),
                                               &info.stacking_context,
-                                              item.filters());
+                                              item.filters(),
+                                              prim_info.is_backface_visible);
                 return Some(subtraversal);
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(info.pipeline_id,
                                     clip_and_scroll.scroll_node_id,
                                     &item.rect(),
                                     &item.local_clip(),
                                     context,
@@ -767,117 +767,128 @@ impl Frame {
             SpecificDisplayItem::PopNestedDisplayList => context.pop_nested_display_list_ids(),
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => { }
 
             SpecificDisplayItem::PopStackingContext =>
                 unreachable!("Should have returned in parent method."),
             SpecificDisplayItem::PushTextShadow(shadow) => {
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = LayerRect::zero();
                 context.builder.push_text_shadow(shadow,
                                                  clip_and_scroll,
-                                                 &clip_with_offset);
+                                                 &prim_info);
             }
             SpecificDisplayItem::PopTextShadow => {
                 context.builder.pop_text_shadow();
             }
         }
         None
     }
 
     /// Try to optimize the rendering of a solid rectangle that is clipped by a single
     /// rounded rectangle, by only masking the parts of the rectangle that intersect
     /// the rounded parts of the clip. This is pretty simple now, so has a lot of
     /// potential for further optimizations.
     fn try_to_add_rectangle_splitting_on_clip(&mut self,
                                               context: &mut FlattenContext,
-                                              rect: &LayerRect,
-                                              local_clip: &LocalClip,
+                                              info: &LayerPrimitiveInfo,
                                               color: &ColorF,
                                               clip_and_scroll: &ClipAndScrollInfo)
                                               -> bool {
         // If this rectangle is not opaque, splitting the rectangle up
         // into an inner opaque region just ends up hurting batching and
         // doing more work than necessary.
         if color.a != 1.0 {
             return false;
         }
 
-        let inner_unclipped_rect = match local_clip {
+        let local_clip = info.local_clip.unwrap();
+        let inner_unclipped_rect = match &local_clip {
             &LocalClip::Rect(_) => return false,
             &LocalClip::RoundedRect(_, ref region) => region.get_inner_rect_full(),
         };
         let inner_unclipped_rect = match inner_unclipped_rect {
             Some(rect) => rect,
             None => return false,
         };
 
         // The inner rectangle is not clipped by its assigned clipping node, so we can
         // let it be clipped by the parent of the clipping node, which may result in
         // less masking some cases.
         let mut clipped_rects = Vec::new();
-        subtract_rect(rect, &inner_unclipped_rect, &mut clipped_rects);
+        subtract_rect(&info.rect, &inner_unclipped_rect, &mut clipped_rects);
+
+        let prim_info = LayerPrimitiveInfo {
+            rect: inner_unclipped_rect,
+            local_clip: Some(LocalClip::from(*info.local_clip.unwrap().clip_rect())),
+            is_backface_visible: info.is_backface_visible,
+        };
 
         context.builder.add_solid_rectangle(*clip_and_scroll,
-                                            &inner_unclipped_rect,
-                                            &LocalClip::from(*local_clip.clip_rect()),
+                                            &prim_info,
                                             color,
                                             PrimitiveFlags::None);
 
         for clipped_rect in &clipped_rects {
+            let mut info = info.clone();
+            info.rect = *clipped_rect;
             context.builder.add_solid_rectangle(*clip_and_scroll,
-                                                clipped_rect,
-                                                local_clip,
+                                                &info,
                                                 color,
                                                 PrimitiveFlags::None);
         }
         true
     }
 
     fn flatten_root<'a>(&mut self,
                         traversal: &mut BuiltDisplayListIter<'a>,
                         pipeline_id: PipelineId,
                         context: &mut FlattenContext,
                         content_size: &LayoutSize) {
         context.builder.push_stacking_context(&LayerVector2D::zero(),
                                               pipeline_id,
                                               CompositeOps::default(),
-                                              TransformStyle::Flat);
+                                              TransformStyle::Flat,
+                                              true,
+                                              true);
 
         // We do this here, rather than above because we want any of the top-level
         // stacking contexts in the display list to be treated like root stacking contexts.
         // FIXME(mrobinson): Currently only the first one will, which for the moment is
         // sufficient for all our use cases.
         context.builder.notify_waiting_for_root_stacking_context();
 
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         let clip_id = ClipId::root_scroll_node(pipeline_id);
         if context.scene.root_pipeline_id != Some(pipeline_id) {
             if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size);
+                    let info = LayerPrimitiveInfo::new(root_bounds);
                     context.builder.add_solid_rectangle(ClipAndScrollInfo::simple(clip_id),
-                                                        &root_bounds,
-                                                        &LocalClip::from(root_bounds),
+                                                        &info,
                                                         &bg_color,
                                                         PrimitiveFlags::None);
                 }
             }
         }
 
 
         self.flatten_items(traversal, pipeline_id, context, LayerVector2D::zero());
 
         if self.frame_builder_config.enable_scrollbars {
             let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
+            let info = LayerPrimitiveInfo::new(scrollbar_rect);
+
             context.builder.add_solid_rectangle(
                 ClipAndScrollInfo::simple(clip_id),
-                &scrollbar_rect,
-                &LocalClip::from(scrollbar_rect),
+                &info,
                 &DEFAULT_SCROLLBAR_COLOR,
                 PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0));
         }
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_items<'a>(&mut self,
@@ -916,102 +927,101 @@ impl Frame {
     /// decompositing the repetition horizontally while repeating vertically in the shader (for
     /// an image where the width is too bug but the height is not).
     ///
     /// decompose_image and decompose_image_row handle image repetitions while decompose_tiled_image
     /// takes care of the decomposition required by the internal tiling of the image.
     fn decompose_image(&mut self,
                        clip_and_scroll: ClipAndScrollInfo,
                        builder: &mut FrameBuilder,
-                       item_rect: &LayerRect,
-                       item_local_clip: &LocalClip,
+                       prim_info: &LayerPrimitiveInfo,
                        info: &ImageDisplayItem,
                        image_size: DeviceUintSize,
                        tile_size: u32) {
         let no_vertical_tiling = image_size.height <= tile_size;
         let no_vertical_spacing = info.tile_spacing.height == 0.0;
+        let item_rect = prim_info.rect;
         if no_vertical_tiling && no_vertical_spacing {
             self.decompose_image_row(clip_and_scroll,
                                      builder,
-                                     item_rect,
-                                     item_local_clip,
+                                     prim_info,
                                      info,
                                      image_size,
                                      tile_size);
             return;
         }
 
         // Decompose each vertical repetition into rows.
         let layout_stride = info.stretch_size.height + info.tile_spacing.height;
         let num_repetitions = (item_rect.size.height / layout_stride).ceil() as u32;
         for i in 0..num_repetitions {
             if let Some(row_rect) = rect(
                 item_rect.origin.x,
                 item_rect.origin.y + (i as f32) * layout_stride,
                 item_rect.size.width,
                 info.stretch_size.height
-            ).intersection(item_rect) {
+            ).intersection(&item_rect) {
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = row_rect;
                 self.decompose_image_row(clip_and_scroll,
                                          builder,
-                                         &row_rect,
-                                         item_local_clip,
+                                         &prim_info,
                                          info,
                                          image_size,
                                          tile_size);
             }
         }
     }
 
     fn decompose_image_row(&mut self,
                            clip_and_scroll: ClipAndScrollInfo,
                            builder: &mut FrameBuilder,
-                           item_rect: &LayerRect,
-                           item_local_clip: &LocalClip,
+                           prim_info: &LayerPrimitiveInfo,
                            info: &ImageDisplayItem,
                            image_size: DeviceUintSize,
                            tile_size: u32) {
         let no_horizontal_tiling = image_size.width <= tile_size;
         let no_horizontal_spacing = info.tile_spacing.width == 0.0;
         if no_horizontal_tiling && no_horizontal_spacing {
             self.decompose_tiled_image(clip_and_scroll,
                                        builder,
-                                       item_rect,
-                                       item_local_clip,
+                                       prim_info,
                                        info,
                                        image_size,
                                        tile_size);
             return;
         }
 
         // Decompose each horizontal repetition.
+        let item_rect = prim_info.rect;
         let layout_stride = info.stretch_size.width + info.tile_spacing.width;
         let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
         for i in 0..num_repetitions {
             if let Some(decomposed_rect) = rect(
                 item_rect.origin.x + (i as f32) * layout_stride,
                 item_rect.origin.y,
                 info.stretch_size.width,
                 item_rect.size.height,
-            ).intersection(item_rect) {
+            ).intersection(&item_rect) {
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = decomposed_rect;
                 self.decompose_tiled_image(clip_and_scroll,
                                            builder,
-                                           &decomposed_rect,
-                                           item_local_clip,
+                                           &prim_info,
                                            info,
                                            image_size,
                                            tile_size);
             }
         }
     }
 
     fn decompose_tiled_image(&mut self,
                              clip_and_scroll: ClipAndScrollInfo,
                              builder: &mut FrameBuilder,
-                             item_rect: &LayerRect,
-                             item_local_clip: &LocalClip,
+                             prim_info: &LayerPrimitiveInfo,
                              info: &ImageDisplayItem,
                              image_size: DeviceUintSize,
                              tile_size: u32) {
         // The image resource is tiled. We have to generate an image primitive
         // for each tile.
         // We need to do this because the image is broken up into smaller tiles in the texture
         // cache and the image shader is not able to work with this type of sparse representation.
 
@@ -1036,16 +1046,17 @@ impl Frame {
         // irregular size in the texture cache.
         //
         // For the case where we don't tile along an axis, we can still perform the repetition in
         // the shader (for this particular axis), and it is worth special-casing for this to avoid
         // generating many primitives.
         // This can happen with very tall and thin images used as a repeating background.
         // Apparently web authors do that...
 
+        let item_rect = prim_info.rect;
         let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
         let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
 
         let tiled_in_x = image_size.width > tile_size;
         let tiled_in_y = image_size.height > tile_size;
 
         // If we don't actually tile in this dimension, repeating can be done in the shader.
         let shader_repeat_x = needs_repeat_x && !tiled_in_x;
@@ -1072,77 +1083,72 @@ impl Frame {
         // than the regular tile size if the image is not a multiple of the tile size.
         // Zero means the image size is a multiple of the tile size.
         let leftover = DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);
 
         for ty in 0..num_tiles_y {
             for tx in 0..num_tiles_x {
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
-                                        item_rect,
-                                        item_local_clip,
+                                        prim_info,
                                         info,
                                         TileOffset::new(tx, ty),
                                         stretched_tile_size,
                                         1.0, 1.0,
                                         shader_repeat_x, shader_repeat_y);
             }
             if leftover.width != 0 {
                 // Tiles on the right edge that are smaller than the tile size.
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
-                                        item_rect,
-                                        item_local_clip,
+                                        prim_info,
                                         info,
                                         TileOffset::new(num_tiles_x, ty),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         1.0,
                                         shader_repeat_x, shader_repeat_y);
             }
         }
 
         if leftover.height != 0 {
             for tx in 0..num_tiles_x {
                 // Tiles on the bottom edge that are smaller than the tile size.
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
-                                        item_rect,
-                                        item_local_clip,
+                                        prim_info,
                                         info,
                                         TileOffset::new(tx, num_tiles_y),
                                         stretched_tile_size,
                                         1.0,
                                         (leftover.height as f32) / tile_size_f32,
                                         shader_repeat_x,
                                         shader_repeat_y);
             }
 
             if leftover.width != 0 {
                 // Finally, the bottom-right tile with a "leftover" size.
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
-                                        item_rect,
-                                        item_local_clip,
+                                        prim_info,
                                         info,
                                         TileOffset::new(num_tiles_x, num_tiles_y),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         (leftover.height as f32) / tile_size_f32,
                                         shader_repeat_x,
                                         shader_repeat_y);
             }
         }
     }
 
     fn add_tile_primitive(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
                           builder: &mut FrameBuilder,
-                          item_rect: &LayerRect,
-                          item_local_clip: &LocalClip,
+                          prim_info: &LayerPrimitiveInfo,
                           info: &ImageDisplayItem,
                           tile_offset: TileOffset,
                           stretched_tile_size: LayerSize,
                           tile_ratio_width: f32,
                           tile_ratio_height: f32,
                           shader_repeat_x: bool,
                           shader_repeat_y: bool) {
         // If the the image is tiled along a given axis, we can't have the shader compute
@@ -1155,82 +1161,87 @@ impl Frame {
         // See the shader_repeat_x/y code below.
 
         let stretched_size = LayerSize::new(
             stretched_tile_size.width * tile_ratio_width,
             stretched_tile_size.height * tile_ratio_height,
         );
 
         let mut prim_rect = LayerRect::new(
-            item_rect.origin + LayerVector2D::new(
+            prim_info.rect.origin + LayerVector2D::new(
                 tile_offset.x as f32 * stretched_tile_size.width,
                 tile_offset.y as f32 * stretched_tile_size.height,
             ),
             stretched_size,
         );
 
         if shader_repeat_x {
             assert_eq!(tile_offset.x, 0);
-            prim_rect.size.width = item_rect.size.width;
+            prim_rect.size.width = prim_info.rect.size.width;
         }
 
         if shader_repeat_y {
             assert_eq!(tile_offset.y, 0);
-            prim_rect.size.height = item_rect.size.height;
+            prim_rect.size.height = prim_info.rect.size.height;
         }
 
         // Fix up the primitive's rect if it overflows the original item rect.
-        if let Some(prim_rect) = prim_rect.intersection(item_rect) {
+        if let Some(prim_rect) = prim_rect.intersection(&prim_info.rect) {
+            let mut prim_info = prim_info.clone();
+            prim_info.rect = prim_rect;
             builder.add_image(clip_and_scroll,
-                              prim_rect,
-                              item_local_clip,
+                              &prim_info,
                               &stretched_size,
                               &info.tile_spacing,
                               None,
                               info.image_key,
                               info.image_rendering,
                               Some(tile_offset));
         }
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  gpu_cache: &mut GpuCache,
                  display_lists: &DisplayListMap,
                  device_pixel_ratio: f32,
                  pan: LayerPoint,
+                 output_pipelines: &FastHashSet<PipelineId>,
                  texture_cache_profile: &mut TextureCacheProfileCounters,
                  gpu_cache_profile: &mut GpuCacheProfileCounters)
                  -> RendererFrame {
         self.clip_scroll_tree.update_all_node_transforms(pan);
         let frame = self.build_frame(resource_cache,
                                      gpu_cache,
                                      display_lists,
                                      device_pixel_ratio,
+                                     output_pipelines,
                                      texture_cache_profile,
                                      gpu_cache_profile);
         frame
     }
 
     fn build_frame(&mut self,
                    resource_cache: &mut ResourceCache,
                    gpu_cache: &mut GpuCache,
                    display_lists: &DisplayListMap,
                    device_pixel_ratio: f32,
+                   output_pipelines: &FastHashSet<PipelineId>,
                    texture_cache_profile: &mut TextureCacheProfileCounters,
                    gpu_cache_profile: &mut GpuCacheProfileCounters)
                    -> RendererFrame {
         let mut frame_builder = self.frame_builder.take();
         let frame = frame_builder.as_mut().map(|builder|
             builder.build(resource_cache,
                           gpu_cache,
                           self.id,
                           &mut self.clip_scroll_tree,
                           display_lists,
                           device_pixel_ratio,
+                          output_pipelines,
                           texture_cache_profile,
                           gpu_cache_profile)
         );
         self.frame_builder = frame_builder;
 
         let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
         RendererFrame::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
     }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,25 +1,25 @@
 /* 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::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontInstance, FontRenderMode};
 use api::{GlyphInstance, GlyphOptions, GradientStop};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
+use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
 use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
 use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
-use clip::{ClipMode, ClipRegion, ClipSource, ClipSources};
+use clip::{ClipMode, ClipRegion, ClipSource, ClipSources, ClipStore};
 use frame::FrameId;
 use gpu_cache::GpuCache;
-use internal_types::{FastHashMap, HardwareCompositeOp};
+use internal_types::{FastHashMap, FastHashSet, HardwareCompositeOp};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, RenderTask};
@@ -105,16 +105,17 @@ pub struct FrameBuilderConfig {
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
 }
 
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
+    pub clip_store: ClipStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
     clip_scroll_group_indices: FastHashMap<ClipAndScrollInfo,
                                            ClipScrollGroupIndex>,
     packed_layers: Vec<PackedLayer>,
@@ -129,17 +130,16 @@ pub struct FrameBuilder {
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
 
     /// Whether or not we've pushed a root stacking context for the current pipeline.
     has_root_stacking_context: bool,
-
 }
 
 impl FrameBuilder {
     pub fn new(previous: Option<FrameBuilder>,
                screen_size: DeviceUintSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
         match previous {
@@ -150,16 +150,17 @@ impl FrameBuilder {
                     clip_scroll_group_indices: FastHashMap::default(),
                     cmds: recycle_vec(prev.cmds),
                     packed_layers: recycle_vec(prev.packed_layers),
                     shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
                     scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                     reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                     stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                     prim_store: prev.prim_store.recycle(),
+                    clip_store: prev.clip_store.recycle(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
@@ -168,16 +169,17 @@ impl FrameBuilder {
                     clip_scroll_group_indices: FastHashMap::default(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
                     shadow_prim_stack: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
+                    clip_store: ClipStore::new(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
                 }
             }
         }
     }
@@ -191,31 +193,32 @@ impl FrameBuilder {
         self.clip_scroll_group_indices.insert(info, group_index);
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     fn create_primitive(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
-                        rect: &LayerRect,
-                        local_clip: &LocalClip,
+                        info: &LayerPrimitiveInfo,
                         mut clip_sources: Vec<ClipSource>,
                         container: PrimitiveContainer) -> PrimitiveIndex {
         self.create_clip_scroll_group_if_necessary(clip_and_scroll);
 
-        if let &LocalClip::RoundedRect(main, region) = local_clip {
+        let local_clip = info.local_clip.unwrap();
+        if let &LocalClip::RoundedRect(main, region) = &local_clip {
             clip_sources.push(ClipSource::Rectangle(main));
             clip_sources.push(ClipSource::RoundedRectangle(region.rect, region.radii, ClipMode::Clip));
         }
 
-        let clip_sources = ClipSources::new(clip_sources);
+        let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
 
-        let prim_index = self.prim_store.add_primitive(rect,
+        let prim_index = self.prim_store.add_primitive(&info.rect,
                                                        &local_clip.clip_rect(),
+                                                       info.is_backface_visible,
                                                        clip_sources,
                                                        container);
 
         prim_index
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(&mut self,
@@ -235,23 +238,21 @@ impl FrameBuilder {
 
         self.cmds.push(PrimitiveRunCmd::PrimitiveRun(prim_index, 1, clip_and_scroll));
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(&mut self,
                          clip_and_scroll: ClipAndScrollInfo,
-                         rect: &LayerRect,
-                         local_clip: &LocalClip,
+                         info: &LayerPrimitiveInfo,
                          clip_sources: Vec<ClipSource>,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.create_primitive(clip_and_scroll,
-                                               rect,
-                                               local_clip,
+                                               info,
                                                clip_sources,
                                                container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
 
         prim_index
     }
 
@@ -272,17 +273,19 @@ impl FrameBuilder {
     pub fn notify_waiting_for_root_stacking_context(&mut self) {
         self.has_root_stacking_context = false;
     }
 
     pub fn push_stacking_context(&mut self,
                                  reference_frame_offset: &LayerVector2D,
                                  pipeline_id: PipelineId,
                                  composite_ops: CompositeOps,
-                                 transform_style: TransformStyle) {
+                                 transform_style: TransformStyle,
+                                 is_backface_visible: bool,
+                                 is_pipeline_root: bool) {
         if let Some(parent_index) = self.stacking_context_stack.last() {
             let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
 
             if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
                 // the parent stacking context of a stacking context with mix-blend-mode
                 // must be drawn with a transparent background, unless the parent stacking context
                 // is the root of the page
                 let isolation = &mut self.stacking_context_store[parent_index.0].isolation;
@@ -293,19 +296,21 @@ impl FrameBuilder {
             }
         }
 
         let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
         let reference_frame_id = self.current_reference_frame_id();
         self.stacking_context_store.push(StackingContext::new(pipeline_id,
                                                               *reference_frame_offset,
                                                               !self.has_root_stacking_context,
+                                                              is_pipeline_root,
                                                               reference_frame_id,
                                                               transform_style,
-                                                              composite_ops));
+                                                              composite_ops,
+                                                              is_backface_visible));
         self.has_root_stacking_context = true;
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
@@ -397,17 +402,19 @@ impl FrameBuilder {
     }
 
     pub fn add_clip_node(&mut self,
                          new_node_id: ClipId,
                          parent_id: ClipId,
                          pipeline_id: PipelineId,
                          clip_region: ClipRegion,
                          clip_scroll_tree: &mut ClipScrollTree) {
-        let clip_info = ClipInfo::new(clip_region, PackedLayerIndex(self.packed_layers.len()));
+        let clip_info = ClipInfo::new(clip_region,
+                                      PackedLayerIndex(self.packed_layers.len()),
+                                      &mut self.clip_store);
         let node = ClipScrollNode::new(pipeline_id, parent_id, clip_info);
         clip_scroll_tree.add_node(node, new_node_id);
         self.packed_layers.push(PackedLayer::empty());
     }
 
     pub fn add_scroll_frame(&mut self,
                             new_node_id: ClipId,
                             parent_id: ClipId,
@@ -427,29 +434,29 @@ impl FrameBuilder {
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
     pub fn push_text_shadow(&mut self,
                             shadow: TextShadow,
                             clip_and_scroll: ClipAndScrollInfo,
-                            local_clip: &LocalClip) {
+                            info: &LayerPrimitiveInfo) {
         let prim = TextShadowPrimitiveCpu {
             shadow,
             primitives: Vec::new(),
+            render_task_id: None,
         };
 
         // Create an empty text-shadow primitive. Insert it into
         // the draw lists immediately so that it will be drawn
         // before any visual text elements that are added as
         // part of this text-shadow context.
         let prim_index = self.add_primitive(clip_and_scroll,
-                                            &LayerRect::zero(),
-                                            local_clip,
+                                            info,
                                             Vec::new(),
                                             PrimitiveContainer::TextShadow(prim));
 
         self.shadow_prim_stack.push(prim_index);
     }
 
     pub fn pop_text_shadow(&mut self) {
         let prim_index = self.shadow_prim_stack
@@ -463,27 +470,25 @@ impl FrameBuilder {
         let metadata = &mut self.prim_store.cpu_metadata[prim_index.0];
         let prim = &self.prim_store.cpu_text_shadows[metadata.cpu_prim_index.0];
 
         metadata.local_rect = metadata.local_rect.translate(&prim.shadow.offset);
     }
 
     pub fn add_solid_rectangle(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
-                               rect: &LayerRect,
-                               local_clip: &LocalClip,
+                               info: &LayerPrimitiveInfo,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
         let prim = RectanglePrimitive {
             color: *color,
         };
 
         let prim_index = self.add_primitive(clip_and_scroll,
-                                            rect,
-                                            local_clip,
+                                            info,
                                             Vec::new(),
                                             PrimitiveContainer::Rectangle(prim));
 
         match flags {
             PrimitiveFlags::None => {}
             PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
                 self.scrollbar_prims.push(ScrollbarPrimitive {
                     prim_index,
@@ -491,17 +496,17 @@ impl FrameBuilder {
                     border_radius,
                 });
             }
         }
     }
 
     pub fn add_line(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
-                    local_clip: &LocalClip,
+                    info: &LayerPrimitiveInfo,
                     baseline: f32,
                     start: f32,
                     end: f32,
                     orientation: LineOrientation,
                     width: f32,
                     color: &ColorF,
                     style: LineStyle) {
         let new_rect = match orientation {
@@ -527,26 +532,28 @@ impl FrameBuilder {
             let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
             if shadow_prim.shadow.blur_radius == 0.0 {
                 fast_text_shadow_prims.push(shadow_prim.shadow);
             }
         }
         for shadow in fast_text_shadow_prims {
             let mut line = line.clone();
             line.color = shadow.color;
+            let mut info = info.clone();
+            info.rect = new_rect.translate(&shadow.offset);
             self.add_primitive(clip_and_scroll,
-                               &new_rect.translate(&shadow.offset),
-                               local_clip,
+                               &info,
                                Vec::new(),
                                PrimitiveContainer::Line(line));
         }
 
+        let mut info = info.clone();
+        info.rect = new_rect;
         let prim_index = self.create_primitive(clip_and_scroll,
-                                               &new_rect,
-                                               local_clip,
+                                               &info,
                                                Vec::new(),
                                                PrimitiveContainer::Line(line));
 
         if color.a > 0.0 {
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         for shadow_prim_index in &self.shadow_prim_stack {
@@ -561,21 +568,21 @@ impl FrameBuilder {
                 shadow_metadata.local_rect = shadow_metadata.local_rect.union(&shadow_rect);
                 shadow_prim.primitives.push(prim_index);
             }
         }
     }
 
     pub fn add_border(&mut self,
                       clip_and_scroll: ClipAndScrollInfo,
-                      rect: LayerRect,
-                      local_clip: &LocalClip,
+                      info: &LayerPrimitiveInfo,
                       border_item: &BorderDisplayItem,
                       gradient_stops: ItemRange<GradientStop>,
                       gradient_stops_count: usize) {
+        let rect = info.rect;
         let create_segments = |outset: SideOffsets2D<f32>| {
             // Calculate the modified rect as specific by border-image-outset
             let origin = LayerPoint::new(rect.origin.x - outset.left,
                                          rect.origin.y - outset.top);
             let size = LayerSize::new(rect.size.width + outset.left + outset.right,
                                       rect.size.height + outset.top + outset.bottom);
             let rect = LayerRect::new(origin, size);
 
@@ -707,92 +714,93 @@ impl FrameBuilder {
                         ImageBorderSegment::new(LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
                                                 TexelRect::new(px2, py1, px3, py2),
                                                 RepeatMode::Stretch,
                                                 border.repeat_vertical),
                     ]);
                 }
 
                 for segment in segments {
+                    let mut info = info.clone();
+                    info.rect = segment.geom_rect;
                     self.add_image(clip_and_scroll,
-                                   segment.geom_rect,
-                                   local_clip,
+                                   &info,
                                    &segment.stretch_size,
                                    &segment.tile_spacing,
                                    Some(segment.sub_rect),
                                    border.image_key,
                                    ImageRendering::Auto,
                                    None);
                 }
             }
             BorderDetails::Normal(ref border) => {
-                self.add_normal_border(&rect,
+                self.add_normal_border(info,
                                        border,
                                        &border_item.widths,
-                                       clip_and_scroll,
-                                       local_clip);
+                                       clip_and_scroll);
             }
             BorderDetails::Gradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
+                    let mut info = info.clone();
+                    info.rect = segment;
 
                     self.add_gradient(clip_and_scroll,
-                                      segment,
-                                      local_clip,
+                                      &info,
                                       border.gradient.start_point - segment_rel,
                                       border.gradient.end_point - segment_rel,
                                       gradient_stops,
                                       gradient_stops_count,
                                       border.gradient.extend_mode,
                                       segment.size,
                                       LayerSize::zero());
                 }
             }
             BorderDetails::RadialGradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
+                    let mut info = info.clone();
+                    info.rect = segment;
 
                     self.add_radial_gradient(clip_and_scroll,
-                                             segment,
-                                             local_clip,
+                                             &info,
                                              border.gradient.start_center - segment_rel,
                                              border.gradient.start_radius,
                                              border.gradient.end_center - segment_rel,
                                              border.gradient.end_radius,
                                              border.gradient.ratio_xy,
                                              gradient_stops,
                                              border.gradient.extend_mode,
                                              segment.size,
                                              LayerSize::zero());
                 }
             }
         }
     }
 
     pub fn add_gradient(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
-                        rect: LayerRect,
-                        local_clip: &LocalClip,
+                        info: &LayerPrimitiveInfo,
                         start_point: LayerPoint,
                         end_point: LayerPoint,
                         stops: ItemRange<GradientStop>,
                         stops_count: usize,
                         extend_mode: ExtendMode,
                         tile_size: LayerSize,
                         tile_spacing: LayerSize) {
         let tile_repeat = tile_size + tile_spacing;
-        let is_not_tiled = tile_repeat.width >= rect.size.width &&
-                           tile_repeat.height >= rect.size.height;
+        let is_not_tiled = tile_repeat.width >= info.rect.size.width &&
+                           tile_repeat.height >= info.rect.size.height;
 
         let aligned_and_fills_rect = (start_point.x == end_point.x &&
                                       start_point.y.min(end_point.y) <= 0.0 &&
-                                      start_point.y.max(end_point.y) >= rect.size.height) ||
+                                      start_point.y.max(end_point.y) >= info.rect.size.height) ||
                                      (start_point.y == end_point.y &&
                                       start_point.x.min(end_point.x) <= 0.0 &&
-                                      start_point.x.max(end_point.x) >= rect.size.width);
+                                      start_point.x.max(end_point.x) >= info.rect.size.width);
 
         // Fast path for clamped, axis-aligned gradients, with gradient lines intersecting all of rect:
         let aligned = extend_mode == ExtendMode::Clamp && is_not_tiled && aligned_and_fills_rect;
 
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
         // are also supplied in reverse that the rendered result will be equivalent. To do this,
         // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
         // just designate the reference orientation as start < end. Aligned gradient rendering
@@ -825,23 +833,22 @@ impl FrameBuilder {
         };
 
         let prim = if aligned {
             PrimitiveContainer::AlignedGradient(gradient_cpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu)
         };
 
-        self.add_primitive(clip_and_scroll, &rect, local_clip, Vec::new(), prim);
+        self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
     }
 
     pub fn add_radial_gradient(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
-                               rect: LayerRect,
-                               local_clip: &LocalClip,
+                               info: &LayerPrimitiveInfo,
                                start_center: LayerPoint,
                                start_radius: f32,
                                end_center: LayerPoint,
                                end_radius: f32,
                                ratio_xy: f32,
                                stops: ItemRange<GradientStop>,
                                extend_mode: ExtendMode,
                                tile_size: LayerSize,
@@ -855,32 +862,31 @@ impl FrameBuilder {
             gpu_blocks: [
                 [start_center.x, start_center.y, end_center.x, end_center.y].into(),
                 [start_radius, end_radius, ratio_xy, pack_as_float(extend_mode as u32)].into(),
                 [tile_size.width, tile_size.height, tile_repeat.width, tile_repeat.height].into(),
             ],
         };
 
         self.add_primitive(clip_and_scroll,
-                           &rect,
-                           local_clip,
+                           info,
                            Vec::new(),
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu));
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     run_offset: LayoutVector2D,
-                    rect: LayerRect,
-                    local_clip: &LocalClip,
+                    info: &LayerPrimitiveInfo,
                     font: &FontInstance,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
+        let rect = info.rect;
         // Trivial early out checks
         if font.size.0 <= 0 {
             return
         }
 
         // Sanity check - anything with glyphs bigger than this
         // is probably going to consume too much memory to render
         // efficiently anyway. This is specifically to work around
@@ -966,28 +972,29 @@ impl FrameBuilder {
                 let mut text_prim = prim.clone();
                 text_prim.font.color = shadow_prim.shadow.color.into();
                 text_prim.color = shadow_prim.shadow.color;
                 text_prim.offset += shadow_prim.shadow.offset;
                 fast_text_shadow_prims.push(text_prim);
             }
         }
         for text_prim in fast_text_shadow_prims {
+            let rect = info.rect;
+            let mut info = info.clone();
+            info.rect = rect.translate(&text_prim.offset);
             self.add_primitive(clip_and_scroll,
-                               &rect.translate(&text_prim.offset),
-                               local_clip,
+                               &info,
                                Vec::new(),
                                PrimitiveContainer::TextRun(text_prim));
         }
 
         // Create (and add to primitive store) the primitive that will be
         // used for both the visual element and also the shadow(s).
         let prim_index = self.create_primitive(clip_and_scroll,
-                                               &rect,
-                                               local_clip,
+                                               info,
                                                Vec::new(),
                                                PrimitiveContainer::TextRun(prim));
 
         // Only add a visual element if it can contribute to the scene.
         if color.a > 0.0 {
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
@@ -1010,51 +1017,51 @@ impl FrameBuilder {
                 shadow_metadata.local_rect = shadow_metadata.local_rect.union(&shadow_rect);
                 shadow_prim.primitives.push(prim_index);
             }
         }
     }
 
     pub fn fill_box_shadow_rect(&mut self,
                                 clip_and_scroll: ClipAndScrollInfo,
-                                box_bounds: &LayerRect,
+                                info: &LayerPrimitiveInfo,
                                 bs_rect: LayerRect,
-                                local_clip: &LocalClip,
                                 color: &ColorF,
                                 border_radius: f32,
                                 clip_mode: BoxShadowClipMode) {
         // We can draw a rectangle instead with the proper border radius clipping.
         let (bs_clip_mode, rect_to_draw) = match clip_mode {
             BoxShadowClipMode::Outset |
             BoxShadowClipMode::None => (ClipMode::Clip, bs_rect),
-            BoxShadowClipMode::Inset => (ClipMode::ClipOut, *box_bounds),
+            BoxShadowClipMode::Inset => (ClipMode::ClipOut, info.rect),
         };
 
         let box_clip_mode = !bs_clip_mode;
 
         // Clip the inside and then the outside of the box.
         let border_radius = BorderRadius::uniform(border_radius);
         let extra_clips = vec![ClipSource::RoundedRectangle(bs_rect, border_radius, bs_clip_mode),
-                               ClipSource::RoundedRectangle(*box_bounds, border_radius, box_clip_mode)];
+                               ClipSource::RoundedRectangle(info.rect, border_radius, box_clip_mode)];
 
         let prim = RectanglePrimitive {
             color: *color,
         };
 
+        let mut info = info.clone();
+        info.rect = rect_to_draw;
+
         self.add_primitive(clip_and_scroll,
-                           &rect_to_draw,
-                           local_clip,
+                           &info,
                            extra_clips,
                            PrimitiveContainer::Rectangle(prim));
     }
 
     pub fn add_box_shadow(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
-                          box_bounds: &LayerRect,
-                          local_clip: &LocalClip,
+                          info: &LayerPrimitiveInfo,
                           box_offset: &LayerVector2D,
                           color: &ColorF,
                           blur_radius: f32,
                           spread_radius: f32,
                           border_radius: f32,
                           clip_mode: BoxShadowClipMode) {
         if color.a == 0.0 {
             return
@@ -1063,38 +1070,36 @@ impl FrameBuilder {
         // The local space box shadow rect. It is the element rect
         // translated by the box shadow offset and inflated by the
         // box shadow spread.
         let inflate_amount = match clip_mode {
             BoxShadowClipMode::Outset | BoxShadowClipMode::None => spread_radius,
             BoxShadowClipMode::Inset => -spread_radius,
         };
 
-        let bs_rect = box_bounds.translate(box_offset)
-                                .inflate(inflate_amount, inflate_amount);
+        let bs_rect = info.rect.translate(box_offset)
+                               .inflate(inflate_amount, inflate_amount);
         // If we have negative inflate amounts.
         // Have to explicitly check this since euclid::TypedRect relies on negative rects
         let bs_rect_empty = bs_rect.size.width <= 0.0 || bs_rect.size.height <= 0.0;
 
         // Just draw a rectangle
         if (blur_radius == 0.0 && spread_radius == 0.0 && clip_mode == BoxShadowClipMode::None)
            || bs_rect_empty {
             self.add_solid_rectangle(clip_and_scroll,
-                                     box_bounds,
-                                     local_clip,
+                                     info,
                                      color,
                                      PrimitiveFlags::None);
             return;
         }
 
         if blur_radius == 0.0 && border_radius != 0.0 {
             self.fill_box_shadow_rect(clip_and_scroll,
-                                      box_bounds,
+                                      info,
                                       bs_rect,
-                                      local_clip,
                                       color,
                                       border_radius,
                                       clip_mode);
             return;
         }
 
         // Get the outer rectangle, based on the blur radius.
         let outside_edge_size = 2.0 * blur_radius;
@@ -1112,18 +1117,18 @@ impl FrameBuilder {
         }
 
         let shadow_kind = match clip_mode {
             BoxShadowClipMode::Outset | BoxShadowClipMode::None => {
                 // If a border radius is set, we need to draw inside
                 // the original box in order to draw where the border
                 // corners are. A clip-out mask applied below will
                 // ensure that we don't draw on the box itself.
-                let inner_box_bounds = box_bounds.inflate(-border_radius,
-                                                          -border_radius);
+                let inner_box_bounds = info.rect.inflate(-border_radius,
+                                                         -border_radius);
                 // For outset shadows, subtracting the element rectangle
                 // from the outer rectangle gives the rectangles we need
                 // to draw. In the simple case (no blur radius), we can
                 // just draw these as solid colors.
                 let mut rects = Vec::new();
                 subtract_rect(&outer_rect, &inner_box_bounds, &mut rects);
                 if edge_size == 0.0 {
                     BoxShadowKind::Simple(rects)
@@ -1143,42 +1148,42 @@ impl FrameBuilder {
                 // shader.
                 // TODO(gw): We should be able to optimize the complex
                 //           inset shadow case to touch fewer pixels. We
                 //           can probably calculate the inner rect that
                 //           can't be affected, and subtract that from
                 //           the element rect?
                 let mut rects = Vec::new();
                 if edge_size == 0.0 {
-                    subtract_rect(box_bounds, &bs_rect, &mut rects);
+                    subtract_rect(&info.rect, &bs_rect, &mut rects);
                     BoxShadowKind::Simple(rects)
                 } else {
-                    rects.push(*box_bounds);
+                    rects.push(info.rect);
                     BoxShadowKind::Shadow(rects)
                 }
             }
         };
 
         match shadow_kind {
             BoxShadowKind::Simple(rects) => {
                 for rect in &rects {
+                    let mut info = info.clone();
+                    info.rect = *rect;
                     self.add_solid_rectangle(clip_and_scroll,
-                                             rect,
-                                             local_clip,
+                                             &info,
                                              color,
                                              PrimitiveFlags::None)
                 }
             }
             BoxShadowKind::Shadow(rects) => {
                 assert!(blur_radius > 0.0);
                 if clip_mode == BoxShadowClipMode::Inset {
                     self.fill_box_shadow_rect(clip_and_scroll,
-                                              box_bounds,
+                                              info,
                                               bs_rect,
-                                              local_clip,
                                               color,
                                               border_radius,
                                               clip_mode);
                 }
 
                 let inverted = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => 0.0,
                     BoxShadowClipMode::Inset => 1.0,
@@ -1188,45 +1193,46 @@ impl FrameBuilder {
                 // need a clip out of the center box.
                 let extra_clip_mode = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => ClipMode::ClipOut,
                     BoxShadowClipMode::Inset => ClipMode::Clip,
                 };
 
                 let mut extra_clips = Vec::new();
                 if border_radius >= 0.0 {
-                    extra_clips.push(ClipSource::RoundedRectangle(*box_bounds,
+                    extra_clips.push(ClipSource::RoundedRectangle(info.rect,
                                                                   BorderRadius::uniform(border_radius),
                                                                   extra_clip_mode));
                 }
 
                 let prim_cpu = BoxShadowPrimitiveCpu {
-                    src_rect: *box_bounds,
+                    src_rect: info.rect,
                     bs_rect,
                     color: *color,
                     blur_radius,
                     border_radius,
                     edge_size,
                     inverted,
                     rects,
+                    render_task_id: None,
                 };
 
+                let mut info = info.clone();
+                info.rect = outer_rect;
                 self.add_primitive(clip_and_scroll,
-                                   &outer_rect,
-                                   local_clip,
+                                   &info,
                                    extra_clips,
                                    PrimitiveContainer::BoxShadow(prim_cpu));
             }
         }
     }
 
     pub fn add_image(&mut self,
                      clip_and_scroll: ClipAndScrollInfo,
-                     rect: LayerRect,
-                     local_clip: &LocalClip,
+                     info: &LayerPrimitiveInfo,
                      stretch_size: &LayerSize,
                      tile_spacing: &LayerSize,
                      sub_rect: Option<TexelRect>,
                      image_key: ImageKey,
                      image_rendering: ImageRendering,
                      tile: Option<TileOffset>) {
         let sub_rect_block = sub_rect.unwrap_or(TexelRect::invalid()).into();
 
@@ -1239,26 +1245,24 @@ impl FrameBuilder {
                             stretch_size.height,
                             tile_spacing.width,
                             tile_spacing.height ].into(),
                             sub_rect_block,
                         ],
         };
 
         self.add_primitive(clip_and_scroll,
-                           &rect,
-                           local_clip,
+                           info,
                            Vec::new(),
                            PrimitiveContainer::Image(prim_cpu));
     }
 
     pub fn add_yuv_image(&mut self,
                          clip_and_scroll: ClipAndScrollInfo,
-                         rect: LayerRect,
-                         clip_rect: &LocalClip,
+                         info: &LayerPrimitiveInfo,
                          yuv_data: YuvData,
                          color_space: YuvColorSpace,
                          image_rendering: ImageRendering) {
         let format = yuv_data.get_format();
         let yuv_key = match yuv_data {
             YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::dummy()],
             YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) =>
                 [plane_0, plane_1, plane_2],
@@ -1266,22 +1270,21 @@ impl FrameBuilder {
                 [plane_0, ImageKey::dummy(), ImageKey::dummy()],
         };
 
         let prim_cpu = YuvImagePrimitiveCpu {
             yuv_key,
             format,
             color_space,
             image_rendering,
-            gpu_block: [rect.size.width, rect.size.height, 0.0, 0.0].into(),
+            gpu_block: [info.rect.size.width, info.rect.size.height, 0.0, 0.0].into(),
         };
 
         self.add_primitive(clip_and_scroll,
-                           &rect,
-                           clip_rect,
+                           info,
                            Vec::new(),
                            PrimitiveContainer::YuvImage(prim_cpu));
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
@@ -1349,24 +1352,26 @@ impl FrameBuilder {
             //           for now. We can re-add that code once the clips
             //           data is moved over to the GPU cache!
         }
     }
 
     fn build_render_task(&mut self,
                          clip_scroll_tree: &ClipScrollTree,
                          gpu_cache: &mut GpuCache,
-                         render_tasks: &mut RenderTaskTree)
+                         render_tasks: &mut RenderTaskTree,
+                         output_pipelines: &FastHashSet<PipelineId>)
                          -> RenderTaskId {
         profile_scope!("build_render_task");
 
         let mut next_z = 0;
         let mut sc_stack: Vec<StackingContextIndex> = Vec::new();
         let mut current_task = RenderTask::new_alpha_batch(DeviceIntPoint::zero(),
-                                                           RenderTaskLocation::Fixed);
+                                                           RenderTaskLocation::Fixed,
+                                                           None);
         // A stack of the alpha batcher tasks. We create them on the way down,
         // and then actually populate with items and dependencies on the way up.
         let mut alpha_task_stack = Vec::new();
         // A map of "preserve-3d" contexts. We are baking these into render targets
         // and only compositing once we are out of "preserve-3d" hierarchy.
         // The stacking contexts that fall into this category are
         //  - ones with `ContextIsolation::Items`, for their actual items to be backed
         //  - immediate children of `ContextIsolation::Items`
@@ -1388,44 +1393,52 @@ impl FrameBuilder {
                         continue;
                     }
 
                     debug!("\tpush {:?} {:?}", stacking_context_index, stacking_context.isolation);
 
                     let stacking_context_rect = &stacking_context.screen_bounds;
                     let composite_count = stacking_context.composite_ops.count();
 
+                    // If this stacking context if the root of a pipeline, and the caller
+                    // has requested it as an output frame, create a render task to isolate it.
+                    if stacking_context.is_pipeline_root && output_pipelines.contains(&stacking_context.pipeline_id) {
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect,
+                                                                           Some(stacking_context.pipeline_id));
+                    }
+
                     if stacking_context.isolation == ContextIsolation::Full && composite_count == 0 {
                         alpha_task_stack.push(current_task);
-                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
+                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect, None);
                     }
 
                     if parent_isolation == Some(ContextIsolation::Items) ||
                        stacking_context.isolation == ContextIsolation::Items {
                         if parent_isolation != Some(ContextIsolation::Items) {
                             splitter_stack.push(BspSplitter::new());
                             preserve_3d_map_stack.push(FastHashMap::default());
                         }
                         alpha_task_stack.push(current_task);
-                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
+                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect, None);
                         //Note: technically, we shouldn't make a new alpha task for "preserve-3d" contexts
                         // that have no child items (only other stacking contexts). However, we don't know if
                         // there are any items at this time (in `PushStackingContext`).
                         //Note: the reason we add the polygon for splitting during `Push*` as opposed to `Pop*`
                         // is because we need to preserve the order of drawing for planes that match together.
                         let frame_node = clip_scroll_tree.nodes.get(&stacking_context.reference_frame_id).unwrap();
                         let sc_polygon = make_polygon(stacking_context, frame_node, stacking_context_index.0);
                         debug!("\tsplitter[{}]: add {:?} -> {:?} with bounds {:?}", splitter_stack.len(),
                             stacking_context_index, sc_polygon, stacking_context.isolated_items_bounds);
                         splitter_stack.last_mut().unwrap().add(sc_polygon);
                     }
 
                     for _ in 0..composite_count {
                         alpha_task_stack.push(current_task);
-                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
+                        current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect, None);
                     }
                 }
                 PrimitiveRunCmd::PopStackingContext => {
                     let stacking_context_index = sc_stack.pop().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
                     let composite_count = stacking_context.composite_ops.count();
 
                     if !stacking_context.is_visible {
@@ -1511,16 +1524,29 @@ impl FrameBuilder {
                             ];
                             let handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
                             let item = AlphaRenderItem::SplitComposite(sc_index, task_id, handle, next_z);
                             current_task.as_alpha_batch_mut().items.push(item);
                         }
                         preserve_3d_map_stack.pop();
                         next_z += 1;
                     }
+
+                    if stacking_context.is_pipeline_root && output_pipelines.contains(&stacking_context.pipeline_id) {
+                        let mut prev_task = alpha_task_stack.pop().unwrap();
+                        let current_task_id = render_tasks.add(current_task);
+                        let item = AlphaRenderItem::HardwareComposite(stacking_context_index,
+                                                                      current_task_id,
+                                                                      HardwareCompositeOp::PremultipliedAlpha,
+                                                                      next_z);
+                        next_z += 1;
+                        prev_task.as_alpha_batch_mut().items.push(item);
+                        prev_task.children.push(current_task_id);
+                        current_task = prev_task;
+                    }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     if !self.stacking_context_store[stacking_context_index.0].is_visible {
                         continue;
                     }
 
                     let group_index = *self.clip_scroll_group_indices.get(&clip_and_scroll).unwrap();
@@ -1530,26 +1556,17 @@ impl FrameBuilder {
                     }
 
                     debug!("\trun of {} items", prim_count);
 
                     for i in 0..prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
                         if self.prim_store.cpu_bounding_rects[prim_index.0].is_some() {
-                            let prim_metadata = self.prim_store.get_metadata(prim_index);
-
-                            // Add any dynamic render tasks needed to render this primitive
-                            if let Some(render_task_id) = prim_metadata.render_task_id {
-                                current_task.children.push(render_task_id);
-                            }
-                            if let Some(clip_task_id) = prim_metadata.clip_task_id {
-                                current_task.children.push(clip_task_id);
-                            }
-
+                            self.prim_store.add_render_tasks_for_prim(prim_index, &mut current_task);
                             let item = AlphaRenderItem::Primitive(Some(group_index), prim_index, next_z);
                             current_task.as_alpha_batch_mut().items.push(item);
                             next_z += 1;
                         }
                     }
                 }
             }
         }
@@ -1561,16 +1578,17 @@ impl FrameBuilder {
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  gpu_cache: &mut GpuCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &mut ClipScrollTree,
                  display_lists: &DisplayListMap,
                  device_pixel_ratio: f32,
+                 output_pipelines: &FastHashSet<PipelineId>,
                  texture_cache_profile: &mut TextureCacheProfileCounters,
                  gpu_cache_profile: &mut GpuCacheProfileCounters)
                  -> Frame {
         profile_scope!("build");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters.total_primitives.set(self.prim_store.prim_count());
 
@@ -1597,17 +1615,20 @@ impl FrameBuilder {
                                                       clip_scroll_tree,
                                                       display_lists,
                                                       resource_cache,
                                                       gpu_cache,
                                                       &mut render_tasks,
                                                       &mut profile_counters,
                                                       device_pixel_ratio);
 
-        let main_render_task_id = self.build_render_task(clip_scroll_tree, gpu_cache, &mut render_tasks);
+        let main_render_task_id = self.build_render_task(clip_scroll_tree,
+                                                         gpu_cache,
+                                                         &mut render_tasks,
+                                                         output_pipelines);
 
         let mut required_pass_count = 0;
         render_tasks.max_depth(main_render_task_id, 0, &mut required_pass_count);
 
         resource_cache.block_until_all_resources_added(gpu_cache, texture_cache_profile);
 
         let mut deferred_resolves = vec![];
 
@@ -1626,17 +1647,17 @@ impl FrameBuilder {
             let ctx = RenderTargetContext {
                 device_pixel_ratio,
                 stacking_context_store: &self.stacking_context_store,
                 clip_scroll_group_store: &self.clip_scroll_group_store,
                 prim_store: &self.prim_store,
                 resource_cache,
             };
 
-            pass.build(&ctx, gpu_cache, &mut render_tasks, &mut deferred_resolves);
+            pass.build(&ctx, gpu_cache, &mut render_tasks, &mut deferred_resolves, &self.clip_store);
 
             profile_counters.passes.inc();
             profile_counters.color_targets.add(pass.color_targets.target_count());
             profile_counters.alpha_targets.add(pass.alpha_targets.target_count());
         }
 
         let gpu_cache_updates = gpu_cache.end_frame(gpu_cache_profile);
 
@@ -1759,20 +1780,21 @@ impl<'a> LayerRectCalculationAndCullingP
 
                 packed_layer.set_rect(&local_viewport_rect,
                                       self.screen_rect,
                                       self.device_pixel_ratio)
             } else {
                 None
             };
 
-            node_clip_info.clip_sources.update(&transform,
-                                               self.gpu_cache,
-                                               self.resource_cache,
-                                               self.device_pixel_ratio);
+            let clip_sources = self.frame_builder.clip_store.get_mut(&node_clip_info.clip_sources);
+            clip_sources.update(&transform,
+                                self.gpu_cache,
+                                self.resource_cache,
+                                self.device_pixel_ratio);
         }
     }
 
     fn recalculate_clip_scroll_groups(&mut self) {
         debug!("recalculate_clip_scroll_groups");
         for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
             let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
             let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
@@ -1871,41 +1893,52 @@ impl<'a> LayerRectCalculationAndCullingP
             current_id = node.parent;
 
             let clip = match node.node_type {
                 NodeType::ReferenceFrame(ref info) => {
                     // if the transform is non-aligned, bake the next LCCR into the clip mask
                     next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
                     continue
                 },
-                NodeType::Clip(ref clip) if clip.clip_sources.is_masking() => clip,
-                _ => continue,
+                NodeType::Clip(ref clip) => {
+                    clip
+                }
+                NodeType::StickyFrame(..) | NodeType::ScrollFrame(..) => {
+                    continue;
+                }
             };
 
+            let clip_sources = self.frame_builder.clip_store.get(&clip.clip_sources);
+            if !clip_sources.is_masking() {
+                continue;
+            }
+
             // apply the screen bounds of the clip node
             //Note: these are based on the local combined viewport, so can be tighter
             if let Some((_kind, ref screen_rect)) = clip.screen_bounding_rect {
                 bounding_rect = match bounding_rect.intersection(screen_rect) {
                     Some(rect) => rect,
                     None => return None,
                 }
             }
 
-            let clip_info = clip.clip_sources.clone_mask_cache_info(next_node_needs_region_mask);
-
             // apply the outer device bounds of the clip stack
-            if let Some(ref outer) = clip_info.bounds.outer {
+            if let Some(ref outer) = clip_sources.bounds.outer {
                 bounding_rect = match bounding_rect.intersection(&outer.device_rect) {
                     Some(rect) => rect,
                     None => return None,
                 }
             }
 
             //TODO-LCCR: bake a single LCCR instead of all aligned rects?
-            self.current_clip_stack.push((clip.packed_layer_index, clip_info));
+            self.current_clip_stack.push(ClipWorkItem {
+                layer_index: clip.packed_layer_index,
+                clip_sources: self.frame_builder.clip_store.create_weak_handle(&clip.clip_sources),
+                apply_rectangles: next_node_needs_region_mask,
+            });
             next_node_needs_region_mask = false;
         }
 
         self.current_clip_stack.reverse();
         self.current_clip_info = Some((clip_id, Some(bounding_rect)));
         Some(bounding_rect)
     }
 
@@ -1945,16 +1978,20 @@ impl<'a> LayerRectCalculationAndCullingP
 
         let stacking_context =
             &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
         let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
         let display_list = self.display_lists.get(&pipeline_id)
                                              .expect("No display list?");
         debug!("\tclip_bounds {:?}, layer_local_clip {:?}", clip_bounds, packed_layer.local_clip_rect);
 
+        if !stacking_context.is_backface_visible && packed_layer.transform.is_backface_visible() {
+            return;
+        }
+
         for i in 0..prim_count {
             let prim_index = PrimitiveIndex(base_prim_index.0 + i);
             let prim_store = &mut self.frame_builder.prim_store;
             let (prim_local_rect, prim_screen_rect) = match prim_store
                 .build_bounding_rect(prim_index,
                                      &clip_bounds,
                                      &packed_layer.transform,
                                      &packed_layer.local_clip_rect,
@@ -1967,54 +2004,60 @@ impl<'a> LayerRectCalculationAndCullingP
 
             let prim_metadata = prim_store.prepare_prim_for_render(prim_index,
                                                                    self.resource_cache,
                                                                    self.gpu_cache,
                                                                    &packed_layer.transform,
                                                                    self.device_pixel_ratio,
                                                                    display_list,
                                                                    TextRunMode::Normal,
-                                                                   &mut self.render_tasks);
+                                                                   &mut self.render_tasks,
+                                                                   &mut self.frame_builder.clip_store);
 
             stacking_context.screen_bounds = stacking_context.screen_bounds.union(&prim_screen_rect);
             stacking_context.isolated_items_bounds = stacking_context.isolated_items_bounds.union(&prim_local_rect);
 
             // Try to create a mask if we may need to.
-            let clip_task = if prim_metadata.clips.is_masking() {
-                let info = prim_metadata.clips.clone_mask_cache_info(false);
-
+            let prim_clips = self.frame_builder.clip_store.get(&prim_metadata.clip_sources);
+            let clip_task = if prim_clips.is_masking() {
                 // Take into account the actual clip info of the primitive, and
                 // mutate the current bounds accordingly.
-                let mask_rect = match info.bounds.outer {
+                let mask_rect = match prim_clips.bounds.outer {
                     Some(ref outer) => {
                         match prim_screen_rect.intersection(&outer.device_rect) {
                             Some(rect) => rect,
                             None => continue,
                         }
                     }
                     _ => prim_screen_rect,
                 };
 
-                let extra = (packed_layer_index, info);
+                let extra = ClipWorkItem {
+                    layer_index: packed_layer_index,
+                    clip_sources: self.frame_builder.clip_store.create_weak_handle(&prim_metadata.clip_sources),
+                    apply_rectangles: false,
+                };
 
                 RenderTask::new_mask(None,
                                      mask_rect,
                                      &self.current_clip_stack,
                                      Some(extra),
-                                     prim_screen_rect)
+                                     prim_screen_rect,
+                                     &self.frame_builder.clip_store)
             } else if !self.current_clip_stack.is_empty() {
                 // If the primitive doesn't have a specific clip, key the task ID off the
                 // stacking context. This means that two primitives which are only clipped
                 // by the stacking context stack can share clip masks during render task
                 // assignment to targets.
                 RenderTask::new_mask(Some(clip_and_scroll.clip_node_id()),
                                      clip_bounds,
                                      &self.current_clip_stack,
                                      None,
-                                     prim_screen_rect)
+                                     prim_screen_rect,
+                                     &self.frame_builder.clip_store)
             } else {
                 None
             };
 
             let render_tasks = &mut self.render_tasks;
             prim_metadata.clip_task_id = clip_task.map(|clip_task| {
                 render_tasks.add(clip_task)
             });
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -1,27 +1,38 @@
 /* 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::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)]
 struct Epoch(u32);
 
 #[derive(Debug)]
 pub struct FreeListHandle<T> {
     index: u32,
     _marker: PhantomData<T>,
 }
 
+impl<T> Clone for WeakFreeListHandle<T> {
+    fn clone(&self) -> WeakFreeListHandle<T> {
+        WeakFreeListHandle {
+            index: self.index,
+            epoch: self.epoch,
+            _marker: PhantomData,
+        }
+    }
+}
+
 #[derive(Debug)]
 pub struct WeakFreeListHandle<T> {
     index: u32,
     epoch: Epoch,
     _marker: PhantomData<T>,
 }
 
 struct Slot<T> {
@@ -43,16 +54,23 @@ pub enum UpsertResult<T> {
 impl<T> FreeList<T> {
     pub fn new() -> FreeList<T> {
         FreeList {
             slots: Vec::new(),
             free_list_head: None,
         }
     }
 
+    pub fn recycle(self) -> FreeList<T> {
+        FreeList {
+            slots: recycle_vec(self.slots),
+            free_list_head: None,
+        }
+    }
+
     #[allow(dead_code)]
     pub fn get(&self, id: &FreeListHandle<T>) -> &T {
         self.slots[id.index as usize]
             .value
             .as_ref()
             .unwrap()
     }
 
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,16 +1,143 @@
 /* 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 gpu_cache::GpuCacheAddress;
 use render_task::RenderTaskAddress;
+use tiling::PackedLayerIndex;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
+#[derive(Debug, Copy, Clone)]
+pub struct PackedLayerAddress(i32);
+
+impl From<PackedLayerIndex> for PackedLayerAddress {
+    fn from(index: PackedLayerIndex) -> PackedLayerAddress {
+        PackedLayerAddress(index.0 as i32)
+    }
+}
+
 // Instance structure for box shadows being drawn into target cache.
 #[derive(Debug)]
 #[repr(C)]
 pub struct BoxShadowCacheInstance {
     pub prim_address: GpuCacheAddress,
     pub task_index: RenderTaskAddress,
 }
+
+#[repr(i32)]
+#[derive(Debug)]
+pub enum BlurDirection {
+    Horizontal = 0,
+    Vertical,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+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)]
+#[repr(C)]
+pub struct ClipMaskInstance {
+    pub render_task_address: RenderTaskAddress,
+    pub layer_address: PackedLayerAddress,
+    pub segment: i32,
+    pub clip_data_address: GpuCacheAddress,
+    pub resource_address: GpuCacheAddress,
+}
+
+// 32 bytes per instance should be enough for anyone!
+#[derive(Debug, Clone)]
+pub struct PrimitiveInstance {
+    data: [i32; 8],
+}
+
+pub struct SimplePrimitiveInstance {
+    pub specific_prim_address: GpuCacheAddress,
+    pub task_address: RenderTaskAddress,
+    pub clip_task_address: RenderTaskAddress,
+    pub layer_address: PackedLayerAddress,
+    pub z_sort_index: i32,
+}
+
+impl SimplePrimitiveInstance {
+    pub fn new(specific_prim_address: GpuCacheAddress,
+               task_address: RenderTaskAddress,
+               clip_task_address: RenderTaskAddress,
+               layer_address: PackedLayerAddress,
+               z_sort_index: i32) -> SimplePrimitiveInstance {
+        SimplePrimitiveInstance {
+            specific_prim_address,
+            task_address,
+            clip_task_address,
+            layer_address,
+            z_sort_index,
+        }
+    }
+
+    pub fn build(&self, data0: i32, data1: i32, data2: i32) -> PrimitiveInstance {
+        PrimitiveInstance {
+            data: [
+                self.specific_prim_address.as_int(),
+                self.task_address.0 as i32,
+                self.clip_task_address.0 as i32,
+                self.layer_address.0,
+                self.z_sort_index,
+                data0,
+                data1,
+                data2,
+            ]
+        }
+    }
+}
+
+pub struct CompositePrimitiveInstance {
+    pub task_address: RenderTaskAddress,
+    pub src_task_address: RenderTaskAddress,
+    pub backdrop_task_address: RenderTaskAddress,
+    pub data0: i32,
+    pub data1: i32,
+    pub z: i32,
+}
+
+impl CompositePrimitiveInstance {
+    pub fn new(task_address: RenderTaskAddress,
+               src_task_address: RenderTaskAddress,
+               backdrop_task_address: RenderTaskAddress,
+               data0: i32,
+               data1: i32,
+               z: i32) -> CompositePrimitiveInstance {
+        CompositePrimitiveInstance {
+            task_address,
+            src_task_address,
+            backdrop_task_address,
+            data0,
+            data1,
+            z,
+        }
+    }
+}
+
+impl From<CompositePrimitiveInstance> for PrimitiveInstance {
+    fn from(instance: CompositePrimitiveInstance) -> PrimitiveInstance {
+        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,
+                0,
+                0,
+            ]
+        }
+    }
+}
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -69,16 +69,24 @@ impl BatchTextures {
         BatchTextures {
             colors: [
                 SourceTexture::CacheRGBA8,
                 SourceTexture::CacheA8,
                 SourceTexture::Invalid,
             ]
         }
     }
+
+    pub fn color(texture: SourceTexture) -> Self {
+        BatchTextures {
+            colors: [ texture,
+                      SourceTexture::Invalid,
+                      SourceTexture::Invalid ]
+        }
+    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RenderTargetMode {
     None,
     RenderTarget,
 }
 
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -64,17 +64,16 @@ mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
 mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
 mod gpu_types;
 mod internal_types;
-mod mask_cache;
 mod prim_store;
 mod print_tree;
 mod profiler;
 mod record;
 mod render_backend;
 mod render_task;
 mod renderer;
 mod resource_cache;
@@ -144,15 +143,16 @@ extern crate serde_json;
 #[macro_use]
 extern crate serde_derive;
 
 #[cfg(any(target_os="macos", target_os="windows"))]
 extern crate gamma_lut;
 
 pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, ReadPixelsFormat, Renderer, RendererOptions};
-pub use renderer::{CpuProfile, GpuProfile, DebugFlags, RendererKind};
-pub use renderer::{MAX_VERTEX_TEXTURE_WIDTH, PROFILER_DBG, RENDER_TARGET_DBG, TEXTURE_CACHE_DBG};
+pub use renderer::{CpuProfile, GpuProfile, DebugFlags, OutputImageHandler, RendererKind};
+pub use renderer::{PROFILER_DBG, RENDER_TARGET_DBG, TEXTURE_CACHE_DBG, ALPHA_PRIM_DBG};
+pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 
 pub use webrender_api as api;
 
 #[doc(hidden)]
 pub use device::build_shader_strings;
deleted file mode 100644
--- a/gfx/webrender/src/mask_cache.rs
+++ /dev/null
@@ -1,266 +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/. */
-
-use api::{DeviceIntRect, ImageMask, LayerPoint, LayerRect};
-use api::{LayerSize, LayerToWorldTransform};
-use border::BorderCornerClipSource;
-use clip::{ClipMode, ClipSource};
-use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
-use prim_store::{CLIP_DATA_GPU_BLOCKS, ClipData, ImageMaskData};
-use util::{extract_inner_rect_safe, TransformedRect};
-
-const MAX_CLIP: f32 = 1000000.0;
-
-#[derive(Debug, Copy, Clone)]
-pub struct ClipAddressRange {
-    pub location: GpuCacheHandle,
-    item_count: usize,
-}
-
-impl ClipAddressRange {
-    fn new(count: usize) -> Self {
-        ClipAddressRange {
-            location: GpuCacheHandle::new(),
-            item_count: count,
-        }
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.item_count == 0
-    }
-
-    pub fn get_count(&self) -> usize {
-        self.item_count
-    }
-
-    fn get_block_count(&self) -> Option<usize> {
-        if self.item_count != 0 {
-            Some(self.item_count * CLIP_DATA_GPU_BLOCKS)
-        } else {
-            None
-        }
-    }
-}
-
-/// Represents a local rect and a device space
-/// rectangles that are either outside or inside bounds.
-#[derive(Clone, Debug, PartialEq)]
-pub struct Geometry {
-    pub local_rect: LayerRect,
-    pub device_rect: DeviceIntRect,
-}
-
-impl From<LayerRect> for Geometry {
-    fn from(local_rect: LayerRect) -> Self {
-        Geometry {
-            local_rect,
-            device_rect: DeviceIntRect::zero(),
-        }
-    }
-}
-
-/// Depending on the complexity of the clip, we may either
-/// know the outer and/or inner rect, or neither or these.
-/// In the case of a clip-out, we currently set the mask
-/// bounds to be unknown. This is conservative, but ensures
-/// correctness. In the future we can make this a lot
-/// more clever with some proper region handling.
-#[derive(Clone, Debug, PartialEq)]
-pub struct MaskBounds {
-    pub outer: Option<Geometry>,
-    pub inner: Option<Geometry>,
-}
-
-impl MaskBounds {
-    pub fn update(&mut self, transform: &LayerToWorldTransform, device_pixel_ratio: f32) {
-        if let Some(ref mut outer) = self.outer {
-            let transformed = TransformedRect::new(&outer.local_rect,
-                                                   transform,
-                                                   device_pixel_ratio);
-            outer.device_rect = transformed.bounding_rect;
-        }
-        if let Some(ref mut inner) = self.inner {
-            let transformed = TransformedRect::new(&inner.local_rect,
-                                                   transform,
-                                                   device_pixel_ratio);
-            inner.device_rect = transformed.inner_rect;
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct MaskCacheInfo {
-    /// Clip items that are always applied
-    pub complex_clip_range: ClipAddressRange,
-    /// Clip items that are only applied if the clip space is transformed from
-    /// the local space of target primitive/layer.
-    pub layer_clip_range: ClipAddressRange,
-    pub image: Option<(ImageMask, GpuCacheHandle)>,
-    pub border_corners: Vec<(BorderCornerClipSource, GpuCacheHandle)>,
-    pub bounds: MaskBounds,
-}
-
-impl MaskCacheInfo {
-    /// Create a new mask cache info. It allocates the GPU store data but leaves
-    /// it uninitialized for the following `update()` call to deal with.
-    pub fn new(clips: &[ClipSource]) -> MaskCacheInfo {
-        let mut image = None;
-        let mut border_corners = Vec::new();
-        let mut complex_clip_count = 0;
-        let mut layer_clip_count = 0;
-
-        // Work out how much clip data space we need to allocate
-        // and if we have an image mask.
-        for clip in clips {
-            match *clip {
-                ClipSource::RoundedRectangle(..) => {
-                    complex_clip_count += 1;
-                }
-                ClipSource::Rectangle(..) => {
-                    layer_clip_count += 1;
-                }
-                ClipSource::Image(image_mask) => {
-                    debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
-                    image = Some((image_mask, GpuCacheHandle::new()));
-                }
-                ClipSource::BorderCorner(ref source) => {
-                    border_corners.push((source.clone(), GpuCacheHandle::new()));
-                }
-            }
-        }
-
-        MaskCacheInfo {
-            complex_clip_range: ClipAddressRange::new(complex_clip_count),
-            layer_clip_range: ClipAddressRange::new(layer_clip_count),
-            image,
-            border_corners,
-            bounds: MaskBounds {
-                inner: None,
-                outer: None,
-            },
-        }
-    }
-
-    pub fn update(&mut self,
-                  sources: &[ClipSource],
-                  transform: &LayerToWorldTransform,
-                  gpu_cache: &mut GpuCache,
-                  device_pixel_ratio: f32)
-                  -> &MaskBounds {
-        // Step[1] - compute the local bounds
-        //TODO: move to initialization stage?
-        if self.bounds.inner.is_none() {
-            let mut local_rect = Some(LayerRect::new(LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
-                                                     LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP)));
-            let mut local_inner = local_rect;
-            let mut has_clip_out = false;
-            let has_border_clip = !self.border_corners.is_empty();
-
-            for source in sources {
-                match *source {
-                    ClipSource::Image(ref mask) => {
-                        if !mask.repeat {
-                            local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
-                        }
-                        local_inner = None;
-                    }
-                    ClipSource::Rectangle(rect) => {
-                        local_rect = local_rect.and_then(|r| r.intersection(&rect));
-                        local_inner = local_inner.and_then(|r| r.intersection(&rect));
-                    }
-                    ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
-                        // Once we encounter a clip-out, we just assume the worst
-                        // case clip mask size, for now.
-                        if mode == ClipMode::ClipOut {
-                            has_clip_out = true;
-                            break;
-                        }
-
-                        local_rect = local_rect.and_then(|r| r.intersection(rect));
-
-                        let inner_rect = extract_inner_rect_safe(rect, radius);
-                        local_inner = local_inner.and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
-                    }
-                    ClipSource::BorderCorner{..} => {}
-                }
-            }
-
-            // Work out the type of mask geometry we have, based on the
-            // list of clip sources above.
-            self.bounds = if has_clip_out || has_border_clip {
-                // For clip-out, the mask rect is not known.
-                MaskBounds {
-                    outer: None,
-                    inner: Some(LayerRect::zero().into()),
-                }
-            } else {
-                MaskBounds {
-                    outer: Some(local_rect.unwrap_or(LayerRect::zero()).into()),
-                    inner: Some(local_inner.unwrap_or(LayerRect::zero()).into()),
-                }
-            };
-        }
-
-        // Step[2] - update GPU cache data
-
-        if let Some(block_count) = self.complex_clip_range.get_block_count() {
-            if let Some(mut request) = gpu_cache.request(&mut self.complex_clip_range.location) {
-                for source in sources {
-                    if let ClipSource::RoundedRectangle(ref rect, ref radius, mode) = *source {
-                        let data = ClipData::rounded_rect(rect, radius, mode);
-                        data.write(&mut request);
-                    }
-                }
-                assert_eq!(request.close(), block_count);
-            }
-        }
-
-        if let Some(block_count) = self.layer_clip_range.get_block_count() {
-            if let Some(mut request) = gpu_cache.request(&mut self.layer_clip_range.location) {
-                for source in sources {
-                    if let ClipSource::Rectangle(rect) = *source {
-                        let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
-                        data.write(&mut request);
-                    }
-                }
-                assert_eq!(request.close(), block_count);
-            }
-        }
-
-        for &mut (ref mut border_source, ref mut gpu_location) in &mut self.border_corners {
-            if let Some(request) = gpu_cache.request(gpu_location) {
-                border_source.write(request);
-            }
-        }
-
-        if let Some((ref mask, ref mut gpu_location)) = self.image {
-            if let Some(request) = gpu_cache.request(gpu_location) {
-                let data = ImageMaskData {
-                    local_rect: mask.rect,
-                };
-                data.write_gpu_blocks(request);
-            }
-        }
-
-        // Step[3] - update the screen bounds
-        self.bounds.update(transform, device_pixel_ratio);
-        &self.bounds
-    }
-
-    /// Check if this `MaskCacheInfo` actually carries any masks.
-    pub fn is_masking(&self) -> bool {
-        self.image.is_some() ||
-        self.complex_clip_range.item_count != 0 ||
-        self.layer_clip_range.item_count != 0 ||
-        !self.border_corners.is_empty()
-    }
-
-    /// Return a clone of this object without any layer-aligned clip items
-    pub fn strip_aligned(&self) -> Self {
-        MaskCacheInfo {
-            layer_clip_range: ClipAddressRange::new(0),
-            .. self.clone()
-        }
-    }
-}
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -4,28 +4,25 @@
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{BorderRadius, ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
 use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
-use clip::{ClipMode, ClipSources};
+use clip::{ClipMode, ClipSourcesHandle, ClipStore};
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
 use util::{pack_as_float, TransformedRect, recycle_vec};
 
-
-pub const CLIP_DATA_GPU_BLOCKS: usize = 10;
-
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
 
 impl PrimitiveOpacity {
     pub fn opaque() -> PrimitiveOpacity {
         PrimitiveOpacity {
@@ -116,49 +113,45 @@ pub enum PrimitiveKind {
     RadialGradient,
     BoxShadow,
     TextShadow,
     Line,
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
-        let address = gpu_cache.get_address(self);
+        gpu_cache.get_address(self).as_int()
+    }
+}
 
+impl GpuCacheAddress {
+    pub fn as_int(&self) -> i32 {
         // TODO(gw): Temporarily encode GPU Cache addresses as a single int.
         //           In the future, we can change the PrimitiveInstance struct
         //           to use 2x u16 for the vertex attribute instead of an i32.
-        address.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + address.u as i32
+        self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32
     }
 }
 
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
     pub opacity: PrimitiveOpacity,
-    pub clips: ClipSources,
+    pub clip_sources: ClipSourcesHandle,
     pub prim_kind: PrimitiveKind,
     pub cpu_prim_index: SpecificPrimitiveIndex,
     pub gpu_location: GpuCacheHandle,
-    // An optional render task that is a dependency of
-    // drawing this primitive. For instance, box shadows
-    // use this to draw a portion of the box shadow to
-    // a render target to reduce the number of pixels
-    // that the box-shadow shader needs to run on. For
-    // text-shadow, this creates a render task chain
-    // that implements a 2-pass separable blur on a
-    // text run.
-    pub render_task_id: Option<RenderTaskId>,
     pub clip_task_id: Option<RenderTaskId>,
 
     // TODO(gw): In the future, we should just pull these
     //           directly from the DL item, instead of
     //           storing them here.
     pub local_rect: LayerRect,
     pub local_clip_rect: LayerRect,
+    pub is_backface_visible: bool,
 }
 
 impl PrimitiveMetadata {
     pub fn needs_clipping(&self) -> bool {
         self.clip_task_id.is_some()
     }
 }
 
@@ -253,16 +246,17 @@ pub struct BoxShadowPrimitiveCpu {
     pub src_rect: LayerRect,
     pub bs_rect: LayerRect,
     pub color: ColorF,
     pub border_radius: f32,
     pub edge_size: f32,
     pub blur_radius: f32,
     pub inverted: f32,
     pub rects: Vec<LayerRect>,
+    pub render_task_id: Option<RenderTaskId>,
 }
 
 impl ToGpuBlocks for BoxShadowPrimitiveCpu {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.push(self.src_rect);
         request.push(self.bs_rect);
         request.push(self.color);
         request.push([self.border_radius,
@@ -488,16 +482,17 @@ impl RadialGradientPrimitiveCpu {
         gradient_builder.build(false, &mut request);
     }
 }
 
 #[derive(Debug)]
 pub struct TextShadowPrimitiveCpu {
     pub shadow: TextShadow,
     pub primitives: Vec<PrimitiveIndex>,
+    pub render_task_id: Option<RenderTaskId>,
 }
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font: FontInstance,
     pub offset: LayerVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
@@ -799,196 +794,197 @@ impl PrimitiveStore {
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
             cpu_lines: recycle_vec(self.cpu_lines),
         }
     }
 
     pub fn add_primitive(&mut self,
                          local_rect: &LayerRect,
                          local_clip_rect: &LayerRect,
-                         clips: ClipSources,
+                         is_backface_visible: bool,
+                         clip_sources: ClipSourcesHandle,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.cpu_metadata.len();
         self.cpu_bounding_rects.push(None);
 
         let metadata = match container {
             PrimitiveContainer::Rectangle(rect) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::from_alpha(rect.color.a),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::Rectangle,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
             PrimitiveContainer::Line(line) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::Line,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_lines.push(line);
                 metadata
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
             PrimitiveContainer::TextShadow(text_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::TextShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_text_shadows.push(text_shadow);
                 metadata
             }
             PrimitiveContainer::Image(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::YuvImage(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::opaque(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::YuvImage,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_yuv_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_borders.push(border_cpu);
                 metadata
             }
             PrimitiveContainer::AlignedGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::AlignedGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::AngleGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::RadialGradient(radial_gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::RadialGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_radial_gradients.push(radial_gradient_cpu);
                 metadata
             }
             PrimitiveContainer::BoxShadow(box_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    clips,
+                    clip_sources,
                     prim_kind: PrimitiveKind::BoxShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_box_shadows.len()),
                     gpu_location: GpuCacheHandle::new(),
-                    render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
+                    is_backface_visible: is_backface_visible,
                 };
 
                 self.cpu_box_shadows.push(box_shadow);
                 metadata
             }
         };
 
         self.cpu_metadata.push(metadata);
@@ -1006,41 +1002,85 @@ impl PrimitiveStore {
 
     pub fn build_bounding_rect(&mut self,
                                prim_index: PrimitiveIndex,
                                screen_rect: &DeviceIntRect,
                                layer_transform: &LayerToWorldTransform,
                                layer_combined_local_clip_rect: &LayerRect,
                                device_pixel_ratio: f32) -> Option<(LayerRect, DeviceIntRect)> {
         let metadata = &self.cpu_metadata[prim_index.0];
+
+        if !metadata.is_backface_visible && layer_transform.is_backface_visible() {
+            return None;
+        }
+
         let local_rect = metadata.local_rect
                                  .intersection(&metadata.local_clip_rect)
                                  .and_then(|rect| rect.intersection(layer_combined_local_clip_rect));
 
         let bounding_rect = local_rect.and_then(|local_rect| {
             let xf_rect = TransformedRect::new(&local_rect,
                                                layer_transform,
                                                device_pixel_ratio);
             xf_rect.bounding_rect.intersection(screen_rect)
         });
 
         self.cpu_bounding_rects[prim_index.0] = bounding_rect;
         bounding_rect.map(|screen_bound| (local_rect.unwrap(), screen_bound))
     }
 
+    /// Add any task dependencies for this primitive to the provided task.
+    pub fn add_render_tasks_for_prim(&self,
+                                     prim_index: PrimitiveIndex,
+                                     task: &mut RenderTask) {
+        // Add any dynamic render tasks needed to render this primitive
+        let metadata = &self.cpu_metadata[prim_index.0];
+
+        let render_task_id = match metadata.prim_kind {
+            PrimitiveKind::BoxShadow => {
+                let box_shadow = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
+                box_shadow.render_task_id
+            }
+            PrimitiveKind::TextShadow => {
+                let text_shadow = &self.cpu_text_shadows[metadata.cpu_prim_index.0];
+                text_shadow.render_task_id
+            }
+            PrimitiveKind::Rectangle |
+            PrimitiveKind::TextRun |
+            PrimitiveKind::Image |
+            PrimitiveKind::AlignedGradient |
+            PrimitiveKind::YuvImage |
+            PrimitiveKind::Border |
+            PrimitiveKind::AngleGradient |
+            PrimitiveKind::RadialGradient |
+            PrimitiveKind::Line => {
+                None
+            }
+        };
+
+        if let Some(render_task_id) = render_task_id {
+            task.children.push(render_task_id);
+        }
+
+        if let Some(clip_task_id) = metadata.clip_task_id {
+            task.children.push(clip_task_id);
+        }
+    }
+
     /// Returns true if the bounding box needs to be updated.
     pub fn prepare_prim_for_render(&mut self,
                                    prim_index: PrimitiveIndex,
                                    resource_cache: &mut ResourceCache,
                                    gpu_cache: &mut GpuCache,
                                    layer_transform: &LayerToWorldTransform,
                                    device_pixel_ratio: f32,
                                    display_list: &BuiltDisplayList,
                                    text_run_mode: TextRunMode,
-                                   render_tasks: &mut RenderTaskTree)
+                                   render_tasks: &mut RenderTaskTree,
+                                   clip_store: &mut ClipStore)
                                    -> &mut PrimitiveMetadata {
         let (prim_kind, cpu_prim_index) = {
             let metadata = &self.cpu_metadata[prim_index.0];
             (metadata.prim_kind, metadata.cpu_prim_index)
         };
 
         // Recurse into any sub primitives and prepare them for rendering first.
         // TODO(gw): This code is a bit hacky to work around the borrow checker.
@@ -1051,39 +1091,40 @@ impl PrimitiveStore {
             for sub_prim_index in self.cpu_text_shadows[cpu_prim_index.0].primitives.clone() {
                 self.prepare_prim_for_render(sub_prim_index,
                                              resource_cache,
                                              gpu_cache,
                                              layer_transform,
                                              device_pixel_ratio,
                                              display_list,
                                              TextRunMode::Shadow,
-                                             render_tasks);
+                                             render_tasks,
+                                             clip_store);
             }
         }
 
         let metadata = &mut self.cpu_metadata[prim_index.0];
-        metadata.clips.update(layer_transform,
-                              gpu_cache,
-                              resource_cache,
-                              device_pixel_ratio);
+        clip_store.get_mut(&metadata.clip_sources).update(layer_transform,
+                                                          gpu_cache,
+                                                          resource_cache,
+                                                          device_pixel_ratio);
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
             PrimitiveKind::Border |
             PrimitiveKind::Line => {}
             PrimitiveKind::BoxShadow => {
                 // TODO(gw): Account for zoom factor!
                 // Here, we calculate the size of the patch required in order
                 // to create the box shadow corner. First, scale it by the
                 // device pixel ratio since the cache shader expects vertices
                 // in device space. The shader adds a 1-pixel border around
                 // the patch, in order to prevent bilinear filter artifacts as
                 // the patch is clamped / mirrored across the box shadow rect.
-                let box_shadow = &self.cpu_box_shadows[cpu_prim_index.0];
+                let box_shadow = &mut self.cpu_box_shadows[cpu_prim_index.0];
                 let edge_size = box_shadow.edge_size.ceil() * device_pixel_ratio;
                 let edge_size = edge_size as i32 + 2;   // Account for bilinear filtering
                 let cache_size = DeviceIntSize::new(edge_size, edge_size);
 
                 let cache_key = BoxShadowPrimitiveCacheKey {
                     blur_radius: Au::from_f32_px(box_shadow.blur_radius),
                     border_radius: Au::from_f32_px(box_shadow.border_radius),
                     inverted: box_shadow.inverted != 0.0,
@@ -1096,17 +1137,17 @@ impl PrimitiveStore {
                 // stretched over the actual primitive rect by the box shadow primitive
                 // shader, to reduce the number of pixels that the expensive box
                 // shadow shader needs to run on.
                 // TODO(gw): In the future, we can probably merge the box shadow
                 // primitive (stretch) shader with the generic cached primitive shader.
                 let render_task = RenderTask::new_box_shadow(cache_key, cache_size, prim_index);
                 let render_task_id = render_tasks.add(render_task);
 
-                metadata.render_task_id = Some(render_task_id);
+                box_shadow.render_task_id = Some(render_task_id);
             }
             PrimitiveKind::TextShadow => {
                 let shadow = &mut self.cpu_text_shadows[cpu_prim_index.0];
 
                 // This is a text-shadow element. Create a render task that will
                 // render the text run to a target, and then apply a gaussian
                 // blur to that text run in order to build the actual primitive
                 // which will be blitted to the framebuffer.
@@ -1114,17 +1155,17 @@ impl PrimitiveStore {
                 let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
                 let cache_size = DeviceIntSize::new(cache_width, cache_height);
                 let blur_radius = device_length(shadow.shadow.blur_radius,
                                                 device_pixel_ratio);
                 let render_task = RenderTask::new_blur(cache_size,
                                                        blur_radius,
                                                        prim_index,
                                                        render_tasks);
-                metadata.render_task_id = Some(render_tasks.add(render_task));
+                shadow.render_task_id = Some(render_tasks.add(render_task));
             }
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[cpu_prim_index.0];
                 text.prepare_for_render(resource_cache,
                                         device_pixel_ratio,
                                         display_list,
                                         text_run_mode,
                                         gpu_cache);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -2,17 +2,17 @@
  * 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/. */
 
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use gpu_cache::GpuCache;
-use internal_types::{DebugOutput, FastHashMap, ResultMsg, RendererFrame};
+use internal_types::{DebugOutput, FastHashMap, FastHashSet, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 #[cfg(feature = "debugger")]
 use serde_json;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
@@ -20,28 +20,31 @@ use std::u32;
 use texture_cache::TextureCache;
 use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
 use rayon::ThreadPool;
 use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
 use api::channel::{PayloadSender, PayloadSenderHelperMethods};
 use api::{ApiMsg, DebugCommand, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, DocumentMsg};
-use api::{IdNamespace, LayerPoint, RenderNotifier};
+use api::{IdNamespace, LayerPoint, PipelineId, RenderNotifier};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 
 struct Document {
     scene: Scene,
     frame: Frame,
     window_size: DeviceUintSize,
     inner_rect: DeviceUintRect,
     pan: DeviceIntPoint,
     page_zoom_factor: f32,
     pinch_zoom_factor: f32,
+    // A set of pipelines that the caller has requested be
+    // made available as output textures.
+    output_pipelines: FastHashSet<PipelineId>,
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
     // the first frame would produce inconsistent rendering results, because
     // scroll events are not necessarily received in deterministic order.
     render_on_scroll: Option<bool>,
 }
 
@@ -60,16 +63,17 @@ impl Document {
             scene: Scene::new(),
             frame: Frame::new(config),
             window_size: initial_size,
             inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_size),
             pan: DeviceIntPoint::zero(),
             page_zoom_factor: 1.0,
             pinch_zoom_factor: 1.0,
             render_on_scroll,
+            output_pipelines: FastHashSet::default(),
         }
     }
 
     fn accumulated_scale_factor(&self, hidpi_factor: f32) -> f32 {
         hidpi_factor * self.page_zoom_factor * self.pinch_zoom_factor
     }
 
     fn build_scene(&mut self, resource_cache: &mut ResourceCache, hidpi_factor: f32) {
@@ -90,16 +94,17 @@ impl Document {
         let accumulated_scale_factor = self.accumulated_scale_factor(hidpi_factor);
         let pan = LayerPoint::new(self.pan.x as f32 / accumulated_scale_factor,
                                   self.pan.y as f32 / accumulated_scale_factor);
         self.frame.build(resource_cache,
                          gpu_cache,
                          &self.scene.display_lists,
                          accumulated_scale_factor,
                          pan,
+                         &self.output_pipelines,
                          &mut resource_profile.texture_cache,
                          &mut resource_profile.gpu_cache)
     }
 }
 
 enum DocumentOp {
     Nop,
     Built,
@@ -181,16 +186,24 @@ impl RenderBackend {
     {
         let doc = self.documents.get_mut(&document_id).expect("No document?");
 
         match message {
             DocumentMsg::SetPageZoom(factor) => {
                 doc.page_zoom_factor = factor.get();
                 DocumentOp::Nop
             }
+            DocumentMsg::EnableFrameOutput(pipeline_id, enable) => {
+                if enable {
+                    doc.output_pipelines.insert(pipeline_id);
+                } else {
+                    doc.output_pipelines.remove(&pipeline_id);
+                }
+                DocumentOp::Nop
+            }
             DocumentMsg::SetPinchZoom(factor) => {
                 doc.pinch_zoom_factor = factor.get();
                 DocumentOp::Nop
             }
             DocumentMsg::SetPan(pan) => {
                 doc.pan = pan;
                 DocumentOp::Nop
             }
@@ -608,19 +621,22 @@ impl RenderBackend {
 
     #[cfg(feature = "debugger")]
     fn get_clip_scroll_tree_for_debugger(&self) -> String {
         let mut debug_root = debug_server::ClipScrollTreeList::new();
 
         for (_, doc) in &self.documents {
             let debug_node = debug_server::TreeNode::new("document clip_scroll tree");
             let mut builder = debug_server::TreeNodeBuilder::new(debug_node);
-            doc.frame.clip_scroll_tree.print_with(&mut builder);
+            // TODO(gw): Restructure the storage of clip-scroll tree, clip store
+            //           etc so this isn't so untidy.
+            let clip_store = &doc.frame.frame_builder.as_ref().unwrap().clip_store;
+            doc.frame.clip_scroll_tree.print_with(clip_store, &mut builder);
 
-            debug_root.add(builder.build());            
+            debug_root.add(builder.build());
         }
 
         serde_json::to_string(&debug_root).unwrap()
     }
 }
 
 #[cfg(feature = "debugger")]
 trait ToDebugString {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,28 +1,30 @@
 /* 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::PipelineId;
+use clip::{ClipSource, ClipSourcesWeakHandle, ClipStore};
 use gpu_cache::GpuCacheHandle;
 use internal_types::HardwareCompositeOp;
-use mask_cache::MaskCacheInfo;
 use prim_store::{BoxShadowPrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 use api::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{FilterOp, MixBlendMode};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct RenderTaskId(pub u32);       // TODO(gw): Make private when using GPU cache!
 
 #[derive(Debug, Copy, Clone)]
+#[repr(C)]
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
 pub struct RenderTaskTree {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
 }
 
@@ -131,16 +133,19 @@ pub enum AlphaRenderItem {
     SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug)]
 pub struct AlphaRenderTask {
     pub screen_origin: DeviceIntPoint,
     pub items: Vec<AlphaRenderItem>,
+    // If this render task is a registered frame output, this
+    // contains the pipeline ID it maps to.
+    pub frame_output_pipeline_id: Option<PipelineId>,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskSegment {
     // This must match the SEGMENT_ values in clip_shared.glsl!
     All = 0,
     TopLeftCorner,
@@ -152,17 +157,54 @@ pub enum MaskSegment {
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskGeometryKind {
     Default,        // Draw the entire rect
     CornersOnly,    // Draw the corners (simple axis aligned mask)
     // TODO(gw): Add more types here (e.g. 4 rectangles outside the inner rect)
 }
 
-pub type ClipWorkItem = (PackedLayerIndex, MaskCacheInfo);
+#[derive(Debug, Clone)]
+pub struct ClipWorkItem {
+    pub layer_index: PackedLayerIndex,
+    pub clip_sources: ClipSourcesWeakHandle,
+    pub apply_rectangles: bool,
+}
+
+impl ClipWorkItem {
+    fn get_geometry_kind(&self, clip_store: &ClipStore) -> MaskGeometryKind {
+        let clips = clip_store.get_opt(&self.clip_sources)
+                              .expect("bug: clip handle should be valid")
+                              .clips();
+        let mut rounded_rect_count = 0;
+
+        for &(ref clip, _) in clips {
+            match *clip {
+                ClipSource::Rectangle(..) => {
+                    if self.apply_rectangles {
+                        return MaskGeometryKind::Default;
+                    }
+                }
+                ClipSource::RoundedRectangle(..) => {
+                    rounded_rect_count += 1;
+                }
+                ClipSource::Image(..) |
+                ClipSource::BorderCorner(..) => {
+                    return MaskGeometryKind::Default;
+                }
+            }
+        }
+
+        if rounded_rect_count == 1 {
+            MaskGeometryKind::CornersOnly
+        } else {
+            MaskGeometryKind::Default
+        }
+    }
+}
 
 #[derive(Debug)]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     inner_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
     pub geometry_kind: MaskGeometryKind,
 }
@@ -189,31 +231,34 @@ pub struct RenderTask {
     pub cache_key: Option<RenderTaskKey>,
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
 }
 
 impl RenderTask {
     pub fn new_alpha_batch(screen_origin: DeviceIntPoint,
-                           location: RenderTaskLocation) -> RenderTask {
+                           location: RenderTaskLocation,
+                           frame_output_pipeline_id: Option<PipelineId>) -> RenderTask {
         RenderTask {
             cache_key: None,
             children: Vec::new(),
             location,
             kind: RenderTaskKind::Alpha(AlphaRenderTask {
                 screen_origin,
                 items: Vec::new(),
+                frame_output_pipeline_id,
             }),
         }
     }
 
-    pub fn new_dynamic_alpha_batch(rect: &DeviceIntRect) -> RenderTask {
+    pub fn new_dynamic_alpha_batch(rect: &DeviceIntRect,
+                                   frame_output_pipeline_id: Option<PipelineId>) -> RenderTask {
         let location = RenderTaskLocation::Dynamic(None, rect.size);
-        Self::new_alpha_batch(rect.origin, location)
+        Self::new_alpha_batch(rect.origin, location, frame_output_pipeline_id)
     }
 
     pub fn new_prim_cache(size: DeviceIntSize,
                           prim_index: PrimitiveIndex) -> RenderTask {
         RenderTask {
             cache_key: None,
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, size),
@@ -240,23 +285,27 @@ impl RenderTask {
             kind: RenderTaskKind::Readback(screen_rect),
         }
     }
 
     pub fn new_mask(key: Option<ClipId>,
                     task_rect: DeviceIntRect,
                     raw_clips: &[ClipWorkItem],
                     extra_clip: Option<ClipWorkItem>,
-                    prim_rect: DeviceIntRect)
+                    prim_rect: DeviceIntRect,
+                    clip_store: &ClipStore)
                     -> Option<RenderTask> {
         // Filter out all the clip instances that don't contribute to the result
         let mut inner_rect = Some(task_rect);
         let clips: Vec<_> = raw_clips.iter()
                                      .chain(extra_clip.iter())
-                                     .filter(|&&(_, ref clip_info)| {
+                                     .filter(|work_item| {
+            let clip_info = clip_store.get_opt(&work_item.clip_sources)
+                                      .expect("bug: clip item should exist");
+
             // If this clip does not contribute to a mask, then ensure
             // it gets filtered out here. Otherwise, if a mask is
             // created (by a different clip in the list), the allocated
             // rectangle for the mask could end up being much bigger
             // than is actually required.
             if !clip_info.is_masking() {
                 return false;
             }
@@ -286,23 +335,17 @@ impl RenderTask {
         let mut geometry_kind = MaskGeometryKind::Default;
         if let Some(inner_rect) = inner_rect {
             // If the inner rect completely contains the primitive
             // rect, then this mask can't affect the primitive.
             if inner_rect.contains_rect(&prim_rect) {
                 return None;
             }
             if clips.len() == 1 {
-                let (_, ref info) = clips[0];
-                if info.border_corners.is_empty() &&
-                   info.image.is_none() &&
-                   info.complex_clip_range.get_count() == 1 &&
-                   info.layer_clip_range.get_count() == 0 {
-                    geometry_kind = MaskGeometryKind::CornersOnly;
-                }
+                geometry_kind = clips[0].get_geometry_kind(clip_store);
             }
         }
 
         Some(RenderTask {
             cache_key: key.map(RenderTaskKey::CacheMask),
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, task_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -15,46 +15,48 @@ use api::ApiMsg;
 use api::channel::MsgSender;
 use api::DebugCommand;
 use debug_colors;
 use debug_render::DebugRenderer;
 #[cfg(feature = "debugger")]
 use debug_server::{self, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, Texture, VertexDescriptor, GpuMarker, GpuProfiler, PBO};
 use device::{GpuTimer, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
-use device::{ExternalTexture, get_gl_format_bgra, TextureSlot, VertexAttribute, VertexAttributeKind};
+use device::{ExternalTexture, FBOId, get_gl_format_bgra, TextureSlot, VertexAttribute, VertexAttributeKind};
 use euclid::{Transform3D, rect};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
+use gpu_types::{PrimitiveInstance};
 use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
 use internal_types::{DebugOutput, TextureUpdateList, RenderTargetMode, TextureUpdateSource};
 use internal_types::{BatchTextures, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
 use profiler::{Profiler, BackendProfileCounters};
 use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::RenderTaskTree;
 #[cfg(feature = "debugger")]
 use serde_json;
 use std;
 use std::cmp;
+use std::collections::hash_map::Entry;
 use std::collections::VecDeque;
 use std::f32;
 use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use rayon::ThreadPool;
 use rayon::Configuration as ThreadPoolConfig;
 use tiling::{AlphaBatchKey, AlphaBatchKind, Frame, RenderTarget};
-use tiling::{AlphaRenderTarget, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
+use tiling::{AlphaRenderTarget, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
 use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier};
 use api::{ExternalImageId, ExternalImageType, ImageFormat};
 use api::{DeviceIntRect, DeviceUintRect, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
 use api::{BlobImageRenderer, channel, FontRenderMode};
 use api::{YuvColorSpace, YuvFormat};
@@ -122,16 +124,17 @@ impl AlphaBatchKind {
 }
 
 bitflags! {
     #[derive(Default)]
     pub struct DebugFlags: u32 {
         const PROFILER_DBG      = 1 << 0;
         const RENDER_TARGET_DBG = 1 << 1;
         const TEXTURE_CACHE_DBG = 1 << 2;
+        const ALPHA_PRIM_DBG    = 1 << 3;
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 enum TextureSampler {
     Color0,
     Color1,
     Color2,
@@ -193,29 +196,29 @@ const DESC_PRIM_INSTANCES: VertexDescrip
     ]
 };
 
 const DESC_BLUR: VertexDescriptor = VertexDescriptor {
     vertex_attributes: &[
         VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
     ],
     instance_attributes: &[
-        VertexAttribute { name: "aBlurRenderTaskIndex", count: 1, kind: VertexAttributeKind::I32 },
-        VertexAttribute { name: "aBlurSourceTaskIndex", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aBlurRenderTaskAddress", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aBlurSourceTaskAddress", count: 1, kind: VertexAttributeKind::I32 },
         VertexAttribute { name: "aBlurDirection", count: 1, kind: VertexAttributeKind::I32 },
     ]
 };
 
 const DESC_CLIP: VertexDescriptor = VertexDescriptor {
     vertex_attributes: &[
         VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
     ],
     instance_attributes: &[
-        VertexAttribute { name: "aClipRenderTaskIndex", count: 1, kind: VertexAttributeKind::I32 },
-        VertexAttribute { name: "aClipLayerIndex", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aClipRenderTaskAddress", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aClipLayerAddress", count: 1, kind: VertexAttributeKind::I32 },
         VertexAttribute { name: "aClipSegment", count: 1, kind: VertexAttributeKind::I32 },
         VertexAttribute { name: "aClipDataResourceAddress", count: 4, kind: VertexAttributeKind::U16 },
     ]
 };
 
 const DESC_CACHE_BOX_SHADOW: VertexDescriptor = VertexDescriptor {
     vertex_attributes: &[
         VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
@@ -911,16 +914,21 @@ fn create_clip_shader(name: &'static str
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum ReadPixelsFormat {
     Rgba8,
     Bgra8,
 }
 
+struct FrameOutput {
+    last_access: FrameId,
+    fbo_id: FBOId,
+}
+
 /// 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>,
@@ -1007,16 +1015,24 @@ pub struct Renderer {
     texture_cache_upload_pbo: PBO,
 
     dither_matrix_texture: Option<Texture>,
 
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
+    /// Optional trait object that allows the client
+    /// application to provide a texture handle to
+    /// copy the WR output to.
+    output_image_handler: Option<Box<OutputImageHandler>>,
+
+    // Currently allocated FBOs for output frames.
+    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>,
 }
 
@@ -1523,16 +1539,18 @@ impl Renderer {
             blur_vao,
             box_shadow_vao,
             clip_vao,
             layer_texture,
             render_task_texture,
             pipeline_epoch_map: FastHashMap::default(),
             dither_matrix_texture,
             external_image_handler: None,
+            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(),
         };
 
@@ -1663,17 +1681,19 @@ impl Renderer {
                     debug_pass.add(debug_target);
                 }
 
                 for target in &pass.color_targets.targets {
                     let mut debug_target = debug_server::Target::new("RGBA8");
 
                     debug_target.add(debug_server::BatchKind::Cache, "Vertical Blur", target.vertical_blurs.len());
                     debug_target.add(debug_server::BatchKind::Cache, "Horizontal Blur", target.horizontal_blurs.len());
-                    debug_target.add(debug_server::BatchKind::Cache, "Text Shadow", target.text_run_cache_prims.len());
+                    for (_, batch) in &target.text_run_cache_prims {
+                        debug_target.add(debug_server::BatchKind::Cache, "Text Shadow", batch.len());
+                    }
                     debug_target.add(debug_server::BatchKind::Cache, "Lines", target.line_cache_prims.len());
 
                     for batch in target.alpha_batcher
                                        .batch_list
                                        .opaque_batch_list
                                        .batches
                                        .iter()
                                        .rev() {
@@ -1715,30 +1735,42 @@ impl Renderer {
             }
             DebugCommand::EnableRenderTargetDebug(enable) => {
                 if enable {
                     self.debug_flags.insert(RENDER_TARGET_DBG);
                 } else {
                     self.debug_flags.remove(RENDER_TARGET_DBG);
                 }
             }
+            DebugCommand::EnableAlphaRectsDebug(enable) => {
+                if enable {
+                    self.debug_flags.insert(ALPHA_PRIM_DBG);
+                } else {
+                    self.debug_flags.remove(ALPHA_PRIM_DBG);
+                }
+            }
             DebugCommand::FetchDocuments => {}
             DebugCommand::FetchClipScrollTree => {}
             DebugCommand::FetchPasses => {
                 let json = self.get_passes_for_debugger();
                 self.debug_server.send(json);
             }
         }
     }
 
     /// Set a callback for handling external images.
     pub fn set_external_image_handler(&mut self, handler: Box<ExternalImageHandler>) {
         self.external_image_handler = Some(handler);
     }
 
+    /// Set a callback for handling external outputs.
+    pub fn set_output_image_handler(&mut self, handler: Box<OutputImageHandler>) {
+        self.output_image_handler = Some(handler);
+    }
+
     /// Retrieve (and clear) the current list of recorded frame profiles.
     pub fn get_frame_profiles(&mut self) -> (Vec<CpuProfile>, Vec<GpuProfile>) {
         let cpu_profiles = self.cpu_profiles.drain(..).collect();
         let gpu_profiles = self.gpu_profiles.drain(..).collect();
         (cpu_profiles, gpu_profiles)
     }
 
     /// Renders the current frame.
@@ -1785,17 +1817,17 @@ impl Renderer {
 
                         self.update_gpu_cache(frame);
 
                         self.device.bind_texture(TextureSampler::ResourceCache, &self.gpu_cache_texture.texture);
 
                         frame_id
                     };
 
-                    self.draw_tile_frame(frame, &framebuffer_size);
+                    self.draw_tile_frame(frame, &framebuffer_size, cpu_frame_id);
 
                     self.gpu_profile.end_frame();
                     cpu_frame_id
                 });
 
                 let current_time = precise_time_ns();
                 let ns = current_time - self.last_time;
                 self.profile_counters.frame_time.set(ns);
@@ -2135,19 +2167,18 @@ impl Renderer {
                 // Need to invert the y coordinates and flip the image vertically when
                 // reading back from the framebuffer.
                 if render_target.is_none() {
                     src.origin.y = target_dimensions.height as i32 - src.size.height - src.origin.y;
                     dest.origin.y += dest.size.height;
                     dest.size.height = -dest.size.height;
                 }
 
-                self.device.blit_render_target(render_target,
-                                               Some(src),
-                                               dest);
+                self.device.bind_read_target(render_target);
+                self.device.blit_render_target(src, dest);
 
                 // Restore draw target to current pass render target + layer.
                 self.device.bind_draw_target(render_target, Some(target_dimensions));
             }
             _ => {}
         }
 
         let _gm = self.gpu_profile.add_marker(marker);
@@ -2158,16 +2189,17 @@ impl Renderer {
 
     fn draw_color_target(&mut self,
         render_target: Option<(&Texture, i32)>,
         target: &ColorRenderTarget,
         target_size: DeviceUintSize,
         clear_color: Option<[f32; 4]>,
         render_tasks: &RenderTaskTree,
         projection: &Transform3D<f32>,
+        frame_id: FrameId,
     ) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
             self.device.disable_depth();
             self.device.enable_depth_write();
             self.device.set_blend(false);
             self.device.set_blend_mode_alpha();
@@ -2222,19 +2254,21 @@ impl Renderer {
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_TEXT_RUN);
             self.cs_text_run.bind(&mut self.device, projection, &mut self.renderer_errors);
-            self.draw_instanced_batch(&target.text_run_cache_prims,
-                                      VertexArrayKind::Primitive,
-                                      &target.text_run_textures);
+            for (texture_id, instances) in &target.text_run_cache_prims {
+                self.draw_instanced_batch(instances,
+                                          VertexArrayKind::Primitive,
+                                          &BatchTextures::color(*texture_id));
+            }
         }
         if !target.line_cache_prims.is_empty() {
             // TODO(gw): Technically, we don't need blend for solid
             //           lines. We could check that here?
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
@@ -2294,28 +2328,73 @@ impl Renderer {
                         BlendMode::Subpixel(color) => {
                             self.device.set_blend(true);
                             self.device.set_blend_mode_subpixel(color);
                         }
                     }
                     prev_blend_mode = batch.key.blend_mode;
                 }
 
+                if self.debug_flags.contains(ALPHA_PRIM_DBG) {
+                    let color = match batch.key.blend_mode {
+                        BlendMode::None => ColorF::new(0.3, 0.3, 0.3, 1.0),
+                        BlendMode::Alpha => ColorF::new(0.0, 0.9, 0.1, 1.0),
+                        BlendMode::PremultipliedAlpha => ColorF::new(0.0, 0.3, 0.7, 1.0),
+                        BlendMode::Subpixel(_) => ColorF::new(0.5, 0.0, 0.4, 1.0),
+                    }.into();
+                    for item_rect in &batch.item_rects {
+                        self.debug.add_rect(item_rect, color);
+                    }
+                }
+
                 self.submit_batch(&batch.key,
                                   &batch.instances,
                                   &projection,
                                   render_tasks,
                                   render_target,
                                   target_size);
             }
 
             self.device.disable_depth();
             self.device.set_blend(false);
             self.gpu_profile.done_sampler();
         }
+
+        // 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);
+                        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 task = render_tasks.get(output.task_id);
+                let (src_rect, _) = task.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);
+                handler.unlock(output.pipeline_id);
+            }
+        }
     }
 
     fn draw_alpha_target(&mut self,
         render_target: (&Texture, i32),
         target: &AlphaRenderTarget,
         target_size: DeviceUintSize,
         projection: &Transform3D<f32>,
     ) {
@@ -2542,17 +2621,18 @@ impl Renderer {
         self.device.bind_texture(TextureSampler::RenderTasks, &self.render_task_texture.texture);
 
         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: &DeviceUintSize,
+                       frame_id: FrameId) {
         let _gm = GpuMarker::new(self.device.rc_gl(), "tile frame draw");
 
         // Some tests use a restricted viewport smaller than the main screen size.
         // Ensure we clear the framebuffer in these tests.
         // TODO(gw): Find a better solution for this?
         let needs_clear = frame.window_size.width < framebuffer_size.width ||
                           frame.window_size.height < framebuffer_size.height;
 
@@ -2611,17 +2691,18 @@ impl Renderer {
                     let render_target = pass.color_texture.as_ref().map(|texture| {
                         (texture, target_index as i32)
                     });
                     self.draw_color_target(render_target,
                                            target,
                                            *size,
                                            clear_color,
                                            &frame.render_tasks,
-                                           &projection);
+                                           &projection,
+                                           frame_id);
 
                 }
 
                 self.texture_resolver.end_pass(pass_index,
                                                pass_count,
                                                pass.alpha_texture.take(),
                                                pass.color_texture.take(),
                                                &mut self.alpha_render_targets,
@@ -2635,16 +2716,27 @@ impl Renderer {
                     }
                 }
             }
 
             self.color_render_targets.reverse();
             self.alpha_render_targets.reverse();
             self.draw_render_target_debug(framebuffer_size);
             self.draw_texture_cache_debug(framebuffer_size);
+
+            // 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);
+                    true
+                } else {
+                    false
+                }
+            });
         }
 
         self.unlock_external_images();
     }
 
     pub fn debug_renderer<'a>(&'a mut self) -> &'a mut DebugRenderer {
         &mut self.debug
     }
@@ -2674,27 +2766,28 @@ impl Renderer {
 
         if num_textures * (size + spacing) > fb_width {
             let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32;
             size = (size as f32 * factor) as i32;
             spacing = (spacing as f32 * factor) as i32;
         }
 
         for (i, texture) in self.color_render_targets.iter().chain(self.alpha_render_targets.iter()).enumerate() {
+            let dimensions = texture.get_dimensions();
+            let src_rect = DeviceIntRect::new(DeviceIntPoint::zero(),
+                                              dimensions.to_i32());
+
             let layer_count = texture.get_render_target_layer_count();
             for layer_index in 0..layer_count {
+                self.device.bind_read_target(Some((texture, layer_index as i32)));
                 let x = fb_width - (spacing + size) * (i as i32 + 1);
                 let y = spacing;
 
                 let dest_rect = rect(x, y, size, size);
-                self.device.blit_render_target(
-                    Some((texture, layer_index as i32)),
-                    None,
-                    dest_rect
-                );
+                self.device.blit_render_target(src_rect, dest_rect);
             }
         }
     }
 
     fn draw_texture_cache_debug(&mut self, framebuffer_size: &DeviceUintSize) {
         if !self.debug_flags.contains(TEXTURE_CACHE_DBG) {
             return;
         }
@@ -2714,28 +2807,34 @@ impl Renderer {
             let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32;
             size = (size as f32 * factor) as i32;
             spacing = (spacing as f32 * factor) as i32;
         }
 
         let mut i = 0;
         for texture in &self.texture_resolver.cache_texture_map {
             let y = spacing + if self.debug_flags.contains(RENDER_TARGET_DBG) { 528 } else { 0 };
+            let dimensions = texture.get_dimensions();
+            let src_rect = DeviceIntRect::new(DeviceIntPoint::zero(),
+                                              DeviceIntSize::new(dimensions.width as i32,
+                                                                 dimensions.height as i32));
 
             let layer_count = texture.get_layer_count();
             for layer_index in 0..layer_count {
+                self.device.bind_read_target(Some((texture, layer_index)));
+
                 let x = fb_width - (spacing + size) * (i as i32 + 1);
 
                 // If we have more targets than fit on one row in screen, just early exit.
                 if x > fb_width {
                     return;
                 }
 
                 let dest_rect = rect(x, y, size, size);
-                self.device.blit_render_target(Some((texture, layer_index)), None, dest_rect);
+                self.device.blit_render_target(src_rect, dest_rect);
                 i += 1;
             }
         }
     }
 
     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);
@@ -2800,16 +2899,19 @@ impl Renderer {
                 shader.deinit(&mut self.device);
             }
         }
         for shader in self.ps_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
         }
+        for (_, target) in self.output_targets {
+            self.device.delete_fbo(target.fbo_id);
+        }
         self.ps_border_corner.deinit(&mut self.device);
         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_box_shadow.deinit(&mut self.device);
         self.ps_cache_image.deinit(&mut self.device);
         self.ps_line.deinit(&mut self.device);
@@ -2855,16 +2957,26 @@ pub trait ExternalImageHandler {
     /// The WR client should not change the image content until the unlock()
     /// call.
     fn lock(&mut self, key: ExternalImageId, channel_index: u8) -> ExternalImage;
     /// Unlock the external image. The WR should not read the image content
     /// after this call.
     fn unlock(&mut self, key: ExternalImageId, channel_index: u8);
 }
 
+/// Allows callers to receive a texture with the contents of a specific
+/// pipeline copied to it. Lock should return the native texture handle
+/// and the size of the texture. Unlock will only be called if the lock()
+/// call succeeds, when WR has issued the GL commands to copy the output
+/// to the texture handle.
+pub trait OutputImageHandler {
+    fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, DeviceIntSize)>;
+    fn unlock(&mut self, pipeline_id: PipelineId);
+}
+
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
     pub max_recorded_profiles: usize,
     pub debug: bool,
     pub enable_scrollbars: bool,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,17 +1,17 @@
 /* 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 app_units::Au;
 use device::TextureFilter;
 use frame::FrameId;
 use glyph_cache::GlyphCache;
-use gpu_cache::{GpuCache, GpuCacheHandle};
+use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheHandle};
@@ -25,16 +25,21 @@ use api::{GlyphDimensions, GlyphKey, IdN
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 use api::{ExternalImageData, ExternalImageType};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
+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
 // for this is that the texture may change
 // dimensions (e.g. the pages in a texture
 // atlas can grow). When this happens, by
 // storing the coordinates as texel values
 // we don't need to go through and update
@@ -531,37 +536,50 @@ impl ResourceCache {
             gpu_cache,
         );
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         self.texture_cache.pending_updates()
     }
 
-    pub fn get_glyphs<F>(&self,
-                         font: FontInstance,
-                         glyph_keys: &[GlyphKey],
-                         mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
+    pub fn fetch_glyphs<F>(&self,
+                           font: FontInstance,
+                           glyph_keys: &[GlyphKey],
+                           fetch_buffer: &mut Vec<GlyphFetchResult>,
+                           gpu_cache: &GpuCache,
+                           mut f: F) where F: FnMut(SourceTexture, &[GlyphFetchResult]) {
         debug_assert_eq!(self.state, State::QueryResources);
-        let mut texture_id = None;
+        let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
 
-        let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
+        let mut current_texture_id = SourceTexture::Invalid;
+        debug_assert!(fetch_buffer.is_empty());
 
         for (loop_index, key) in glyph_keys.iter().enumerate() {
             let glyph = glyph_key_cache.get(key);
             let cache_item = glyph.as_ref().map(|info| self.texture_cache.get(&info.texture_cache_handle));
             if let Some(cache_item) = cache_item {
-                f(loop_index, &cache_item.uv_rect_handle);
-                debug_assert!(texture_id == None ||
-                              texture_id == Some(cache_item.texture_id));
-                texture_id = Some(cache_item.texture_id);
+                if current_texture_id != cache_item.texture_id {
+                    if !fetch_buffer.is_empty() {
+                        f(current_texture_id, fetch_buffer);
+                        fetch_buffer.clear();
+                    }
+                    current_texture_id = cache_item.texture_id;
+                }
+                fetch_buffer.push(GlyphFetchResult {
+                    index_in_text_run: loop_index as i32,
+                    uv_rect_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
+                });
             }
         }
 
-        texture_id.unwrap_or(SourceTexture::Invalid)
+        if !fetch_buffer.is_empty() {
+            f(current_texture_id, fetch_buffer);
+            fetch_buffer.clear();
+        }
     }
 
     pub fn get_glyph_dimensions(&mut self,
                                 font: &FontInstance,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
         let key = GlyphRequest::new(font, key);
 
         match self.cached_glyph_dimensions.entry(key.clone()) {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,28 +1,29 @@
 /* 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 border::{BorderCornerInstance, BorderCornerSide};
+use clip::{ClipSource, ClipStore};
 use device::Texture;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuCacheUpdateList};
-use gpu_types::BoxShadowCacheInstance;
+use gpu_types::{BlurDirection, BlurInstance, BoxShadowCacheInstance, ClipMaskInstance};
+use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::BatchTextures;
 use internal_types::{FastHashMap, SourceTexture};
-use mask_cache::MaskCacheInfo;
-use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve};
+use prim_store::{DeferredResolve};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use profiler::FrameProfileCounters;
-use render_task::{AlphaRenderItem, MaskGeometryKind, MaskSegment};
+use render_task::{AlphaRenderItem, ClipWorkItem, MaskGeometryKind, MaskSegment};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKey, RenderTaskKind};
 use render_task::{RenderTaskLocation, RenderTaskTree};
 use renderer::BlendMode;
 use renderer::ImageBufferKind;
-use resource_cache::ResourceCache;
+use resource_cache::{GlyphFetchResult, ResourceCache};
 use std::{f32, i32, usize};
 use texture_allocator::GuillotineAllocator;
 use util::{TransformedRect, TransformedRectKind};
 use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
 use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
 use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
 use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
 use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
@@ -238,27 +239,29 @@ impl BatchList {
         self.opaque_batch_list.finalize()
     }
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatcher {
     pub batch_list: BatchList,
     tasks: Vec<RenderTaskId>,
+    glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
 impl AlphaRenderItem {
     fn add_to_batch(&self,
                     batch_list: &mut BatchList,
                     ctx: &RenderTargetContext,
                     gpu_cache: &mut GpuCache,
                     render_tasks: &RenderTaskTree,
                     task_id: RenderTaskId,
                     task_address: RenderTaskAddress,
-                    deferred_resolves: &mut Vec<DeferredResolve>) {
+                    deferred_resolves: &mut Vec<DeferredResolve>,
+                    glyph_fetch_buffer: &mut Vec<GlyphFetchResult>) {
         match *self {
             AlphaRenderItem::Blend(stacking_context_index, src_id, filter, z) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let key = AlphaBatchKey::new(AlphaBatchKind::Blend,
                                              AlphaBatchKeyFlags::empty(),
                                              BlendMode::PremultipliedAlpha,
                                              BatchTextures::no_texture());
                 let src_task_address = render_tasks.get_task_address(src_id);
@@ -352,23 +355,22 @@ impl AlphaRenderItem {
                 let clip_task_address = prim_metadata.clip_task_id.map_or(OPAQUE_TASK_ADDRESS, |id| {
                     render_tasks.get_task_address(id)
                 });
                 let needs_blending = !prim_metadata.opacity.is_opaque ||
                                      needs_clipping ||
                                      transform_kind == TransformedRectKind::Complex;
                 let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
 
-                let prim_cache_address = prim_metadata.gpu_location
-                                                      .as_int(gpu_cache);
+                let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
 
                 let base_instance = SimplePrimitiveInstance::new(prim_cache_address,
                                                                  task_address,
                                                                  clip_task_address,
-                                                                 packed_layer_index,
+                                                                 packed_layer_index.into(),
                                                                  z);
 
                 let no_textures = BatchTextures::no_texture();
 
                 match prim_metadata.prim_kind {
                     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!!
@@ -449,42 +451,41 @@ impl AlphaRenderItem {
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.push(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
 
-                        // TODO(gw): avoid / recycle this allocation in the future.
-                        let mut instances = Vec::new();
-
                         let mut font = text_cpu.font.clone();
                         font.size = font.size.scale_by(ctx.device_pixel_ratio);
 
-                        let texture_id = ctx.resource_cache.get_glyphs(font,
-                                                                       &text_cpu.glyph_keys,
-                                                                       |index, handle| {
-                            let uv_address = handle.as_int(gpu_cache);
-                            instances.push(base_instance.build(index as i32, uv_address, 0));
-                        });
-
-                        if texture_id != SourceTexture::Invalid {
+                        ctx.resource_cache.fetch_glyphs(font,
+                                                        &text_cpu.glyph_keys,
+                                                        glyph_fetch_buffer,
+                                                        gpu_cache,
+                                                        |texture_id, glyphs| {
                             let textures = BatchTextures {
                                 colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                             };
 
                             let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures);
                             let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
-                            batch.extend_from_slice(&instances);
-                        }
+                            for glyph in glyphs {
+                                batch.push(base_instance.build(glyph.index_in_text_run,
+                                                               glyph.uv_rect_address.as_int(),
+                                                               0));
+                            }
+                        });
                     }
                     PrimitiveKind::TextShadow => {
-                        let cache_task_id = prim_metadata.render_task_id.expect("no render task!");
+                        let text_shadow = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
+                        let cache_task_id = text_shadow.render_task_id.expect("no render task!");
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
                         let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.push(base_instance.build(0, cache_task_address.0 as i32, 0));
                     }
                     PrimitiveKind::AlignedGradient => {
                         let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
@@ -564,17 +565,17 @@ impl AlphaRenderItem {
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                         batch.push(base_instance.build(uv_rect_addresses[0],
                                                        uv_rect_addresses[1],
                                                        uv_rect_addresses[2]));
                     }
                     PrimitiveKind::BoxShadow => {
                         let box_shadow = &ctx.prim_store.cpu_box_shadows[prim_metadata.cpu_prim_index.0];
-                        let cache_task_id = prim_metadata.render_task_id.unwrap();
+                        let cache_task_id = box_shadow.render_task_id.unwrap();
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
 
                         let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                         for rect_index in 0..box_shadow.rects.len() {
                             batch.push(base_instance.build(rect_index as i32,
@@ -606,16 +607,17 @@ impl AlphaRenderItem {
     }
 }
 
 impl AlphaBatcher {
     fn new() -> AlphaBatcher {
         AlphaBatcher {
             tasks: Vec::new(),
             batch_list: BatchList::new(),
+            glyph_fetch_buffer: Vec::new(),
         }
     }
 
     fn add_task(&mut self, task_id: RenderTaskId) {
         self.tasks.push(task_id);
     }
 
     fn build(&mut self,
@@ -630,144 +632,139 @@ impl AlphaBatcher {
 
             for item in &task.items {
                 item.add_to_batch(&mut self.batch_list,
                                   ctx,
                                   gpu_cache,
                                   render_tasks,
                                   task_id,
                                   task_address,
-                                  deferred_resolves);
+                                  deferred_resolves,
+                                  &mut self.glyph_fetch_buffer);
             }
         }
 
         self.batch_list.finalize();
     }
 
     pub fn is_empty(&self) -> bool {
         self.batch_list.opaque_batch_list.batches.is_empty() &&
         self.batch_list.alpha_batch_list.batches.is_empty()
     }
 }
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
-    pub rectangles: Vec<CacheClipInstance>,
+    pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
-    pub images: FastHashMap<SourceTexture, Vec<CacheClipInstance>>,
-    pub border_clears: Vec<CacheClipInstance>,
-    pub borders: Vec<CacheClipInstance>,
+    pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
+    pub border_clears: Vec<ClipMaskInstance>,
+    pub borders: Vec<ClipMaskInstance>,
 }
 
 impl ClipBatcher {
     fn new() -> ClipBatcher {
         ClipBatcher {
             rectangles: Vec::new(),
             images: FastHashMap::default(),
             border_clears: Vec::new(),
             borders: Vec::new(),
         }
     }
 
-    fn add<'a>(&mut self,
-               task_address: RenderTaskAddress,
-               clips: &[(PackedLayerIndex, MaskCacheInfo)],
-               resource_cache: &ResourceCache,
-               gpu_cache: &GpuCache,
-               geometry_kind: MaskGeometryKind) {
-
-        for &(packed_layer_index, ref info) in clips.iter() {
-            let instance = CacheClipInstance {
-                render_task_address: task_address.0 as i32,
-                layer_index: packed_layer_index.0 as i32,
+    fn add(&mut self,
+           task_address: RenderTaskAddress,
+           clips: &[ClipWorkItem],
+           resource_cache: &ResourceCache,
+           gpu_cache: &GpuCache,
+           geometry_kind: MaskGeometryKind,
+           clip_store: &ClipStore) {
+        for work_item in clips.iter() {
+            let instance = ClipMaskInstance {
+                render_task_address: task_address,
+                layer_address: work_item.layer_index.into(),
                 segment: 0,
                 clip_data_address: GpuCacheAddress::invalid(),
                 resource_address: GpuCacheAddress::invalid(),
             };
+            let info = clip_store.get_opt(&work_item.clip_sources)
+                                 .expect("bug: clip handle should be valid");
 
-            if !info.complex_clip_range.is_empty() {
-                let base_gpu_address = gpu_cache.get_address(&info.complex_clip_range.location);
+            for &(ref source, ref handle) in &info.clips {
+                let gpu_address = gpu_cache.get_address(handle);
 
-                for clip_index in 0 .. info.complex_clip_range.get_count() {
-                    let gpu_address = base_gpu_address + CLIP_DATA_GPU_BLOCKS * clip_index;
-                    match geometry_kind {
-                        MaskGeometryKind::Default => {
-                            self.rectangles.push(CacheClipInstance {
+                match *source {
+                    ClipSource::Image(ref mask) => {
+                        let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto, None);
+                        self.images.entry(cache_item.texture_id)
+                                   .or_insert(Vec::new())
+                                   .push(ClipMaskInstance {
+                            clip_data_address: gpu_address,
+                            resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
+                            ..instance
+                        });
+                    }
+                    ClipSource::Rectangle(..) => {
+                        if work_item.apply_rectangles {
+                            self.rectangles.push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 segment: MaskSegment::All as i32,
                                 ..instance
                             });
                         }
-                        MaskGeometryKind::CornersOnly => {
-                            self.rectangles.extend_from_slice(&[
-                                CacheClipInstance {
+                    }
+                    ClipSource::RoundedRectangle(..) => {
+                        match geometry_kind {
+                            MaskGeometryKind::Default => {
+                                self.rectangles.push(ClipMaskInstance {
+                                    clip_data_address: gpu_address,
+                                    segment: MaskSegment::All as i32,
+                                    ..instance
+                                });
+                            }
+                            MaskGeometryKind::CornersOnly => {
+                                self.rectangles.push(ClipMaskInstance {
                                     clip_data_address: gpu_address,
                                     segment: MaskSegment::TopLeftCorner as i32,
                                     ..instance
-                                },
-                                CacheClipInstance {
+                                });
+                                self.rectangles.push(ClipMaskInstance {
                                     clip_data_address: gpu_address,
                                     segment: MaskSegment::TopRightCorner as i32,
                                     ..instance
-                                },
-                                CacheClipInstance {
+                                });
+                                self.rectangles.push(ClipMaskInstance {
                                     clip_data_address: gpu_address,
                                     segment: MaskSegment::BottomLeftCorner as i32,
                                     ..instance
-                                },
-                                CacheClipInstance {
+                                });
+                                self.rectangles.push(ClipMaskInstance {
                                     clip_data_address: gpu_address,
                                     segment: MaskSegment::BottomRightCorner as i32,
                                     ..instance
-                                },
-                            ]);
+                                });
+                            }
                         }
                     }
-                }
-            }
-
-            if !info.layer_clip_range.is_empty() {
-                let base_gpu_address = gpu_cache.get_address(&info.layer_clip_range.location);
-
-                for clip_index in 0 .. info.layer_clip_range.get_count() {
-                    let gpu_address = base_gpu_address + CLIP_DATA_GPU_BLOCKS * clip_index;
-                    self.rectangles.push(CacheClipInstance {
-                        clip_data_address: gpu_address,
-                        segment: MaskSegment::All as i32,
-                        ..instance
-                    });
-                }
-            }
-
-            if let Some((ref mask, ref gpu_location)) = info.image {
-                let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto, None);
-                self.images.entry(cache_item.texture_id)
-                           .or_insert(Vec::new())
-                           .push(CacheClipInstance {
-                    clip_data_address: gpu_cache.get_address(gpu_location),
-                    resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
-                    ..instance
-                })
-            }
-
-            for &(ref source, ref gpu_location) in &info.border_corners {
-                let gpu_address = gpu_cache.get_address(gpu_location);
-                self.border_clears.push(CacheClipInstance {
-                    clip_data_address: gpu_address,
-                    segment: 0,
-                    ..instance
-                });
-                for clip_index in 0..source.actual_clip_count {
-                    self.borders.push(CacheClipInstance {
-                        clip_data_address: gpu_address,
-                        segment: 1 + clip_index as i32,
-                        ..instance
-                    })
+                    ClipSource::BorderCorner(ref source) => {
+                        self.border_clears.push(ClipMaskInstance {
+                            clip_data_address: gpu_address,
+                            segment: 0,
+                            ..instance
+                        });
+                        for clip_index in 0..source.actual_clip_count {
+                            self.borders.push(ClipMaskInstance {
+                                clip_data_address: gpu_address,
+                                segment: 1 + clip_index as i32,
+                                ..instance
+                            })
+                        }
+                    }
                 }
             }
         }
     }
 }
 
 pub struct RenderTargetContext<'a> {
     pub device_pixel_ratio: f32,
@@ -824,17 +821,18 @@ pub trait RenderTarget {
              _ctx: &RenderTargetContext,
              _gpu_cache: &mut GpuCache,
              _render_tasks: &mut RenderTaskTree,
              _deferred_resolves: &mut Vec<DeferredResolve>) {}
     fn add_task(&mut self,
                 task_id: RenderTaskId,
                 ctx: &RenderTargetContext,
                 gpu_cache: &GpuCache,
-                render_tasks: &RenderTaskTree);
+                render_tasks: &RenderTaskTree,
+                clip_store: &ClipStore);
     fn used_rect(&self) -> DeviceIntRect;
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum RenderTargetKind {
     Color,   // RGBA32
     Alpha,   // R8
 }
@@ -870,18 +868,22 @@ impl<T: RenderTarget> RenderTargetList<T
             target.build(ctx, gpu_cache, render_tasks, deferred_resolves);
         }
     }
 
     fn add_task(&mut self,
                 task_id: RenderTaskId,
                 ctx: &RenderTargetContext,
                 gpu_cache: &GpuCache,
-                render_tasks: &mut RenderTaskTree) {
-        self.targets.last_mut().unwrap().add_task(task_id, ctx, gpu_cache, render_tasks);
+                render_tasks: &mut RenderTaskTree,
+                clip_store: &ClipStore) {
+        self.targets
+            .last_mut()
+            .unwrap()
+            .add_task(task_id, ctx, gpu_cache, render_tasks, clip_store);
     }
 
     fn allocate(&mut self, alloc_size: DeviceUintSize) -> (DeviceUintPoint, RenderTargetIndex) {
         let existing_origin = self.targets
                                   .last_mut()
                                   .and_then(|target| target.allocate(alloc_size));
 
         let origin = match existing_origin {
@@ -894,52 +896,57 @@ impl<T: RenderTarget> RenderTargetList<T
                 origin
             }
         };
 
         (origin, RenderTargetIndex(self.targets.len() - 1))
     }
 }
 
+/// 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.
+pub struct FrameOutput {
+    pub task_id: RenderTaskId,
+    pub pipeline_id: PipelineId,
+}
+
 /// A render target represents a number of rendering operations on a surface.
 pub struct ColorRenderTarget {
     pub alpha_batcher: AlphaBatcher,
     // List of text runs to be cached to this render target.
-    // TODO(gw): For now, assume that these all come from
-    //           the same source texture id. This is almost
-    //           always true except for pathological test
-    //           cases with more than 4k x 4k of unique
-    //           glyphs visible. Once the future glyph / texture
-    //           cache changes land, this restriction will
-    //           be removed anyway.
-    pub text_run_cache_prims: Vec<PrimitiveInstance>,
+    pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
     pub line_cache_prims: Vec<PrimitiveInstance>,
-    pub text_run_textures: BatchTextures,
     // List of blur operations to apply for this render target.
-    pub vertical_blurs: Vec<BlurCommand>,
-    pub horizontal_blurs: Vec<BlurCommand>,
+    pub vertical_blurs: Vec<BlurInstance>,
+    pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
+    // List of frame buffer outputs for this render target.
+    pub outputs: Vec<FrameOutput>,
     allocator: TextureAllocator,
+    glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
 impl RenderTarget for ColorRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
         self.allocator.allocate(&size)
     }
 
     fn new(size: DeviceUintSize) -> ColorRenderTarget {
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
-            text_run_cache_prims: Vec::new(),
+            text_run_cache_prims: FastHashMap::default(),
             line_cache_prims: Vec::new(),
-            text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             allocator: TextureAllocator::new(size),
+            glyph_fetch_buffer: Vec::new(),
+            outputs: Vec::new(),
         }
     }
 
     fn used_rect(&self) -> DeviceIntRect {
         self.allocator.used_rect
     }
 
     fn build(&mut self,
@@ -952,99 +959,99 @@ impl RenderTarget for ColorRenderTarget 
                                  render_tasks,
                                  deferred_resolves);
     }
 
     fn add_task(&mut self,
                 task_id: RenderTaskId,
                 ctx: &RenderTargetContext,
                 gpu_cache: &GpuCache,
-                render_tasks: &RenderTaskTree) {
+                render_tasks: &RenderTaskTree,
+                _: &ClipStore) {
         let task = render_tasks.get(task_id);
 
         match task.kind {
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: add_task() called on invalidated task");
             }
-            RenderTaskKind::Alpha(..) => {
+            RenderTaskKind::Alpha(ref info) => {
                 self.alpha_batcher.add_task(task_id);
+
+                // If this pipeline is registered as a frame output
+                // store the information necessary to do the copy.
+                if let Some(pipeline_id) = info.frame_output_pipeline_id {
+                    self.outputs.push(FrameOutput {
+                        pipeline_id,
+                        task_id,
+                    });
+                }
             }
             RenderTaskKind::VerticalBlur(..) => {
                 // Find the child render task that we are applying
                 // a vertical blur on.
-                self.vertical_blurs.push(BlurCommand {
-                    task_id: task_id.0 as i32,
-                    src_task_id: task.children[0].0 as i32,
-                    blur_direction: BlurDirection::Vertical as i32,
+                self.vertical_blurs.push(BlurInstance {
+                    task_address: render_tasks.get_task_address(task_id),
+                    src_task_address: render_tasks.get_task_address(task.children[0]),
+                    blur_direction: BlurDirection::Vertical,
                 });
             }
             RenderTaskKind::HorizontalBlur(..) => {
                 // Find the child render task that we are applying
                 // a horizontal blur on.
-                self.horizontal_blurs.push(BlurCommand {
-                    task_id: task_id.0 as i32,
-                    src_task_id: task.children[0].0 as i32,
-                    blur_direction: BlurDirection::Horizontal as i32,
+                self.horizontal_blurs.push(BlurInstance {
+                    task_address: render_tasks.get_task_address(task_id),
+                    src_task_address: render_tasks.get_task_address(task.children[0]),
+                    blur_direction: BlurDirection::Horizontal,
                 });
             }
             RenderTaskKind::CachePrimitive(prim_index) => {
                 let prim_metadata = ctx.prim_store.get_metadata(prim_index);
                 let prim_address = prim_metadata.gpu_location.as_int(gpu_cache);
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::TextShadow => {
                         let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
 
-                        // todo(gw): avoid / recycle this allocation...
-                        let mut instances = Vec::new();
-
                         let task_index = render_tasks.get_task_address(task_id);
 
                         for sub_prim_index in &prim.primitives {
                             let sub_metadata = ctx.prim_store.get_metadata(*sub_prim_index);
-                            let sub_prim_address = sub_metadata.gpu_location.as_int(gpu_cache);
+                            let sub_prim_address = gpu_cache.get_address(&sub_metadata.gpu_location);
                             let instance = SimplePrimitiveInstance::new(sub_prim_address,
                                                                         task_index,
                                                                         RenderTaskAddress(0),
-                                                                        PackedLayerIndex(0),
+                                                                        PackedLayerIndex(0).into(),
                                                                         0);     // z is disabled for rendering cache primitives
 
                             match sub_metadata.prim_kind {
                                 PrimitiveKind::TextRun => {
                                     // Add instances that reference the text run GPU location. Also supply
                                     // the parent text-shadow prim address as a user data field, allowing
                                     // the shader to fetch the text-shadow parameters.
                                     let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0];
+                                    let text_run_cache_prims = &mut self.text_run_cache_prims;
 
                                     let mut font = text.font.clone();
                                     font.size = font.size.scale_by(ctx.device_pixel_ratio);
                                     font.render_mode = text.shadow_render_mode;
 
-                                    let texture_id = ctx.resource_cache.get_glyphs(font,
-                                                                                   &text.glyph_keys,
-                                                                                   |index, handle| {
-                                        let uv_address = handle.as_int(gpu_cache);
-                                        instances.push(instance.build(index as i32,
-                                                                      uv_address,
-                                                                      prim_address));
-                                    });
+                                    ctx.resource_cache.fetch_glyphs(font,
+                                                                    &text.glyph_keys,
+                                                                    &mut self.glyph_fetch_buffer,
+                                                                    gpu_cache,
+                                                                    |texture_id, glyphs| {
+                                        let batch = text_run_cache_prims.entry(texture_id)
+                                                                        .or_insert(Vec::new());
 
-                                    if texture_id != SourceTexture::Invalid {
-                                        let textures = BatchTextures {
-                                            colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
-                                        };
-
-                                        self.text_run_cache_prims.extend_from_slice(&instances);
-                                        instances.clear();
-
-                                        debug_assert!(textures.colors[0] != SourceTexture::Invalid);
-                                        debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
-                                                      self.text_run_textures.colors[0] == textures.colors[0]);
-                                        self.text_run_textures = textures;
-                                    }
+                                        for glyph in glyphs {
+                                            batch.push(instance.build(glyph.index_in_text_run,
+                                                                      glyph.uv_rect_address.as_int(),
+                                                                      prim_address));
+                                        }
+                                    });
                                 }
                                 PrimitiveKind::Line => {
                                     self.line_cache_prims.push(instance.build(prim_address, 0, 0));
                                 }
                                 _ => {
                                     unreachable!("Unexpected sub primitive type");
                                 }
                             }
@@ -1089,17 +1096,18 @@ impl RenderTarget for AlphaRenderTarget 
     fn used_rect(&self) -> DeviceIntRect {
         self.allocator.used_rect
     }
 
     fn add_task(&mut self,
                 task_id: RenderTaskId,
                 ctx: &RenderTargetContext,
                 gpu_cache: &GpuCache,
-                render_tasks: &RenderTaskTree) {
+                render_tasks: &RenderTaskTree,
+                clip_store: &ClipStore) {
         let task = render_tasks.get(task_id);
         match task.kind {
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: add_task() called on invalidated task");
             }
             RenderTaskKind::Alpha(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::HorizontalBlur(..) |
@@ -1123,17 +1131,18 @@ impl RenderTarget for AlphaRenderTarget 
                 }
             }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add(task_address,
                                       &task_info.clips,
                                       &ctx.resource_cache,
                                       gpu_cache,
-                                      task_info.geometry_kind);
+                                      task_info.geometry_kind,
+                                      clip_store);
             }
         }
     }
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
@@ -1180,17 +1189,18 @@ impl RenderPass {
             RenderTargetKind::Alpha => self.alpha_targets.target_count(),
         }
     }
 
     pub fn build(&mut self,
                  ctx: &RenderTargetContext,
                  gpu_cache: &mut GpuCache,
                  render_tasks: &mut RenderTaskTree,
-                 deferred_resolves: &mut Vec<DeferredResolve>) {
+                 deferred_resolves: &mut Vec<DeferredResolve>,
+                 clip_store: &ClipStore) {
         profile_scope!("RenderPass::build");
 
         // Step through each task, adding to batches as appropriate.
         for task_id in &self.tasks {
             let task_id = *task_id;
 
             let target_kind = {
                 let task = render_tasks.get_mut(task_id);
@@ -1236,18 +1246,28 @@ impl RenderPass {
                         }
                     }
                 }
 
                 target_kind
             };
 
             match target_kind {
-                RenderTargetKind::Color => self.color_targets.add_task(task_id, ctx, gpu_cache, render_tasks),
-                RenderTargetKind::Alpha => self.alpha_targets.add_task(task_id, ctx, gpu_cache, render_tasks),
+                RenderTargetKind::Color => self.color_targets
+                                               .add_task(task_id,
+                                                         ctx,
+                                                         gpu_cache,
+                                                         render_tasks,
+                                                         clip_store),
+                RenderTargetKind::Alpha => self.alpha_targets
+                                               .add_task(task_id,
+                                                         ctx,
+                                                         gpu_cache,
+                                                         render_tasks,
+                                                         clip_store),
             }
         }
 
         self.color_targets.build(ctx, gpu_cache, render_tasks, deferred_resolves);
         self.alpha_targets.build(ctx, gpu_cache, render_tasks, deferred_resolves);
     }
 }
 
@@ -1322,144 +1342,21 @@ impl AlphaBatchKey {
             self.flags == other.flags &&
             self.blend_mode == other.blend_mode &&
             textures_compatible(self.textures.colors[0], other.textures.colors[0]) &&
             textures_compatible(self.textures.colors[1], other.textures.colors[1]) &&
             textures_compatible(self.textures.colors[2], other.textures.colors[2])
     }
 }
 
-#[repr(C)]
-#[derive(Debug)]
-pub enum BlurDirection {
-    Horizontal = 0,
-    Vertical,
-}
-
 #[inline]
 fn textures_compatible(t1: SourceTexture, t2: SourceTexture) -> bool {
     t1 == SourceTexture::Invalid || t2 == SourceTexture::Invalid || t1 == t2
 }
 
-// All Packed Primitives below must be 16 byte aligned.
-#[derive(Debug)]
-pub struct BlurCommand {
-    task_id: i32,
-    src_task_id: i32,
-    blur_direction: i32,
-}
-
-/// A clipping primitive drawn into the clipping mask.
-/// Could be an image or a rectangle, which defines the
-/// way `address` is treated.
-#[derive(Clone, Copy, Debug)]
-#[repr(C)]
-pub struct CacheClipInstance {
-    render_task_address: i32,
-    layer_index: i32,
-    segment: i32,
-    clip_data_address: GpuCacheAddress,
-    resource_address: GpuCacheAddress,
-}
-
-// 32 bytes per instance should be enough for anyone!
-#[derive(Debug, Clone)]
-pub struct PrimitiveInstance {
-    data: [i32; 8],
-}
-
-struct SimplePrimitiveInstance {
-    // TODO(gw): specific_prim_address is encoded as an i32, since
-    //           some primitives use GPU Cache and some still use
-    //           GPU Store. Once everything is converted to use the
-    //           on-demand GPU cache, then we change change this to
-    //           be an ivec2 of u16 - and encode the UV directly
-    //           so that the vertex shader can fetch directly.
-    pub specific_prim_address: i32,
-    pub task_index: i32,
-    pub clip_task_index: i32,
-    pub layer_index: i32,
-    pub z_sort_index: i32,
-}
-
-impl SimplePrimitiveInstance {
-    fn new(specific_prim_address: i32,
-           task_index: RenderTaskAddress,
-           clip_task_index: RenderTaskAddress,
-           layer_index: PackedLayerIndex,
-           z_sort_index: i32) -> SimplePrimitiveInstance {
-        SimplePrimitiveInstance {
-            specific_prim_address,
-            task_index: task_index.0 as i32,
-            clip_task_index: clip_task_index.0 as i32,
-            layer_index: layer_index.0 as i32,
-            z_sort_index,
-        }
-    }
-
-    fn build(&self, data0: i32, data1: i32, data2: i32) -> PrimitiveInstance {
-        PrimitiveInstance {
-            data: [
-                self.specific_prim_address,
-                self.task_index,
-                self.clip_task_index,
-                self.layer_index,
-                self.z_sort_index,
-                data0,
-                data1,
-                data2,
-            ]
-        }
-    }
-}
-
-pub struct CompositePrimitiveInstance {
-    pub task_address: RenderTaskAddress,
-    pub src_task_address: RenderTaskAddress,
-    pub backdrop_task_address: RenderTaskAddress,
-    pub data0: i32,
-    pub data1: i32,
-    pub z: i32,
-}
-
-impl CompositePrimitiveInstance {
-    fn new(task_address: RenderTaskAddress,
-           src_task_address: RenderTaskAddress,
-           backdrop_task_address: RenderTaskAddress,
-           data0: i32,
-           data1: i32,
-           z: i32) -> CompositePrimitiveInstance {
-        CompositePrimitiveInstance {
-            task_address,
-            src_task_address,
-            backdrop_task_address,
-            data0,
-            data1,
-            z,
-        }
-    }
-}
-
-impl From<CompositePrimitiveInstance> for PrimitiveInstance {
-    fn from(instance: CompositePrimitiveInstance) -> PrimitiveInstance {
-        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,
-                0,
-                0,
-            ]
-        }
-    }
-}
-
 #[derive(Debug)]
 pub struct AlphaPrimitiveBatch {
     pub key: AlphaBatchKey,
     pub instances: Vec<PrimitiveInstance>,
     pub item_rects: Vec<DeviceIntRect>,
 }
 
 impl AlphaPrimitiveBatch {
@@ -1528,43 +1425,53 @@ pub struct StackingContext {
 
     /// Type of the isolation of the content.
     pub isolation: ContextIsolation,
 
     /// Set for the root stacking context of a display list or an iframe. Used for determining
     /// when to isolate a mix-blend-mode composite.
     pub is_page_root: bool,
 
+    /// Set to true if this is the root stacking context for a pipeline.
+    pub is_pipeline_root: bool,
+
     /// Whether or not this stacking context has any visible components, calculated
     /// based on the size and position of all children and how they are clipped.
     pub is_visible: bool,
+
+    /// Current stacking context visibility of backface.
+    pub is_backface_visible: bool,
 }
 
 impl StackingContext {
     pub fn new(pipeline_id: PipelineId,
                reference_frame_offset: LayerVector2D,
                is_page_root: bool,
+               is_pipeline_root: bool,
                reference_frame_id: ClipId,
                transform_style: TransformStyle,
-               composite_ops: CompositeOps)
+               composite_ops: CompositeOps,
+               is_backface_visible: bool)
                -> StackingContext {
         let isolation = match transform_style {
             TransformStyle::Flat => ContextIsolation::None,
             TransformStyle::Preserve3D => ContextIsolation::Items,
         };
         StackingContext {
             pipeline_id,
             reference_frame_offset,
             reference_frame_id,
             screen_bounds: DeviceIntRect::zero(),
             isolated_items_bounds: LayerRect::zero(),
             composite_ops,
             isolation,
             is_page_root,
+            is_pipeline_root,
             is_visible: false,
+            is_backface_visible,
         }
     }
 
     pub fn can_contribute_to_scene(&self) -> bool {
         !self.composite_ops.will_make_invisible()
     }
 }
 
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -5,17 +5,17 @@ authors = ["Glenn Watson <gw@intuitionli
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 
 [dependencies]
-app_units = "0.5"
+app_units = "0.5.6"
 bincode = "0.8"
 byteorder = "1.0"
 euclid = "0.15"
 fxhash = "0.2.1"
 heapsize = ">= 0.3.6, < 0.5"
 ipc-channel = {version = "0.8", optional = true}
 serde = { version = "1.0", features = ["rc", "derive"] }
 time = "0.1"
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -139,16 +139,17 @@ pub enum DocumentMsg {
         preserve_frame_state: bool,
         resources: ResourceUpdates,
     },
     SetPageZoom(ZoomFactor),
     SetPinchZoom(ZoomFactor),
     SetPan(DeviceIntPoint),
     SetRootPipeline(PipelineId),
     RemovePipeline(PipelineId),
+    EnableFrameOutput(PipelineId, bool),
     SetWindowParameters {
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
     },
     Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
     ScrollNodeWithId(LayoutPoint, ClipId, ScrollClamping),
     TickScrollingBounce,
     GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
@@ -165,28 +166,31 @@ impl fmt::Debug for DocumentMsg {
             DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
             DocumentMsg::RemovePipeline(..) => "DocumentMsg::RemovePipeline",
             DocumentMsg::SetWindowParameters{..} => "DocumentMsg::SetWindowParameters",
             DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
             DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
             DocumentMsg::TickScrollingBounce => "DocumentMsg::TickScrollingBounce",
             DocumentMsg::GetScrollNodeState(..) => "DocumentMsg::GetScrollNodeState",
             DocumentMsg::GenerateFrame(..) => "DocumentMsg::GenerateFrame",
+            DocumentMsg::EnableFrameOutput(..) => "DocumentMsg::EnableFrameOutput",
         })
     }
 }
 
 #[derive(Debug, 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.
+    EnableAlphaRectsDebug(bool),
     // Fetch current documents and display lists.
     FetchDocuments,
     // Fetch current passes and batches.
     FetchPasses,
     // Fetch clip-scroll tree.
     FetchClipScrollTree,
 }
 
@@ -576,16 +580,25 @@ impl RenderApi {
     }
 
     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>) {
         self.send(document_id, DocumentMsg::GenerateFrame(property_bindings));
     }
 }
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use euclid::{SideOffsets2D, TypedSideOffsets2D};
-use {ColorF, FontInstanceKey, ImageKey, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
+use euclid::{SideOffsets2D, TypedRect, TypedSideOffsets2D};
+use {ColorF, FontInstanceKey, ImageKey, LayerPixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
 use {GlyphOptions, LayoutVector2D, PipelineId, PropertyBinding};
 
 // NOTE: some of these structs have an "IMPLICIT" comment.
 // This indicates that the BuiltDisplayList will have serialized
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -35,22 +35,41 @@ impl ClipAndScrollInfo {
     pub fn clip_node_id(&self) -> ClipId {
         self.clip_node_id.unwrap_or(self.scroll_node_id)
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct DisplayItem {
     pub item: SpecificDisplayItem,
-    pub rect: LayoutRect,
-    pub local_clip: LocalClip,
     pub clip_and_scroll: ClipAndScrollInfo,
+    pub info: LayoutPrimitiveInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct PrimitiveInfo<T> {
+    pub rect: TypedRect<f32, T>,
+    pub local_clip: Option<LocalClip>,
+    pub is_backface_visible: bool,
+}
+
+impl LayerPrimitiveInfo {
+    pub fn new(rect: TypedRect<f32, LayerPixel>) -> Self {
+        PrimitiveInfo {
+            rect: rect,
+            local_clip: Some(LocalClip::from(rect)),
+            is_backface_visible: true,
+        }
+    }
+}
+
+pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
+pub type LayerPrimitiveInfo = PrimitiveInfo<LayerPixel>;
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
     ScrollFrame(ScrollFrameDisplayItem),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     Line(LineDisplayItem),
     Text(TextDisplayItem),
     Image(ImageDisplayItem),
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -5,19 +5,19 @@
 use bincode;
 use serde::{Deserialize, Serialize, Serializer};
 use serde::ser::{SerializeSeq, SerializeMap};
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
 use {ExtendMode, FastHashMap, FastHashSet, FilterOp, FontInstanceKey, GlyphIndex, GlyphInstance};
 use {GlyphOptions, Gradient, GradientDisplayItem, GradientStop, IframeDisplayItem};
-use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, LayoutPoint, LayoutRect, LayoutSize};
-use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, LocalClip};
-use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
+use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, LayerPrimitiveInfo, LayoutPoint, LayoutRect, LayoutSize};
+use {LayoutTransform, LayoutVector2D, LayoutPrimitiveInfo, LineDisplayItem, LineOrientation, LineStyle, LocalClip};
+use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, PrimitiveInfo, RadialGradient};
 use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy};
 use {ScrollSensitivity, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem};
 use {StickyFrameInfo, TextDisplayItem, TextShadow, TransformStyle};
 use {YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
 // We don't want to push a long text-run. If a text-run is too long, split it into several parts.
 // Please check the renderer::MAX_VERTEX_TEXTURE_WIDTH for the detail.
@@ -183,19 +183,22 @@ impl<'a> BuiltDisplayListIter<'a> {
     }
 
     pub fn new_with_list_and_data(list: &'a BuiltDisplayList, data: &'a [u8]) -> Self {
         BuiltDisplayListIter {
             list,
             data: &data,
             cur_item: DisplayItem { // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
-                rect: LayoutRect::zero(),
-                local_clip: LocalClip::from(LayoutRect::zero()),
                 clip_and_scroll: ClipAndScrollInfo::simple(ClipId::new(0, PipelineId::dummy())),
+                info: PrimitiveInfo {
+                    rect: LayoutRect::zero(),
+                    local_clip: Some(LocalClip::from(LayoutRect::zero())),
+                    is_backface_visible: true,
+                },
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
         }
     }
@@ -314,25 +317,30 @@ impl<'a> Iterator for GlyphsIter<'a> {
 
 // Some of these might just become ItemRanges
 impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn display_item(&self) -> &DisplayItem {
         &self.iter.cur_item
     }
 
     pub fn rect(&self) -> LayoutRect {
-        self.iter.cur_item.rect
+        self.iter.cur_item.info.rect
+    }
+
+    pub fn get_layer_primitive_info(&self, offset: &LayoutVector2D) -> LayerPrimitiveInfo {
+        let info = self.iter.cur_item.info;
+        LayerPrimitiveInfo {
+            rect: info.rect.translate(&offset),
+            local_clip: info.local_clip.map(|clip| clip.create_with_offset(offset)),
+            is_backface_visible: info.is_backface_visible,
+        }
     }
 
     pub fn local_clip(&self) -> &LocalClip {
-        &self.iter.cur_item.local_clip
-    }
-
-    pub fn local_clip_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
-        self.iter.cur_item.local_clip.create_with_offset(offset)
+        self.iter.cur_item.info.local_clip.as_ref().unwrap()
     }
 
     pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
         self.iter.cur_item.clip_and_scroll
     }
 
     pub fn item(&self) -> &SpecificDisplayItem {
         &self.iter.cur_item.item
@@ -353,16 +361,20 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn filters(&self) -> ItemRange<FilterOp> {
         self.iter.cur_filters
     }
 
     pub fn display_list(&self) -> &BuiltDisplayList {
         self.iter.display_list()
     }
 
+    pub fn is_backface_visible(&self) -> bool {
+        self.iter.cur_item.info.is_backface_visible
+    }
+
     // Creates a new iterator where this element's iterator is, to hack around borrowck.
     pub fn sub_iter(&self) -> BuiltDisplayListIter<'a> {
         BuiltDisplayListIter::new_with_list_and_data(self.iter.list, self.iter.data)
     }
 }
 
 impl<'de, 'a, T: Deserialize<'de>> AuxIter<'a, T> {
     pub fn new(mut data: &'a [u8]) -> Self {
@@ -500,33 +512,32 @@ impl DisplayListBuilder {
             }
         }
 
         self.data = temp.data;
     }
 
     fn push_item(&mut self,
                  item: SpecificDisplayItem,
-                 rect: LayoutRect,
-                 local_clip: Option<LocalClip>) {
-        let local_clip = local_clip.unwrap_or_else(|| LocalClip::from(rect));
+                 info: &LayoutPrimitiveInfo) {
+        let mut new_info = info.clone();
+        new_info.local_clip = info.local_clip.or(Some(LocalClip::from(info.rect)));
         bincode::serialize_into(&mut self.data, &DisplayItem {
             item,
-            rect,
-            local_clip: local_clip,
             clip_and_scroll: *self.clip_stack.last().unwrap(),
+            info: new_info,
         }, bincode::Infinite).unwrap();
     }
 
     fn push_new_empty_item(&mut self, item: SpecificDisplayItem) {
+        let info = LayoutPrimitiveInfo::new(LayoutRect::zero());
         bincode::serialize_into(&mut self.data, &DisplayItem {
             item,
-            rect: LayoutRect::zero(),
-            local_clip: LocalClip::from(LayoutRect::zero()),
             clip_and_scroll: *self.clip_stack.last().unwrap(),
+            info,
         }, bincode::Infinite).unwrap();
     }
 
     fn push_iter<I>(&mut self, iter: I)
     where I: IntoIterator,
           I::IntoIter: ExactSizeIterator,
           I::Item: Serialize,
     {
@@ -538,88 +549,87 @@ impl DisplayListBuilder {
         for elem in iter {
             count += 1;
             bincode::serialize_into(&mut self.data, &elem, bincode::Infinite).unwrap();
         }
 
         debug_assert_eq!(len, count);
     }
 
-    pub fn push_rect(&mut self, rect: LayoutRect, local_clip: Option<LocalClip>, color: ColorF) {
+    pub fn push_rect(&mut self,
+                     info: &LayoutPrimitiveInfo,
+                     color: ColorF) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem {
             color,
         });
 
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_line(&mut self,
-                     local_clip: Option<LocalClip>,
+                     info: &LayoutPrimitiveInfo,
                      baseline: f32,
                      start: f32,
                      end: f32,
                      orientation: LineOrientation,
                      width: f32,
                      color: ColorF,
                      style: LineStyle) {
         let item = SpecificDisplayItem::Line(LineDisplayItem {
             baseline, start, end, orientation,
             width, color, style,
         });
 
-        self.push_item(item, LayoutRect::zero(), local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_image(&mut self,
-                      rect: LayoutRect,
-                      local_clip: Option<LocalClip>,
+                      info: &LayoutPrimitiveInfo,
                       stretch_size: LayoutSize,
                       tile_spacing: LayoutSize,
                       image_rendering: ImageRendering,
                       key: ImageKey) {
         let item = SpecificDisplayItem::Image(ImageDisplayItem {
             image_key: key,
             stretch_size,
             tile_spacing,
             image_rendering,
         });
 
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     /// Push a yuv image. All planar data in yuv image should use the same buffer type.
     pub fn push_yuv_image(&mut self,
-                          rect: LayoutRect,
-                          local_clip: Option<LocalClip>,
+                          info: &LayoutPrimitiveInfo,
                           yuv_data: YuvData,
                           color_space: YuvColorSpace,
                           image_rendering: ImageRendering) {
         let item = SpecificDisplayItem::YuvImage(YuvImageDisplayItem {
             yuv_data,
             color_space,
             image_rendering,
         });
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_text(&mut self,
-                     rect: LayoutRect,
-                     local_clip: Option<LocalClip>,
+                     info: &LayoutPrimitiveInfo,
                      glyphs: &[GlyphInstance],
                      font_key: FontInstanceKey,
                      color: ColorF,
                      glyph_options: Option<GlyphOptions>) {
         let item = SpecificDisplayItem::Text(TextDisplayItem {
             color,
             font_key,
             glyph_options,
         });
 
         for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
-            self.push_item(item, rect, local_clip);
+            self.push_item(item, info);
             self.push_iter(split_glyphs);
 
             // Remember that we've seen these glyphs
             self.cache_glyphs(font_key, color, split_glyphs.iter().map(|glyph| glyph.index));
         }
     }
 
     fn cache_glyphs<I: Iterator<Item=GlyphIndex>>(&mut self,
@@ -799,31 +809,29 @@ impl DisplayListBuilder {
             end_center,
             end_radius,
             ratio_xy,
             extend_mode,
         }
     }
 
     pub fn push_border(&mut self,
-                       rect: LayoutRect,
-                       local_clip: Option<LocalClip>,
+                       info: &LayoutPrimitiveInfo,
                        widths: BorderWidths,
                        details: BorderDetails) {
         let item = SpecificDisplayItem::Border(BorderDisplayItem {
             details,
             widths,
         });
 
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_box_shadow(&mut self,
-                           rect: LayoutRect,
-                           local_clip: Option<LocalClip>,
+                           info: &LayoutPrimitiveInfo,
                            box_bounds: LayoutRect,
                            offset: LayoutVector2D,
                            color: ColorF,
                            blur_radius: f32,
                            spread_radius: f32,
                            border_radius: f32,
                            clip_mode: BoxShadowClipMode) {
         let item = SpecificDisplayItem::BoxShadow(BoxShadowDisplayItem {
@@ -831,68 +839,66 @@ impl DisplayListBuilder {
             offset,
             color,
             blur_radius,
             spread_radius,
             border_radius,
             clip_mode,
         });
 
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_gradient(&mut self,
-                         rect: LayoutRect,
-                         local_clip: Option<LocalClip>,
+                         info: &LayoutPrimitiveInfo,
                          gradient: Gradient,
                          tile_size: LayoutSize,
                          tile_spacing: LayoutSize) {
         let item = SpecificDisplayItem::Gradient(GradientDisplayItem {
             gradient,
             tile_size,
             tile_spacing,
         });
 
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_radial_gradient(&mut self,
-                                rect: LayoutRect,
-                                local_clip: Option<LocalClip>,
+                                info: &LayoutPrimitiveInfo,
                                 gradient: RadialGradient,
                                 tile_size: LayoutSize,
                                 tile_spacing: LayoutSize) {
         let item = SpecificDisplayItem::RadialGradient(RadialGradientDisplayItem {
             gradient,
             tile_size,
             tile_spacing,
         });
 
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     pub fn push_stacking_context(&mut self,
+                                 info: &LayoutPrimitiveInfo,
                                  scroll_policy: ScrollPolicy,
-                                 bounds: LayoutRect,
                                  transform: Option<PropertyBinding<LayoutTransform>>,
                                  transform_style: TransformStyle,
                                  perspective: Option<LayoutTransform>,
                                  mix_blend_mode: MixBlendMode,
                                  filters: Vec<FilterOp>) {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
                 scroll_policy,
                 transform,
                 transform_style,
                 perspective,
                 mix_blend_mode,
             }
         });
 
-        self.push_item(item, bounds, None);
+        self.push_item(item, info);
         self.push_iter(&filters);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopStackingContext);
     }
 
     pub fn push_stops(&mut self, stops: &[GradientStop]) {
@@ -923,17 +929,23 @@ impl DisplayListBuilder {
         let id = self.generate_clip_id(id);
         let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
             id: id,
             parent_id: self.clip_stack.last().unwrap().scroll_node_id,
             image_mask: image_mask,
             scroll_sensitivity,
         });
 
-        self.push_item(item, content_rect, Some(LocalClip::from(clip_rect)));
+        let info = LayoutPrimitiveInfo {
+            rect: content_rect,
+            local_clip: Some(LocalClip::from(clip_rect)),
+            is_backface_visible: true,
+        };
+
+        self.push_item(item, &info);
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_clip<I>(&mut self,
                           id: Option<ClipId>,
                           clip_rect: LayoutRect,
                           complex_clips: I,
@@ -943,33 +955,41 @@ impl DisplayListBuilder {
                                 I::IntoIter: ExactSizeIterator {
         let id = self.generate_clip_id(id);
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
             id,
             parent_id: self.clip_stack.last().unwrap().scroll_node_id,
             image_mask: image_mask,
         });
 
-        self.push_item(item, clip_rect, Some(LocalClip::from(clip_rect)));
+        let info = LayoutPrimitiveInfo::new(clip_rect);
+
+        self.push_item(item, &info);
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_sticky_frame(&mut self,
                                id: Option<ClipId>,
                                frame_rect: LayoutRect,
                                sticky_frame_info: StickyFrameInfo)
                                -> ClipId {
         let id = self.generate_clip_id(id);
         let item = SpecificDisplayItem::StickyFrame(StickyFrameDisplayItem {
             id,
             sticky_frame_info,
         });
 
-        self.push_item(item, frame_rect, None);
+        let info = LayoutPrimitiveInfo {
+            rect: frame_rect,
+            local_clip: None,
+            is_backface_visible: true,
+        };
+
+        self.push_item(item, &info);
         id
     }
 
     pub fn push_clip_id(&mut self, id: ClipId) {
         self.clip_stack.push(ClipAndScrollInfo::simple(id));
     }
 
     pub fn push_clip_and_scroll_info(&mut self, info: ClipAndScrollInfo) {
@@ -977,21 +997,20 @@ impl DisplayListBuilder {
     }
 
     pub fn pop_clip_id(&mut self) {
         self.clip_stack.pop();
         assert!(self.clip_stack.len() > 0);
     }
 
     pub fn push_iframe(&mut self,
-                       rect: LayoutRect,
-                       local_clip: Option<LocalClip>,
+                       info: &LayoutPrimitiveInfo,
                        pipeline_id: PipelineId) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem { pipeline_id: pipeline_id });
-        self.push_item(item, rect, local_clip);
+        self.push_item(item, info);
     }
 
     // Don't use this function. It will go away.
     //
     // We're using this method as a hack in Gecko to retain parts sub-parts of display
     // lists so that we can regenerate them without building Gecko display items. WebRender
     // will replace references to the root scroll frame id with the current scroll frame
     // id.
@@ -1004,22 +1023,19 @@ impl DisplayListBuilder {
         }
 
         // Only append the actual items, not any caches
         self.data.extend_from_slice(built_display_list.item_slice());
         self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList);
     }
 
     pub fn push_text_shadow(&mut self,
-                            rect: LayoutRect,
-                            local_clip: Option<LocalClip>,
+                            info: &LayoutPrimitiveInfo,
                             shadow: TextShadow) {
-        self.push_item(SpecificDisplayItem::PushTextShadow(shadow),
-                       rect,
-                       local_clip);
+        self.push_item(SpecificDisplayItem::PushTextShadow(shadow), info);
     }
 
     pub fn pop_text_shadow(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopTextShadow);
     }
 
     pub fn finalize(mut self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
 
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -41,17 +41,17 @@ pub type LayoutPixel = LayerPixel;
 
 pub type LayoutRect = LayerRect;
 pub type LayoutPoint = LayerPoint;
 pub type LayoutVector2D = LayerVector2D;
 pub type LayoutVector3D = LayerVector3D;
 pub type LayoutSize = LayerSize;
 
 /// Geometry in a layer's local coordinate space (logical pixels).
-#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
 pub struct LayerPixel;
 
 pub type LayerRect = TypedRect<f32, LayerPixel>;
 pub type LayerPoint = TypedPoint2D<f32, LayerPixel>;
 pub type LayerPoint3D = TypedPoint3D<f32, LayerPixel>;
 pub type LayerVector2D = TypedVector2D<f32, LayerPixel>;
 pub type LayerVector3D = TypedVector3D<f32, LayerPixel>;
 pub type LayerSize = TypedSize2D<f32, LayerPixel>;
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -5,15 +5,15 @@ authors = ["The Mozilla Project Develope
 license = "MPL-2.0"
 
 [dependencies]
 webrender_api = {path = "../webrender_api", version = "0.50.0"}
 bincode = "0.8"
 rayon = "0.8"
 thread_profiler = "0.1.1"
 euclid = "0.15"
-app_units = "0.5"
+app_units = "0.5.6"
 gleam = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
 version = "0.50.0"
 default-features = false