Bug 1349692 - Update webrender to 5c2a9ff065665cb99b72809681b1b7d043363c28. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 28 Mar 2017 15:18:46 -0400
changeset 552604 e8070bd4cb1f9f1aa48791901b9a16ebaabff8d9
parent 552603 a82772358806e8fccdb8b9aa0545548932943868
child 552605 83bdae77243cb58608d130cb5a6a0aa3080cdbf8
push id51407
push userkgupta@mozilla.com
push dateTue, 28 Mar 2017 19:25:45 +0000
reviewersjrmuizel
bugs1349692
milestone55.0a1
Bug 1349692 - Update webrender to 5c2a9ff065665cb99b72809681b1b7d043363c28. r?jrmuizel MozReview-Commit-ID: JardJUOpkd4
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/res/cs_blur.fs.glsl
gfx/webrender/res/cs_blur.vs.glsl
gfx/webrender/res/cs_box_shadow.fs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_angle_gradient.fs.glsl
gfx/webrender/res/ps_angle_gradient.vs.glsl
gfx/webrender/res/ps_blend.fs.glsl
gfx/webrender/res/ps_blend.vs.glsl
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_box_shadow.fs.glsl
gfx/webrender/res/ps_box_shadow.vs.glsl
gfx/webrender/res/ps_cache_image.fs.glsl
gfx/webrender/res/ps_cache_image.vs.glsl
gfx/webrender/res/ps_composite.fs.glsl
gfx/webrender/res/ps_composite.vs.glsl
gfx/webrender/res/ps_gradient.fs.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_hardware_composite.fs.glsl
gfx/webrender/res/ps_hardware_composite.vs.glsl
gfx/webrender/res/ps_radial_gradient.fs.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_store.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/webgl_stubs.rs
gfx/webrender/src/webgl_types.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/lib.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 0794911f97cae92496fca992d7430da696fa24eb
+Latest Commit: 5c2a9ff065665cb99b72809681b1b7d043363c28
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,33 +1,34 @@
 [package]
 name = "webrender"
-version = "0.25.0"
+version = "0.26.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
-default = ["freetype-lib"]
+default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
+webgl = ["offscreen_gl_context", "webrender_traits/webgl"]
 
 [dependencies]
 app_units = "0.4"
 bincode = "1.0.0-alpha2"
 bit-set = "0.4"
 byteorder = "1.0"
 euclid = "0.11"
 fnv = "1.0"
 gleam = "0.4.1"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
-offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"]}
+offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"], optional = true}
 time = "0.1"
 threadpool = "1.3.2"
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
 gamma-lut = "0.1"
 thread_profiler = "0.1.1"
 
 [dev-dependencies]
--- a/gfx/webrender/res/cs_blur.fs.glsl
+++ b/gfx/webrender/res/cs_blur.fs.glsl
@@ -10,31 +10,31 @@
 // 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(sCache, vUv);
+    vec4 cache_sample = texture(sCacheRGBA8, vUv);
     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(sCache, vec3(st0, vUv.z));
+        vec4 color0 = texture(sCacheRGBA8, vec3(st0, vUv.z));
 
         vec2 st1 = clamp(vUv.xy - offset, vUvRect.xy, vUvRect.zw);
-        vec4 color1 = texture(sCache, vec3(st1, vUv.z));
+        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 = color;
+    oFragColor = dither(color);
 }
