clip sources. try: -b do -p linux64 -u all[linux64-qr] -t none
authorGlenn Watson <gwatson@mozilla.com>
Wed, 06 Sep 2017 16:54:06 +1000
changeset 1284643 4434224b2b0b2b310b6bd58fcac6caae23d4935a
parent 1284202 f64e2b4dcf5eec0b4ad456c149680a67b7c26dc4
child 1574508 b0e9fef31451fb811a159ed092cfc2b051bbac2c
push id219117
push usergwatson@mozilla.com
push dateWed, 06 Sep 2017 06:58:28 +0000
treeherdertry@4434224b2b0b [default view] [failures only]
milestone57.0a1
clip sources. try: -b do -p linux64 -u all[linux64-qr] -t none
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_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
gfx/webrender/src/util.rs
--- 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_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.
@@ -223,17 +223,17 @@ impl NormalBorderHelpers for NormalBorde
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
                                    rect: &LayerRect,
                                    border: &NormalBorder,
                                    widths: &BorderWidths,
                                    clip_and_scroll: ClipAndScrollInfo,
                                    local_clip: &LocalClip,
                                    corner_instances: [BorderCornerInstance; 4],
-                                   extra_clips: &[ClipSource]) {
+                                   clip_sources: Vec<ClipSource>) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
@@ -269,17 +269,17 @@ impl FrameBuilder {
                   radius.bottom_left.width,
                   radius.bottom_left.height ].into(),
             ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            local_clip,
-                           extra_clips,
+                           clip_sources,
                            PrimitiveContainer::Border(prim_cpu));
     }
 
     // TODO(gw): This allows us to move border types over to the
     // simplified shader model one at a time. Once all borders
     // are converted, this can be removed, along with the complex
     // border code path.
     pub fn add_normal_border(&mut self,
@@ -420,17 +420,17 @@ impl FrameBuilder {
             }
 
             self.add_normal_border_primitive(rect,
                                              border,
                                              widths,
                                              clip_and_scroll,
                                              local_clip,
                                              corner_instances,
-                                             &extra_clips);
+                                             extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
                     scale_factor_0: f32,
                     scale_factor_1: f32,
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/clip.rs
@@ -0,0 +1,158 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use api::{BorderRadius, ComplexClipRegion, ImageMask, ImageRendering};
+use api::{LayerPoint, LayerRect, LayerToWorldTransform, LocalClip};
+use 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)
+    }
+}
+
+#[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(Debug)]
+pub enum ClipSource {
+    Rectangle(LayerRect),
+    RoundedRectangle(LayerRect, BorderRadius, ClipMode),
+    Image(ImageMask),
+    /// 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 From<ClipRegion> for ClipSources {
+    fn from(region: ClipRegion) -> ClipSources {
+        let mut clips = Vec::new();
+
+        if let Some(info) = region.image_mask {
+            clips.push(ClipSource::Image(info));
+        }
+
+        clips.push(ClipSource::Rectangle(region.main));
+
+        for complex in region.complex_clips {
+            clips.push(ClipSource::RoundedRectangle(complex.rect, complex.radii, ClipMode::Clip));
+        }
+
+        ClipSources::new(clips)
+    }
+}
+
+#[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::Image(ref mask) = *clip {
+                resource_cache.request_image(mask.image,
+                                             ImageRendering::Auto,
+                                             None,
+                                             gpu_cache);
+            }
+        }
+    }
+
+    pub fn is_masking(&self) -> bool {
+        self.mask_cache_info.is_masking()
+    }
+
+    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, 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::from(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
@@ -1,25 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
+use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontInstance, FontRenderMode};
 use api::{GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
 use api::{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};
@@ -193,35 +193,30 @@ impl FrameBuilder {
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     fn create_primitive(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
                         rect: &LayerRect,
                         local_clip: &LocalClip,
-                        extra_clips: &[ClipSource],
+                        mut clip_sources: Vec<ClipSource>,
                         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)))
+        if let &LocalClip::RoundedRect(main, region) = local_clip {
+            clip_sources.push(ClipSource::Rectangle(main));
+            clip_sources.push(ClipSource::RoundedRectangle(region.rect, region.radii, ClipMode::Clip));
         }
 
