Bug 1396972 - Update webrender to commit fe83b424e7b8bce3d3661c3561cfd58a6cd186fe. r=jrmuizel
authorRyan Hunt <rhunt@eqrion.net>
Wed, 06 Sep 2017 14:30:08 -0500
changeset 379436 9a7fde903010acbe233ffddd9a9f11dcb0ab69b9
parent 379435 83228a8eab3304bf076adae195c971851ca40601
child 379437 f63826a2f12bdffdb6dbb6f8cb94370bcb8db85d
push id50642
push userarchaeopteryx@coole-files.de
push dateThu, 07 Sep 2017 10:41:07 +0000
treeherderautoland@bd0ce93776fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1396972
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1396972 - Update webrender to commit fe83b424e7b8bce3d3661c3561cfd58a6cd186fe. r=jrmuizel MozReview-Commit-ID: 3JKxqbbq129
gfx/doc/README.webrender
gfx/webrender/examples/scrolling.rs
gfx/webrender/res/cs_blur.fs.glsl
gfx/webrender/res/cs_blur.glsl
gfx/webrender/res/cs_blur.vs.glsl
gfx/webrender/res/ps_angle_gradient.fs.glsl
gfx/webrender/res/ps_angle_gradient.glsl
gfx/webrender/res/ps_angle_gradient.vs.glsl
gfx/webrender/res/ps_clear.fs.glsl
gfx/webrender/res/ps_clear.glsl
gfx/webrender/res/ps_clear.vs.glsl
gfx/webrender/res/ps_composite.fs.glsl
gfx/webrender/res/ps_composite.glsl
gfx/webrender/res/ps_composite.vs.glsl
gfx/webrender/res/ps_gradient.fs.glsl
gfx/webrender/res/ps_gradient.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_line.fs.glsl
gfx/webrender/res/ps_line.glsl
gfx/webrender/res/ps_line.vs.glsl
gfx/webrender/res/ps_radial_gradient.fs.glsl
gfx/webrender/res/ps_radial_gradient.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/res/ps_split_composite.fs.glsl
gfx/webrender/res/ps_split_composite.glsl
gfx/webrender/res/ps_split_composite.vs.glsl
gfx/webrender/src/border.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_task.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 81cba6b139c4c1061cab6a1c38acf2ae7f50445d
+Latest Commit: fe83b424e7b8bce3d3661c3561cfd58a6cd186fe
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -77,18 +77,31 @@ impl Example for App {
                                                              ScrollSensitivity::ScriptAndInputEvents);
             builder.push_clip_id(nested_clip_id);
 
             // give it a giant gray background just to distinguish it and to easily
             // visually identify the nested scrollbox
             builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
             // add a teal square to visualize the scrolling/clipping behaviour
