Bug 1438892 - Update webrender to e8d2ffb404a85651fe08a6d09abbece9bd2b9182. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 20 Feb 2018 09:02:40 -0500
changeset 404516 7b1107be0b7408386eb7f4287ea5ebe5e81be0fb
parent 404515 0acceb224b7d7fd23402a75655b8aeff45d5aace
child 404517 2f0342644d576df1d1c0bda70a629c019187e6f4
push id100027
push usershindli@mozilla.com
push dateTue, 20 Feb 2018 19:15:11 +0000
treeherdermozilla-inbound@080fed1bd67f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1438892
milestone60.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 1438892 - Update webrender to e8d2ffb404a85651fe08a6d09abbece9bd2b9182. r=jrmuizel MozReview-Commit-ID: hO7HCOnrIz
gfx/webrender/Cargo.toml
gfx/webrender/examples/blob.rs
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_blend.glsl
gfx/webrender/res/brush_mix_blend.glsl
gfx/webrender/res/brush_radial_gradient.glsl
gfx/webrender/res/brush_yuv_image.glsl
gfx/webrender/res/ellipse.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_border_corner.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/ps_gradient.glsl
gfx/webrender/res/ps_radial_gradient.glsl
gfx/webrender/res/ps_yuv_image.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/tiling.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -20,17 +20,17 @@ bincode = "0.9"
 byteorder = "1.0"
 euclid = "0.16"
 fxhash = "0.2.1"
 gleam = "0.4.20"
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.1.32"
 time = "0.1"
-rayon = "0.8"
+rayon = "1"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
 plane-split = "0.7"
 png = { optional = true, version = "0.11" }
 smallvec = "0.6"
 ws = { optional = true, version = "0.7.3" }
 serde_json = { optional = true, version = "1.0" }
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -6,17 +6,17 @@ extern crate gleam;
 extern crate glutin;
 extern crate rayon;
 extern crate webrender;
 
 #[path = "common/boilerplate.rs"]
 mod boilerplate;
 
 use boilerplate::{Example, HandyDandyRectBuilder};
-use rayon::{Configuration as ThreadPoolConfig, ThreadPool};
+use rayon::{ThreadPool, ThreadPoolBuilder};
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, channel};
 use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, ResourceUpdates};
 
 // This example shows how to implement a very basic BlobImageRenderer that can only render
 // a checkerboard pattern.