-        let clip_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,
@@ -242,22 +237,22 @@ impl FrameBuilder {
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(&mut self,
                          clip_and_scroll: ClipAndScrollInfo,
                          rect: &LayerRect,
                          local_clip: &LocalClip,
-                         extra_clips: &[ClipSource],
+                         clip_sources: Vec<ClipSource>,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.create_primitive(clip_and_scroll,
                                                rect,
                                                local_clip,
-                                               extra_clips,
+                                               clip_sources,
                                                container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
 
         prim_index
     }
 
     pub fn create_clip_scroll_group(&mut self, info: ClipAndScrollInfo) -> ClipScrollGroupIndex {
@@ -445,17 +440,17 @@ impl FrameBuilder {
 
         // Create an empty text-shadow primitive. Insert it into
         // the draw lists immediately so that it will be drawn
         // before any visual text elements that are added as
         // part of this text-shadow context.
         let prim_index = self.add_primitive(clip_and_scroll,
                                             &LayerRect::zero(),
                                             local_clip,
-                                            &[],
+                                            Vec::new(),
                                             PrimitiveContainer::TextShadow(prim));
 
         self.shadow_prim_stack.push(prim_index);
     }
 
     pub fn pop_text_shadow(&mut self) {
         let prim_index = self.shadow_prim_stack
                              .pop()
@@ -479,17 +474,17 @@ impl FrameBuilder {
                                flags: PrimitiveFlags) {
         let prim = RectanglePrimitive {
             color: *color,
         };
 
         let prim_index = self.add_primitive(clip_and_scroll,
                                             rect,
                                             local_clip,
-                                            &[],
+                                            Vec::new(),
                                             PrimitiveContainer::Rectangle(prim));
 
         match flags {
             PrimitiveFlags::None => {}
             PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
                 self.scrollbar_prims.push(ScrollbarPrimitive {
                     prim_index,
                     clip_id,
@@ -535,24 +530,24 @@ impl FrameBuilder {
             }
         }
         for shadow in fast_text_shadow_prims {
             let mut line = line.clone();
             line.color = shadow.color;
             self.add_primitive(clip_and_scroll,
                                &new_rect.translate(&shadow.offset),
                                local_clip,
-                               &[],
+                               Vec::new(),
                                PrimitiveContainer::Line(line));
         }
 
         let prim_index = self.create_primitive(clip_and_scroll,
                                                &new_rect,
                                                local_clip,
-                                               &[],
+                                               Vec::new(),
                                                PrimitiveContainer::Line(line));
 
         if color.a > 0.0 {
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
@@ -830,17 +825,17 @@ impl FrameBuilder {
         };
 
         let prim = if aligned {
             PrimitiveContainer::AlignedGradient(gradient_cpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu)
         };
 
-        self.add_primitive(clip_and_scroll, &rect, local_clip, &[], prim);
+        self.add_primitive(clip_and_scroll, &rect, local_clip, Vec::new(), prim);
     }
 
     pub fn add_radial_gradient(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: LayerRect,
                                local_clip: &LocalClip,
                                start_center: LayerPoint,
                                start_radius: f32,
@@ -862,17 +857,17 @@ impl FrameBuilder {
                 [start_radius, end_radius, ratio_xy, pack_as_float(extend_mode as u32)].into(),
                 [tile_size.width, tile_size.height, tile_repeat.width, tile_repeat.height].into(),
             ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            local_clip,
-                           &[],
+                           Vec::new(),
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu));
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     run_offset: LayoutVector2D,
                     rect: LayerRect,
                     local_clip: &LocalClip,
@@ -974,26 +969,26 @@ impl FrameBuilder {
                 text_prim.offset += shadow_prim.shadow.offset;
                 fast_text_shadow_prims.push(text_prim);
             }
         }
         for text_prim in fast_text_shadow_prims {
             self.add_primitive(clip_and_scroll,
                                &rect.translate(&text_prim.offset),
                                local_clip,
-                               &[],
+                               Vec::new(),
                                PrimitiveContainer::TextRun(text_prim));
         }
 
         // Create (and add to primitive store) the primitive that will be
         // used for both the visual element and also the shadow(s).
         let prim_index = self.create_primitive(clip_and_scroll,
                                                &rect,
                                                local_clip,
-                                               &[],
+                                               Vec::new(),
                                                PrimitiveContainer::TextRun(prim));
 
         // Only add a visual element if it can contribute to the scene.
         if color.a > 0.0 {
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         // Now add this primitive index to all the currently active text shadow
@@ -1031,27 +1026,28 @@ impl FrameBuilder {
             BoxShadowClipMode::Outset |
             BoxShadowClipMode::None => (ClipMode::Clip, bs_rect),
             BoxShadowClipMode::Inset => (ClipMode::ClipOut, *box_bounds),
         };
 
         let box_clip_mode = !bs_clip_mode;
 
         // Clip the inside and then the outside of the box.
-        let extra_clips = [ClipSource::Complex(bs_rect, border_radius, bs_clip_mode),
-                           ClipSource::Complex(*box_bounds, border_radius, box_clip_mode)];
+        let border_radius = BorderRadius::uniform(border_radius);
+        let extra_clips = vec![ClipSource::RoundedRectangle(bs_rect, border_radius, bs_clip_mode),
+                               ClipSource::RoundedRectangle(*box_bounds, border_radius, box_clip_mode)];
 
         let prim = RectanglePrimitive {
             color: *color,
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect_to_draw,
                            local_clip,
-                           &extra_clips,
+                           extra_clips,
                            PrimitiveContainer::Rectangle(prim));
     }
 
     pub fn add_box_shadow(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
                           box_bounds: &LayerRect,
                           local_clip: &LocalClip,
                           box_offset: &LayerVector2D,
@@ -1192,36 +1188,36 @@ impl FrameBuilder {
                 // need a clip out of the center box.
                 let extra_clip_mode = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => ClipMode::ClipOut,
                     BoxShadowClipMode::Inset => ClipMode::Clip,
                 };
 
                 let mut extra_clips = Vec::new();
                 if border_radius >= 0.0 {
-                    extra_clips.push(ClipSource::Complex(*box_bounds,
-                                                border_radius,
-                                                extra_clip_mode));
+                    extra_clips.push(ClipSource::RoundedRectangle(*box_bounds,
+                                                                  BorderRadius::uniform(border_radius),
+                                                                  extra_clip_mode));
                 }
 
                 let prim_cpu = BoxShadowPrimitiveCpu {
                     src_rect: *box_bounds,
                     bs_rect,
                     color: *color,
                     blur_radius,
                     border_radius,
                     edge_size,
                     inverted,
                     rects,
                 };
 
                 self.add_primitive(clip_and_scroll,
                                    &outer_rect,
                                    local_clip,
-                                   extra_clips.as_slice(),
+                                   extra_clips,
                                    PrimitiveContainer::BoxShadow(prim_cpu));
             }
         }
     }
 
     pub fn add_image(&mut self,
                      clip_and_scroll: ClipAndScrollInfo,
                      rect: LayerRect,
@@ -1245,17 +1241,17 @@ impl FrameBuilder {
                             tile_spacing.height ].into(),
                             sub_rect_block,
                         ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            local_clip,
-                           &[],
+                           Vec::new(),
                            PrimitiveContainer::Image(prim_cpu));
     }
 
     pub fn add_yuv_image(&mut self,
                          clip_and_scroll: ClipAndScrollInfo,
                          rect: LayerRect,
                          clip_rect: &LocalClip,
                          yuv_data: YuvData,
@@ -1276,17 +1272,17 @@ impl FrameBuilder {
             color_space,
             image_rendering,
             gpu_block: [rect.size.width, rect.size.height, 0.0, 0.0].into(),
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            clip_rect,
-                           &[],
+                           Vec::new(),
                            PrimitiveContainer::YuvImage(prim_cpu));
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
                                                 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::{DeviceIntRect, ImageMask, LayerPoint, LayerRect};
+use api::{LayerSize, LayerToWorldTransform};
 use border::BorderCornerClipSource;
+use clip::{ClipMode, ClipSource};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{CLIP_DATA_GPU_BLOCKS, ClipData, ImageMaskData};
-use util::{ComplexClipRegionHelpers, TransformedRect};
-use std::ops::Not;
+use util::{extract_inner_rect_safe, TransformedRect};
 
 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 {
@@ -203,27 +109,26 @@ impl MaskCacheInfo {
         let mut border_corners = Vec::new();
         let mut complex_clip_count = 0;
         let mut layer_clip_count = 0;
 
         // Work out how much clip data space we need to allocate
         // and if we have an image mask.
         for clip in clips {
             match *clip {
-                ClipSource::Complex(..) => {
+                ClipSource::RoundedRectangle(..) => {
                     complex_clip_count += 1;
                 }
-                ClipSource::Region(ref region) => {
-                    if let Some(info) = region.image_mask {
-                        debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
-                        image = Some((info, GpuCacheHandle::new()));
-                    }
-                    complex_clip_count += region.complex_clips.len();
+                ClipSource::Rectangle(..) => {
                     layer_clip_count += 1;
                 }
+                ClipSource::Image(image_mask) => {
+                    debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
+                    image = Some((image_mask, GpuCacheHandle::new()));
+                }
                 ClipSource::BorderCorner(ref source) => {
                     border_corners.push((source.clone(), GpuCacheHandle::new()));
                 }
             }
         }
 
         MaskCacheInfo {
             complex_clip_range: ClipAddressRange::new(complex_clip_count),
@@ -238,112 +143,89 @@ impl MaskCacheInfo {
     }
 
     pub fn update(&mut self,
                   sources: &[ClipSource],
                   transform: &LayerToWorldTransform,
                   gpu_cache: &mut GpuCache,
                   device_pixel_ratio: f32)
                   -> &MaskBounds {
-
         // Step[1] - compute the local bounds
         //TODO: move to initialization stage?
         if self.bounds.inner.is_none() {
             let mut local_rect = Some(LayerRect::new(LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
                                                      LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP)));
-            let mut local_inner: Option<LayerRect> = None;
+            let mut local_inner = local_rect;
             let mut has_clip_out = false;
             let has_border_clip = !self.border_corners.is_empty();
 
             for source in sources {
                 match *source {
-                    ClipSource::Complex(rect, radius, mode) => {
+                    ClipSource::Image(ref mask) => {
+                        if !mask.repeat {
+                            local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
+                        }
+                        local_inner = None;
+                    }
+                    ClipSource::Rectangle(rect) => {
+                        local_rect = local_rect.and_then(|r| r.intersection(&rect));
+                        local_inner = local_inner.and_then(|r| r.intersection(&rect));
+                    }
+                    ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         // Once we encounter a clip-out, we just assume the worst
                         // case clip mask size, for now.
                         if mode == ClipMode::ClipOut {
                             has_clip_out = true;
                             break;
                         }
-                        local_rect = local_rect.and_then(|r| r.intersection(&rect));
-                        local_inner = ComplexClipRegion::new(rect, BorderRadius::uniform(radius))
-                                                        .get_inner_rect_safe();
-                    }
-                    ClipSource::Region(ref region) => {
-                        local_rect = local_rect.and_then(|r| r.intersection(&region.main));
-                        local_inner = match region.image_mask {
-                            Some(ref mask) => {
-                                if !mask.repeat {
-                                    local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
-                                }
-                                None
-                            },
-                            None => local_rect,
-                        };
+
+                        local_rect = local_rect.and_then(|r| r.intersection(rect));
 
-                        for clip in &region.complex_clips {
-                            local_rect = local_rect.and_then(|r| r.intersection(&clip.rect));
-                            local_inner = local_inner.and_then(|r| clip.get_inner_rect_safe()
-                                                                       .and_then(|ref inner| r.intersection(inner)));
-                        }
+                        let inner_rect = extract_inner_rect_safe(rect, radius);
+                        local_inner = local_inner.and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
                     }
                     ClipSource::BorderCorner{..} => {}
                 }
             }
 
             // Work out the type of mask geometry we have, based on the
             // list of clip sources above.
             self.bounds = if has_clip_out || has_border_clip {
                 // For clip-out, the mask rect is not known.
                 MaskBounds {
                     outer: None,
                     inner: Some(LayerRect::zero().into()),
                 }
             } else {
-                // TODO(gw): local inner is only valid if there's a single clip (for now).
-                // This can be improved in the future, with some proper
-                // rectangle region handling.
-                if sources.len() > 1 {
-                    local_inner = None;
-                }
-
                 MaskBounds {
                     outer: Some(local_rect.unwrap_or(LayerRect::zero()).into()),
                     inner: Some(local_inner.unwrap_or(LayerRect::zero()).into()),
                 }
             };
         }
 
         // Step[2] - update GPU cache data
 
         if let Some(block_count) = self.complex_clip_range.get_block_count() {
             if let Some(mut request) = gpu_cache.request(&mut self.complex_clip_range.location) {
                 for source in sources {
-                    match *source {
-                        ClipSource::Complex(rect, radius, mode) => {
-                            let data = ClipData::uniform(rect, radius, mode);
-                            data.write(&mut request);
-                        }
-                        ClipSource::Region(ref region) => {
-                            for clip in &region.complex_clips {
-                                let data = ClipData::from_clip_region(&clip);
-                                data.write(&mut request);
-                            }
-                        }
-                        ClipSource::BorderCorner{..} => {}
+                    if let ClipSource::RoundedRectangle(ref rect, ref radius, mode) = *source {
+                        let data = ClipData::rounded_rect(rect, radius, mode);
+                        data.write(&mut request);
                     }
                 }
                 assert_eq!(request.close(), block_count);
             }
         }
 
         if let Some(block_count) = self.layer_clip_range.get_block_count() {
             if let Some(mut request) = gpu_cache.request(&mut self.layer_clip_range.location) {
                 for source in sources {
-                    if let ClipSource::Region(ref region) = *source {
-                        let data = ClipData::uniform(region.main, 0.0, ClipMode::Clip);
+                    if let ClipSource::Rectangle(rect) = *source {
+                        let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
                         data.write(&mut request);
                     }
                 }
                 assert_eq!(request.close(), block_count);
             }
         }
 
         for &mut (ref mut border_source, ref mut gpu_location) in &mut self.border_corners {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,22 +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::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
-use api::{ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
+use api::{BorderRadius, ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
 use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
+use clip::{ClipMode, ClipSources};
 use 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
@@ -660,57 +659,56 @@ pub struct ClipData {
     rect: ClipRect,
     top_left: ClipCorner,
     top_right: ClipCorner,
     bottom_left: ClipCorner,
     bottom_right: ClipCorner,
 }
 
 impl ClipData {
-    pub fn from_clip_region(clip: &ComplexClipRegion) -> ClipData {
+    pub fn rounded_rect(rect: &LayerRect, radii: &BorderRadius, mode: ClipMode) -> ClipData {
         ClipData {
             rect: ClipRect {
-                rect: clip.rect,
-                // TODO(gw): Support other clip modes for regions?
-                mode: ClipMode::Clip as u32 as f32,
+                rect: *rect,
+                mode: mode as u32 as f32,
             },
             top_left: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x, clip.rect.origin.y),
-                    LayerSize::new(clip.radii.top_left.width, clip.radii.top_left.height)),
-                outer_radius_x: clip.radii.top_left.width,
-                outer_radius_y: clip.radii.top_left.height,
+                    LayerPoint::new(rect.origin.x, rect.origin.y),
+                    LayerSize::new(radii.top_left.width, radii.top_left.height)),
+                outer_radius_x: radii.top_left.width,
+                outer_radius_y: radii.top_left.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
             top_right: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x + clip.rect.size.width - clip.radii.top_right.width, clip.rect.origin.y),
-                    LayerSize::new(clip.radii.top_right.width, clip.radii.top_right.height)),
-                outer_radius_x: clip.radii.top_right.width,
-                outer_radius_y: clip.radii.top_right.height,
+                    LayerPoint::new(rect.origin.x + rect.size.width - radii.top_right.width, rect.origin.y),
+                    LayerSize::new(radii.top_right.width, radii.top_right.height)),
+                outer_radius_x: radii.top_right.width,
+                outer_radius_y: radii.top_right.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
             bottom_left: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x, clip.rect.origin.y + clip.rect.size.height - clip.radii.bottom_left.height),
-                    LayerSize::new(clip.radii.bottom_left.width, clip.radii.bottom_left.height)),
-                outer_radius_x: clip.radii.bottom_left.width,
-                outer_radius_y: clip.radii.bottom_left.height,
+                    LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.height),
+                    LayerSize::new(radii.bottom_left.width, radii.bottom_left.height)),
+                outer_radius_x: radii.bottom_left.width,
+                outer_radius_y: radii.bottom_left.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
             bottom_right: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x + clip.rect.size.width - clip.radii.bottom_right.width,
-                                    clip.rect.origin.y + clip.rect.size.height - clip.radii.bottom_right.height),
-                    LayerSize::new(clip.radii.bottom_right.width, clip.radii.bottom_right.height)),
-                outer_radius_x: clip.radii.bottom_right.width,
-                outer_radius_y: clip.radii.bottom_right.height,
+                    LayerPoint::new(rect.origin.x + rect.size.width - radii.bottom_right.width,
+                                    rect.origin.y + rect.size.height - radii.bottom_right.height),
+                    LayerSize::new(radii.bottom_right.width, radii.bottom_right.height)),
+                outer_radius_x: radii.bottom_right.width,
+                outer_radius_y: radii.bottom_right.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
         }
     }
 
     pub fn uniform(rect: LayerRect, radius: f32, mode: ClipMode) -> ClipData {
         ClipData {
@@ -814,28 +812,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 +839,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 +952,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 +969,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 +1069,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 {
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -262,31 +262,22 @@ impl TransformedRect {
 
 #[inline(always)]
 pub fn pack_as_float(value: u32) -> f32 {
     value as f32 + 0.5
 }
 
 
 pub trait ComplexClipRegionHelpers {
-    /// Return an aligned rectangle that is inside the clip region and doesn't intersect
-    /// any of the bounding rectangles of the rounded corners.
-    fn get_inner_rect_safe(&self) -> Option<LayoutRect>;
     /// Return the approximately largest aligned rectangle that is fully inside
     /// the provided clip region.
     fn get_inner_rect_full(&self) -> Option<LayoutRect>;
 }
 
 impl ComplexClipRegionHelpers for ComplexClipRegion {
-    fn get_inner_rect_safe(&self) -> Option<LayoutRect> {
-        // value of `k==1.0` is used for extraction of the corner rectangles
-        // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
-        extract_inner_rect_impl(&self.rect, &self.radii, 1.0)
-    }
-
     fn get_inner_rect_full(&self) -> Option<LayoutRect> {
         // this `k` optimal for a simple case of all border radii being equal
         let k = 1.0 - 0.5 * FRAC_1_SQRT_2; // could be nicely approximated to `0.3`
         extract_inner_rect_impl(&self.rect, &self.radii, k)
     }
 }
 
 #[inline]
@@ -305,16 +296,25 @@ fn extract_inner_rect_impl<U>(rect: &Typ
     if xl <= xr && yt <= yb {
         Some(TypedRect::new(TypedPoint2D::new(rect.origin.x + xl, rect.origin.y + yt),
              TypedSize2D::new(xr-xl, yb-yt)))
     } else {
         None
     }
 }
 
+/// Return an aligned rectangle that is inside the clip region and doesn't intersect
+/// any of the bounding rectangles of the rounded corners.
+pub fn extract_inner_rect_safe<U>(rect: &TypedRect<f32, U>,
+                                  radii: &BorderRadius) -> Option<TypedRect<f32, U>> {
+    // value of `k==1.0` is used for extraction of the corner rectangles
+    // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
+    extract_inner_rect_impl(rect, radii, 1.0)
+}
+
 /// Consumes the old vector and returns a new one that may reuse the old vector's allocated
 /// memory.
 pub fn recycle_vec<T>(mut old_vec: Vec<T>) -> Vec<T> {
     if old_vec.capacity() > 2 * old_vec.len() {
         // Avoid reusing the buffer if it is a lot larger than it needs to be. This prevents
         // a frame with exceptionally large allocations to cause subsequent frames to retain
         // more memory than they need.
         return Vec::with_capacity(old_vec.len());