-            // as you scroll the nested scrollbox with WASD keys
-            builder.push_rect((0, 100).to(50, 150), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
+            // as you scroll the nested scrollbox
+            builder.push_rect((0, 200).to(50, 250), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
+
+            // Add a sticky frame. It will "stick" at a margin of 10px from the top, until
+            // the scrollframe scrolls another 60px, at which point it will "unstick". This lines
+            // it up with the above teal square as it scrolls out of the visible area of the
+            // scrollframe
+            let sticky_id = builder.define_sticky_frame(
+                None,
+                (50, 140).to(100, 190),
+                StickyFrameInfo::new(Some(StickySideConstraint{ margin: 10.0, max_offset: 60.0 }),
+                                     None, None, None));
+            builder.push_clip_id(sticky_id);
+            builder.push_rect((50, 140).to(100, 190), None, ColorF::new(0.5, 0.5, 1.0, 1.0));
+            builder.pop_clip_id(); // sticky_id
 
             // just for good measure add another teal square in the bottom-right
             // corner of the nested scrollframe content, which can be scrolled into
             // view by the user
             builder.push_rect((250, 350).to(300, 400), None, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             builder.pop_clip_id(); // nested_clip_id
 
deleted file mode 100644
--- a/gfx/webrender/res/cs_blur.fs.glsl
+++ /dev/null
@@ -1,48 +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/. */
-
-// TODO(gw): Write a fast path blur that handles smaller blur radii
-//           with a offset / weight uniform table and a constant
-//           loop iteration count!
-
-// TODO(gw): Make use of the bilinear sampling trick to reduce
-//           the number of texture fetches needed for a gaussian blur.
-
-float gauss(float x, float sigma) {
-    return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
-}
-
-void main(void) {
-    vec4 cache_sample = texture(sCacheRGBA8, vUv);
-
-    // TODO(gw): The gauss function gets NaNs when blur radius
-    //           is zero. In the future, detect this earlier
-    //           and skip the blur passes completely.
-    if (vBlurRadius == 0) {
-        oFragColor = cache_sample;
-        return;
-    }
-
-    vec4 color = vec4(cache_sample.rgb, 1.0) * (cache_sample.a * gauss(0.0, vSigma));
-
-    for (int i=1 ; i < vBlurRadius ; ++i) {
-        vec2 offset = vec2(float(i)) * vOffsetScale;
-
-        vec2 st0 = clamp(vUv.xy + offset, vUvRect.xy, vUvRect.zw);
-        vec4 color0 = texture(sCacheRGBA8, vec3(st0, vUv.z));
-
-        vec2 st1 = clamp(vUv.xy - offset, vUvRect.xy, vUvRect.zw);
-        vec4 color1 = texture(sCacheRGBA8, vec3(st1, vUv.z));
-
-        // Alpha must be premultiplied in order to properly blur the alpha channel.
-        float weight = gauss(float(i), vSigma);
-        color += vec4(color0.rgb * color0.a, color0.a) * weight;
-        color += vec4(color1.rgb * color1.a, color1.a) * weight;
-    }
-
-    // Unpremultiply the alpha.
-    color.rgb /= color.a;
-
-    oFragColor = dither(color);
-}
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -4,8 +4,119 @@
 
 #include shared,prim_shared
 
 varying vec3 vUv;
 flat varying vec4 vUvRect;
 flat varying vec2 vOffsetScale;
 flat varying float vSigma;
 flat varying int vBlurRadius;
+
+#ifdef WR_VERTEX_SHADER
+// Applies a separable gaussian blur in one direction, as specified
+// by the dir field in the blur command.
+
+#define DIR_HORIZONTAL  0
+#define DIR_VERTICAL    1
+
+in int aBlurRenderTaskIndex;
+in int aBlurSourceTaskIndex;
+in int aBlurDirection;
+
+struct BlurCommand {
+    int task_id;
+    int src_task_id;
+    int dir;
+};
+
+BlurCommand fetch_blur() {
+    BlurCommand blur;
+
+    blur.task_id = aBlurRenderTaskIndex;
+    blur.src_task_id = aBlurSourceTaskIndex;
+    blur.dir = aBlurDirection;
+
+    return blur;
+}
+
+void main(void) {
+    BlurCommand cmd = fetch_blur();
+    RenderTaskData task = fetch_render_task(cmd.task_id);
+    RenderTaskData src_task = fetch_render_task(cmd.src_task_id);
+
+    vec4 local_rect = task.data0;
+
+    vec2 pos = mix(local_rect.xy,
+                   local_rect.xy + local_rect.zw,
+                   aPosition.xy);
+
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0).xy);
+    vUv.z = src_task.data1.x;
+    vBlurRadius = int(task.data1.y);
+    vSigma = task.data1.y * 0.5;
+
+    switch (cmd.dir) {
+        case DIR_HORIZONTAL:
+            vOffsetScale = vec2(1.0 / texture_size.x, 0.0);
+            break;
+        case DIR_VERTICAL:
+            vOffsetScale = vec2(0.0, 1.0 / texture_size.y);
+            break;
+    }
+
+    vUvRect = vec4(src_task.data0.xy + vec2(0.5),
+                   src_task.data0.xy + src_task.data0.zw - vec2(0.5));
+    vUvRect /= texture_size.xyxy;
+
+    vec2 uv0 = src_task.data0.xy / texture_size;
+    vec2 uv1 = (src_task.data0.xy + src_task.data0.zw) / texture_size;
+    vUv.xy = mix(uv0, uv1, aPosition.xy);
+
+    gl_Position = uTransform * vec4(pos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+// TODO(gw): Write a fast path blur that handles smaller blur radii
+//           with a offset / weight uniform table and a constant
+//           loop iteration count!
+
+// TODO(gw): Make use of the bilinear sampling trick to reduce
+//           the number of texture fetches needed for a gaussian blur.
+
+float gauss(float x, float sigma) {
+    return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
+}
+
+void main(void) {
+    vec4 cache_sample = texture(sCacheRGBA8, vUv);
+
+    // TODO(gw): The gauss function gets NaNs when blur radius
+    //           is zero. In the future, detect this earlier
+    //           and skip the blur passes completely.
+    if (vBlurRadius == 0) {
+        oFragColor = cache_sample;
+        return;
+    }
+
+    vec4 color = vec4(cache_sample.rgb, 1.0) * (cache_sample.a * gauss(0.0, vSigma));
+
+    for (int i=1 ; i < vBlurRadius ; ++i) {
+        vec2 offset = vec2(float(i)) * vOffsetScale;
+
+        vec2 st0 = clamp(vUv.xy + offset, vUvRect.xy, vUvRect.zw);
+        vec4 color0 = texture(sCacheRGBA8, vec3(st0, vUv.z));
+
+        vec2 st1 = clamp(vUv.xy - offset, vUvRect.xy, vUvRect.zw);
+        vec4 color1 = texture(sCacheRGBA8, vec3(st1, vUv.z));
+
+        // Alpha must be premultiplied in order to properly blur the alpha channel.
+        float weight = gauss(float(i), vSigma);
+        color += vec4(color0.rgb * color0.a, color0.a) * weight;
+        color += vec4(color1.rgb * color1.a, color1.a) * weight;
+    }
+
+    // Unpremultiply the alpha.
+    color.rgb /= color.a;
+
+    oFragColor = dither(color);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/cs_blur.vs.glsl
+++ /dev/null
@@ -1,65 +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/. */
-
-// Applies a separable gaussian blur in one direction, as specified
-// by the dir field in the blur command.
-
-#define DIR_HORIZONTAL  0
-#define DIR_VERTICAL    1
-
-in int aBlurRenderTaskIndex;
-in int aBlurSourceTaskIndex;
-in int aBlurDirection;
-
-struct BlurCommand {
-    int task_id;
-    int src_task_id;
-    int dir;
-};
-
-BlurCommand fetch_blur() {
-    BlurCommand blur;
-
-    blur.task_id = aBlurRenderTaskIndex;
-    blur.src_task_id = aBlurSourceTaskIndex;
-    blur.dir = aBlurDirection;
-
-    return blur;
-}
-
-void main(void) {
-    BlurCommand cmd = fetch_blur();
-    RenderTaskData task = fetch_render_task(cmd.task_id);
-    RenderTaskData src_task = fetch_render_task(cmd.src_task_id);
-
-    vec4 local_rect = task.data0;
-
-    vec2 pos = mix(local_rect.xy,
-                   local_rect.xy + local_rect.zw,
-                   aPosition.xy);
-
-    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0).xy);
-    vUv.z = src_task.data1.x;
-    vBlurRadius = int(task.data1.y);
-    vSigma = task.data1.y * 0.5;
-
-    switch (cmd.dir) {
-        case DIR_HORIZONTAL:
-            vOffsetScale = vec2(1.0 / texture_size.x, 0.0);
-            break;
-        case DIR_VERTICAL:
-            vOffsetScale = vec2(0.0, 1.0 / texture_size.y);
-            break;
-    }
-
-    vUvRect = vec4(src_task.data0.xy + vec2(0.5),
-                   src_task.data0.xy + src_task.data0.zw - vec2(0.5));
-    vUvRect /= texture_size.xyxy;
-
-    vec2 uv0 = src_task.data0.xy / texture_size;
-    vec2 uv1 = (src_task.data0.xy + src_task.data0.zw) / texture_size;
-    vUv.xy = mix(uv0, uv1, aPosition.xy);
-
-    gl_Position = uTransform * vec4(pos, 0.0, 1.0);
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_angle_gradient.fs.glsl
+++ /dev/null
@@ -1,18 +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/. */
-
-void main(void) {
-    vec2 pos = mod(vPos, vTileRepeat);
-
-    if (pos.x >= vTileSize.x ||
-        pos.y >= vTileSize.y) {
-        discard;
-    }
-
-    float offset = dot(pos - vStartPoint, vScaledDir);
-
-    oFragColor = sample_gradient(vGradientAddress,
-                                 offset,
-                                 vGradientRepeat);
-}
--- a/gfx/webrender/res/ps_angle_gradient.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.glsl
@@ -9,8 +9,56 @@ flat varying float vGradientRepeat;
 
 flat varying vec2 vScaledDir;
 flat varying vec2 vStartPoint;
 
 flat varying vec2 vTileSize;
 flat varying vec2 vTileRepeat;
 
 varying vec2 vPos;
+
+#ifdef WR_VERTEX_SHADER
+void main(void) {
+    Primitive prim = load_primitive();
+    Gradient gradient = fetch_gradient(prim.specific_prim_address);
+
+    VertexInfo vi = write_vertex(prim.local_rect,
+                                 prim.local_clip_rect,
+                                 prim.z,
+                                 prim.layer,
+                                 prim.task,
+                                 prim.local_rect);
+
+    vPos = vi.local_pos - prim.local_rect.p0;
+
+    vec2 start_point = gradient.start_end_point.xy;
+    vec2 end_point = gradient.start_end_point.zw;
+    vec2 dir = end_point - start_point;
+
+    vStartPoint = start_point;
+    vScaledDir = dir / dot(dir, dir);
+
+    vTileSize = gradient.tile_size_repeat.xy;
+    vTileRepeat = gradient.tile_size_repeat.zw;
+
+    vGradientAddress = prim.specific_prim_address + VECS_PER_GRADIENT;
+
+    // Whether to repeat the gradient instead of clamping.
+    vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    vec2 pos = mod(vPos, vTileRepeat);
+
+    if (pos.x >= vTileSize.x ||
+        pos.y >= vTileSize.y) {
+        discard;
+    }
+
+    float offset = dot(pos - vStartPoint, vScaledDir);
+
+    oFragColor = sample_gradient(vGradientAddress,
+                                 offset,
+                                 vGradientRepeat);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ /dev/null
@@ -1,32 +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/. */
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Gradient gradient = fetch_gradient(prim.specific_prim_address);
-
-    VertexInfo vi = write_vertex(prim.local_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect);
-
-    vPos = vi.local_pos - prim.local_rect.p0;
-
-    vec2 start_point = gradient.start_end_point.xy;
-    vec2 end_point = gradient.start_end_point.zw;
-    vec2 dir = end_point - start_point;
-
-    vStartPoint = start_point;
-    vScaledDir = dir / dot(dir, dir);
-
-    vTileSize = gradient.tile_size_repeat.xy;
-    vTileRepeat = gradient.tile_size_repeat.zw;
-
-    vGradientAddress = prim.specific_prim_address + VECS_PER_GRADIENT;
-
-    // Whether to repeat the gradient instead of clamping.
-    vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_clear.fs.glsl
+++ /dev/null
@@ -1,7 +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/. */
-
-void main(void) {
-    oFragColor = vec4(1.0, 1.0, 1.0, 1.0);
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_clear.glsl
+++ /dev/null
@@ -1,3 +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/. */
deleted file mode 100644
--- a/gfx/webrender/res/ps_clear.vs.glsl
+++ /dev/null
@@ -1,12 +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/. */
-
-in ivec4 aClearRectangle;
-
-void main() {
-    vec4 rect = vec4(aClearRectangle);
-
-    vec4 pos = vec4(mix(rect.xy, rect.xy + rect.zw, aPosition.xy), 0, 1);
-    gl_Position = uTransform * pos;
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_composite.fs.glsl
+++ /dev/null
@@ -1,247 +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/. */
-
-float gauss(float x, float sigma) {
-    if (sigma == 0.0)
-        return 1.0;
-    return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
-}
-
-vec3 Multiply(vec3 Cb, vec3 Cs) {
-    return Cb * Cs;
-}
-
-vec3 Screen(vec3 Cb, vec3 Cs) {
-    return Cb + Cs - (Cb * Cs);
-}
-
-vec3 HardLight(vec3 Cb, vec3 Cs) {
-    vec3 m = Multiply(Cb, 2.0 * Cs);
-    vec3 s = Screen(Cb, 2.0 * Cs - 1.0);
-    vec3 edge = vec3(0.5, 0.5, 0.5);
-    return mix(m, s, step(edge, Cs));
-}
-
-// TODO: Worth doing with mix/step? Check GLSL output.
-float ColorDodge(float Cb, float Cs) {
-    if (Cb == 0.0)
-        return 0.0;
-    else if (Cs == 1.0)
-        return 1.0;
-    else
-        return min(1.0, Cb / (1.0 - Cs));
-}
-
-// TODO: Worth doing with mix/step? Check GLSL output.
-float ColorBurn(float Cb, float Cs) {
-    if (Cb == 1.0)
-        return 1.0;
-    else if (Cs == 0.0)
-        return 0.0;
-    else
-        return 1.0 - min(1.0, (1.0 - Cb) / Cs);
-}
-
-float SoftLight(float Cb, float Cs) {
-    if (Cs <= 0.5) {
-        return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb);
-    } else {
-        float D;
-
-        if (Cb <= 0.25)
-            D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb;
-        else
-            D = sqrt(Cb);
-
-        return Cb + (2.0 * Cs - 1.0) * (D - Cb);
-    }
-}
-
-vec3 Difference(vec3 Cb, vec3 Cs) {
-    return abs(Cb - Cs);
-}
-
-vec3 Exclusion(vec3 Cb, vec3 Cs) {
-    return Cb + Cs - 2.0 * Cb * Cs;
-}
-
-// These functions below are taken from the spec.
-// There's probably a much quicker way to implement
-// them in GLSL...
-float Sat(vec3 c) {
-    return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
-}
-
-float Lum(vec3 c) {
-    vec3 f = vec3(0.3, 0.59, 0.11);
-    return dot(c, f);
-}
-
-vec3 ClipColor(vec3 C) {
-    float L = Lum(C);
-    float n = min(C.r, min(C.g, C.b));
-    float x = max(C.r, max(C.g, C.b));
-
-    if (n < 0.0)
-        C = L + (((C - L) * L) / (L - n));
-
-    if (x > 1.0)
-        C = L + (((C - L) * (1.0 - L)) / (x - L));
-
-    return C;
-}
-
-vec3 SetLum(vec3 C, float l) {
-    float d = l - Lum(C);
-    return ClipColor(C + d);
-}
-
-void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) {
-    if (Cmax > Cmin) {
-        Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin));
-        Cmax = s;
-    } else {
-        Cmid = 0.0;
-        Cmax = 0.0;
-    }
-    Cmin = 0.0;
-}
-
-vec3 SetSat(vec3 C, float s) {
-    if (C.r <= C.g) {
-        if (C.g <= C.b) {
-            SetSatInner(C.r, C.g, C.b, s);
-        } else {
-            if (C.r <= C.b) {
-                SetSatInner(C.r, C.b, C.g, s);
-            } else {
-                SetSatInner(C.b, C.r, C.g, s);
-            }
-        }
-    } else {
-        if (C.r <= C.b) {
-            SetSatInner(C.g, C.r, C.b, s);
-        } else {
-            if (C.g <= C.b) {
-                SetSatInner(C.g, C.b, C.r, s);
-            } else {
-                SetSatInner(C.b, C.g, C.r, s);
-            }
-        }
-    }
-    return C;
-}
-
-vec3 Hue(vec3 Cb, vec3 Cs) {
-    return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb));
-}
-
-vec3 Saturation(vec3 Cb, vec3 Cs) {
-    return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb));
-}
-
-vec3 Color(vec3 Cb, vec3 Cs) {
-    return SetLum(Cs, Lum(Cb));
-}
-
-vec3 Luminosity(vec3 Cb, vec3 Cs) {
-    return SetLum(Cb, Lum(Cs));
-}
-
-const int MixBlendMode_Multiply    = 1;
-const int MixBlendMode_Screen      = 2;
-const int MixBlendMode_Overlay     = 3;
-const int MixBlendMode_Darken      = 4;
-const int MixBlendMode_Lighten     = 5;
-const int MixBlendMode_ColorDodge  = 6;
-const int MixBlendMode_ColorBurn   = 7;
-const int MixBlendMode_HardLight   = 8;
-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;
-
-void main(void) {
-    vec4 Cb = texture(sCacheRGBA8, vUv0);
-    vec4 Cs = texture(sCacheRGBA8, vUv1);
-
-    // The mix-blend-mode functions assume no premultiplied alpha
-    Cb.rgb /= Cb.a;
-    Cs.rgb /= Cs.a;
-
-    if (Cb.a == 0.0) {
-        oFragColor = Cs;
-        return;
-    }
-    if (Cs.a == 0.0) {
-        oFragColor = vec4(0.0, 0.0, 0.0, 0.0);
-        return;
-    }
-
-    // Return yellow if none of the branches match (shouldn't happen).
-    vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
-
-    switch (vOp) {
-        case MixBlendMode_Multiply:
-            result.rgb = Multiply(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Screen:
-            result.rgb = Screen(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Overlay:
-            // Overlay is inverse of Hardlight
-            result.rgb = HardLight(Cs.rgb, Cb.rgb);
-            break;
-        case MixBlendMode_Darken:
-            result.rgb = min(Cs.rgb, Cb.rgb);
-            break;
-        case MixBlendMode_Lighten:
-            result.rgb = max(Cs.rgb, Cb.rgb);
-            break;
-        case MixBlendMode_ColorDodge:
-            result.r = ColorDodge(Cb.r, Cs.r);
-            result.g = ColorDodge(Cb.g, Cs.g);
-            result.b = ColorDodge(Cb.b, Cs.b);
-            break;
-        case MixBlendMode_ColorBurn:
-            result.r = ColorBurn(Cb.r, Cs.r);
-            result.g = ColorBurn(Cb.g, Cs.g);
-            result.b = ColorBurn(Cb.b, Cs.b);
-            break;
-        case MixBlendMode_HardLight:
-            result.rgb = HardLight(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_SoftLight:
-            result.r = SoftLight(Cb.r, Cs.r);
-            result.g = SoftLight(Cb.g, Cs.g);
-            result.b = SoftLight(Cb.b, Cs.b);
-            break;
-        case MixBlendMode_Difference:
-            result.rgb = Difference(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Exclusion:
-            result.rgb = Exclusion(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Hue:
-            result.rgb = Hue(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Saturation:
-            result.rgb = Saturation(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Color:
-            result.rgb = Color(Cb.rgb, Cs.rgb);
-            break;
-        case MixBlendMode_Luminosity:
-            result.rgb = Luminosity(Cb.rgb, Cs.rgb);
-            break;
-    }
-
-    result.rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
-    result.a = Cs.a;
-
-    oFragColor = result;
-}
--- a/gfx/webrender/res/ps_composite.glsl
+++ b/gfx/webrender/res/ps_composite.glsl
@@ -2,8 +2,285 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include shared,prim_shared
 
 varying vec3 vUv0;
 varying vec3 vUv1;
 flat varying int vOp;
+
+#ifdef WR_VERTEX_SHADER
+void main(void) {
+    CompositeInstance ci = fetch_composite_instance();
+    AlphaBatchTask dest_task = fetch_alpha_batch_task(ci.render_task_index);
+    ReadbackTask backdrop_task = fetch_readback_task(ci.backdrop_task_index);
+    AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
+
+    vec2 dest_origin = dest_task.render_target_origin -
+                       dest_task.screen_space_origin +
+                       src_task.screen_space_origin;
+
+    vec2 local_pos = mix(dest_origin,
+                         dest_origin + src_task.size,
+                         aPosition.xy);
+
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
+
+    vec2 st0 = backdrop_task.render_target_origin / texture_size;
+    vec2 st1 = (backdrop_task.render_target_origin + backdrop_task.size) / texture_size;
+    vUv0 = vec3(mix(st0, st1, aPosition.xy), backdrop_task.render_target_layer_index);
+
+    st0 = src_task.render_target_origin / texture_size;
+    st1 = (src_task.render_target_origin + src_task.size) / texture_size;
+    vUv1 = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
+
+    vOp = ci.user_data0;
+
+    gl_Position = uTransform * vec4(local_pos, ci.z, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+float gauss(float x, float sigma) {
+    if (sigma == 0.0)
+        return 1.0;
+    return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
+}
+
+vec3 Multiply(vec3 Cb, vec3 Cs) {
+    return Cb * Cs;
+}
+
+vec3 Screen(vec3 Cb, vec3 Cs) {
+    return Cb + Cs - (Cb * Cs);
+}
+
+vec3 HardLight(vec3 Cb, vec3 Cs) {
+    vec3 m = Multiply(Cb, 2.0 * Cs);
+    vec3 s = Screen(Cb, 2.0 * Cs - 1.0);
+    vec3 edge = vec3(0.5, 0.5, 0.5);
+    return mix(m, s, step(edge, Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorDodge(float Cb, float Cs) {
+    if (Cb == 0.0)
+        return 0.0;
+    else if (Cs == 1.0)
+        return 1.0;
+    else
+        return min(1.0, Cb / (1.0 - Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorBurn(float Cb, float Cs) {
+    if (Cb == 1.0)
+        return 1.0;
+    else if (Cs == 0.0)
+        return 0.0;
+    else
+        return 1.0 - min(1.0, (1.0 - Cb) / Cs);
+}
+
+float SoftLight(float Cb, float Cs) {
+    if (Cs <= 0.5) {
+        return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb);
+    } else {
+        float D;
+
+        if (Cb <= 0.25)
+            D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb;
+        else
+            D = sqrt(Cb);
+
+        return Cb + (2.0 * Cs - 1.0) * (D - Cb);
+    }
+}
+
+vec3 Difference(vec3 Cb, vec3 Cs) {
+    return abs(Cb - Cs);
+}
+
+vec3 Exclusion(vec3 Cb, vec3 Cs) {
+    return Cb + Cs - 2.0 * Cb * Cs;
+}
+
+// These functions below are taken from the spec.
+// There's probably a much quicker way to implement
+// them in GLSL...
+float Sat(vec3 c) {
+    return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
+}
+
+float Lum(vec3 c) {
+    vec3 f = vec3(0.3, 0.59, 0.11);
+    return dot(c, f);
+}
+
+vec3 ClipColor(vec3 C) {
+    float L = Lum(C);
+    float n = min(C.r, min(C.g, C.b));
+    float x = max(C.r, max(C.g, C.b));
+
+    if (n < 0.0)
+        C = L + (((C - L) * L) / (L - n));
+
+    if (x > 1.0)
+        C = L + (((C - L) * (1.0 - L)) / (x - L));
+
+    return C;
+}
+
+vec3 SetLum(vec3 C, float l) {
+    float d = l - Lum(C);
+    return ClipColor(C + d);
+}
+
+void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) {
+    if (Cmax > Cmin) {
+        Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin));
+        Cmax = s;
+    } else {
+        Cmid = 0.0;
+        Cmax = 0.0;
+    }
+    Cmin = 0.0;
+}
+
+vec3 SetSat(vec3 C, float s) {
+    if (C.r <= C.g) {
+        if (C.g <= C.b) {
+            SetSatInner(C.r, C.g, C.b, s);
+        } else {
+            if (C.r <= C.b) {
+                SetSatInner(C.r, C.b, C.g, s);
+            } else {
+                SetSatInner(C.b, C.r, C.g, s);
+            }
+        }
+    } else {
+        if (C.r <= C.b) {
+            SetSatInner(C.g, C.r, C.b, s);
+        } else {
+            if (C.g <= C.b) {
+                SetSatInner(C.g, C.b, C.r, s);
+            } else {
+                SetSatInner(C.b, C.g, C.r, s);
+            }
+        }
+    }
+    return C;
+}
+
+vec3 Hue(vec3 Cb, vec3 Cs) {
+    return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb));
+}
+
+vec3 Saturation(vec3 Cb, vec3 Cs) {
+    return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb));
+}
+
+vec3 Color(vec3 Cb, vec3 Cs) {
+    return SetLum(Cs, Lum(Cb));
+}
+
+vec3 Luminosity(vec3 Cb, vec3 Cs) {
+    return SetLum(Cb, Lum(Cs));
+}
+
+const int MixBlendMode_Multiply    = 1;
+const int MixBlendMode_Screen      = 2;
+const int MixBlendMode_Overlay     = 3;
+const int MixBlendMode_Darken      = 4;
+const int MixBlendMode_Lighten     = 5;
+const int MixBlendMode_ColorDodge  = 6;
+const int MixBlendMode_ColorBurn   = 7;
+const int MixBlendMode_HardLight   = 8;
+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;
+
+void main(void) {
+    vec4 Cb = texture(sCacheRGBA8, vUv0);
+    vec4 Cs = texture(sCacheRGBA8, vUv1);
+
+    // The mix-blend-mode functions assume no premultiplied alpha
+    Cb.rgb /= Cb.a;
+    Cs.rgb /= Cs.a;
+
+    if (Cb.a == 0.0) {
+        oFragColor = Cs;
+        return;
+    }
+    if (Cs.a == 0.0) {
+        oFragColor = vec4(0.0, 0.0, 0.0, 0.0);
+        return;
+    }
+
+    // Return yellow if none of the branches match (shouldn't happen).
+    vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
+
+    switch (vOp) {
+        case MixBlendMode_Multiply:
+            result.rgb = Multiply(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Screen:
+            result.rgb = Screen(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Overlay:
+            // Overlay is inverse of Hardlight
+            result.rgb = HardLight(Cs.rgb, Cb.rgb);
+            break;
+        case MixBlendMode_Darken:
+            result.rgb = min(Cs.rgb, Cb.rgb);
+            break;
+        case MixBlendMode_Lighten:
+            result.rgb = max(Cs.rgb, Cb.rgb);
+            break;
+        case MixBlendMode_ColorDodge:
+            result.r = ColorDodge(Cb.r, Cs.r);
+            result.g = ColorDodge(Cb.g, Cs.g);
+            result.b = ColorDodge(Cb.b, Cs.b);
+            break;
+        case MixBlendMode_ColorBurn:
+            result.r = ColorBurn(Cb.r, Cs.r);
+            result.g = ColorBurn(Cb.g, Cs.g);
+            result.b = ColorBurn(Cb.b, Cs.b);
+            break;
+        case MixBlendMode_HardLight:
+            result.rgb = HardLight(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_SoftLight:
+            result.r = SoftLight(Cb.r, Cs.r);
+            result.g = SoftLight(Cb.g, Cs.g);
+            result.b = SoftLight(Cb.b, Cs.b);
+            break;
+        case MixBlendMode_Difference:
+            result.rgb = Difference(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Exclusion:
+            result.rgb = Exclusion(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Hue:
+            result.rgb = Hue(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Saturation:
+            result.rgb = Saturation(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Color:
+            result.rgb = Color(Cb.rgb, Cs.rgb);
+            break;
+        case MixBlendMode_Luminosity:
+            result.rgb = Luminosity(Cb.rgb, Cs.rgb);
+            break;
+    }
+
+    result.rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
+    result.a = Cs.a;
+
+    oFragColor = result;
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_composite.vs.glsl
+++ /dev/null
@@ -1,32 +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/. */
-
-void main(void) {
-    CompositeInstance ci = fetch_composite_instance();
-    AlphaBatchTask dest_task = fetch_alpha_batch_task(ci.render_task_index);
-    ReadbackTask backdrop_task = fetch_readback_task(ci.backdrop_task_index);
-    AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
-
-    vec2 dest_origin = dest_task.render_target_origin -
-                       dest_task.screen_space_origin +
-                       src_task.screen_space_origin;
-
-    vec2 local_pos = mix(dest_origin,
-                         dest_origin + src_task.size,
-                         aPosition.xy);
-
-    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
-
-    vec2 st0 = backdrop_task.render_target_origin / texture_size;
-    vec2 st1 = (backdrop_task.render_target_origin + backdrop_task.size) / texture_size;
-    vUv0 = vec3(mix(st0, st1, aPosition.xy), backdrop_task.render_target_layer_index);
-
-    st0 = src_task.render_target_origin / texture_size;
-    st1 = (src_task.render_target_origin + src_task.size) / texture_size;
-    vUv1 = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
-
-    vOp = ci.user_data0;
-
-    gl_Position = uTransform * vec4(local_pos, ci.z, 1.0);
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_gradient.fs.glsl
+++ /dev/null
@@ -1,16 +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/. */
-
-void main(void) {
-#ifdef WR_FEATURE_TRANSFORM
-    float alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-#else
-    float alpha = 1.0;
-    vec2 local_pos = vPos;
-#endif
-
-    alpha = min(alpha, do_clip());
-    oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
-}
--- a/gfx/webrender/res/ps_gradient.glsl
+++ b/gfx/webrender/res/ps_gradient.glsl
@@ -6,8 +6,108 @@
 
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vPos;
 #endif
+
+#ifdef WR_VERTEX_SHADER
+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 +
+                       VECS_PER_GRADIENT_STOP * prim.user_data0;
+
+    GradientStop g0 = fetch_gradient_stop(stop_address);
+    GradientStop g1 = fetch_gradient_stop(stop_address + VECS_PER_GRADIENT_STOP);
+
+    RectWithSize segment_rect;
+    vec2 axis;
+    vec4 adjusted_color_g0 = g0.color;
+    vec4 adjusted_color_g1 = g1.color;
+    if (abs_start_end_point.y == abs_start_end_point.w) {
+        // Calculate the x coord of the gradient stops
+        vec2 g01_x = mix(abs_start_end_point.xx, abs_start_end_point.zz,
+                         vec2(g0.offset.x, g1.offset.x));
+
+        // The gradient stops might exceed the geometry rect so clamp them
+        vec2 g01_x_clamped = clamp(g01_x,
+                                   prim.local_rect.p0.xx,
+                                   prim.local_rect.p0.xx + prim.local_rect.size.xx);
+
+        // Calculate the segment rect using the clamped coords
+        segment_rect.p0 = vec2(g01_x_clamped.x, prim.local_rect.p0.y);
+        segment_rect.size = vec2(g01_x_clamped.y - g01_x_clamped.x, prim.local_rect.size.y);
+        axis = vec2(1.0, 0.0);
+
+        // Adjust the stop colors by how much they were clamped
+        vec2 adjusted_offset = (g01_x_clamped - g01_x.xx) / (g01_x.y - g01_x.x);
+        adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
+        adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
+    } else {
+        // Calculate the y coord of the gradient stops
+        vec2 g01_y = mix(abs_start_end_point.yy, abs_start_end_point.ww,
+                         vec2(g0.offset.x, g1.offset.x));
+
+        // The gradient stops might exceed the geometry rect so clamp them
+        vec2 g01_y_clamped = clamp(g01_y,
+                                   prim.local_rect.p0.yy,
+                                   prim.local_rect.p0.yy + prim.local_rect.size.yy);
+
+        // Calculate the segment rect using the clamped coords
+        segment_rect.p0 = vec2(prim.local_rect.p0.x, g01_y_clamped.x);
+        segment_rect.size = vec2(prim.local_rect.size.x, g01_y_clamped.y - g01_y_clamped.x);
+        axis = vec2(0.0, 1.0);
+
+        // Adjust the stop colors by how much they were clamped
+        vec2 adjusted_offset = (g01_y_clamped - g01_y.xx) / (g01_y.y - g01_y.x);
+        adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
+        adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
+    }
+
+#ifdef WR_FEATURE_TRANSFORM
+    TransformVertexInfo vi = write_transform_vertex(segment_rect,
+                                                    prim.local_clip_rect,
+                                                    prim.z,
+                                                    prim.layer,
+                                                    prim.task,
+                                                    prim.local_rect);
+    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.layer,
+                                 prim.task,
+                                 prim.local_rect);
+
+    vec2 f = (vi.local_pos - segment_rect.p0) / segment_rect.size;
+    vPos = vi.local_pos;
+#endif
+
+    write_clip(vi.screen_pos, prim.clip_area);
+
+    vColor = mix(adjusted_color_g0, adjusted_color_g1, dot(f, axis));
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+#ifdef WR_FEATURE_TRANSFORM
+    float alpha = 0.0;
+    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
+#else
+    float alpha = 1.0;
+    vec2 local_pos = vPos;
+#endif
+
+    alpha = min(alpha, do_clip());
+    oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ /dev/null
@@ -1,86 +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/. */
-
-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 +
-                       VECS_PER_GRADIENT_STOP * prim.user_data0;
-
-    GradientStop g0 = fetch_gradient_stop(stop_address);
-    GradientStop g1 = fetch_gradient_stop(stop_address + VECS_PER_GRADIENT_STOP);
-
-    RectWithSize segment_rect;
-    vec2 axis;
-    vec4 adjusted_color_g0 = g0.color;
-    vec4 adjusted_color_g1 = g1.color;
-    if (abs_start_end_point.y == abs_start_end_point.w) {
-        // Calculate the x coord of the gradient stops
-        vec2 g01_x = mix(abs_start_end_point.xx, abs_start_end_point.zz,
-                         vec2(g0.offset.x, g1.offset.x));
-
-        // The gradient stops might exceed the geometry rect so clamp them
-        vec2 g01_x_clamped = clamp(g01_x,
-                                   prim.local_rect.p0.xx,
-                                   prim.local_rect.p0.xx + prim.local_rect.size.xx);
-
-        // Calculate the segment rect using the clamped coords
-        segment_rect.p0 = vec2(g01_x_clamped.x, prim.local_rect.p0.y);
-        segment_rect.size = vec2(g01_x_clamped.y - g01_x_clamped.x, prim.local_rect.size.y);
-        axis = vec2(1.0, 0.0);
-
-        // Adjust the stop colors by how much they were clamped
-        vec2 adjusted_offset = (g01_x_clamped - g01_x.xx) / (g01_x.y - g01_x.x);
-        adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
-        adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
-    } else {
-        // Calculate the y coord of the gradient stops
-        vec2 g01_y = mix(abs_start_end_point.yy, abs_start_end_point.ww,
-                         vec2(g0.offset.x, g1.offset.x));
-
-        // The gradient stops might exceed the geometry rect so clamp them
-        vec2 g01_y_clamped = clamp(g01_y,
-                                   prim.local_rect.p0.yy,
-                                   prim.local_rect.p0.yy + prim.local_rect.size.yy);
-
-        // Calculate the segment rect using the clamped coords
-        segment_rect.p0 = vec2(prim.local_rect.p0.x, g01_y_clamped.x);
-        segment_rect.size = vec2(prim.local_rect.size.x, g01_y_clamped.y - g01_y_clamped.x);
-        axis = vec2(0.0, 1.0);
-
-        // Adjust the stop colors by how much they were clamped
-        vec2 adjusted_offset = (g01_y_clamped - g01_y.xx) / (g01_y.y - g01_y.x);
-        adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
-        adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
-    }
-
-#ifdef WR_FEATURE_TRANSFORM
-    TransformVertexInfo vi = write_transform_vertex(segment_rect,
-                                                    prim.local_clip_rect,
-                                                    prim.z,
-                                                    prim.layer,
-                                                    prim.task,
-                                                    prim.local_rect);
-    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.layer,
-                                 prim.task,
-                                 prim.local_rect);
-
-    vec2 f = (vi.local_pos - segment_rect.p0) / segment_rect.size;
-    vPos = vi.local_pos;
-#endif
-
-    write_clip(vi.screen_pos, prim.clip_area);
-
-    vColor = mix(adjusted_color_g0, adjusted_color_g1, dot(f, axis));
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_line.fs.glsl
+++ /dev/null
@@ -1,122 +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/. */
-
-float det(vec2 a, vec2 b) {
-    return a.x * b.y - b.x * a.y;
-}
-
-// From: http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
-vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) {
-    float a = det(b0, b2);
-    float b = 2.0 * det(b1, b0);
-    float d = 2.0 * det(b2, b1);
-
-    float f = b * d - a * a;
-    vec2 d21 = b2 - b1;
-    vec2 d10 = b1 - b0;
-    vec2 d20 = b2 - b0;
-
-    vec2 gf = 2.0 * (b *d21 + d * d10 + a * d20);
-    gf = vec2(gf.y,-gf.x);
-    vec2 pp = -f * gf / dot(gf, gf);
-    vec2 d0p = b0 - pp;
-    float ap = det(d0p, d20);
-    float bp = 2.0 * det(d10, d0p);
-
-    float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0);
-    return mix(mix(b0, b1, t), mix(b1,b2,t), t);
-}
-
-// Approximate distance from point to quadratic bezier.
-float approx_distance(vec2 p, vec2 b0, vec2 b1, vec2 b2) {
-    return length(get_distance_vector(b0 - p, b1 - p, b2 - p));
-}
-
-void main(void) {
-    float alpha = 1.0;
-
-#ifdef WR_FEATURE_CACHE
-    vec2 local_pos = vLocalPos;
-#else
-    #ifdef WR_FEATURE_TRANSFORM
-        alpha = 0.0;
-        vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-    #else
-        vec2 local_pos = vLocalPos;
-    #endif
-
-        alpha = min(alpha, do_clip());
-#endif
-
-    // Find the appropriate distance to apply the step over.
-    vec2 fw = fwidth(local_pos);
-    float afwidth = length(fw);
-
-    // Select the x/y coord, depending on which axis this edge is.
-    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
-
-    switch (vStyle) {
-        case LINE_STYLE_SOLID: {
-            break;
-        }
-        case LINE_STYLE_DASHED: {
-            // Get the main-axis position relative to closest dot or dash.
-            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
-
-            // Calculate dash alpha (on/off) based on dash length
-            alpha = min(alpha, step(x, vParams.y));
-            break;
-        }
-        case LINE_STYLE_DOTTED: {
-            // Get the main-axis position relative to closest dot or dash.
-            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
-
-            // Get the dot alpha
-            vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
-            float dot_distance = length(dot_relative_pos) - vParams.y;
-            alpha = min(alpha, 1.0 - smoothstep(-0.5 * afwidth,
-                                                0.5 * afwidth,
-                                                dot_distance));
-            break;
-        }
-        case LINE_STYLE_WAVY: {
-            vec2 normalized_local_pos = pos - vLocalOrigin.xy;
-
-            float y0 = vParams.y;
-            float dy = vParams.z;
-            float dx = vParams.w;
-
-            // Flip the position of the bezier center points each
-            // wave period.
-            dy *= step(mod(normalized_local_pos.x, 4.0 * dx), 2.0 * dx) * 2.0 - 1.0;
-
-            // Convert pos to a local position within one wave period.
-            normalized_local_pos.x = dx + mod(normalized_local_pos.x, 2.0 * dx);
-
-            // Evaluate SDF to the first bezier.
-            vec2 b0_0 = vec2(0.0 * dx,  y0);
-            vec2 b1_0 = vec2(1.0 * dx,  y0 - dy);
-            vec2 b2_0 = vec2(2.0 * dx,  y0);
-            float d1 = approx_distance(normalized_local_pos, b0_0, b1_0, b2_0);
-
-            // Evaluate SDF to the second bezier.
-            vec2 b0_1 = vec2(2.0 * dx,  y0);
-            vec2 b1_1 = vec2(3.0 * dx,  y0 + dy);
-            vec2 b2_1 = vec2(4.0 * dx,  y0);
-            float d2 = approx_distance(normalized_local_pos, b0_1, b1_1, b2_1);
-
-            // SDF union - this is needed to avoid artifacts where the
-            // bezier curves join.
-            float d = min(d1, d2);
-
-            // Apply AA based on the thickness of the wave.
-            alpha = 1.0 - smoothstep(vParams.x - 0.5 * afwidth,
-                                     vParams.x + 0.5 * afwidth,
-                                     d);
-            break;
-        }
-    }
-
-    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
-}
--- a/gfx/webrender/res/ps_line.glsl
+++ b/gfx/webrender/res/ps_line.glsl
@@ -10,8 +10,243 @@ flat varying float vAxisSelect;
 flat varying vec4 vParams;
 flat varying vec2 vLocalOrigin;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
+
+#ifdef WR_VERTEX_SHADER
+#define LINE_ORIENTATION_VERTICAL       0
+#define LINE_ORIENTATION_HORIZONTAL     1
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Line line = fetch_line(prim.specific_prim_address);
+
+    vec2 pos, size;
+
+    switch (int(line.orientation)) {
+        case LINE_ORIENTATION_HORIZONTAL:
+            vAxisSelect = 0.0;
+            pos = prim.local_rect.p0;
+            size = prim.local_rect.size;
+            break;
+        case LINE_ORIENTATION_VERTICAL:
+            vAxisSelect = 1.0;
+            pos = prim.local_rect.p0.yx;
+            size = prim.local_rect.size.yx;
+            break;
+    }
+
+    vLocalOrigin = pos;
+    vStyle = int(line.style);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // y = dash on + off length
+            // z = dash length
+            // w = center line of edge cross-axis (for dots only)
+            float desired_dash_length = size.y * 3.0;
+            // Consider half total length since there is an equal on/off for each dash.
+            float dash_count = 1.0 + ceil(size.x / desired_dash_length);
+            float dash_length = size.x / dash_count;
+            vParams = vec4(2.0 * dash_length,
+                           dash_length,
+                           0.0,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            float diameter = size.y;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * size.x / diameter);
+            float empty_space = size.x - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            float center_line = pos.y + 0.5 * size.y;
+            vParams = vec4(distance_between_centers,
+                           radius,
+                           center_line,
+                           0.0);
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            // Choose some arbitrary values to scale thickness,
+            // wave period etc.
+            // TODO(gw): Tune these to get closer to what Gecko uses.
+            float thickness = 0.15 * size.y;
+            vParams = vec4(thickness,
+                           size.y * 0.5,
+                           size.y * 0.75,
+                           size.y * 0.5);
+            break;
+        }
+    }
+
+#ifdef WR_FEATURE_CACHE
+    int text_shadow_address = prim.user_data0;
+    PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
+    TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
+
+    vec2 device_origin = prim.task.render_target_origin +
+                         uDevicePixelRatio * (prim.local_rect.p0 + shadow.offset - shadow_geom.local_rect.p0);
+    vec2 device_size = uDevicePixelRatio * prim.local_rect.size;
+
+    vec2 device_pos = mix(device_origin,
+                          device_origin + device_size,
+                          aPosition.xy);
+
+    vColor = shadow.color;
+    vLocalPos = mix(prim.local_rect.p0,
+                    prim.local_rect.p0 + prim.local_rect.size,
+                    aPosition.xy);
+
+    gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
+#else
+    vColor = line.color;
+
+    #ifdef WR_FEATURE_TRANSFORM
+        TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
+                                                        prim.local_clip_rect,
+                                                        prim.z,
+                                                        prim.layer,
+                                                        prim.task,
+                                                        prim.local_rect);
+    #else
+        VertexInfo vi = write_vertex(prim.local_rect,
+                                     prim.local_clip_rect,
+                                     prim.z,
+                                     prim.layer,
+                                     prim.task,
+                                     prim.local_rect);
+    #endif
+
+    vLocalPos = vi.local_pos;
+    write_clip(vi.screen_pos, prim.clip_area);
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+float det(vec2 a, vec2 b) {
+    return a.x * b.y - b.x * a.y;
+}
+
+// From: http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf
+vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) {
+    float a = det(b0, b2);
+    float b = 2.0 * det(b1, b0);
+    float d = 2.0 * det(b2, b1);
+
+    float f = b * d - a * a;
+    vec2 d21 = b2 - b1;
+    vec2 d10 = b1 - b0;
+    vec2 d20 = b2 - b0;
+
+    vec2 gf = 2.0 * (b *d21 + d * d10 + a * d20);
+    gf = vec2(gf.y,-gf.x);
+    vec2 pp = -f * gf / dot(gf, gf);
+    vec2 d0p = b0 - pp;
+    float ap = det(d0p, d20);
+    float bp = 2.0 * det(d10, d0p);
+
+    float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0);
+    return mix(mix(b0, b1, t), mix(b1,b2,t), t);
+}
+
+// Approximate distance from point to quadratic bezier.
+float approx_distance(vec2 p, vec2 b0, vec2 b1, vec2 b2) {
+    return length(get_distance_vector(b0 - p, b1 - p, b2 - p));
+}
+
+void main(void) {
+    float alpha = 1.0;
+
+#ifdef WR_FEATURE_CACHE
+    vec2 local_pos = vLocalPos;
+#else
+    #ifdef WR_FEATURE_TRANSFORM
+        alpha = 0.0;
+        vec2 local_pos = init_transform_fs(vLocalPos, alpha);
+    #else
+        vec2 local_pos = vLocalPos;
+    #endif
+
+        alpha = min(alpha, do_clip());
+#endif
+
+    // Find the appropriate distance to apply the step over.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+
+    // Select the x/y coord, depending on which axis this edge is.
+    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
+
+    switch (vStyle) {
+        case LINE_STYLE_SOLID: {
+            break;
+        }
+        case LINE_STYLE_DASHED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Calculate dash alpha (on/off) based on dash length
+            alpha = min(alpha, step(x, vParams.y));
+            break;
+        }
+        case LINE_STYLE_DOTTED: {
+            // Get the main-axis position relative to closest dot or dash.
+            float x = mod(pos.x - vLocalOrigin.x, vParams.x);
+
+            // Get the dot alpha
+            vec2 dot_relative_pos = vec2(x, pos.y) - vParams.yz;
+            float dot_distance = length(dot_relative_pos) - vParams.y;
+            alpha = min(alpha, 1.0 - smoothstep(-0.5 * afwidth,
+                                                0.5 * afwidth,
+                                                dot_distance));
+            break;
+        }
+        case LINE_STYLE_WAVY: {
+            vec2 normalized_local_pos = pos - vLocalOrigin.xy;
+
+            float y0 = vParams.y;
+            float dy = vParams.z;
+            float dx = vParams.w;
+
+            // Flip the position of the bezier center points each
+            // wave period.
+            dy *= step(mod(normalized_local_pos.x, 4.0 * dx), 2.0 * dx) * 2.0 - 1.0;
+
+            // Convert pos to a local position within one wave period.
+            normalized_local_pos.x = dx + mod(normalized_local_pos.x, 2.0 * dx);
+
+            // Evaluate SDF to the first bezier.
+            vec2 b0_0 = vec2(0.0 * dx,  y0);
+            vec2 b1_0 = vec2(1.0 * dx,  y0 - dy);
+            vec2 b2_0 = vec2(2.0 * dx,  y0);
+            float d1 = approx_distance(normalized_local_pos, b0_0, b1_0, b2_0);
+
+            // Evaluate SDF to the second bezier.
+            vec2 b0_1 = vec2(2.0 * dx,  y0);
+            vec2 b1_1 = vec2(3.0 * dx,  y0 + dy);
+            vec2 b2_1 = vec2(4.0 * dx,  y0);
+            float d2 = approx_distance(normalized_local_pos, b0_1, b1_1, b2_1);
+
+            // SDF union - this is needed to avoid artifacts where the
+            // bezier curves join.
+            float d = min(d1, d2);
+
+            // Apply AA based on the thickness of the wave.
+            alpha = 1.0 - smoothstep(vParams.x - 0.5 * afwidth,
+                                     vParams.x + 0.5 * afwidth,
+                                     d);
+            break;
+        }
+    }
+
+    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_line.vs.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/. */
-
-#define LINE_ORIENTATION_VERTICAL       0
-#define LINE_ORIENTATION_HORIZONTAL     1
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Line line = fetch_line(prim.specific_prim_address);
-
-    vec2 pos, size;
-
-    switch (int(line.orientation)) {
-        case LINE_ORIENTATION_HORIZONTAL:
-            vAxisSelect = 0.0;
-            pos = prim.local_rect.p0;
-            size = prim.local_rect.size;
-            break;
-        case LINE_ORIENTATION_VERTICAL:
-            vAxisSelect = 1.0;
-            pos = prim.local_rect.p0.yx;
-            size = prim.local_rect.size.yx;
-            break;
-    }
-
-    vLocalOrigin = pos;
-    vStyle = int(line.style);
-
-    switch (vStyle) {
-        case LINE_STYLE_SOLID: {
-            break;
-        }
-        case LINE_STYLE_DASHED: {
-            // y = dash on + off length
-            // z = dash length
-            // w = center line of edge cross-axis (for dots only)
-            float desired_dash_length = size.y * 3.0;
-            // Consider half total length since there is an equal on/off for each dash.
-            float dash_count = 1.0 + ceil(size.x / desired_dash_length);
-            float dash_length = size.x / dash_count;
-            vParams = vec4(2.0 * dash_length,
-                           dash_length,
-                           0.0,
-                           0.0);
-            break;
-        }
-        case LINE_STYLE_DOTTED: {
-            float diameter = size.y;
-            float radius = 0.5 * diameter;
-            float dot_count = ceil(0.5 * size.x / diameter);
-            float empty_space = size.x - dot_count * diameter;
-            float distance_between_centers = diameter + empty_space / dot_count;
-            float center_line = pos.y + 0.5 * size.y;
-            vParams = vec4(distance_between_centers,
-                           radius,
-                           center_line,
-                           0.0);
-            break;
-        }
-        case LINE_STYLE_WAVY: {
-            // Choose some arbitrary values to scale thickness,
-            // wave period etc.
-            // TODO(gw): Tune these to get closer to what Gecko uses.
-            float thickness = 0.15 * size.y;
-            vParams = vec4(thickness,
-                           size.y * 0.5,
-                           size.y * 0.75,
-                           size.y * 0.5);
-            break;
-        }
-    }
-
-#ifdef WR_FEATURE_CACHE
-    int text_shadow_address = prim.user_data0;
-    PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
-    TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
-
-    vec2 device_origin = prim.task.render_target_origin +
-                         uDevicePixelRatio * (prim.local_rect.p0 + shadow.offset - shadow_geom.local_rect.p0);
-    vec2 device_size = uDevicePixelRatio * prim.local_rect.size;
-
-    vec2 device_pos = mix(device_origin,
-                          device_origin + device_size,
-                          aPosition.xy);
-
-    vColor = shadow.color;
-    vLocalPos = mix(prim.local_rect.p0,
-                    prim.local_rect.p0 + prim.local_rect.size,
-                    aPosition.xy);
-
-    gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
-#else
-    vColor = line.color;
-
-    #ifdef WR_FEATURE_TRANSFORM
-        TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
-                                                        prim.local_clip_rect,
-                                                        prim.z,
-                                                        prim.layer,
-                                                        prim.task,
-                                                        prim.local_rect);
-    #else
-        VertexInfo vi = write_vertex(prim.local_rect,
-                                     prim.local_clip_rect,
-                                     prim.z,
-                                     prim.layer,
-                                     prim.task,
-                                     prim.local_rect);
-    #endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-#endif
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_radial_gradient.fs.glsl
+++ /dev/null
@@ -1,55 +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/. */
-
-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;
-        }
-    }
-
-    oFragColor = sample_gradient(vGradientAddress,
-                                 offset,
-                                 vGradientRepeat);
-}
--- a/gfx/webrender/res/ps_radial_gradient.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.glsl
@@ -11,8 +11,101 @@ 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.layer,
+                                 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_REPEAT);
+}
+#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;
+        }
+    }
+
+    oFragColor = sample_gradient(vGradientAddress,
+                                 offset,
+                                 vGradientRepeat);
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ /dev/null
@@ -1,40 +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/. */
-
-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.layer,
-                                 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_REPEAT);
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_split_composite.fs.glsl
+++ /dev/null
@@ -1,14 +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/. */
-
-void main(void) {
-    bvec4 inside = lessThanEqual(vec4(vUvTaskBounds.xy, vUv.xy),
-                                 vec4(vUv.xy, vUvTaskBounds.zw));
-    if (all(inside)) {
-        vec2 uv = clamp(vUv.xy, vUvSampleBounds.xy, vUvSampleBounds.zw);
-        oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
-    } else {
-        oFragColor = vec4(0.0);
-    }
-}
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -2,8 +2,68 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include shared,prim_shared
 
 varying vec3 vUv;
 flat varying vec4 vUvTaskBounds;
 flat varying vec4 vUvSampleBounds;