@@ -277,20 +277,21 @@ impl Example for App {
             blob_img2,
         );
 
         builder.pop_stacking_context();
     }
 }
 
 fn main() {
-    let worker_config =
-        ThreadPoolConfig::new().thread_name(|idx| format!("WebRender:Worker#{}", idx));
+    let workers =
+        ThreadPoolBuilder::new().thread_name(|idx| format!("WebRender:Worker#{}", idx))
+                                .build();
 
-    let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
+    let workers = Arc::new(workers.unwrap());
 
     let opts = webrender::RendererOptions {
         workers: Some(Arc::clone(&workers)),
         // Register our blob renderer, so that WebRender integrates it in the resource cache..
         // Share the same pool of worker threads between WebRender and our blob renderer.
         blob_image_renderer: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
         ..Default::default()
     };
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -10,39 +10,43 @@ void brush_vs(
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 );
 
 #define VECS_PER_BRUSH_PRIM                 2
 #define VECS_PER_SEGMENT                    2
 
+#define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
+
 struct BrushInstance {
     int picture_address;
     int prim_address;
     int clip_chain_rect_index;
     int scroll_node_id;
     int clip_address;
     int z;
     int segment_index;
     int edge_mask;
+    int flags;
     ivec3 user_data;
 };
 
 BrushInstance load_brush() {
     BrushInstance bi;
 
     bi.picture_address = aData0.x & 0xffff;
     bi.clip_address = aData0.x >> 16;
     bi.prim_address = aData0.y;
     bi.clip_chain_rect_index = aData0.z >> 16;
     bi.scroll_node_id = aData0.z & 0xffff;
     bi.z = aData0.w;
     bi.segment_index = aData1.x & 0xffff;
-    bi.edge_mask = aData1.x >> 16;
+    bi.edge_mask = (aData1.x >> 16) & 0xff;
+    bi.flags = (aData1.x >> 24);
     bi.user_data = aData1.yzw;
 
     return bi;
 }
 
 struct BrushPrimitive {
     RectWithSize local_rect;
     RectWithSize local_clip_rect;
@@ -130,24 +134,27 @@ void main(void) {
             //           items. For now, just ensure it has no
             //           effect. We can tidy this up as we move
             //           more items to be brush shaders.
 #ifdef WR_FEATURE_ALPHA_PASS
             vLocalBounds = vec4(vec2(-1000000.0), vec2(1000000.0));
 #endif
         } else {
             bvec4 edge_mask = notEqual(brush.edge_mask & ivec4(1, 2, 4, 8), ivec4(0));
+            bool do_perspective_interpolation = (brush.flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0;
+
             vi = write_transform_vertex(
                 local_segment_rect,
                 brush_prim.local_rect,
                 brush_prim.local_clip_rect,
                 mix(vec4(0.0), vec4(1.0), edge_mask),
                 float(brush.z),
                 scroll_node,
-                pic_task
+                pic_task,
+                do_perspective_interpolation
             );
         }
 
         // For brush instances in the alpha pass, always write
         // out clip information.
         // TODO(gw): It's possible that we might want alpha
         //           shaders that don't clip in the future,
         //           but it's reasonable to assume that one
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #define VECS_PER_SPECIFIC_BRUSH 5
+#define FORCE_NO_PERSPECTIVE
 
 #include shared,prim_shared,brush
 
 varying vec3 vUv;
-varying float vW;
 
 flat varying float vAmount;
 flat varying int vOp;
 flat varying mat4 vColorMat;
 flat varying vec4 vColorOffset;
 
 #ifdef WR_VERTEX_SHADER
 
@@ -20,20 +20,19 @@ void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
-    vec2 uv = (vi.snapped_device_pos +
-               src_task.common_data.task_rect.p0 -
-               src_task.content_origin) * vi.w;
-    vW = vi.w;
+    vec2 uv = vi.snapped_device_pos +
+              src_task.common_data.task_rect.p0 -
+              src_task.content_origin;
     vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
 
     vOp = user_data.y;
 
     float lumR = 0.2126;
     float lumG = 0.7152;
     float lumB = 0.0722;
     float oneMinusLumR = 1.0 - lumR;
@@ -110,18 +109,17 @@ vec4 Brightness(vec4 Cs, float amount) {
     return vec4(clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0)), Cs.a);
 }
 
 vec4 Opacity(vec4 Cs, float amount) {
     return vec4(Cs.rgb, Cs.a * amount);
 }
 
 vec4 brush_fs() {
-    vec2 uv = vUv.xy / vW;
-    vec4 Cs = texture(sColor0, vec3(uv, vUv.z));
+    vec4 Cs = texture(sColor0, vUv);
 
     // Un-premultiply the input.
     Cs.rgb /= Cs.a;
 
     vec4 color;
 
     switch (vOp) {
         case 0:
--- a/gfx/webrender/res/brush_mix_blend.glsl
+++ b/gfx/webrender/res/brush_mix_blend.glsl
@@ -1,46 +1,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #define VECS_PER_SPECIFIC_BRUSH 5
 
 #include shared,prim_shared,brush
 
-varying vec3 vUv;
-varying float vW;
-
 varying vec3 vSrcUv;
 varying vec3 vBackdropUv;
 flat varying int vOp;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vOp = user_data.x;
-    vW = vi.w;
 
     PictureTask src_task = fetch_picture_task(user_data.z);
-    vec2 src_uv = (vi.snapped_device_pos +
-                   src_task.common_data.task_rect.p0 -
-                   src_task.content_origin) * vi.w;
+    vec2 src_uv = vi.snapped_device_pos +
+                  src_task.common_data.task_rect.p0 -
+                  src_task.content_origin;
     vSrcUv = vec3(src_uv / texture_size, src_task.common_data.texture_layer_index);
 
     RenderTaskCommonData backdrop_task = fetch_render_task_common_data(user_data.y);
-    vec2 backdrop_uv = (vi.snapped_device_pos +
-                        backdrop_task.task_rect.p0 -
-                        src_task.content_origin) * vi.w;
+    vec2 backdrop_uv = vi.snapped_device_pos +
+                       backdrop_task.task_rect.p0 -
+                       src_task.content_origin;
     vBackdropUv = vec3(backdrop_uv / texture_size, backdrop_task.texture_layer_index);
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 float gauss(float x, float sigma) {
     if (sigma == 0.0)
         return 1.0;
@@ -200,20 +196,18 @@ const int MixBlendMode_SoftLight   = 9;
 const int MixBlendMode_Difference  = 10;
 const int MixBlendMode_Exclusion   = 11;
 const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 vec4 brush_fs() {
-    vec2 uv = vUv.xy / vW;
-
-    vec4 Cb = texture(sCacheRGBA8, vBackdropUv);
-    vec4 Cs = texture(sCacheRGBA8, vSrcUv);
+    vec4 Cb = textureLod(sCacheRGBA8, vBackdropUv, 0.0);
+    vec4 Cs = textureLod(sCacheRGBA8, vSrcUv, 0.0);
 
     if (Cb.a == 0.0) {
         return Cs;
     }
     if (Cs.a == 0.0) {
         return vec4(0.0);
     }
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 2
+
+#include shared,prim_shared,brush
+
+flat varying int vGradientAddress;
+flat varying float vGradientRepeat;
+
+flat varying vec2 vStartCenter;
+flat varying vec2 vEndCenter;
+flat varying float vStartRadius;
+flat varying float vEndRadius;
+
+varying vec2 vPos;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 vLocalPos;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+
+struct RadialGradient {
+    vec4 start_end_center;
+    vec4 start_end_radius_ratio_xy_extend_mode;
+};
+
+RadialGradient fetch_radial_gradient(int address) {
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+    return RadialGradient(data[0], data[1]);
+}
+
+void brush_vs(
+    VertexInfo vi,
+    int prim_address,
+    RectWithSize local_rect,
+    ivec3 user_data,
+    PictureTask pic_task
+) {
+    RadialGradient gradient = fetch_radial_gradient(prim_address);
+
+    vPos = vi.local_pos - local_rect.p0;
+
+    vStartCenter = gradient.start_end_center.xy;
+    vEndCenter = gradient.start_end_center.zw;
+
+    vStartRadius = gradient.start_end_radius_ratio_xy_extend_mode.x;
+    vEndRadius = gradient.start_end_radius_ratio_xy_extend_mode.y;
+
+    // Transform all coordinates by the y scale so the
+    // fragment shader can work with circles
+    float ratio_xy = gradient.start_end_radius_ratio_xy_extend_mode.z;
+    vPos.y *= ratio_xy;
+    vStartCenter.y *= ratio_xy;
+    vEndCenter.y *= ratio_xy;
+
+    vGradientAddress = user_data.x;
+
+    // Whether to repeat the gradient instead of clamping.
+    vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) != EXTEND_MODE_CLAMP);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    vLocalPos = vi.local_pos;
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec4 brush_fs() {
+    vec2 cd = vEndCenter - vStartCenter;
+    vec2 pd = vPos - vStartCenter;
+    float rd = vEndRadius - vStartRadius;
+
+    // Solve for t in length(t * cd - pd) = vStartRadius + t * rd
+    // using a quadratic equation in form of At^2 - 2Bt + C = 0
+    float A = dot(cd, cd) - rd * rd;
+    float B = dot(pd, cd) + vStartRadius * rd;
+    float C = dot(pd, pd) - vStartRadius * vStartRadius;
+
+    float offset;
+    if (A == 0.0) {
+        // Since A is 0, just solve for -2Bt + C = 0
+        if (B == 0.0) {
+            discard;
+        }
+        float t = 0.5 * C / B;
+        if (vStartRadius + rd * t >= 0.0) {
+            offset = t;
+        } else {
+            discard;
+        }
+    } else {
+        float discr = B * B - A * C;
+        if (discr < 0.0) {
+            discard;
+        }
+        discr = sqrt(discr);
+        float t0 = (B + discr) / A;
+        float t1 = (B - discr) / A;
+        if (vStartRadius + rd * t0 >= 0.0) {
+            offset = t0;
+        } else if (vStartRadius + rd * t1 >= 0.0) {
+            offset = t1;
+        } else {
+            discard;
+        }
+    }
+
+    vec4 color = sample_gradient(vGradientAddress,
+                                 offset,
+                                 vGradientRepeat);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    color *= init_transform_fs(vLocalPos);
+#endif
+
+    return color;
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/brush_yuv_image.glsl
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 0
+
+#include shared,prim_shared,brush
+
+// TODO(gw): Consider whether we should even have separate shader compilations
+//           for the various YUV modes. To save on the number of shaders we
+//           need to compile, it might be worth just doing this as an
+//           uber-shader instead.
+// TODO(gw): Regardless of the above, we should remove the separate shader
+//           compilations for the different color space matrix below. That
+//           can be provided by a branch in the VS and pushed through the
+//           interpolators, or even as a uniform that breaks batches, rather
+//           that needing to compile to / select a different shader when
+//           there is a different color space.
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 vLocalPos;
+#endif
+
+#if defined (WR_FEATURE_YUV_PLANAR)
+    varying vec3 vUv_Y;
+    flat varying vec4 vUvBounds_Y;
+
+    varying vec3 vUv_U;
+    flat varying vec4 vUvBounds_U;
+
+    varying vec3 vUv_V;
+    flat varying vec4 vUvBounds_V;
+#elif defined (WR_FEATURE_YUV_NV12)
+    varying vec3 vUv_Y;
+    flat varying vec4 vUvBounds_Y;
+
+    varying vec3 vUv_UV;
+    flat varying vec4 vUvBounds_UV;
+#elif defined (WR_FEATURE_YUV_INTERLEAVED)
+    varying vec3 vUv_YUV;
+    flat varying vec4 vUvBounds_YUV;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+void write_uv_rect(
+    int resource_id,
+    vec2 f,
+    vec2 texture_size,
+    out vec3 uv,
+    out vec4 uv_bounds
+) {
+    ImageResource res = fetch_image_resource(resource_id);
+    vec2 uv0 = res.uv_rect.p0;
+    vec2 uv1 = res.uv_rect.p1;
+
+    uv.xy = mix(uv0, uv1, f);
+    uv.z = res.layer;
+
+    uv_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5));
+
+    #ifndef WR_FEATURE_TEXTURE_RECT
+        uv.xy /= texture_size;
+        uv_bounds /= texture_size.xyxy;
+    #endif
+}
+
+void brush_vs(
+    VertexInfo vi,
+    int prim_address,
+    RectWithSize local_rect,
+    ivec3 user_data,
+    PictureTask pic_task
+) {
+    vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    vLocalPos = vi.local_pos;
+#endif
+
+#if defined (WR_FEATURE_YUV_PLANAR)
+    write_uv_rect(user_data.x, f, vec2(textureSize(sColor0, 0).xy), vUv_Y, vUvBounds_Y);
+    write_uv_rect(user_data.y, f, vec2(textureSize(sColor1, 0).xy), vUv_U, vUvBounds_U);
+    write_uv_rect(user_data.z, f, vec2(textureSize(sColor2, 0).xy), vUv_V, vUvBounds_V);
+#elif defined (WR_FEATURE_YUV_NV12)
+    write_uv_rect(user_data.x, f, vec2(textureSize(sColor0, 0).xy), vUv_Y, vUvBounds_Y);
+    write_uv_rect(user_data.y, f, vec2(textureSize(sColor1, 0).xy), vUv_UV, vUvBounds_UV);
+#elif defined (WR_FEATURE_YUV_INTERLEAVED)
+    write_uv_rect(user_data.x, f, vec2(textureSize(sColor0, 0).xy), vUv_YUV, vUvBounds_YUV);
+#endif //WR_FEATURE_YUV_*
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#if !defined(WR_FEATURE_YUV_REC601) && !defined(WR_FEATURE_YUV_REC709)
+#define WR_FEATURE_YUV_REC601
+#endif
+
+// The constants added to the Y, U and V components are applied in the fragment shader.
+#if defined(WR_FEATURE_YUV_REC601)
+// From Rec601:
+// [R]   [1.1643835616438356,  0.0,                 1.5960267857142858   ]   [Y -  16]
+// [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708   ] x [U - 128]
+// [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [V - 128]
+//
+// For the range [0,1] instead of [0,255].
+//
+// The matrix is stored in column-major.
+const mat3 YuvColorMatrix = mat3(
+    1.16438,  1.16438, 1.16438,
+    0.0,     -0.39176, 2.01723,
+    1.59603, -0.81297, 0.0
+);
+#elif defined(WR_FEATURE_YUV_REC709)
+// From Rec709:
+// [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]   [Y -  16]
+// [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444 ] x [U - 128]
+// [B]   [1.1643835616438356,  2.1124017857142854,     0.0               ]   [V - 128]
+//
+// For the range [0,1] instead of [0,255]:
+//
+// The matrix is stored in column-major.
+const mat3 YuvColorMatrix = mat3(
+    1.16438,  1.16438,  1.16438,
+    0.0    , -0.21325,  2.11240,
+    1.79274, -0.53291,  0.0
+);
+#endif
+
+vec4 brush_fs() {
+    vec3 yuv_value;
+
+#if defined (WR_FEATURE_YUV_PLANAR)
+    // The yuv_planar format should have this third texture coordinate.
+    vec2 uv_y = clamp(vUv_Y.xy, vUvBounds_Y.xy, vUvBounds_Y.zw);
+    vec2 uv_u = clamp(vUv_U.xy, vUvBounds_U.xy, vUvBounds_U.zw);
+    vec2 uv_v = clamp(vUv_V.xy, vUvBounds_V.xy, vUvBounds_V.zw);
+    yuv_value.x = TEX_SAMPLE(sColor0, vec3(uv_y, vUv_Y.z)).r;
+    yuv_value.y = TEX_SAMPLE(sColor1, vec3(uv_u, vUv_U.z)).r;
+    yuv_value.z = TEX_SAMPLE(sColor2, vec3(uv_v, vUv_V.z)).r;
+#elif defined (WR_FEATURE_YUV_NV12)
+    vec2 uv_y = clamp(vUv_Y.xy, vUvBounds_Y.xy, vUvBounds_Y.zw);
+    vec2 uv_uv = clamp(vUv_UV.xy, vUvBounds_UV.xy, vUvBounds_UV.zw);
+    yuv_value.x = TEX_SAMPLE(sColor0, vec3(uv_y, vUv_Y.z)).r;
+    yuv_value.yz = TEX_SAMPLE(sColor1, vec3(uv_uv, vUv_UV.z)).rg;
+#elif defined (WR_FEATURE_YUV_INTERLEAVED)
+    // "The Y, Cb and Cr color channels within the 422 data are mapped into
+    // the existing green, blue and red color channels."
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+    vec2 uv_y = clamp(vUv_YUV.xy, vUvBounds_YUV.xy, vUvBounds_YUV.zw);
+    yuv_value = TEX_SAMPLE(sColor0, vec3(uv_y, vUv_YUV.z)).gbr;
+#else
+    yuv_value = vec3(0.0);
+#endif
+
+    // See the YuvColorMatrix definition for an explanation of where the constants come from.
+    vec3 rgb = YuvColorMatrix * (yuv_value - vec3(0.06275, 0.50196, 0.50196));
+    vec4 color = vec4(rgb, 1.0);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    color *= init_transform_fs(vLocalPos);
+#endif
+
+    return color;
+}
+#endif
--- a/gfx/webrender/res/ellipse.glsl
+++ b/gfx/webrender/res/ellipse.glsl
@@ -1,81 +1,39 @@
 /* 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/. */
 
 #ifdef WR_FRAGMENT_SHADER
 
+// One iteration of Newton's method on the 2D equation of an ellipse:
 //
-// Signed distance to an ellipse.
-// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
-// Note that this fails for exact circles.
+//     E(x, y) = x^2/a^2 + y^2/b^2 - 1
+//
+// The Jacobian of this equation is:
+//
+//     J(E(x, y)) = [ 2*x/a^2 2*y/b^2 ]
+//
+// We approximate the distance with:
+//
+//     E(x, y) / ||J(E(x, y))||
 //
-float sdEllipse( vec2 p, in vec2 ab ) {
-    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
-    float l = ab.y*ab.y - ab.x*ab.x;
-
-    float m = ab.x*p.x/l;
-    float n = ab.y*p.y/l;
-    float m2 = m*m;
-    float n2 = n*n;
-
-    float c = (m2 + n2 - 1.0)/3.0;
-    float c3 = c*c*c;
-
-    float q = c3 + m2*n2*2.0;
-    float d = c3 + m2*n2;
-    float g = m + m*n2;
-
-    float co;
-
-    if( d<0.0 )
-    {
-        float p = acos(q/c3)/3.0;
-        float s = cos(p);
-        float t = sin(p)*sqrt(3.0);
-        float rx = sqrt( -c*(s + t + 2.0) + m2 );
-        float ry = sqrt( -c*(s - t + 2.0) + m2 );
-        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
+// See G. Taubin, "Distance Approximations for Rasterizing Implicit
+// Curves", section 3.
+float distance_to_ellipse(vec2 p, vec2 radii, float aa_range) {
+    float dist;
+    if (any(lessThanEqual(radii, vec2(0.0)))) {
+        dist = length(p);
+    } else {
+        vec2 invRadiiSq = 1.0 / (radii * radii);
+        float g = dot(p * p * invRadiiSq, vec2(1.0)) - 1.0;
+        vec2 dG = 2.0 * p * invRadiiSq;
+        dist = g * inversesqrt(dot(dG, dG));
     }
-    else
-    {
-        float h = 2.0*m*n*sqrt( d );
-        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
-        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
-        float rx = -s - u - c*4.0 + 2.0*m2;
-        float ry = (s - u)*sqrt(3.0);
-        float rm = sqrt( rx*rx + ry*ry );
-        float p = ry/sqrt(rm-rx);
-        co = (p + 2.0*g/rm - m)/2.0;
-    }
-
-    float si = sqrt( 1.0 - co*co );
-
-    vec2 r = vec2( ab.x*co, ab.y*si );
-
-    return length(r - p ) * sign(p.y-r.y);
-}
-
-float distance_to_ellipse(vec2 p, vec2 radii, float aa_range) {
-    // sdEllipse fails on exact circles, so handle equal
-    // radii here. The branch coherency should make this
-    // a performance win for the circle case too.
-    float len = length(p);
-    if (radii.x == radii.y) {
-        return len - radii.x;
-    } else {
-        if (len < min(radii.x, radii.y) - aa_range) {
-          return -aa_range;
-        } else if (len > max(radii.x, radii.y) + aa_range) {
-          return aa_range;
-        }
-
-        return sdEllipse(p, radii);
-    }
+    return clamp(dist, -aa_range, aa_range);
 }
 
 float clip_against_ellipse_if_needed(
     vec2 pos,
     float current_distance,
     vec4 ellipse_center_radius,
     vec2 sign_modifier,
     float aa_range
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -310,37 +310,16 @@ struct Gradient {
     vec4 extend_mode;
 };
 
 Gradient fetch_gradient(int address) {
     vec4 data[3] = fetch_from_resource_cache_3(address);
     return Gradient(data[0], data[1], data[2]);
 }
 
-struct GradientStop {
-    vec4 color;
-    vec4 offset;
-};
-
-GradientStop fetch_gradient_stop(int address) {
-    vec4 data[2] = fetch_from_resource_cache_2(address);
-    return GradientStop(data[0], data[1]);
-}
-
-struct RadialGradient {
-    vec4 start_end_center;
-    vec4 start_end_radius_ratio_xy_extend_mode;
-    vec4 tile_size_repeat;
-};
-
-RadialGradient fetch_radial_gradient(int address) {
-    vec4 data[3] = fetch_from_resource_cache_3(address);
-    return RadialGradient(data[0], data[1], data[2]);
-}
-
 struct Glyph {
     vec2 offset;
 };
 
 Glyph fetch_glyph(int specific_prim_address,
                   int glyph_index,
                   int subpx_dir) {
     // Two glyphs are packed in each texel in the GPU cache.
@@ -619,17 +598,18 @@ vec2 intersect_lines(vec2 p0, vec2 p1, v
 }
 
 VertexInfo write_transform_vertex(RectWithSize local_segment_rect,
                                   RectWithSize local_prim_rect,
                                   RectWithSize local_clip_rect,
                                   vec4 clip_edge_mask,
                                   float z,
                                   ClipScrollNode scroll_node,
-                                  PictureTask task) {
+                                  PictureTask task,
+                                  bool do_perspective_interpolation) {
     // Calculate a clip rect from local_rect + local clip
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_clip_rect);
     RectWithEndpoint segment_rect = to_rect_with_endpoint(local_segment_rect);
     segment_rect.p0 = clamp(segment_rect.p0, clip_rect.p0, clip_rect.p1);
     segment_rect.p1 = clamp(segment_rect.p1, clip_rect.p0, clip_rect.p1);
 
     // Calculate a clip rect from local_rect + local clip
     RectWithEndpoint prim_rect = to_rect_with_endpoint(local_prim_rect);
@@ -656,23 +636,26 @@ VertexInfo write_transform_vertex(RectWi
 
     // Transform the current vertex to the world cpace.
     vec4 world_pos = scroll_node.transform * vec4(local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
     vec2 task_offset = task.common_data.task_rect.p0 - task.content_origin;
 
+    // Force w = 1, if we don't want perspective interpolation (for
+    // example, drawing a screen-space quad on an element with a
+    // perspective transform).
+    world_pos.w = mix(1.0, world_pos.w, do_perspective_interpolation);
+
     // We want the world space coords to be perspective divided by W.
     // We also want that to apply to any interpolators. However, we
     // want a constant Z across the primitive, since we're using it
     // for draw ordering - so scale by the W coord to ensure this.
-    vec4 final_pos = vec4((device_pos + task_offset) * world_pos.w,
-                          z * world_pos.w,
-                          world_pos.w);
+    vec4 final_pos = vec4(device_pos + task_offset, z, 1.0) * world_pos.w;
     gl_Position = uTransform * final_pos;
 
     vLocalBounds = mix(
         vec4(prim_rect.p0, prim_rect.p1),
         vec4(segment_rect.p0, segment_rect.p1),
         clip_edge_mask
     );
 
@@ -689,17 +672,18 @@ VertexInfo write_transform_vertex(RectWi
 VertexInfo write_transform_vertex_primitive(Primitive prim) {
     return write_transform_vertex(
         prim.local_rect,
         prim.local_rect,
         prim.local_clip_rect,
         vec4(1.0),
         prim.z,
         prim.scroll_node,
-        prim.task
+        prim.task,
+        true
     );
 }
 
 struct GlyphResource {
     vec4 uv_rect;
     float layer;
     vec2 offset;
     float scale;
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -315,17 +315,18 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(segment_rect,
                                            prim.local_rect,
                                            prim.local_clip_rect,
                                            edge_mask,
                                            prim.z,
                                            prim.scroll_node,
-                                           prim.task);
+                                           prim.task,
+                                           true);
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.scroll_node,
                                  prim.task,
                                  prim.local_rect);
 #endif
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -228,17 +228,18 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(segment_rect,
                                            prim.local_rect,
                                            prim.local_clip_rect,
                                            edge_mask,
                                            prim.z,
                                            prim.scroll_node,
-                                           prim.task);
+                                           prim.task,
+                                           true);
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.scroll_node,
                                  prim.task,
                                  prim.local_rect);
 #endif
--- a/gfx/webrender/res/ps_gradient.glsl
+++ b/gfx/webrender/res/ps_gradient.glsl
@@ -4,16 +4,26 @@
 
 #include shared,prim_shared
 
 varying vec4 vColor;
 
 varying vec2 vLocalPos;
 
 #ifdef WR_VERTEX_SHADER
+struct GradientStop {
+    vec4 color;
+    vec4 offset;
+};
+
+GradientStop fetch_gradient_stop(int address) {
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+    return GradientStop(data[0], data[1]);
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Gradient gradient = fetch_gradient(prim.specific_prim_address);
 
     vec4 abs_start_end_point = gradient.start_end_point + prim.local_rect.p0.xyxy;
 
     int stop_address = prim.specific_prim_address +
                        VECS_PER_GRADIENT +
@@ -68,17 +78,18 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(segment_rect,
                                            prim.local_rect,
                                            prim.local_clip_rect,
                                            vec4(1.0),
                                            prim.z,
                                            prim.scroll_node,
-                                           prim.task);
+                                           prim.task,
+                                           true);
     vLocalPos = vi.local_pos;
     vec2 f = (vi.local_pos.xy - prim.local_rect.p0) / prim.local_rect.size;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.scroll_node,
                                  prim.task,
deleted file mode 100644
--- a/gfx/webrender/res/ps_radial_gradient.glsl
+++ /dev/null
@@ -1,115 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared
-
-flat varying int vGradientAddress;
-flat varying float vGradientRepeat;
-
-flat varying vec2 vStartCenter;
-flat varying vec2 vEndCenter;
-flat varying float vStartRadius;
-flat varying float vEndRadius;
-
-flat varying vec2 vTileSize;
-flat varying vec2 vTileRepeat;
-
-varying vec2 vPos;
-
-#ifdef WR_VERTEX_SHADER
-void main(void) {
-    Primitive prim = load_primitive();
-    RadialGradient gradient = fetch_radial_gradient(prim.specific_prim_address);
-
-    VertexInfo vi = write_vertex(prim.local_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-
-    vPos = vi.local_pos - prim.local_rect.p0;
-
-    vStartCenter = gradient.start_end_center.xy;
-    vEndCenter = gradient.start_end_center.zw;
-
-    vStartRadius = gradient.start_end_radius_ratio_xy_extend_mode.x;
-    vEndRadius = gradient.start_end_radius_ratio_xy_extend_mode.y;
-
-    vTileSize = gradient.tile_size_repeat.xy;
-    vTileRepeat = gradient.tile_size_repeat.zw;
-
-    // Transform all coordinates by the y scale so the
-    // fragment shader can work with circles
-    float ratio_xy = gradient.start_end_radius_ratio_xy_extend_mode.z;
-    vPos.y *= ratio_xy;
-    vStartCenter.y *= ratio_xy;
-    vEndCenter.y *= ratio_xy;
-    vTileSize.y *= ratio_xy;
-    vTileRepeat.y *= ratio_xy;
-
-    vGradientAddress = prim.specific_prim_address + VECS_PER_GRADIENT;
-
-    // Whether to repeat the gradient instead of clamping.
-    vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) != EXTEND_MODE_CLAMP);
-
-    write_clip(vi.screen_pos, prim.clip_area);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    vec2 pos = mod(vPos, vTileRepeat);
-
-    if (pos.x >= vTileSize.x ||
-        pos.y >= vTileSize.y) {
-        discard;
-    }
-
-    vec2 cd = vEndCenter - vStartCenter;
-    vec2 pd = pos - vStartCenter;
-    float rd = vEndRadius - vStartRadius;
-
-    // Solve for t in length(t * cd - pd) = vStartRadius + t * rd
-    // using a quadratic equation in form of At^2 - 2Bt + C = 0
-    float A = dot(cd, cd) - rd * rd;
-    float B = dot(pd, cd) + vStartRadius * rd;
-    float C = dot(pd, pd) - vStartRadius * vStartRadius;
-
-    float offset;
-    if (A == 0.0) {
-        // Since A is 0, just solve for -2Bt + C = 0
-        if (B == 0.0) {
-            discard;
-        }
-        float t = 0.5 * C / B;
-        if (vStartRadius + rd * t >= 0.0) {
-            offset = t;
-        } else {
-            discard;
-        }
-    } else {
-        float discr = B * B - A * C;
-        if (discr < 0.0) {
-            discard;
-        }
-        discr = sqrt(discr);
-        float t0 = (B + discr) / A;
-        float t1 = (B - discr) / A;
-        if (vStartRadius + rd * t0 >= 0.0) {
-            offset = t0;
-        } else if (vStartRadius + rd * t1 >= 0.0) {
-            offset = t1;
-        } else {
-            discard;
-        }
-    }
-
-    vec4 color = sample_gradient(vGradientAddress,
-                                 offset,
-                                 vGradientRepeat);
-
-    oFragColor = color * do_clip();
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ /dev/null
@@ -1,197 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared
-
-// If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized
-// texture coordinates. Otherwise, it uses normalized texture coordinates. Please
-// check GL_TEXTURE_RECTANGLE.
-flat varying vec2 vTextureOffsetY; // Offset of the y plane into the texture atlas.
-flat varying vec2 vTextureOffsetU; // Offset of the u plane into the texture atlas.
-flat varying vec2 vTextureOffsetV; // Offset of the v plane into the texture atlas.
-flat varying vec2 vTextureSizeY;   // Size of the y plane in the texture atlas.
-flat varying vec2 vTextureSizeUv;  // Size of the u and v planes in the texture atlas.
-flat varying vec2 vStretchSize;
-flat varying vec2 vHalfTexelY;     // Normalized length of the half of a Y texel.
-flat varying vec2 vHalfTexelUv;    // Normalized length of the half of u and v texels.
-flat varying vec3 vLayers;
-
-#ifdef WR_FEATURE_TRANSFORM
-flat varying vec4 vLocalRect;
-#endif
-
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-struct YuvImage {
-    vec2 size;
-};
-
-YuvImage fetch_yuv_image(int address) {
-    vec4 data = fetch_from_resource_cache_1(address);
-    return YuvImage(data.xy);
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex_primitive(prim);
-    vLocalPos = vi.local_pos;
-    vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.p0 + prim.local_rect.size);
-#else
-    VertexInfo vi = write_vertex(prim.local_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-    vLocalPos = vi.local_pos - prim.local_rect.p0;
-#endif
-
-    write_clip(vi.screen_pos, prim.clip_area);
-
-    ImageResource y_rect = fetch_image_resource(prim.user_data0);
-    vLayers = vec3(y_rect.layer, 0.0, 0.0);
-
-#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR  // only 1 channel
-    ImageResource u_rect = fetch_image_resource(prim.user_data1);
-    vLayers.y = u_rect.layer;
-#ifndef WR_FEATURE_NV12 // 2 channel
-    ImageResource v_rect = fetch_image_resource(prim.user_data2);
-    vLayers.z = v_rect.layer;
-#endif
-#endif
-
-    // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
-    // non-normalized texture coordinates.
-#ifdef WR_FEATURE_TEXTURE_RECT
-    vec2 y_texture_size_normalization_factor = vec2(1, 1);
-#else
-    vec2 y_texture_size_normalization_factor = vec2(textureSize(sColor0, 0));
-#endif
-    vec2 y_st0 = y_rect.uv_rect.p0 / y_texture_size_normalization_factor;
-    vec2 y_st1 = y_rect.uv_rect.p1 / y_texture_size_normalization_factor;
-
-    vTextureSizeY = y_st1 - y_st0;
-    vTextureOffsetY = y_st0;
-
-#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
-    // This assumes the U and V surfaces have the same size.
-#ifdef WR_FEATURE_TEXTURE_RECT
-    vec2 uv_texture_size_normalization_factor = vec2(1, 1);
-#else
-    vec2 uv_texture_size_normalization_factor = vec2(textureSize(sColor1, 0));
-#endif
-    vec2 u_st0 = u_rect.uv_rect.p0 / uv_texture_size_normalization_factor;
-    vec2 u_st1 = u_rect.uv_rect.p1 / uv_texture_size_normalization_factor;
-
-#ifndef WR_FEATURE_NV12
-    vec2 v_st0 = v_rect.uv_rect.p0 / uv_texture_size_normalization_factor;
-#endif
-
-    vTextureSizeUv = u_st1 - u_st0;
-    vTextureOffsetU = u_st0;
-#ifndef WR_FEATURE_NV12
-    vTextureOffsetV = v_st0;
-#endif
-#endif
-
-    YuvImage image = fetch_yuv_image(prim.specific_prim_address);
-    vStretchSize = image.size;
-
-    vHalfTexelY = vec2(0.5) / y_texture_size_normalization_factor;
-#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
-    vHalfTexelUv = vec2(0.5) / uv_texture_size_normalization_factor;
-#endif
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-#if !defined(WR_FEATURE_YUV_REC601) && !defined(WR_FEATURE_YUV_REC709)
-#define WR_FEATURE_YUV_REC601
-#endif
-
-// The constants added to the Y, U and V components are applied in the fragment shader.
-#if defined(WR_FEATURE_YUV_REC601)
-// From Rec601:
-// [R]   [1.1643835616438356,  0.0,                 1.5960267857142858   ]   [Y -  16]
-// [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708   ] x [U - 128]
-// [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [V - 128]
-//
-// For the range [0,1] instead of [0,255].
-//
-// The matrix is stored in column-major.
-const mat3 YuvColorMatrix = mat3(
-    1.16438,  1.16438, 1.16438,
-    0.0,     -0.39176, 2.01723,
-    1.59603, -0.81297, 0.0
-);
-#elif defined(WR_FEATURE_YUV_REC709)
-// From Rec709:
-// [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]   [Y -  16]
-// [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444 ] x [U - 128]
-// [B]   [1.1643835616438356,  2.1124017857142854,     0.0               ]   [V - 128]
-//
-// For the range [0,1] instead of [0,255]:
-//
-// The matrix is stored in column-major.
-const mat3 YuvColorMatrix = mat3(
-    1.16438,  1.16438,  1.16438,
-    0.0    , -0.21325,  2.11240,
-    1.79274, -0.53291,  0.0
-);
-#endif
-
-void main(void) {
-#ifdef WR_FEATURE_TRANSFORM
-    float alpha = init_transform_fs(vLocalPos);
-
-    // We clamp the texture coordinate calculation here to the local rectangle boundaries,
-    // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp(vLocalPos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy;
-#else
-    float alpha = 1.0;;
-    vec2 relative_pos_in_rect = vLocalPos;
-#endif
-
-    alpha *= do_clip();
-
-    // We clamp the texture coordinates to the half-pixel offset from the borders
-    // in order to avoid sampling outside of the texture area.
-    vec2 st_y = vTextureOffsetY + clamp(
-        relative_pos_in_rect / vStretchSize * vTextureSizeY,
-        vHalfTexelY, vTextureSizeY - vHalfTexelY);
-#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
-    vec2 uv_offset = clamp(
-        relative_pos_in_rect / vStretchSize * vTextureSizeUv,
-        vHalfTexelUv, vTextureSizeUv - vHalfTexelUv);
-    // NV12 only uses 2 textures. The sColor0 is for y and sColor1 is for uv.
-    // The texture coordinates of u and v are the same. So, we could skip the
-    // st_v if the format is NV12.
-    vec2 st_u = vTextureOffsetU + uv_offset;
-#endif
-
-    vec3 yuv_value;
-#ifdef WR_FEATURE_INTERLEAVED_Y_CB_CR
-    // "The Y, Cb and Cr color channels within the 422 data are mapped into
-    // the existing green, blue and red color channels."
-    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
-    yuv_value = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).gbr;
-#elif defined(WR_FEATURE_NV12)
-    yuv_value.x = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).r;
-    yuv_value.yz = TEX_SAMPLE(sColor1, vec3(st_u, vLayers.y)).rg;
-#else
-    // The yuv_planar format should have this third texture coordinate.
-    vec2 st_v = vTextureOffsetV + uv_offset;
-
-    yuv_value.x = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).r;
-    yuv_value.y = TEX_SAMPLE(sColor1, vec3(st_u, vLayers.y)).r;
-    yuv_value.z = TEX_SAMPLE(sColor2, vec3(st_v, vLayers.z)).r;
-#endif
-
-    // See the YuvColorMatrix definition for an explanation of where the constants come from.
-    vec3 rgb = YuvColorMatrix * (yuv_value - vec3(0.06275, 0.50196, 0.50196));
-    oFragColor = vec4(rgb * alpha, alpha);
-}
-#endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -2,32 +2,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, DeviceIntRect, DeviceIntSize, LayerToWorldScale};
 use api::{DeviceUintRect, DeviceUintPoint, DeviceUintSize, ExternalImageType, FilterOp, ImageRendering, LayerRect};
 use api::{DeviceIntPoint, LayerPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
-use clip::{ClipSource, ClipStore};
+use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
-use gpu_types::{BrushImageKind, BrushInstance, ClipChainRectIndex};
+use gpu_types::{BrushFlags, BrushImageKind, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
-use internal_types::{FastHashMap, SourceTexture};
+use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PrimitiveRun};
-use render_task::{ClipWorkItem};
-use render_task::{RenderTaskAddress, RenderTaskId};
-use render_task::{RenderTaskKind, RenderTaskTree};
+use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext, RenderTargetKind};
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
@@ -35,20 +33,18 @@ use util::{MatrixHelpers, TransformedRec
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformBatchKind {
     TextRun(GlyphFormat),
     Image(ImageBufferKind),
-    YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
     AlignedGradient,
     AngleGradient,
-    RadialGradient,
     BorderCorner,
     BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushImageSourceKind {
@@ -75,16 +71,18 @@ pub enum BrushBatchKind {
     Line,
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
+    YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
+    RadialGradient,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BatchKind {
     HardwareComposite,
     SplitComposite,
@@ -948,16 +946,17 @@ impl AlphaBatchBuilder {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
                                     clip_chain_rect_index,
                                     scroll_id,
                                     clip_task_address,
                                     z,
                                     segment_index: 0,
                                     edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                                     user_data: [
                                         cache_task_address.0 as i32,
                                         BrushImageKind::Simple as i32,
                                         0,
                                     ],
                                 };
                                 batch.push(PrimitiveInstance::from(instance));
                             }
@@ -1034,35 +1033,36 @@ impl AlphaBatchBuilder {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
+                                                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                                                     user_data: [
                                                         cache_task_address.0 as i32,
                                                         BrushImageKind::Simple as i32,
                                                         0,
                                                     ],
                                                 };
 
                                                 {
                                                     let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                                     batch.push(PrimitiveInstance::from(instance));
                                                 }
 
                                                 let secondary_id = secondary_render_task_id.expect("no secondary!?");
-                                                let render_task = &render_tasks[secondary_id];
+                                                let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
+                                                debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
                                                 let secondary_task_address = render_tasks.get_task_address(secondary_id);
-                                                let render_pass_index = render_task.pass_index.expect("no render_pass_index!?");
                                                 let secondary_textures = BatchTextures {
                                                     colors: [
-                                                        SourceTexture::RenderTaskCacheRGBA8(render_pass_index),
+                                                        SourceTexture::RenderTaskCache(saved_index),
                                                         SourceTexture::Invalid,
                                                         SourceTexture::Invalid,
                                                     ],
                                                 };
                                                 let key = BatchKey::new(
                                                     BatchKind::HardwareComposite,
                                                     BlendMode::PremultipliedAlpha,
                                                     secondary_textures,
@@ -1111,16 +1111,17 @@ impl AlphaBatchBuilder {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
+                                                    brush_flags: BrushFlags::empty(),
                                                     user_data: [
                                                         cache_task_address.0 as i32,
                                                         filter_mode,
                                                         0,
                                                     ],
                                                 };
 
                                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
@@ -1150,16 +1151,17 @@ impl AlphaBatchBuilder {
                                             picture_address: task_address,
                                             prim_address: prim_cache_address,
                                             clip_chain_rect_index,
                                             scroll_id,
                                             clip_task_address,
                                             z,
                                             segment_index: 0,
                                             edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags: BrushFlags::empty(),
                                             user_data: [
                                                 mode as u32 as i32,
                                                 backdrop_task_address.0 as i32,
                                                 source_task_address.0 as i32,
                                             ],
                                         };
 
                                         batch.push(PrimitiveInstance::from(instance));
@@ -1221,83 +1223,16 @@ impl AlphaBatchBuilder {
                 let kind = BatchKind::Transformable(
                     transform_kind,
                     TransformBatchKind::AngleGradient,
                 );
                 let key = BatchKey::new(kind, non_segmented_blend_mode, no_textures);
                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(0, 0, 0));
             }
-            PrimitiveKind::RadialGradient => {
-                let kind = BatchKind::Transformable(
-                    transform_kind,
-                    TransformBatchKind::RadialGradient,
-                );
-                let key = BatchKey::new(kind, non_segmented_blend_mode, no_textures);
-                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                batch.push(base_instance.build(0, 0, 0));
-            }
-            PrimitiveKind::YuvImage => {
-                let mut textures = BatchTextures::no_texture();
-                let mut uv_rect_addresses = [0; 3];
-                let image_yuv_cpu =
-                    &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
-
-                //yuv channel
-                let channel_count = image_yuv_cpu.format.get_plane_num();
-                debug_assert!(channel_count <= 3);
-                for channel in 0 .. channel_count {
-                    let image_key = image_yuv_cpu.yuv_key[channel];
-
-                    let cache_item = resolve_image(
-                        ImageRequest {
-                            key: image_key,
-                            rendering: image_yuv_cpu.image_rendering,
-                            tile: None,
-                        },
-                        ctx.resource_cache,
-                        gpu_cache,
-                        deferred_resolves,
-                    );
-
-                    if cache_item.texture_id == SourceTexture::Invalid {
-                        warn!("Warnings: skip a PrimitiveKind::YuvImage");
-                        debug!("at {:?}.", task_relative_bounding_rect);
-                        return;
-                    }
-
-                    textures.colors[channel] = cache_item.texture_id;
-                    uv_rect_addresses[channel] = cache_item.uv_rect_handle.as_int(gpu_cache);
-                }
-
-                // All yuv textures should be the same type.
-                let buffer_kind = get_buffer_kind(textures.colors[0]);
-                assert!(
-                    textures.colors[1 .. image_yuv_cpu.format.get_plane_num()]
-                        .iter()
-                        .all(|&tid| buffer_kind == get_buffer_kind(tid))
-                );
-
-                let kind = BatchKind::Transformable(
-                    transform_kind,
-                    TransformBatchKind::YuvImage(
-                        buffer_kind,
-                        image_yuv_cpu.format,
-                        image_yuv_cpu.color_space,
-                    ),
-                );
-                let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
-                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-
-                batch.push(base_instance.build(
-                    uv_rect_addresses[0],
-                    uv_rect_addresses[1],
-                    uv_rect_addresses[2],
-                ));
-            }
         }
     }
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
         prim_metadata: &PrimitiveMetadata,
         batch_kind: BrushBatchKind,
@@ -1319,16 +1254,17 @@ impl AlphaBatchBuilder {
             picture_address: task_address,
             prim_address: prim_cache_address,
             clip_chain_rect_index,
             scroll_id,
             clip_task_address,
             z,
             segment_index: 0,
             edge_flags: EdgeAaSegmentMask::all(),
+            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
             user_data,
         };
 
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 let alpha_batch_key = BatchKey {
                     blend_mode: alpha_blend_mode,
                     kind: BatchKind::Brush(batch_kind),
@@ -1435,16 +1371,81 @@ impl BrushPrimitive {
             }
             BrushKind::Clear => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
                 ))
             }
+            BrushKind::RadialGradient { ref stops_handle, .. } => {
+                Some((
+                    BrushBatchKind::RadialGradient,
+                    BatchTextures::no_texture(),
+                    [
+                        stops_handle.as_int(gpu_cache),
+                        0,
+                        0,
+                    ],
+                ))
+            }
+            BrushKind::YuvImage { format, yuv_key, image_rendering, color_space } => {
+                let mut textures = BatchTextures::no_texture();
+                let mut uv_rect_addresses = [0; 3];
+
+                //yuv channel
+                let channel_count = format.get_plane_num();
+                debug_assert!(channel_count <= 3);
+                for channel in 0 .. channel_count {
+                    let image_key = yuv_key[channel];
+
+                    let cache_item = resolve_image(
+                        ImageRequest {
+                            key: image_key,
+                            rendering: image_rendering,
+                            tile: None,
+                        },
+                        resource_cache,
+                        gpu_cache,
+                        deferred_resolves,
+                    );
+
+                    if cache_item.texture_id == SourceTexture::Invalid {
+                        warn!("Warnings: skip a PrimitiveKind::YuvImage");
+                        return None;
+                    }
+
+                    textures.colors[channel] = cache_item.texture_id;
+                    uv_rect_addresses[channel] = cache_item.uv_rect_handle.as_int(gpu_cache);
+                }
+
+                // All yuv textures should be the same type.
+                let buffer_kind = get_buffer_kind(textures.colors[0]);
+                assert!(
+                    textures.colors[1 .. format.get_plane_num()]
+                        .iter()
+                        .all(|&tid| buffer_kind == get_buffer_kind(tid))
+                );
+
+                let kind = BrushBatchKind::YuvImage(
+                    buffer_kind,
+                    format,
+                    color_space,
+                );
+
+                Some((
+                    kind,
+                    textures,
+                    [
+                        uv_rect_addresses[0],
+                        uv_rect_addresses[1],
+                        uv_rect_addresses[2],
+                    ],
+                ))
+            }
             BrushKind::Mask { .. } => {
                 unreachable!("bug: mask brushes not expected in normal alpha pass");
             }
         }
     }
 }
 
 trait AlphaBatchHelpers {
@@ -1458,20 +1459,18 @@ impl AlphaBatchHelpers for PrimitiveStor
     fn get_blend_mode(&self, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             // Can only resolve the TextRun's blend mode once glyphs are fetched.
             PrimitiveKind::TextRun => {
                 BlendMode::PremultipliedAlpha
             }
 
             PrimitiveKind::Border |
-            PrimitiveKind::YuvImage |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
-            PrimitiveKind::RadialGradient |
             PrimitiveKind::Picture => {
                 BlendMode::PremultipliedAlpha
             }
 
             PrimitiveKind::Brush => {
                 let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                 match brush.kind {
                     BrushKind::Clear => {
@@ -1481,16 +1480,18 @@ impl AlphaBatchHelpers for PrimitiveStor
                         match alpha_type {
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::Solid { .. } |
                     BrushKind::Mask { .. } |
                     BrushKind::Line { .. } |
+                    BrushKind::YuvImage { .. } |
+                    BrushKind::RadialGradient { .. } |
                     BrushKind::Picture => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
             }
             PrimitiveKind::Image => {
                 let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0];
                 match image_cpu.alpha_type {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,21 +1,20 @@
 /* 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, BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ColorF};
-use api::{LayerPoint, LayerRect, LayerPrimitiveInfo, LayerSize};
-use api::{NormalBorder, RepeatMode, TexelRect};
+use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LayerSize, NormalBorder, RepeatMode, TexelRect};
 use clip::ClipSource;
 use ellipse::Ellipse;
 use frame_builder::FrameBuilder;
 use gpu_cache::GpuDataRequest;
-use prim_store::{BorderPrimitiveCpu, BrushSegment, BrushSegmentDescriptor};
-use prim_store::{BrushClipMaskKind, EdgeAaSegmentMask, PrimitiveContainer};
+use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
+use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, pack_as_float};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
     None,
     Single, // Single instance needed - corner styles are same or similar.
     Double, // Different corner styles. Draw two instances, one per style.
@@ -281,17 +280,17 @@ pub fn ensure_no_corner_overlap(
 }
 
 impl FrameBuilder {
     fn add_normal_border_primitive(
         &mut self,
         info: &LayerPrimitiveInfo,
         border: &NormalBorder,
         widths: &BorderWidths,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         corner_instances: [BorderCornerInstance; 4],
         edges: [BorderEdgeKind; 4],
         clip_sources: Vec<ClipSource>,
     ) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
@@ -349,17 +348,17 @@ impl FrameBuilder {
     // 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,
         info: &LayerPrimitiveInfo,
         border: &NormalBorder,
         widths: &BorderWidths,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
     ) {
         // 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
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -1,22 +1,21 @@
 /* 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::{ColorF, LayerPoint, LayerRect, LayerSize, LayerVector2D};
-use api::{BorderRadius, BoxShadowClipMode, LayoutSize, LayerPrimitiveInfo};
-use api::{ClipMode, ClipAndScrollInfo, ComplexClipRegion, LocalClip};
-use api::{PipelineId};
+use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LocalClip};
+use api::PipelineId;
 use app_units::Au;
 use clip::ClipSource;
 use frame_builder::FrameBuilder;
 use gpu_types::BrushImageKind;
-use prim_store::{PrimitiveContainer};
-use prim_store::{BrushMaskKind, BrushKind, BrushPrimitive};
+use prim_store::{BrushKind, BrushMaskKind, BrushPrimitive, PrimitiveContainer};
+use prim_store::ScrollNodeAndClipChain;
 use picture::PicturePrimitive;
 use util::RectHelpers;
 use render_task::MAX_BLUR_STD_DEVIATION;
 
 // The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
 pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 
 // Maximum blur radius.
@@ -48,17 +47,17 @@ pub struct BoxShadowCacheKey {
     pub br_bottom_right_h: Au,
     pub clip_mode: BoxShadowClipMode,
 }
 
 impl FrameBuilder {
     pub fn add_box_shadow(
         &mut self,
         pipeline_id: PipelineId,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         prim_info: &LayerPrimitiveInfo,
         box_offset: &LayerVector2D,
         color: &ColorF,
         mut blur_radius: f32,
         spread_radius: f32,
         border_radius: BorderRadius,
         clip_mode: BoxShadowClipMode,
     ) {
@@ -83,17 +82,18 @@ impl FrameBuilder {
         let shadow_rect = prim_info.rect
             .translate(box_offset)
             .inflate(spread_amount, spread_amount);
 
         if blur_radius == 0.0 {
             if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
                 return;
             }
-            let mut clips = Vec::new();
+            let mut clips = Vec::with_capacity(2);
+            clips.push(ClipSource::Rectangle(*prim_info.local_clip.clip_rect()));
 
             let fast_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
                     clips.push(ClipSource::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut
@@ -259,18 +259,20 @@ impl FrameBuilder {
                         clip_and_scroll
                     );
 
                     extra_clips.push(ClipSource::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut,
                     ));
-
-                    let pic_info = LayerPrimitiveInfo::new(pic_rect);
+                    let pic_info = LayerPrimitiveInfo::with_clip_rect(
+                        pic_rect,
+                        *prim_info.local_clip.clip_rect()
+                    );
                     self.add_primitive(
                         clip_and_scroll,
                         &pic_info,
                         extra_clips,
                         PrimitiveContainer::Picture(pic_prim),
                     );
                 }
                 BoxShadowClipMode::Inset => {
@@ -327,24 +329,30 @@ impl FrameBuilder {
                         cache_key,
                         pipeline_id,
                     );
                     pic_prim.add_primitive(
                         brush_prim_index,
                         clip_and_scroll
                     );
 
+                    let clip_rect = prim_info.local_clip.clip_rect();
+                    let clip_rect = match prim_info.rect.intersection(clip_rect) {
+                        Some(clip_rect) => clip_rect,
+                        None => return,
+                    };
+
                     // Draw the picture one pixel outside the original
                     // rect to account for the inflate above. This
                     // extra edge will be clipped by the local clip
                     // rect set below.
                     let pic_rect = prim_info.rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
                     let pic_info = LayerPrimitiveInfo::with_clip_rect(
                         pic_rect,
-                        prim_info.rect
+                        clip_rect
                     );
 
                     // Add a normal clip to ensure nothing gets drawn
                     // outside the primitive rect.
                     if !border_radius.is_zero() {
                         extra_clips.push(ClipSource::new_rounded_rect(
                             prim_info.rect,
                             border_radius,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,22 +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::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayerRect, LayerToWorldTransform, LayoutPoint, LayoutVector2D};
 use api::LocalClip;
 use border::{BorderCornerClipSource, ensure_no_corner_overlap};
+use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
+use gpu_types::ClipScrollNodeIndex;
 use prim_store::{ClipData, ImageMaskData};
 use resource_cache::{ImageRequest, ResourceCache};
 use util::{MaxRect, MatrixHelpers, calculate_screen_bounding_rect, extract_inner_rect_safe};
+use std::rc::Rc;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
     pub main: LayerRect,
@@ -344,8 +347,111 @@ pub fn rounded_rectangle_contains_point(
                              LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
     if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
        !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
         return false;
     }
 
     true
 }
+
+pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
+
+#[derive(Debug, Clone)]
+pub struct ClipChainNode {
+    pub work_item: ClipWorkItem,
+    pub local_clip_rect: LayerRect,
+    pub screen_outer_rect: DeviceIntRect,
+    pub screen_inner_rect: DeviceIntRect,
+    pub prev: ClipChainNodeRef,
+}
+
+#[derive(Debug, Clone)]
+pub struct ClipChain {
+    pub parent_index: Option<ClipChainIndex>,
+    pub combined_outer_screen_rect: DeviceIntRect,
+    pub combined_inner_screen_rect: DeviceIntRect,
+    pub nodes: ClipChainNodeRef,
+}
+
+impl ClipChain {
+    pub fn empty(screen_rect: &DeviceIntRect) -> ClipChain {
+        ClipChain {
+            parent_index: None,
+            combined_inner_screen_rect: *screen_rect,
+            combined_outer_screen_rect: *screen_rect,
+            nodes: None,
+        }
+    }
+
+    pub fn new_with_added_node(
+        &self,
+        work_item: ClipWorkItem,
+        local_clip_rect: LayerRect,
+        screen_outer_rect: DeviceIntRect,
+        screen_inner_rect: DeviceIntRect,
+    ) -> ClipChain {
+        // If the new node's inner rectangle completely surrounds our outer rectangle,
+        // we can discard the new node entirely since it isn't going to affect anything.
+        if screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
+            return self.clone();
+        }
+
+        let new_node = ClipChainNode {
+            work_item,
+            local_clip_rect,
+            screen_outer_rect,
+            screen_inner_rect,
+            prev: None,
+        };
+
+        let mut new_chain = self.clone();
+        new_chain.add_node(new_node);
+        new_chain
+    }
+
+    pub fn add_node(&mut self, mut new_node: ClipChainNode) {
+        new_node.prev = self.nodes.clone();
+
+        // If this clip's outer rectangle is completely enclosed by the clip
+        // chain's inner rectangle, then the only clip that matters from this point
+        // on is this clip. We can disconnect this clip from the parent clip chain.
+        if self.combined_inner_screen_rect.contains_rect(&new_node.screen_outer_rect) {
+            new_node.prev = None;
+        }
+
+        self.combined_outer_screen_rect =
+            self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
+            .unwrap_or_else(DeviceIntRect::zero);
+        self.combined_inner_screen_rect =
+            self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
+            .unwrap_or_else(DeviceIntRect::zero);
+
+        self.nodes = Some(Rc::new(new_node));
+    }
+}
+
+pub struct ClipChainNodeIter {
+    pub current: ClipChainNodeRef,
+}
+
+impl Iterator for ClipChainNodeIter {
+    type Item = Rc<ClipChainNode>;
+
+    fn next(&mut self) -> ClipChainNodeRef {
+        let previous = self.current.clone();
+        self.current = match self.current {
+            Some(ref item) => item.prev.clone(),
+            None => return None,
+        };
+        previous
+    }
+}
+
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipWorkItem {
+    pub scroll_node_data_index: ClipScrollNodeIndex,
+    pub clip_sources: ClipSourcesWeakHandle,
+    pub coordinate_system_id: CoordinateSystemId,
+}
+
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,23 +1,22 @@
 /* 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, DevicePixelScale, ExternalScrollId, LayerPixel, LayerPoint, LayerRect};
 use api::{LayerSize, LayerToWorldTransform, LayerTransform, LayerVector2D, LayoutTransform};
 use api::{LayoutVector2D, PipelineId, PropertyBinding, ScrollClamping, ScrollEventPhase};
-use api::{ScrollLocation, ScrollNodeIdType, ScrollSensitivity, StickyOffsetBounds, WorldPoint};
-use clip::{ClipSourcesHandle, ClipStore};
-use clip_scroll_tree::{CoordinateSystemId, TransformUpdateState};
+use api::{ScrollLocation, ScrollSensitivity, StickyOffsetBounds, WorldPoint};
+use clip::{ClipChain, ClipSourcesHandle, ClipStore, ClipWorkItem};
+use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, TransformUpdateState};
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
-use render_task::{ClipChain, ClipWorkItem};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use spring::{DAMPING, STIFFNESS, Spring};
 use util::{MatrixHelpers, TransformOrOffset, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
@@ -51,17 +50,20 @@ impl StickyFrameInfo {
 }
 
 #[derive(Debug)]
 pub enum NodeType {
     /// A reference frame establishes a new coordinate space in the tree.
     ReferenceFrame(ReferenceFrameInfo),
 
     /// Other nodes just do clipping, but no transformation.
-    Clip(ClipSourcesHandle),
+    Clip {
+        handle: ClipSourcesHandle,
+        clip_chain_index: ClipChainIndex
+    },
 
     /// Transforms it's content, but doesn't clip it. Can also be adjusted
     /// by scroll events or setting scroll offsets.
     ScrollFrame(ScrollFrameInfo),
 
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning offset bounds.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
@@ -101,19 +103,16 @@ pub struct ClipScrollNode {
     pub parent: Option<ClipId>,
 
     /// Child layers
     pub children: Vec<ClipId>,
 
     /// The type of this node and any data associated with that node type.
     pub node_type: NodeType,
 
-    /// The ClipChain that will be used if this node is used as the 'clipping node.'
-    pub clip_chain: Option<ClipChain>,
-
     /// True if this node is transformed by an invertible transform.  If not, display items
     /// transformed by this node will not be displayed and display items not transformed by this
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
 
     /// The axis-aligned coordinate system id of this node.
     pub coordinate_system_id: CoordinateSystemId,
 
@@ -123,31 +122,30 @@ pub struct ClipScrollNode {
     pub coordinate_system_relative_transform: TransformOrOffset,
 
     /// A linear ID / index of this clip-scroll node. Used as a reference to
     /// pass to shaders, to allow them to fetch a given clip-scroll node.
     pub node_data_index: ClipScrollNodeIndex,
 }
 
 impl ClipScrollNode {
-    fn new(
+    pub fn new(
         pipeline_id: PipelineId,
         parent_id: Option<ClipId>,
         rect: &LayerRect,
         node_type: NodeType
     ) -> Self {
         ClipScrollNode {
             local_viewport_rect: *rect,
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             parent: parent_id,
             children: Vec::new(),
             pipeline_id,
             node_type: node_type,
-            clip_chain: None,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
             coordinate_system_relative_transform: TransformOrOffset::zero(),
             node_data_index: ClipScrollNodeIndex(0),
         }
     }
 
     pub fn new_scroll_frame(
@@ -165,25 +163,16 @@ impl ClipScrollNode {
                 (content_size.height - frame_rect.size.height).max(0.0)
             ),
             external_id,
         ));
 
         Self::new(pipeline_id, Some(parent_id), frame_rect, node_type)
     }
 
-    pub fn new_clip_node(
-        pipeline_id: PipelineId,
-        parent_id: ClipId,
-        handle: ClipSourcesHandle,
-        clip_rect: LayerRect,
-    ) -> Self {
-        Self::new(pipeline_id, Some(parent_id), &clip_rect, NodeType::Clip(handle))
-    }
-
     pub fn new_reference_frame(
         parent_id: Option<ClipId>,
         frame_rect: &LayerRect,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
     ) -> Self {
@@ -266,17 +255,16 @@ impl ClipScrollNode {
         scrolling.started_bouncing_back = false;
         true
     }
 
     pub fn mark_uninvertible(&mut self) {
         self.invertible = false;
         self.world_content_transform = LayerToWorldTransform::identity();
         self.world_viewport_transform = LayerToWorldTransform::identity();
-        self.clip_chain = None;
     }
 
     pub fn push_gpu_node_data(&mut self, node_data: &mut Vec<ClipScrollNodeData>) {
         if !self.invertible {
             node_data.push(ClipScrollNodeData::invalid());
             return;
         }
 
@@ -299,16 +287,17 @@ impl ClipScrollNode {
         &mut self,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         scene_properties: &SceneProperties,
+        clip_chains: &mut Vec<ClipChain>,
     ) {
         // If any of our parents was not rendered, we are not rendered either and can just
         // quit here.
         if !state.invertible {
             self.mark_uninvertible();
             return;
         }
 
@@ -326,31 +315,32 @@ impl ClipScrollNode {
         }
 
         self.update_clip_work_item(
             state,
             device_pixel_scale,
             clip_store,
             resource_cache,
             gpu_cache,
+            clip_chains,
         );
     }
 
     pub fn update_clip_work_item(
         &mut self,
         state: &mut TransformUpdateState,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
+        clip_chains: &mut Vec<ClipChain>,
     ) {
-        let clip_sources_handle = match self.node_type {
-            NodeType::Clip(ref handle) => handle,
+        let (clip_sources_handle, clip_chain_index) = match self.node_type {
+            NodeType::Clip { ref handle, clip_chain_index } => (handle, clip_chain_index),
             _ => {
-                self.clip_chain = Some(state.parent_clip_chain.clone());
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
         clip_sources.update(gpu_cache, resource_cache);
         let (screen_inner_rect, screen_outer_rect) =
@@ -359,39 +349,32 @@ impl ClipScrollNode {
         // All clipping ClipScrollNodes should have outer rectangles, because they never
         // use the BorderCorner clip type and they always have at last one non-ClipOut
         // Rectangle ClipSource.
         let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
         let local_outer_rect = clip_sources.local_outer_rect.expect(
             "Clipping node didn't have outer rect."
         );
 
-        // If this clip's inner rectangle completely surrounds the existing clip
-        // chain's outer rectangle, we can discard this clip entirely since it isn't
-        // going to affect anything.
-        if screen_inner_rect.contains_rect(&state.parent_clip_chain.combined_outer_screen_rect) {
-            self.clip_chain = Some(state.parent_clip_chain.clone());
-            return;
-        }
-
         let work_item = ClipWorkItem {
             scroll_node_data_index: self.node_data_index,
             clip_sources: clip_sources_handle.weak(),
             coordinate_system_id: state.current_coordinate_system_id,
         };
 
-        let clip_chain = state.parent_clip_chain.new_with_added_node(
+        let mut clip_chain = clip_chains[state.parent_clip_chain_index.0].new_with_added_node(
             work_item,
             self.coordinate_system_relative_transform.apply(&local_outer_rect),
             screen_outer_rect,
             screen_inner_rect,
         );
 
-        self.clip_chain = Some(clip_chain.clone());
-        state.parent_clip_chain = clip_chain;
+        clip_chain.parent_index = Some(state.parent_clip_chain_index);
+        clip_chains[clip_chain_index.0] = clip_chain;
+        state.parent_clip_chain_index = clip_chain_index;
     }
 
     pub fn update_transform(
         &mut self,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         scene_properties: &SceneProperties,
     ) {
@@ -616,17 +599,17 @@ impl ClipScrollNode {
                 state.parent_accumulated_scroll_offset = LayerVector2D::zero();
                 state.coordinate_system_relative_transform =
                     self.coordinate_system_relative_transform.clone();
                 let translation = -info.origin_in_parent_reference_frame;
                 state.nearest_scrolling_ancestor_viewport =
                     state.nearest_scrolling_ancestor_viewport
                        .translate(&translation);
             }
-            NodeType::Clip(..) => { }
+            NodeType::Clip{ .. } => { }
             NodeType::ScrollFrame(ref scrolling) => {
                 state.parent_accumulated_scroll_offset =
                     scrolling.offset + state.parent_accumulated_scroll_offset;
                 state.nearest_scrolling_ancestor_offset = scrolling.offset;
                 state.nearest_scrolling_ancestor_viewport = self.local_viewport_rect;
             }
             NodeType::StickyFrame(ref info) => {
                 // We don't translate the combined rect by the sticky offset, because sticky
@@ -763,22 +746,17 @@ impl ClipScrollNode {
 
     pub fn is_overscrolling(&self) -> bool {
         match self.node_type {
             NodeType::ScrollFrame(ref state) => state.overscroll_amount() != LayerVector2D::zero(),
             _ => false,
         }
     }
 
-    pub fn matches_id(&self, node_id: ClipId, id_to_match: ScrollNodeIdType) -> bool {
-        let external_id = match id_to_match {
-            ScrollNodeIdType::ExternalScrollId(id) => id,
-            ScrollNodeIdType::ClipId(clip_id) => return node_id == clip_id,
-        };
-
+    pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
         match self.node_type {
             NodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
             _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,23 +1,21 @@
 /* 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::{ClipChainId, ClipId, DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint};
-use api::{LayerRect, LayerToWorldTransform, LayerVector2D, PipelineId, ScrollClamping};
-use api::{ScrollEventPhase, ScrollLocation, ScrollNodeIdType};
-use api::{ScrollNodeState, WorldPoint};
-use clip::ClipStore;
+use api::{ClipId, DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint, LayerRect};
+use api::{LayerToWorldTransform, LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase};
+use api::{ScrollLocation, ScrollNodeState, WorldPoint};
+use clip::{ClipChain, ClipSourcesHandle, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollFrameInfo, StickyFrameInfo};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
-use render_task::ClipChain;
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::TransformOrOffset;
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
@@ -39,33 +37,37 @@ impl CoordinateSystemId {
     }
 
     pub fn advance(&mut self) {
         self.0 += 1;
     }
 }
 
 pub struct ClipChainDescriptor {
-    pub id: ClipChainId,
-    pub parent: Option<ClipChainId>,
+    pub index: ClipChainIndex,
+    pub parent: Option<ClipChainIndex>,
     pub clips: Vec<ClipId>,
 }
 
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct ClipChainIndex(pub usize);
+
 pub struct ClipScrollTree {
     pub nodes: FastHashMap<ClipId, ClipScrollNode>,
 
     /// A Vec of all descriptors that describe ClipChains in the order in which they are
     /// encountered during display list flattening. ClipChains are expected to never be
     /// the children of ClipChains later in the list.
     pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
-    /// A HashMap of built ClipChains that are described by `clip_chains_descriptors`.
-    pub clip_chains: FastHashMap<ClipChainId, ClipChain>,
+    /// A vector of all ClipChains in this ClipScrollTree including those from
+    /// ClipChainDescriptors and also those defined by the clipping node hierarchy.
+    pub clip_chains: Vec<ClipChain>,
 
-    pub pending_scroll_offsets: FastHashMap<ScrollNodeIdType, (LayerPoint, ScrollClamping)>,
+    pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current frame id, used for giving a unique id to all new dynamically
     /// added frames and clips. The ClipScrollTree increments this by one every
     /// time a new dynamic frame is created.
@@ -85,17 +87,19 @@ pub struct ClipScrollTree {
 }
 
 #[derive(Clone)]
 pub struct TransformUpdateState {
     pub parent_reference_frame_transform: LayerToWorldTransform,
     pub parent_accumulated_scroll_offset: LayerVector2D,
     pub nearest_scrolling_ancestor_offset: LayerVector2D,
     pub nearest_scrolling_ancestor_viewport: LayerRect,
-    pub parent_clip_chain: ClipChain,
+
+    /// The index of the current parent's clip chain.
+    pub parent_clip_chain_index: ClipChainIndex,
 
     /// An id for keeping track of the axis-aligned space of this node. This is used in
     /// order to to track what kinds of clip optimizations can be done for a particular
     /// display list item, since optimizations can usually only be done among
     /// coordinate systems which are relatively axis aligned.
     pub current_coordinate_system_id: CoordinateSystemId,
 
     /// Transform from the coordinate system that started this compatible coordinate system.
@@ -108,17 +112,17 @@ pub struct TransformUpdateState {
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         let dummy_pipeline = PipelineId::dummy();
         ClipScrollTree {
             nodes: FastHashMap::default(),
             clip_chains_descriptors: Vec::new(),
-            clip_chains: FastHashMap::default(),
+            clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
             pending_scroll_offsets: FastHashMap::default(),
             currently_scrolling_node_id: None,
             root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
             topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
             current_new_node_item: 1,
             pipelines_to_discard: FastHashSet::default(),
         }
     }
@@ -206,29 +210,29 @@ impl ClipScrollTree {
                 NodeType::ScrollFrame(info) if info.external_id.is_some() => {
                     scroll_states.insert(info.external_id.unwrap(), info);
                 }
                 _ => {}
             }
         }
 
         self.pipelines_to_discard.clear();
-        self.clip_chains.clear();
+        self.clip_chains = vec![ClipChain::empty(&DeviceIntRect::zero())];
         self.clip_chains_descriptors.clear();
         scroll_states
     }
 
     pub fn scroll_node(
         &mut self,
         origin: LayerPoint,
-        id: ScrollNodeIdType,
+        id: ExternalScrollId,
         clamp: ScrollClamping
     ) -> bool {
-        for (clip_id, node) in &mut self.nodes {
-            if node.matches_id(*clip_id, id) {
+        for node in &mut self.nodes.values_mut() {
+            if node.matches_external_id(id) {
                 return node.set_scroll_origin(&origin, clamp);
             }
         }
 
         self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
@@ -316,27 +320,29 @@ impl ClipScrollTree {
         pan: WorldPoint,
         node_data: &mut Vec<ClipScrollNodeData>,
         scene_properties: &SceneProperties,
     ) {
         if self.nodes.is_empty() {
             return;
         }
 
+        self.clip_chains[0] = ClipChain::empty(screen_rect);
+
         let root_reference_frame_id = self.root_reference_frame_id();
         let mut state = TransformUpdateState {
             parent_reference_frame_transform: LayerToWorldTransform::create_translation(
                 pan.x,
                 pan.y,
                 0.0,
             ),
             parent_accumulated_scroll_offset: LayerVector2D::zero(),
             nearest_scrolling_ancestor_offset: LayerVector2D::zero(),
             nearest_scrolling_ancestor_viewport: LayerRect::zero(),
-            parent_clip_chain: ClipChain::empty(screen_rect),
+            parent_clip_chain_index: ClipChainIndex(0),
             current_coordinate_system_id: CoordinateSystemId::root(),
             coordinate_system_relative_transform: TransformOrOffset::zero(),
             invertible: true,
         };
         let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
         self.update_node(
             root_reference_frame_id,
             &mut state,
@@ -379,16 +385,17 @@ impl ClipScrollTree {
             node.update(
                 &mut state,
                 next_coordinate_system_id,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 scene_properties,
+                &mut self.clip_chains,
             );
 
             node.push_gpu_node_data(gpu_node_data);
 
             if node.children.is_empty() {
                 return;
             }
 
@@ -412,65 +419,79 @@ impl ClipScrollTree {
     }
 
     pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
         for descriptor in &self.clip_chains_descriptors {
             // A ClipChain is an optional parent (which is another ClipChain) and a list of
             // ClipScrollNode clipping nodes. Here we start the ClipChain with a clone of the
             // parent's node, if necessary.
             let mut chain = match descriptor.parent {
-                Some(id) => self.clip_chains[&id].clone(),
+                Some(index) => self.clip_chains[index.0].clone(),
                 None => ClipChain::empty(screen_rect),
             };
 
             // Now we walk through each ClipScrollNode in the vector of clip nodes and
             // extract their ClipChain nodes to construct the final list.
             for clip_id in &descriptor.clips {
-                if let Some(ref node_chain) = self.nodes[&clip_id].clip_chain {
-                    if let Some(ref nodes) = node_chain.nodes {
-                        chain.add_node((**nodes).clone());
+                let node_clip_chain_index = match self.nodes[&clip_id].node_type {
+                    NodeType::Clip { clip_chain_index, .. } => clip_chain_index,
+                    _ => {
+                        warn!("Tried to create a clip chain with non-clipping node.");
+                        continue;
                     }
+                };
+
+                if let Some(ref nodes) = self.clip_chains[node_clip_chain_index.0].nodes {
+                    chain.add_node((**nodes).clone());
                 }
             }
 
-            self.clip_chains.insert(descriptor.id, chain);
+            chain.parent_index = descriptor.parent;
+            self.clip_chains[descriptor.index.0] = chain;
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
-        for (clip_id, node) in &mut self.nodes {
+        for node in self.nodes.values_mut() {
             let external_id = match node.node_type {
-                NodeType::ScrollFrame(info) => info.external_id,
-                _ => None,
+                NodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
+                _ => continue,
             };
 
-            if let Some(external_id) = external_id {
-                if let Some(scrolling_state) = old_states.get(&external_id) {
-                    node.apply_old_scrolling_state(scrolling_state);
-                }
+            if let Some(scrolling_state) = old_states.get(&external_id) {
+                node.apply_old_scrolling_state(scrolling_state);
+            }
 
 
-                let id = external_id.into();
-                if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&id) {
-                    node.set_scroll_origin(&offset, clamping);
-                }
-            }
-
-            if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&clip_id.into()) {
+            if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
                 node.set_scroll_origin(&offset, clamping);
             }
         }
     }
 
+    pub fn add_clip_node(
+        &mut self,
+        id: ClipId,
+        parent_id: ClipId,
+        handle: ClipSourcesHandle,
+        clip_rect: LayerRect,
+    )  -> ClipChainIndex {
+        let clip_chain_index = self.allocate_clip_chain();
+        let node_type = NodeType::Clip { handle, clip_chain_index };
+        let node = ClipScrollNode::new(id.pipeline_id(), Some(parent_id), &clip_rect, node_type);
+        self.add_node(node, id);
+        clip_chain_index
+    }
+
     pub fn add_sticky_frame(
         &mut self,
         id: ClipId,
         parent_id: ClipId,
         frame_rect: LayerRect,
         sticky_frame_info: StickyFrameInfo,
     ) {
         let node = ClipScrollNode::new_sticky_frame(
@@ -479,21 +500,22 @@ impl ClipScrollTree {
             sticky_frame_info,
             id.pipeline_id(),
         );
         self.add_node(node, id);
     }
 
     pub fn add_clip_chain_descriptor(
         &mut self,
-        id: ClipChainId,
-        parent: Option<ClipChainId>,
+        parent: Option<ClipChainIndex>,
         clips: Vec<ClipId>
-    ) {
-        self.clip_chains_descriptors.push(ClipChainDescriptor { id, parent, clips });
+    ) -> ClipChainIndex {
+        let index = self.allocate_clip_chain();
+        self.clip_chains_descriptors.push(ClipChainDescriptor { index, parent, clips });
+        index
     }
 
     pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
         // When the parent node is None this means we are adding the root.
         match node.parent {
             Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),
             None => self.root_reference_frame_id = id,
         }
@@ -510,21 +532,21 @@ impl ClipScrollTree {
             _ => {}
         }
     }
 
     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 clip_sources_handle) => {
+            NodeType::Clip { ref handle, .. } => {
                 pt.new_level("Clip".to_owned());
 
                 pt.add_item(format!("id: {:?}", id));
-                let clips = clip_store.get(&clip_sources_handle).clips();
+                let clips = clip_store.get(&handle).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.resolved_transform));
@@ -576,16 +598,20 @@ impl ClipScrollTree {
     }
 
     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, clip_store);
         }
     }
 
-    pub fn get_clip_chain(&self, id: &ClipId) -> Option<&ClipChain> {
-        match id {
-            &ClipId::ClipChain(clip_chain_id) => Some(&self.clip_chains[&clip_chain_id]),
-            _ => self.nodes[id].clip_chain.as_ref(),
-        }
+    pub fn allocate_clip_chain(&mut self) -> ClipChainIndex {
+        debug_assert!(!self.clip_chains.is_empty());
+        let new_clip_chain =self.clip_chains[0].clone();
+        self.clip_chains.push(new_clip_chain);
+        ClipChainIndex(self.clip_chains.len() - 1)
+    }
+
+    pub fn get_clip_chain(&self, index: ClipChainIndex) -> &ClipChain {
+        &self.clip_chains[index.0]
     }
 
 }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -433,16 +433,17 @@ pub struct Texture {
     layer_count: i32,
     format: ImageFormat,
     width: u32,
     height: u32,
     filter: TextureFilter,
     render_target: Option<RenderTargetInfo>,
     fbo_ids: Vec<FBOId>,
     depth_rb: Option<RBOId>,
+    last_frame_used: FrameId,
 }
 
 impl Texture {
     pub fn get_dimensions(&self) -> DeviceUintSize {
         DeviceUintSize::new(self.width, self.height)
     }
 
     pub fn get_render_target_layer_count(&self) -> usize {
@@ -468,16 +469,20 @@ impl Texture {
     pub fn has_depth(&self) -> bool {
         self.depth_rb.is_some()
     }
 
     pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
         self.render_target.as_ref()
     }
 
+    pub fn used_in_frame(&self, frame_id: FrameId) -> bool {
+        self.last_frame_used == frame_id
+    }
+
     #[cfg(feature = "replay")]
     pub fn into_external(mut self) -> ExternalTexture {
         let ext = ExternalTexture {
             id: self.id,
             target: self.target,
         };
         self.id = 0; // don't complain, moved out
         ext
@@ -935,16 +940,17 @@ impl Device {
             width: 0,
             height: 0,
             layer_count: 0,
             format,
             filter: TextureFilter::Nearest,
             render_target: None,
             fbo_ids: vec![],
             depth_rb: None,
+            last_frame_used: self.frame_id,
         }
     }
 
     fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
         let mag_filter = match filter {
             TextureFilter::Nearest => gl::NEAREST,
             TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
         };
@@ -1014,16 +1020,17 @@ impl Device {
 
         let is_resized = texture.width != width || texture.height != height;
 
         texture.width = width;
         texture.height = height;
         texture.filter = filter;
         texture.layer_count = layer_count;
         texture.render_target = render_target;
+        texture.last_frame_used = self.frame_id;
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, filter);
 
         match render_target {
             Some(info) => {
                 self.update_target_storage(texture, &info, is_resized, pixels);
             }
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.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 api::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion};
-use api::{DevicePixelScale, DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch};
-use api::{ExternalScrollId, FilterOp, IframeDisplayItem, ImageDisplayItem, ItemRange, LayerPoint};
+use api::{BuiltDisplayListIter, ClipId, ColorF, ComplexClipRegion, DevicePixelScale};
+use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch, ExternalScrollId};
+use api::{FilterOp, IframeDisplayItem, ImageDisplayItem, ItemRange, LayerPoint};
 use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, PipelineId};
 use api::{ScrollClamping, ScrollEventPhase, ScrollFrameDisplayItem, ScrollLocation};
-use api::{ScrollNodeIdType, ScrollNodeState, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem};
-use api::{StackingContext, TileOffset, TransformStyle, WorldPoint};
+use api::{ScrollNodeState, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
+use api::{TileOffset, TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_node::StickyFrameInfo;
-use clip_scroll_tree::{ClipScrollTree, ScrollStates};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
 use gpu_cache::GpuCache;
 use hit_test::HitTester;
 use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
+use prim_store::ScrollNodeAndClipChain;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
 use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
 use tiling::{CompositeOps, Frame};
 use renderer::PipelineInfo;
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -31,25 +32,73 @@ pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
 
+/// A data structure that keeps track of mapping between API clip ids and the indices
+/// used internally in the ClipScrollTree to avoid having to do HashMap lookups. This
+/// also includes a small LRU cache. Currently the cache is small (1 entry), but in the
+/// future we could use uluru here to do something more involved.
+pub struct ClipIdToIndexMapper {
+    map: FastHashMap<ClipId, ClipChainIndex>,
+    cached_index: Option<(ClipId, ClipChainIndex)>,
+}
+
+impl ClipIdToIndexMapper {
+    fn new() -> ClipIdToIndexMapper {
+        ClipIdToIndexMapper {
+            map: FastHashMap::default(),
+            cached_index: None,
+        }
+    }
+
+    pub fn add(&mut self, id: ClipId, index: ClipChainIndex) {
+        debug_assert!(!self.map.contains_key(&id));
+        self.map.insert(id, index);
+    }
+
+    pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
+        let parent_chain_index = self.map_clip_id(parent_id);
+        self.add(id, parent_chain_index);
+    }
+
+    pub fn map_clip_id(&mut self, id: &ClipId) -> ClipChainIndex {
+        match self.cached_index {
+            Some((cached_id, cached_index)) if cached_id == *id => return cached_index,
+            _ => {}
+        }
+
+        self.map[id]
+    }
+
+    pub fn map_clip_id_and_cache_result(&mut self, id: &ClipId) -> ClipChainIndex {
+        let index = self.map_clip_id(id);
+        self.cached_index = Some((*id, index));
+        index
+    }
+
+    pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
+        ScrollNodeAndClipChain::new(*id, self.map_clip_id(&id))
+    }
+}
+
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: FrameBuilder,
     clip_scroll_tree: &'a mut ClipScrollTree,
     font_instances: FontInstanceMap,
     tiled_image_map: TiledImageMap,
     pipeline_epochs: Vec<(PipelineId, Epoch)>,
     replacements: Vec<(ClipId, ClipId)>,
     output_pipelines: &'a FastHashSet<PipelineId>,
+    id_to_index_mapper: ClipIdToIndexMapper,
 }
 
 impl<'a> FlattenContext<'a> {
     /// Since WebRender still handles fixed position and reference frame content internally
     /// we need to apply this table of id replacements only to the id that affects the
     /// position of a node. We can eventually remove this when clients start handling
     /// reference frames themselves. This method applies these replacements.
     fn apply_scroll_frame_id_replacement(&self, id: ClipId) -> ClipId {
@@ -99,35 +148,45 @@ impl<'a> FlattenContext<'a> {
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         frame_size: &LayoutSize,
     ) {
         let root_reference_frame_id = ClipId::root_reference_frame(pipeline_id);
         let root_scroll_frame_id = ClipId::root_scroll_node(pipeline_id);
 
+        let root_clip_chain_index =
+            self.id_to_index_mapper.map_clip_id_and_cache_result(&root_reference_frame_id);
+        let root_reference_frame_clip_and_scroll = ScrollNodeAndClipChain::new(
+            root_reference_frame_id,
+            root_clip_chain_index,
+        );
+
         self.builder.push_stacking_context(
             pipeline_id,
             CompositeOps::default(),
             TransformStyle::Flat,
             true,
             true,
-            ClipAndScrollInfo::simple(root_scroll_frame_id),
+            ScrollNodeAndClipChain::new(
+                ClipId::root_scroll_node(pipeline_id),
+                root_clip_chain_index,
+            ),
             self.output_pipelines,
         );
 
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         if self.scene.root_pipeline_id != Some(pipeline_id) {
             if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
                     let info = LayerPrimitiveInfo::new(root_bounds);
                     self.builder.add_solid_rectangle(
-                        ClipAndScrollInfo::simple(root_reference_frame_id),
+                        root_reference_frame_clip_and_scroll,
                         &info,
                         bg_color,
                         None,
                     );
                 }
             }
         }
 
@@ -137,17 +196,17 @@ impl<'a> FlattenContext<'a> {
             pipeline_id,
             LayerVector2D::zero(),
         );
 
         if self.builder.config.enable_scrollbars {
             let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
             let container_rect = LayerRect::new(LayerPoint::zero(), *frame_size);
             self.builder.add_scroll_bar(
-                ClipAndScrollInfo::simple(root_reference_frame_id),
+                root_reference_frame_clip_and_scroll,
                 &LayerPrimitiveInfo::new(scrollbar_rect),
                 DEFAULT_SCROLLBAR_COLOR,
                 ScrollbarInfo(root_scroll_frame_id, container_rect),
             );
         }
 
         self.builder.pop_stacking_context();
     }
@@ -179,38 +238,32 @@ impl<'a> FlattenContext<'a> {
             // If flatten_item created a sub-traversal, we need `traversal` to have the
             // same state as the completed subtraversal, so we reinitialize it here.
             if let Some(subtraversal) = subtraversal {
                 *traversal = subtraversal;
             }
         }
     }
 
-    fn flatten_clip(
-        &mut self,
-        pipeline_id: PipelineId,
-        parent_id: &ClipId,
-        new_clip_id: &ClipId,
-        clip_region: ClipRegion,
-    ) {
+    fn flatten_clip(&mut self, parent_id: &ClipId, new_clip_id: &ClipId, clip_region: ClipRegion) {
         self.builder.add_clip_node(
             *new_clip_id,
             *parent_id,
-            pipeline_id,
             clip_region,
             self.clip_scroll_tree,
+            &mut self.id_to_index_mapper,
         );
     }
 
     fn flatten_scroll_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &ScrollFrameDisplayItem,
         pipeline_id: PipelineId,
-        clip_and_scroll: &ClipAndScrollInfo,
+        clip_and_scroll: &ScrollNodeAndClipChain,
         reference_frame_relative_offset: &LayerVector2D,
     ) {
         let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
         let clip_region = ClipRegion::create_for_clip_node(
             *item.local_clip().clip_rect(),
             complex_clips,
             info.image_mask,
             &reference_frame_relative_offset,
@@ -224,38 +277,40 @@ impl<'a> FlattenContext<'a> {
             .translate(&reference_frame_relative_offset);
         let content_rect = item.rect().translate(&reference_frame_relative_offset);
 
         debug_assert!(info.clip_id != info.scroll_frame_id);
 
         self.builder.add_clip_node(
             info.clip_id,
             clip_and_scroll.scroll_node_id,
-            pipeline_id,
             clip_region,
             self.clip_scroll_tree,
+            &mut self.id_to_index_mapper,
         );
 
         self.builder.add_scroll_frame(
             info.scroll_frame_id,
             info.clip_id,
             info.external_id,
             pipeline_id,
             &frame_rect,
             &content_rect.size,
             info.scroll_sensitivity,
             self.clip_scroll_tree,
+            &mut self.id_to_index_mapper,
         );
     }
 
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
-        context_scroll_node_id: ClipId,
+        unreplaced_scroll_id: ClipId,
+        clip_and_scroll: ScrollNodeAndClipChain,
         mut reference_frame_relative_offset: LayerVector2D,
         bounds: &LayerRect,
         stacking_context: &StackingContext,
         filters: ItemRange<FilterOp>,
         is_backface_visible: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
@@ -274,57 +329,60 @@ impl<'a> FlattenContext<'a> {
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(display_list, filters),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             self.replacements.push((
-                context_scroll_node_id,
+                unreplaced_scroll_id,
                 self.builder.current_reference_frame_id(),
             ));
         }
 
         reference_frame_relative_offset += bounds.origin.to_vector();
 
         // If we have a transformation or a perspective, we should have been assigned a new
         // reference frame id. This means this stacking context establishes a new reference frame.
         // Descendant fixed position content will be positioned relative to us.
         if let Some(reference_frame_id) = stacking_context.reference_frame_id {
             debug_assert!(
                 stacking_context.transform.is_some() ||
                 stacking_context.perspective.is_some()
             );
 
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
-            let mut parent_id = self.apply_scroll_frame_id_replacement(context_scroll_node_id);
             self.builder.push_reference_frame(
                 reference_frame_id,
-                Some(parent_id),
+                Some(clip_and_scroll.scroll_node_id),
                 pipeline_id,
                 &reference_frame_bounds,
                 stacking_context.transform,
                 stacking_context.perspective,
                 reference_frame_relative_offset,
                 self.clip_scroll_tree,
+                &mut self.id_to_index_mapper,
             );
-            self.replacements.push((context_scroll_node_id, reference_frame_id));
+            self.replacements.push((unreplaced_scroll_id, reference_frame_id));
             reference_frame_relative_offset = LayerVector2D::zero();
         }
 
-        let sc_scroll_node_id = self.apply_scroll_frame_id_replacement(context_scroll_node_id);
-
+        // We apply the replacements one more time in case we need to set it to a replacement
+        // that we just pushed above.
+        let new_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
+        let stacking_context_clip_and_scroll =
+            self.id_to_index_mapper.simple_scroll_and_clip_chain(&new_scroll_node);
         self.builder.push_stacking_context(
             pipeline_id,
             composition_operations,
             stacking_context.transform_style,
             is_backface_visible,
             false,
-            ClipAndScrollInfo::simple(sc_scroll_node_id),
+            stacking_context_clip_and_scroll,
             self.output_pipelines,
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
             reference_frame_relative_offset,
         );
@@ -340,76 +398,81 @@ impl<'a> FlattenContext<'a> {
 
         self.builder.pop_stacking_context();
     }
 
     fn flatten_iframe(
         &mut self,
         item: &DisplayItemRef,
         info: &IframeDisplayItem,
-        parent_pipeline_id: PipelineId,
-        clip_and_scroll: &ClipAndScrollInfo,
+        clip_and_scroll: &ScrollNodeAndClipChain,
         reference_frame_relative_offset: &LayerVector2D,
     ) {
         let iframe_pipeline_id = info.pipeline_id;
         let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
             Some(pipeline) => pipeline,
             None => return,
         };
 
         self.builder.add_clip_node(
             info.clip_id,
             clip_and_scroll.scroll_node_id,
-            parent_pipeline_id,
             ClipRegion::create_for_clip_node_with_local_clip(
                 &item.local_clip(),
                 &reference_frame_relative_offset
             ),
             self.clip_scroll_tree,
+            &mut self.id_to_index_mapper,
         );
 
         self.pipeline_epochs.push((iframe_pipeline_id, pipeline.epoch));
 
         let bounds = item.rect();
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
         self.builder.push_reference_frame(
             ClipId::root_reference_frame(iframe_pipeline_id),
             Some(info.clip_id),
             iframe_pipeline_id,
             &iframe_rect,
             None,
             None,
             origin,
             self.clip_scroll_tree,
+            &mut self.id_to_index_mapper,
         );
 
         self.builder.add_scroll_frame(
             ClipId::root_scroll_node(iframe_pipeline_id),
             ClipId::root_reference_frame(iframe_pipeline_id),
             Some(ExternalScrollId(0, iframe_pipeline_id)),
             iframe_pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             self.clip_scroll_tree,
+            &mut self.id_to_index_mapper,
         );
 
         self.flatten_root(&mut pipeline.display_list.iter(), iframe_pipeline_id, &iframe_rect.size);
 
         self.builder.pop_reference_frame();
     }
 
     fn flatten_item<'b>(
         &'b mut self,
         item: DisplayItemRef<'a, 'b>,
         pipeline_id: PipelineId,
         reference_frame_relative_offset: LayerVector2D,
     ) -> Option<BuiltDisplayListIter<'a>> {
-        let mut clip_and_scroll = item.clip_and_scroll();
+        let clip_and_scroll = item.clip_and_scroll();
+        let mut clip_and_scroll = ScrollNodeAndClipChain::new(
+            clip_and_scroll.scroll_node_id,
+            self.id_to_index_mapper.map_clip_id_and_cache_result(&clip_and_scroll.clip_node_id()),
+        );
 
         let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
         clip_and_scroll.scroll_node_id =
             self.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
 
         let prim_info = item.get_layer_primitive_info(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::Image(ref info) => {
@@ -552,51 +615,51 @@ impl<'a> FlattenContext<'a> {
                 );
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
                     unreplaced_scroll_id,
+                    clip_and_scroll,
                     reference_frame_relative_offset,
                     &item.rect(),
                     &info.stacking_context,
                     item.filters(),
                     prim_info.is_backface_visible,
                 );
                 return Some(subtraversal);
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(
                     &item,
                     info,
-                    pipeline_id,
                     &clip_and_scroll,
                     &reference_frame_relative_offset
                 );
             }
             SpecificDisplayItem::Clip(ref info) => {
                 let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
                 let clip_region = ClipRegion::create_for_clip_node(
                     *item.local_clip().clip_rect(),
                     complex_clips,
                     info.image_mask,
                     &reference_frame_relative_offset,
                 );
-                self.flatten_clip(
-                    pipeline_id,
-                    &clip_and_scroll.scroll_node_id,
-                    &info.id,
-                    clip_region,
-                );
+                self.flatten_clip(&clip_and_scroll.scroll_node_id, &info.id, clip_region);
             }
             SpecificDisplayItem::ClipChain(ref info) => {
                 let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items());
-                self.clip_scroll_tree.add_clip_chain_descriptor(info.id, info.parent, items);
+                let parent = info.parent.map(|id|
+                     self.id_to_index_mapper.map_clip_id(&ClipId::ClipChain(id))
+                );
+                let clip_chain_index =
+                    self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
+                self.id_to_index_mapper.add(ClipId::ClipChain(info.id), clip_chain_index);
             },
             SpecificDisplayItem::ScrollFrame(ref info) => {
                 self.flatten_scroll_frame(
                     &item,
                     info,
                     pipeline_id,
                     &clip_and_scroll,
                     &reference_frame_relative_offset
@@ -605,22 +668,24 @@ impl<'a> FlattenContext<'a> {
             SpecificDisplayItem::StickyFrame(ref info) => {
                 let frame_rect = item.rect().translate(&reference_frame_relative_offset);
                 let sticky_frame_info = StickyFrameInfo::new(
                     info.margins,
                     info.vertical_offset_bounds,
                     info.horizontal_offset_bounds,
                     info.previously_applied_offset,
                 );
+                let parent_id = clip_and_scroll.scroll_node_id;
                 self.clip_scroll_tree.add_sticky_frame(
                     info.id,
-                    clip_and_scroll.scroll_node_id, /* parent id */
+                    parent_id,
                     frame_rect,
                     sticky_frame_info
                 );
+                self.id_to_index_mapper.map_to_parent_clip_chain(info.id, &parent_id);
             }
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => {}
 
             SpecificDisplayItem::PopStackingContext => {
                 unreachable!("Should have returned in parent method.")
             }
@@ -645,17 +710,17 @@ impl<'a> FlattenContext<'a> {
     /// decomposition. This lets us generate the minimum amount of primitives by, for  example,
     /// 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,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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;
@@ -691,17 +756,17 @@ impl<'a> FlattenContext<'a> {
                     tile_size,
                 );
             }
         }
     }
 
     fn decompose_image_row(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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 {
@@ -737,17 +802,17 @@ impl<'a> FlattenContext<'a> {
                     tile_size,
                 );
             }
         }
     }
 
     fn decompose_tiled_image(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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
@@ -873,17 +938,17 @@ impl<'a> FlattenContext<'a> {
                     shader_repeat_y,
                 );
             }
         }
     }
 
     fn add_tile_primitive(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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,
@@ -978,17 +1043,17 @@ impl FrameContext {
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
     /// Returns true if the node actually changed position or false otherwise.
     pub fn scroll_node(
         &mut self,
         origin: LayerPoint,
-        id: ScrollNodeIdType,
+        id: ExternalScrollId,
         clamp: ScrollClamping
     ) -> bool {
         self.clip_scroll_tree.scroll_node(origin, id, clamp)
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll(
         &mut self,
@@ -1051,23 +1116,25 @@ impl FrameContext {
                     self.frame_builder_config,
                 ),
                 clip_scroll_tree: &mut self.clip_scroll_tree,
                 font_instances: resource_cache.get_font_instances(),
                 tiled_image_map: resource_cache.get_tiled_image_map(),
                 pipeline_epochs: Vec::new(),
                 replacements: Vec::new(),
                 output_pipelines,
+                id_to_index_mapper: ClipIdToIndexMapper::new(),
             };
 
             roller.builder.push_root(
                 root_pipeline_id,
                 &root_pipeline.viewport_size,
                 &root_pipeline.content_size,
                 roller.clip_scroll_tree,
+                &mut roller.id_to_index_mapper,
             );
 
             roller.builder.setup_viewport_offset(
                 inner_rect,
                 device_pixel_scale,
                 roller.clip_scroll_tree,
             );
 
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,41 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipAndScrollInfo};
-use api::{ClipId, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
-use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, Epoch, ExtendMode};
-use api::{ExternalScrollId, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop, ImageKey};
-use api::{ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize};
-use api::{LayerTransform, LayerVector2D, LayoutTransform, LayoutVector2D, LineOrientation};
-use api::{LineStyle, LocalClip, PipelineId, PremultipliedColorF, PropertyBinding, RepeatMode};
-use api::{ScrollSensitivity, Shadow, TexelRect, TileOffset, TransformStyle, WorldPoint};
-use api::{WorldToLayerTransform, YuvColorSpace, YuvData};
+use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipId, ColorF};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
+use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, Epoch, ExtendMode, ExternalScrollId};
+use api::{FontRenderMode, GlyphInstance, GlyphOptions, GradientStop, ImageKey, ImageRendering};
+use api::{ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize, LayerTransform};
+use api::{LayerVector2D, LayoutTransform, LayoutVector2D, LineOrientation, LineStyle, LocalClip};
+use api::{PipelineId, PremultipliedColorF, PropertyBinding, RepeatMode, ScrollSensitivity, Shadow};
+use api::{TexelRect, TileOffset, TransformStyle, WorldPoint, WorldToLayerTransform, YuvColorSpace};
+use api::YuvData;
 use app_units::Au;
 use border::ImageBorderSegment;
-use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
+use clip::{ClipChain, ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::ClipScrollTree;
+use clip_scroll_tree::{ClipScrollTree, ClipChainIndex};
 use euclid::{SideOffsets2D, vec2};
-use frame::FrameId;
+use frame::{FrameId, ClipIdToIndexMapper};
 use glyph_rasterizer::FontInstance;
-use gpu_cache::GpuCache;
+use gpu_cache::{GpuCache, GpuCacheHandle};
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
 use hit_test::{HitTester, HitTestingItem, HitTestingRun};
-use internal_types::{FastHashMap, FastHashSet, RenderPassIndex};
+use internal_types::{FastHashMap, FastHashSet};
 use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
-use prim_store::{BrushKind, BrushPrimitive, ImageCacheKey, YuvImagePrimitiveCpu};
-use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, ImageSource, PrimitiveKind};
-use prim_store::{PrimitiveContainer, PrimitiveIndex};
-use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
-use prim_store::{BrushSegmentDescriptor, PrimitiveRun, TextRunPrimitiveCpu};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, GradientPrimitiveCpu};
+use prim_store::{ImageCacheKey, ImagePrimitiveCpu, ImageSource, PrimitiveContainer};
+use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveRun, PrimitiveStore};
+use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_task::{ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
+use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ImageRequest, ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize, f32};
 use tiling::{CompositeOps, Frame, RenderPass, RenderTargetKind};
 use tiling::{RenderPassKind, RenderTargetContext, ScrollbarPrimitive};
 use util::{self, MaxRect, pack_as_float, RectHelpers, recycle_vec};
 
 #[derive(Debug)]