--- a/gfx/webrender/res/cs_blur.vs.glsl
+++ b/gfx/webrender/res/cs_blur.vs.glsl
@@ -35,17 +35,17 @@ void main(void) {
     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(sCache, 0).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;
--- a/gfx/webrender/res/cs_box_shadow.fs.glsl
+++ b/gfx/webrender/res/cs_box_shadow.fs.glsl
@@ -139,10 +139,10 @@ float color(vec2 pos, vec2 p0Rect, vec2 
 void main(void) {
     vec2 pos = vPos.xy;
     vec2 p0Rect = vBoxShadowRect.xy, p1Rect = vBoxShadowRect.zw;
     vec2 radii = vBorderRadii.xy;
     float sigma = vBlurRadius / 2.0;
     float value = color(pos, p0Rect, p1Rect, radii, sigma);
 
     value = max(value, 0.0);
-    oFragColor = vec4(1.0, 1.0, 1.0, vInverted == 1.0 ? 1.0 - value : value);
+    oFragColor = dither(vec4(1.0, 1.0, 1.0, vInverted == 1.0 ? 1.0 - value : value));
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -28,17 +28,18 @@
 #define BORDER_BOTTOM    3
 
 #define UV_NORMALIZED    uint(0)
 #define UV_PIXEL         uint(1)
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
-uniform sampler2DArray sCache;
+uniform sampler2DArray sCacheA8;
+uniform sampler2DArray sCacheRGBA8;
 
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
 
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_LAYER             13
 #define VECS_PER_RENDER_TASK        3
@@ -702,17 +703,17 @@ BoxShadow fetch_boxshadow(int index) {
     bs.bs_rect = texelFetchOffset(sData64, uv, 0, ivec2(1, 0));
     bs.color = texelFetchOffset(sData64, uv, 0, ivec2(2, 0));
     bs.border_radius_edge_size_blur_radius_inverted = texelFetchOffset(sData64, uv, 0, ivec2(3, 0));
 
     return bs;
 }
 
 void write_clip(vec2 global_pos, ClipArea area) {
-    vec2 texture_size = vec2(textureSize(sCache, 0).xy);
+    vec2 texture_size = vec2(textureSize(sCacheA8, 0).xy);
     vec2 uv = global_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
     vClipMaskUvBounds = area.task_bounds / texture_size.xyxy;
     vClipMaskUv = vec3(uv / texture_size, area.screen_origin_target_index.z);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
@@ -750,11 +751,20 @@ vec2 init_transform_fs(vec3 local_pos, v
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
-        all(inside) ? textureLod(sCache, vClipMaskUv, 0.0).r : 0.0;
+        all(inside) ? textureLod(sCacheA8, vClipMaskUv, 0.0).r : 0.0;
+}
+
+vec4 dither(vec4 color) {
+    const int matrix_mask = 7;
+
+    ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
+    float noise_factor = 4.0 / 255.0;
+    float noise = texelFetch(sDither, pos, 0).r * noise_factor;
+    return color + vec4(noise, noise, noise, 0);
 }
 #endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/ps_angle_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.fs.glsl
@@ -6,16 +6,15 @@ uniform sampler2D sGradients;
 
 void main(void) {
     vec2 texture_size = vec2(textureSize(sGradients, 0));
 
     // Either saturate or modulo the offset depending on repeat mode, then scale to number of
     // gradient color entries (texture width / 2).
     float x = mix(clamp(vOffset, 0.0, 1.0), fract(vOffset), vGradientRepeat) * 0.5 * texture_size.x;
 
-    // Start at the center of first color in the nearest 2-color entry, then offset with the
-    // fractional remainder to interpolate between the colors. Rely on texture clamping when
-    // outside of valid range.
     x = 2.0 * floor(x) + 0.5 + fract(x);
 
-    // Normalize the texture coordates so we can use texture() for bilinear filtering.
-    oFragColor = texture(sGradients, vec2(x, vGradientIndex) / texture_size);
+    // Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
+    // bits (vGradientIndex)
+    float y = vGradientIndex * 2.0 + 0.5 + 1.0 / 256.0;
+    oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
 }
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.vs.glsl
@@ -22,13 +22,13 @@ void main(void) {
     vec2 start_point = floor(0.5 + gradient.start_end_point.xy * uDevicePixelRatio) / uDevicePixelRatio;
     vec2 end_point = floor(0.5 + gradient.start_end_point.zw * uDevicePixelRatio) / uDevicePixelRatio;
 
     vec2 dir = end_point - start_point;
     // Normalized offset of this vertex within the gradient, before clamp/repeat.
     vOffset = dot(vi.local_pos - start_point, dir) / dot(dir, dir);
 
     // V coordinate of gradient row in lookup texture.
-    vGradientIndex = float(prim.sub_index) + 0.5;
+    vGradientIndex = float(prim.sub_index);
 
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/ps_blend.fs.glsl
+++ b/gfx/webrender/res/ps_blend.fs.glsl
@@ -94,17 +94,17 @@ vec4 Brightness(vec4 Cs, float amount) {
     return vec4(Cs.rgb * amount, Cs.a);
 }
 
 vec4 Opacity(vec4 Cs, float amount) {
     return vec4(Cs.rgb, Cs.a * amount);
 }
 
 void main(void) {
-    vec4 Cs = texture(sCache, vUv);
+    vec4 Cs = texture(sCacheRGBA8, vUv);
 
     if (Cs.a == 0.0) {
         discard;
     }
 
     switch (vOp) {
         case 0:
             // Gaussian blur is specially handled:
--- a/gfx/webrender/res/ps_blend.vs.glsl
+++ b/gfx/webrender/res/ps_blend.vs.glsl
@@ -11,17 +11,17 @@ void main(void) {
     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(sCache, 0));
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vec2 st0 = src_task.render_target_origin / texture_size;
     vec2 st1 = (src_task.render_target_origin + src_task.size) / texture_size;
     vUv = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
 
     vOp = pi.sub_index;
     vAmount = float(pi.user_data.y) / 65535.0;
 
     gl_Position = uTransform * vec4(local_pos, pi.z, 1.0);
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ b/gfx/webrender/res/ps_border.fs.glsl
@@ -361,20 +361,34 @@ void draw_mixed_border(float distanceFro
     case PST_TOP_LEFT:
     case PST_TOP_RIGHT:
     case PST_BOTTOM_LEFT:
     case PST_BOTTOM_RIGHT: {
       // This is the conversion factor for transformations and device pixel scaling.
       float pixelsPerFragment = length(fwidth(localPos.xy));
       vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
 
-      float distance = distance(vRefPoint, localPos) - vRadii.z;
-      float length = vRadii.x - vRadii.z;
-      if (distanceFromMiddle < 0.0) {
-        distance = length - distance;
+      if (vRadii.x > 0.0) {
+        float distance = distance(vRefPoint, localPos) - vRadii.z;
+        float length = vRadii.x - vRadii.z;
+        if (distanceFromMiddle < 0.0) {
+          distance = length - distance;
+        }
+
+        oFragColor = 0.0 <= distance && distance <= length ?
+          draw_mixed_edge(distance, length, color, brightness_mod) : vec4(0.0, 0.0, 0.0, 0.0);
+        break;
+      }
+
+      bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distanceFromMixLine < 0.0 :
+                                                         distanceFromMixLine >= 0.0;
+      float distance = is_vertical ? abs(localPos.x - vRefPoint.x) : abs(localPos.y - vRefPoint.y);
+      float length = is_vertical ? abs(vPieceRect.z) : abs(vPieceRect.w);
+      if (distanceFromMiddle > 0.0) {
+          distance = length - distance;
       }
 
       oFragColor = 0.0 <= distance && distance <= length ?
         draw_mixed_edge(distance, length, color, brightness_mod) : vec4(0.0, 0.0, 0.0, 0.0);
       break;
     }
     case PST_BOTTOM:
     case PST_TOP: {
--- a/gfx/webrender/res/ps_box_shadow.fs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.fs.glsl
@@ -11,10 +11,10 @@ void main(void) {
     // shadow corner. This can happen, for example, when
     // drawing the outer parts of an inset box shadow.
     uv = clamp(uv, vec2(0.0), vec2(1.0));
 
     // Map the unit UV to the actual UV rect in the cache.
     uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);
 
     // Modulate the box shadow by the color.
-    oFragColor = vColor * texture(sCache, vec3(uv, vUv.z));
+    oFragColor = dither(vColor * texture(sCacheRGBA8, vec3(uv, vUv.z)));
 }
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -20,13 +20,13 @@ void main(void) {
     // Constant offsets to inset from bilinear filtering border.
     vec2 patch_origin = child_task.data0.xy + vec2(1.0);
     vec2 patch_size_device_pixels = child_task.data0.zw - vec2(2.0);
     vec2 patch_size = patch_size_device_pixels / uDevicePixelRatio;
 
     vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
     vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
 
-    vec2 texture_size = vec2(textureSize(sCache, 0));
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
 
     vColor = bs.color;
 }
--- a/gfx/webrender/res/ps_cache_image.fs.glsl
+++ b/gfx/webrender/res/ps_cache_image.fs.glsl
@@ -1,7 +1,7 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
-    oFragColor = texture(sCache, vUv);
+    oFragColor = texture(sCacheRGBA8, vUv);
 }
--- a/gfx/webrender/res/ps_cache_image.vs.glsl
+++ b/gfx/webrender/res/ps_cache_image.vs.glsl
@@ -13,16 +13,16 @@ void main(void) {
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task);
 
     RenderTaskData child_task = fetch_render_task(prim.user_data.x);
     vUv.z = child_task.data1.x;
 
-    vec2 texture_size = vec2(textureSize(sCache, 0));
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vec2 uv0 = child_task.data0.xy / texture_size;
     vec2 uv1 = (child_task.data0.xy + child_task.data0.zw) / texture_size;
 
     vec2 f = (vi.local_pos - prim.local_rect.p0) / prim.local_rect.size;
 
     vUv.xy = mix(uv0, uv1, f);
 }
--- a/gfx/webrender/res/ps_composite.fs.glsl
+++ b/gfx/webrender/res/ps_composite.fs.glsl
@@ -163,18 +163,18 @@ const int MixBlendMode_SoftLight   = 9;
 const int MixBlendMode_Difference  = 10;
 const int MixBlendMode_Exclusion   = 11;
 const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 void main(void) {
-    vec4 Cb = texture(sCache, vUv0);
-    vec4 Cs = texture(sCache, vUv1);
+    vec4 Cb = texture(sCacheRGBA8, vUv0);
+    vec4 Cs = texture(sCacheRGBA8, vUv1);
 
     // 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;
--- a/gfx/webrender/res/ps_composite.vs.glsl
+++ b/gfx/webrender/res/ps_composite.vs.glsl
@@ -12,17 +12,17 @@ void main(void) {
     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(sCache, 0));
+    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);
--- a/gfx/webrender/res/ps_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_gradient.fs.glsl
@@ -7,10 +7,10 @@ void main(void) {
     float alpha = 0.0;
     vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
 #else
     float alpha = 1.0;
     vec2 local_pos = vPos;
 #endif
 
     alpha = min(alpha, do_clip());
-    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+    oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
 }
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -10,41 +10,53 @@ void main(void) {
     GradientStop g0 = fetch_gradient_stop(prim.sub_index + 0);
     GradientStop g1 = fetch_gradient_stop(prim.sub_index + 1);
 
     RectWithSize segment_rect;
     vec2 axis;
     vec4 adjusted_color_g0 = g0.color;
     vec4 adjusted_color_g1 = g1.color;
     if (gradient.start_end_point.y == gradient.start_end_point.w) {
-        vec2 x = mix(gradient.start_end_point.xx, gradient.start_end_point.zz,
-                     vec2(g0.offset.x, g1.offset.x));
+        // Calculate the x coord of the gradient stops
+        vec2 g01_x = mix(gradient.start_end_point.xx, gradient.start_end_point.zz,
+                         vec2(g0.offset.x, g1.offset.x));
+
         // The start and end point of gradient might exceed the geometry rect. So clamp
         // it to the geometry rect.
-        x = clamp(x, prim.local_rect.p0.xx, prim.local_rect.p0.xx + prim.local_rect.size.xx);
+        g01_x = clamp(g01_x, prim.local_rect.p0.xx, prim.local_rect.p0.xx + prim.local_rect.size.xx);
+
+        // Calculate the rect using the clamped coords
+        segment_rect.p0 = vec2(g01_x.x, prim.local_rect.p0.y);
+        segment_rect.size = vec2(g01_x.y - g01_x.x, prim.local_rect.size.y);
+        axis = vec2(1.0, 0.0);
+
+        // We need to adjust the colors of the stops because they may have been clamped
         vec2 adjusted_offset =
-            (x - gradient.start_end_point.xx) / (gradient.start_end_point.zz - gradient.start_end_point.xx);
+            (g01_x - segment_rect.p0.xx) / segment_rect.size.xx;
         adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
         adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
-        segment_rect.p0 = vec2(x.x, prim.local_rect.p0.y);
-        segment_rect.size = vec2(x.y - x.x, prim.local_rect.size.y);
-        axis = vec2(1.0, 0.0);
     } else {
-        vec2 y = mix(gradient.start_end_point.yy, gradient.start_end_point.ww,
-                     vec2(g0.offset.x, g1.offset.x));
+        // Calculate the y coord of the gradient stops
+        vec2 g01_y = mix(gradient.start_end_point.yy, gradient.start_end_point.ww,
+                         vec2(g0.offset.x, g1.offset.x));
+
         // The start and end point of gradient might exceed the geometry rect. So clamp
         // it to the geometry rect.
-        y = clamp(y, prim.local_rect.p0.yy, prim.local_rect.p0.yy + prim.local_rect.size.yy);
+        g01_y = clamp(g01_y, prim.local_rect.p0.yy, prim.local_rect.p0.yy + prim.local_rect.size.yy);
+
+        // Calculate the rect using the clamped coords
+        segment_rect.p0 = vec2(prim.local_rect.p0.x, g01_y.x);
+        segment_rect.size = vec2(prim.local_rect.size.x, g01_y.y - g01_y.x);
+        axis = vec2(0.0, 1.0);
+
+        // We need to adjust the colors of the stops because they may have been clamped
         vec2 adjusted_offset =
-            (y - gradient.start_end_point.yy) / (gradient.start_end_point.ww - gradient.start_end_point.yy);
+            (g01_y - segment_rect.p0.yy) / segment_rect.size.yy;
         adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
         adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
-        segment_rect.p0 = vec2(prim.local_rect.p0.x, y.x);
-        segment_rect.size = vec2(prim.local_rect.size.x, y.y - y.x);
-        axis = vec2(0.0, 1.0);
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task);
--- a/gfx/webrender/res/ps_hardware_composite.fs.glsl
+++ b/gfx/webrender/res/ps_hardware_composite.fs.glsl
@@ -1,7 +1,7 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
-    oFragColor = texture(sCache, vUv);
+    oFragColor = texture(sCacheRGBA8, vUv);
 }
--- a/gfx/webrender/res/ps_hardware_composite.vs.glsl
+++ b/gfx/webrender/res/ps_hardware_composite.vs.glsl
@@ -11,15 +11,15 @@ void main(void) {
     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(sCache, 0));
+    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vec2 st0 = src_task.render_target_origin / texture_size;
     vec2 st1 = (src_task.render_target_origin + src_task.size) / texture_size;
     vUv = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
 
     gl_Position = uTransform * vec4(local_pos, pi.z, 1.0);
 }
--- a/gfx/webrender/res/ps_radial_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.fs.glsl
@@ -45,16 +45,15 @@ void main(void) {
     }
 
     vec2 texture_size = vec2(textureSize(sGradients, 0));
 
     // Either saturate or modulo the offset depending on repeat mode, then scale to number of
     // gradient color entries (texture width / 2).
     x = mix(clamp(x, 0.0, 1.0), fract(x), vGradientRepeat) * 0.5 * texture_size.x;
 
-    // Start at the center of first color in the nearest 2-color entry, then offset with the
-    // fractional remainder to interpolate between the colors. Rely on texture clamping when
-    // outside of valid range.
     x = 2.0 * floor(x) + 0.5 + fract(x);
 
-    // Normalize the texture coordates so we can use texture() for bilinear filtering.
-    oFragColor = texture(sGradients, vec2(x, vGradientIndex) / texture_size);
+    // Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
+    // bits (vGradientIndex)
+    float y = vGradientIndex * 2.0 + 0.5 + 1.0 / 256.0;
+    oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
 }
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -22,13 +22,13 @@ void main(void) {
     // be better to fix this higher up in DL construction
     // and not snap here?
     vStartCenter = floor(0.5 + gradient.start_end_center.xy * uDevicePixelRatio) / uDevicePixelRatio;
     vEndCenter = floor(0.5 + gradient.start_end_center.zw * uDevicePixelRatio) / uDevicePixelRatio;
     vStartRadius = gradient.start_end_radius_extend_mode.x;
     vEndRadius = gradient.start_end_radius_extend_mode.y;
 
     // V coordinate of gradient row in lookup texture.
-    vGradientIndex = float(prim.sub_index) + 0.5;
+    vGradientIndex = float(prim.sub_index);
 
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.start_end_radius_extend_mode.z) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -31,16 +31,17 @@
 #endif
 
 //======================================================================================
 // Shared shader uniforms
 //======================================================================================
 uniform sampler2D sColor0;
 uniform sampler2D sColor1;
 uniform sampler2D sColor2;
+uniform sampler2D sDither;
 uniform sampler2D sMask;
 
 //======================================================================================
 // Interpolator definitions
 //======================================================================================
 
 //======================================================================================
 // VS only types and UBOs
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -243,19 +243,23 @@ impl ClipScrollNode {
             inv_transform.pre_translated(-parent_accumulated_scroll_offset.x,
                                          -parent_accumulated_scroll_offset.y,
                                          0.0)
                          .transform_rect(parent_combined_viewport_rect);
 
         // Now that we have the combined viewport rectangle of the parent nodes in local space,
         // we do the intersection and get our combined viewport rect in the coordinate system
         // starting from our origin.
-        self.combined_local_viewport_rect =
-            parent_combined_viewport_in_local_space.intersection(&self.local_clip_rect)
-                                                    .unwrap_or(LayerRect::zero());
+        self.combined_local_viewport_rect = match self.node_type {
+            NodeType::Clip(_) => {
+                parent_combined_viewport_in_local_space.intersection(&self.local_clip_rect)
+                                                       .unwrap_or(LayerRect::zero())
+            }
+            NodeType::ReferenceFrame(_) => parent_combined_viewport_in_local_space,
+        };
 
         // The transformation for this viewport in world coordinates is the transformation for
         // our parent reference frame, plus any accumulated scrolling offsets from nodes
         // between our reference frame and this node. For reference frames, we also include
         // whatever local transformation this reference frame provides. This can be combined
         // with the local_viewport_rect to get its position in world space.
         self.world_viewport_transform =
             parent_reference_frame_transform
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1542,24 +1542,32 @@ impl Device {
         let u_color1 = self.gl.get_uniform_location(program.id, "sColor1");
         if u_color1 != -1 {
             self.gl.uniform_1i(u_color1, TextureSampler::Color1 as i32);
         }
         let u_color_2 = self.gl.get_uniform_location(program.id, "sColor2");
         if u_color_2 != -1 {
             self.gl.uniform_1i(u_color_2, TextureSampler::Color2 as i32);
         }
+        let u_noise = self.gl.get_uniform_location(program.id, "sDither");
+        if u_noise != -1 {
+            self.gl.uniform_1i(u_noise, TextureSampler::Dither as i32);
+        }
         let u_mask = self.gl.get_uniform_location(program.id, "sMask");
         if u_mask != -1 {
             self.gl.uniform_1i(u_mask, TextureSampler::Mask as i32);
         }
 
-        let u_cache = self.gl.get_uniform_location(program.id, "sCache");
-        if u_cache != -1 {
-            self.gl.uniform_1i(u_cache, TextureSampler::Cache as i32);
+        let u_cache_a8 = self.gl.get_uniform_location(program.id, "sCacheA8");
+        if u_cache_a8 != -1 {
+            self.gl.uniform_1i(u_cache_a8, TextureSampler::CacheA8 as i32);
+        }
+        let u_cache_rgba8 = self.gl.get_uniform_location(program.id, "sCacheRGBA8");
+        if u_cache_rgba8 != -1 {
+            self.gl.uniform_1i(u_cache_rgba8, TextureSampler::CacheRGBA8 as i32);
         }
 
         let u_layers = self.gl.get_uniform_location(program.id, "sLayers");
         if u_layers != -1 {
             self.gl.uniform_1i(u_layers, TextureSampler::Layers as i32);
         }
 
         let u_tasks = self.gl.get_uniform_location(program.id, "sRenderTasks");
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -11,19 +11,20 @@ use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use profiler::TextureCacheProfileCounters;
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use tiling::{AuxiliaryListsMap, CompositeOps, PrimitiveFlags};
+use util::subtract_rect;
 use webrender_traits::{AuxiliaryLists, ClipDisplayItem, ClipRegion, ColorF, DeviceUintRect};
 use webrender_traits::{DeviceUintSize, DisplayItem, Epoch, FilterOp, ImageDisplayItem, LayerPoint};
-use webrender_traits::{LayerRect, LayerSize, LayerToScrollTransform, LayoutTransform};
+use webrender_traits::{LayerRect, LayerSize, LayerToScrollTransform, LayoutRect, LayoutTransform};
 use webrender_traits::{MixBlendMode, PipelineId, ScrollEventPhase, ScrollLayerId};
 use webrender_traits::{ScrollLayerState, ScrollLocation, ScrollPolicy, SpecificDisplayItem};
 use webrender_traits::{StackingContext, TileOffset, WorldPoint};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
@@ -62,24 +63,24 @@ pub struct Frame {
     pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
     pub pipeline_auxiliary_lists: AuxiliaryListsMap,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
     frame_builder: Option<FrameBuilder>,
 }
 
 trait DisplayListHelpers {
-    fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a ClipRegion)>;
+    fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a LayoutRect)>;
 }
 
 impl DisplayListHelpers for Vec<DisplayItem> {
-    fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a ClipRegion)> {
+    fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a LayoutRect)> {
         self.first().and_then(|item| match item.item {
             SpecificDisplayItem::PushStackingContext(ref specific_item) => {
-                Some((&specific_item.stacking_context, &item.clip))
+                Some((&specific_item.stacking_context, &item.rect))
             },
             _ => None,
         })
     }
 }
 
 trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
@@ -200,16 +201,32 @@ impl<'a> Iterator for DisplayListTravers
         }
 
         let item = &self.display_list[self.next_item_index];
         self.next_item_index += 1;
         Some(item)
     }
 }
 
+fn clip_intersection(original_rect: &LayerRect,
+                     region: &ClipRegion,
+                     aux_lists: &AuxiliaryLists)
+                     -> Option<LayerRect> {
+    if region.image_mask.is_some() {
+        return None;
+    }
+    let clips = aux_lists.complex_clip_regions(&region.complex);
+    let base_rect = region.main.intersection(original_rect);
+    clips.iter().fold(base_rect, |inner_combined, ccr| {
+        inner_combined.and_then(|combined| {
+            ccr.get_inner_rect().and_then(|ir| ir.intersection(&combined))
+        })
+    })
+}
+
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
         Frame {
             pipeline_epoch_map: HashMap::with_hasher(Default::default()),
             pipeline_auxiliary_lists: HashMap::with_hasher(Default::default()),
             clip_scroll_tree: ClipScrollTree::new(),
             id: FrameId(0),
             frame_builder: None,
@@ -279,17 +296,17 @@ impl Frame {
             return;
         }
 
         let old_scrolling_states = self.reset();
         self.pipeline_auxiliary_lists = scene.pipeline_auxiliary_lists.clone();
 
         self.pipeline_epoch_map.insert(root_pipeline_id, root_pipeline.epoch);
 
-        let (root_stacking_context, root_clip) = match display_list.starting_stacking_context() {
+        let (root_stacking_context, root_bounds) = match display_list.starting_stacking_context() {
             Some(some) => some,
             None => {
                 warn!("Pipeline display list does not start with a stacking context.");
                 return;
             }
         };
 
         let background_color = root_pipeline.background_color.and_then(|color| {
@@ -304,33 +321,33 @@ impl Frame {
                                                   background_color,
                                                   self.frame_builder_config);
 
         {
             let mut context = FlattenContext::new(scene, &mut frame_builder, resource_cache);
 
             let scroll_layer_id = context.builder.push_root(root_pipeline_id,
                                                             &root_pipeline.viewport_size,
-                                                            &root_clip.main.size,
+                                                            &root_bounds.size,
                                                             &mut self.clip_scroll_tree);
 
             context.builder.setup_viewport_offset(window_size,
                                                   inner_rect,
                                                   device_pixel_ratio,
                                                   &mut self.clip_scroll_tree);
 
             let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
             self.flatten_stacking_context(&mut traversal,
                                           root_pipeline_id,
                                           &mut context,
                                           scroll_layer_id,
                                           LayerPoint::zero(),
                                           0,
-                                          &root_stacking_context,
-                                          root_clip);
+                                          &root_bounds,
+                                          &root_stacking_context);
         }
 
         self.frame_builder = Some(frame_builder);
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
     }
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
@@ -352,18 +369,18 @@ impl Frame {
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut DisplayListTraversal<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_layer_id: ScrollLayerId,
                                     mut reference_frame_relative_offset: LayerPoint,
                                     level: i32,
-                                    stacking_context: &StackingContext,
-                                    clip_region: &ClipRegion) {
+                                    bounds: &LayerRect,
+                                    stacking_context: &StackingContext) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
             let auxiliary_lists = self.pipeline_auxiliary_lists
@@ -395,50 +412,48 @@ impl Frame {
             let transform = stacking_context.transform.as_ref();
             let transform = context.scene.properties.resolve_layout_transform(transform);
             let perspective =
                 stacking_context.perspective.unwrap_or_else(LayoutTransform::identity);
             let transform =
                 LayerToScrollTransform::create_translation(reference_frame_relative_offset.x,
                                                            reference_frame_relative_offset.y,
                                                            0.0)
-                                        .pre_translated(stacking_context.bounds.origin.x,
-                                                        stacking_context.bounds.origin.y,
-                                                        0.0)
+                                        .pre_translated(bounds.origin.x, bounds.origin.y, 0.0)
                                         .pre_mul(&transform)
                                         .pre_mul(&perspective);
 
+            let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
             scroll_layer_id = context.builder.push_reference_frame(Some(scroll_layer_id),
                                                                    pipeline_id,
-                                                                   &clip_region.main,
+                                                                   &reference_frame_bounds,
                                                                    &transform,
                                                                    &mut self.clip_scroll_tree);
             context.replacements.push((context_scroll_layer_id, scroll_layer_id));
             reference_frame_relative_offset = LayerPoint::zero();
         } else {
             reference_frame_relative_offset = LayerPoint::new(
-                reference_frame_relative_offset.x + stacking_context.bounds.origin.x,
-                reference_frame_relative_offset.y + stacking_context.bounds.origin.y);
+                reference_frame_relative_offset.x + bounds.origin.x,
+                reference_frame_relative_offset.y + bounds.origin.y);
         }
 
         // TODO(gw): Int with overflow etc
-        context.builder.push_stacking_context(reference_frame_relative_offset,
-                                              clip_region.main,
+        context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
                                               level == 0,
                                               composition_operations);
 
         if level == 0 {
             if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     // Note: we don't use the original clip region here,
                     // it's already processed by the node we just pushed.
-                    let background_rect = LayerRect::new(LayerPoint::zero(), clip_region.main.size);
+                    let background_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
                     context.builder.add_solid_rectangle(scroll_layer_id,
-                                                        &clip_region.main,
+                                                        &bounds,
                                                         &ClipRegion::simple(&background_rect),
                                                         &bg_color,
                                                         PrimitiveFlags::None);
                 }
             }
         }
 
         self.flatten_items(traversal,
@@ -482,17 +497,18 @@ impl Frame {
         };
 
         let display_list = context.scene.display_lists.get(&pipeline_id);
         let display_list = match display_list {
             Some(display_list) => display_list,
             None => return,
         };
 
-        let (iframe_stacking_context, iframe_clip) = match display_list.starting_stacking_context() {
+        let (iframe_stacking_context,
+             iframe_stacking_context_bounds) = match display_list.starting_stacking_context() {
             Some(some) => some,
             None => {
                 warn!("Pipeline display list does not start with a stacking context.");
                 return;
             }
         };
 
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
@@ -511,29 +527,29 @@ impl Frame {
                                                  &mut self.clip_scroll_tree);
 
         let iframe_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
         context.builder.add_clip_scroll_node(
             iframe_scroll_layer_id,
             iframe_reference_frame_id,
             pipeline_id,
             &LayerRect::new(LayerPoint::zero(), iframe_rect.size),
-            &iframe_clip.main.size,
-            iframe_clip,
+            &iframe_stacking_context_bounds.size,
+            &ClipRegion::simple(&iframe_stacking_context_bounds),
             &mut self.clip_scroll_tree);
 
         let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
         self.flatten_stacking_context(&mut traversal,
                                       pipeline_id,
                                       context,
                                       iframe_scroll_layer_id,
                                       LayerPoint::zero(),
                                       0,
-                                      &iframe_stacking_context,
-                                      iframe_clip);
+                                      &iframe_stacking_context_bounds,
+                                      &iframe_stacking_context);
 
         context.builder.pop_reference_frame();
     }
 
     fn flatten_items<'a>(&mut self,
                          traversal: &mut DisplayListTraversal<'a>,
                          pipeline_id: PipelineId,
                          context: &mut FlattenContext,
@@ -589,21 +605,44 @@ impl Frame {
                                              text_info.font_key,
                                              text_info.size,
                                              text_info.blur_radius,
                                              &text_info.color,
                                              text_info.glyphs,
                                              text_info.glyph_options);
                 }
                 SpecificDisplayItem::Rectangle(ref info) => {
-                    context.builder.add_solid_rectangle(scroll_layer_id,
-                                                        &item.rect,
-                                                        &item.clip,
-                                                        &info.color,
-                                                        PrimitiveFlags::None);
+                    let auxiliary_lists = self.pipeline_auxiliary_lists
+                                              .get(&pipeline_id)
+                                              .expect("No auxiliary lists?!");
+                    // Try to extract the opaque inner rectangle out of the clipped primitive.
+                    if let Some(opaque_rect) = clip_intersection(&item.rect, &item.clip, &auxiliary_lists) {
+                        let mut results = Vec::new();
+                        subtract_rect(&item.rect, &opaque_rect, &mut results);
+                        // The inner rectangle is considered opaque within this layer.
+                        // It may still inherit some masking from the clip stack.
+                        context.builder.add_solid_rectangle(scroll_layer_id,
+                                                            &opaque_rect,
+                                                            &ClipRegion::simple(&item.clip.main),
+                                                            &info.color,
+                                                            PrimitiveFlags::None);
+                        for transparent_rect in &results {
+                            context.builder.add_solid_rectangle(scroll_layer_id,
+                                                                transparent_rect,
+                                                                &item.clip,
+                                                                &info.color,
+                                                                PrimitiveFlags::None);
+                        }
+                    } else {
+                        context.builder.add_solid_rectangle(scroll_layer_id,
+                                                            &item.rect,
+                                                            &item.clip,
+                                                            &info.color,
+                                                            PrimitiveFlags::None);
+                    }
                 }
                 SpecificDisplayItem::Gradient(ref info) => {
                     context.builder.add_gradient(scroll_layer_id,
                                                  item.rect,
                                                  &item.clip,
                                                  info.gradient.start_point,
                                                  info.gradient.end_point,
                                                  info.gradient.stops,
@@ -639,18 +678,18 @@ impl Frame {
                 }
                 SpecificDisplayItem::PushStackingContext(ref info) => {
                     self.flatten_stacking_context(traversal,
                                                   pipeline_id,
                                                   context,
                                                   item.scroll_layer_id,
                                                   reference_frame_relative_offset,
                                                   level + 1,
-                                                  &info.stacking_context,
-                                                  &item.clip);
+                                                  &item.rect,
+                                                  &info.stacking_context);
                 }
                 SpecificDisplayItem::Iframe(ref info) => {
                     self.flatten_iframe(info.pipeline_id,
                                         scroll_layer_id,
                                         &item.rect,
                                         context,
                                         reference_frame_relative_offset);
                 }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -206,36 +206,34 @@ impl FrameBuilder {
             packed_layer_index: packed_layer_index,
             xf_rect: None,
          });
 
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, scroll_layer_id)
     }
 
     pub fn push_stacking_context(&mut self,
-                                 reference_frame_offset: LayerPoint,
-                                 rect: LayerRect,
+                                 reference_frame_offset: &LayerPoint,
                                  pipeline_id: PipelineId,
                                  is_page_root: bool,
                                  composite_ops: CompositeOps) {
         if let Some(parent_index) = self.stacking_context_stack.last() {
             let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
 
             if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
                 // the parent stacking context of a stacking context with mix-blend-mode
                 // must be drawn with a transparent background, unless the parent stacking context
                 // is the root of the page
                 self.stacking_context_store[parent_index.0].should_isolate = true;
             }
         }
 
         let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
         self.stacking_context_store.push(StackingContext::new(pipeline_id,
-                                                              reference_frame_offset,
-                                                              rect,
+                                                              *reference_frame_offset,
                                                               is_page_root,
                                                               composite_ops));
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
@@ -1327,17 +1325,18 @@ impl FrameBuilder {
                 clip_scroll_group_store: &self.clip_scroll_group_store,
                 prim_store: &self.prim_store,
                 resource_cache: resource_cache,
             };
 
             pass.build(&ctx, &mut render_tasks);
 
             profile_counters.passes.inc();