+
+#ifdef WR_VERTEX_SHADER
+struct SplitGeometry {
+    vec3 points[4];
+};
+
+SplitGeometry fetch_split_geometry(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+
+    vec4 data0 = texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0));
+    vec4 data1 = texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0));
+    vec4 data2 = texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0));
+
+    SplitGeometry geo;
+    geo.points = vec3[4](
+        data0.xyz, vec3(data0.w, data1.xy),
+        vec3(data1.zw, data2.x), data2.yzw
+    );
+    return geo;
+}
+
+vec3 bilerp(vec3 a, vec3 b, vec3 c, vec3 d, float s, float t) {
+    vec3 x = mix(a, b, t);
+    vec3 y = mix(c, d, t);
+    return mix(x, y, s);
+}
+
+void main(void) {
+    CompositeInstance ci = fetch_composite_instance();
+    SplitGeometry geometry = fetch_split_geometry(ci.user_data0);
+    AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
+
+    vec3 world_pos = bilerp(geometry.points[0], geometry.points[1],
+                            geometry.points[3], geometry.points[2],
+                            aPosition.y, aPosition.x);
+    vec4 final_pos = vec4(world_pos.xy * uDevicePixelRatio, ci.z, 1.0);
+
+    gl_Position = uTransform * final_pos;
+
+    vec2 uv_origin = src_task.render_target_origin;
+    vec2 uv_pos = uv_origin + world_pos.xy - src_task.screen_space_origin;
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
+    vUv = vec3(uv_pos / texture_size, src_task.render_target_layer_index);
+    vUvTaskBounds = vec4(uv_origin, uv_origin + src_task.size) / texture_size.xyxy;
+    vUvSampleBounds = vec4(uv_origin + 0.5, uv_origin + src_task.size - 0.5) / texture_size.xyxy;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+    bvec4 inside = lessThanEqual(vec4(vUvTaskBounds.xy, vUv.xy),
+                                 vec4(vUv.xy, vUvTaskBounds.zw));
+    if (all(inside)) {
+        vec2 uv = clamp(vUv.xy, vUvSampleBounds.xy, vUvSampleBounds.zw);
+        oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    } else {
+        oFragColor = vec4(0.0);
+    }
+}
+#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_split_composite.vs.glsl
+++ /dev/null
@@ -1,48 +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/. */
-
-struct SplitGeometry {
-    vec3 points[4];
-};
-
-SplitGeometry fetch_split_geometry(int address) {
-    ivec2 uv = get_resource_cache_uv(address);
-
-    vec4 data0 = texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0));
-    vec4 data1 = texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0));
-    vec4 data2 = texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0));
-
-    SplitGeometry geo;
-    geo.points = vec3[4](
-        data0.xyz, vec3(data0.w, data1.xy),
-        vec3(data1.zw, data2.x), data2.yzw
-    );
-    return geo;
-}
-
-vec3 bilerp(vec3 a, vec3 b, vec3 c, vec3 d, float s, float t) {
-    vec3 x = mix(a, b, t);
-    vec3 y = mix(c, d, t);
-    return mix(x, y, s);
-}
-
-void main(void) {
-    CompositeInstance ci = fetch_composite_instance();
-    SplitGeometry geometry = fetch_split_geometry(ci.user_data0);
-    AlphaBatchTask src_task = fetch_alpha_batch_task(ci.src_task_index);
-
-    vec3 world_pos = bilerp(geometry.points[0], geometry.points[1],
-                            geometry.points[3], geometry.points[2],
-                            aPosition.y, aPosition.x);
-    vec4 final_pos = vec4(world_pos.xy * uDevicePixelRatio, ci.z, 1.0);
-
-    gl_Position = uTransform * final_pos;
-
-    vec2 uv_origin = src_task.render_target_origin;
-    vec2 uv_pos = uv_origin + world_pos.xy - src_task.screen_space_origin;
-    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
-    vUv = vec3(uv_pos / texture_size, src_task.render_target_layer_index);
-    vUvTaskBounds = vec4(uv_origin, uv_origin + src_task.size) / texture_size.xyxy;
-    vUvSampleBounds = vec4(uv_origin + 0.5, uv_origin + src_task.size - 0.5) / texture_size.xyxy;
-}
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -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/. */
 
 use api::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ColorF, LayerPoint, LayerRect};
 use api::{LayerSize, LocalClip, NormalBorder};