@@ -85,20 +84,20 @@ pub struct FrameBuilder {
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 
     // A stack of the current shadow primitives.
     // The sub-Vec stores a buffer of fast-path primitives to be appended on pop.
-    shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ClipAndScrollInfo)>)>,
+    shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ScrollNodeAndClipChain)>)>,
     // If we're doing any fast-path shadows, we buffer the "real"
     // content here, to be appended when the shadow stack is empty.
-    pending_shadow_contents: Vec<(PrimitiveIndex, ClipAndScrollInfo, LayerPrimitiveInfo)>,
+    pending_shadow_contents: Vec<(PrimitiveIndex, ScrollNodeAndClipChain, LayerPrimitiveInfo)>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of the current pictures, used during scene building.
@@ -145,24 +144,24 @@ impl PictureState {
     pub fn new() -> PictureState {
         PictureState {
             tasks: Vec::new(),
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
-    pub clip_chain: Option<&'a ClipChain>,
+    pub clip_chain: &'a ClipChain,
     pub scroll_node: &'a ClipScrollNode,
     pub clip_chain_rect_index: ClipChainRectIndex,
 }
 
 impl<'a> PrimitiveRunContext<'a> {
     pub fn new(
-        clip_chain: Option<&'a ClipChain>,
+        clip_chain: &'a ClipChain,
         scroll_node: &'a ClipScrollNode,
         clip_chain_rect_index: ClipChainRectIndex,
     ) -> Self {
         PrimitiveRunContext {
             clip_chain,
             scroll_node,
             clip_chain_rect_index,
         }
@@ -247,17 +246,17 @@ impl FrameBuilder {
         );
 
         prim_index
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
         info: &LayerPrimitiveInfo,
-        clip_and_scroll: ClipAndScrollInfo
+        clip_and_scroll: ScrollNodeAndClipChain
     ) {
         let tag = match info.tag {
             Some(tag) => tag,
             None => return,
         };
 
         let new_item = HitTestingItem::new(tag, info);
         match self.hit_testing_runs.last_mut() {
@@ -271,17 +270,17 @@ impl FrameBuilder {
 
         self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_index: PrimitiveIndex,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
     ) {
         // Add primitive to the top-most Picture on the stack.
         // TODO(gw): Let's consider removing the extra indirection
         //           needed to get a specific primitive index...
         let pic_prim_index = self.picture_stack.last().unwrap();
         let metadata = &self.prim_store.cpu_metadata[pic_prim_index.0];
         let pic = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
         pic.add_primitive(
@@ -289,17 +288,17 @@ impl FrameBuilder {
             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,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         clip_sources: Vec<ClipSource>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
         let prim_index = self.create_primitive(info, clip_sources, container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
@@ -308,17 +307,17 @@ impl FrameBuilder {
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         output_pipelines: &FastHashSet<PipelineId>,
     ) {
         // Construct the necessary set of Picture primitives
         // to draw this stacking context.
         let current_reference_frame_id = self.current_reference_frame_id();
 
         // An arbitrary large clip rect. For now, we don't
         // specify a clip specific to the stacking context.
@@ -635,27 +634,34 @@ impl FrameBuilder {
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
         rect: &LayerRect,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayerVector2D,
         clip_scroll_tree: &mut ClipScrollTree,
+        id_to_index_mapper: &mut ClipIdToIndexMapper,
     ) {
         let node = ClipScrollNode::new_reference_frame(
             parent_id,
             rect,
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         clip_scroll_tree.add_node(node, reference_frame_id);
         self.reference_frame_stack.push(reference_frame_id);
+
+        match parent_id {
+            Some(ref parent_id) =>
+                id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
+            _ => id_to_index_mapper.add(reference_frame_id, ClipChainIndex(0)),
+        }
     }
 
     pub fn current_reference_frame_id(&self) -> ClipId {
         *self.reference_frame_stack.last().unwrap()
     }
 
     pub fn setup_viewport_offset(
         &mut self,
@@ -677,95 +683,105 @@ impl FrameBuilder {
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayerSize,
         content_size: &LayerSize,
         clip_scroll_tree: &mut ClipScrollTree,
+        id_to_index_mapper: &mut ClipIdToIndexMapper,
     ) -> ClipId {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
         self.push_reference_frame(
             ClipId::root_reference_frame(pipeline_id),
             None,
             pipeline_id,
             &viewport_rect,
             None,
             None,
             LayerVector2D::zero(),
             clip_scroll_tree,
+            id_to_index_mapper,
         );
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
         self.add_scroll_frame(
             topmost_scrolling_node_id,
             clip_scroll_tree.root_reference_frame_id,
             Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &viewport_rect,
             content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             clip_scroll_tree,
+            id_to_index_mapper,
         );
 
         topmost_scrolling_node_id
     }
 
     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,
+        id_to_index_mapper: &mut ClipIdToIndexMapper,
     ) {
         let clip_rect = clip_region.main;
         let clip_sources = ClipSources::from(clip_region);
+
         debug_assert!(clip_sources.has_clips());
-
         let handle = self.clip_store.insert(clip_sources);
 
-        let node = ClipScrollNode::new_clip_node(pipeline_id, parent_id, handle, clip_rect);
-        clip_scroll_tree.add_node(node, new_node_id);
+        let clip_chain_index = clip_scroll_tree.add_clip_node(
+            new_node_id,
+            parent_id,
+            handle,
+            clip_rect
+        );
+        id_to_index_mapper.add(new_node_id, clip_chain_index);
     }
 
     pub fn add_scroll_frame(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
         scroll_sensitivity: ScrollSensitivity,
         clip_scroll_tree: &mut ClipScrollTree,
+        id_to_index_mapper: &mut ClipIdToIndexMapper,
     ) {
         let node = ClipScrollNode::new_scroll_frame(
             pipeline_id,
             parent_id,
             external_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
         );
 
         clip_scroll_tree.add_node(node, new_node_id);
+        id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
     ) {
         let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
         let prim = PicturePrimitive::new_text_shadow(shadow, pipeline_id);
 
         // Create an empty shadow primitive. Insert it into
         // the draw lists immediately so that it will be drawn
         // before any visual text elements that are added as
@@ -799,17 +815,17 @@ impl FrameBuilder {
         }
 
         mem::replace(&mut self.pending_shadow_contents, pending_primitives);
         mem::replace(&mut self.shadow_prim_stack, shadows);
     }
 
     pub fn add_solid_rectangle(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
             // testing. This allows specifying invisible hit testing areas.
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
@@ -828,17 +844,17 @@ impl FrameBuilder {
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_clear_rectangle(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
     ) {
         let prim = BrushPrimitive::new(
             BrushKind::Clear,
             None,
         );
 
         self.add_primitive(
@@ -846,17 +862,17 @@ impl FrameBuilder {
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_scroll_bar(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         color: ColorF,
         scrollbar_info: ScrollbarInfo,
     ) {
         if color.a == 0.0 {
             return;
         }
 
@@ -878,17 +894,17 @@ impl FrameBuilder {
             prim_index,
             clip_id: scrollbar_info.0,
             frame_rect: scrollbar_info.1,
         });
     }
 
     pub fn add_line(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         wavy_line_thickness: f32,
         orientation: LineOrientation,
         line_color: &ColorF,
         style: LineStyle,
     ) {
         let line = BrushPrimitive::new(
             BrushKind::Line {
@@ -965,17 +981,17 @@ impl FrameBuilder {
                 }
                 _ => {}
             }
         }
     }
 
     pub fn add_border(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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
@@ -1217,17 +1233,17 @@ impl FrameBuilder {
                     );
                 }
             }
         }
     }
 
     pub fn add_gradient(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         start_point: LayerPoint,
         end_point: LayerPoint,
         stops: ItemRange<GradientStop>,
         stops_count: usize,
         extend_mode: ExtendMode,
         tile_size: LayerSize,
         tile_spacing: LayerSize,
@@ -1285,64 +1301,102 @@ impl FrameBuilder {
             PrimitiveContainer::AlignedGradient(gradient_cpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu)
         };
 
         self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
     }
 
+    fn add_radial_gradient_impl(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        start_center: LayerPoint,
+        start_radius: f32,
+        end_center: LayerPoint,
+        end_radius: f32,
+        ratio_xy: f32,
+        stops: ItemRange<GradientStop>,
+        extend_mode: ExtendMode,
+    ) {
+        let prim = BrushPrimitive::new(
+            BrushKind::RadialGradient {
+                stops_range: stops,
+                extend_mode,
+                stops_handle: GpuCacheHandle::new(),
+                start_center,
+                end_center,
+                start_radius,
+                end_radius,
+                ratio_xy,
+            },
+            None,
+        );
+
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
+    }
+
     pub fn add_radial_gradient(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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,
         tile_spacing: LayerSize,
     ) {
-        let tile_repeat = tile_size + tile_spacing;
+        let prim_infos = info.decompose(
+            tile_size,
+            tile_spacing,
+            64 * 64,
+        );
 
-        let radial_gradient_cpu = RadialGradientPrimitiveCpu {
-            stops_range: stops,
-            extend_mode,
-            gpu_data_count: 0,
-            gpu_blocks: [
-                [start_center.x, start_center.y, end_center.x, end_center.y].into(),
-                [
+        if prim_infos.is_empty() {
+            self.add_radial_gradient_impl(
+                clip_and_scroll,
+                info,
+                start_center,
+                start_radius,
+                end_center,
+                end_radius,
+                ratio_xy,
+                stops,
+                extend_mode,
+            );
+        } else {
+            for prim_info in prim_infos {
+                self.add_radial_gradient_impl(
+                    clip_and_scroll,
+                    &prim_info,
+                    start_center,
                     start_radius,
+                    end_center,
                     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,
-            info,
-            Vec::new(),
-            PrimitiveContainer::RadialGradient(radial_gradient_cpu),
-        );
+                    stops,
+                    extend_mode,
+                );
+            }
+        }
     }
 
     pub fn add_text(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         run_offset: LayoutVector2D,
         info: &LayerPrimitiveInfo,
         font: &FontInstance,
         text_color: &ColorF,
         glyph_range: ItemRange<GlyphInstance>,
         glyph_count: usize,
         glyph_options: Option<GlyphOptions>,
     ) {
@@ -1488,17 +1542,17 @@ impl FrameBuilder {
                 }
                 _ => {}
             }
         }
     }
 
     pub fn add_image(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
         stretch_size: LayerSize,
         mut tile_spacing: LayerSize,
         sub_rect: Option<TexelRect>,
         image_key: ImageKey,
         image_rendering: ImageRendering,
         alpha_type: AlphaType,
         tile_offset: Option<TileOffset>,
@@ -1568,42 +1622,44 @@ impl FrameBuilder {
                 Vec::new(),
                 PrimitiveContainer::Image(prim_cpu),
             );
         }
     }
 
     pub fn add_yuv_image(
         &mut self,
-        clip_and_scroll: ClipAndScrollInfo,
+        clip_and_scroll: ScrollNodeAndClipChain,
         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],
             YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
         };
 
-        let prim_cpu = YuvImagePrimitiveCpu {
-            yuv_key,
-            format,
-            color_space,
-            image_rendering,
-            gpu_block: [info.rect.size.width, info.rect.size.height, 0.0, 0.0].into(),
-        };
+        let prim = BrushPrimitive::new(
+            BrushKind::YuvImage {
+                yuv_key,
+                format,
+                color_space,
+                image_rendering,
+            },
+            None,
+        );
 
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
-            PrimitiveContainer::YuvImage(prim_cpu),
+            PrimitiveContainer::Brush(prim),
         );
     }
 
     /// 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,
         clip_scroll_tree: &ClipScrollTree,
@@ -1802,33 +1858,32 @@ impl FrameBuilder {
             );
         }
 
         let mut deferred_resolves = vec![];
         let mut has_texture_cache_tasks = false;
         let use_dual_source_blending = self.config.dual_source_blending_is_enabled &&
                                        self.config.dual_source_blending_is_supported;
 
-        for (pass_index, pass) in passes.iter_mut().enumerate() {
+        for pass in &mut passes {
             let ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 clip_scroll_tree,
                 use_dual_source_blending,
                 node_data: &node_data,
             };
 
             pass.build(
                 &ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
-                RenderPassIndex(pass_index),
             );
 
             if let RenderPassKind::OffScreen { ref texture_cache, .. } = pass.kind {
                 has_texture_cache_tasks |= !texture_cache.is_empty();
             }
         }
 
         let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
@@ -1858,8 +1913,72 @@ impl FrameBuilder {
     pub fn create_hit_tester(&mut self, clip_scroll_tree: &ClipScrollTree) -> HitTester {
         HitTester::new(
             &self.hit_testing_runs,
             &clip_scroll_tree,
             &self.clip_store
         )
     }
 }
+
+trait PrimitiveInfoTiler {
+    fn decompose(
+        &self,
+        tile_size: LayerSize,
+        tile_spacing: LayerSize,
+        max_prims: usize,
+    ) -> Vec<LayerPrimitiveInfo>;
+}
+
+impl PrimitiveInfoTiler for LayerPrimitiveInfo {
+    fn decompose(
+        &self,
+        tile_size: LayerSize,
+        tile_spacing: LayerSize,
+        max_prims: usize,
+    ) -> Vec<LayerPrimitiveInfo> {
+        let mut prims = Vec::new();
+        let tile_repeat = tile_size + tile_spacing;
+
+        if tile_repeat.width <= 0.0 ||
+           tile_repeat.height <= 0.0 {
+            return prims;
+        }
+
+        if tile_repeat.width < self.rect.size.width ||
+           tile_repeat.height < self.rect.size.height {
+            let local_clip = self.local_clip.clip_by(&self.rect);
+            let rect_p0 = self.rect.origin;
+            let rect_p1 = self.rect.bottom_right();
+
+            let mut y0 = rect_p0.y;
+            while y0 < rect_p1.y {
+                let mut x0 = rect_p0.x;
+
+                while x0 < rect_p1.x {
+                    prims.push(LayerPrimitiveInfo {
+                        rect: LayerRect::new(
+                            LayerPoint::new(x0, y0),
+                            tile_size,
+                        ),
+                        local_clip,
+                        is_backface_visible: self.is_backface_visible,
+                        tag: self.tag,
+                    });
+
+                    // Mostly a safety against a crazy number of primitives
+                    // being generated. If we exceed that amount, just bail
+                    // out and only draw the maximum amount.
+                    if prims.len() > max_prims {
+                        warn!("too many prims found due to repeat/tile. dropping extra prims!");
+                        return prims;
+                    }
+
+                    x0 += tile_repeat.width;
+                }
+
+                y0 += tile_repeat.height;
+            }
+        }
+
+        prims
+    }
+}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -623,26 +623,27 @@ struct GlyphRasterJob {
     result: Option<RasterizedGlyph>,
 }
 
 #[test]
 fn rasterize_200_glyphs() {
     // This test loads a font from disc, the renders 4 requests containing
     // 50 glyphs each, deletes the font and waits for the result.
 
-    use rayon::Configuration;
+    use rayon::ThreadPoolBuilder;
     use std::fs::File;
     use std::io::Read;
 
-    let worker_config = Configuration::new()
+    let worker = ThreadPoolBuilder::new()
         .thread_name(|idx|{ format!("WRWorker#{}", idx) })
         .start_handler(move |idx| {
             register_thread_with_profiler(format!("WRWorker#{}", idx));
-        });
-    let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
+        })
+        .build();
+    let workers = Arc::new(worker.unwrap());
     let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
     let mut glyph_cache = GlyphCache::new();
     let mut gpu_cache = GpuCache::new();
     let mut texture_cache = TextureCache::new(2048);
 
     let mut font_file =
         File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
     let mut font_data = vec![];
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -142,16 +142,24 @@ impl From<CompositePrimitiveInstance> fo
                 instance.data1,
                 instance.data2,
                 instance.data3,
             ],
         }
     }
 }
 
+bitflags! {
+    /// Flags that define how the common brush shader
+    /// code should process this instance.
+    pub struct BrushFlags: u8 {
+        const PERSPECTIVE_INTERPOLATION = 0x1;
+    }
+}
+
 // TODO(gw): While we are comverting things over, we
 //           need to have the instance be the same
 //           size as an old PrimitiveInstance. In the
 //           future, we can compress this vertex
 //           format a lot - e.g. z, render task
 //           addresses etc can reasonably become
 //           a u16 type.
 #[repr(C)]
@@ -159,28 +167,31 @@ pub struct BrushInstance {
     pub picture_address: RenderTaskAddress,
     pub prim_address: GpuCacheAddress,
     pub clip_chain_rect_index: ClipChainRectIndex,
     pub scroll_id: ClipScrollNodeIndex,
     pub clip_task_address: RenderTaskAddress,
     pub z: i32,
     pub segment_index: i32,
     pub edge_flags: EdgeAaSegmentMask,
+    pub brush_flags: BrushFlags,
     pub user_data: [i32; 3],
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
     fn from(instance: BrushInstance) -> Self {
         PrimitiveInstance {
             data: [
                 instance.picture_address.0 as i32 | (instance.clip_task_address.0 as i32) << 16,
                 instance.prim_address.as_int(),
                 ((instance.clip_chain_rect_index.0 as i32) << 16) | instance.scroll_id.0 as i32,
                 instance.z,
-                instance.segment_index | ((instance.edge_flags.bits() as i32) << 16),
+                instance.segment_index |
+                    ((instance.edge_flags.bits() as i32) << 16) |
+                    ((instance.brush_flags.bits() as i32) << 24),
                 instance.user_data[0],
                 instance.user_data[1],
                 instance.user_data[2],
             ]
         }
     }
 }
 
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,19 +1,20 @@
 /* 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, ClipAndScrollInfo, ClipId, ClipMode, HitTestFlags, HitTestItem};
-use api::{HitTestResult, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect};
-use api::{LayerToWorldTransform, LocalClip, PipelineId, WorldPoint};
+use api::{BorderRadius, ClipId, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag};
+use api::{LayerPoint, LayerPrimitiveInfo, LayerRect, LayerToWorldTransform, LocalClip, PipelineId};
+use api::WorldPoint;
 use clip::{ClipSource, ClipStore, Contains, rounded_rectangle_contains_point};
 use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::ClipScrollTree;
+use clip_scroll_tree::{ClipChainIndex, ClipScrollTree};
 use internal_types::FastHashMap;
+use prim_store::ScrollNodeAndClipChain;
 
 /// A copy of important clip scroll node data to use during hit testing. This a copy of
 /// data from the ClipScrollTree that will persist as a new frame is under construction,
 /// allowing hit tests consistent with the currently rendered frame.
 pub struct HitTestClipScrollNode {
     /// A particular point must be inside all of these regions to be considered clipped in
     /// for the purposes of a hit test.
     regions: Vec<HitTestRegion>,
@@ -28,21 +29,31 @@ pub struct HitTestClipScrollNode {
     node_origin: LayerPoint,
 }
 
 /// A description of a clip chain in the HitTester. This is used to describe
 /// hierarchical clip scroll nodes as well as ClipChains, so that they can be
 /// handled the same way during hit testing. Once we represent all ClipChains
 /// using ClipChainDescriptors, we can get rid of this and just use the
 /// ClipChainDescriptor here.
+#[derive(Clone)]
 struct HitTestClipChainDescriptor {
-    parent: Option<ClipId>,
+    parent: Option<ClipChainIndex>,
     clips: Vec<ClipId>,
 }
 
+impl HitTestClipChainDescriptor {
+    fn empty() -> HitTestClipChainDescriptor {
+        HitTestClipChainDescriptor {
+            parent: None,
+            clips: Vec::new(),
+        }
+    }
+}
+
 #[derive(Clone)]
 pub struct HitTestingItem {
     rect: LayerRect,
     clip: LocalClip,
     tag: ItemTag,
 }
 
 impl HitTestingItem {
@@ -51,17 +62,17 @@ impl HitTestingItem {
             rect: info.rect,
             clip: info.local_clip,
             tag: tag,
         }
     }
 }
 
 #[derive(Clone)]
-pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ClipAndScrollInfo);
+pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ScrollNodeAndClipChain);
 
 enum HitTestRegion {
     Rectangle(LayerRect),
     RoundedRectangle(LayerRect, BorderRadius, ClipMode),
 }
 
 impl HitTestRegion {
     pub fn contains(&self, point: &LayerPoint) -> bool {
@@ -73,95 +84,98 @@ impl HitTestRegion {
                 !rounded_rectangle_contains_point(point, &rect, &radii),
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
     nodes: FastHashMap<ClipId, HitTestClipScrollNode>,
-    clip_chains: FastHashMap<ClipId, HitTestClipChainDescriptor>,
+    clip_chains: Vec<HitTestClipChainDescriptor>,
 }
 
 impl HitTester {
     pub fn new(
         runs: &Vec<HitTestingRun>,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
     ) -> HitTester {
         let mut hit_tester = HitTester {
             runs: runs.clone(),
             nodes: FastHashMap::default(),
-            clip_chains: FastHashMap::default(),
+            clip_chains: Vec::new(),
         };
         hit_tester.read_clip_scroll_tree(clip_scroll_tree, clip_store);
         hit_tester
     }
 
     fn read_clip_scroll_tree(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
     ) {
         self.nodes.clear();
+        self.clip_chains.clear();
+        self.clip_chains.resize(
+            clip_scroll_tree.clip_chains.len(),
+            HitTestClipChainDescriptor::empty()
+        );
 
         for (id, node) in &clip_scroll_tree.nodes {
             self.nodes.insert(*id, HitTestClipScrollNode {
                 regions: get_regions_for_clip_scroll_node(node, clip_store),
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
                 node_origin: node.local_viewport_rect.origin,
             });
 
-            self.clip_chains.insert(*id, HitTestClipChainDescriptor {
-                parent: node.parent,
-                clips: vec![*id],
-            });
+            if let NodeType::Clip { clip_chain_index, .. } = node.node_type {
+              let clip_chain = self.clip_chains.get_mut(clip_chain_index.0).unwrap();
+              clip_chain.parent =
+                  clip_scroll_tree.get_clip_chain(clip_chain_index).parent_index;
+              clip_chain.clips = vec![*id];
+            }
         }
 
         for descriptor in &clip_scroll_tree.clip_chains_descriptors {
-            self.clip_chains.insert(
-                ClipId::ClipChain(descriptor.id),
-                HitTestClipChainDescriptor {
-                    parent: descriptor.parent.map(|id| ClipId::ClipChain(id)),
-                    clips: descriptor.clips.clone(),
-                }
-            );
+            let clip_chain = self.clip_chains.get_mut(descriptor.index.0).unwrap();
+            clip_chain.parent = clip_scroll_tree.get_clip_chain(descriptor.index).parent_index;
+            clip_chain.clips = descriptor.clips.clone();
         }
     }
 
     fn is_point_clipped_in_for_clip_chain(
         &self,
         point: WorldPoint,
-        chain_id: &ClipId,
+        clip_chain_index: ClipChainIndex,
         test: &mut HitTest
     ) -> bool {
-        if let Some(result) = test.clip_chain_cache.get(&chain_id) {
-            return *result;
+        if let Some(result) = test.get_from_clip_chain_cache(clip_chain_index) {
+            return result;
         }
 
-        let descriptor = &self.clip_chains[&chain_id];
+        let descriptor = &self.clip_chains[clip_chain_index.0];
         let parent_clipped_in = match descriptor.parent {
             None => true,
-            Some(ref parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
+            Some(parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
         };
 
         if !parent_clipped_in {
-            test.clip_chain_cache.insert(*chain_id, false);
+            test.set_in_clip_chain_cache(clip_chain_index, false);
             return false;
         }
 
         for clip_node in &descriptor.clips {
             if !self.is_point_clipped_in_for_node(point, clip_node, test) {
-                test.clip_chain_cache.insert(*chain_id, false);
+                test.set_in_clip_chain_cache(clip_chain_index, false);
                 return false;
             }
         }
 
-        test.clip_chain_cache.insert(*chain_id, true);
+        test.set_in_clip_chain_cache(clip_chain_index, true);
         true
     }
 
     fn is_point_clipped_in_for_node(
         &self,
         point: WorldPoint,
         node_id: &ClipId,
         test: &mut HitTest
@@ -192,17 +206,19 @@ impl HitTester {
         true
     }
 
     pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
         let point = test.get_absolute_point(self);
 
         let mut result = HitTestResult::default();
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
-            let scroll_node = &self.nodes[&clip_and_scroll.scroll_node_id];
+            let scroll_node_id = clip_and_scroll.scroll_node_id;
+            let scroll_node = &self.nodes[&scroll_node_id];
+            let pipeline_id = scroll_node_id.pipeline_id();
             match (test.pipeline_id, clip_and_scroll.scroll_node_id.pipeline_id()) {
                 (Some(id), node_id) if node_id != id => continue,
                 _ => {},
             }
 
             let transform = scroll_node.world_content_transform;
             let point_in_layer = match transform.inverse() {
                 Some(inverted) => inverted.transform_point2d(&point),
@@ -210,40 +226,39 @@ impl HitTester {
             };
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) || !item.clip.contains(&point_in_layer) {
                     continue;
                 }
 
-                let clip_id = &clip_and_scroll.clip_node_id();
+                let clip_chain_index = clip_and_scroll.clip_chain_index;
+                clipped_in |=
+                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test);
                 if !clipped_in {
-                    clipped_in = self.is_point_clipped_in_for_clip_chain(point, clip_id, &mut test);
-                    if !clipped_in {
-                        break;
-                    }
+                    break;
                 }
 
                 // We need to trigger a lookup against the root reference frame here, because
                 // items that are clipped by clip chains won't test against that part of the
                 // hierarchy. If we don't have a valid point for this test, we are likely
                 // in a situation where the reference frame has an univertible transform, but the
                 // item's clip does not.
-                let root_reference_frame = ClipId::root_reference_frame(clip_id.pipeline_id());
+                let root_reference_frame = ClipId::root_reference_frame(pipeline_id);
                 if !self.is_point_clipped_in_for_node(point, &root_reference_frame, &mut test) {
                     continue;
                 }
                 let point_in_viewport = match test.node_cache[&root_reference_frame] {
                     Some(point) => point,
                     None => continue,
                 };
 
                 result.items.push(HitTestItem {
-                    pipeline: clip_and_scroll.clip_node_id().pipeline_id(),
+                    pipeline: pipeline_id,
                     tag: item.tag,
                     point_in_viewport,
                     point_relative_to_item: point_in_layer - item.rect.origin.to_vector(),
                 });
                 if !test.flags.contains(HitTestFlags::FIND_ALL) {
                     return result;
                 }
             }
@@ -254,17 +269,17 @@ impl HitTester {
     }
 }
 
 fn get_regions_for_clip_scroll_node(
     node: &ClipScrollNode,
     clip_store: &ClipStore
 ) -> Vec<HitTestRegion> {
     let clips = match node.node_type {
-        NodeType::Clip(ref handle) => clip_store.get(handle).clips(),
+        NodeType::Clip{ ref handle, .. } => clip_store.get(handle).clips(),
         _ => return Vec::new(),
     };
 
     clips.iter().map(|ref source| {
         match source.0 {
             ClipSource::Rectangle(ref rect) => HitTestRegion::Rectangle(*rect),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
@@ -275,34 +290,49 @@ fn get_regions_for_clip_scroll_node(
     }).collect()
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
     node_cache: FastHashMap<ClipId, Option<LayerPoint>>,
-    clip_chain_cache: FastHashMap<ClipId, bool>,
+    clip_chain_cache: Vec<Option<bool>>,
 }
 
 impl HitTest {
     pub fn new(
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags,
     ) -> HitTest {
         HitTest {
             pipeline_id,
             point,
             flags,
             node_cache: FastHashMap::default(),
-            clip_chain_cache: FastHashMap::default(),
+            clip_chain_cache: Vec::new(),
         }
     }
 
+    pub fn get_from_clip_chain_cache(&mut self, index: ClipChainIndex) -> Option<bool> {
+        if index.0 >= self.clip_chain_cache.len() {
+            None
+        } else {
+            self.clip_chain_cache[index.0]
+        }
+    }
+
+    pub fn set_in_clip_chain_cache(&mut self, index: ClipChainIndex, value: bool) {
+        if index.0 >= self.clip_chain_cache.len() {
+            self.clip_chain_cache.resize(index.0 + 1, None);
+        }
+        self.clip_chain_cache[index.0] = Some(value);
+    }
+
     pub fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
         if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
             return self.point;
         }
 
         let point =  &LayerPoint::new(self.point.x, self.point.y);
         self.pipeline_id.and_then(|id| hit_tester.nodes.get(&ClipId::root_reference_frame(id)))
                    .map(|node| node.world_viewport_transform.transform_point2d(&point))
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -39,37 +39,38 @@ pub type FastHashSet<K> = HashSet<K, Bui
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheTextureId(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct RenderPassIndex(pub usize);
+pub struct SavedTargetIndex(pub usize);
+
+impl SavedTargetIndex {
+    pub const PENDING: Self = SavedTargetIndex(!0);
+}
 
 // Represents the source for a texture.
 // These are passed from throughout the
 // pipeline until they reach the rendering
 // thread, where they are resolved to a
 // native texture ID.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum SourceTexture {
     Invalid,
     TextureCache(CacheTextureId),
     External(ExternalImageData),
     CacheA8,
     CacheRGBA8,
-    // XXX Remove this once RenderTaskCacheA8 is used.
-    #[allow(dead_code)]
-    RenderTaskCacheA8(RenderPassIndex),
-    RenderTaskCacheRGBA8(RenderPassIndex),
+    RenderTaskCache(SavedTargetIndex),
 }
 
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,21 +1,21 @@
 /* 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::{ColorF, ClipAndScrollInfo, FilterOp, MixBlendMode};
-use api::{DeviceIntPoint, DeviceIntRect, LayerToWorldScale, PipelineId};
-use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerVector2D, Shadow};
-use api::{ClipId, PremultipliedColorF};
+use api::{BoxShadowClipMode, ClipId, ColorF, DeviceIntPoint, DeviceIntRect, FilterOp, LayerPoint};
+use api::{LayerRect, LayerToWorldScale, LayerVector2D, MixBlendMode, PipelineId};
+use api::{PremultipliedColorF, Shadow};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
 use frame_builder::{FrameContext, FrameState, PictureState};
 use gpu_cache::GpuDataRequest;
 use gpu_types::{BrushImageKind, PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
+use prim_store::ScrollNodeAndClipChain;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::CacheItem;
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  A picture represents a dynamically rendered image. It consists of:
@@ -225,17 +225,17 @@ impl PicturePrimitive {
                 None,
             ),
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
-        clip_and_scroll: ClipAndScrollInfo
+        clip_and_scroll: ScrollNodeAndClipChain
     ) {
         if let Some(ref mut run) = self.runs.last_mut() {
             if run.clip_and_scroll == clip_and_scroll &&
                run.base_prim_index.0 + run.count == prim_index.0 {
                 run.count += 1;
                 return;
             }
         }
@@ -363,26 +363,27 @@ impl PicturePrimitive {
                         );
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
-                        let picture_task = RenderTask::new_picture(
+                        let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             ContentOrigin::Screen(rect.origin),
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
+                        picture_task.mark_for_saving();
 
                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                         let (blur_render_task, _) = RenderTask::new_blur(
                             blur_std_deviation.round(),
                             picture_task_id,
                             frame_state.render_tasks,
@@ -596,17 +597,17 @@ impl PicturePrimitive {
                 for _ in 0 .. 5 {
                     request.push([0.0; 4]);
                 }
             }
             PictureKind::Image { composite_mode, .. } => {
                 match composite_mode {
                     Some(PictureCompositeMode::Filter(FilterOp::ColorMatrix(m))) => {
                         for i in 0..5 {
-                            request.push([m[i], m[i+5], m[i+10], m[i+15]]);
+                            request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
                     Some(PictureCompositeMode::Filter(filter)) => {
                         let amount = match filter {
                             FilterOp::Contrast(amount) => amount,
                             FilterOp::Grayscale(amount) => amount,
                             FilterOp::HueRotate(angle) => 0.01745329251 * angle,
                             FilterOp::Invert(amount) => amount,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,47 +1,58 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipAndScrollInfo, ClipMode};
-use api::{ColorF, DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch};
-use api::{ComplexClipRegion, ExtendMode, FontRenderMode};
+use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipId, ClipMode, ColorF, ComplexClipRegion};
+use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
-use api::{LineStyle, PremultipliedColorF};
-use api::{WorldToLayerTransform, YuvColorSpace, YuvFormat};
+use api::{LineStyle, PremultipliedColorF, WorldToLayerTransform, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
-use clip_scroll_tree::{CoordinateSystemId};
+use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
-use clip::{ClipSource, ClipSourcesHandle};
+use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
+use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameContext, FrameState, PictureContext, PictureState, PrimitiveRunContext};
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex};
 use picture::{PictureKind, PicturePrimitive};
-use render_task::{BlitSource, ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipWorkItem};
-use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId};
+use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind};
+use render_task::RenderTaskId;
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{CacheItem, ImageProperties, ImageRequest, ResourceCache};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::rc::Rc;
 use util::{MatrixHelpers, calculate_screen_bounding_rect, pack_as_float};
 use util::recycle_vec;
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0;
 
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct ScrollNodeAndClipChain {
+    pub scroll_node_id: ClipId,
+    pub clip_chain_index: ClipChainIndex,
+}
+
+impl ScrollNodeAndClipChain {
+    pub fn new(scroll_node_id: ClipId, clip_chain_index: ClipChainIndex) -> ScrollNodeAndClipChain {
+        ScrollNodeAndClipChain { scroll_node_id, clip_chain_index }
+    }
+}
+
 #[derive(Debug)]
 pub struct PrimitiveRun {
     pub base_prim_index: PrimitiveIndex,
     pub count: usize,
-    pub clip_and_scroll: ClipAndScrollInfo,
+    pub clip_and_scroll: ScrollNodeAndClipChain,
 }
 
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
 
 impl PrimitiveOpacity {
@@ -107,21 +118,19 @@ pub struct SpecificPrimitiveIndex(pub us
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
     TextRun,
     Image,
-    YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
-    RadialGradient,
     Picture,
     Brush,
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
         gpu_cache.get_address(self).as_int()
     }
@@ -190,24 +199,42 @@ pub enum BrushKind {
         orientation: LineOrientation,
     },
     Picture,
     Image {
         request: ImageRequest,
         current_epoch: Epoch,
         alpha_type: AlphaType,
     },
+    YuvImage {
+        yuv_key: [ImageKey; 3],
+        format: YuvFormat,
+        color_space: YuvColorSpace,
+        image_rendering: ImageRendering,
+    },
+    RadialGradient {
+        stops_range: ItemRange<GradientStop>,
+        extend_mode: ExtendMode,
+        stops_handle: GpuCacheHandle,
+        start_center: LayerPoint,
+        end_center: LayerPoint,
+        start_radius: f32,
+        end_radius: f32,
+        ratio_xy: f32,
+    }
 }
 
 impl BrushKind {
     fn supports_segments(&self) -> bool {
         match *self {
             BrushKind::Solid { .. } |
             BrushKind::Picture |
-            BrushKind::Image { .. } => true,
+            BrushKind::Image { .. } |
+            BrushKind::YuvImage { .. } |
+            BrushKind::RadialGradient { .. } => true,
 
             BrushKind::Mask { .. } |
             BrushKind::Clear |
             BrushKind::Line { .. } => false,
         }
     }
 }
 
@@ -279,17 +306,18 @@ impl BrushPrimitive {
             segment_desc,
         }
     }
 
     fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // has to match VECS_PER_SPECIFIC_BRUSH
         match self.kind {
             BrushKind::Picture |
-            BrushKind::Image { .. } => {
+            BrushKind::Image { .. } |
+            BrushKind::YuvImage { .. } => {
             }
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
@@ -326,16 +354,30 @@ impl BrushPrimitive {
                 request.push(color);
                 request.push([
                     wavy_line_thickness,
                     pack_as_float(style as u32),
                     pack_as_float(orientation as u32),
                     0.0,
                 ]);
             }
+            BrushKind::RadialGradient { start_center, end_center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
+                request.push([
+                    start_center.x,
+                    start_center.y,
+                    end_center.x,
+                    end_center.y,
+                ]);
+                request.push([
+                    start_radius,
+                    end_radius,
+                    ratio_xy,
+                    pack_as_float(extend_mode as u32),
+                ]);
+            }
         }
     }
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -373,34 +415,16 @@ impl ToGpuBlocks for ImagePrimitiveCpu {
         request.push([
             self.stretch_size.width, self.stretch_size.height,
             self.tile_spacing.width, self.tile_spacing.height,
         ]);
     }
 }
 
 #[derive(Debug)]
-pub struct YuvImagePrimitiveCpu {
-    pub yuv_key: [ImageKey; 3],
-    pub format: YuvFormat,
-    pub color_space: YuvColorSpace,
-
-    pub image_rendering: ImageRendering,
-
-    // TODO(gw): Generate on demand
-    pub gpu_block: GpuBlockData,
-}
-
-impl ToGpuBlocks for YuvImagePrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.push(self.gpu_block);
-    }
-}
-
-#[derive(Debug)]
 pub struct BorderPrimitiveCpu {
     pub corner_instances: [BorderCornerInstance; 4],
     pub edges: [BorderEdgeKind; 4],
     pub gpu_blocks: [GpuBlockData; 8],
 }
 
 impl ToGpuBlocks for BorderPrimitiveCpu {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
@@ -626,37 +650,16 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
         for entry in entries.iter() {
             request.push(entry.start_color);
             request.push(entry.end_color);
         }
     }
 }
 
-#[derive(Debug)]
-pub struct RadialGradientPrimitiveCpu {
-    pub stops_range: ItemRange<GradientStop>,
-    pub extend_mode: ExtendMode,
-    pub gpu_data_count: i32,
-    pub gpu_blocks: [GpuBlockData; 3],
-}
-
-impl RadialGradientPrimitiveCpu {
-    fn build_gpu_blocks_for_angle_radial(
-        &self,
-        display_list: &BuiltDisplayList,
-        mut request: GpuDataRequest,
-    ) {
-        request.extend_from_slice(&self.gpu_blocks);
-
-        let gradient_builder = GradientGpuBlockBuilder::new(self.stops_range, display_list);
-        gradient_builder.build(false, &mut request);
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font: FontInstance,
     pub offset: LayerVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
@@ -928,63 +931,55 @@ impl ClipData {
         }
     }
 }
 
 #[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
     Image(ImagePrimitiveCpu),
-    YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
-    RadialGradient(RadialGradientPrimitiveCpu),
     Picture(PicturePrimitive),
     Brush(BrushPrimitive),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_pictures: Vec<PicturePrimitive>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
-    pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
-    pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_brushes: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_pictures: Vec::new(),
             cpu_images: Vec::new(),
-            cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
-            cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_brushes: recycle_vec(self.cpu_brushes),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_pictures: recycle_vec(self.cpu_pictures),
             cpu_images: recycle_vec(self.cpu_images),
-            cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
-            cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
         }
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayerRect,
         local_clip_rect: &LayerRect,
@@ -1013,16 +1008,18 @@ impl PrimitiveStore {
         let metadata = match container {
             PrimitiveContainer::Brush(brush) => {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color } => PrimitiveOpacity::from_alpha(color.a),
                     BrushKind::Mask { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Line { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
+                    BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
+                    BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture => {
                         // TODO(gw): This is not currently used. In the future
                         //           we should detect opaque pictures.
                         unreachable!();
                     }
                 };
 
                 let metadata = PrimitiveMetadata {
@@ -1064,27 +1061,16 @@ impl PrimitiveStore {
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     ..base_metadata
                 };
 
                 self.cpu_images.push(image_cpu);
                 metadata
             }
-            PrimitiveContainer::YuvImage(image_cpu) => {
-                let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::opaque(),
-                    prim_kind: PrimitiveKind::YuvImage,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_yuv_images.push(image_cpu);
-                metadata
-            }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
                     ..base_metadata
                 };
 
@@ -1109,28 +1095,16 @@ impl PrimitiveStore {
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     ..base_metadata
                 };
 
                 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(),
-                    prim_kind: PrimitiveKind::RadialGradient,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_radial_gradients.push(radial_gradient_cpu);
-                metadata
-            }
         };
 
         self.cpu_metadata.push(metadata);
 
         PrimitiveIndex(prim_index)
     }
 
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
@@ -1288,32 +1262,16 @@ impl PrimitiveStore {
                     if request_source_image {
                         frame_state.resource_cache.request_image(
                             image_cpu.key.request,
                             frame_state.gpu_cache,
                         );
                     }
                 }
             }
-            PrimitiveKind::YuvImage => {
-                let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
-
-                let channel_num = image_cpu.format.get_plane_num();
-                debug_assert!(channel_num <= 3);
-                for channel in 0 .. channel_num {
-                    frame_state.resource_cache.request_image(
-                        ImageRequest {
-                            key: image_cpu.yuv_key[channel],
-                            rendering: image_cpu.image_rendering,
-                            tile: None,
-                        },
-                        frame_state.gpu_cache,
-                    );
-                }
-            }
             PrimitiveKind::Brush => {
                 let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
                 match brush.kind {
                     BrushKind::Image { request, ref mut current_epoch, .. } => {
                         let image_properties = frame_state
                             .resource_cache
                             .get_image_properties(request.key);
@@ -1327,26 +1285,51 @@ impl PrimitiveStore {
                             }
                         }
 
                         frame_state.resource_cache.request_image(
                             request,
                             frame_state.gpu_cache,
                         );
                     }
+                    BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
+                        let channel_num = format.get_plane_num();
+                        debug_assert!(channel_num <= 3);
+                        for channel in 0 .. channel_num {
+                            frame_state.resource_cache.request_image(
+                                ImageRequest {
+                                    key: yuv_key[channel],
+                                    rendering: image_rendering,
+                                    tile: None,
+                                },
+                                frame_state.gpu_cache,
+                            );
+                        }
+                    }
+                    BrushKind::RadialGradient { ref mut stops_handle, stops_range, .. } => {
+                        if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
+                            let gradient_builder = GradientGpuBlockBuilder::new(
+                                stops_range,
+                                pic_context.display_list,
+                            );
+                            gradient_builder.build(
+                                false,
+                                &mut request,
+                            );
+                        }
+                    }
                     BrushKind::Mask { .. } |
                     BrushKind::Solid { .. } |
                     BrushKind::Clear |
                     BrushKind::Line { .. } |
                     BrushKind::Picture { .. } => {}
                 }
             }
             PrimitiveKind::AlignedGradient |
-            PrimitiveKind::AngleGradient |
-            PrimitiveKind::RadialGradient => {}
+            PrimitiveKind::AngleGradient => {}
         }
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
@@ -1354,41 +1337,30 @@ impl PrimitiveStore {
                 PrimitiveKind::Border => {
                     let border = &self.cpu_borders[metadata.cpu_prim_index.0];
                     border.write_gpu_blocks(request);
                 }
                 PrimitiveKind::Image => {
                     let image = &self.cpu_images[metadata.cpu_prim_index.0];
                     image.write_gpu_blocks(request);
                 }
-                PrimitiveKind::YuvImage => {
-                    let yuv_image = &self.cpu_yuv_images[metadata.cpu_prim_index.0];
-                    yuv_image.write_gpu_blocks(request);
-                }
                 PrimitiveKind::AlignedGradient => {
                     let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
                     metadata.opacity = gradient.build_gpu_blocks_for_aligned(
                         pic_context.display_list,
                         request,
                     );
                 }
                 PrimitiveKind::AngleGradient => {
                     let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
                     gradient.build_gpu_blocks_for_angle_radial(
                         pic_context.display_list,
                         request,
                     );
                 }
-                PrimitiveKind::RadialGradient => {
-                    let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
-                    gradient.build_gpu_blocks_for_angle_radial(
-                        pic_context.display_list,
-                        request,
-                    );
-                }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Picture => {
                     let pic = &self.cpu_pictures[metadata.cpu_prim_index.0];
                     pic.write_gpu_blocks(&mut request);
 
@@ -1629,22 +1601,19 @@ impl PrimitiveStore {
         let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
             Some(rect) => rect,
             None => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
             }
         };
 
-        let mut combined_outer_rect = match prim_run_context.clip_chain {
-            Some(ref chain) => prim_screen_rect.intersection(&chain.combined_outer_screen_rect),
-            None => Some(prim_screen_rect),
-        };
-
-        let clip_chain = prim_run_context.clip_chain.map_or(None, |x| x.nodes.clone());
+        let mut combined_outer_rect =
+            prim_screen_rect.intersection(&prim_run_context.clip_chain.combined_outer_screen_rect);
+        let clip_chain = prim_run_context.clip_chain.nodes.clone();
 
         let prim_coordinate_system_id = prim_run_context.scroll_node.coordinate_system_id;
         let transform = &prim_run_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
             let prim_clips = frame_state.clip_store.get_mut(&metadata.clip_sources);
             if prim_clips.has_clips() {
                 prim_clips.update(
@@ -1855,22 +1824,18 @@ impl PrimitiveStore {
             };
 
             let screen_bounding_rect = calculate_screen_bounding_rect(
                 &prim_run_context.scroll_node.world_content_transform,
                 &local_rect,
                 frame_context.device_pixel_scale,
             );
 
-            let clip_bounds = match prim_run_context.clip_chain {
-                Some(ref node) => node.combined_outer_screen_rect,
-                None => frame_context.screen_rect,
-            };
             metadata.screen_rect = screen_bounding_rect
-                .intersection(&clip_bounds)
+                .intersection(&prim_run_context.clip_chain.combined_outer_screen_rect)
                 .map(|clipped| {
                     ScreenRect {
                         clipped,
                         unclipped: screen_bounding_rect,
                     }
                 });
 
             if metadata.screen_rect.is_none() && pic_context.perform_culling {
@@ -1930,34 +1895,30 @@ impl PrimitiveStore {
             // TODO(gw): Perhaps we can restructure this to not need to create
             //           a new primitive context for every run (if the hash
             //           lookups ever show up in a profile).
             let scroll_node = &frame_context
                 .clip_scroll_tree
                 .nodes[&run.clip_and_scroll.scroll_node_id];
             let clip_chain = frame_context
                 .clip_scroll_tree
-                .get_clip_chain(&run.clip_and_scroll.clip_node_id());
+                .get_clip_chain(run.clip_and_scroll.clip_chain_index);
 
             if pic_context.perform_culling {
                 if !scroll_node.invertible {
                     debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
                     continue;
                 }
 
-                match clip_chain {
-                     Some(ref chain) if chain.combined_outer_screen_rect.is_empty() => {
-                        debug!("{:?} {:?}: clipped out", run.base_prim_index, pic_context.pipeline_id);
-                        continue;
-                    }
-                    _ => {},
+                if clip_chain.combined_outer_screen_rect.is_empty() {
+                    debug!("{:?} {:?}: clipped out", run.base_prim_index, pic_context.pipeline_id);
+                    continue;
                 }
             }
 
-
             let parent_relative_transform = pic_context
                 .inv_world_transform
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
             let original_relative_transform = pic_context.original_reference_frame_id
                 .and_then(|original_reference_frame_id| {
@@ -2077,24 +2038,19 @@ fn convert_clip_chain_to_clip_vector(
 
             Some(node.work_item.clone())
         })
         .collect()
 }
 
 fn get_local_clip_rect_for_nodes(
     scroll_node: &ClipScrollNode,
-    clip_chain: Option<&ClipChain>,
+    clip_chain: &ClipChain,
 ) -> Option<LayerRect> {
-    let clip_chain_nodes = match clip_chain {
-        Some(ref clip_chain) => clip_chain.nodes.clone(),
-        None => return None,
-    };
-
-    let local_rect = ClipChainNodeIter { current: clip_chain_nodes }.fold(
+    let local_rect = ClipChainNodeIter { current: clip_chain.nodes.clone() }.fold(
         None,
         |combined_local_clip_rect: Option<LayerRect>, node| {
             if node.work_item.coordinate_system_id != scroll_node.coordinate_system_id {
                 return combined_local_clip_rect;
             }
 
             Some(match combined_local_clip_rect {
                 Some(combined_rect) =>
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -295,25 +295,36 @@ impl ProfileCounter for AverageTimeProfi
     }
 }
 
 
 #[derive(Clone)]
 pub struct FrameProfileCounters {
     pub total_primitives: IntProfileCounter,
     pub visible_primitives: IntProfileCounter,
+    pub targets_used: IntProfileCounter,
+    pub targets_changed: IntProfileCounter,
+    pub targets_created: IntProfileCounter,
 }
 
 impl FrameProfileCounters {
     pub fn new() -> Self {
         FrameProfileCounters {
             total_primitives: IntProfileCounter::new("Total Primitives"),
             visible_primitives: IntProfileCounter::new("Visible Primitives"),
+            targets_used: IntProfileCounter::new("Used targets"),
+            targets_changed: IntProfileCounter::new("Changed targets"),
+            targets_created: IntProfileCounter::new("Created targets"),
         }
     }
+    pub fn reset_targets(&mut self) {
+        self.targets_used.reset();
+        self.targets_changed.reset();
+        self.targets_created.reset();
+    }
 }
 
 #[derive(Clone)]
 pub struct TextureCacheProfileCounters {
     pub pages_a8_linear: ResourceProfileCounter,
     pub pages_rgba8_linear: ResourceProfileCounter,
     pub pages_rgba8_nearest: ResourceProfileCounter,
 }
@@ -835,17 +846,17 @@ impl Profiler {
         let new_y = total_rect.origin.y + total_rect.size.height + 30.0;
         if left {
             draw_state.y_left = new_y;
         } else {
             draw_state.y_right = new_y;
         }
     }
 
-    fn draw_gpu_cache_bar(
+    fn draw_bar(
         &mut self,
         label: &str,
         label_color: ColorU,
         counters: &[(ColorU, &IntProfileCounter)],
         debug_renderer: &mut DebugRenderer,
     ) -> Rect<f32> {
         let mut rect = debug_renderer.add_text(
             self.draw_state.x_left,
@@ -893,34 +904,73 @@ impl Profiler {
             description: "",
             value: counters.updated_blocks.value + counters.saved_blocks.value,
         };
         let total_blocks = IntProfileCounter {
             description: "",
             value: counters.allocated_rows.value * MAX_VERTEX_TEXTURE_WIDTH,
         };
 
-        let rect0 = self.draw_gpu_cache_bar(
+        let rect0 = self.draw_bar(
             &format!("GPU cache rows ({}):", counters.allocated_rows.value),
-            ColorU::new(255, 255, 255, 255),
+            ColorU::new(0xFF, 0xFF, 0xFF, 0xFF),
             &[
                 (color_updated, &counters.updated_rows),
                 (color_free, &counters.allocated_rows),
             ],
             debug_renderer,
         );
 
-        let rect1 = self.draw_gpu_cache_bar(
+        let rect1 = self.draw_bar(
             "GPU cache blocks",
-            ColorU::new(255, 255, 0, 255),
+            ColorU::new(0xFF, 0xFF, 0, 0xFF),
             &[
                 (color_updated, &counters.updated_blocks),
                 (color_saved, &requested_blocks),
                 (color_free, &counters.allocated_blocks),
-                (ColorU::new(0, 0, 0, 255), &total_blocks),
+                (ColorU::new(0, 0, 0, 0xFF), &total_blocks),
+            ],
+            debug_renderer,
+        );
+
+        let total_rect = rect0.union(&rect1).inflate(10.0, 10.0);
+        debug_renderer.add_quad(
+            total_rect.origin.x,
+            total_rect.origin.y,
+            total_rect.origin.x + total_rect.size.width,
+            total_rect.origin.y + total_rect.size.height,
+            ColorF::new(0.1, 0.1, 0.1, 0.8).into(),
+            ColorF::new(0.2, 0.2, 0.2, 0.8).into(),
+        );
+
+        self.draw_state.y_left = total_rect.origin.y + total_rect.size.height + 30.0;
+    }
+
+    fn draw_frame_bars(
+        &mut self,
+        counters: &FrameProfileCounters,
+        debug_renderer: &mut DebugRenderer,
+    ) {
+        let rect0 = self.draw_bar(
+            &format!("primitives ({}):", counters.total_primitives.value),
+            ColorU::new(0xFF, 0xFF, 0xFF, 0xFF),
+            &[
+                (ColorU::new(0, 0, 0xFF, 0xFF), &counters.visible_primitives),
+                (ColorU::new(0, 0, 0, 0xFF), &counters.total_primitives),
+            ],
+            debug_renderer,
+        );
+
+        let rect1 = self.draw_bar(
+            &format!("GPU targets ({}):", &counters.targets_used.value),
+            ColorU::new(0xFF, 0xFF, 0, 0xFF),
+            &[
+                (ColorU::new(0, 0, 0xFF, 0xFF), &counters.targets_created),
+                (ColorU::new(0xFF, 0, 0, 0xFF), &counters.targets_changed),
+                (ColorU::new(0, 0xFF, 0, 0xFF), &counters.targets_used),
             ],
             debug_renderer,
         );
 
         let total_rect = rect0.union(&rect1).inflate(10.0, 10.0);
         debug_renderer.add_quad(
             total_rect.origin.x,
             total_rect.origin.y,
@@ -1012,25 +1062,17 @@ impl Profiler {
                 &backend_profile.ipc.total_time,
             ],
             debug_renderer,
             true,
             &mut self.draw_state
         );
 
         for frame_profile in frame_profiles {
-            Profiler::draw_counters(
-                &[
-                    &frame_profile.total_primitives,
-                    &frame_profile.visible_primitives,
-                ],
-                debug_renderer,
-                true,
-                &mut self.draw_state
-            );
+            self.draw_frame_bars(frame_profile, debug_renderer);
         }
 
         Profiler::draw_counters(
             &[&renderer_profile.draw_calls, &renderer_profile.vertices],
             debug_renderer,
             true,
             &mut self.draw_state
         );
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,28 +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::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{ImageDescriptor, ImageFormat, LayerRect, PremultipliedColorF};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, ImageDescriptor, ImageFormat};
+use api::PremultipliedColorF;
 use box_shadow::BoxShadowCacheKey;
-use clip::{ClipSourcesWeakHandle};
+use clip::ClipWorkItem;
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
 use gpu_cache::GpuCache;
-use gpu_types::{ClipScrollNodeIndex, PictureType};
-use internal_types::{FastHashMap, RenderPassIndex, SourceTexture};
+use gpu_types::PictureType;
+use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::ContentOrigin;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use resource_cache::CacheItem;
 use std::{cmp, ops, usize, f32, i32};
-use std::rc::Rc;
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
@@ -38,108 +37,25 @@ pub struct RenderTaskId(pub u32); // TOD
 pub struct RenderTaskAddress(pub u32);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskTree {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
-}
-
-pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
-
-#[derive(Debug, Clone)]
-pub struct ClipChainNode {
-    pub work_item: ClipWorkItem,
-    pub local_clip_rect: LayerRect,
-    pub screen_outer_rect: DeviceIntRect,
-    pub screen_inner_rect: DeviceIntRect,
-    pub prev: ClipChainNodeRef,
-}
-
-#[derive(Debug, Clone)]
-pub struct ClipChain {
-    pub combined_outer_screen_rect: DeviceIntRect,
-    pub combined_inner_screen_rect: DeviceIntRect,
-    pub nodes: ClipChainNodeRef,
-}
-
-impl ClipChain {
-    pub fn empty(screen_rect: &DeviceIntRect) -> ClipChain {
-        ClipChain {
-            combined_inner_screen_rect: *screen_rect,
-            combined_outer_screen_rect: *screen_rect,
-            nodes: None,
-        }
-    }
-
-    pub fn new_with_added_node(
-        &self,
-        work_item: ClipWorkItem,
-        local_clip_rect: LayerRect,
-        screen_outer_rect: DeviceIntRect,
-        screen_inner_rect: DeviceIntRect,
-    ) -> ClipChain {
-        let new_node = ClipChainNode {
-            work_item,
-            local_clip_rect,
-            screen_outer_rect,
-            screen_inner_rect,
-            prev: None,
-        };
-
-        let mut new_chain = self.clone();
-        new_chain.add_node(new_node);
-        new_chain
-    }
-
-    pub fn add_node(&mut self, mut new_node: ClipChainNode) {
-        new_node.prev = self.nodes.clone();
-
-        // If this clip's outer rectangle is completely enclosed by the clip
-        // chain's inner rectangle, then the only clip that matters from this point
-        // on is this clip. We can disconnect this clip from the parent clip chain.
-        if self.combined_inner_screen_rect.contains_rect(&new_node.screen_outer_rect) {
-            new_node.prev = None;
-        }
-
-        self.combined_outer_screen_rect =
-            self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
-            .unwrap_or_else(DeviceIntRect::zero);
-        self.combined_inner_screen_rect =
-            self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
-            .unwrap_or_else(DeviceIntRect::zero);
-
-        self.nodes = Some(Rc::new(new_node));
-    }
-}
-
-pub struct ClipChainNodeIter {
-    pub current: ClipChainNodeRef,
-}
-
-impl Iterator for ClipChainNodeIter {
-    type Item = Rc<ClipChainNode>;
-
-    fn next(&mut self) -> ClipChainNodeRef {
-        let previous = self.current.clone();
-        self.current = match self.current {
-            Some(ref item) => item.prev.clone(),
-            None => return None,
-        };
-        previous
-    }
+    next_saved: SavedTargetIndex,
 }
 
 impl RenderTaskTree {
     pub fn new() -> Self {
         RenderTaskTree {
             tasks: Vec::new(),
             task_data: Vec::new(),
+            next_saved: SavedTargetIndex(0),
         }
     }
 
     pub fn add(&mut self, task: RenderTask) -> RenderTaskId {
         let id = RenderTaskId(self.tasks.len() as u32);
         self.tasks.push(task);
         id
     }
@@ -190,20 +106,26 @@ impl RenderTaskTree {
         pass.add_render_task(id, task.get_dynamic_size(), task.target_kind());
     }
 
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
         RenderTaskAddress(id.0)
     }
 
     pub fn build(&mut self) {
-        for task in &mut self.tasks {
+        for task in &self.tasks {
             self.task_data.push(task.write_task_data());
         }
     }
+
+    pub fn save_target(&mut self) -> SavedTargetIndex {
+        let id = self.next_saved;
+        self.next_saved.0 += 1;
+        id
+    }
 }
 
 impl ops::Index<RenderTaskId> for RenderTaskTree {
     type Output = RenderTask;
     fn index(&self, id: RenderTaskId) -> &RenderTask {
         &self.tasks[id.0 as usize]
     }
 }
@@ -218,25 +140,16 @@ impl ops::IndexMut<RenderTaskId> for Ren
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskLocation {
     Fixed(DeviceIntRect),
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
     TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
-#[derive(Debug, Clone)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ClipWorkItem {
-    pub scroll_node_data_index: ClipScrollNodeIndex,
-    pub clip_sources: ClipSourcesWeakHandle,
-    pub coordinate_system_id: CoordinateSystemId,
-}
-
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     pub clips: Vec<ClipWorkItem>,
     pub coordinate_system_id: CoordinateSystemId,
 }
@@ -326,17 +239,17 @@ pub enum ClearMode {
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTask {
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
-    pub pass_index: Option<RenderPassIndex>,
+    pub saved_index: Option<SavedTargetIndex>,
 }
 
 impl RenderTask {
     pub fn new_picture(
         location: RenderTaskLocation,
         prim_index: PrimitiveIndex,
         target_kind: RenderTargetKind,
         content_origin: ContentOrigin,
@@ -351,27 +264,27 @@ impl RenderTask {
             kind: RenderTaskKind::Picture(PictureTask {
                 prim_index,
                 target_kind,
                 content_origin,
                 color,
                 pic_type,
             }),
             clear_mode,
-            pass_index: None,
+            saved_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
         RenderTask {
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, screen_rect.size),
             kind: RenderTaskKind::Readback(screen_rect),
             clear_mode: ClearMode::Transparent,
-            pass_index: None,
+            saved_index: None,
         }
     }
 
     pub fn new_blit(
         size: DeviceIntSize,
         source: BlitSource,
     ) -> Self {
         let mut children = Vec::new();
@@ -387,35 +300,35 @@ impl RenderTask {
 
         RenderTask {
             children,
             location: RenderTaskLocation::Dynamic(None, size),
             kind: RenderTaskKind::Blit(BlitTask {
                 source,
             }),
             clear_mode: ClearMode::Transparent,
-            pass_index: None,
+            saved_index: None,
         }
     }
 
     pub fn new_mask(
         outer_rect: DeviceIntRect,
         clips: Vec<ClipWorkItem>,
         prim_coordinate_system_id: CoordinateSystemId,
-    ) -> RenderTask {
+    ) -> Self {
         RenderTask {
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, outer_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
                 clips,
                 coordinate_system_id: prim_coordinate_system_id,
             }),
             clear_mode: ClearMode::One,
-            pass_index: None,
+            saved_index: None,
         }
     }
 
     // Construct a render task to apply a blur to a primitive.
     // The render task chain that is constructed looks like:
     //
     //    PrimitiveCacheTask: Draw the primitives.
     //           ^
@@ -468,32 +381,32 @@ impl RenderTask {
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::VerticalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 color,
                 scale_factor,
             }),
             clear_mode,
-            pass_index: None,
+            saved_index: None,
         };
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
         let blur_task_h = RenderTask {
             children: vec![blur_task_v_id],
             location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
             kind: RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 color,
                 scale_factor,
             }),
             clear_mode,
-            pass_index: None,
+            saved_index: None,
         };
 
         (blur_task_h, scale_factor)
     }
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
@@ -502,17 +415,17 @@ impl RenderTask {
         RenderTask {
             children: vec![src_task_id],
             location: RenderTaskLocation::Dynamic(None, target_size),
             kind: RenderTaskKind::Scaling(target_kind),
             clear_mode: match target_kind {
                 RenderTargetKind::Color => ClearMode::Transparent,
                 RenderTargetKind::Alpha => ClearMode::One,
             },
-            pass_index: None,
+            saved_index: None,
         }
     }
 
     // Write (up to) 8 floats of data specific to the type
     // of render task that is provided to the GPU shaders
     // via a vertex texture.
     pub fn write_task_data(&self) -> RenderTaskData {
         // NOTE: The ordering and layout of these structures are
@@ -669,17 +582,16 @@ impl RenderTask {
     pub fn is_shared(&self) -> bool {
         match self.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) => false,
-
             RenderTaskKind::CacheMask(..) => true,
         }
     }
 
     #[cfg(feature = "debugger")]
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskTree) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
@@ -718,16 +630,29 @@ impl RenderTask {
             if tree[child_id].print_with(pt, tree) {
                 pt.add_item(format!("self: {:?}", child_id))
             }
         }
 
         pt.end_level();
         true
     }
+
+    /// Mark this render task for keeping the results alive up until the end of the frame.
+    pub fn mark_for_saving(&mut self) {
+        match self.location {
+            RenderTaskLocation::Fixed(..) |
+            RenderTaskLocation::Dynamic(..) => {
+                self.saved_index = Some(SavedTargetIndex::PENDING);
+            }
+            RenderTaskLocation::TextureCache(..) => {
+                panic!("Unable to mark a permanently cached task for saving!");
+            }
+        }
+    }
 }
 
 #[derive(Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -35,25 +35,25 @@ use device::{FileWatcherHandler, ShaderE
 use device::{ProgramCache, ReadPixelsFormat};
 use euclid::{rect, Transform3D};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use gpu_types::PrimitiveInstance;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
-use internal_types::{CacheTextureId, FastHashMap, RenderedDocument, ResultMsg, TextureUpdateOp};
-use internal_types::{DebugOutput, RenderPassIndex, RenderTargetInfo, TextureUpdateList, TextureUpdateSource};
+use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
+use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
+use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use picture::ContentOrigin;
 use prim_store::DeferredResolve;
-use profiler::{BackendProfileCounters, Profiler};
+use profiler::{BackendProfileCounters, FrameProfileCounters, Profiler};
 use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use query::{GpuProfiler, GpuTimer};
-use rayon::Configuration as ThreadPoolConfig;
-use rayon::ThreadPool;
+use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::{RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 #[cfg(feature = "debugger")]
 use serde_json;
 use std;
 use std::cmp;
@@ -78,16 +78,24 @@ pub const MAX_VERTEX_TEXTURE_WIDTH: usiz
 /// Enabling this toggle would force the GPU cache scattered texture to
 /// be resized every frame, which enables GPU debuggers to see if this
 /// is performed correctly.
 const GPU_CACHE_RESIZE_TEST: bool = false;
 
 /// Number of GPU blocks per UV rectangle provided for an image.
 pub const BLOCKS_PER_UV_RECT: usize = 2;
 
+const GPU_TAG_BRUSH_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
+    label: "B_RadialGradient",
+    color: debug_colors::LIGHTPINK,
+};
+const GPU_TAG_BRUSH_YUV_IMAGE: GpuProfileTag = GpuProfileTag {
+    label: "B_YuvImage",
+    color: debug_colors::DARKGREEN,
+};
 const GPU_TAG_BRUSH_MIXBLEND: GpuProfileTag = GpuProfileTag {
     label: "B_MixBlend",
     color: debug_colors::MAGENTA,
 };
 const GPU_TAG_BRUSH_BLEND: GpuProfileTag = GpuProfileTag {
     label: "B_Blend",
     color: debug_colors::LIGHTBLUE,
 };
@@ -126,20 +134,16 @@ const GPU_TAG_SETUP_TARGET: GpuProfileTa
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
     label: "data init",
     color: debug_colors::LIGHTGREY,
 };
 const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag {
     label: "Image",
     color: debug_colors::GREEN,
 };
-const GPU_TAG_PRIM_YUV_IMAGE: GpuProfileTag = GpuProfileTag {
-    label: "YuvImage",
-    color: debug_colors::DARKGREEN,
-};
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag {
     label: "HwComposite",
     color: debug_colors::DODGERBLUE,
 };
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag {
     label: "SplitComposite",
     color: debug_colors::DARKBLUE,
 };
@@ -150,20 +154,16 @@ const GPU_TAG_PRIM_TEXT_RUN: GpuProfileT
 const GPU_TAG_PRIM_GRADIENT: GpuProfileTag = GpuProfileTag {
     label: "Gradient",
     color: debug_colors::YELLOW,
 };
 const GPU_TAG_PRIM_ANGLE_GRADIENT: GpuProfileTag = GpuProfileTag {
     label: "AngleGradient",
     color: debug_colors::POWDERBLUE,
 };
-const GPU_TAG_PRIM_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
-    label: "RadialGradient",
-    color: debug_colors::LIGHTPINK,
-};
 const GPU_TAG_PRIM_BORDER_CORNER: GpuProfileTag = GpuProfileTag {
     label: "BorderCorner",
     color: debug_colors::DARKSLATEGREY,
 };
 const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag {
     label: "BorderEdge",
     color: debug_colors::LAVENDER,
 };
@@ -195,35 +195,31 @@ impl TransformBatchKind {
         match *self {
             TransformBatchKind::TextRun(..) => "TextRun",
             TransformBatchKind::Image(image_buffer_kind, ..) => match image_buffer_kind {
                 ImageBufferKind::Texture2D => "Image (2D)",
                 ImageBufferKind::TextureRect => "Image (Rect)",
                 ImageBufferKind::TextureExternal => "Image (External)",
                 ImageBufferKind::Texture2DArray => "Image (Array)",
             },
-            TransformBatchKind::YuvImage(..) => "YuvImage",
             TransformBatchKind::AlignedGradient => "AlignedGradient",
             TransformBatchKind::AngleGradient => "AngleGradient",
-            TransformBatchKind::RadialGradient => "RadialGradient",
             TransformBatchKind::BorderCorner => "BorderCorner",
             TransformBatchKind::BorderEdge => "BorderEdge",
         }
     }
 
     fn gpu_sampler_tag(&self) -> GpuProfileTag {
         match *self {
             TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN,
             TransformBatchKind::Image(..) => GPU_TAG_PRIM_IMAGE,
-            TransformBatchKind::YuvImage(..) => GPU_TAG_PRIM_YUV_IMAGE,
             TransformBatchKind::BorderCorner => GPU_TAG_PRIM_BORDER_CORNER,
             TransformBatchKind::BorderEdge => GPU_TAG_PRIM_BORDER_EDGE,
             TransformBatchKind::AlignedGradient => GPU_TAG_PRIM_GRADIENT,
             TransformBatchKind::AngleGradient => GPU_TAG_PRIM_ANGLE_GRADIENT,
-            TransformBatchKind::RadialGradient => GPU_TAG_PRIM_RADIAL_GRADIENT,
         }
     }
 }
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
@@ -232,16 +228,18 @@ impl BatchKind {
             BatchKind::Brush(kind) => {
                 match kind {
                     BrushBatchKind::Picture(..) => "Brush (Picture)",
                     BrushBatchKind::Solid => "Brush (Solid)",
                     BrushBatchKind::Line => "Brush (Line)",
                     BrushBatchKind::Image(..) => "Brush (Image)",
                     BrushBatchKind::Blend => "Brush (Blend)",
                     BrushBatchKind::MixBlend { .. } => "Brush (Composite)",
+                    BrushBatchKind::YuvImage(..) => "Brush (YuvImage)",
+                    BrushBatchKind::RadialGradient => "Brush (RadialGradient)",
                 }
             }
             BatchKind::Transformable(_, batch_kind) => batch_kind.debug_name(),
         }
     }
 
     fn gpu_sampler_tag(&self) -> GpuProfileTag {
         match *self {
@@ -250,16 +248,18 @@ impl BatchKind {
             BatchKind::Brush(kind) => {
                 match kind {
                     BrushBatchKind::Picture(..) => GPU_TAG_BRUSH_PICTURE,
                     BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
                     BrushBatchKind::Line => GPU_TAG_BRUSH_LINE,
                     BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
                     BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
                     BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
+                    BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
+                    BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT,
                 }
             }
             BatchKind::Transformable(_, batch_kind) => batch_kind.gpu_sampler_tag(),
         }
     }
 }
 
 bitflags! {
@@ -593,17 +593,21 @@ impl CpuProfile {
             frame_id,
             backend_time_ns,
             composite_time_ns,
             draw_calls,
         }
     }
 }
 
-struct RenderTargetPoolId(usize);
+struct ActiveTexture {
+    texture: Texture,
+    saved_index: Option<SavedTargetIndex>,
+    is_shared: bool,
+}
 
 struct SourceTextureResolver {
     /// A vector for fast resolves of texture cache IDs to
     /// native texture IDs. This maps to a free-list managed
     /// by the backend thread / texture cache. We free the
     /// texture memory associated with a TextureId when its
     /// texture cache ID is freed by the texture cache, but
     /// reuse the TextureId when the texture caches's free
@@ -615,22 +619,27 @@ struct SourceTextureResolver {
     external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>,
 
     /// A special 1x1 dummy cache texture used for shaders that expect to work
     /// with the cache but are actually running in the first pass
     /// when no target is yet provided as a cache texture input.
     dummy_cache_texture: Texture,
 
     /// The current cache textures.
-    cache_rgba8_texture: Option<Texture>,
-    cache_a8_texture: Option<Texture>,
-
-    pass_rgba8_textures: FastHashMap<RenderPassIndex, RenderTargetPoolId>,
-    pass_a8_textures: FastHashMap<RenderPassIndex, RenderTargetPoolId>,
-
+    cache_rgba8_texture: Option<ActiveTexture>,
+    cache_a8_texture: Option<ActiveTexture>,
+
+    /// An alpha texture shared between all passes.
+    //TODO: just use the standard texture saving logic instead.
+    shared_alpha_texture: Option<Texture>,
+
+    /// Saved cache textures that are to be re-used.
+    saved_textures: Vec<Texture>,
+
+    /// General pool of render targets.
     render_target_pool: Vec<Texture>,
 }
 
 impl SourceTextureResolver {
     fn new(device: &mut Device) -> SourceTextureResolver {
         let mut dummy_cache_texture = device
             .create_texture(TextureTarget::Array, ImageFormat::BGRA8);
         device.init_texture(
@@ -644,18 +653,18 @@ impl SourceTextureResolver {
         );
 
         SourceTextureResolver {
             cache_texture_map: Vec::new(),
             external_images: FastHashMap::default(),
             dummy_cache_texture,
             cache_a8_texture: None,
             cache_rgba8_texture: None,
-            pass_rgba8_textures: FastHashMap::default(),
-            pass_a8_textures: FastHashMap::default(),
+            shared_alpha_texture: None,
+            saved_textures: Vec::default(),
             render_target_pool: Vec::new(),
         }
     }
 
     fn deinit(self, device: &mut Device) {
         device.delete_texture(self.dummy_cache_texture);
 
         for texture in self.cache_texture_map {
@@ -665,125 +674,127 @@ impl SourceTextureResolver {
         for texture in self.render_target_pool {
             device.delete_texture(texture);
         }
     }
 
     fn begin_frame(&mut self) {
         assert!(self.cache_rgba8_texture.is_none());
         assert!(self.cache_a8_texture.is_none());
-
-        self.pass_rgba8_textures.clear();
-        self.pass_a8_textures.clear();
+        assert!(self.saved_textures.is_empty());
     }
 
-    fn end_frame(&mut self, pass_index: RenderPassIndex) {
+    fn end_frame(&mut self) {
         // return the cached targets to the pool
-        self.end_pass(None, None, pass_index)
+        self.end_pass(None, None);
+        // return the global alpha texture
+        self.render_target_pool.extend(self.shared_alpha_texture.take());
+        // return the saved targets as well
+        self.render_target_pool.extend(self.saved_textures.drain(..));
     }
 
     fn end_pass(
         &mut self,
-        a8_texture: Option<Texture>,
-        rgba8_texture: Option<Texture>,
-        pass_index: RenderPassIndex,
+        a8_texture: Option<ActiveTexture>,
+        rgba8_texture: Option<ActiveTexture>,
     ) {
         // If we have cache textures from previous pass, return them to the pool.
         // Also assign the pool index of those cache textures to last pass's index because this is
         // the result of last pass.
-        if let Some(texture) = self.cache_rgba8_texture.take() {
-            self.pass_rgba8_textures.insert(
-                RenderPassIndex(pass_index.0 - 1), RenderTargetPoolId(self.render_target_pool.len()));
-            self.render_target_pool.push(texture);
+        // Note: the order here is important, needs to match the logic in `RenderPass::build()`.
+        if let Some(at) = self.cache_rgba8_texture.take() {
+            assert!(!at.is_shared);
+            if let Some(index) = at.saved_index {
+                assert_eq!(self.saved_textures.len(), index.0);
+                self.saved_textures.push(at.texture);
+            } else {
+                self.render_target_pool.push(at.texture);
+            }
         }
-        if let Some(texture) = self.cache_a8_texture.take() {
-            self.pass_a8_textures.insert(
-                RenderPassIndex(pass_index.0 - 1), RenderTargetPoolId(self.render_target_pool.len()));
-            self.render_target_pool.push(texture);
+        if let Some(at) = self.cache_a8_texture.take() {
+            if let Some(index) = at.saved_index {
+                assert!(!at.is_shared);
+                assert_eq!(self.saved_textures.len(), index.0);
+                self.saved_textures.push(at.texture);
+            } else if at.is_shared {
+                assert!(self.shared_alpha_texture.is_none());
+                self.shared_alpha_texture = Some(at.texture);
+            } else {
+                self.render_target_pool.push(at.texture);
+            }
         }
 
         // We have another pass to process, make these textures available
         // as inputs to the next pass.
         self.cache_rgba8_texture = rgba8_texture;
         self.cache_a8_texture = a8_texture;
     }
 
     // Bind a source texture to the device.
     fn bind(&self, texture_id: &SourceTexture, sampler: TextureSampler, device: &mut Device) {
         match *texture_id {
             SourceTexture::Invalid => {}
             SourceTexture::CacheA8 => {
-                let texture = self.cache_a8_texture
-                    .as_ref()
-                    .unwrap_or(&self.dummy_cache_texture);
+                let texture = match self.cache_a8_texture {
+                    Some(ref at) => &at.texture,
+                    None => &self.dummy_cache_texture,
+                };
                 device.bind_texture(sampler, texture);
             }
             SourceTexture::CacheRGBA8 => {
-                let texture = self.cache_rgba8_texture
-                    .as_ref()
-                    .unwrap_or(&self.dummy_cache_texture);
+                let texture = match self.cache_rgba8_texture {
+                    Some(ref at) => &at.texture,
+                    None => &self.dummy_cache_texture,
+                };
                 device.bind_texture(sampler, texture);
             }
             SourceTexture::External(external_image) => {
                 let texture = self.external_images
                     .get(&(external_image.id, external_image.channel_index))
                     .expect(&format!("BUG: External image should be resolved by now"));
                 device.bind_external_texture(sampler, texture);
             }
             SourceTexture::TextureCache(index) => {
                 let texture = &self.cache_texture_map[index.0];
                 device.bind_texture(sampler, texture);
             }
-            SourceTexture::RenderTaskCacheRGBA8(pass_index) => {
-                let pool_index = self.pass_rgba8_textures
-                    .get(&pass_index)
-                    .expect("BUG: pass_index doesn't map to pool_index");
-                device.bind_texture(sampler, &self.render_target_pool[pool_index.0])
-            }
-            SourceTexture::RenderTaskCacheA8(pass_index) => {
-                let pool_index = self.pass_a8_textures
-                    .get(&pass_index)
-                    .expect("BUG: pass_index doesn't map to pool_index");
-                device.bind_texture(sampler, &self.render_target_pool[pool_index.0])
+            SourceTexture::RenderTaskCache(saved_index) => {
+                let texture = &self.saved_textures[saved_index.0];
+                device.bind_texture(sampler, texture)
             }
         }
     }
 
     // Get the real (OpenGL) texture ID for a given source texture.
     // For a texture cache texture, the IDs are stored in a vector
     // map for fast access.
     fn resolve(&self, texture_id: &SourceTexture) -> Option<&Texture> {
         match *texture_id {
             SourceTexture::Invalid => None,
             SourceTexture::CacheA8 => Some(
-                self.cache_a8_texture
-                    .as_ref()
-                    .unwrap_or(&self.dummy_cache_texture),
+                match self.cache_a8_texture {
+                    Some(ref at) => &at.texture,
+                    None => &self.dummy_cache_texture,
+                }
             ),
             SourceTexture::CacheRGBA8 => Some(
-                self.cache_rgba8_texture
-                    .as_ref()
-                    .unwrap_or(&self.dummy_cache_texture),
+                match self.cache_rgba8_texture {
+                    Some(ref at) => &at.texture,
+                    None => &self.dummy_cache_texture,
+                }
             ),
             SourceTexture::External(..) => {
                 panic!("BUG: External textures cannot be resolved, they can only be bound.");
             }
-            SourceTexture::TextureCache(index) => Some(&self.cache_texture_map[index.0]),
-            SourceTexture::RenderTaskCacheRGBA8(pass_index) => {
-                let pool_index = self.pass_rgba8_textures
-                    .get(&pass_index)
-                    .expect("BUG: pass_index doesn't map to pool_index");
-                Some(&self.render_target_pool[pool_index.0])
-            },
-            SourceTexture::RenderTaskCacheA8(pass_index) => {
-                let pool_index = self.pass_a8_textures
-                    .get(&pass_index)
-                    .expect("BUG: pass_index doesn't map to pool_index");
-                Some(&self.render_target_pool[pool_index.0])
-            },
+            SourceTexture::TextureCache(index) => {
+                Some(&self.cache_texture_map[index.0])
+            }
+            SourceTexture::RenderTaskCache(saved_index) => {
+                Some(&self.saved_textures[saved_index.0])
+            }
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[allow(dead_code)] // SubpixelVariableTextColor is not used at the moment.
@@ -1600,16 +1611,18 @@ pub struct Renderer {
     brush_picture_rgba8: BrushShader,
     brush_picture_rgba8_alpha_mask: BrushShader,
     brush_picture_a8: BrushShader,
     brush_solid: BrushShader,
     brush_line: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
+    brush_yuv_image: Vec<Option<BrushShader>>,
+    brush_radial_gradient: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     cs_clip_rectangle: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
     cs_clip_border: LazilyCompiledShader,
 
@@ -1618,22 +1631,20 @@ pub struct Renderer {
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
     ps_text_run: TextShader,
     ps_text_run_dual_source: TextShader,
     ps_image: Vec<Option<PrimitiveShader>>,
-    ps_yuv_image: Vec<Option<PrimitiveShader>>,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
-    ps_radial_gradient: PrimitiveShader,
 
     ps_hw_composite: LazilyCompiledShader,
     ps_split_composite: LazilyCompiledShader,
 
     max_texture_size: u32,
 
     max_recorded_profiles: usize,
     clear_color: Option<ColorF>,
@@ -1740,16 +1751,17 @@ impl Renderer {
         gl: Rc<gl::Gl>,
         notifier: Box<RenderNotifier>,
         mut options: RendererOptions,
     ) -> Result<(Renderer, RenderApiSender), RendererError> {
         let (api_tx, api_rx) = try!{ channel::msg_channel() };
         let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
+        let dithering_feature = ["DITHERING"];
 
         let debug_server = DebugServer::new(api_tx.clone());
 
         let file_watch_handler = FileWatcher {
             result_tx: result_tx.clone(),
             notifier: notifier.clone(),
         };
 
@@ -1856,16 +1868,27 @@ impl Renderer {
 
         let brush_picture_rgba8_alpha_mask = try!{
             BrushShader::new("brush_picture",
                              &mut device,
                              &["COLOR_TARGET_ALPHA_MASK"],
                              options.precache_shaders)
         };
 
+        let brush_radial_gradient = try!{
+            BrushShader::new("brush_radial_gradient",
+                             &mut device,
+                             if options.enable_dithering {
+                                &dithering_feature
+                             } else {
+                                &[]
+                             },
+                             options.precache_shaders)
+        };
+
         let cs_blur_a8 = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexArrayKind::Blur),
                                      "cs_blur",
                                       &["ALPHA_TARGET"],
                                       &mut device,
                                       options.precache_shaders)
         };
 
@@ -1947,20 +1970,20 @@ impl Renderer {
                 brush_image[buffer_kind] = Some(shader);
             }
             image_features.clear();
         }
 
         // All yuv_image configuration.
         let mut yuv_features = Vec::new();
         let yuv_shader_num = IMAGE_BUFFER_KINDS.len() * YUV_FORMATS.len() * YUV_COLOR_SPACES.len();
-        let mut ps_yuv_image: Vec<Option<PrimitiveShader>> = Vec::new();
+        let mut brush_yuv_image = Vec::new();
         // PrimitiveShader is not clonable. Use push() to initialize the vec.
         for _ in 0 .. yuv_shader_num {
-            ps_yuv_image.push(None);
+            brush_yuv_image.push(None);
         }
         for buffer_kind in 0 .. IMAGE_BUFFER_KINDS.len() {
             if IMAGE_BUFFER_KINDS[buffer_kind].has_platform_support(&gl_type) {
                 for format_kind in 0 .. YUV_FORMATS.len() {
                     for color_space_kind in 0 .. YUV_COLOR_SPACES.len() {
                         let feature_string = IMAGE_BUFFER_KINDS[buffer_kind].get_feature_string();
                         if feature_string != "" {
                             yuv_features.push(feature_string);
@@ -1971,27 +1994,27 @@ impl Renderer {
                         }
                         let feature_string =
                             YUV_COLOR_SPACES[color_space_kind].get_feature_string();
                         if feature_string != "" {
                             yuv_features.push(feature_string);
                         }
 
                         let shader = try!{
-                            PrimitiveShader::new("ps_yuv_image",
-                                                 &mut device,
-                                                 &yuv_features,
-                                                 options.precache_shaders)
+                            BrushShader::new("brush_yuv_image",
+                                             &mut device,
+                                             &yuv_features,
+                                             options.precache_shaders)
                         };
                         let index = Renderer::get_yuv_shader_index(
                             IMAGE_BUFFER_KINDS[buffer_kind],
                             YUV_FORMATS[format_kind],
                             YUV_COLOR_SPACES[color_space_kind],
                         );
-                        ps_yuv_image[index] = Some(shader);
+                        brush_yuv_image[index] = Some(shader);
                         yuv_features.clear();
                     }
                 }
             }
         }
 
         let ps_border_corner = try!{
             PrimitiveShader::new("ps_border_corner",
@@ -2002,18 +2025,16 @@ impl Renderer {
 
         let ps_border_edge = try!{
             PrimitiveShader::new("ps_border_edge",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
-        let dithering_feature = ["DITHERING"];
-
         let ps_gradient = try!{
             PrimitiveShader::new("ps_gradient",
                                  &mut device,
                                  if options.enable_dithering {
                                     &dithering_feature
                                  } else {
                                     &[]
                                  },
@@ -2026,27 +2047,16 @@ impl Renderer {
                                  if options.enable_dithering {
                                     &dithering_feature
                                  } else {
                                     &[]
                                  },
                                  options.precache_shaders)
         };
 
-        let ps_radial_gradient = try!{
-            PrimitiveShader::new("ps_radial_gradient",
-                                 &mut device,
-                                 if options.enable_dithering {
-                                    &dithering_feature
-                                 } else {
-                                    &[]
-                                 },
-                                 options.precache_shaders)
-        };
-
         let ps_hw_composite = try!{
             LazilyCompiledShader::new(ShaderKind::Primitive,
                                      "ps_hardware_composite",
                                      &[],
                                      &mut device,
                                      options.precache_shaders)
         };
 
@@ -2210,30 +2220,31 @@ impl Renderer {
         let recorder = options.recorder;
         let thread_listener = Arc::new(options.thread_listener);
         let thread_listener_for_rayon_start = thread_listener.clone();
         let thread_listener_for_rayon_end = thread_listener.clone();
         let workers = options
             .workers
             .take()
             .unwrap_or_else(|| {
-                let worker_config = ThreadPoolConfig::new()
+                let worker = ThreadPoolBuilder::new()
                     .thread_name(|idx|{ format!("WRWorker#{}", idx) })
                     .start_handler(move |idx| {
                         register_thread_with_profiler(format!("WRWorker#{}", idx));
                         if let Some(ref thread_listener) = *thread_listener_for_rayon_start {
                             thread_listener.thread_started(&format!("WRWorker#{}", idx));
                         }
                     })
                     .exit_handler(move |idx| {
                         if let Some(ref thread_listener) = *thread_listener_for_rayon_end {
                             thread_listener.thread_stopped(&format!("WRWorker#{}", idx));
                         }
-                    });
-                Arc::new(ThreadPool::new(worker_config).unwrap())
+                    })
+                    .build();
+                Arc::new(worker.unwrap())
             });
         let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let resource_cache = ResourceCache::new(
             texture_cache,
@@ -2285,28 +2296,28 @@ impl Renderer {
             brush_picture_rgba8,
             brush_picture_rgba8_alpha_mask,
             brush_picture_a8,
             brush_solid,
             brush_line,
             brush_image,
             brush_blend,
             brush_mix_blend,
+            brush_yuv_image,
+            brush_radial_gradient,
             cs_clip_rectangle,
             cs_clip_border,
             cs_clip_image,
             ps_text_run,
             ps_text_run_dual_source,
             ps_image,
-            ps_yuv_image,
             ps_border_corner,
             ps_border_edge,
             ps_gradient,
             ps_angle_gradient,
-            ps_radial_gradient,
             ps_hw_composite,
             ps_split_composite,
             debug: debug_renderer,
             debug_flags,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
             max_texture_size: max_texture_size,
@@ -2868,28 +2879,23 @@ impl Renderer {
                     };
                     self.device.bind_draw_target(None, None);
                     self.device.enable_depth_write();
                     self.device.clear_target(clear_color, clear_depth_value, None);
                     self.device.disable_depth_write();
                 }
             }
 
-            // Re-use whatever targets possible from the pool, before
-            // they get changed/re-allocated by the rendered frames.
-            for doc_with_id in &mut active_documents {
-                self.prepare_tile_frame(&mut doc_with_id.1.frame);
-            }
-
             #[cfg(feature = "replay")]
             self.texture_resolver.external_images.extend(
                 self.owned_external_images.iter().map(|(key, value)| (*key, value.clone()))
             );
 
             for &mut (_, RenderedDocument { ref mut frame, .. }) in &mut active_documents {
+                frame.profile_counters.reset_targets();
                 self.prepare_gpu_cache(frame);
                 assert!(frame.gpu_cache_frame_id <= self.gpu_cache_frame_id);
 
                 self.draw_tile_frame(
                     frame,
                     framebuffer_size,
                     clear_depth_value.is_some(),
                     cpu_frame_id,
@@ -3252,16 +3258,39 @@ impl Renderer {
                         self.brush_mix_blend.bind(
                             &mut self.device,
                             key.blend_mode,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
                     }
+                    BrushBatchKind::RadialGradient => {
+                        self.brush_radial_gradient.bind(
+                            &mut self.device,
+                            key.blend_mode,
+                            projection,
+                            0,
+                            &mut self.renderer_errors,
+                        );
+                    }
+                    BrushBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
+                        let shader_index =
+                            Renderer::get_yuv_shader_index(image_buffer_kind, format, color_space);
+                        self.brush_yuv_image[shader_index]
+                            .as_mut()
+                            .expect("Unsupported YUV shader kind")
+                            .bind(
+                                &mut self.device,
+                                key.blend_mode,
+                                projection,
+                                0,
+                                &mut self.renderer_errors,
+                            );
+                    }
                 }
             }
             BatchKind::Transformable(transform_kind, batch_kind) => match batch_kind {
                 TransformBatchKind::TextRun(..) => {
                     unreachable!("bug: text batches are special cased");
                 }
                 TransformBatchKind::Image(image_buffer_kind) => {
                     self.ps_image[image_buffer_kind as usize]
@@ -3270,30 +3299,16 @@ impl Renderer {
                         .bind(
                             &mut self.device,
                             transform_kind,
                             projection,
                             0,
                             &mut self.renderer_errors,
                         );
                 }
-                TransformBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
-                    let shader_index =
-                        Renderer::get_yuv_shader_index(image_buffer_kind, format, color_space);
-                    self.ps_yuv_image[shader_index]
-                        .as_mut()
-                        .expect("Unsupported YUV shader kind")
-                        .bind(
-                            &mut self.device,
-                            transform_kind,
-                            projection,
-                            0,
-                            &mut self.renderer_errors,
-                        );
-                }
                 TransformBatchKind::BorderCorner => {
                     self.ps_border_corner.bind(
                         &mut self.device,
                         transform_kind,
                         projection,
                         0,
                         &mut self.renderer_errors,
                     );
@@ -3320,25 +3335,16 @@ impl Renderer {
                     self.ps_angle_gradient.bind(
                         &mut self.device,
                         transform_kind,
                         projection,
                         0,
                         &mut self.renderer_errors,
                     );
                 }
-                TransformBatchKind::RadialGradient => {
-                    self.ps_radial_gradient.bind(
-                        &mut self.device,
-                        transform_kind,
-                        projection,
-                        0,
-                        &mut self.renderer_errors,
-                    );
-                }
             },
         };
 
         // Handle special case readback for composites.
         if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = key.kind {
             if scissor_rect.is_some() {
                 self.device.disable_scissor();
             }
@@ -4245,99 +4251,89 @@ impl Renderer {
                 .expect("Found external image, but no handler set!");
 
             for (ext_data, _) in self.texture_resolver.external_images.drain() {
                 handler.unlock(ext_data.0, ext_data.1);
             }
         }
     }
 
-    fn prepare_target_list<T: RenderTarget>(
+    fn allocate_target_texture<T: RenderTarget>(
         &mut self,
         list: &mut RenderTargetList<T>,
-        perfect_only: bool,
-    ) {
+        counters: &mut FrameProfileCounters,
+        frame_id: FrameId,
+    ) -> Option<ActiveTexture> {
         debug_assert_ne!(list.max_size, DeviceUintSize::zero());
         if list.targets.is_empty() {
-            return;
+            return None
         }
-        let mut texture = if perfect_only {
-            debug_assert!(list.texture.is_none());
-
-            let selector = TargetSelector {
-                size: list.max_size,
-                num_layers: list.targets.len() as _,
-                format: list.format,
-            };
-            let index = self.texture_resolver.render_target_pool
+
+        counters.targets_used.inc();
+
+        // First, try finding a perfect match
+        let selector = TargetSelector {
+            size: list.max_size,
+            num_layers: list.targets.len() as _,
+            format: list.format,
+        };
+        let mut index = self.texture_resolver.render_target_pool
+            .iter()
+            .position(|texture| {
+                //TODO: re-use a part of a larger target, if available
+                selector == TargetSelector {
+                    size: texture.get_dimensions(),
+                    num_layers: texture.get_render_target_layer_count(),
+                    format: texture.get_format(),
+                }
+            });
+
+        // Next, try at least finding a matching format
+        if index.is_none() {
+            counters.targets_changed.inc();
+            index = self.texture_resolver.render_target_pool
                 .iter()
-                .position(|texture| {
-                    selector == TargetSelector {
-                        size: texture.get_dimensions(),
-                        num_layers: texture.get_render_target_layer_count(),
-                        format: texture.get_format(),
-                    }
-                });
-            match index {
-                Some(pos) => self.texture_resolver.render_target_pool.swap_remove(pos),
-                None => return,
+                .position(|texture| texture.get_format() == list.format && !texture.used_in_frame(frame_id));
+        }
+
+        let mut texture = match index {
+            Some(pos) => {
+                self.texture_resolver.render_target_pool.swap_remove(pos)
             }
-        } else {
-            if list.texture.is_some() {
-                list.check_ready();
-                return
-            }
-            let index = self.texture_resolver.render_target_pool
-                .iter()
-                .position(|texture| texture.get_format() == list.format);
-            match index {
-                Some(pos) => self.texture_resolver.render_target_pool.swap_remove(pos),
-                None => self.device.create_texture(TextureTarget::Array, list.format),
+            None => {
+                counters.targets_created.inc();
+                // finally, give up and create a new one
+                self.device.create_texture(TextureTarget::Array, list.format)
             }
         };
 
         self.device.init_texture(
             &mut texture,
             list.max_size.width,
             list.max_size.height,
             TextureFilter::Linear,
             Some(RenderTargetInfo {
                 has_depth: list.needs_depth(),
             }),
             list.targets.len() as _,
             None,
         );
-        list.texture = Some(texture);
-        list.check_ready();
-    }
-
-    fn prepare_tile_frame(&mut self, frame: &mut Frame) {
-        // Init textures and render targets to match this scene.
-        // First pass grabs all the perfectly matching targets from the pool.
-        for pass in &mut frame.passes {
-            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color, .. } = pass.kind {
-                self.prepare_target_list(alpha, true);
-                self.prepare_target_list(color, true);
-            }
-        }
+
+        list.check_ready(&texture);
+        Some(ActiveTexture {
+            texture,
+            saved_index: list.saved_index.clone(),
+            is_shared: list.is_shared,
+        })
     }
 
     fn bind_frame_data(&mut self, frame: &mut Frame) {
         let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_DATA);
         self.device.set_device_pixel_ratio(frame.device_pixel_ratio);
 
-        // Some of the textures are already assigned by `prepare_frame`.
-        // Now re-allocate the space for the rest of the target textures.
-        for pass in &mut frame.passes {
-            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color, .. } = pass.kind {
-                self.prepare_target_list(alpha, false);
-                self.prepare_target_list(color, false);
-            }
-        }
-
         self.node_data_texture.update(&mut self.device, &mut frame.node_data);
         self.device.bind_texture(TextureSampler::ClipScrollNodes, &self.node_data_texture.texture);
 
         self.local_clip_rects_texture.update(
             &mut self.device,
             &mut frame.clip_chain_local_clip_rects
         );
         self.device.bind_texture(
@@ -4419,18 +4415,18 @@ impl Renderer {
                             frame_id,
                             stats,
                         );
                     }
 
                     (None, None)
                 }
                 RenderPassKind::OffScreen { ref mut alpha, ref mut color, ref mut texture_cache } => {
-                    alpha.check_ready();
-                    color.check_ready();
+                    let alpha_tex = self.allocate_target_texture(alpha, &mut frame.profile_counters, frame_id);
+                    let color_tex = self.allocate_target_texture(color, &mut frame.profile_counters, frame_id);
 
                     // If this frame has already been drawn, then any texture
                     // cache targets have already been updated and can be
                     // skipped this time.
                     if !frame.has_been_rendered {
                         for (&(texture_id, target_index), target) in texture_cache {
                             self.draw_texture_cache_target(
                                 &texture_id,
@@ -4450,17 +4446,17 @@ impl Renderer {
                             alpha.max_size.width as f32,
                             0.0,
                             alpha.max_size.height as f32,
                             ORTHO_NEAR_PLANE,
                             ORTHO_FAR_PLANE,
                         );
 
                         self.draw_alpha_target(
-                            (alpha.texture.as_ref().unwrap(), target_index as i32),
+                            (&alpha_tex.as_ref().unwrap().texture, target_index as i32),
                             target,
                             alpha.max_size,
                             &projection,
                             &frame.render_tasks,
                             stats,
                         );
                     }
 
@@ -4472,52 +4468,46 @@ impl Renderer {
                             color.max_size.width as f32,
                             0.0,
                             color.max_size.height as f32,
                             ORTHO_NEAR_PLANE,
                             ORTHO_FAR_PLANE,
                         );
 
                         self.draw_color_target(
-                            Some((color.texture.as_ref().unwrap(), target_index as i32)),
+                            Some((&color_tex.as_ref().unwrap().texture, target_index as i32)),
                             target,
                             frame.inner_rect,
                             color.max_size,
                             false,
                             Some([0.0, 0.0, 0.0, 0.0]),
                             &frame.render_tasks,
                             &projection,
                             frame_id,
                             stats,
                         );
                     }
 
-                    (alpha.texture.take(), color.texture.take())
+                    (alpha_tex, color_tex)
                 }
             };
 
+            //Note: the `end_pass` will make sure this texture is not recycled this frame
+            if let Some(ActiveTexture { ref texture, is_shared: true, .. }) = cur_alpha {
+                self.device
+                    .bind_texture(TextureSampler::SharedCacheA8, texture);
+            }
+
             self.texture_resolver.end_pass(
                 cur_alpha,
                 cur_color,
-                RenderPassIndex(pass_index),
             );
-
-            // After completing the first pass, make the A8 target available as an
-            // input to any subsequent passes.
-            if pass_index == 0 {
-                if let Some(shared_alpha_texture) =
-                    self.texture_resolver.resolve(&SourceTexture::CacheA8)
-                {
-                    self.device
-                        .bind_texture(TextureSampler::SharedCacheA8, shared_alpha_texture);
-                }
-            }
         }
 
-        self.texture_resolver.end_frame(RenderPassIndex(frame.passes.len()));
+        self.texture_resolver.end_frame();
         if let Some(framebuffer_size) = framebuffer_size {
             self.draw_render_target_debug(framebuffer_size);
             self.draw_texture_cache_debug(framebuffer_size);
         }
         self.draw_epoch_debug();
 
         // Garbage collect any frame outputs that weren't used this frame.
         let device = &mut self.device;
@@ -4745,44 +4735,44 @@ impl Renderer {
         self.brush_mask_corner.deinit(&mut self.device);
         self.brush_picture_rgba8.deinit(&mut self.device);
         self.brush_picture_rgba8_alpha_mask.deinit(&mut self.device);
         self.brush_picture_a8.deinit(&mut self.device);
         self.brush_solid.deinit(&mut self.device);
         self.brush_line.deinit(&mut self.device);
         self.brush_blend.deinit(&mut self.device);
         self.brush_mix_blend.deinit(&mut self.device);
+        self.brush_radial_gradient.deinit(&mut self.device);
         self.cs_clip_rectangle.deinit(&mut self.device);
         self.cs_clip_image.deinit(&mut self.device);
         self.cs_clip_border.deinit(&mut self.device);
         self.ps_text_run.deinit(&mut self.device);
         self.ps_text_run_dual_source.deinit(&mut self.device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
         }
         for shader in self.ps_image {
             if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
         }
-        for shader in self.ps_yuv_image {
+        for shader in self.brush_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_hw_composite.deinit(&mut self.device);
         self.ps_split_composite.deinit(&mut self.device);
         #[cfg(feature = "capture")]
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -192,20 +192,21 @@ impl FilterOpHelpers for FilterOp {
             FilterOp::Invert(amount) => amount == 0.0,
             FilterOp::Opacity(_, amount) => amount >= 1.0,
             FilterOp::Saturate(amount) => amount == 1.0,
             FilterOp::Sepia(amount) => amount == 0.0,
             FilterOp::DropShadow(offset, blur, _) => {
                 offset.x == 0.0 && offset.y == 0.0 && blur == 0.0
             },
             FilterOp::ColorMatrix(matrix) => {
-                matrix == [1.0, 0.0, 0.0, 0.0, 0.0,
-                           0.0, 1.0, 0.0, 0.0, 0.0,
-                           0.0, 0.0, 1.0, 0.0, 0.0,
-                           0.0, 0.0, 0.0, 1.0, 0.0]
+                matrix == [1.0, 0.0, 0.0, 0.0,
+                           0.0, 1.0, 0.0, 0.0,
+                           0.0, 0.0, 1.0, 0.0,
+                           0.0, 0.0, 0.0, 1.0,
+                           0.0, 0.0, 0.0, 0.0]
             }
         }
     }
 }
 
 pub trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -6,20 +6,20 @@ use api::{ClipId, ColorF, DeviceIntPoint
 use api::{DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentLayer, FilterOp, ImageFormat};
 use api::{LayerRect, MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree};
 use device::{FrameId, Texture};
 use gpu_cache::{GpuCache};
-use gpu_types::{BlurDirection, BlurInstance, BrushInstance, ClipChainRectIndex};
+use gpu_types::{BlurDirection, BlurInstance, BrushFlags, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex};
 use gpu_types::{PrimitiveInstance};
-use internal_types::{FastHashMap, RenderPassIndex, SourceTexture};
+use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureKind};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, EdgeAaSegmentMask};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32};
@@ -132,41 +132,46 @@ pub enum RenderTargetKind {
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTargetList<T> {
     screen_size: DeviceIntSize,
     pub format: ImageFormat,
     pub max_size: DeviceUintSize,
     pub targets: Vec<T>,
-    #[cfg_attr(feature = "serde", serde(skip))]
-    pub texture: Option<Texture>,
+    pub saved_index: Option<SavedTargetIndex>,
+    pub is_shared: bool,
 }
 
 impl<T: RenderTarget> RenderTargetList<T> {
     fn new(
         screen_size: DeviceIntSize,
         format: ImageFormat,
     ) -> Self {
         RenderTargetList {
             screen_size,
             format,
             max_size: DeviceUintSize::new(MIN_TARGET_SIZE, MIN_TARGET_SIZE),
             targets: Vec::new(),
-            texture: None,
+            saved_index: None,
+            is_shared: false,
         }
     }
 
     fn build(
         &mut self,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
+        saved_index: Option<SavedTargetIndex>,
     ) {
+        debug_assert_eq!(None, self.saved_index);
+        self.saved_index = saved_index;
+
         for target in &mut self.targets {
             target.build(ctx, gpu_cache, render_tasks, deferred_resolves);
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
@@ -209,30 +214,23 @@ impl<T: RenderTarget> RenderTargetList<T
 
         (origin, RenderTargetIndex(self.targets.len() - 1))
     }
 
     pub fn needs_depth(&self) -> bool {
         self.targets.iter().any(|target| target.needs_depth())
     }
 
-    pub fn check_ready(&self) {
-        match self.texture {
-            Some(ref t) => {
-                assert_eq!(t.get_dimensions(), self.max_size);
-                assert_eq!(t.get_format(), self.format);
-                assert_eq!(t.get_render_target_layer_count(), self.targets.len());
-                assert_eq!(t.get_layer_count() as usize, self.targets.len());
-                assert_eq!(t.has_depth(), t.get_rt_info().unwrap().has_depth);
-                assert_eq!(t.has_depth(), self.needs_depth());
-            }
-            None => {
-                assert!(self.targets.is_empty())
-            }
-        }
+    pub fn check_ready(&self, t: &Texture) {
+        assert_eq!(t.get_dimensions(), self.max_size);
+        assert_eq!(t.get_format(), self.format);
+        assert_eq!(t.get_render_target_layer_count(), self.targets.len());
+        assert_eq!(t.get_layer_count() as usize, self.targets.len());
+        assert_eq!(t.has_depth(), t.get_rt_info().unwrap().has_depth);
+        assert_eq!(t.has_depth(), self.needs_depth());
     }
 }
 
 /// Frame output information for a given pipeline ID.
 /// Storing the task ID allows the renderer to find
 /// the target rect within the render target that this
 /// pipeline exists at.
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -584,25 +582,28 @@ impl RenderTarget for AlphaRenderTarget 
                                             //           tasks support clip masks and
                                             //           transform primitives, these
                                             //           will need to be filled out!
                                             clip_chain_rect_index: ClipChainRectIndex(0),
                                             scroll_id: ClipScrollNodeIndex(0),
                                             clip_task_address: RenderTaskAddress(0),
                                             z: 0,
                                             segment_index: 0,
+                                            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             user_data: [0; 3],
                                         };
                                         let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
                                         let batch = match brush.kind {
                                             BrushKind::Solid { .. } |
                                             BrushKind::Clear |
                                             BrushKind::Picture |
                                             BrushKind::Line { .. } |
+                                            BrushKind::YuvImage { .. } |
+                                            BrushKind::RadialGradient { .. } |
                                             BrushKind::Image { .. } => {
                                                 unreachable!("bug: unexpected brush here");
                                             }
                                             BrushKind::Mask { ref kind, .. } => {
                                                 match *kind {
                                                     BrushMaskKind::Corner(..) => &mut self.brush_mask_corners,
                                                     BrushMaskKind::RoundedRect(..) => &mut self.brush_mask_rounded_rects,
                                                 }
@@ -779,47 +780,67 @@ impl RenderPass {
 
     pub fn build(
         &mut self,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         clip_store: &ClipStore,
-        pass_index: RenderPassIndex,
     ) {
         profile_scope!("RenderPass::build");
 
         match self.kind {
             RenderPassKind::MainFramebuffer(ref mut target) => {
                 for &task_id in &self.tasks {
                     assert_eq!(render_tasks[task_id].target_kind(), RenderTargetKind::Color);
-                    render_tasks[task_id].pass_index = Some(pass_index);
                     target.add_task(
                         task_id,
                         ctx,
                         gpu_cache,
                         render_tasks,
                         clip_store,
                         deferred_resolves,
                     );
                 }
                 target.build(ctx, gpu_cache, render_tasks, deferred_resolves);
             }
             RenderPassKind::OffScreen { ref mut color, ref mut alpha, ref mut texture_cache } => {
+                let is_shared_alpha = self.tasks.iter().any(|&task_id| {
+                    match render_tasks[task_id].kind {
+                        RenderTaskKind::CacheMask(..) => true,
+                        _ => false,
+                    }
+                });
+                let saved_color = if self.tasks.iter().any(|&task_id| {
+                    let t = &render_tasks[task_id];
+                    t.target_kind() == RenderTargetKind::Color && t.saved_index.is_some()
+                }) {
+                    Some(render_tasks.save_target())
+                } else {
+                    None
+                };
+                let saved_alpha = if self.tasks.iter().any(|&task_id| {
+                    let t = &render_tasks[task_id];
+                    t.target_kind() == RenderTargetKind::Alpha && t.saved_index.is_some()
+                }) {
+                    Some(render_tasks.save_target())
+                } else {
+                    None
+                };
+
                 // Step through each task, adding to batches as appropriate.
                 for &task_id in &self.tasks {
                     let (target_kind, texture_target) = {
                         let task = &mut render_tasks[task_id];
-                        task.pass_index = Some(pass_index);
                         let target_kind = task.target_kind();
 
                         // Find a target to assign this task to, or create a new
                         // one if required.
-                        match task.location {
+                        let (target_kind, texture_target) = match task.location {
                             RenderTaskLocation::TextureCache(texture_id, layer, _) => {
                                 // TODO(gw): When we support caching color items, we will
                                 //           need to calculate that here to get the
                                 //           correct target kind.
                                 (RenderTargetKind::Alpha, Some((texture_id, layer)))
                             }
                             RenderTaskLocation::Fixed(..) => {
                                 (RenderTargetKind::Color, None)
@@ -829,17 +850,28 @@ impl RenderPass {
                                 let (alloc_origin, target_index) =  match target_kind {
                                     RenderTargetKind::Color => color.allocate(alloc_size),
                                     RenderTargetKind::Alpha => alpha.allocate(alloc_size),
                                 };
                                 *origin = Some((alloc_origin.to_i32(), target_index));
 
                                 (target_kind, None)
                             }
+                        };
+
+                        // Replace the pending saved index with a real one
+                        if let Some(index) = task.saved_index {
+                            assert_eq!(index, SavedTargetIndex::PENDING);
+                            task.saved_index = match target_kind {
+                                RenderTargetKind::Color => saved_color,
+                                RenderTargetKind::Alpha => saved_alpha,
+                            };
                         }
+
+                        (target_kind, texture_target)
                     };
 
                     match texture_target {
                         Some(texture_target) => {
                             let texture = texture_cache
                                 .entry(texture_target)
                                 .or_insert(
                                     TextureCacheRenderTarget::new(None, DeviceIntSize::zero())
@@ -864,18 +896,19 @@ impl RenderPass {
                                     clip_store,
                                     deferred_resolves,
                                 ),
                             }
                         }
                     }
                 }
 
-                color.build(ctx, gpu_cache, render_tasks, deferred_resolves);
-                alpha.build(ctx, gpu_cache, render_tasks, deferred_resolves);
+                color.build(ctx, gpu_cache, render_tasks, deferred_resolves, saved_color);
+                alpha.build(ctx, gpu_cache, render_tasks, deferred_resolves, saved_alpha);
+                alpha.is_shared = is_shared_alpha;
             }
         }
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -58,41 +58,37 @@ const SHADERS: &[Shader] = &[
         name: "ps_gradient",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_angle_gradient",
         features: PRIM_FEATURES,
     },
     Shader {
-        name: "ps_radial_gradient",
-        features: PRIM_FEATURES,
-    },
-    Shader {
         name: "ps_hardware_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_image",
         features: PRIM_FEATURES,
     },
     Shader {
-        name: "ps_yuv_image",
-        features: PRIM_FEATURES,
-    },
-    Shader {
         name: "ps_text_run",
         features: PRIM_FEATURES,
     },
     // Brush shaders
     Shader {
+        name: "brush_yuv_image",
+        features: &["", "YUV_NV12", "YUV_PLANAR", "YUV_INTERLEAVED"],
+    },
+    Shader {
         name: "brush_mask",
         features: &[],
     },
     Shader {
         name: "brush_solid",
         features: &[],
     },
     Shader {
@@ -106,16 +102,20 @@ const SHADERS: &[Shader] = &[
     Shader {
         name: "brush_composite",
         features: &[],
     },
     Shader {
         name: "brush_line",
         features: &[],
     },
+    Shader {
+        name: "brush_radial_gradient",
+        features: &[],
+    },
 ];
 
 const VERSION_STRING: &str = "#version 300 es\n";
 
 #[test]
 fn validate_shaders() {
     angle::hl::initialize().unwrap();
 
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -4,17 +4,17 @@
 
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use std::path::PathBuf;
 use std::u32;
-use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceUintRect};
+use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect};
 use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
 use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphKey, ImageData};
 use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
 use {NativeFontHandle, WorldPoint};
 
 pub type TileSize = u16;
 /// Documents are rendered in the ascending order of their associated layer values.
 pub type DocumentLayer = i8;
@@ -249,17 +249,17 @@ impl Transaction {
         phase: ScrollEventPhase,
     ) {
         self.ops.push(DocumentMsg::Scroll(scroll_location, cursor, phase));
     }
 
     pub fn scroll_node_with_id(
         &mut self,
         origin: LayoutPoint,
-        id: ScrollNodeIdType,
+        id: ExternalScrollId,
         clamp: ScrollClamping,
     ) {
         self.ops.push(DocumentMsg::ScrollNodeWithId(origin, id, clamp));
     }
 
     pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) {
         self.ops.push(DocumentMsg::SetPageZoom(page_zoom));
     }
@@ -379,47 +379,23 @@ pub enum DocumentMsg {
     RemovePipeline(PipelineId),
     EnableFrameOutput(PipelineId, bool),
     SetWindowParameters {
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
         device_pixel_ratio: f32,
     },
     Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
-    ScrollNodeWithId(LayoutPoint, ScrollNodeIdType, ScrollClamping),
+    ScrollNodeWithId(LayoutPoint, ExternalScrollId, ScrollClamping),
     TickScrollingBounce,
     GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     GenerateFrame,
     UpdateDynamicProperties(DynamicProperties),
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub enum ScrollNodeIdType {
-    ExternalScrollId(ExternalScrollId),
-    ClipId(ClipId),
-}
-
-impl From<ExternalScrollId> for ScrollNodeIdType {
-    fn from(id: ExternalScrollId) -> Self {
-        ScrollNodeIdType::ExternalScrollId(id)
-    }
-}
-
-impl From<ClipId> for ScrollNodeIdType {
-    fn from(id: ClipId) -> Self {
-        ScrollNodeIdType::ClipId(id)
-    }
-}
-
-impl<'a> From<&'a ClipId> for ScrollNodeIdType {
-    fn from(id: &'a ClipId) -> Self {
-        ScrollNodeIdType::ClipId(*id)
-    }
-}
-
 impl fmt::Debug for DocumentMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList",
             DocumentMsg::HitTest(..) => "DocumentMsg::HitTest",
             DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
             DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
             DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -596,19 +596,19 @@ impl YuvFormat {
             YuvFormat::NV12 => 2,
             YuvFormat::PlanarYCbCr => 3,
             YuvFormat::InterleavedYCbCr => 1,
         }
     }
 
     pub fn get_feature_string(&self) -> &'static str {
         match *self {
-            YuvFormat::NV12 => "NV12",
-            YuvFormat::PlanarYCbCr => "",
-            YuvFormat::InterleavedYCbCr => "INTERLEAVED_Y_CB_CR",
+            YuvFormat::NV12 => "YUV_NV12",
+            YuvFormat::PlanarYCbCr => "YUV_PLANAR",
+            YuvFormat::InterleavedYCbCr => "YUV_INTERLEAVED",
         }
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
@@ -644,16 +644,32 @@ impl LocalClip {
                 ComplexClipRegion {
                     rect: complex.rect.translate(offset),
                     radii: complex.radii,
                     mode: complex.mode,
                 },
             ),
         }
     }
+
+    pub fn clip_by(&self, rect: &LayoutRect) -> LocalClip {
+        match *self {
+            LocalClip::Rect(clip_rect) => {
+                LocalClip::Rect(
+                    clip_rect.intersection(rect).unwrap_or(LayoutRect::zero())
+                )
+            }
+            LocalClip::RoundedRect(clip_rect, complex) => {
+                LocalClip::RoundedRect(
+                    clip_rect.intersection(rect).unwrap_or(LayoutRect::zero()),
+                    complex,
+                )
+            }
+        }
+    }
 }
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub enum ClipMode {
     Clip,    // Pixels inside the region are visible.
     ClipOut, // Pixels outside the region are visible.
 }
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -1,16 +1,16 @@
 [package]
 name = "webrender_bindings"
 version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
-rayon = "0.8"
+rayon = "1"
 thread_profiler = "0.1.1"
 euclid = "0.16"
 app_units = "0.6"
 gleam = "0.4.20"
 log = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-4af31b8aa79d5a1f3c0242dea6be4876c71075c5
+e8d2ffb404a85651fe08a6d09abbece9bd2b9182
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -497,17 +497,17 @@ impl Wrench {
     pub fn begin_frame(&mut self) {
         self.frame_start_sender.push(time::SteadyTime::now());
     }
 
     pub fn send_lists(
         &mut self,
         frame_number: u32,
         display_lists: Vec<(PipelineId, LayerSize, BuiltDisplayList)>,
-        scroll_offsets: &HashMap<ClipId, LayerPoint>,
+        scroll_offsets: &HashMap<ExternalScrollId, LayerPoint>,
     ) {
         let root_background_color = Some(ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         let mut txn = Transaction::new();
         for display_list in display_lists {
             txn.set_display_list(
                 Epoch(frame_number),
                 root_background_color,
@@ -518,17 +518,17 @@ impl Wrench {
         }
         // TODO(nical) - Need to separate the set_display_list from the scrolling
         // operations into separate transactions for mysterious -but probably related
         // to the other comment below- reasons.
         self.api.send_transaction(self.document_id, txn);
 
         let mut txn = Transaction::new();
         for (id, offset) in scroll_offsets {
-            txn.scroll_node_with_id(*offset, id.into(), ScrollClamping::NoClamping);
+            txn.scroll_node_with_id(*offset, *id, ScrollClamping::NoClamping);
         }
         // TODO(nical) - Wrench does not notify frames when there was scrolling
         // in the transaction (See RenderNotifier implementations). If we don't
         // generate a frame after scrolling, wrench just stops and some tests
         // will time out.
         // I suppose this was to avoid taking the snapshot after scrolling if
         // there was other updates coming in a subsequent messages but it's very
         // error-prone with transactions.
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -188,17 +188,17 @@ pub struct YamlFrameReader {
 
     include_only: Vec<String>,
 
     watch_source: bool,
     list_resources: bool,
 
     /// A HashMap of offsets which specify what scroll offsets particular
     /// scroll layers should be initialized with.
-    scroll_offsets: HashMap<ClipId, LayerPoint>,
+    scroll_offsets: HashMap<ExternalScrollId, LayerPoint>,
 
     image_map: HashMap<(PathBuf, Option<i64>), (ImageKey, LayoutSize)>,
 
     fonts: HashMap<FontDescriptor, FontKey>,
     font_instances: HashMap<(FontKey, Au, FontInstanceFlags), FontInstanceKey>,
     font_render_mode: Option<FontRenderMode>,
 
     /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
@@ -1268,17 +1268,22 @@ impl YamlFrameReader {
     }
 
     pub fn add_display_list_items_from_yaml(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
-        let full_clip = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
+        // A very large number (but safely far away from finite limits of f32)
+        let big_number = 1.0e30;
+        // A rect that should in practical terms serve as a no-op for clipping
+        let full_clip = LayoutRect::new(
+            LayoutPoint::new(-big_number / 2.0, -big_number / 2.0),
+            LayoutSize::new(big_number, big_number));
 
         for item in yaml.as_vec().unwrap() {
             // an explicit type can be skipped with some shorthand
             let item_type = if !item["rect"].is_badvalue() {
                 "rect"
             } else if !item["image"].is_badvalue() {
                 "image"
             } else if !item["text"].is_badvalue() {
@@ -1357,33 +1362,34 @@ impl YamlFrameReader {
         let content_size = yaml["content-size"].as_size().unwrap_or(clip_rect.size);
         let content_rect = LayerRect::new(clip_rect.origin, content_size);
 
         let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
 
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
-        let external_id = numeric_id.map(|id| ExternalScrollId(id as u64, dl.pipeline_id));
+        let external_id =  yaml["scroll-offset"].as_point().map(|size| {
+            let id = ExternalScrollId((self.scroll_offsets.len() + 1) as u64, dl.pipeline_id);
+            self.scroll_offsets.insert(id, LayerPoint::new(size.x, size.y));
+            id
+        });
+
         let real_id = dl.define_scroll_frame(
             external_id,
             content_rect,
             clip_rect,
             complex_clips,
             image_mask,
             ScrollSensitivity::Script,
         );
         if let Some(numeric_id) = numeric_id {
             self.clip_id_map.insert(numeric_id, real_id);
         }
 
-        if let Some(size) = yaml["scroll-offset"].as_point() {
-            self.scroll_offsets.insert(real_id, LayerPoint::new(size.x, size.y));
-        }
-
         if !yaml["items"].is_badvalue() {
             dl.push_clip_id(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
             dl.pop_clip_id();
         }
     }
 
     pub fn handle_sticky_frame(
@@ -1537,19 +1543,18 @@ impl YamlFrameReader {
             .as_mix_blend_mode()
             .unwrap_or(MixBlendMode::Normal);
         let scroll_policy = yaml["scroll-policy"]
             .as_scroll_policy()
             .unwrap_or(ScrollPolicy::Scrollable);
 
         if is_root {
             if let Some(size) = yaml["scroll-offset"].as_point() {
-                let id = ClipId::root_scroll_node(dl.pipeline_id);
-                self.scroll_offsets
-                    .insert(id, LayerPoint::new(size.x, size.y));
+                let external_id = ExternalScrollId(0, dl.pipeline_id);
+                self.scroll_offsets.insert(external_id, LayerPoint::new(size.x, size.y));
             }
         }
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
         info.rect = bounds;
         info.local_clip = LocalClip::from(bounds);
 
         dl.push_stacking_context(