-            profile_counters.targets.add(pass.targets.len());
+            profile_counters.color_targets.add(pass.color_targets.target_count());
+            profile_counters.alpha_targets.add(pass.alpha_targets.target_count());
         }
 
         resource_cache.end_frame();
 
         Frame {
             device_pixel_ratio: device_pixel_ratio,
             background_color: self.background_color,
             window_size: self.screen_size,
@@ -1441,17 +1440,17 @@ impl<'a> LayerRectCalculationAndCullingP
                                                 0.0);
             packed_layer.set_transform(transform);
 
             // Meanwhile, the combined viewport rect is relative to the reference frame, so
             // we move it into the local coordinate system of the node.
             let local_viewport_rect =
                 node.combined_local_viewport_rect.translate(&-node.local_viewport_rect.origin);
 
-            node_clip_info.xf_rect = packed_layer.set_rect(Some(local_viewport_rect),
+            node_clip_info.xf_rect = packed_layer.set_rect(&local_viewport_rect,
                                                            self.screen_rect,
                                                            self.device_pixel_ratio);
 
             let mask_info = match node_clip_info.mask_cache_info {
                 Some(ref mut mask_info) => mask_info,
                 _ => continue,
             };
 
@@ -1488,26 +1487,23 @@ impl<'a> LayerRectCalculationAndCullingP
                                                 stacking_context.reference_frame_offset.y,
                                                 0.0);
             packed_layer.set_transform(transform);
 
             if !stacking_context.can_contribute_to_scene() {
                 return;
             }
 
-            // Here we want to find the intersection between the clipping region and the
-            // stacking context content, so we move the viewport rectangle into the coordinate
-            // system of the stacking context content.
+            // Here we move the viewport rectangle into the coordinate system
+            // of the stacking context content.
             let viewport_rect =
                 &node.combined_local_viewport_rect
                      .translate(&-stacking_context.reference_frame_offset)
                      .translate(&-node.scrolling.offset);