+use clip::ClipSource;
 use ellipse::Ellipse;
 use gpu_cache::GpuDataRequest;
 use frame_builder::FrameBuilder;
-use mask_cache::ClipSource;
 use prim_store::{BorderPrimitiveCpu, PrimitiveContainer};
 use tiling::PrimitiveFlags;
 use util::{lerp, pack_as_float};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
     Single,     // Single instance needed - corner styles are same or similar.
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/clip.rs
@@ -0,0 +1,153 @@
+/* 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::{ComplexClipRegion, ImageMask, ImageRendering};
+use api::{LayerPoint, LayerRect, LayerToWorldTransform, LocalClip};
+use border::BorderCornerClipSource;
+use gpu_cache::GpuCache;
+use mask_cache::MaskCacheInfo;
+use resource_cache::ResourceCache;
+use std::ops::Not;
+
+#[derive(Clone, Debug)]
+pub struct ClipRegion {
+    pub origin: LayerPoint,
+    pub main: LayerRect,
+    pub image_mask: Option<ImageMask>,
+    pub complex_clips: Vec<ComplexClipRegion>,
+}
+
+impl ClipRegion {
+    pub fn create_for_clip_node(rect: LayerRect,
+                                mut complex_clips: Vec<ComplexClipRegion>,
+                                mut image_mask: Option<ImageMask>)
+                                -> ClipRegion {
+        // All the coordinates we receive are relative to the stacking context, but we want
+        // to convert them to something relative to the origin of the clip.
+        let negative_origin = -rect.origin.to_vector();
+        if let Some(ref mut image_mask) = image_mask {
+            image_mask.rect = image_mask.rect.translate(&negative_origin);
+        }
+
+        for complex_clip in complex_clips.iter_mut() {
+            complex_clip.rect = complex_clip.rect.translate(&negative_origin);
+        }
+
+        ClipRegion {
+            origin: rect.origin,
+            main: LayerRect::new(LayerPoint::zero(), rect.size),
+            image_mask,
+            complex_clips,
+        }
+    }
+
+    pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
+        let complex_clips = match local_clip {
+            &LocalClip::Rect(_) => Vec::new(),
+            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
+        };
+        ClipRegion::create_for_clip_node(*local_clip.clip_rect(), complex_clips, None)
+    }
+
+    pub fn create_for_local_clip(local_clip: &LocalClip) -> ClipRegion {
+        let complex_clips = match local_clip {
+            &LocalClip::Rect(_) => Vec::new(),
+            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
+        };
+
+        ClipRegion {
+            origin: LayerPoint::zero(),
+            main: *local_clip.clip_rect(),
+            image_mask: None,
+            complex_clips,
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ClipMode {
+    Clip,           // Pixels inside the region are visible.
+    ClipOut,        // Pixels outside the region are visible.
+}
+
+impl Not for ClipMode {
+    type Output = ClipMode;
+
+    fn not(self) -> ClipMode {
+        match self {
+            ClipMode::Clip => ClipMode::ClipOut,
+            ClipMode::ClipOut => ClipMode::Clip
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum ClipSource {
+    Complex(LayerRect, f32, ClipMode),
+    Region(ClipRegion),
+    /// TODO(gw): This currently only handles dashed style
+    /// clips, where the border style is dashed for both
+    /// adjacent border edges. Expand to handle dotted style
+    /// and different styles per edge.
+    BorderCorner(BorderCornerClipSource),
+}
+
+#[derive(Debug)]
+pub struct ClipSources {
+    clips: Vec<ClipSource>,
+    mask_cache_info: MaskCacheInfo,
+}
+
+impl ClipSources {
+    pub fn new(clips: Vec<ClipSource>) -> ClipSources {
+        let mask_cache_info = MaskCacheInfo::new(&clips);
+
+        ClipSources {
+            clips,
+            mask_cache_info,
+        }
+    }
+
+    pub fn clips(&self) -> &[ClipSource] {
+        &self.clips
+    }
+
+    pub fn update(&mut self,
+                  layer_transform: &LayerToWorldTransform,
+                  gpu_cache: &mut GpuCache,
+                  resource_cache: &mut ResourceCache,
+                  device_pixel_ratio: f32) {
+        if self.clips.is_empty() {
+            return;
+        }
+
+        self.mask_cache_info
+            .update(&self.clips,
+                    layer_transform,
+                    gpu_cache,
+                    device_pixel_ratio);
+
+        for clip in &self.clips {
+            if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
+                resource_cache.request_image(mask.image,
+                                             ImageRendering::Auto,
+                                             None,
+                                             gpu_cache);
+            }
+        }
+    }
+
+    pub fn is_masking(&self) -> bool {
+        self.mask_cache_info.is_masking()
+    }
+
+    pub fn clone_mask_cache_info(&self, keep_aligned: bool) -> MaskCacheInfo {
+        if keep_aligned {
+            self.mask_cache_info.clone()
+        } else {
+            self.mask_cache_info.strip_aligned()
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,72 +1,61 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
 use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyFrameInfo};
 use api::WorldPoint;
+use clip::{ClipRegion, ClipSource, ClipSources};
 use clip_scroll_tree::TransformUpdateState;
 use geometry::ray_intersects_rect;
-use mask_cache::{ClipRegion, ClipSource, MaskCacheInfo};
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::{MatrixHelpers, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub struct ClipInfo {
-    /// The ClipSource for this node, which is used to generate mask_cache_info.
-    pub clip_sources: Vec<ClipSource>,
-
-    /// The MaskCacheInfo for this node, which is produced by processing the
-    /// provided ClipSource.
-    pub mask_cache_info: MaskCacheInfo,
+    /// The clips for this node.
+    pub clip_sources: ClipSources,
 
     /// The packed layer index for this node, which is used to render a clip mask
     /// for it, if necessary.
     pub packed_layer_index: PackedLayerIndex,
 
     /// The final transformed rectangle of this clipping region for this node,
     /// which depends on the screen rectangle and the transformation of all of
     /// the parents.
     pub screen_bounding_rect: Option<(TransformedRectKind, DeviceIntRect)>,
 
-    /// The biggest final transformed rectangle that is completely inside the
-    /// clipping region for this node.
-    pub screen_inner_rect: DeviceIntRect,
-
     /// A rectangle which defines the rough boundaries of this clip in reference
     /// frame relative coordinates (with no scroll offsets).
     pub clip_rect: LayerRect,
 }
 
 impl ClipInfo {
     pub fn new(clip_region: ClipRegion, packed_layer_index: PackedLayerIndex) -> ClipInfo {
         let clip_rect = LayerRect::new(clip_region.origin, clip_region.main.size);
-        let clip_sources = vec![ClipSource::Region(clip_region)];
         ClipInfo {
-            mask_cache_info: MaskCacheInfo::new(&clip_sources),
-            clip_sources,
+            clip_sources: ClipSources::new(vec![ClipSource::Region(clip_region)]),
             packed_layer_index,
             screen_bounding_rect: None,
-            screen_inner_rect: DeviceIntRect::zero(),
             clip_rect: clip_rect,
         }
     }
 }
 
-#[derive(Clone, Debug)]
+#[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(ClipInfo),
 
     /// Transforms it's content, but doesn't clip it. Can also be adjusted
@@ -76,17 +65,17 @@ pub enum NodeType {
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning constraints.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
 }
 
 /// Contains information common among all types of ClipScrollTree nodes.
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub struct ClipScrollNode {
     /// Size of the content inside the scroll region (in logical pixels)
     pub content_size: LayerSize,
 
     /// Viewing rectangle in the coordinate system of the parent reference frame.
     pub local_viewport_rect: LayerRect,
 
     /// Clip rect of this node - typically the same as viewport rect, except
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -372,20 +372,20 @@ impl ClipScrollTree {
 
     fn print_node(&self, id: &ClipId, pt: &mut PrintTree) {
         let node = self.nodes.get(id).unwrap();
 
         match node.node_type {
             NodeType::Clip(ref info) => {
                 pt.new_level("Clip".to_owned());
                 pt.add_item(format!("screen_bounding_rect: {:?}", info.screen_bounding_rect));
-                pt.add_item(format!("screen_inner_rect: {:?}", info.screen_inner_rect));
 
-                pt.new_level(format!("Clip Sources [{}]", info.clip_sources.len()));
-                for source in &info.clip_sources {
+                let clips = info.clip_sources.clips();
+                pt.new_level(format!("Clip Sources [{}]", clips.len()));
+                for source in clips {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.transform));
             }
             NodeType::ScrollFrame(scrolling_info) => {
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -4,22 +4,22 @@
 
 use api::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF};
 use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
 use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
 use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, MixBlendMode, PipelineId};
 use api::{PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
 use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
 use api::{TransformStyle, WorldPoint};
+use clip::ClipRegion;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
-use mask_cache::ClipRegion;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{ResourceCache, TiledImageMap};
 use scene::{Scene, SceneProperties};
 use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
 pub struct FrameId(pub u32);
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -6,20 +6,20 @@ use api::{BorderDetails, BorderDisplayIt
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontInstance, FontRenderMode};
 use api::{GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
 use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
 use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
+use clip::{ClipMode, ClipRegion, ClipSource, ClipSources};
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, HardwareCompositeOp};
-use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, RenderTask};
@@ -202,26 +202,21 @@ impl FrameBuilder {
                         container: PrimitiveContainer) -> PrimitiveIndex {
         self.create_clip_scroll_group_if_necessary(clip_and_scroll);
 
         let mut clip_sources = extra_clips.to_vec();
         if let &LocalClip::RoundedRect(_, _) = local_clip {
             clip_sources.push(ClipSource::Region(ClipRegion::create_for_local_clip(local_clip)))
         }
 
-        let clip_info = if !clip_sources.is_empty() {
-            Some(MaskCacheInfo::new(&clip_sources))
-        } else {
-            None
-        };
+        let clip_sources = ClipSources::new(clip_sources);
 
         let prim_index = self.prim_store.add_primitive(rect,
                                                        &local_clip.clip_rect(),
                                                        clip_sources,
-                                                       clip_info,
                                                        container);
 
         prim_index
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(&mut self,
                                       prim_index: PrimitiveIndex,
@@ -1369,17 +1364,17 @@ impl FrameBuilder {
         // A stack of the alpha batcher tasks. We create them on the way down,
         // and then actually populate with items and dependencies on the way up.
         let mut alpha_task_stack = Vec::new();
         // A map of "preserve-3d" contexts. We are baking these into render targets
         // and only compositing once we are out of "preserve-3d" hierarchy.
         // The stacking contexts that fall into this category are
         //  - ones with `ContextIsolation::Items`, for their actual items to be backed
         //  - immediate children of `ContextIsolation::Items`
-        let mut preserve_3d_map: FastHashMap<StackingContextIndex, RenderTaskId> = FastHashMap::default();
+        let mut preserve_3d_map_stack: Vec<FastHashMap<StackingContextIndex, RenderTaskId>> = Vec::new();
         // The plane splitter stack, using a simple BSP tree.
         let mut splitter_stack = Vec::new();
 
         debug!("build_render_task()");
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
@@ -1401,16 +1396,17 @@ impl FrameBuilder {
                         alpha_task_stack.push(current_task);
                         current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
                     }
 
                     if parent_isolation == Some(ContextIsolation::Items) ||
                        stacking_context.isolation == ContextIsolation::Items {
                         if parent_isolation != Some(ContextIsolation::Items) {
                             splitter_stack.push(BspSplitter::new());
+                            preserve_3d_map_stack.push(FastHashMap::default());
                         }
                         alpha_task_stack.push(current_task);
                         current_task = RenderTask::new_dynamic_alpha_batch(stacking_context_rect);
                         //Note: technically, we shouldn't make a new alpha task for "preserve-3d" contexts
                         // that have no child items (only other stacking contexts). However, we don't know if
                         // there are any items at this time (in `PushStackingContext`).
                         //Note: the reason we add the polygon for splitting during `Push*` as opposed to `Pop*`
                         // is because we need to preserve the order of drawing for planes that match together.
@@ -1485,43 +1481,43 @@ impl FrameBuilder {
                     }
 
                     if parent_isolation == Some(ContextIsolation::Items) ||
                        stacking_context.isolation == ContextIsolation::Items {
                         //Note: we don't register the dependent tasks here. It's only done
                         // when we are out of the `preserve-3d` branch (see the code below),
                         // since this is only where the parent task is known.
                         let current_task_id = render_tasks.add(current_task);
-                        preserve_3d_map.insert(stacking_context_index, current_task_id);
+                        preserve_3d_map_stack.last_mut().unwrap().insert(stacking_context_index, current_task_id);
                         current_task = alpha_task_stack.pop().unwrap();
                     }
 
                     if parent_isolation != Some(ContextIsolation::Items) &&
                        stacking_context.isolation == ContextIsolation::Items {
                         debug!("\tsplitter[{}]: flush", splitter_stack.len());
                         let mut splitter = splitter_stack.pop().unwrap();
                         // Flush the accumulated plane splits onto the task tree.
                         // Notice how this is done before splitting in order to avoid duplicate tasks.
-                        current_task.children.extend(preserve_3d_map.values().cloned());
+                        current_task.children.extend(preserve_3d_map_stack.last().unwrap().values().cloned());
                         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
                         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
                             let sc_index = StackingContextIndex(poly.anchor);
-                            let task_id = preserve_3d_map[&sc_index];
+                            let task_id = preserve_3d_map_stack.last().unwrap()[&sc_index];
                             debug!("\t\tproduce {:?} -> {:?} for {:?}", sc_index, poly, task_id);
                             let pp = &poly.points;
                             let gpu_blocks = [
                                 [pp[0].x, pp[0].y, pp[0].z, pp[1].x].into(),
                                 [pp[1].y, pp[1].z, pp[2].x, pp[2].y].into(),
                                 [pp[2].z, pp[3].x, pp[3].y, pp[3].z].into(),
                             ];
                             let handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
                             let item = AlphaRenderItem::SplitComposite(sc_index, task_id, handle, next_z);
                             current_task.as_alpha_batch_mut().items.push(item);
                         }
-                        preserve_3d_map.clear();
+                        preserve_3d_map_stack.pop();
                         next_z += 1;
                     }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     if !self.stacking_context_store[stacking_context_index.0].is_visible {
                         continue;
                     }
@@ -1553,17 +1549,17 @@ impl FrameBuilder {
                             next_z += 1;
                         }
                     }
                 }
             }
         }
 
         debug_assert!(alpha_task_stack.is_empty());
-        debug_assert!(preserve_3d_map.is_empty());
+        debug_assert!(preserve_3d_map_stack.is_empty());
         render_tasks.add(current_task)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  gpu_cache: &mut GpuCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &mut ClipScrollTree,
@@ -1762,41 +1758,20 @@ impl<'a> LayerRectCalculationAndCullingP
 
                 packed_layer.set_rect(&local_viewport_rect,
                                       self.screen_rect,
                                       self.device_pixel_ratio)
             } else {
                 None
             };
 
-            let inner_rect = match node_clip_info.screen_bounding_rect {
-                Some((_, rect)) => rect,
-                None => DeviceIntRect::zero(),
-            };
-            node_clip_info.screen_inner_rect = inner_rect;
-
-            let bounds = node_clip_info.mask_cache_info.update(&node_clip_info.clip_sources,
-                                                               &transform,
-                                                               self.gpu_cache,
-                                                               self.device_pixel_ratio);
-
-            node_clip_info.screen_inner_rect = bounds.inner.as_ref()
-               .and_then(|inner| inner.device_rect.intersection(&inner_rect))
-               .unwrap_or(DeviceIntRect::zero());
-
-            for clip_source in &node_clip_info.clip_sources {
-                if let Some(mask) = clip_source.image_mask() {
-                    // We don't add the image mask for resolution, because
-                    // layer masks are resolved later.
-                    self.resource_cache.request_image(mask.image,
-                                                      ImageRendering::Auto,
-                                                      None,
-                                                      self.gpu_cache);
-                }
-            }
+            node_clip_info.clip_sources.update(&transform,
+                                               self.gpu_cache,
+                                               self.resource_cache,
+                                               self.device_pixel_ratio);
         }
     }
 
     fn recalculate_clip_scroll_groups(&mut self) {
         debug!("recalculate_clip_scroll_groups");
         for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
             let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
             let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
@@ -1895,34 +1870,30 @@ impl<'a> LayerRectCalculationAndCullingP
             current_id = node.parent;
 
             let clip = match node.node_type {
                 NodeType::ReferenceFrame(ref info) => {
                     // if the transform is non-aligned, bake the next LCCR into the clip mask
                     next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
                     continue
                 },
-                NodeType::Clip(ref clip) if clip.mask_cache_info.is_masking() => clip,
+                NodeType::Clip(ref clip) if clip.clip_sources.is_masking() => clip,
                 _ => continue,
             };
 
             // apply the screen bounds of the clip node
             //Note: these are based on the local combined viewport, so can be tighter
             if let Some((_kind, ref screen_rect)) = clip.screen_bounding_rect {
                 bounding_rect = match bounding_rect.intersection(screen_rect) {
                     Some(rect) => rect,
                     None => return None,
                 }
             }
 
-            let clip_info = if next_node_needs_region_mask {
-                clip.mask_cache_info.clone()
-            } else {
-                clip.mask_cache_info.strip_aligned()
-            };
+            let clip_info = clip.clip_sources.clone_mask_cache_info(next_node_needs_region_mask);
 
             // apply the outer device bounds of the clip stack
             if let Some(ref outer) = clip_info.bounds.outer {
                 bounding_rect = match bounding_rect.intersection(&outer.device_rect) {
                     Some(rect) => rect,
                     None => return None,
                 }
             }
@@ -2001,50 +1972,53 @@ impl<'a> LayerRectCalculationAndCullingP
                                                                    display_list,
                                                                    TextRunMode::Normal,
                                                                    &mut self.render_tasks);
 
             stacking_context.screen_bounds = stacking_context.screen_bounds.union(&prim_screen_rect);
             stacking_context.isolated_items_bounds = stacking_context.isolated_items_bounds.union(&prim_local_rect);
 
             // Try to create a mask if we may need to.
-            if !self.current_clip_stack.is_empty() || prim_metadata.clip_cache_info.is_some() {
+            let clip_task = if prim_metadata.clips.is_masking() {
+                let info = prim_metadata.clips.clone_mask_cache_info(false);
+
+                // Take into account the actual clip info of the primitive, and
+                // mutate the current bounds accordingly.
+                let mask_rect = match info.bounds.outer {
+                    Some(ref outer) => {
+                        match prim_screen_rect.intersection(&outer.device_rect) {
+                            Some(rect) => rect,
+                            None => continue,
+                        }
+                    }
+                    _ => prim_screen_rect,
+                };
+
+                let extra = (packed_layer_index, info);
+
+                RenderTask::new_mask(None,
+                                     mask_rect,
+                                     &self.current_clip_stack,
+                                     Some(extra),
+                                     prim_screen_rect)
+            } else if !self.current_clip_stack.is_empty() {
                 // If the primitive doesn't have a specific clip, key the task ID off the
                 // stacking context. This means that two primitives which are only clipped
                 // by the stacking context stack can share clip masks during render task
                 // assignment to targets.
-                let (cache_key, mask_rect, extra) = match prim_metadata.clip_cache_info {
-                    Some(ref info) => {
-                        // Take into account the actual clip info of the primitive, and
-                        // mutate the current bounds accordingly.
-                        let mask_rect = match info.bounds.outer {
-                            Some(ref outer) => {
-                                match prim_screen_rect.intersection(&outer.device_rect) {
-                                    Some(rect) => rect,
-                                    None => continue,
-                                }
-                            }
-                            _ => prim_screen_rect,
-                        };
-                        (None,
-                         mask_rect,
-                         Some((packed_layer_index, info.strip_aligned())))
-                    }
-                    None => {
-                        (Some(clip_and_scroll.clip_node_id()),
-                         clip_bounds,
-                         None)
-                    }
-                };
-                let clip_task = RenderTask::new_mask(cache_key,
-                                                     mask_rect,
-                                                     &self.current_clip_stack,
-                                                     extra);
-                let render_tasks = &mut self.render_tasks;
-                prim_metadata.clip_task_id = clip_task.map(|clip_task| {
-                    render_tasks.add(clip_task)
-                });
-            }
+                RenderTask::new_mask(Some(clip_and_scroll.clip_node_id()),
+                                     clip_bounds,
+                                     &self.current_clip_stack,
+                                     None,
+                                     prim_screen_rect)
+            } else {
+                None
+            };
+
+            let render_tasks = &mut self.render_tasks;
+            prim_metadata.clip_task_id = clip_task.map(|clip_task| {
+                render_tasks.add(clip_task)
+            });
 
             self.profile_counters.visible_primitives.inc();
         }
     }
 }
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -45,16 +45,17 @@ extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
 extern crate thread_profiler;
 
 mod border;
+mod clip;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
 mod device;
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -1,116 +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::{BorderRadius, ComplexClipRegion, DeviceIntRect, ImageMask, LayerPoint, LayerRect};
-use api::{LayerSize, LayerToWorldTransform, LocalClip};
+use api::{LayerSize, LayerToWorldTransform};
 use border::BorderCornerClipSource;
+use clip::{ClipMode, ClipSource};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{CLIP_DATA_GPU_BLOCKS, ClipData, ImageMaskData};
 use util::{ComplexClipRegionHelpers, TransformedRect};
-use std::ops::Not;
 
 const MAX_CLIP: f32 = 1000000.0;
 
-#[derive(Clone, Debug)]
-pub struct ClipRegion {
-    pub origin: LayerPoint,
-    pub main: LayerRect,
-    pub image_mask: Option<ImageMask>,
-    pub complex_clips: Vec<ComplexClipRegion>,
-}
-
-impl ClipRegion {
-    pub fn create_for_clip_node(rect: LayerRect,
-                                mut complex_clips: Vec<ComplexClipRegion>,
-                                mut image_mask: Option<ImageMask>)
-                                -> ClipRegion {
-        // All the coordinates we receive are relative to the stacking context, but we want
-        // to convert them to something relative to the origin of the clip.
-        let negative_origin = -rect.origin.to_vector();
-        if let Some(ref mut image_mask) = image_mask {
-            image_mask.rect = image_mask.rect.translate(&negative_origin);
-        }
-
-        for complex_clip in complex_clips.iter_mut() {
-            complex_clip.rect = complex_clip.rect.translate(&negative_origin);
-        }
-
-        ClipRegion {
-            origin: rect.origin,
-            main: LayerRect::new(LayerPoint::zero(), rect.size),
-            image_mask,
-            complex_clips,
-        }
-    }
-
-    pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
-        let complex_clips = match local_clip {
-            &LocalClip::Rect(_) => Vec::new(),
-            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
-        };
-        ClipRegion::create_for_clip_node(*local_clip.clip_rect(), complex_clips, None)
-    }
-
-    pub fn create_for_local_clip(local_clip: &LocalClip) -> ClipRegion {
-        let complex_clips = match local_clip {
-            &LocalClip::Rect(_) => Vec::new(),
-            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
-        };
-
-        ClipRegion {
-            origin: LayerPoint::zero(),
-            main: *local_clip.clip_rect(),
-            image_mask: None,
-            complex_clips,
-        }
-    }
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum ClipMode {
-    Clip,           // Pixels inside the region are visible.
-    ClipOut,        // Pixels outside the region are visible.
-}
-
-impl Not for ClipMode {
-    type Output = ClipMode;
-
-    fn not(self) -> ClipMode {
-        match self {
-            ClipMode::Clip => ClipMode::ClipOut,
-            ClipMode::ClipOut => ClipMode::Clip
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub enum ClipSource {
-    Complex(LayerRect, f32, ClipMode),
-    Region(ClipRegion),
-    /// TODO(gw): This currently only handles dashed style
-    /// clips, where the border style is dashed for both
-    /// adjacent border edges. Expand to handle dotted style
-    /// and different styles per edge.
-    BorderCorner(BorderCornerClipSource),
-}
-
-impl ClipSource {
-    pub fn image_mask(&self) -> Option<ImageMask> {
-        match *self {
-            ClipSource::Complex(..) |
-            ClipSource::BorderCorner(..) => None,
-            ClipSource::Region(ref region) => region.image_mask,
-        }
-    }
-}
-
 #[derive(Debug, Copy, Clone)]
 pub struct ClipAddressRange {
     pub location: GpuCacheHandle,
     item_count: usize,
 }
 
 impl ClipAddressRange {
     fn new(count: usize) -> Self {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -4,19 +4,19 @@
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
 use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
+use clip::{ClipMode, ClipSources};
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
-use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
 use resource_cache::{ImageProperties, ResourceCache};
 use std::{mem, usize};
 use util::{pack_as_float, TransformedRect, recycle_vec};
 
 
 pub const CLIP_DATA_GPU_BLOCKS: usize = 10;
@@ -129,18 +129,17 @@ impl GpuCacheHandle {
         address.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + address.u as i32
     }
 }
 
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
     pub opacity: PrimitiveOpacity,
-    pub clips: Vec<ClipSource>,
-    pub clip_cache_info: Option<MaskCacheInfo>,
+    pub clips: ClipSources,
     pub prim_kind: PrimitiveKind,
     pub cpu_prim_index: SpecificPrimitiveIndex,
     pub gpu_location: GpuCacheHandle,
     // An optional render task that is a dependency of
     // drawing this primitive. For instance, box shadows
     // use this to draw a portion of the box shadow to
     // a render target to reduce the number of pixels
     // that the box-shadow shader needs to run on. For
@@ -814,28 +813,26 @@ impl PrimitiveStore {
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
             cpu_lines: recycle_vec(self.cpu_lines),
         }
     }
 
     pub fn add_primitive(&mut self,
                          local_rect: &LayerRect,
                          local_clip_rect: &LayerRect,
-                         clips: Vec<ClipSource>,
-                         clip_info: Option<MaskCacheInfo>,
+                         clips: ClipSources,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.cpu_metadata.len();
         self.cpu_bounding_rects.push(None);
 
         let metadata = match container {
             PrimitiveContainer::Rectangle(rect) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::from_alpha(rect.color.a),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Rectangle,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
@@ -843,119 +840,112 @@ impl PrimitiveStore {
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
             PrimitiveContainer::Line(line) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Line,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_lines.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_lines.push(line);
                 metadata
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
             PrimitiveContainer::TextShadow(text_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::TextShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_text_shadows.push(text_shadow);
                 metadata
             }
             PrimitiveContainer::Image(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::YuvImage(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::opaque(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::YuvImage,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_yuv_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_borders.push(border_cpu);
                 metadata
             }
             PrimitiveContainer::AlignedGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AlignedGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
@@ -963,17 +953,16 @@ impl PrimitiveStore {
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::AngleGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
@@ -981,34 +970,32 @@ impl PrimitiveStore {
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::RadialGradient(radial_gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::RadialGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_radial_gradients.push(radial_gradient_cpu);
                 metadata
             }
             PrimitiveContainer::BoxShadow(box_shadow) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
-                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::BoxShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_box_shadows.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task_id: None,
                     clip_task_id: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
@@ -1083,32 +1070,20 @@ impl PrimitiveStore {
                                              device_pixel_ratio,
                                              display_list,
                                              TextRunMode::Shadow,
                                              render_tasks);
             }
         }
 
         let metadata = &mut self.cpu_metadata[prim_index.0];
-
-        if let Some(ref mut clip_info) = metadata.clip_cache_info {
-            clip_info.update(&metadata.clips, layer_transform, gpu_cache, device_pixel_ratio);
-
-            //TODO-LCCR: we could tighten up the `local_clip_rect` here
-            // but that would require invalidating the whole GPU block
-
-            for clip in &metadata.clips {
-                if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
-                    resource_cache.request_image(mask.image,
-                                                 ImageRendering::Auto,
-                                                 None,
-                                                 gpu_cache);
-                }
-            }
-        }
+        metadata.clips.update(layer_transform,
+                              gpu_cache,
+                              resource_cache,
+                              device_pixel_ratio);
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
             PrimitiveKind::Border |
             PrimitiveKind::Line => {}
             PrimitiveKind::BoxShadow => {
                 // TODO(gw): Account for zoom factor!
                 // Here, we calculate the size of the patch required in order
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -239,17 +239,18 @@ impl RenderTask {
             location: RenderTaskLocation::Dynamic(None, screen_rect.size),
             kind: RenderTaskKind::Readback(screen_rect),
         }
     }
 
     pub fn new_mask(key: Option<ClipId>,
                     task_rect: DeviceIntRect,
                     raw_clips: &[ClipWorkItem],
-                    extra_clip: Option<ClipWorkItem>)
+                    extra_clip: Option<ClipWorkItem>,
+                    prim_rect: DeviceIntRect)
                     -> Option<RenderTask> {
         // Filter out all the clip instances that don't contribute to the result
         let mut inner_rect = Some(task_rect);
         let clips: Vec<_> = raw_clips.iter()
                                      .chain(extra_clip.iter())
                                      .filter(|&&(_, ref clip_info)| {
             // If this clip does not contribute to a mask, then ensure
             // it gets filtered out here. Otherwise, if a mask is
@@ -278,23 +279,30 @@ impl RenderTask {
         }
 
         // TODO(gw): This optimization is very conservative for now.
         //           For now, only draw optimized geometry if it is
         //           a single aligned rect mask with rounded corners.
         //           In the future, we'll expand this to handle the
         //           more complex types of clip mask geometry.
         let mut geometry_kind = MaskGeometryKind::Default;
-        if inner_rect.is_some() && clips.len() == 1 {
-            let (_, ref info) = clips[0];
-            if info.border_corners.is_empty() &&
-               info.image.is_none() &&
-               info.complex_clip_range.get_count() == 1 &&
-               info.layer_clip_range.get_count() == 0 {
-                geometry_kind = MaskGeometryKind::CornersOnly;
+        if let Some(inner_rect) = inner_rect {
+            // If the inner rect completely contains the primitive
+            // rect, then this mask can't affect the primitive.
+            if inner_rect.contains_rect(&prim_rect) {
+                return None;
+            }
+            if clips.len() == 1 {
+                let (_, ref info) = clips[0];
+                if info.border_corners.is_empty() &&
+                   info.image.is_none() &&
+                   info.complex_clip_range.get_count() == 1 &&
+                   info.layer_clip_range.get_count() == 0 {
+                    geometry_kind = MaskGeometryKind::CornersOnly;
+                }
             }
         }
 
         Some(RenderTask {
             cache_key: key.map(RenderTaskKey::CacheMask),
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, task_rect.size),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {