Bug 1496171. Update webrender to commit 3c3f9a4e919b81639f078d7bd101012de61b9396
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Thu, 04 Oct 2018 23:16:32 -0400
changeset 495504 4b23febdfd1742da5774f548e322e298a8425f6b
parent 495503 e8aec0dccbb8213dd292d7331f01307969cdff9a
child 495505 b58765f52e8a93acabb6d48ed1ffc16365832759
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1496171
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1496171. Update webrender to commit 3c3f9a4e919b81639f078d7bd101012de61b9396
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_mix_blend.glsl
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_blur.glsl
gfx/webrender/res/debug_color.glsl
gfx/webrender/res/debug_font.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_split_composite.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/render_task.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/res/snap.glsl
gfx/webrender/src/clip.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/device/gl.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_glyph_renderer.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/util.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/src/image.rs
gfx/webrender_bindings/revision.txt
gfx/webrender_bindings/src/bindings.rs.rej
gfx/wrench/src/blob.rs
gfx/wrench/src/wrench.rs
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -37,30 +37,16 @@ ImageBrushData fetch_image_data(int addr
     ImageBrushData data = ImageBrushData(
         raw_data[0],
         raw_data[1],
         raw_data[2].xy
     );
     return data;
 }
 
-#ifdef WR_FEATURE_ALPHA_PASS
-vec2 transform_point_snapped(
-    vec2 local_pos,
-    RectWithSize local_rect,
-    mat4 transform
-) {
-    vec2 snap_offset = compute_snap_offset(local_pos, transform, local_rect);
-    vec4 world_pos = transform * vec4(local_pos, 0.0, 1.0);
-    vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
-
-    return device_pos + snap_offset;
-}
-#endif
-
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize prim_rect,
     RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
@@ -90,20 +76,20 @@ void brush_vs(
         local_rect = segment_rect;
         stretch_size = local_rect.size;
 
         // Note: Here we can assume that texels in device
         //       space map to local space, due to how border-image
         //       works. That assumption may not hold if this
         //       is used for other purposes in the future.
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
-            stretch_size.x = (texel_rect.z - texel_rect.x) / uDevicePixelRatio;
+            stretch_size.x = (texel_rect.z - texel_rect.x) / pic_task.common_data.device_pixel_scale;
         }
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
-            stretch_size.y = (texel_rect.w - texel_rect.y) / uDevicePixelRatio;
+            stretch_size.y = (texel_rect.w - texel_rect.y) / pic_task.common_data.device_pixel_scale;
         }
 
         uv0 = res.uv_rect.p0 + texel_rect.xy;
         uv1 = res.uv_rect.p0 + texel_rect.zw;
     }
 
     vUv.z = res.layer;
 
--- a/gfx/webrender/res/brush_mix_blend.glsl
+++ b/gfx/webrender/res/brush_mix_blend.glsl
@@ -7,28 +7,33 @@
 #include shared,prim_shared,brush
 
 varying vec3 vSrcUv;
 varying vec3 vBackdropUv;
 flat varying int vOp;
 
 #ifdef WR_VERTEX_SHADER
 
+//Note: this function is unsafe for `vi.world_pos.w <= 0.0`
+vec2 snap_device_pos(VertexInfo vi, float device_pixel_scale) {
+    return vi.world_pos.xy * device_pixel_scale / max(0.0, vi.world_pos.w) + vi.snap_offset;
+}
+
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
-    vec2 snapped_device_pos = snap_device_pos(vi);
+    vec2 snapped_device_pos = snap_device_pos(vi, pic_task.common_data.device_pixel_scale);
     vec2 texture_size = vec2(textureSize(sPrevPassColor, 0));
     vOp = user_data.x;
 
     PictureTask src_task = fetch_picture_task(user_data.z);
     vec2 src_uv = snapped_device_pos +
                   src_task.common_data.task_rect.p0 -
                   src_task.content_origin;
     vSrcUv = vec3(src_uv / texture_size, src_task.common_data.texture_layer_index);
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -58,31 +58,32 @@ ClipVertexInfo write_clip_tile_vertex(Re
                                       ClipArea area) {
     vec2 device_pos = area.screen_origin +
                       aPosition.xy * area.common_data.task_rect.size;
 
     if (clip_transform.is_axis_aligned && prim_transform.is_axis_aligned) {
         mat4 snap_mat = clip_transform.m * prim_transform.inv_m;
         vec4 snap_positions = compute_snap_positions(
             snap_mat,
-            local_clip_rect
+            local_clip_rect,
+            area.common_data.device_pixel_scale
         );
 
         vec2 snap_offsets = compute_snap_offset_impl(
             device_pos,
             snap_mat,
             local_clip_rect,
             RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
             snap_positions
         );
 
         device_pos -= snap_offsets;
     }
 
-    vec2 world_pos = device_pos / uDevicePixelRatio;
+    vec2 world_pos = device_pos / area.common_data.device_pixel_scale;
 
     vec4 pos = prim_transform.m * vec4(world_pos, 0.0, 1.0);
     pos.xyz /= pos.w;
 
     vec4 p = get_node_pos(pos.xy, clip_transform);
     vec3 local_pos = p.xyw * pos.w;
 
     vec4 vertex_pos = vec4(
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -27,17 +27,17 @@ struct BlurTask {
     float blur_radius;
 };
 
 BlurTask fetch_blur_task(int address) {
     RenderTaskData task_data = fetch_render_task_data(address);
 
     BlurTask task = BlurTask(
         task_data.common_data,
-        task_data.data1.x
+        task_data.user_data.x
     );
 
     return task;
 }
 
 void main(void) {
     BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
     RenderTaskCommonData src_task = fetch_render_task_common_data(aBlurSourceTaskAddress);
--- a/gfx/webrender/res/debug_color.glsl
+++ b/gfx/webrender/res/debug_color.glsl
@@ -7,17 +7,17 @@
 varying vec4 vColor;
 
 #ifdef WR_VERTEX_SHADER
 in vec4 aColor;
 
 void main(void) {
     vColor = vec4(aColor.rgb * aColor.a, aColor.a);
     vec4 pos = vec4(aPosition, 1.0);
-    pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
+    pos.xy = floor(pos.xy + 0.5);
     gl_Position = uTransform * pos;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
     oFragColor = vColor;
 }
--- a/gfx/webrender/res/debug_font.glsl
+++ b/gfx/webrender/res/debug_font.glsl
@@ -10,17 +10,17 @@ varying vec4 vColor;
 #ifdef WR_VERTEX_SHADER
 in vec4 aColor;
 in vec2 aColorTexCoord;
 
 void main(void) {
     vColor = aColor;
     vColorTexCoord = aColorTexCoord;
     vec4 pos = vec4(aPosition, 1.0);
-    pos.xy = floor(pos.xy * uDevicePixelRatio + 0.5) / uDevicePixelRatio;
+    pos.xy = floor(pos.xy + 0.5);
     gl_Position = uTransform * pos;
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 void main(void) {
     float alpha = texture(sColor0, vec3(vColorTexCoord.xy, 0.0)).r;
     oFragColor = vColor * alpha;
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -84,21 +84,16 @@ PrimitiveHeader fetch_prim_header(int in
 }
 
 struct VertexInfo {
     vec2 local_pos;
     vec2 snap_offset;
     vec4 world_pos;
 };
 
-//Note: this function is unsafe for `vi.world_pos.w <= 0.0`
-vec2 snap_device_pos(VertexInfo vi) {
-    return vi.world_pos.xy * uDevicePixelRatio / max(0.0, vi.world_pos.w) + vi.snap_offset;
-}
-
 VertexInfo write_vertex(RectWithSize instance_rect,
                         RectWithSize local_clip_rect,
                         float z,
                         Transform transform,
                         PictureTask task,
                         RectWithSize snap_rect) {
 
     // Select the corner of the local rect that we are processing.
@@ -106,24 +101,25 @@ VertexInfo write_vertex(RectWithSize ins
 
     // Clamp to the two local clip rects.
     vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect);
 
     /// Compute the snapping offset.
     vec2 snap_offset = compute_snap_offset(
         clamped_local_pos,
         transform.m,
-        snap_rect
+        snap_rect,
+        task.common_data.device_pixel_scale
     );
 
     // Transform the current vertex to world space.
     vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
-    vec2 device_pos = world_pos.xy * uDevicePixelRatio;
+    vec2 device_pos = world_pos.xy * task.common_data.device_pixel_scale;
 
     // Apply offsets for the render task to get correct screen location.
     vec2 final_offset = snap_offset - task.content_origin + task.common_data.task_rect.p0;
 
     gl_Position = uTransform * vec4(device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w);
 
     VertexInfo vi = VertexInfo(
         clamped_local_pos,
@@ -190,17 +186,17 @@ VertexInfo write_transform_vertex(RectWi
     vec2 local_pos = local_segment_rect.p0 + local_segment_rect.size * aPosition.xy;
 
     // Convert the world positions to device pixel space.
     vec2 task_offset = task.common_data.task_rect.p0 - task.content_origin;
 
     // Transform the current vertex to the world cpace.
     vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
     vec4 final_pos = vec4(
-        world_pos.xy * uDevicePixelRatio + task_offset * world_pos.w,
+        world_pos.xy * task.common_data.device_pixel_scale + task_offset * world_pos.w,
         z * world_pos.w,
         world_pos.w
     );
 
     gl_Position = uTransform * final_pos;
 
     init_transform_vs(mix(
         vec4(prim_rect.p0, prim_rect.p1),
@@ -213,17 +209,17 @@ VertexInfo write_transform_vertex(RectWi
         vec2(0.0),
         world_pos
     );
 
     return vi;
 }
 
 void write_clip(vec4 world_pos, vec2 snap_offset, ClipArea area) {
-    vec2 uv = world_pos.xy * uDevicePixelRatio +
+    vec2 uv = world_pos.xy * area.common_data.device_pixel_scale +
         world_pos.w * (snap_offset + area.common_data.task_rect.p0 - area.screen_origin);
     vClipMaskUvBounds = vec4(
         area.common_data.task_rect.p0,
         area.common_data.task_rect.p0 + area.common_data.task_rect.size
     );
     vClipMaskUv = vec4(uv, area.common_data.texture_layer_index, world_pos.w);
 }
 #endif //WR_VERTEX_SHADER
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -68,17 +68,17 @@ void main(void) {
                        dest_task.content_origin;
 
     vec2 local_pos = bilerp(geometry.local[0], geometry.local[1],
                             geometry.local[3], geometry.local[2],
                             aPosition.y, aPosition.x);
     vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
 
     vec4 final_pos = vec4(
-        dest_origin * world_pos.w + world_pos.xy * uDevicePixelRatio,
+        dest_origin * world_pos.w + world_pos.xy * dest_task.common_data.device_pixel_scale,
         world_pos.w * ci.z,
         world_pos.w
     );
 
     write_clip(
         world_pos,
         vec2(0.0),
         clip_area
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -76,17 +76,17 @@ VertexInfo write_text_vertex(RectWithSiz
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     bool remove_subpx_offset = true;
 #else
     bool remove_subpx_offset = transform.is_axis_aligned;
 #endif
     // Compute the snapping offset only if the scroll node transform is axis-aligned.
     if (remove_subpx_offset) {
         // Transform from local space to device space.
-        float device_scale = uDevicePixelRatio / transform.m[3].w;
+        float device_scale = task.common_data.device_pixel_scale / transform.m[3].w;
         mat2 device_transform = mat2(transform.m) * device_scale;
 
         // Ensure the transformed text offset does not contain a subpixel translation
         // such that glyph snapping is stable for equivalent glyph subpixel positions.
         vec2 device_text_pos = device_transform * text_offset + transform.m[3].xy * device_scale;
         snap_offset = floor(device_text_pos + 0.5) - device_text_pos;
 
         // Snap the glyph offset to a device pixel, using an appropriate bias depending
@@ -125,17 +125,17 @@ VertexInfo write_text_vertex(RectWithSiz
     vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
 #endif
 
     // Clamp to the local clip rect.
     local_pos = clamp_rect(local_pos, local_clip_rect);
 
     // Map the clamped local space corner into device space.
     vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
-    vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
+    vec2 device_pos = world_pos.xy / world_pos.w * task.common_data.device_pixel_scale;
 
     // Apply offsets for the render task to get correct screen location.
     vec2 final_pos = device_pos -
                      task.content_origin +
                      task.common_data.task_rect.p0;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
@@ -167,25 +167,25 @@ void main(void) {
         color_mode = uMode;
     }
 
     Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
     GlyphResource res = fetch_glyph_resource(resource_address);
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     // Transform from local space to glyph space.
-    mat2 glyph_transform = mat2(transform.m) * uDevicePixelRatio;
+    mat2 glyph_transform = mat2(transform.m) * task.common_data.device_pixel_scale;
 
     // Compute the glyph rect in glyph space.
     RectWithSize glyph_rect = RectWithSize(res.offset + glyph_transform * (text.offset + glyph.offset),
                                            res.uv_rect.zw - res.uv_rect.xy);
 
 #else
     // Scale from glyph space to local space.
-    float scale = res.scale / uDevicePixelRatio;
+    float scale = res.scale / task.common_data.device_pixel_scale;
 
     // Compute the glyph rect in local space.
     RectWithSize glyph_rect = RectWithSize(scale * res.offset + text.offset + glyph.offset,
                                            scale * (res.uv_rect.zw - res.uv_rect.xy));
 #endif
 
     vec2 snap_bias;
     // In subpixel mode, the subpixel offset has already been
--- a/gfx/webrender/res/render_task.glsl
+++ b/gfx/webrender/res/render_task.glsl
@@ -6,42 +6,44 @@
 #ifdef WR_VERTEX_SHADER
 #define VECS_PER_RENDER_TASK        2U
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
 
 struct RenderTaskCommonData {
     RectWithSize task_rect;
     float texture_layer_index;
+    float device_pixel_scale;
 };
 
 struct RenderTaskData {
     RenderTaskCommonData common_data;
-    vec3 data1;
+    vec2 user_data;
 };
 
 RenderTaskData fetch_render_task_data(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
 
     vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
     vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
 
     RectWithSize task_rect = RectWithSize(
         texel0.xy,
         texel0.zw
     );
 
     RenderTaskCommonData common_data = RenderTaskCommonData(
         task_rect,
-        texel1.x
+        texel1.x,
+        texel1.y
     );
 
     RenderTaskData data = RenderTaskData(
         common_data,
-        texel1.yzw
+        texel1.zw
     );
 
     return data;
 }
 
 RenderTaskCommonData fetch_render_task_common_data(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
 
@@ -50,17 +52,18 @@ RenderTaskCommonData fetch_render_task_c
 
     RectWithSize task_rect = RectWithSize(
         texel0.xy,
         texel0.zw
     );
 
     RenderTaskCommonData data = RenderTaskCommonData(
         task_rect,
-        texel1.x
+        texel1.x,
+        texel1.y
     );
 
     return data;
 }
 
 #define PIC_TYPE_IMAGE          1
 #define PIC_TYPE_TEXT_SHADOW    2
 
@@ -74,43 +77,40 @@ struct PictureTask {
     vec2 content_origin;
 };
 
 PictureTask fetch_picture_task(int address) {
     RenderTaskData task_data = fetch_render_task_data(address);
 
     PictureTask task = PictureTask(
         task_data.common_data,
-        task_data.data1.xy
+        task_data.user_data
     );
 
     return task;
 }
 
 #define CLIP_TASK_EMPTY 0x7FFF
 
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
-    bool local_space;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
 
     if (index >= CLIP_TASK_EMPTY) {
         RectWithSize rect = RectWithSize(vec2(0.0), vec2(0.0));
 
-        area.common_data = RenderTaskCommonData(rect, 0.0);
+        area.common_data = RenderTaskCommonData(rect, 0.0, 1.0);
         area.screen_origin = vec2(0.0);
-        area.local_space = false;
     } else {
         RenderTaskData task_data = fetch_render_task_data(index);
 
         area.common_data = task_data.common_data;
-        area.screen_origin = task_data.data1.xy;
-        area.local_space = task_data.data1.z == 0.0;
+        area.screen_origin = task_data.user_data;
     }
 
     return area;
 }
 
 #endif //WR_VERTEX_SHADER
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -25,17 +25,16 @@
 //======================================================================================
 #ifdef WR_VERTEX_SHADER
     // A generic uniform that shaders can optionally use to configure
     // an operation mode for this batch.
     uniform int uMode;
 
     // Uniform inputs
     uniform mat4 uTransform;       // Orthographic projection
-    uniform float uDevicePixelRatio;
 
     // Attribute inputs
     in vec3 aPosition;
 
     // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
     // TODO: convert back to a function once the driver issues are resolved, if ever.
     // https://github.com/servo/webrender/pull/623
     // https://github.com/servo/servo/issues/13953
--- a/gfx/webrender/res/snap.glsl
+++ b/gfx/webrender/res/snap.glsl
@@ -1,29 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifdef WR_VERTEX_SHADER
 
-vec4 compute_snap_positions(mat4 transform, RectWithSize snap_rect) {
+vec4 compute_snap_positions(
+    mat4 transform,
+    RectWithSize snap_rect,
+    float device_pixel_scale
+) {
     // Ensure that the snap rect is at *least* one device pixel in size.
     // TODO(gw): It's not clear to me that this is "correct". Specifically,
     //           how should it interact with sub-pixel snap rects when there
     //           is a transform with scale present? But it does fix
     //           the test cases we have in Servo that are failing without it
     //           and seem better than not having this at all.
-    snap_rect.size = max(snap_rect.size, vec2(1.0 / uDevicePixelRatio));
+    snap_rect.size = max(snap_rect.size, vec2(1.0 / device_pixel_scale));
 
     // Transform the snap corners to the world space.
     vec4 world_snap_p0 = transform * vec4(snap_rect.p0, 0.0, 1.0);
     vec4 world_snap_p1 = transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
     // Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right
-    vec4 world_snap = uDevicePixelRatio * vec4(world_snap_p0.xy, world_snap_p1.xy) /
-                                          vec4(world_snap_p0.ww, world_snap_p1.ww);
+    vec4 world_snap = device_pixel_scale * vec4(world_snap_p0.xy, world_snap_p1.xy) /
+                                           vec4(world_snap_p0.ww, world_snap_p1.ww);
     return world_snap;
 }
 
 vec2 compute_snap_offset_impl(
     vec2 reference_pos,
     mat4 transform,
     RectWithSize snap_rect,
     RectWithSize reference_rect,
@@ -38,20 +42,22 @@ vec2 compute_snap_offset_impl(
     /// Compute the actual world offset for this vertex needed to make it snap.
     return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
 }
 
 // Compute a snapping offset in world space (adjusted to pixel ratio),
 // given local position on the transform and a snap rectangle.
 vec2 compute_snap_offset(vec2 local_pos,
                          mat4 transform,
-                         RectWithSize snap_rect) {
+                         RectWithSize snap_rect,
+                         float device_pixel_scale) {
     vec4 snap_positions = compute_snap_positions(
         transform,
-        snap_rect
+        snap_rect,
+        device_pixel_scale
     );
 
     vec2 snap_offsets = compute_snap_offset_impl(
         local_pos,
         transform,
         snap_rect,
         snap_rect,
         snap_positions
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1224,21 +1224,19 @@ fn add_clip_node_to_current_chain(
     let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_spatial_node_index.0];
     let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0];
 
     // Determine the most efficient way to convert between coordinate
     // systems of the primitive and clip node.
     let conversion = if spatial_node_index == clip_spatial_node_index {
         Some(ClipSpaceConversion::Local)
     } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
-        let scale_offset = ref_spatial_node
-            .coordinate_system_relative_scale_offset
-            .difference(
-                &clip_spatial_node.coordinate_system_relative_scale_offset
-            );
+        let scale_offset = ref_spatial_node.coordinate_system_relative_scale_offset
+            .inverse()
+            .accumulate(&clip_spatial_node.coordinate_system_relative_scale_offset);
         Some(ClipSpaceConversion::ScaleOffset(scale_offset))
     } else {
         let xf = clip_scroll_tree.get_relative_transform(
             clip_spatial_node_index,
             ROOT_SPATIAL_NODE_INDEX,
         );
 
         xf.map(|xf| {
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -100,20 +100,29 @@ pub struct DebugRenderer {
     tri_vao: VAO,
     line_vertices: Vec<DebugColorVertex>,
     line_vao: VAO,
     color_program: Program,
 }
 
 impl DebugRenderer {
     pub fn new(device: &mut Device) -> Result<Self, ShaderError> {
-        let font_program = device.create_program("debug_font", "", &DESC_FONT)?;
+        let font_program = device.create_program_linked(
+            "debug_font",
+            "",
+            &DESC_FONT,
+        )?;
+        device.bind_program(&font_program);
         device.bind_shader_samplers(&font_program, &[("sColor0", DebugSampler::Font)]);
 
-        let color_program = device.create_program("debug_color", "", &DESC_COLOR)?;
+        let color_program = device.create_program_linked(
+            "debug_color",
+            "",
+            &DESC_COLOR,
+        )?;
 
         let font_vao = device.create_vao(&DESC_FONT);
         let line_vao = device.create_vao(&DESC_COLOR);
         let tri_vao = device.create_vao(&DESC_COLOR);
 
         let mut font_texture = device.create_texture(TextureTarget::Array, ImageFormat::R8);
         device.init_texture(
             &mut font_texture,
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -521,21 +521,34 @@ impl Texture {
 }
 
 impl Drop for Texture {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
+/// Temporary state retained by a program when it
+/// is created, discarded when it is linked.
+struct ProgramInitState {
+    base_filename: String,
+    sources: ProgramSources,
+}
+
 pub struct Program {
     id: gl::GLuint,
     u_transform: gl::GLint,
-    u_device_pixel_ratio: gl::GLint,
     u_mode: gl::GLint,
+    init_state: Option<ProgramInitState>,
+}
+
+impl Program {
+    pub fn is_initialized(&self) -> bool {
+        self.init_state.is_none()
+    }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
         debug_assert!(
             thread::panicking() || self.id == 0,
             "renderer::deinit not called"
         );
@@ -1031,18 +1044,142 @@ impl Device {
         debug_assert!(self.inside_frame);
 
         if self.bound_draw_fbo != fbo_id {
             self.bound_draw_fbo = fbo_id;
             fbo_id.bind(self.gl(), FBOTarget::Draw);
         }
     }
 
+    /// Link a program, attaching the supplied vertex format.
+    /// Ideally, this should be run some time after the program
+    /// is created. This gives some drivers time to compile the
+    /// shader on a background thread, before blocking due to
+    /// an API call accessing the shader.
+    pub fn link_program(
+        &mut self,
+        program: &mut Program,
+        descriptor: &VertexDescriptor,
+    ) -> Result<(), ShaderError> {
+        if let Some(init_state) = program.init_state.take() {
+            let mut build_program = true;
+
+            // See if we hit the binary shader cache
+            if let Some(ref cached_programs) = self.cached_programs {
+                if let Some(binary) = cached_programs.binaries.borrow().get(&init_state.sources) {
+                    let mut link_status = [0];
+                    unsafe {
+                        self.gl.get_program_iv(program.id, gl::LINK_STATUS, &mut link_status);
+                    }
+                    if link_status[0] == 0 {
+                        let error_log = self.gl.get_program_info_log(program.id);
+                        error!(
+                          "Failed to load a program object with a program binary: {} renderer {}\n{}",
+                          &init_state.base_filename,
+                          self.renderer_name,
+                          error_log
+                        );
+                        if let Some(ref program_cache_handler) = cached_programs.program_cache_handler {
+                            program_cache_handler.notify_program_binary_failed(&binary);
+                        }
+                    } else {
+                        build_program = false;
+                    }
+                }
+            }
+
+            // If not, we need to do a normal compile + link pass.
+            if build_program {
+                // Compile the vertex shader
+                let vs_id =
+                    match Device::compile_shader(&*self.gl, &init_state.base_filename, gl::VERTEX_SHADER, &init_state.sources.vs_source) {
+                        Ok(vs_id) => vs_id,
+                        Err(err) => return Err(err),
+                    };
+
+                // Compile the fragment shader
+                let fs_id =
+                    match Device::compile_shader(&*self.gl, &init_state.base_filename, gl::FRAGMENT_SHADER, &init_state.sources.fs_source) {
+                        Ok(fs_id) => fs_id,
+                        Err(err) => {
+                            self.gl.delete_shader(vs_id);
+                            return Err(err);
+                        }
+                    };
+
+                // Attach shaders
+                self.gl.attach_shader(program.id, vs_id);
+                self.gl.attach_shader(program.id, fs_id);
+
+                // Bind vertex attributes
+                for (i, attr) in descriptor
+                    .vertex_attributes
+                    .iter()
+                    .chain(descriptor.instance_attributes.iter())
+                    .enumerate()
+                {
+                    self.gl
+                        .bind_attrib_location(program.id, i as gl::GLuint, attr.name);
+                }
+
+                if self.cached_programs.is_some() {
+                    self.gl.program_parameter_i(program.id, gl::PROGRAM_BINARY_RETRIEVABLE_HINT, gl::TRUE as gl::GLint);
+                }
+
+                // Link!
+                self.gl.link_program(program.id);
+
+                // GL recommends detaching and deleting shaders once the link
+                // is complete (whether successful or not). This allows the driver
+                // to free any memory associated with the parsing and compilation.
+                self.gl.detach_shader(program.id, vs_id);
+                self.gl.detach_shader(program.id, fs_id);
+                self.gl.delete_shader(vs_id);
+                self.gl.delete_shader(fs_id);
+
+                let mut link_status = [0];
+                unsafe {
+                    self.gl.get_program_iv(program.id, gl::LINK_STATUS, &mut link_status);
+                }
+                if link_status[0] == 0 {
+                    let error_log = self.gl.get_program_info_log(program.id);
+                    error!(
+                        "Failed to link shader program: {}\n{}",
+                        &init_state.base_filename,
+                        error_log
+                    );
+                    self.gl.delete_program(program.id);
+                    return Err(ShaderError::Link(init_state.base_filename.clone(), error_log));
+                }
+
+                if let Some(ref cached_programs) = self.cached_programs {
+                    if !cached_programs.binaries.borrow().contains_key(&init_state.sources) {
+                        let (buffer, format) = self.gl.get_program_binary(program.id);
+                        if buffer.len() > 0 {
+                            let program_binary = Arc::new(ProgramBinary::new(buffer, format, &init_state.sources));
+                            if let Some(ref program_cache_handler) = cached_programs.program_cache_handler {
+                                program_cache_handler.notify_binary_added(&program_binary);
+                            }
+                            cached_programs.binaries.borrow_mut().insert(init_state.sources, program_binary);
+                        }
+                    }
+                }
+            }
+
+            // If we get here, the link succeeded, so get the uniforms.
+            program.u_transform = self.gl.get_uniform_location(program.id, "uTransform");
+            program.u_mode = self.gl.get_uniform_location(program.id, "uMode");
+        }
+
+        Ok(())
+    }
+
     pub fn bind_program(&mut self, program: &Program) {
         debug_assert!(self.inside_frame);
+        debug_assert!(program.init_state.is_none());
 
         if self.bound_program != program.id {
             self.gl.use_program(program.id);
             self.bound_program = program.id;
             self.program_mode_id = UniformLocation(program.u_mode);
         }
     }
 
@@ -1426,21 +1563,35 @@ impl Device {
         external.id = 0;
     }
 
     pub fn delete_program(&mut self, mut program: Program) {
         self.gl.delete_program(program.id);
         program.id = 0;
     }
 
+    /// Create a shader program and link it immediately.
+    pub fn create_program_linked(
+        &mut self,
+        base_filename: &str,
+        features: &str,
+        descriptor: &VertexDescriptor,
+    ) -> Result<Program, ShaderError> {
+        let mut program = self.create_program(base_filename, features)?;
+        self.link_program(&mut program, descriptor)?;
+        Ok(program)
+    }
+
+    /// Create a shader program. This does minimal amount of work
+    /// to start loading a binary shader. The main part of the
+    /// work is done in link_program.
     pub fn create_program(
         &mut self,
         base_filename: &str,
         features: &str,
-        descriptor: &VertexDescriptor,
     ) -> Result<Program, ShaderError> {
         debug_assert!(self.inside_frame);
 
         let gl_version_string = get_shader_version(&*self.gl);
 
         let (vs_source, fs_source) = build_shader_strings(
             gl_version_string,
             features,
@@ -1448,141 +1599,49 @@ impl Device {
             &self.resource_override_path,
         );
 
         let sources = ProgramSources::new(self.renderer_name.clone(), vs_source, fs_source);
 
         // Create program
         let pid = self.gl.create_program();
 
-        let mut loaded = false;
-
+        // Attempt to load a cached binary if possible.
         if let Some(ref cached_programs) = self.cached_programs {
-            if let Some(binary) = cached_programs.binaries.borrow().get(&sources)
-            {
+            if let Some(binary) = cached_programs.binaries.borrow().get(&sources) {
                 self.gl.program_binary(pid, binary.format, &binary.binary);
-
-                let mut link_status = [0];
-                unsafe {
-                    self.gl.get_program_iv(pid, gl::LINK_STATUS, &mut link_status);
-                }
-                if link_status[0] == 0 {
-                    let error_log = self.gl.get_program_info_log(pid);
-                    error!(
-                      "Failed to load a program object with a program binary: {} renderer {}\n{}",
-                      base_filename,
-                      self.renderer_name,
-                      error_log
-                    );
-                    if let Some(ref program_cache_handler) = cached_programs.program_cache_handler {
-                        program_cache_handler.notify_program_binary_failed(&binary);
-                    }
-                } else {
-                    loaded = true;
-                }
             }
         }
 
-        if loaded == false {
-            // Compile the vertex shader
-            let vs_id =
-                match Device::compile_shader(&*self.gl, base_filename, gl::VERTEX_SHADER, &sources.vs_source) {
-                    Ok(vs_id) => vs_id,
-                    Err(err) => return Err(err),
-                };
-
-            // Compiler the fragment shader
-            let fs_id =
-                match Device::compile_shader(&*self.gl, base_filename, gl::FRAGMENT_SHADER, &sources.fs_source) {
-                    Ok(fs_id) => fs_id,
-                    Err(err) => {
-                        self.gl.delete_shader(vs_id);
-                        return Err(err);
-                    }
-                };
-
-            // Attach shaders
-            self.gl.attach_shader(pid, vs_id);
-            self.gl.attach_shader(pid, fs_id);
-
-            // Bind vertex attributes
-            for (i, attr) in descriptor
-                .vertex_attributes
-                .iter()
-                .chain(descriptor.instance_attributes.iter())
-                .enumerate()
-            {
-                self.gl
-                    .bind_attrib_location(pid, i as gl::GLuint, attr.name);
-            }
-
-            if self.cached_programs.is_some() {
-                self.gl.program_parameter_i(pid, gl::PROGRAM_BINARY_RETRIEVABLE_HINT, gl::TRUE as gl::GLint);
-            }
-
-            // Link!
-            self.gl.link_program(pid);
-
-            // GL recommends detaching and deleting shaders once the link
-            // is complete (whether successful or not). This allows the driver
-            // to free any memory associated with the parsing and compilation.
-            self.gl.detach_shader(pid, vs_id);
-            self.gl.detach_shader(pid, fs_id);
-            self.gl.delete_shader(vs_id);
-            self.gl.delete_shader(fs_id);
-
-            let mut link_status = [0];
-            unsafe {
-                self.gl.get_program_iv(pid, gl::LINK_STATUS, &mut link_status);
-            }
-            if link_status[0] == 0 {
-                let error_log = self.gl.get_program_info_log(pid);
-                error!(
-                    "Failed to link shader program: {}\n{}",
-                    base_filename,
-                    error_log
-                );
-                self.gl.delete_program(pid);
-                return Err(ShaderError::Link(base_filename.to_string(), error_log));
-            }
-        }
-
-        if let Some(ref cached_programs) = self.cached_programs {
-            if !cached_programs.binaries.borrow().contains_key(&sources) {
-                let (buffer, format) = self.gl.get_program_binary(pid);
-                if buffer.len() > 0 {
-                    let program_binary = Arc::new(ProgramBinary::new(buffer, format, &sources));
-                    if let Some(ref program_cache_handler) = cached_programs.program_cache_handler {
-                        program_cache_handler.notify_binary_added(&program_binary);
-                    }
-                    cached_programs.binaries.borrow_mut().insert(sources, program_binary);
-                }
-            }
-        }
+        // Set up the init state that will be used in link_program.
+        let init_state = Some(ProgramInitState {
+            base_filename: base_filename.to_owned(),
+            sources,
+        });
 
         let u_transform = self.gl.get_uniform_location(pid, "uTransform");
-        let u_device_pixel_ratio = self.gl.get_uniform_location(pid, "uDevicePixelRatio");
         let u_mode = self.gl.get_uniform_location(pid, "uMode");
 
         let program = Program {
             id: pid,
             u_transform,
-            u_device_pixel_ratio,
             u_mode,
+            init_state,
         };
 
-        self.bind_program(&program);
-
         Ok(program)
     }
 
     pub fn bind_shader_samplers<S>(&mut self, program: &Program, bindings: &[(&'static str, S)])
     where
         S: Into<TextureSlot> + Copy,
     {
+        // bind_program() must be called before calling bind_shader_samplers
+        assert_eq!(self.bound_program, program.id);
+
         for binding in bindings {
             let u_location = self.gl.get_uniform_location(program.id, binding.0);
             if u_location != -1 {
                 self.bind_program(program);
                 self.gl
                     .uniform_1i(u_location, binding.1.into().0 as gl::GLint);
             }
         }
@@ -1596,18 +1655,16 @@ impl Device {
     pub fn set_uniforms(
         &self,
         program: &Program,
         transform: &Transform3D<f32>,
     ) {
         debug_assert!(self.inside_frame);
         self.gl
             .uniform_matrix_4fv(program.u_transform, false, &transform.to_row_major_array());
-        self.gl
-            .uniform_1f(program.u_device_pixel_ratio, self.device_pixel_ratio);
     }
 
     pub fn switch_mode(&self, mode: i32) {
         debug_assert!(self.inside_frame);
         self.gl.uniform_1i(self.program_mode_id.0, mode);
     }
 
     pub fn create_pbo(&mut self) -> PBO {
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -394,17 +394,17 @@ impl FrameBuilder {
 
             if let RenderPassKind::OffScreen { ref texture_cache, .. } = pass.kind {
                 has_texture_cache_tasks |= !texture_cache.is_empty();
             }
         }
 
         let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
 
-        render_tasks.write_task_data();
+        render_tasks.write_task_data(device_pixel_scale);
 
         resource_cache.end_frame();
 
         Frame {
             window_size: self.window_size,
             inner_rect: self.screen_rect,
             device_pixel_ratio: device_pixel_scale.0,
             background_color: self.background_color,
--- a/gfx/webrender/src/gpu_glyph_renderer.rs
+++ b/gfx/webrender/src/gpu_glyph_renderer.rs
@@ -8,17 +8,17 @@ use api::{DeviceIntPoint, DeviceIntRect,
 use api::{ImageFormat, TextureTarget};
 use debug_colors;
 use device::{Device, Texture, TextureFilter, VAO};
 use euclid::{Point2D, Size2D, Transform3D, TypedVector2D, Vector2D};
 use internal_types::RenderTargetInfo;
 use pathfinder_gfx_utils::ShelfBinPacker;
 use profiler::GpuProfileTag;
 use renderer::{self, ImageBufferKind, Renderer, RendererError, RendererStats};
-use renderer::{TextureSampler, VertexArrayKind};
+use renderer::{TextureSampler, VertexArrayKind, ShaderPrecacheFlags};
 use shade::{LazilyCompiledShader, ShaderKind};
 use tiling::GlyphJob;
 
 // The area lookup table in uncompressed grayscale TGA format (TGA image format 3).
 static AREA_LUT_TGA_BYTES: &'static [u8] = include_bytes!("../res/area-lut.tga");
 
 const HORIZONTAL_BIN_PADDING: i32 = 3;
 
@@ -37,17 +37,17 @@ pub struct GpuGlyphRenderer {
     pub vector_cover_vao: VAO,
 
     // These are Pathfinder shaders, used for rendering vector graphics.
     vector_stencil: LazilyCompiledShader,
     vector_cover: LazilyCompiledShader,
 }
 
 impl GpuGlyphRenderer {
-    pub fn new(device: &mut Device, prim_vao: &VAO, precache_shaders: bool)
+    pub fn new(device: &mut Device, prim_vao: &VAO, precache_flags: ShaderPrecacheFlags)
                -> Result<GpuGlyphRenderer, RendererError> {
         // Make sure the area LUT is uncompressed grayscale TGA, 8bpp.
         debug_assert!(AREA_LUT_TGA_BYTES[2] == 3);
         debug_assert!(AREA_LUT_TGA_BYTES[16] == 8);
         let area_lut_width = (AREA_LUT_TGA_BYTES[12] as u32) |
             ((AREA_LUT_TGA_BYTES[13] as u32) << 8);
         let area_lut_height = (AREA_LUT_TGA_BYTES[14] as u32) |
             ((AREA_LUT_TGA_BYTES[15] as u32) << 8);
@@ -69,24 +69,24 @@ impl GpuGlyphRenderer {
                                                                     prim_vao);
 
         // Load Pathfinder vector graphics shaders.
         let vector_stencil = try!{
             LazilyCompiledShader::new(ShaderKind::VectorStencil,
                                       "pf_vector_stencil",
                                       &[ImageBufferKind::Texture2D.get_feature_string()],
                                       device,
-                                      precache_shaders)
+                                      precache_flags)
         };
         let vector_cover = try!{
             LazilyCompiledShader::new(ShaderKind::VectorCover,
                                       "pf_vector_cover",
                                       &[ImageBufferKind::Texture2D.get_feature_string()],
                                       device,
-                                      precache_shaders)
+                                      precache_flags)
         };
 
         Ok(GpuGlyphRenderer {
             area_lut_texture,
             vector_stencil_vao,
             vector_cover_vao,
             vector_stencil,
             vector_cover,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -181,12 +181,12 @@ pub extern crate webrender_api;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use device::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSources};
 pub use frame_builder::ChasePrimitive;
 pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
-pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener};
+pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
 pub use resource_cache::intersect_for_tile;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -126,17 +126,18 @@ impl<F, T> SpaceMapper<F, T> where F: fm
             let target_spatial_node = &spatial_nodes[target_node_index.0];
             self.current_target_spatial_node_index = target_node_index;
 
             self.kind = if self.ref_spatial_node_index == target_node_index {
                 CoordinateSpaceMapping::Local
             } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id {
                 CoordinateSpaceMapping::ScaleOffset(
                     ref_spatial_node.coordinate_system_relative_scale_offset
-                        .difference(
+                        .inverse()
+                        .accumulate(
                             &target_spatial_node.coordinate_system_relative_scale_offset
                         )
                 )
             } else {
                 let transform = clip_scroll_tree.get_relative_transform(
                     target_node_index,
                     self.ref_spatial_node_index,
                 ).expect("bug: should have already been culled");
@@ -514,16 +515,68 @@ impl BrushSegment {
             local_rect,
             clip_task_id: BrushSegmentTaskId::Opaque,
             may_need_clip_mask,
             edge_flags,
             extra_data,
             brush_flags,
         }
     }
+
+    pub fn update_clip_task(
+        &mut self,
+        clip_chain: Option<&ClipChainInstance>,
+        prim_bounding_rect: WorldRect,
+        root_spatial_node_index: SpatialNodeIndex,
+        pic_state: &mut PictureState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        match clip_chain {
+            Some(clip_chain) => {
+                if !clip_chain.needs_mask ||
+                   (!self.may_need_clip_mask && !clip_chain.has_non_local_clips) {
+                    self.clip_task_id = BrushSegmentTaskId::Opaque;
+                    return;
+                }
+
+                let (device_rect, _, _) = match get_raster_rects(
+                    clip_chain.pic_clip_rect,
+                    &pic_state.map_pic_to_raster,
+                    &pic_state.map_raster_to_world,
+                    prim_bounding_rect,
+                    frame_context.device_pixel_scale,
+                ) {
+                    Some(info) => info,
+                    None => {
+                        self.clip_task_id = BrushSegmentTaskId::Empty;
+                        return;
+                    }
+                };
+
+                let clip_task = RenderTask::new_mask(
+                    device_rect.to_i32(),
+                    clip_chain.clips_range,
+                    root_spatial_node_index,
+                    frame_state.clip_store,
+                    frame_state.gpu_cache,
+                    frame_state.resource_cache,
+                    frame_state.render_tasks,
+                    &mut frame_state.resources.clip_data_store,
+                );
+
+                let clip_task_id = frame_state.render_tasks.add(clip_task);
+                pic_state.tasks.push(clip_task_id);
+                self.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
+            }
+            None => {
+                self.clip_task_id = BrushSegmentTaskId::Empty;
+            }
+        }
+    }
 }
 
 pub type BrushSegmentVec = SmallVec<[BrushSegment; 8]>;
 
 #[derive(Debug)]
 pub struct BrushSegmentDescriptor {
     pub segments: BrushSegmentVec,
 }
@@ -2230,78 +2283,60 @@ impl Primitive {
             frame_state,
         );
 
         let segment_desc = match brush.segment_desc {
             Some(ref mut description) => description,
             None => return false,
         };
 
-        for segment in &mut segment_desc.segments {
-            // Build a clip chain for the smaller segment rect. This will
-            // often manage to eliminate most/all clips, and sometimes
-            // clip the segment completely.
-            let segment_clip_chain = frame_state
-                .clip_store
-                .build_clip_chain_instance(
-                    self.metadata.clip_chain_id,
-                    segment.local_rect,
-                    self.metadata.local_clip_rect,
-                    prim_context.spatial_node_index,
-                    &pic_state.map_local_to_pic,
-                    &pic_state.map_pic_to_world,
-                    &frame_context.clip_scroll_tree,
-                    frame_state.gpu_cache,
-                    frame_state.resource_cache,
-                    frame_context.device_pixel_scale,
-                    &frame_context.world_rect,
-                    clip_node_collector,
-                    &mut frame_state.resources.clip_data_store,
-                );
-
-            match segment_clip_chain {
-                Some(segment_clip_chain) => {
-                    if !segment_clip_chain.needs_mask ||
-                       (!segment.may_need_clip_mask && !segment_clip_chain.has_non_local_clips) {
-                        segment.clip_task_id = BrushSegmentTaskId::Opaque;
-                        continue;
-                    }
-
-                    let (device_rect, _, _) = match get_raster_rects(
-                        segment_clip_chain.pic_clip_rect,
-                        &pic_state.map_pic_to_raster,
-                        &pic_state.map_raster_to_world,
-                        prim_bounding_rect,
-                        frame_context.device_pixel_scale,
-                    ) {
-                        Some(info) => info,
-                        None => {
-                            segment.clip_task_id = BrushSegmentTaskId::Empty;
-                            continue;
-                        }
-                    };
-
-                    let clip_task = RenderTask::new_mask(
-                        device_rect.to_i32(),
-                        segment_clip_chain.clips_range,
-                        root_spatial_node_index,
-                        frame_state.clip_store,
+        // If we only built 1 segment, there is no point in re-running
+        // the clip chain builder. Instead, just use the clip chain
+        // instance that was built for the main primitive. This is a
+        // significant optimization for the common case.
+        if segment_desc.segments.len() == 1 {
+            segment_desc.segments[0].update_clip_task(
+                Some(prim_clip_chain),
+                prim_bounding_rect,
+                root_spatial_node_index,
+                pic_state,
+                frame_context,
+                frame_state,
+            );
+        } else {
+            for segment in &mut segment_desc.segments {
+                // Build a clip chain for the smaller segment rect. This will
+                // often manage to eliminate most/all clips, and sometimes
+                // clip the segment completely.
+                let segment_clip_chain = frame_state
+                    .clip_store
+                    .build_clip_chain_instance(
+                        self.metadata.clip_chain_id,
+                        segment.local_rect,
+                        self.metadata.local_clip_rect,
+                        prim_context.spatial_node_index,
+                        &pic_state.map_local_to_pic,
+                        &pic_state.map_pic_to_world,
+                        &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
-                        frame_state.render_tasks,
+                        frame_context.device_pixel_scale,
+                        &frame_context.world_rect,
+                        clip_node_collector,
                         &mut frame_state.resources.clip_data_store,
                     );
 
-                    let clip_task_id = frame_state.render_tasks.add(clip_task);
-                    pic_state.tasks.push(clip_task_id);
-                    segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
-                }
-                None => {
-                    segment.clip_task_id = BrushSegmentTaskId::Empty;
-                }
+                segment.update_clip_task(
+                    segment_clip_chain.as_ref(),
+                    prim_bounding_rect,
+                    root_spatial_node_index,
+                    pic_state,
+                    frame_context,
+                    frame_state,
+                );
             }
         }
 
         true
     }
 
     // Returns true if the primitive *might* need a clip mask. If
     // false, there is no need to even check for clip masks for
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,26 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets, ImageDescriptor, ImageFormat};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets};
+use api::{DevicePixelScale, ImageDescriptor, ImageFormat};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use border::BorderCacheKey;
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange};
 use clip_scroll_tree::SpatialNodeIndex;
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use gpu_types::{BorderInstance, ImageSource, RasterizationSpace, UvRectKind};
+use gpu_types::{BorderInstance, ImageSource, UvRectKind};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::PictureCacheKey;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
@@ -126,19 +127,19 @@ impl RenderTaskTree {
         }
     }
 
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
         debug_assert_eq!(self.frame_id, id.1);
         RenderTaskAddress(id.0)
     }
 
-    pub fn write_task_data(&mut self) {
+    pub fn write_task_data(&mut self, device_pixel_scale: DevicePixelScale) {
         for task in &self.tasks {
-            self.task_data.push(task.write_task_data());
+            self.task_data.push(task.write_task_data(device_pixel_scale));
         }
     }
 
     pub fn save_target(&mut self) -> SavedTargetIndex {
         let id = self.next_saved;
         self.next_saved.0 += 1;
         id
     }
@@ -704,64 +705,55 @@ impl RenderTask {
                 UvRectKind::Rect
             }
         }
     }
 
     // Write (up to) 8 floats of data specific to the type
     // of render task that is provided to the GPU shaders
     // via a vertex texture.
-    pub fn write_task_data(&self) -> RenderTaskData {
+    pub fn write_task_data(&self, device_pixel_scale: DevicePixelScale) -> RenderTaskData {
         // NOTE: The ordering and layout of these structures are
         //       required to match both the GPU structures declared
         //       in prim_shared.glsl, and also the uses in submit_batch()
         //       in renderer.rs.
         // TODO(gw): Maybe there's a way to make this stuff a bit
         //           more type-safe. Although, it will always need
         //           to be kept in sync with the GLSL code anyway.
 
         let data = match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 // Note: has to match `PICTURE_TYPE_*` in shaders
                 [
                     task.content_origin.x as f32,
                     task.content_origin.y as f32,
-                    0.0,
                 ]
             }
             RenderTaskKind::CacheMask(ref task) => {
                 [
                     task.actual_rect.origin.x as f32,
                     task.actual_rect.origin.y as f32,
-                    RasterizationSpace::Screen as i32 as f32,
-                ]
-            }
-            RenderTaskKind::ClipRegion(..) => {
-                [
-                    0.0,
-                    0.0,
-                    RasterizationSpace::Local as i32 as f32,
                 ]
             }
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 [
                     task.blur_std_deviation,
                     0.0,
-                    0.0,
                 ]
             }
             RenderTaskKind::Glyph(_) => {
-                [1.0, 0.0, 0.0]
+                [1.0, 0.0]
             }
+            RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::Blit(..) => {
-                [0.0; 3]
+                [0.0; 2]
             }
         };
 
         let (mut target_rect, target_index) = self.get_target_rect();
         // The primitives inside a fixed-location render task
         // are already placed to their corresponding positions,
         // so the shader doesn't need to shift by the origin.
         if let RenderTaskLocation::Fixed(_) = self.location {
@@ -770,19 +762,19 @@ impl RenderTask {
 
         RenderTaskData {
             data: [
                 target_rect.origin.x as f32,
                 target_rect.origin.y as f32,
                 target_rect.size.width as f32,
                 target_rect.size.height as f32,
                 target_index.0 as f32,
+                device_pixel_scale.0,
                 data[0],
                 data[1],
-                data[2],
             ]
         }
     }
 
     pub fn get_texture_address(&self, gpu_cache: &GpuCache) -> GpuCacheAddress {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -703,17 +703,17 @@ impl CpuProfile {
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 pub struct GpuGlyphRenderer;
 
 #[cfg(not(feature = "pathfinder"))]
 impl GpuGlyphRenderer {
-    fn new(_: &mut Device, _: &VAO, _: bool) -> Result<GpuGlyphRenderer, RendererError> {
+    fn new(_: &mut Device, _: &VAO, _: ShaderPrecacheFlags) -> Result<GpuGlyphRenderer, RendererError> {
         Ok(GpuGlyphRenderer)
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 struct StenciledGlyphPage;
 
 /// A Texture that has been initialized by the `device` module and is ready to
@@ -1019,18 +1019,21 @@ struct GpuCacheTexture {
     bus: GpuCacheBus,
 }
 
 impl GpuCacheTexture {
     fn new(device: &mut Device, use_scatter: bool) -> Result<Self, RendererError> {
         let texture = device.create_texture(TextureTarget::Default, ImageFormat::RGBAF32);
 
         let bus = if use_scatter {
-            let program = device
-                .create_program("gpu_cache_update", "", &desc::GPU_CACHE_UPDATE)?;
+            let program = device.create_program_linked(
+                "gpu_cache_update",
+                "",
+                &desc::GPU_CACHE_UPDATE,
+            )?;
             let buf_position = device.create_vbo();
             let buf_value = device.create_vbo();
             //Note: the vertex attributes have to be supplied in the same order
             // as for program creation, but each assigned to a different stream.
             let vao = device.create_custom_vao(&[
                 buf_position.stream_with(&desc::GPU_CACHE_UPDATE.vertex_attributes[0..1]),
                 buf_value   .stream_with(&desc::GPU_CACHE_UPDATE.vertex_attributes[1..2]),
             ]);
@@ -1709,17 +1712,17 @@ impl Renderer {
 
         let prim_vao = device.create_vao(&desc::PRIM_INSTANCES);
         device.bind_vao(&prim_vao);
         device.update_vao_indices(&prim_vao, &quad_indices, VertexUsageHint::Static);
         device.update_vao_main_vertices(&prim_vao, &quad_vertices, VertexUsageHint::Static);
 
         let gpu_glyph_renderer = try!(GpuGlyphRenderer::new(&mut device,
                                                             &prim_vao,
-                                                            options.precache_shaders));
+                                                            options.precache_flags));
 
         let blur_vao = device.create_vao_with_new_instances(&desc::BLUR, &prim_vao);
         let clip_vao = device.create_vao_with_new_instances(&desc::CLIP, &prim_vao);
         let border_vao = device.create_vao_with_new_instances(&desc::BORDER, &prim_vao);
         let scale_vao = device.create_vao_with_new_instances(&desc::SCALE, &prim_vao);
         let texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = TextureResolver::new(&mut device);
@@ -4420,23 +4423,38 @@ pub trait AsyncPropertySampler {
     /// (i.e. that will trigger a render). The list of frame messages returned
     /// are processed as though they were part of the original transaction.
     fn sample(&self) -> Vec<FrameMsg>;
     /// This is called exactly once, when the render backend thread is about to
     /// terminate.
     fn deregister(&self);
 }
 
+/// Flags that control how shaders are pre-cached, if at all.
+bitflags! {
+    #[derive(Default)]
+    pub struct ShaderPrecacheFlags: u32 {
+        /// Needed for const initialization
+        const EMPTY                 = 0;
+
+        /// Only start async compile
+        const ASYNC_COMPILE         = 1 << 2;
+
+        /// Do a full compile/link during startup
+        const FULL_COMPILE          = 1 << 3;
+    }
+}
+
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
     pub max_recorded_profiles: usize,
-    pub precache_shaders: bool,
+    pub precache_flags: ShaderPrecacheFlags,
     pub renderer_kind: RendererKind,
     pub enable_subpixel_aa: bool,
     pub clear_color: Option<ColorF>,
     pub enable_clear_scissor: bool,
     pub max_texture_size: Option<u32>,
     pub scatter_gpu_cache_updates: bool,
     pub upload_method: UploadMethod,
     pub workers: Option<Arc<ThreadPool>>,
@@ -4459,17 +4477,17 @@ impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
             enable_dithering: true,
             debug_flags: DebugFlags::empty(),
             max_recorded_profiles: 0,
-            precache_shaders: false,
+            precache_flags: ShaderPrecacheFlags::empty(),
             renderer_kind: RendererKind::Native,
             enable_subpixel_aa: false,
             clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
             enable_clear_scissor: true,
             max_texture_size: None,
             // Scattered GPU cache updates haven't met a test that would show their superiority yet.
             scatter_gpu_cache_updates: false,
             // This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1438,20 +1438,21 @@ impl ResourceCache {
             return;
         }
 
         self.blob_image_handler
             .as_mut()
             .unwrap()
             .prepare_resources(&self.resources, &self.missing_blob_images);
 
+        let is_low_priority = false;
         let rasterized_blobs = self.blob_image_rasterizer
             .as_mut()
             .unwrap()
-            .rasterize(&self.missing_blob_images);
+            .rasterize(&self.missing_blob_images, is_low_priority);
 
         self.add_rasterized_blob_images(rasterized_blobs);
 
         self.missing_blob_images.clear();
     }
 
     fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
         for request in self.pending_image_requests.drain() {
@@ -1879,17 +1880,17 @@ impl ResourceCache {
                             },
                             dirty_rect: None,
                         }
                     ];
 
                     let blob_handler = self.blob_image_handler.as_mut().unwrap();
                     blob_handler.prepare_resources(&self.resources, blob_request_params);
                     let mut rasterizer = blob_handler.create_blob_rasterizer();
-                    let (_, result) = rasterizer.rasterize(blob_request_params).pop().unwrap();
+                    let (_, result) = rasterizer.rasterize(blob_request_params, false).pop().unwrap();
                     let result = result.expect("Blob rasterization failed");
 
                     assert_eq!(result.rasterized_rect.size, desc.size);
                     assert_eq!(result.data.len(), desc.compute_total_size() as usize);
 
                     num_blobs += 1;
                     #[cfg(feature = "png")]
                     CaptureConfig::save_png(
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -442,20 +442,21 @@ impl SceneBuilder {
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
         }
 
+        let is_low_priority = false;
         let blob_requests = replace(&mut txn.blob_requests, Vec::new());
         let mut rasterized_blobs = txn.blob_rasterizer.as_mut().map_or(
             Vec::new(),
-            |rasterizer| rasterizer.rasterize(&blob_requests),
+            |rasterizer| rasterizer.rasterize(&blob_requests, is_low_priority),
         );
         rasterized_blobs.append(&mut txn.rasterized_blobs);
 
         drain_filter(
             &mut txn.notifications,
             |n| { n.when() == Checkpoint::SceneBuilt },
             |n| { n.notify(); },
         );
@@ -565,19 +566,20 @@ impl LowPrioritySceneBuilder {
                     break;
                 }
             }
         }
     }
 
     fn process_transaction(&mut self, mut txn: Box<Transaction>) -> Box<Transaction> {
         let blob_requests = replace(&mut txn.blob_requests, Vec::new());
+        let is_low_priority = true;
         let mut more_rasterized_blobs = txn.blob_rasterizer.as_mut().map_or(
             Vec::new(),
-            |rasterizer| rasterizer.rasterize(&blob_requests),
+            |rasterizer| rasterizer.rasterize(&blob_requests, is_low_priority),
         );
         txn.rasterized_blobs.append(&mut more_rasterized_blobs);
 
         if self.simulate_slow_ms > 0 {
             thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
         }
 
         txn
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -9,17 +9,17 @@ use api::{
 use batch::{BatchKey, BatchKind, BrushBatchKind};
 use device::{Device, Program, ShaderError};
 use euclid::{Transform3D};
 use glyph_rasterizer::GlyphFormat;
 use renderer::{
     desc,
     MAX_VERTEX_TEXTURE_WIDTH,
     BlendMode, DebugFlags, ImageBufferKind, RendererError, RendererOptions,
-    TextureSampler, VertexArrayKind,
+    TextureSampler, VertexArrayKind, ShaderPrecacheFlags,
 };
 
 use gleam::gl::GlType;
 use time::precise_time_ns;
 
 
 impl ImageBufferKind {
     pub(crate) fn get_feature_string(&self) -> &'static str {
@@ -74,35 +74,31 @@ pub struct LazilyCompiledShader {
 }
 
 impl LazilyCompiledShader {
     pub(crate) fn new(
         kind: ShaderKind,
         name: &'static str,
         features: &[&'static str],
         device: &mut Device,
-        precache: bool,
+        precache_flags: ShaderPrecacheFlags,
     ) -> Result<Self, ShaderError> {
         let mut shader = LazilyCompiledShader {
             program: None,
             name,
             kind,
             features: features.to_vec(),
         };
 
-        if precache {
+        if precache_flags.intersects(ShaderPrecacheFlags::ASYNC_COMPILE | ShaderPrecacheFlags::FULL_COMPILE) {
             let t0 = precise_time_ns();
-            let program = shader.get(device)?;
+            shader.get_internal(device, precache_flags)?;
             let t1 = precise_time_ns();
-            device.bind_program(program);
-            device.draw_triangles_u16(0, 3);
-            let t2 = precise_time_ns();
-            debug!("[C: {:.1} ms D: {:.1} ms] Precache {} {:?}",
+            debug!("[C: {:.1} ms ] Precache {} {:?}",
                 (t1 - t0) as f64 / 1000000.0,
-                (t2 - t1) as f64 / 1000000.0,
                 name,
                 features
             );
         }
 
         Ok(shader)
     }
 
@@ -118,51 +114,115 @@ impl LazilyCompiledShader {
                 renderer_errors.push(RendererError::from(e));
                 return;
             }
         };
         device.bind_program(program);
         device.set_uniforms(program, projection);
     }
 
-    fn get(&mut self, device: &mut Device) -> Result<&Program, ShaderError> {
+    fn get_internal(
+        &mut self,
+        device: &mut Device,
+        precache_flags: ShaderPrecacheFlags,
+    ) -> Result<&mut Program, ShaderError> {
         if self.program.is_none() {
             let program = match self.kind {
                 ShaderKind::Primitive | ShaderKind::Brush | ShaderKind::Text => {
                     create_prim_shader(self.name,
                                        device,
-                                       &self.features,
-                                       VertexArrayKind::Primitive)
+                                       &self.features)
                 }
-                ShaderKind::Cache(format) => {
+                ShaderKind::Cache(..) => {
                     create_prim_shader(self.name,
                                        device,
-                                       &self.features,
-                                       format)
+                                       &self.features)
                 }
                 ShaderKind::VectorStencil => {
                     create_prim_shader(self.name,
                                        device,
-                                       &self.features,
-                                       VertexArrayKind::VectorStencil)
+                                       &self.features)
                 }
                 ShaderKind::VectorCover => {
                     create_prim_shader(self.name,
                                        device,
-                                       &self.features,
-                                       VertexArrayKind::VectorCover)
+                                       &self.features)
                 }
                 ShaderKind::ClipCache => {
                     create_clip_shader(self.name, device)
                 }
             };
             self.program = Some(program?);
         }
 
-        Ok(self.program.as_ref().unwrap())
+        let program = self.program.as_mut().unwrap();
+
+        if precache_flags.contains(ShaderPrecacheFlags::FULL_COMPILE) && !program.is_initialized() {
+            let vertex_format = match self.kind {
+                ShaderKind::Primitive |
+                ShaderKind::Brush |
+                ShaderKind::Text => VertexArrayKind::Primitive,
+                ShaderKind::Cache(format) => format,
+                ShaderKind::VectorStencil => VertexArrayKind::VectorStencil,
+                ShaderKind::VectorCover => VertexArrayKind::VectorCover,
+                ShaderKind::ClipCache => VertexArrayKind::Clip,
+            };
+
+            let vertex_descriptor = match vertex_format {
+                VertexArrayKind::Primitive => &desc::PRIM_INSTANCES,
+                VertexArrayKind::Blur => &desc::BLUR,
+                VertexArrayKind::Clip => &desc::CLIP,
+                VertexArrayKind::VectorStencil => &desc::VECTOR_STENCIL,
+                VertexArrayKind::VectorCover => &desc::VECTOR_COVER,
+                VertexArrayKind::Border => &desc::BORDER,
+                VertexArrayKind::Scale => &desc::SCALE,
+            };
+
+            device.link_program(program, vertex_descriptor)?;
+            device.bind_program(program);
+            match self.kind {
+                ShaderKind::ClipCache => {
+                    device.bind_shader_samplers(
+                        &program,
+                        &[
+                            ("sColor0", TextureSampler::Color0),
+                            ("sTransformPalette", TextureSampler::TransformPalette),
+                            ("sRenderTasks", TextureSampler::RenderTasks),
+                            ("sGpuCache", TextureSampler::GpuCache),
+                            ("sPrimitiveHeadersF", TextureSampler::PrimitiveHeadersF),
+                            ("sPrimitiveHeadersI", TextureSampler::PrimitiveHeadersI),
+                        ],
+                    );
+                }
+                _ => {
+                    device.bind_shader_samplers(
+                        &program,
+                        &[
+                            ("sColor0", TextureSampler::Color0),
+                            ("sColor1", TextureSampler::Color1),
+                            ("sColor2", TextureSampler::Color2),
+                            ("sDither", TextureSampler::Dither),
+                            ("sPrevPassAlpha", TextureSampler::PrevPassAlpha),
+                            ("sPrevPassColor", TextureSampler::PrevPassColor),
+                            ("sTransformPalette", TextureSampler::TransformPalette),
+                            ("sRenderTasks", TextureSampler::RenderTasks),
+                            ("sGpuCache", TextureSampler::GpuCache),
+                            ("sPrimitiveHeadersF", TextureSampler::PrimitiveHeadersF),
+                            ("sPrimitiveHeadersI", TextureSampler::PrimitiveHeadersI),
+                        ],
+                    );
+                }
+            }
+        }
+
+        Ok(program)
+    }
+
+    fn get(&mut self, device: &mut Device) -> Result<&mut Program, ShaderError> {
+        self.get_internal(device, ShaderPrecacheFlags::FULL_COMPILE)
     }
 
     fn deinit(self, device: &mut Device) {
         if let Some(program) = self.program {
             device.delete_program(program);
         }
     }
 }
@@ -185,64 +245,64 @@ struct BrushShader {
     debug_overdraw: LazilyCompiledShader,
 }
 
 impl BrushShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
-        precache: bool,
+        precache_flags: ShaderPrecacheFlags,
         dual_source: bool,
     ) -> Result<Self, ShaderError> {
         let opaque = LazilyCompiledShader::new(
             ShaderKind::Brush,
             name,
             features,
             device,
-            precache,
+            precache_flags,
         )?;
 
         let mut alpha_features = features.to_vec();
         alpha_features.push(ALPHA_FEATURE);
 
         let alpha = LazilyCompiledShader::new(
             ShaderKind::Brush,
             name,
             &alpha_features,
             device,
-            precache,
+            precache_flags,
         )?;
 
         let dual_source = if dual_source {
             let mut dual_source_features = alpha_features.to_vec();
             dual_source_features.push(DUAL_SOURCE_FEATURE);
 
             let shader = LazilyCompiledShader::new(
                 ShaderKind::Brush,
                 name,
                 &dual_source_features,
                 device,
-                precache,
+                precache_flags,
             )?;
 
             Some(shader)
         } else {
             None
         };
 
         let mut debug_overdraw_features = features.to_vec();
         debug_overdraw_features.push(DEBUG_OVERDRAW_FEATURE);
 
         let debug_overdraw = LazilyCompiledShader::new(
             ShaderKind::Brush,
             name,
             &debug_overdraw_features,
             device,
-            precache,
+            precache_flags,
         )?;
 
         Ok(BrushShader {
             opaque,
             alpha,
             dual_source,
             debug_overdraw,
         })
@@ -282,46 +342,46 @@ pub struct TextShader {
     debug_overdraw: LazilyCompiledShader,
 }
 
 impl TextShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
-        precache: bool,
+        precache_flags: ShaderPrecacheFlags,
     ) -> Result<Self, ShaderError> {
         let simple = LazilyCompiledShader::new(
             ShaderKind::Text,
             name,
             features,
             device,
-            precache,
+            precache_flags,
         )?;
 
         let mut glyph_transform_features = features.to_vec();
         glyph_transform_features.push("GLYPH_TRANSFORM");
 
         let glyph_transform = LazilyCompiledShader::new(
             ShaderKind::Text,
             name,
             &glyph_transform_features,
             device,
-            precache,
+            precache_flags,
         )?;
 
         let mut debug_overdraw_features = features.to_vec();
         debug_overdraw_features.push("DEBUG_OVERDRAW");
 
         let debug_overdraw = LazilyCompiledShader::new(
             ShaderKind::Text,
             name,
             &debug_overdraw_features,
             device,
-            precache,
+            precache_flags,
         )?;
 
         Ok(TextShader { simple, glyph_transform, debug_overdraw })
     }
 
     pub fn get(
         &mut self,
         glyph_format: GlyphFormat,
@@ -344,88 +404,40 @@ impl TextShader {
         self.debug_overdraw.deinit(device);
     }
 }
 
 fn create_prim_shader(
     name: &'static str,
     device: &mut Device,
     features: &[&'static str],
-    vertex_format: VertexArrayKind,
 ) -> Result<Program, ShaderError> {
     let mut prefix = format!(
         "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U\n",
         MAX_VERTEX_TEXTURE_WIDTH
     );
 
     for feature in features {
         prefix.push_str(&format!("#define WR_FEATURE_{}\n", feature));
     }
 
     debug!("PrimShader {}", name);
 
-    let vertex_descriptor = match vertex_format {
-        VertexArrayKind::Primitive => desc::PRIM_INSTANCES,
-        VertexArrayKind::Blur => desc::BLUR,
-        VertexArrayKind::Clip => desc::CLIP,
-        VertexArrayKind::VectorStencil => desc::VECTOR_STENCIL,
-        VertexArrayKind::VectorCover => desc::VECTOR_COVER,
-        VertexArrayKind::Border => desc::BORDER,
-        VertexArrayKind::Scale => desc::SCALE,
-    };
-
-    let program = device.create_program(name, &prefix, &vertex_descriptor);
-
-    if let Ok(ref program) = program {
-        device.bind_shader_samplers(
-            program,
-            &[
-                ("sColor0", TextureSampler::Color0),
-                ("sColor1", TextureSampler::Color1),
-                ("sColor2", TextureSampler::Color2),
-                ("sDither", TextureSampler::Dither),
-                ("sPrevPassAlpha", TextureSampler::PrevPassAlpha),
-                ("sPrevPassColor", TextureSampler::PrevPassColor),
-                ("sTransformPalette", TextureSampler::TransformPalette),
-                ("sRenderTasks", TextureSampler::RenderTasks),
-                ("sGpuCache", TextureSampler::GpuCache),
-                ("sPrimitiveHeadersF", TextureSampler::PrimitiveHeadersF),
-                ("sPrimitiveHeadersI", TextureSampler::PrimitiveHeadersI),
-            ],
-        );
-    }
-
-    program
+    device.create_program(name, &prefix)
 }
 
 fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<Program, ShaderError> {
     let prefix = format!(
         "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U\n",
         MAX_VERTEX_TEXTURE_WIDTH
     );
 
     debug!("ClipShader {}", name);
 
-    let program = device.create_program(name, &prefix, &desc::CLIP);
-
-    if let Ok(ref program) = program {
-        device.bind_shader_samplers(
-            program,
-            &[
-                ("sColor0", TextureSampler::Color0),
-                ("sTransformPalette", TextureSampler::TransformPalette),
-                ("sRenderTasks", TextureSampler::RenderTasks),
-                ("sGpuCache", TextureSampler::GpuCache),
-                ("sPrimitiveHeadersF", TextureSampler::PrimitiveHeadersF),
-                ("sPrimitiveHeadersI", TextureSampler::PrimitiveHeadersI),
-            ],
-        );
-    }
-
-    program
+    device.create_program(name, &prefix)
 }
 
 // NB: If you add a new shader here, make sure to deinitialize it
 // in `Shaders::deinit()` below.
 pub struct Shaders {
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
     // of these shaders are then used by the primitive shaders.
@@ -467,147 +479,144 @@ pub struct Shaders {
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
         options: &RendererOptions,
     ) -> Result<Self, ShaderError> {
-        // needed for the precache fake draws
-        let dummy_vao = if options.precache_shaders {
-            let vao = device.create_custom_vao(&[]);
-            device.bind_custom_vao(&vao);
-            Some(vao)
-        } else {
-            None
-        };
-
         let brush_solid = BrushShader::new(
             "brush_solid",
             device,
             &[],
-            options.precache_shaders,
+            options.precache_flags,
             false,
         )?;
 
         let brush_blend = BrushShader::new(
             "brush_blend",
             device,
             &[],
-            options.precache_shaders,
+            options.precache_flags,
             false,
         )?;
 
         let brush_mix_blend = BrushShader::new(
             "brush_mix_blend",
             device,
             &[],
-            options.precache_shaders,
+            options.precache_flags,
             false,
         )?;
 
         let brush_radial_gradient = BrushShader::new(
             "brush_radial_gradient",
             device,
             if options.enable_dithering {
                &[DITHERING_FEATURE]
             } else {
                &[]
             },
-            options.precache_shaders,
+            options.precache_flags,
             false,
         )?;
 
         let brush_linear_gradient = BrushShader::new(
             "brush_linear_gradient",
             device,
             if options.enable_dithering {
                &[DITHERING_FEATURE]
             } else {
                &[]
             },
-            options.precache_shaders,
+            options.precache_flags,
             false,
         )?;
 
         let cs_blur_a8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Blur),
             "cs_blur",
             &["ALPHA_TARGET"],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_blur_rgba8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Blur),
             "cs_blur",
             &["COLOR_TARGET"],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_clip_rectangle = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_rectangle",
             &[],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_clip_box_shadow = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_box_shadow",
             &[],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_clip_line = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_line",
             &[],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_clip_image = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_image",
             &[],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_scale_a8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Scale),
             "cs_scale",
             &["ALPHA_TARGET"],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let cs_scale_rgba8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Scale),
             "cs_scale",
             &["COLOR_TARGET"],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let ps_text_run = TextShader::new("ps_text_run",
             device,
             &[],
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
+        let dual_source_precache_flags = if options.disable_dual_source_blending {
+            ShaderPrecacheFlags::empty()
+        } else {
+            options.precache_flags
+        };
+
         let ps_text_run_dual_source = TextShader::new("ps_text_run",
             device,
             &[DUAL_SOURCE_FEATURE],
-            options.precache_shaders && !options.disable_dual_source_blending,
+            dual_source_precache_flags,
         )?;
 
         // All image configuration.
         let mut image_features = Vec::new();
         let mut brush_image = Vec::new();
         // PrimitiveShader is not clonable. Use push() to initialize the vec.
         for _ in 0 .. IMAGE_BUFFER_KINDS.len() {
             brush_image.push(None);
@@ -617,17 +626,17 @@ impl Shaders {
                 let feature_string = IMAGE_BUFFER_KINDS[buffer_kind].get_feature_string();
                 if feature_string != "" {
                     image_features.push(feature_string);
                 }
                 brush_image[buffer_kind] = Some(BrushShader::new(
                     "brush_image",
                     device,
                     &image_features,
-                    options.precache_shaders,
+                    options.precache_flags,
                     !options.disable_dual_source_blending,
                 )?);
             }
             image_features.clear();
         }
 
         // All yuv_image configuration.
         let mut yuv_features = Vec::new();
@@ -653,17 +662,17 @@ impl Shaders {
                         if feature_string != "" {
                             yuv_features.push(feature_string);
                         }
 
                         let shader = BrushShader::new(
                             "brush_yuv_image",
                             device,
                             &yuv_features,
-                            options.precache_shaders,
+                            options.precache_flags,
                             false,
                         )?;
                         let index = Self::get_yuv_shader_index(
                             *image_buffer_kind,
                             *format_kind,
                             *color_space_kind,
                         );
                         brush_yuv_image[index] = Some(shader);
@@ -673,39 +682,35 @@ impl Shaders {
             }
         }
 
         let cs_border_segment = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_segment",
              &[],
              device,
-             options.precache_shaders,
+             options.precache_flags,
         )?;
 
         let cs_border_solid = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_solid",
             &[],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
         let ps_split_composite = LazilyCompiledShader::new(
             ShaderKind::Primitive,
             "ps_split_composite",
             &[],
             device,
-            options.precache_shaders,
+            options.precache_flags,
         )?;
 
-        if let Some(vao) = dummy_vao {
-            device.delete_custom_vao(vao);
-        }
-
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             cs_border_segment,
             cs_border_solid,
             cs_scale_a8,
             cs_scale_rgba8,
             brush_solid,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -78,20 +78,22 @@ impl ScaleOffset {
             offset: Vector2D::new(
                 -self.offset.x / self.scale.x,
                 -self.offset.y / self.scale.y,
             ),
         }
     }
 
     pub fn offset(&self, offset: Vector2D<f32>) -> Self {
-        ScaleOffset {
-            scale: self.scale,
-            offset: self.offset + offset,
-        }
+        self.accumulate(
+            &ScaleOffset {
+                scale: Vector2D::new(1.0, 1.0),
+                offset,
+            }
+        )
     }
 
     // Produce a ScaleOffset that includes both self
     // and other. The 'self' ScaleOffset is applied
     // after other.
     pub fn accumulate(&self, other: &ScaleOffset) -> Self {
         ScaleOffset {
             scale: Vector2D::new(
@@ -100,30 +102,16 @@ impl ScaleOffset {
             ),
             offset: Vector2D::new(
                 self.offset.x + self.scale.x * other.offset.x,
                 self.offset.y + self.scale.y * other.offset.y,
             ),
         }
     }
 
-    // Find the difference between two ScaleOffset types.
-    pub fn difference(&self, other: &ScaleOffset) -> Self {
-        ScaleOffset {
-            scale: Vector2D::new(
-                other.scale.x / self.scale.x,
-                other.scale.y / self.scale.y,
-            ),
-            offset: Vector2D::new(
-                (other.offset.x - self.offset.x) / self.scale.x,
-                (other.offset.y - self.offset.y) / self.scale.y,
-            ),
-        }
-    }
-
     pub fn map_rect<F, T>(&self, rect: &TypedRect<f32, F>) -> TypedRect<f32, T> {
         TypedRect::new(
             TypedPoint2D::new(
                 rect.origin.x * self.scale.x + self.offset.x,
                 rect.origin.y * self.scale.y + self.offset.y,
             ),
             TypedSize2D::new(
                 rect.size.width * self.scale.x,
@@ -373,30 +361,101 @@ pub fn extract_inner_rect_safe<U>(
 ) -> Option<TypedRect<f32, U>> {
     // value of `k==1.0` is used for extraction of the corner rectangles
     // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
     extract_inner_rect_impl(rect, radii, 1.0)
 }
 
 #[cfg(test)]
 pub mod test {
+    use api::{LayoutTransform, LayoutVector3D};
     use super::*;
     use euclid::{Point2D, Angle, Transform3D};
     use std::f32::consts::PI;
 
     #[test]
     fn inverse_project() {
         let m0 = Transform3D::identity();
         let p0 = Point2D::new(1.0, 2.0);
         // an identical transform doesn't need any inverse projection
         assert_eq!(m0.inverse_project(&p0), Some(p0));
         let m1 = Transform3D::create_rotation(0.0, 1.0, 0.0, Angle::radians(PI / 3.0));
         // rotation by 60 degrees would imply scaling of X component by a factor of 2
         assert_eq!(m1.inverse_project(&p0), Some(Point2D::new(2.0, 2.0)));
     }
+
+    fn validate_convert(xref: &LayoutTransform) {
+        let so = ScaleOffset::from_transform(xref).unwrap();
+        let xf = so.to_transform();
+        assert!(xref.approx_eq(&xf));
+    }
+
+    #[test]
+    fn scale_offset_convert() {
+        let xref = LayoutTransform::create_translation(130.0, 200.0, 0.0);
+        validate_convert(&xref);
+
+        let xref = LayoutTransform::create_scale(13.0, 8.0, 1.0);
+        validate_convert(&xref);
+
+        let xref = LayoutTransform::create_scale(0.5, 0.5, 1.0)
+                        .pre_translate(LayoutVector3D::new(124.0, 38.0, 0.0));
+        validate_convert(&xref);
+
+        let xref = LayoutTransform::create_translation(50.0, 240.0, 0.0)
+                        .pre_mul(&LayoutTransform::create_scale(30.0, 11.0, 1.0));
+        validate_convert(&xref);
+    }
+
+    fn validate_inverse(xref: &LayoutTransform) {
+        let s0 = ScaleOffset::from_transform(xref).unwrap();
+        let s1 = s0.inverse().accumulate(&s0);
+        assert!((s1.scale.x - 1.0).abs() < NEARLY_ZERO &&
+                (s1.scale.y - 1.0).abs() < NEARLY_ZERO &&
+                s1.offset.x.abs() < NEARLY_ZERO &&
+                s1.offset.y.abs() < NEARLY_ZERO,
+                "{:?}",
+                s1);
+    }
+
+    #[test]
+    fn scale_offset_inverse() {
+        let xref = LayoutTransform::create_translation(130.0, 200.0, 0.0);
+        validate_inverse(&xref);
+
+        let xref = LayoutTransform::create_scale(13.0, 8.0, 1.0);
+        validate_inverse(&xref);
+
+        let xref = LayoutTransform::create_scale(0.5, 0.5, 1.0)
+                        .pre_translate(LayoutVector3D::new(124.0, 38.0, 0.0));
+        validate_inverse(&xref);
+
+        let xref = LayoutTransform::create_translation(50.0, 240.0, 0.0)
+                        .pre_mul(&LayoutTransform::create_scale(30.0, 11.0, 1.0));
+        validate_inverse(&xref);
+    }
+
+    fn validate_accumulate(x0: &LayoutTransform, x1: &LayoutTransform) {
+        let x = x0.pre_mul(x1);
+
+        let s0 = ScaleOffset::from_transform(x0).unwrap();
+        let s1 = ScaleOffset::from_transform(x1).unwrap();
+
+        let s = s0.accumulate(&s1).to_transform();
+
+        assert!(x.approx_eq(&s), "{:?}\n{:?}", x, s);
+    }
+
+    #[test]
+    fn scale_offset_accumulate() {
+        let x0 = LayoutTransform::create_translation(130.0, 200.0, 0.0);
+        let x1 = LayoutTransform::create_scale(7.0, 3.0, 1.0);
+
+        validate_accumulate(&x0, &x1);
+    }
 }
 
 pub trait MaxRect {
     fn max_rect() -> Self;
 }
 
 impl MaxRect for DeviceIntRect {
     fn max_rect() -> Self {
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -14,17 +14,17 @@ const VERTEX_SHADER: u32 = 0x8B31;
 struct Shader {
     name: &'static str,
     features: &'static [&'static str],
 }
 
 const SHADER_PREFIX: &str = "#define WR_MAX_VERTEX_TEXTURE_WIDTH 1024U\n";
 
 const BRUSH_FEATURES: &[&str] = &["", "ALPHA_PASS"];
-const CLIP_FEATURES: &[&str] = &["TRANSFORM"];
+const CLIP_FEATURES: &[&str] = &[""];
 const CACHE_FEATURES: &[&str] = &[""];
 const GRADIENT_FEATURES: &[&str] = &[ "", "DITHERING", "ALPHA_PASS", "DITHERING,ALPHA_PASS" ];
 const PRIM_FEATURES: &[&str] = &[""];
 
 const SHADERS: &[Shader] = &[
     // Clip mask shaders
     Shader {
         name: "cs_clip_rectangle",
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -338,17 +338,24 @@ pub trait BlobImageHandler: Send {
     /// A hook to let the handler clean up any state related a given namespace before the
     /// resource cache deletes them.
     fn clear_namespace(&mut self, namespace: IdNamespace);
 }
 
 /// A group of rasterization requests to execute synchronously on the scene builder thread.
 pub trait AsyncBlobImageRasterizer : Send {
     /// Rasterize the requests.
-    fn rasterize(&mut self, requests: &[BlobImageParams]) -> Vec<(BlobImageRequest, BlobImageResult)>;
+    ///
+    /// Gecko uses te priority hint to schedule work in a way that minimizes the risk
+    /// of high priority work being blocked by (or enqued behind) low priority work.
+    fn rasterize(
+        &mut self,
+        requests: &[BlobImageParams],
+        low_priority: bool
+    ) -> Vec<(BlobImageRequest, BlobImageResult)>;
 }
 
 
 /// Input parameters for the BlobImageRasterizer.
 #[derive(Copy, Clone, Debug)]
 pub struct BlobImageParams {
     /// A key that identifies the blob image rasterization request.
     pub request: BlobImageRequest,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-9536249e3ed920a920346f6cc0a79473cad16099
+3c3f9a4e919b81639f078d7bd101012de61b9396
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_bindings/src/bindings.rs.rej
@@ -0,0 +1,11 @@
+--- bindings.rs
++++ bindings.rs
+@@ -1017,7 +1023,7 @@
+         sampler: Some(Box::new(SamplerCallback::new(window_id))),
+         max_texture_size: Some(8192), // Moz2D doesn't like textures bigger than this
+         clear_color: Some(ColorF::new(0.0, 0.0, 0.0, 0.0)),
+-        precache_shaders: env_var_to_bool("MOZ_WR_PRECACHE_SHADERS"),
++        precache_flags,
+         ..Default::default()
+     };
+ 
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -178,17 +178,21 @@ struct Command {
     dirty_rect: Option<DeviceUintRect>
 }
 
 struct Rasterizer {
     image_cmds: HashMap<ImageKey, (ColorU, Option<TileSize>)>,
 }
 
 impl AsyncBlobImageRasterizer for Rasterizer {
-    fn rasterize(&mut self, requests: &[BlobImageParams]) -> Vec<(BlobImageRequest, BlobImageResult)> {
+    fn rasterize(
+        &mut self,
+        requests: &[BlobImageParams],
+        _low_priority: bool
+    ) -> Vec<(BlobImageRequest, BlobImageResult)> {
         let requests: Vec<Command> = requests.into_iter().map(
             |item| {
                 let (color, tile_size) = self.image_cmds[&item.request.key];
 
                 let tile = item.request.tile.map(|tile| (tile_size.unwrap(), tile));
 
                 Command {
                     request: item.request,
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -15,17 +15,17 @@ use json_frame_writer::JsonFrameWriter;
 use ron_frame_writer::RonFrameWriter;
 use std::collections::HashMap;
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Receiver;
 use time;
 use webrender;
 use webrender::api::*;
-use webrender::{DebugFlags, RendererStats};
+use webrender::{DebugFlags, RendererStats, ShaderPrecacheFlags};
 use yaml_frame_writer::YamlFrameWriterReceiver;
 use {WindowWrapper, NotifierEvent};
 
 // TODO(gw): This descriptor matches what we currently support for fonts
 //           but is quite a mess. We should at least document and
 //           use better types for things like the style and stretch.
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub enum FontDescriptor {
@@ -200,25 +200,31 @@ impl Wrench {
                 &PathBuf::from("wr-record.bin"),
             )) as Box<webrender::ApiRecordingReceiver>,
         });
 
         let mut debug_flags = DebugFlags::ECHO_DRIVER_MESSAGES;
         debug_flags.set(DebugFlags::DISABLE_BATCHING, no_batch);
         let callbacks = Arc::new(Mutex::new(blob::BlobCallbacks::new()));
 
+        let precache_flags = if precache_shaders {
+            ShaderPrecacheFlags::FULL_COMPILE
+        } else {
+            ShaderPrecacheFlags::empty()
+        };
+
         let opts = webrender::RendererOptions {
             device_pixel_ratio: dp_ratio,
             resource_override_path: shader_override_path,
             recorder,
             enable_subpixel_aa: !no_subpixel_aa,
             debug_flags,
             enable_clear_scissor: !no_scissor,
             max_recorded_profiles: 16,
-            precache_shaders,
+            precache_flags,
             blob_image_handler: Some(Box::new(blob::CheckerboardRenderer::new(callbacks.clone()))),
             disable_dual_source_blending,
             chase_primitive,
             ..Default::default()
         };
 
         // put an Awakened event into the queue to kick off the first frame
         if let Some(ref elp) = proxy {