-            let intersected_rect = stacking_context.local_rect.intersection(viewport_rect);
-
-            group.xf_rect = packed_layer.set_rect(intersected_rect,
+            group.xf_rect = packed_layer.set_rect(viewport_rect,
                                                   self.screen_rect,
                                                   self.device_pixel_ratio);
         }
     }
 
     fn compute_stacking_context_visibility(&mut self) {
         for context_index in 0..self.frame_builder.stacking_context_store.len() {
             let is_visible = {
--- a/gfx/webrender/src/gpu_store.rs
+++ b/gfx/webrender/src/gpu_store.rs
@@ -40,16 +40,20 @@ pub trait GpuStoreLayout {
         let texel_size = Self::texel_size();
         debug_assert!(item_size % texel_size == 0);
         item_size / texel_size
     }
 
     fn items_per_row<T>() -> usize {
         Self::texture_width::<T>() / Self::texels_per_item::<T>()
     }
+
+    fn rows_per_item<T>() -> usize {
+        Self::texels_per_item::<T>() / Self::texture_width::<T>()
+    }
 }
 
 /// A CPU-side buffer storing content to be uploaded to the GPU.
 pub struct GpuStore<T, L> {
     data: Vec<T>,
     layout: PhantomData<L>,
     // TODO(gw): Could store this intrusively inside
     // the data array free slots.
@@ -76,18 +80,20 @@ impl<T: Clone + Default, L: GpuStoreLayo
     pub fn build(&self) -> Vec<T> {
         let items_per_row = L::items_per_row::<T>();
 
         let mut items = self.data.clone();
 
         // Extend the data array to be a multiple of the row size.
         // This ensures memory safety when the array is passed to
         // OpenGL to upload to the GPU.
-        while items.len() % items_per_row != 0 {
-            items.push(T::default());
+        if items_per_row != 0 {
+            while items_per_row != 0 && items.len() % items_per_row != 0 {
+                items.push(T::default());
+            }
         }
 
         items
     }
 
     pub fn alloc(&mut self, count: usize) -> GpuStoreAddress {
         let address = self.get_next_address();
 
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,33 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use device::TextureFilter;
 use euclid::{TypedPoint2D, UnknownUnit};
 use fnv::FnvHasher;
-use gleam::gl;
-use offscreen_gl_context::{NativeGLContext, NativeGLContextHandle};
-use offscreen_gl_context::{GLContext, NativeGLContextMethods, GLContextDispatcher};
-use offscreen_gl_context::{OSMesaContext, OSMesaContextHandle};
-use offscreen_gl_context::{ColorAttachmentType, GLContextAttributes, GLLimits};
 use profiler::BackendProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
-use webrender_traits::{Epoch, ColorF, PipelineId, DeviceIntSize};
+use webrender_traits::{Epoch, ColorF, PipelineId};
 use webrender_traits::{ImageFormat, NativeFontHandle};
-use webrender_traits::{ExternalImageId, ScrollLayerId, WebGLCommand};
+use webrender_traits::{ExternalImageId, ScrollLayerId};
 use webrender_traits::{ImageData};
 use webrender_traits::{DeviceUintRect};
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
 // to be added to an atlas). The texture cache
@@ -43,138 +38,25 @@ pub struct CacheTextureId(pub usize);
 // pipeline until they reach the rendering
 // thread, where they are resolved to a
 // native texture ID.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 pub enum SourceTexture {
     Invalid,
     TextureCache(CacheTextureId),
-    WebGL(u32),                         // Is actually a gl::GLuint
     External(ExternalImageId),
-}
-
-pub enum GLContextHandleWrapper {
-    Native(NativeGLContextHandle),
-    OSMesa(OSMesaContextHandle),
-}
-
-impl GLContextHandleWrapper {
-    pub fn current_native_handle() -> Option<GLContextHandleWrapper> {
-        NativeGLContext::current_handle().map(GLContextHandleWrapper::Native)
-    }
-
-    pub fn current_osmesa_handle() -> Option<GLContextHandleWrapper> {
-        OSMesaContext::current_handle().map(GLContextHandleWrapper::OSMesa)
-    }
-
-    pub fn new_context(&self,
-                       size: DeviceIntSize,
-                       attributes: GLContextAttributes,
-                       dispatcher: Option<Box<GLContextDispatcher>>) -> Result<GLContextWrapper, &'static str> {
-        match *self {
-            GLContextHandleWrapper::Native(ref handle) => {
-                let ctx = GLContext::<NativeGLContext>::new_shared_with_dispatcher(size.to_untyped(),
-                                                                                   attributes,
-                                                                                   ColorAttachmentType::Texture,
-                                                                                   gl::GlType::default(),
-                                                                                   Some(handle),
-                                                                                   dispatcher);
-                ctx.map(GLContextWrapper::Native)
-            }
-            GLContextHandleWrapper::OSMesa(ref handle) => {
-                let ctx = GLContext::<OSMesaContext>::new_shared_with_dispatcher(size.to_untyped(),
-                                                                                 attributes,
-                                                                                 ColorAttachmentType::Texture,
-                                                                                 gl::GlType::default(),
-                                                                                 Some(handle),
-                                                                                 dispatcher);
-                ctx.map(GLContextWrapper::OSMesa)
-            }
-        }
-    }
-}
-
-pub enum GLContextWrapper {
-    Native(GLContext<NativeGLContext>),
-    OSMesa(GLContext<OSMesaContext>),
-}
-
-impl GLContextWrapper {
-    pub fn make_current(&self) {
-        match *self {
-            GLContextWrapper::Native(ref ctx) => {
-                ctx.make_current().unwrap();
-            }
-            GLContextWrapper::OSMesa(ref ctx) => {
-                ctx.make_current().unwrap();
-            }
-        }
-    }
-
-    pub fn unbind(&self) {
-        match *self {
-            GLContextWrapper::Native(ref ctx) => {
-                ctx.unbind().unwrap();
-            }
-            GLContextWrapper::OSMesa(ref ctx) => {
-                ctx.unbind().unwrap();
-            }
-        }
-    }
-
-    pub fn apply_command(&self, cmd: WebGLCommand) {
-        match *self {
-            GLContextWrapper::Native(ref ctx) => {
-                cmd.apply(ctx);
-            }
-            GLContextWrapper::OSMesa(ref ctx) => {
-                cmd.apply(ctx);
-            }
-        }
-    }
-
-    pub fn get_info(&self) -> (DeviceIntSize, u32, GLLimits) {
-        match *self {
-            GLContextWrapper::Native(ref ctx) => {
-                let (real_size, texture_id) = {
-                    let draw_buffer = ctx.borrow_draw_buffer().unwrap();
-                    (draw_buffer.size(), draw_buffer.get_bound_texture_id().unwrap())
-                };
-
-                let limits = ctx.borrow_limits().clone();
-
-                (DeviceIntSize::from_untyped(&real_size), texture_id, limits)
-            }
-            GLContextWrapper::OSMesa(ref ctx) => {
-                let (real_size, texture_id) = {
-                    let draw_buffer = ctx.borrow_draw_buffer().unwrap();
-                    (draw_buffer.size(), draw_buffer.get_bound_texture_id().unwrap())
-                };
-
-                let limits = ctx.borrow_limits().clone();
-
-                (DeviceIntSize::from_untyped(&real_size), texture_id, limits)
-            }
-        }
-    }
-
-    pub fn resize(&mut self, size: &DeviceIntSize) -> Result<(), &'static str> {
-        match *self {
-            GLContextWrapper::Native(ref mut ctx) => {
-                ctx.resize(size.to_untyped())
-            }
-            GLContextWrapper::OSMesa(ref mut ctx) => {
-                ctx.resize(size.to_untyped())
-            }
-        }
-    }
+    #[cfg_attr(not(feature = "webgl"), allow(dead_code))]
+    /// This is actually a gl::GLuint, with the shared texture id between the
+    /// main context and the WebGL context.
+    WebGL(u32),
 }
 
 const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
+const COLOR_FLOAT_TO_FIXED_WIDE: f32 = 65535.0;
 pub const ANGLE_FLOAT_TO_FIXED: f32 = 65535.0;
 
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Clone)]
 pub enum FontTemplate {
     Raw(Arc<Vec<u8>>),
@@ -182,26 +64,28 @@ pub enum FontTemplate {
 }
 
 #[derive(Debug, PartialEq, Eq)]
 pub enum TextureSampler {
     Color0,
     Color1,
     Color2,
     Mask,
-    Cache,
+    CacheA8,
+    CacheRGBA8,
     Data16,
     Data32,
     Data64,
     Data128,
     Layers,
     RenderTasks,
     Geometry,
     ResourceRects,
     Gradients,
+    Dither,
 }
 
 impl TextureSampler {
     pub fn color(n: usize) -> TextureSampler {
         match n {
             0 => TextureSampler::Color0,
             1 => TextureSampler::Color1,
             2 => TextureSampler::Color2,
@@ -307,22 +191,30 @@ impl PackedColor {
 pub struct PackedTexel {
     pub b: u8,
     pub g: u8,
     pub r: u8,
     pub a: u8,
 }
 
 impl PackedTexel {
-    pub fn from_color(color: &ColorF) -> PackedTexel {
+    pub fn high_bytes(color: &ColorF) -> PackedTexel {
+        Self::extract_bytes(color, COLOR_FLOAT_TO_FIXED)
+    }
+
+    pub fn low_bytes(color: &ColorF) -> PackedTexel {
+        Self::extract_bytes(color, COLOR_FLOAT_TO_FIXED_WIDE)
+    }
+
+    fn extract_bytes(color: &ColorF, multiplier: f32) -> PackedTexel {
         PackedTexel {
-            b: (0.5 + color.b * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            g: (0.5 + color.g * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            r: (0.5 + color.r * COLOR_FLOAT_TO_FIXED).floor() as u8,
-            a: (0.5 + color.a * COLOR_FLOAT_TO_FIXED).floor() as u8,
+            b: ((0.5 + color.b * multiplier).floor() as u32 & 0xff) as u8,
+            g: ((0.5 + color.g * multiplier).floor() as u32 & 0xff) as u8,
+            r: ((0.5 + color.r * multiplier).floor() as u32 & 0xff) as u8,
+            a: ((0.5 + color.a * multiplier).floor() as u32 & 0xff) as u8,
         }
     }
 }
 
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -72,16 +72,23 @@ mod render_backend;
 mod render_task;
 mod resource_cache;
 mod scene;
 mod spring;
 mod texture_cache;
 mod tiling;
 mod util;
 
+#[cfg(feature = "webgl")]
+mod webgl_types;
+
+#[cfg(not(feature = "webgl"))]
+#[path = "webgl_stubs.rs"]
+mod webgl_types;
+
 mod shader_source {
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
 }
 
 pub use record::{ApiRecordingReceiver, BinaryRecorder, WEBRENDER_RECORDING_HEADER};
 
 mod platform {
     #[cfg(target_os="macos")]
@@ -122,16 +129,17 @@ extern crate app_units;
 extern crate bincode;
 extern crate euclid;
 extern crate fnv;
 extern crate gleam;
 extern crate num_traits;
 //extern crate notify;
 extern crate time;
 extern crate webrender_traits;
+#[cfg(feature = "webgl")]
 extern crate offscreen_gl_context;
 extern crate byteorder;
 extern crate threadpool;
 
 #[cfg(any(target_os="macos", target_os="windows"))]
 extern crate gamma_lut;
 
 pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -275,31 +275,34 @@ pub struct GradientDataEntry {
 // A table of gradient entries, with two colors per entry, that specify the start and end color
 // within the segment of the gradient space represented by that entry. To lookup a gradient result,
 // first the entry index is calculated to determine which two colors to interpolate between, then
 // the offset within that entry bucket is used to interpolate between the two colors in that entry.
 // This layout preserves hard stops, as the end color for a given entry can differ from the start
 // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
 // format for texture upload.
 pub struct GradientData {
-    pub colors: [GradientDataEntry; GRADIENT_DATA_RESOLUTION],
+    pub colors_high: [GradientDataEntry; GRADIENT_DATA_RESOLUTION],
+    pub colors_low: [GradientDataEntry; GRADIENT_DATA_RESOLUTION],
 }
 
 impl Default for GradientData {
     fn default() -> GradientData {
         GradientData {
-            colors: unsafe { mem::uninitialized() }
+            colors_high: unsafe { mem::uninitialized() },
+            colors_low: unsafe { mem::uninitialized() }
         }
     }
 }
 
 impl Clone for GradientData {
     fn clone(&self) -> GradientData {
         GradientData {
-            colors: self.colors,
+            colors_high: self.colors_high,
+            colors_low: self.colors_low,
         }
     }
 }
 
 impl GradientData {
     // Generate a color ramp between the start and end indexes from a start color to an end color.
     fn fill_colors(&mut self, start_idx: usize, end_idx: usize, start_color: &ColorF, end_color: &ColorF) -> usize {
         if start_idx >= end_idx {
@@ -309,28 +312,34 @@ impl GradientData {
         // Calculate the color difference for individual steps in the ramp.
         let inv_steps = 1.0 / (end_idx - start_idx) as f32;
         let step_r = (end_color.r - start_color.r) * inv_steps;
         let step_g = (end_color.g - start_color.g) * inv_steps;
         let step_b = (end_color.b - start_color.b) * inv_steps;
         let step_a = (end_color.a - start_color.a) * inv_steps;
 
         let mut cur_color = *start_color;
-        let mut cur_packed_color = PackedTexel::from_color(&cur_color);
+        let mut cur_color_high = PackedTexel::high_bytes(&cur_color);
+        let mut cur_color_low = PackedTexel::low_bytes(&cur_color);
 
         // Walk the ramp writing start and end colors for each entry.
-        for entry in &mut self.colors[start_idx..end_idx] {
-            entry.start_color = cur_packed_color;
+        for index in start_idx..end_idx {
+            let high_byte_entry = &mut self.colors_high[index];
+            let low_byte_entry = &mut self.colors_low[index];
 
+            high_byte_entry.start_color = cur_color_high;
+            low_byte_entry.start_color = cur_color_low;
             cur_color.r += step_r;
             cur_color.g += step_g;
             cur_color.b += step_b;
             cur_color.a += step_a;
-            cur_packed_color = PackedTexel::from_color(&cur_color);
-            entry.end_color = cur_packed_color;
+            cur_color_high = PackedTexel::high_bytes(&cur_color);
+            cur_color_low = PackedTexel::low_bytes(&cur_color);
+            high_byte_entry.end_color = cur_color_high;
+            low_byte_entry.end_color = cur_color_low;
         }
 
         end_idx
     }
 
     // Compute an entry index based on a gradient stop offset.
     #[inline]
     fn get_index(offset: f32) -> usize {
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -245,26 +245,28 @@ impl ProfileCounter for AverageTimeProfi
         }
     }
 }
 
 pub struct FrameProfileCounters {
     pub total_primitives: IntProfileCounter,
     pub visible_primitives: IntProfileCounter,
     pub passes: IntProfileCounter,
-    pub targets: IntProfileCounter,
+    pub color_targets: IntProfileCounter,
+    pub alpha_targets: IntProfileCounter,
 }
 
 impl FrameProfileCounters {
     pub fn new() -> FrameProfileCounters {
         FrameProfileCounters {
             total_primitives: IntProfileCounter::new("Total Primitives"),
             visible_primitives: IntProfileCounter::new("Visible Primitives"),
             passes: IntProfileCounter::new("Passes"),
-            targets: IntProfileCounter::new("Render Targets"),
+            color_targets: IntProfileCounter::new("Color Targets"),
+            alpha_targets: IntProfileCounter::new("Alpha Targets"),
         }
     }
 }
 
 #[derive(Clone)]
 pub struct TextureCacheProfileCounters {
     pub pages_a8: ResourceProfileCounter,
     pub pages_rgb8: ResourceProfileCounter,
@@ -656,17 +658,18 @@ impl Profiler {
             &renderer_profile.frame_counter,
             &renderer_profile.frame_time,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &frame_profile.total_primitives,
             &frame_profile.visible_primitives,
             &frame_profile.passes,
-            &frame_profile.targets,
+            &frame_profile.color_targets,
+            &frame_profile.alpha_targets,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.font_templates,
             &backend_profile.image_templates,
         ], debug_renderer, true);
 
         self.draw_counters(&[
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,34 +1,37 @@
 /* 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 byteorder::{LittleEndian, ReadBytesExt};
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
-use internal_types::{FontTemplate, GLContextHandleWrapper, GLContextWrapper};
-use internal_types::{SourceTexture, ResultMsg, RendererFrame};
+use internal_types::{FontTemplate, SourceTexture, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, TextureCacheProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 use std::collections::HashMap;
 use std::io::{Cursor, Read};
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
 use thread_profiler::register_thread_with_profiler;
 use threadpool::ThreadPool;
+use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use webrender_traits::{DeviceIntPoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize, LayerPoint};
 use webrender_traits::{ApiMsg, AuxiliaryLists, BuiltDisplayList, IdNamespace, ImageData};
 use webrender_traits::{PipelineId, RenderNotifier, RenderDispatcher, WebGLCommand, WebGLContextId};
 use webrender_traits::channel::{PayloadHelperMethods, PayloadReceiver, PayloadSender, MsgReceiver};
 use webrender_traits::{BlobImageRenderer, VRCompositorCommand, VRCompositorHandler};
+#[cfg(feature = "webgl")]
 use offscreen_gl_context::GLContextDispatcher;
+#[cfg(not(feature = "webgl"))]
+use webgl_types::GLContextDispatcher;
 
 /// The render backend is responsible for transforming high level display lists into
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
     api_rx: MsgReceiver<ApiMsg>,
     payload_rx: PayloadReceiver,
@@ -142,18 +145,18 @@ impl RenderBackend {
                             tx.send(glyph_dimensions).unwrap();
                         }
                         ApiMsg::AddImage(id, descriptor, data, tiling) => {
                             if let ImageData::Raw(ref bytes) = data {
                                 profile_counters.image_templates.inc(bytes.len());
                             }
                             self.resource_cache.add_image_template(id, descriptor, data, tiling);
                         }
-                        ApiMsg::UpdateImage(id, descriptor, bytes) => {
-                            self.resource_cache.update_image_template(id, descriptor, bytes);
+                        ApiMsg::UpdateImage(id, descriptor, bytes, dirty_rect) => {
+                            self.resource_cache.update_image_template(id, descriptor, bytes, dirty_rect);
                         }
                         ApiMsg::DeleteImage(id) => {
                             self.resource_cache.delete_image_template(id);
                         }
                         ApiMsg::SetPageZoom(factor) => {
                             self.page_zoom_factor = factor.get();
                         }
                         ApiMsg::SetPinchZoom(factor) => {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
 use mask_cache::MaskCacheInfo;
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
-use tiling::{StackingContextIndex};
+use tiling::{RenderTargetKind, StackingContextIndex};
 use webrender_traits::{DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{MixBlendMode, ScrollLayerId};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct RenderTaskIndex(pub usize);
 
@@ -434,9 +434,20 @@ impl RenderTask {
 
     pub fn max_depth(&self, depth: usize, max_depth: &mut usize) {
         let depth = depth + 1;
         *max_depth = cmp::max(*max_depth, depth);
         for child in &self.children {
             child.max_depth(depth, max_depth);
         }
     }
+
+    pub fn target_kind(&self) -> RenderTargetKind {
+        match self.kind {
+            RenderTaskKind::Alpha(..) |
+            RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::VerticalBlur(..) |
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::HorizontalBlur(..) => RenderTargetKind::Color,
+            RenderTaskKind::CacheMask(..) => RenderTargetKind::Alpha,
+        }
+    }
 }
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -16,17 +16,17 @@ use device::{GpuSample, TextureFilter, V
 use euclid::Matrix4D;
 use fnv::FnvHasher;
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_store::{GpuStore, GpuStoreLayout};
 use internal_types::{CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
 use internal_types::{ExternalImageUpdateList, TextureUpdateList, PackedVertex, RenderTargetMode};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
-use internal_types::{BatchTextures, TextureSampler, GLContextHandleWrapper};
+use internal_types::{BatchTextures, TextureSampler};
 use prim_store::GradientData;
 use profiler::{Profiler, BackendProfileCounters};
 use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::RenderTaskData;
 use std;
 use std::cmp;
@@ -38,20 +38,21 @@ use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use threadpool::ThreadPool;
 use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, PrimitiveBatchData};
-use tiling::{CacheClipInstance, PrimitiveInstance, RenderTarget};
+use tiling::{AlphaRenderTarget, CacheClipInstance, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
+use webgl_types::GLContextHandleWrapper;
 use webrender_traits::{ColorF, Epoch, PipelineId, RenderNotifier, RenderDispatcher};
 use webrender_traits::{ExternalImageId, ImageData, ImageFormat, RenderApiSender};
 use webrender_traits::{DeviceIntRect, DevicePoint, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
 use webrender_traits::{ImageDescriptor, BlobImageRenderer};
 use webrender_traits::channel;
 use webrender_traits::VRCompositorHandler;
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
@@ -149,25 +150,32 @@ impl<L: GpuStoreLayout> GpuDataTexture<L
     fn init<T: Default>(&mut self,
                         device: &mut Device,
                         data: &mut Vec<T>) {
         if data.is_empty() {
             return;
         }
 
         let items_per_row = L::items_per_row::<T>();
+        let rows_per_item = L::rows_per_item::<T>();
 
         // Extend the data array to be a multiple of the row size.
         // This ensures memory safety when the array is passed to
         // OpenGL to upload to the GPU.
-        while data.len() % items_per_row != 0 {
-            data.push(T::default());
+        if items_per_row != 0 {
+            while data.len() % items_per_row != 0 {
+                data.push(T::default());
+            }
         }
 
-        let height = data.len() / items_per_row;
+        let height = if items_per_row != 0 {
+            data.len() / items_per_row
+        } else {
+            data.len() * rows_per_item
+        };
 
         device.init_texture(self.id,
                             L::texture_width::<T>() as u32,
                             height as u32,
                             L::image_format(),
                             L::texture_filter(),
                             RenderTargetMode::None,
                             Some(unsafe { mem::transmute(data.as_slice()) } ));
@@ -196,17 +204,17 @@ pub type VertexDataStore<T> = GpuStore<T
 pub struct GradientDataTextureLayout {}
 
 impl GpuStoreLayout for GradientDataTextureLayout {
     fn image_format() -> ImageFormat {
         ImageFormat::RGBA8
     }
 
     fn texture_width<T>() -> usize {
-        mem::size_of::<GradientData>() / Self::texel_size()
+        mem::size_of::<GradientData>() / Self::texel_size() / 2
     }
 
     fn texture_filter() -> TextureFilter {
         TextureFilter::Linear
     }
 }
 
 type GradientDataTexture = GpuDataTexture<GradientDataTextureLayout>;
@@ -477,17 +485,18 @@ pub struct Renderer {
     clear_color: ColorF,
     debug: DebugRenderer,
     render_target_debug: bool,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     profiler: Profiler,
     last_time: u64,
 
-    render_targets: Vec<TextureId>,
+    color_render_targets: Vec<TextureId>,
+    alpha_render_targets: Vec<TextureId>,
 
     gpu_profile: GpuProfiler<GpuProfileTag>,
     prim_vao_id: VAOId,
     blur_vao_id: VAOId,
     clip_vao_id: VAOId,
 
     gdt_index: usize,
     gpu_data_textures: [GpuDataTextures; GPU_DATA_TEXTURE_POOL],
@@ -507,16 +516,18 @@ pub struct Renderer {
     /// use a hashmap, and allows a flat vector for performance.
     cache_texture_id_map: Vec<TextureId>,
 
     /// A special 1x1 dummy cache texture used for shaders that expect to work
     /// with the cache but are actually running in the first pass
     /// when no target is yet provided as a cache texture input.
     dummy_cache_texture_id: TextureId,
 
+    dither_matrix_texture_id: TextureId,
+
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
     /// Map of external image IDs to native textures.
     external_images: HashMap<ExternalImageId, TextureId, BuildHasherDefault<FnvHasher>>,
 
     // Optional trait object that handles WebVR commands.
@@ -743,16 +754,28 @@ impl Renderer {
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
         ];
         let mask_pixels: Vec<u8> = vec![
             0xff, 0xff,
             0xff, 0xff,
         ];
+
+        let dither_matrix: [u8; 64] = [
+            00, 48, 12, 60, 03, 51, 15, 63,
+            32, 16, 44, 28, 35, 19, 47, 31,
+            08, 56, 04, 52, 11, 59, 07, 55,
+            40, 24, 36, 20, 43, 27, 39, 23,
+            02, 50, 14, 62, 01, 49, 13, 61,
+            34, 18, 46, 30, 33, 17, 45, 29,
+            10, 58, 06, 54, 09, 57, 05, 53,
+            42, 26, 38, 22, 41, 25, 37, 21
+        ];
+
         // TODO: Ensure that the white texture can never get evicted when the cache supports LRU eviction!
         let white_image_id = texture_cache.new_item_id();
         texture_cache.insert(white_image_id,
                              ImageDescriptor::new(2, 2, ImageFormat::RGBA8, false),
                              TextureFilter::Linear,
                              ImageData::Raw(Arc::new(white_pixels)),
                              &mut backend_profile_counters.texture_cache);
 
@@ -767,16 +790,25 @@ impl Renderer {
         device.init_texture(dummy_cache_texture_id,
                             1,
                             1,
                             ImageFormat::RGBA8,
                             TextureFilter::Linear,
                             RenderTargetMode::LayerRenderTarget(1),
                             None);
 
+        let dither_matrix_texture_id = device.create_texture_ids(1, TextureTarget::Default)[0];
+        device.init_texture(dither_matrix_texture_id,
+                            8,
+                            8,
+                            ImageFormat::A8,
+                            TextureFilter::Nearest,
+                            RenderTargetMode::None,
+                            Some(&dither_matrix));
+
         let debug_renderer = DebugRenderer::new(&mut device);
 
         let gpu_data_textures = [
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
             GpuDataTextures::new(&mut device),
@@ -896,27 +928,29 @@ impl Renderer {
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
             enable_profiler: options.enable_profiler,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_framebuffer: options.clear_framebuffer,
             clear_color: options.clear_color,
             last_time: 0,
-            render_targets: Vec::new(),
+            color_render_targets: Vec::new(),
+            alpha_render_targets: Vec::new(),
             gpu_profile: gpu_profile,
             prim_vao_id: prim_vao_id,
             blur_vao_id: blur_vao_id,
             clip_vao_id: clip_vao_id,
             gdt_index: 0,
             gpu_data_textures: gpu_data_textures,
             pipeline_epoch_map: HashMap::with_hasher(Default::default()),
             main_thread_dispatcher: main_thread_dispatcher,
             cache_texture_id_map: Vec::new(),
             dummy_cache_texture_id: dummy_cache_texture_id,
+            dither_matrix_texture_id: dither_matrix_texture_id,
             external_image_handler: None,
             external_images: HashMap::with_hasher(Default::default()),
             vr_compositor_handler: vr_compositor,
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
@@ -1247,16 +1281,19 @@ impl Renderer {
         self.device.bind_vao(vao);
         self.device.bind_program(shader, projection);
 
         for i in 0..textures.colors.len() {
             let texture_id = self.resolve_source_texture(&textures.colors[i]);
             self.device.bind_texture(TextureSampler::color(i), texture_id);
         }
 
+        // TODO: this probably isn't the best place for this.
+        self.device.bind_texture(TextureSampler::Dither, self.dither_matrix_texture_id);
+
         self.device.update_vao_instances(vao, data, VertexUsageHint::Stream);
         self.device.draw_indexed_triangles_instanced_u16(6, data.len() as i32);
         self.profile_counters.vertices.add(6 * data.len());
         self.profile_counters.draw_calls.inc();
     }
 
     fn submit_batch(&mut self,
                     batch: &PrimitiveBatch,
@@ -1402,85 +1439,40 @@ impl Renderer {
                                           vao,
                                           shader,
                                           &batch.key.textures,
                                           projection);
             }
         }
     }
 
-    fn draw_target(&mut self,
-                   render_target: Option<(TextureId, i32)>,
-                   target: &RenderTarget,
-                   target_size: DeviceUintSize,
-                   cache_texture: TextureId,
-                   should_clear: bool,
-                   background_color: Option<ColorF>,
-                   render_task_data: &Vec<RenderTaskData>) {
-        self.device.disable_depth();
-        self.device.enable_depth_write();
-
-        let projection = {
+    fn draw_color_target(&mut self,
+                         render_target: Option<(TextureId, i32)>,
+                         target: &ColorRenderTarget,
+                         target_size: DeviceUintSize,
+                         color_cache_texture: TextureId,
+                         clear_color: Option<[f32; 4]>,
+                         render_task_data: &Vec<RenderTaskData>,
+                         projection: &Matrix4D<f32>) {
+        {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
-
+            self.device.disable_depth();
+            self.device.enable_depth_write();
             self.device.set_blend(false);
             self.device.set_blend_mode_alpha();
-            self.device.bind_texture(TextureSampler::Cache, cache_texture);
-
-            let (color, projection) = match render_target {
-                Some(..) => (
-                    // The clear color here is chosen specifically such that:
-                    // - The red channel is cleared to 1, so that the clip mask
-                    //   generation (which reads/writes the red channel) can
-                    //   assume that each allocated rect is opaque / non-clipped
-                    //   initially.
-                    // - The alpha channel is cleared to 0, so that visual render
-                    //   tasks can assume that pixels are transparent if not
-                    //   rendered. (This is relied on by the compositing support
-                    //   for mix-blend-mode etc).
-                    [1.0, 1.0, 1.0, 0.0],
-                    Matrix4D::ortho(0.0,
-                                   target_size.width as f32,
-                                   0.0,
-                                   target_size.height as f32,
-                                   ORTHO_NEAR_PLANE,
-                                   ORTHO_FAR_PLANE)
-                ),
-                None => (
-                    background_color.map_or(self.clear_color.to_array(), |color| {
-                        color.to_array()
-                    }),
-                    Matrix4D::ortho(0.0,
-                                   target_size.width as f32,
-                                   target_size.height as f32,
-                                   0.0,
-                                   ORTHO_NEAR_PLANE,
-                                   ORTHO_FAR_PLANE)
-                ),
-            };
-
-            let clear_depth = Some(1.0);
-            let clear_color = if should_clear {
-                Some(color)
-            } else {
-                None
-            };
-
-            self.device.clear_target(clear_color, clear_depth);
+            self.device.clear_target(clear_color, Some(1.0));
 
             let isolate_clear_color = Some([0.0, 0.0, 0.0, 0.0]);
             for isolate_clear in &target.isolate_clears {
                 self.device.clear_target_rect(isolate_clear_color, None, *isolate_clear);
             }
 
-            projection
-        };
-
-        self.device.disable_depth_write();
+            self.device.disable_depth_write();
+        }
 
         // Draw any blurs for this target.
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
@@ -1510,47 +1502,16 @@ impl Renderer {
             let shader = self.cs_box_shadow.get(&mut self.device).unwrap();
             self.draw_instanced_batch(&target.box_shadow_cache_prims,
                                       vao,
                                       shader,
                                       &BatchTextures::no_texture(),
                                       &projection);
         }
 
-        // Draw the clip items into the tiled alpha mask.
-        {
-            let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
-            let vao = self.clip_vao_id;
-            // switch to multiplicative blending
-            self.device.set_blend(true);
-            self.device.set_blend_mode_multiply();
-            // draw rounded cornered rectangles
-            if !target.clip_batcher.rectangles.is_empty() {
-                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip rectangles");
-                let shader = self.cs_clip_rectangle.get(&mut self.device).unwrap();
-                self.draw_instanced_batch(&target.clip_batcher.rectangles,
-                                          vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
-            }
-            // draw image masks
-            for (mask_texture_id, items) in target.clip_batcher.images.iter() {
-                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip images");
-                let texture_id = self.resolve_source_texture(mask_texture_id);
-                self.device.bind_texture(TextureSampler::Mask, texture_id);
-                let shader = self.cs_clip_image.get(&mut self.device).unwrap();
-                self.draw_instanced_batch(items,
-                                          vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
-            }
-        }
-
         // Draw any textrun caches for this target. For now, this
         // is only used to cache text runs that are to be blurred
         // for text-shadow support. In the future it may be worth
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
@@ -1574,17 +1535,17 @@ impl Renderer {
         self.device.set_depth_func(DepthFunction::Less);
         self.device.enable_depth();
         self.device.enable_depth_write();
 
         for batch in &target.alpha_batcher.opaque_batches {
             self.submit_batch(batch,
                               &projection,
                               render_task_data,
-                              cache_texture,
+                              color_cache_texture,
                               render_target,
                               target_size);
         }
 
         self.device.disable_depth_write();
 
         for batch in &target.alpha_batcher.alpha_batches {
             if batch.key.blend_mode != prev_blend_mode {
@@ -1606,25 +1567,73 @@ impl Renderer {
                     }
                 }
                 prev_blend_mode = batch.key.blend_mode;
             }
 
             self.submit_batch(batch,
                               &projection,
                               render_task_data,
-                              cache_texture,
+                              color_cache_texture,
                               render_target,
                               target_size);
         }
 
         self.device.disable_depth();
         self.device.set_blend(false);
     }
 
+    fn draw_alpha_target(&mut self,
+                         render_target: (TextureId, i32),
+                         target: &AlphaRenderTarget,
+                         target_size: DeviceUintSize,
+                         projection: &Matrix4D<f32>) {
+        {
+            let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
+            self.device.bind_draw_target(Some(render_target), Some(target_size));
+            self.device.disable_depth();
+            self.device.disable_depth_write();
+
+            let clear_color = [1.0, 1.0, 1.0, 0.0];
+            self.device.clear_target(Some(clear_color), None);
+        }
+
+        // Draw the clip items into the tiled alpha mask.
+        {
+            let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
+            let vao = self.clip_vao_id;
+            // switch to multiplicative blending
+            self.device.set_blend(true);
+            self.device.set_blend_mode_multiply();
+
+            // draw rounded cornered rectangles
+            if !target.clip_batcher.rectangles.is_empty() {
+                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip rectangles");
+                let shader = self.cs_clip_rectangle.get(&mut self.device).unwrap();
+                self.draw_instanced_batch(&target.clip_batcher.rectangles,
+                                          vao,
+                                          shader,
+                                          &BatchTextures::no_texture(),
+                                          &projection);
+            }
+            // draw image masks
+            for (mask_texture_id, items) in target.clip_batcher.images.iter() {
+                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip images");
+                let texture_id = self.resolve_source_texture(mask_texture_id);
+                self.device.bind_texture(TextureSampler::Mask, texture_id);
+                let shader = self.cs_clip_image.get(&mut self.device).unwrap();
+                self.draw_instanced_batch(items,
+                                          vao,
+                                          shader,
+                                          &BatchTextures::no_texture(),
+                                          &projection);
+            }
+        }
+    }
+
     fn update_deferred_resolves(&mut self, frame: &mut Frame) {
         // The first thing we do is run through any pending deferred
         // resolves, and use a callback to get the UV rect for this
         // custom item. Then we patch the resource_rects structure
         // here before it's uploaded to the GPU.
         if !frame.deferred_resolves.is_empty() {
             let handler = self.external_image_handler
                               .as_mut()
@@ -1689,72 +1698,143 @@ impl Renderer {
 
         self.device.disable_depth_write();
         self.device.disable_stencil();
         self.device.set_blend(false);
 
         if frame.passes.is_empty() {
             self.device.clear_target(Some(self.clear_color.to_array()), Some(1.0));
         } else {
-            // Add new render targets to the pool if required.
-            let needed_targets = frame.passes.len() - 1;     // framebuffer doesn't need a target!
-            let current_target_count = self.render_targets.len();
-            if needed_targets > current_target_count {
-                let new_target_count = needed_targets - current_target_count;
-                let new_targets = self.device.create_texture_ids(new_target_count as i32,
-                                                                 TextureTarget::Array);
-                self.render_targets.extend_from_slice(&new_targets);
+            // Assign render targets to the passes.
+            for pass in &mut frame.passes {
+                debug_assert!(pass.color_texture_id.is_none());
+                debug_assert!(pass.alpha_texture_id.is_none());
+
+                if pass.needs_render_target_kind(RenderTargetKind::Color) {
+                    pass.color_texture_id = Some(self.color_render_targets
+                                                     .pop()
+                                                     .unwrap_or_else(|| {
+                                                         self.device
+                                                             .create_texture_ids(1, TextureTarget::Array)[0]
+                                                      }));
+                }
+
+                if pass.needs_render_target_kind(RenderTargetKind::Alpha) {
+                    pass.alpha_texture_id = Some(self.alpha_render_targets
+                                                     .pop()
+                                                     .unwrap_or_else(|| {
+                                                         self.device
+                                                             .create_texture_ids(1, TextureTarget::Array)[0]
+                                                      }));
+                }
             }
 
             // Init textures and render targets to match this scene.
-            for (pass, texture_id) in frame.passes.iter().zip(self.render_targets.iter()) {
-                self.device.init_texture(*texture_id,
-                                         frame.cache_size.width as u32,
-                                         frame.cache_size.height as u32,
-                                         ImageFormat::RGBA8,
-                                         TextureFilter::Linear,
-                                         RenderTargetMode::LayerRenderTarget(pass.targets.len() as i32),
-                                         None);
+            for pass in &frame.passes {
+                if let Some(texture_id) = pass.color_texture_id {
+                    let target_count = pass.required_target_count(RenderTargetKind::Color);
+                    self.device.init_texture(texture_id,
+                                             frame.cache_size.width as u32,
+                                             frame.cache_size.height as u32,
+                                             ImageFormat::RGBA8,
+                                             TextureFilter::Linear,
+                                             RenderTargetMode::LayerRenderTarget(target_count as i32),
+                                             None);
+                }
+                if let Some(texture_id) = pass.alpha_texture_id {
+                    let target_count = pass.required_target_count(RenderTargetKind::Alpha);
+                    self.device.init_texture(texture_id,
+                                             frame.cache_size.width as u32,
+                                             frame.cache_size.height as u32,
+                                             ImageFormat::A8,
+                                             TextureFilter::Nearest,
+                                             RenderTargetMode::LayerRenderTarget(target_count as i32),
+                                             None);
+                }
             }
 
             // TODO(gw): This is a hack / workaround for #728.
             // We should find a better way to implement these updates rather
             // than wasting this extra memory, but for now it removes a large
             // number of driver stalls.
             self.gpu_data_textures[self.gdt_index].init_frame(&mut self.device, frame);
             self.gdt_index = (self.gdt_index + 1) % GPU_DATA_TEXTURE_POOL;
 
-            let mut src_id = self.dummy_cache_texture_id;
+            let mut src_color_id = self.dummy_cache_texture_id;
+            let mut src_alpha_id = self.dummy_cache_texture_id;
+
+            for pass in &mut frame.passes {
+                let size;
+                let clear_color;
+                let projection;
 
-            for (pass_index, pass) in frame.passes.iter().enumerate() {
-                let (do_clear, size, target_id) = if pass.is_framebuffer {
-                    (self.clear_framebuffer || needs_clear,
-                     framebuffer_size,
-                     None)
+                if pass.is_framebuffer {
+                    clear_color = if self.clear_framebuffer || needs_clear {
+                        Some(frame.background_color.map_or(self.clear_color.to_array(), |color| {
+                            color.to_array()
+                        }))
+                    } else {
+                        None
+                    };
+                    size = framebuffer_size;
+                    projection = Matrix4D::ortho(0.0,
+                                                 size.width as f32,
+                                                 size.height as f32,
+                                                 0.0,
+                                                 ORTHO_NEAR_PLANE,
+                                                 ORTHO_FAR_PLANE)
                 } else {
-                    (true, &frame.cache_size, Some(self.render_targets[pass_index]))
-                };
+                    size = &frame.cache_size;
+                    clear_color = Some([1.0, 1.0, 1.0, 0.0]);
+                    projection = Matrix4D::ortho(0.0,
+                                                 size.width as f32,
+                                                 0.0,
+                                                 size.height as f32,
+                                                 ORTHO_NEAR_PLANE,
+                                                 ORTHO_FAR_PLANE);
+                }
 
-                for (target_index, target) in pass.targets.iter().enumerate() {
-                    let render_target = target_id.map(|texture_id| {
+                self.device.bind_texture(TextureSampler::CacheA8, src_alpha_id);
+                self.device.bind_texture(TextureSampler::CacheRGBA8, src_color_id);
+
+                for (target_index, target) in pass.alpha_targets.targets.iter().enumerate() {
+                    self.draw_alpha_target((pass.alpha_texture_id.unwrap(), target_index as i32),
+                                           target,
+                                           *size,
+                                           &projection);
+                }
+
+                for (target_index, target) in pass.color_targets.targets.iter().enumerate() {
+                    let render_target = pass.color_texture_id.map(|texture_id| {
                         (texture_id, target_index as i32)
                     });
-                    self.draw_target(render_target,
-                                     target,
-                                     *size,
-                                     src_id,
-                                     do_clear,
-                                     frame.background_color,
-                                     &frame.render_task_data);
+                    self.draw_color_target(render_target,
+                                           target,
+                                           *size,
+                                           src_color_id,
+                                           clear_color,
+                                           &frame.render_task_data,
+                                           &projection);
 
                 }
 
-                src_id = target_id.unwrap_or(self.dummy_cache_texture_id);
+                src_color_id = pass.color_texture_id.unwrap_or(self.dummy_cache_texture_id);
+                src_alpha_id = pass.alpha_texture_id.unwrap_or(self.dummy_cache_texture_id);
+
+                // Return the texture IDs to the pool for next frame.
+                if let Some(texture_id) = pass.color_texture_id.take() {
+                    self.color_render_targets.push(texture_id);
+                }
+                if let Some(texture_id) = pass.alpha_texture_id.take() {
+                    self.alpha_render_targets.push(texture_id);
+                }
             }
 
+            self.color_render_targets.reverse();
+            self.alpha_render_targets.reverse();
             self.draw_render_target_debug(framebuffer_size);
         }
 
         self.unlock_external_images();
     }
 
     pub fn debug_renderer<'a>(&'a mut self) -> &'a mut DebugRenderer {
         &mut self.debug
@@ -1779,17 +1859,17 @@ impl Renderer {
             // Right now, it just draws them in one row at the bottom of the screen,
             // with a fixed size.
             let rt_debug_x0 = 16;
             let rt_debug_y0 = 16;
             let rt_debug_spacing = 16;
             let rt_debug_size = 512;
             let mut current_target = 0;
 
-            for texture_id in &self.render_targets {
+            for texture_id in self.color_render_targets.iter().chain(self.alpha_render_targets.iter()) {
                 let layer_count = self.device.get_render_target_layer_count(*texture_id);
                 for layer_index in 0..layer_count {
                     let x0 = rt_debug_x0 + (rt_debug_spacing + rt_debug_size) * current_target;
                     let y0 = rt_debug_y0;
 
                     // If we have more targets than fit on one row in screen, just early exit.
                     if x0 > framebuffer_size.width as i32 {
                         return;
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -18,17 +18,17 @@ use std::hash::Hash;
 use std::mem;
 use std::sync::{Arc, Barrier, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::{TextureCache, TextureCacheItemId};
 use thread_profiler::register_thread_with_profiler;
 use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering};
 use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
-use webrender_traits::{DevicePoint, DeviceIntSize, ImageDescriptor, ColorF};
+use webrender_traits::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
 use webrender_traits::{ExternalImageId, GlyphOptions, GlyphInstance, TileOffset, TileSize};
 use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError};
 use threadpool::ThreadPool;
 use euclid::Point2D;
 
 thread_local!(pub static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new()));
 
 type GlyphCache = ResourceClassCache<RenderedGlyphKey, Option<TextureCacheItemId>>;
@@ -105,16 +105,17 @@ enum State {
     QueryResources,
 }
 
 struct ImageResource {
     data: ImageData,
     descriptor: ImageDescriptor,
     epoch: Epoch,
     tiling: Option<TileSize>,
+    dirty_rect: Option<DeviceUintRect>
 }
 
 struct CachedImageInfo {
     texture_cache_id: TextureCacheItemId,
     epoch: Epoch,
 }
 
 pub struct ResourceClassCache<K,V> {
@@ -280,48 +281,55 @@ impl ResourceCache {
             tiling = Some(512);
         }
 
         let resource = ImageResource {
             descriptor: descriptor,
             data: data,
             epoch: Epoch(0),
             tiling: tiling,
+            dirty_rect: None,
         };
 
         self.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(&mut self,
                                  image_key: ImageKey,
                                  descriptor: ImageDescriptor,
-                                 bytes: Vec<u8>) {
-        let next_epoch = match self.image_templates.get(&image_key) {
+                                 bytes: Vec<u8>,
+                                 dirty_rect: Option<DeviceUintRect>) {
+        let (next_epoch, prev_dirty_rect) = match self.image_templates.get(&image_key) {
             Some(image) => {
                 // This image should not be an external image.
                 match image.data {
                     ImageData::ExternalHandle(id) => {
                         panic!("Update an external image with buffer, id={} image_key={:?}", id.0, image_key);
                     },
                     _ => {},
                 }
 
                 let Epoch(current_epoch) = image.epoch;
-                Epoch(current_epoch + 1)
+                (Epoch(current_epoch + 1), image.dirty_rect)
             }
             None => {
-                Epoch(0)
+                (Epoch(0), None)
             }
         };
 
         let resource = ImageResource {
             descriptor: descriptor,
             data: ImageData::new(bytes),
             epoch: next_epoch,
             tiling: None,
+            dirty_rect: match (dirty_rect, prev_dirty_rect) {
+                (Some(rect), Some(prev_rect)) => Some(rect.union(&prev_rect)),
+                (Some(rect), None) => Some(rect),
+                _ => None,
+            },
         };
 
         self.image_templates.insert(image_key, resource);
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         let value = self.image_templates.remove(&image_key);
 
@@ -643,17 +651,17 @@ impl ResourceCache {
             }
         }
     }
 
     fn finalize_image_request(&mut self,
                               request: ImageRequest,
                               image_data: Option<ImageData>,
                               texture_cache_profile: &mut TextureCacheProfileCounters) {
-        let image_template = &self.image_templates[&request.key];
+        let image_template = self.image_templates.get_mut(&request.key).unwrap();
         let image_data = image_data.unwrap_or_else(||{
             image_template.data.clone()
         });
 
         match image_template.data {
             ImageData::ExternalHandle(..) => {
                 // external handle doesn't need to update the texture_cache.
             }
@@ -695,23 +703,25 @@ impl ResourceCache {
 
                 match self.cached_images.entry(request.clone(), self.current_frame_id) {
                     Occupied(entry) => {
                         let image_id = entry.get().texture_cache_id;
 
                         if entry.get().epoch != image_template.epoch {
                             self.texture_cache.update(image_id,
                                                       descriptor,
-                                                      image_data);
+                                                      image_data,
+                                                      image_template.dirty_rect);
 
                             // Update the cached epoch
                             *entry.into_mut() = CachedImageInfo {
                                 texture_cache_id: image_id,
                                 epoch: image_template.epoch,
                             };
+                            image_template.dirty_rect = None;
                         }
                     }
                     Vacant(entry) => {
                         let image_id = self.texture_cache.new_item_id();
 
                         let filter = match request.rendering {
                             ImageRendering::Pixelated => TextureFilter::Nearest,
                             ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -701,39 +701,54 @@ impl TextureCache {
             item: cache_item,
             kind: AllocationKind::TexturePage,
         }
     }
 
     pub fn update(&mut self,
                   image_id: TextureCacheItemId,
                   descriptor: ImageDescriptor,
-                  data: ImageData) {
+                  data: ImageData,
+                  dirty_rect: Option<DeviceUintRect>) {
         let existing_item = self.items.get(image_id);
 
         // TODO(gw): Handle updates to size/format!
         debug_assert_eq!(existing_item.allocated_rect.size.width, descriptor.width);
         debug_assert_eq!(existing_item.allocated_rect.size.height, descriptor.height);
 
         let op = match data {
             ImageData::ExternalHandle(..) | ImageData::ExternalBuffer(..)=> {
                 panic!("Doesn't support Update() for external image.");
             }
             ImageData::Blob(..) => {
                 panic!("The vector image should have been rasterized into a raw image.");
             }
             ImageData::Raw(bytes) => {
-                TextureUpdateOp::Update {
-                    page_pos_x: existing_item.allocated_rect.origin.x,
-                    page_pos_y: existing_item.allocated_rect.origin.y,
-                    width: descriptor.width,
-                    height: descriptor.height,
-                    data: bytes,
-                    stride: descriptor.stride,
-                    offset: descriptor.offset,
+                if let Some(dirty) = dirty_rect {
+                    let stride = descriptor.compute_stride();
+                    let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x;
+                    TextureUpdateOp::Update {
+                        page_pos_x: existing_item.allocated_rect.origin.x + dirty.origin.x,
+                        page_pos_y: existing_item.allocated_rect.origin.y + dirty.origin.y,
+                        width: dirty.size.width,
+                        height: dirty.size.height,
+                        data: bytes,
+                        stride: Some(stride),
+                        offset: offset,
+                    }
+                } else {
+                    TextureUpdateOp::Update {
+                        page_pos_x: existing_item.allocated_rect.origin.x,
+                        page_pos_y: existing_item.allocated_rect.origin.y,
+                        width: descriptor.width,
+                        height: descriptor.height,
+                        data: bytes,
+                        stride: descriptor.stride,
+                        offset: descriptor.offset,
+                    }
                 }
             }
         };
 
         let update_op = TextureUpdate {
             id: existing_item.texture_id,
             op: op,
         };
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
+use device::TextureId;
 use fnv::FnvHasher;
 use gpu_store::GpuStoreAddress;
 use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchTextures, CacheTextureId, LowLevelFilterOp};
 use internal_types::SourceTexture;
 use mask_cache::MaskCacheInfo;
 use prim_store::{CLIP_DATA_GPU_SIZE, DeferredResolve, GpuBlock128, GpuBlock16, GpuBlock32};
 use prim_store::{GpuBlock64, GradientData, PrimitiveCacheKey, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveKind, PrimitiveMetadata, PrimitiveStore, TexelRect};
@@ -123,17 +124,20 @@ impl AlphaBatchHelpers for PrimitiveStor
                         FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
                         FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
                     }
                 } else {
                     // Text runs drawn to blur never get drawn with subpixel AA.
                     BlendMode::Alpha
                 }
             }
-            PrimitiveKind::Image => {
+            PrimitiveKind::Image |
+            PrimitiveKind::AlignedGradient |
+            PrimitiveKind::AngleGradient |
+            PrimitiveKind::RadialGradient => {
                 if needs_blending {
                     BlendMode::PremultipliedAlpha
                 } else {
                     BlendMode::None
                 }
             }
             _ => {
                 if needs_blending {
@@ -408,28 +412,21 @@ pub enum PrimitiveRunCmd {
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum PrimitiveFlags {
     None,
     Scrollbar(ScrollLayerId, f32)
 }
 
-// TODO(gw): I've had to make several of these types below public
-//           with the changes for text-shadow. The proper solution
-//           is to split the render task and render target code into
-//           its own module. However, I'm avoiding that for now since
-//           this PR is large enough already, and other people are working
-//           on PRs that make use of render tasks.
-
 #[derive(Debug, Copy, Clone)]
 pub struct RenderTargetIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-struct RenderPassIndex(isize);
+pub struct RenderPassIndex(isize);
 
 struct DynamicTaskInfo {
     index: RenderTaskIndex,
     rect: DeviceIntRect,
 }
 
 pub struct RenderTaskCollection {
     pub render_task_data: Vec<RenderTaskData>,
@@ -852,20 +849,99 @@ impl ClipBatcher {
 
 pub struct RenderTargetContext<'a> {
     pub stacking_context_store: &'a [StackingContext],
     pub clip_scroll_group_store: &'a [ClipScrollGroup],
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
 }
 
+pub trait RenderTarget {
+    fn new(size: DeviceUintSize) -> Self;
+    fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint>;
+    fn build(&mut self,
+             _ctx: &RenderTargetContext,
+             _render_tasks: &mut RenderTaskCollection,
+             _child_pass_index: RenderPassIndex) {}
+    fn add_task(&mut self,
+                task: RenderTask,
+                ctx: &RenderTargetContext,
+                render_tasks: &RenderTaskCollection,
+                pass_index: RenderPassIndex);
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum RenderTargetKind {
+    Color,   // RGBA32
+    Alpha,   // R8
+}
+
+pub struct RenderTargetList<T> {
+    target_size: DeviceUintSize,
+    pub targets: Vec<T>,
+}
+
+impl<T: RenderTarget> RenderTargetList<T> {
+    fn new(target_size: DeviceUintSize, create_initial_target: bool) -> RenderTargetList<T> {
+        let mut targets = Vec::new();
+        if create_initial_target {
+            targets.push(T::new(target_size));
+        }
+
+        RenderTargetList {
+            targets: targets,
+            target_size: target_size,
+        }
+    }
+
+    pub fn target_count(&self) -> usize {
+        self.targets.len()
+    }
+
+    fn build(&mut self,
+             ctx: &RenderTargetContext,
+             render_tasks: &mut RenderTaskCollection,
+             pass_index: RenderPassIndex) {
+        for target in &mut self.targets {
+            let child_pass_index = RenderPassIndex(pass_index.0 - 1);
+            target.build(ctx, render_tasks, child_pass_index);
+        }
+    }
+
+    fn add_task(&mut self,
+                task: RenderTask,
+                ctx: &RenderTargetContext,
+                render_tasks: &mut RenderTaskCollection,
+                pass_index: RenderPassIndex) {
+        self.targets.last_mut().unwrap().add_task(task, ctx, render_tasks, pass_index);
+    }
+
+    fn allocate(&mut self, alloc_size: DeviceUintSize) -> (DeviceUintPoint, RenderTargetIndex) {
+        let existing_origin = self.targets
+                                  .last_mut()
+                                  .and_then(|target| target.allocate(alloc_size));
+
+        let origin = match existing_origin {
+            Some(origin) => origin,
+            None => {
+                let mut new_target = T::new(self.target_size);
+                let origin = new_target.allocate(alloc_size)
+                                       .expect(&format!("Each render task must allocate <= size of one target! ({:?})", alloc_size));
+                self.targets.push(new_target);
+                origin
+            }
+        };
+
+        (origin, RenderTargetIndex(self.targets.len() - 1))
+    }
+}
+
 /// A render target represents a number of rendering operations on a surface.
-pub struct RenderTarget {
+pub struct ColorRenderTarget {
     pub alpha_batcher: AlphaBatcher,
-    pub clip_batcher: ClipBatcher,
     pub box_shadow_cache_prims: Vec<PrimitiveInstance>,
     // List of text runs to be cached to this render target.
     // TODO(gw): For now, assume that these all come from
     //           the same source texture id. This is almost
     //           always true except for pathological test
     //           cases with more than 4k x 4k of unique
     //           glyphs visible. Once the future glyph / texture
     //           cache changes land, this restriction will
@@ -875,21 +951,24 @@ pub struct RenderTarget {
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurCommand>,
     pub horizontal_blurs: Vec<BlurCommand>,
     pub readbacks: Vec<DeviceIntRect>,
     pub isolate_clears: Vec<DeviceIntRect>,
     page_allocator: TexturePage,
 }
 
-impl RenderTarget {
-    fn new(size: DeviceUintSize) -> RenderTarget {
-        RenderTarget {
+impl RenderTarget for ColorRenderTarget {
+    fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
+        self.page_allocator.allocate(&size)
+    }
+
+    fn new(size: DeviceUintSize) -> ColorRenderTarget {
+        ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
-            clip_batcher: ClipBatcher::new(),
             box_shadow_cache_prims: Vec::new(),
             text_run_cache_prims: Vec::new(),
             text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             isolate_clears: Vec::new(),
             page_allocator: TexturePage::new(CacheTextureId(0), size),
@@ -1003,84 +1082,142 @@ impl RenderTarget {
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
+            RenderTaskKind::CacheMask(..) => {
+                panic!("Should not be added to color target!");
+            }
+            RenderTaskKind::Readback(device_rect) => {
+                self.readbacks.push(device_rect);
+            }
+        }
+    }
+}
+
+pub struct AlphaRenderTarget {
+    pub clip_batcher: ClipBatcher,
+    page_allocator: TexturePage,
+}
+
+impl RenderTarget for AlphaRenderTarget {
+    fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
+        self.page_allocator.allocate(&size)
+    }
+
+    fn new(size: DeviceUintSize) -> AlphaRenderTarget {
+        AlphaRenderTarget {
+            clip_batcher: ClipBatcher::new(),
+            page_allocator: TexturePage::new(CacheTextureId(0), size),
+        }
+    }
+
+    fn add_task(&mut self,
+                task: RenderTask,
+                ctx: &RenderTargetContext,
+                render_tasks: &RenderTaskCollection,
+                pass_index: RenderPassIndex) {
+        match task.kind {
+            RenderTaskKind::Alpha(..) |
+            RenderTaskKind::VerticalBlur(..) |
+            RenderTaskKind::HorizontalBlur(..) |
+            RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::Readback(..) => {
+                panic!("Should not be added to alpha target!");
+            }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_index = render_tasks.get_task_index(&task.id, pass_index);
                 self.clip_batcher.add(task_index,
                                       &task_info.clips,
                                       &ctx.resource_cache,
                                       task_info.geometry_kind);
             }
-            RenderTaskKind::Readback(device_rect) => {
-                self.readbacks.push(device_rect);
-            }
         }
     }
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
 /// A render pass can have several render targets if there wasn't enough space in one
 /// target to do all of the rendering for that pass.
 pub struct RenderPass {
     pass_index: RenderPassIndex,
     pub is_framebuffer: bool,
     tasks: Vec<RenderTask>,
-    pub targets: Vec<RenderTarget>,
-    size: DeviceUintSize,
+    pub color_targets: RenderTargetList<ColorRenderTarget>,
+    pub alpha_targets: RenderTargetList<AlphaRenderTarget>,
+    pub color_texture_id: Option<TextureId>,
+    pub alpha_texture_id: Option<TextureId>,
 }
 
 impl RenderPass {
     pub fn new(pass_index: isize, is_framebuffer: bool, size: DeviceUintSize) -> RenderPass {
         RenderPass {
             pass_index: RenderPassIndex(pass_index),
             is_framebuffer: is_framebuffer,
-            targets: vec![ RenderTarget::new(size) ],
+            color_targets: RenderTargetList::new(size, is_framebuffer),
+            alpha_targets: RenderTargetList::new(size, false),
             tasks: vec![],
-            size: size,
+            color_texture_id: None,
+            alpha_texture_id: None,
         }
     }
 
     pub fn add_render_task(&mut self, task: RenderTask) {
         self.tasks.push(task);
     }
 
-    fn allocate_target(&mut self, alloc_size: DeviceUintSize) -> DeviceUintPoint {
-        let existing_origin = self.targets
-                                  .last_mut()
-                                  .unwrap()
-                                  .page_allocator
-                                  .allocate(&alloc_size);
-        match existing_origin {
-            Some(origin) => origin,
-            None => {
-                let mut new_target = RenderTarget::new(self.size);
-                let origin = new_target.page_allocator
-                                       .allocate(&alloc_size)
-                                       .expect(&format!("Each render task must allocate <= size of one target! ({:?})", alloc_size));
-                self.targets.push(new_target);
-                origin
-            }
+    fn add_task(&mut self,
+                task: RenderTask,
+                ctx: &RenderTargetContext,
+                render_tasks: &mut RenderTaskCollection) {
+        match task.target_kind() {
+            RenderTargetKind::Color => self.color_targets.add_task(task, ctx, render_tasks, self.pass_index),
+            RenderTargetKind::Alpha => self.alpha_targets.add_task(task, ctx, render_tasks, self.pass_index),
         }
     }
 
+    fn allocate_target(&mut self,
+                       kind: RenderTargetKind,
+                       alloc_size: DeviceUintSize) -> (DeviceUintPoint, RenderTargetIndex) {
+        match kind {
+            RenderTargetKind::Color => self.color_targets.allocate(alloc_size),
+            RenderTargetKind::Alpha => self.alpha_targets.allocate(alloc_size),
+        }
+    }
+
+    pub fn needs_render_target_kind(&self, kind: RenderTargetKind) -> bool {
+        if self.is_framebuffer {
+            false
+        } else {
+            self.required_target_count(kind) > 0
+        }
+    }
+
+    pub fn required_target_count(&self, kind: RenderTargetKind) -> usize {
+        debug_assert!(!self.is_framebuffer);        // framebuffer never needs targets
+        match kind {
+            RenderTargetKind::Color => self.color_targets.target_count(),
+            RenderTargetKind::Alpha => self.alpha_targets.target_count(),
+        }
+    }
 
     pub fn build(&mut self, ctx: &RenderTargetContext, render_tasks: &mut RenderTaskCollection) {
         profile_scope!("RenderPass::build");
 
         // Step through each task, adding to batches as appropriate.
         let tasks = mem::replace(&mut self.tasks, Vec::new());
         for mut task in tasks {
+            let target_kind = task.target_kind();
+
             // Find a target to assign this task to, or create a new
             // one if required.
             match task.location {
                 RenderTaskLocation::Fixed => {}
                 RenderTaskLocation::Dynamic(ref mut origin, ref size) => {
                     // See if this task is a duplicate.
                     // If so, just skip adding it!
                     match task.id {
@@ -1093,35 +1230,30 @@ impl RenderPass {
                             if let Some(rect) = render_tasks.get_dynamic_allocation(self.pass_index, key) {
                                 debug_assert_eq!(rect.size, *size);
                                 continue;
                             }
                         }
                     }
 
                     let alloc_size = DeviceUintSize::new(size.width as u32, size.height as u32);
-                    let alloc_origin = self.allocate_target(alloc_size);
+                    let (alloc_origin, target_index) = self.allocate_target(target_kind, alloc_size);
 
                     *origin = Some((DeviceIntPoint::new(alloc_origin.x as i32,
-                                                     alloc_origin.y as i32),
-                                    RenderTargetIndex(self.targets.len() - 1)));
+                                                        alloc_origin.y as i32),
+                                    target_index));
                 }
             }
 
             render_tasks.add(&task, self.pass_index);
-            self.targets.last_mut().unwrap().add_task(task,
-                                                      ctx,
-                                                      render_tasks,
-                                                      self.pass_index);
+            self.add_task(task, ctx, render_tasks);
         }
 
-        for target in &mut self.targets {
-            let child_pass_index = RenderPassIndex(self.pass_index.0 - 1);
-            target.build(ctx, render_tasks, child_pass_index);
-        }
+        self.color_targets.build(ctx, render_tasks, self.pass_index);
+        self.alpha_targets.build(ctx, render_tasks, self.pass_index);
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[repr(u8)]
 pub enum AlphaBatchKind {
     Composite = 0,
     HardwareComposite,
@@ -1319,46 +1451,46 @@ pub struct StackingContextIndex(pub usiz
 #[derive(Debug)]
 pub struct StackingContext {
     pub pipeline_id: PipelineId,
 
     // Offset in the parent reference frame to the origin of this stacking
     // context's coordinate system.
     pub reference_frame_offset: LayerPoint,
 
-    // Bounds of this stacking context in its own coordinate system.
-    pub local_rect: LayerRect,
+    // Bounding rectangle for this stacking context calculated based on the size
+    // and position of all its children.
+    pub bounding_rect: DeviceIntRect,
 
-    pub bounding_rect: DeviceIntRect,
     pub composite_ops: CompositeOps,
     pub clip_scroll_groups: Vec<ClipScrollGroupIndex>,
 
     // Signifies that this stacking context should be drawn in a separate render pass
     // with a transparent background and then composited back to its parent. Used to
     // support mix-blend-mode in certain cases.
     pub should_isolate: bool,
 
     // Set for the root stacking context of a display list or an iframe. Used for determining
     // when to isolate a mix-blend-mode composite.
     pub is_page_root: bool,
 
+    // Wehther or not this stacking context has any visible components, calculated
+    // based on the size and position of all children and how they are clipped.
     pub is_visible: bool,
 }
 
 impl StackingContext {
     pub fn new(pipeline_id: PipelineId,
                reference_frame_offset: LayerPoint,
-               local_rect: LayerRect,
                is_page_root: bool,
                composite_ops: CompositeOps)
                -> StackingContext {
         StackingContext {
             pipeline_id: pipeline_id,
             reference_frame_offset: reference_frame_offset,
-            local_rect: local_rect,
             bounding_rect: DeviceIntRect::zero(),
             composite_ops: composite_ops,
             clip_scroll_groups: Vec::new(),
             should_isolate: false,
             is_page_root: is_page_root,
             is_visible: false,
         }
     }
@@ -1427,32 +1559,27 @@ impl PackedLayer {
     }
 
     pub fn set_transform(&mut self, transform: LayerToWorldTransform) {
         self.transform = transform;
         self.inv_transform = self.transform.inverse().unwrap();
     }
 
     pub fn set_rect(&mut self,
-                    local_rect: Option<LayerRect>,
+                    local_rect: &LayerRect,
                     screen_rect: &DeviceIntRect,
                     device_pixel_ratio: f32)
                     -> Option<TransformedRect> {
-        let local_rect = match local_rect {
-            Some(rect) if !rect.is_empty() => rect,
-            _ => return None,
-        };
-
         let xf_rect = TransformedRect::new(&local_rect, &self.transform, device_pixel_ratio);
         if !xf_rect.bounding_rect.intersects(screen_rect) {
             return None;
         }
 
         self.screen_vertices = xf_rect.vertices.clone();
-        self.local_clip_rect = local_rect;
+        self.local_clip_rect = *local_rect;
         Some(xf_rect)
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
     pub filters: Vec<LowLevelFilterOp>,
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/webgl_stubs.rs
@@ -0,0 +1,59 @@
+/* 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/. */
+
+//! Stubs for the types contained in webgl_types.rs
+//!
+//! The API surface provided here should be roughly the same to the one provided
+//! in webgl_types, modulo completely compiled-out stuff.
+
+use webrender_traits::DeviceIntSize;
+use webrender_traits::{GLContextAttributes, GLLimits};
+use webrender_traits::WebGLCommand;
+
+pub struct GLContextHandleWrapper;
+
+impl GLContextHandleWrapper {
+    pub fn new_context(&self,
+                       _: DeviceIntSize,
+                       _: GLContextAttributes,
+                       _: Option<Box<GLContextDispatcher>>) -> Result<GLContextWrapper, &'static str> {
+        unreachable!()
+    }
+
+    pub fn current_native_handle() -> Option<GLContextHandleWrapper> {
+        None
+    }
+
+    pub fn current_osmesa_handle() -> Option<GLContextHandleWrapper> {
+        None
+    }
+}
+
+pub struct GLContextWrapper;
+
+impl GLContextWrapper {
+    pub fn make_current(&self) {
+        unreachable!()
+    }
+
+    pub fn unbind(&self) {
+        unreachable!()
+    }
+
+    pub fn apply_command(&self, _: WebGLCommand) {
+        unreachable!()
+    }
+
+    pub fn get_info(&self) -> (DeviceIntSize, u32, GLLimits) {
+        unreachable!()
+    }
+
+    pub fn resize(&mut self, _: &DeviceIntSize) -> Result<(), &'static str> {
+        unreachable!()
+    }
+}
+
+pub trait GLContextDispatcher {
+    fn dispatch(&self, Box<Fn() + Send>);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/webgl_types.rs
@@ -0,0 +1,130 @@
+/* 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/. */
+
+//! A set of WebGL-related types, in their own module so it's easy to
+//! compile it off.
+
+use gleam::gl;
+use offscreen_gl_context::{NativeGLContext, NativeGLContextHandle};
+use offscreen_gl_context::{GLContext, NativeGLContextMethods, GLContextDispatcher};
+use offscreen_gl_context::{OSMesaContext, OSMesaContextHandle};
+use offscreen_gl_context::{ColorAttachmentType, GLContextAttributes, GLLimits};
+use webrender_traits::{WebGLCommand, DeviceIntSize};
+
+pub enum GLContextHandleWrapper {
+    Native(NativeGLContextHandle),
+    OSMesa(OSMesaContextHandle),
+}
+
+impl GLContextHandleWrapper {
+    pub fn current_native_handle() -> Option<GLContextHandleWrapper> {
+        NativeGLContext::current_handle().map(GLContextHandleWrapper::Native)
+    }
+
+    pub fn current_osmesa_handle() -> Option<GLContextHandleWrapper> {
+        OSMesaContext::current_handle().map(GLContextHandleWrapper::OSMesa)
+    }
+
+    pub fn new_context(&self,
+                       size: DeviceIntSize,
+                       attributes: GLContextAttributes,
+                       dispatcher: Option<Box<GLContextDispatcher>>) -> Result<GLContextWrapper, &'static str> {
+        match *self {
+            GLContextHandleWrapper::Native(ref handle) => {
+                let ctx = GLContext::<NativeGLContext>::new_shared_with_dispatcher(size.to_untyped(),
+                                                                                   attributes,
+                                                                                   ColorAttachmentType::Texture,
+                                                                                   gl::GlType::default(),
+                                                                                   Some(handle),
+                                                                                   dispatcher);
+                ctx.map(GLContextWrapper::Native)
+            }
+            GLContextHandleWrapper::OSMesa(ref handle) => {
+                let ctx = GLContext::<OSMesaContext>::new_shared_with_dispatcher(size.to_untyped(),
+                                                                                 attributes,
+                                                                                 ColorAttachmentType::Texture,
+                                                                                 gl::GlType::default(),
+                                                                                 Some(handle),
+                                                                                 dispatcher);
+                ctx.map(GLContextWrapper::OSMesa)
+            }
+        }
+    }
+}
+
+pub enum GLContextWrapper {
+    Native(GLContext<NativeGLContext>),
+    OSMesa(GLContext<OSMesaContext>),
+}
+
+impl GLContextWrapper {
+    pub fn make_current(&self) {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                ctx.make_current().unwrap();
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                ctx.make_current().unwrap();
+            }
+        }
+    }
+
+    pub fn unbind(&self) {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                ctx.unbind().unwrap();
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                ctx.unbind().unwrap();
+            }
+        }
+    }
+
+    pub fn apply_command(&self, cmd: WebGLCommand) {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                cmd.apply(ctx);
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                cmd.apply(ctx);
+            }
+        }
+    }
+
+    pub fn get_info(&self) -> (DeviceIntSize, u32, GLLimits) {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                let (real_size, texture_id) = {
+                    let draw_buffer = ctx.borrow_draw_buffer().unwrap();
+                    (draw_buffer.size(), draw_buffer.get_bound_texture_id().unwrap())
+                };
+
+                let limits = ctx.borrow_limits().clone();
+
+                (DeviceIntSize::from_untyped(&real_size), texture_id, limits)
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                let (real_size, texture_id) = {
+                    let draw_buffer = ctx.borrow_draw_buffer().unwrap();
+                    (draw_buffer.size(), draw_buffer.get_bound_texture_id().unwrap())
+                };
+
+                let limits = ctx.borrow_limits().clone();
+
+                (DeviceIntSize::from_untyped(&real_size), texture_id, limits)
+            }
+        }
+    }
+
+    pub fn resize(&mut self, size: &DeviceIntSize) -> Result<(), &'static str> {
+        match *self {
+            GLContextWrapper::Native(ref mut ctx) => {
+                ctx.resize(size.to_untyped())
+            }
+            GLContextWrapper::OSMesa(ref mut ctx) => {
+                ctx.resize(size.to_untyped())
+            }
+        }
+    }
+}
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,27 +1,28 @@
 [package]
 name = "webrender_traits"
-version = "0.26.0"
+version = "0.27.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
+webgl = ["offscreen_gl_context"]
 
 [dependencies]
 app_units = "0.4"
 byteorder = "1.0"
 euclid = "0.11"
 gleam = "0.4"
 heapsize = "0.3.6"
 ipc-channel = {version = "0.7", optional = true}
-offscreen_gl_context = {version = "0.8", features = ["serde"]}
+offscreen_gl_context = {version = "0.8", features = ["serde"], optional = true}
 serde = "0.9"
 serde_derive = "0.9"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.7"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.3"
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -1,36 +1,39 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use byteorder::{LittleEndian, WriteBytesExt};
 use channel::{self, MsgSender, PayloadHelperMethods, PayloadSender};
+#[cfg(feature = "webgl")]
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use {AuxiliaryLists, AuxiliaryListsDescriptor, BuiltDisplayList, BuiltDisplayListDescriptor};
 use {ColorF, DeviceIntPoint, DeviceIntSize, DeviceUintRect, DeviceUintSize, FontKey};
 use {GlyphDimensions, GlyphKey, ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize};
-use {LayoutTransform, NativeFontHandle, ScrollLayerId, WebGLCommand, WebGLContextId, WorldPoint};
+use {LayoutTransform, NativeFontHandle, ScrollLayerId, WorldPoint};
+#[cfg(feature = "webgl")]
+use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>),
     AddNativeFont(FontKey, NativeFontHandle),
     DeleteFont(FontKey),
     /// Gets the glyph dimensions
     GetGlyphDimensions(Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
     /// Adds an image from the resource cache.
     AddImage(ImageKey, ImageDescriptor, ImageData, Option<TileSize>),
     /// Updates the the resource cache with the new image data.
-    UpdateImage(ImageKey, ImageDescriptor, Vec<u8>),
+    UpdateImage(ImageKey, ImageDescriptor, Vec<u8>, Option<DeviceUintRect>),
     /// Drops an image from the resource cache.
     DeleteImage(ImageKey),
     CloneApi(MsgSender<IdNamespace>),
     /// Supplies a new frame to WebRender.
     ///
     /// After receiving this message, WebRender will read the display list, followed by the
     /// auxiliary lists, from the payload channel.
     SetRootDisplayList(Option<ColorF>,
@@ -95,16 +98,34 @@ impl fmt::Debug for ApiMsg {
         }
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
+#[cfg(not(feature = "webgl"))]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
+pub struct WebGLContextId(pub usize);
+
+#[cfg(not(feature = "webgl"))]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct GLContextAttributes([u8; 0]);
+
+#[cfg(not(feature = "webgl"))]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct GLLimits([u8; 0]);
+
+#[cfg(not(feature = "webgl"))]
+#[derive(Clone, Deserialize, Serialize)]
+pub enum WebGLCommand {
+    Flush,
+}
+
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct PipelineId(pub u32, pub u32);
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct IdNamespace(pub u32);
 
@@ -224,18 +245,19 @@ impl RenderApi {
 
     /// Updates a specific image.
     ///
     /// Currently doesn't support changing dimensions or format by updating.
     // TODO: Support changing dimensions (and format) during image update?
     pub fn update_image(&self,
                         key: ImageKey,
                         descriptor: ImageDescriptor,
-                        bytes: Vec<u8>) {
-        let msg = ApiMsg::UpdateImage(key, descriptor, bytes);
+                        bytes: Vec<u8>,
+                        dirty_rect: Option<DeviceUintRect>) {
+        let msg = ApiMsg::UpdateImage(key, descriptor, bytes, dirty_rect);
         self.api_sender.send(msg).unwrap();
     }
 
     /// Deletes the specific image.
     pub fn delete_image(&self, key: ImageKey) {
         let msg = ApiMsg::DeleteImage(key);
         self.api_sender.send(msg).unwrap();
     }
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -239,17 +239,16 @@ pub struct RadialGradientDisplayItem {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushStackingContextDisplayItem {
     pub stacking_context: StackingContext,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub scroll_policy: ScrollPolicy,
-    pub bounds: LayoutRect,
     pub z_index: i32,
     pub transform: Option<PropertyBinding<LayoutTransform>>,
     pub perspective: Option<LayoutTransform>,
     pub mix_blend_mode: MixBlendMode,
     pub filters: ItemRange,
 }
 
 #[repr(u32)]
@@ -351,27 +350,25 @@ pub struct ComplexClipRegion {
     /// The boundaries of the rectangle.
     pub rect: LayoutRect,
     /// Border radii of this rectangle.
     pub radii: BorderRadius,
 }
 
 impl StackingContext {
     pub fn new(scroll_policy: ScrollPolicy,
-               bounds: LayoutRect,
                z_index: i32,
                transform: Option<PropertyBinding<LayoutTransform>>,
                perspective: Option<LayoutTransform>,
                mix_blend_mode: MixBlendMode,
                filters: Vec<FilterOp>,
                auxiliary_lists_builder: &mut AuxiliaryListsBuilder)
                -> StackingContext {
         StackingContext {
             scroll_policy: scroll_policy,
-            bounds: bounds,
             z_index: z_index,
             transform: transform,
             perspective: perspective,
             mix_blend_mode: mix_blend_mode,
             filters: auxiliary_lists_builder.add_filters(&filters),
         }
     }
 }
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -308,35 +308,33 @@ impl DisplayListBuilder {
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_stacking_context(&mut self,
                                  scroll_policy: ScrollPolicy,
                                  bounds: LayoutRect,
-                                 clip: ClipRegion,
                                  z_index: i32,
                                  transform: Option<PropertyBinding<LayoutTransform>>,
                                  perspective: Option<LayoutTransform>,
                                  mix_blend_mode: MixBlendMode,
                                  filters: Vec<FilterOp>) {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
                 scroll_policy: scroll_policy,
-                bounds: bounds,
                 z_index: z_index,
                 transform: transform,
                 perspective: perspective,
                 mix_blend_mode: mix_blend_mode,
                 filters: self.auxiliary_lists_builder.add_filters(&filters),
             }
         });
 
-        self.push_item(item, LayoutRect::zero(), clip);
+        self.push_item(item, bounds, ClipRegion::simple(&LayoutRect::zero()));
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopStackingContext);
     }
 
     pub fn define_clip(&mut self,
                        clip: ClipRegion,
@@ -427,17 +425,17 @@ impl DisplayListBuilder {
                            complex: Vec<ComplexClipRegion>,
                            image_mask: Option<ImageMask>)
                            -> ClipRegion {
         ClipRegion::new(rect, complex, image_mask, &mut self.auxiliary_lists_builder)
     }
 
     pub fn finalize(self) -> (PipelineId, BuiltDisplayList, AuxiliaryLists) {
         unsafe {
-            let blob = convert_pod_to_blob(&self.list).to_vec();
+            let blob = convert_vec_pod_to_blob(self.list);
             let display_list_items_size = blob.len();
 
             (self.pipeline_id,
              BuiltDisplayList {
                  descriptor: BuiltDisplayListDescriptor {
                      display_list_items_size: display_list_items_size,
                  },
                  data: blob,
@@ -522,17 +520,17 @@ impl AuxiliaryListsBuilder {
     }
 
     pub fn glyph_instances(&self, glyph_instances_range: &ItemRange) -> &[GlyphInstance] {
         glyph_instances_range.get(&self.glyph_instances[..])
     }
 
     pub fn finalize(self) -> AuxiliaryLists {
         unsafe {
-            let mut blob = convert_pod_to_blob(&self.gradient_stops).to_vec();
+            let mut blob = convert_vec_pod_to_blob(self.gradient_stops);
             let gradient_stops_size = blob.len();
             blob.extend_from_slice(convert_pod_to_blob(&self.complex_clip_regions));
             let complex_clip_regions_size = blob.len() - gradient_stops_size;
             blob.extend_from_slice(convert_pod_to_blob(&self.filters));
             let filters_size = blob.len() - (complex_clip_regions_size + gradient_stops_size);
             blob.extend_from_slice(convert_pod_to_blob(&self.glyph_instances));
             let glyph_instances_size = blob.len() -
                 (complex_clip_regions_size + gradient_stops_size + filters_size);
@@ -611,12 +609,19 @@ impl AuxiliaryLists {
         }
     }
 }
 
 unsafe fn convert_pod_to_blob<T>(data: &[T]) -> &[u8] where T: Copy + 'static {
     slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::<T>())
 }
 
+// this variant of the above lets us convert without needing to make a copy
+unsafe fn convert_vec_pod_to_blob<T>(mut data: Vec<T>) -> Vec<u8> where T: Copy + 'static {
+    let v = Vec::from_raw_parts(data.as_mut_ptr() as *mut u8, data.capacity() * mem::size_of::<T>(), data.len() * mem::size_of::<T>());
+    mem::forget(data);
+    v
+}
+
 unsafe fn convert_blob_to_pod<T>(blob: &[u8]) -> &[T] where T: Copy + 'static {
     slice::from_raw_parts(blob.as_ptr() as *const T, blob.len() / mem::size_of::<T>())
 }
 
--- a/gfx/webrender_traits/src/lib.rs
+++ b/gfx/webrender_traits/src/lib.rs
@@ -9,16 +9,17 @@ extern crate byteorder;
 #[cfg(feature = "nightly")]
 extern crate core;
 extern crate euclid;
 extern crate gleam;
 #[macro_use]
 extern crate heapsize;
 #[cfg(feature = "ipc")]
 extern crate ipc_channel;
+#[cfg(feature = "webgl")]
 extern crate offscreen_gl_context;
 extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
 #[cfg(target_os = "macos")]
 extern crate core_graphics;
 
@@ -28,18 +29,20 @@ extern crate dwrote;
 mod units;
 mod api;
 mod color;
 pub mod channel;
 mod display_item;
 mod display_list;
 mod font;
 mod image;
+#[cfg(feature = "webgl")]
 mod webgl;
 
 pub use api::*;
 pub use color::*;
 pub use display_item::*;
 pub use display_list::*;
 pub use font::*;
 pub use image::*;
 pub use units::*;
+#[cfg(feature = "webgl")]
 pub use webgl::*;