Backed out 3 changesets (bug 1470125) for force-cargo-library-build bustages on a CLOSED TREE
authorshindli <shindli@mozilla.com>
Thu, 28 Jun 2018 21:25:33 +0300
changeset 424384 27f60c02583dca519ae203bcf34ac8133483f0e7
parent 424383 cc00d5a02ec20413abeb2f177a66b4b96e2da3be
child 424385 afe2313f83edb3bd1664553be57d5af483c4cb61
push id104800
push userebalazs@mozilla.com
push dateFri, 29 Jun 2018 09:53:02 +0000
treeherdermozilla-inbound@28534c22986e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1470125
milestone63.0a1
backs outc6ef35a760aecc90c41398b4cd4af1be1a03753f
cb8bed4a7691d9b12a42a80bbb40fb7ff719f6ff
82527f62f249da6b1ec1ed544fe84011c28a056d
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
Backed out 3 changesets (bug 1470125) for force-cargo-library-build bustages on a CLOSED TREE Backed out changeset c6ef35a760ae (bug 1470125) Backed out changeset cb8bed4a7691 (bug 1470125) Backed out changeset 82527f62f249 (bug 1470125)
gfx/webrender/Cargo.toml
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_linear_gradient.glsl
gfx/webrender/res/brush_radial_gradient.glsl
gfx/webrender/res/clip_scroll.glsl
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_clip_box_shadow.glsl
gfx/webrender/res/cs_clip_image.glsl
gfx/webrender/res/cs_clip_line.glsl
gfx/webrender/res/cs_clip_rectangle.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_split_composite.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/snap.glsl
gfx/webrender/res/transform.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/device/gl.rs
gfx/webrender/src/device/mod.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.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/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/color.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/image.rs
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/revision.txt
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
gfx/wrench/Cargo.toml
gfx/wrench/src/args.yaml
gfx/wrench/src/main.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
layout/painting/nsCSSRenderingBorders.cpp
layout/painting/nsDisplayList.cpp
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -22,22 +22,22 @@ app_units = "0.6"
 base64 = { optional = true, version = "0.6" }
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.0"
 cfg-if = "0.1.2"
 euclid = "0.17.3"
 fxhash = "0.2.1"
 gleam = "0.5"
-image = { optional = true, version = "0.19" }
+image = { optional = true, version = "0.18" }
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.1.43"
 plane-split = "0.9.1"
-png = { optional = true, version = "0.12" }
+png = { optional = true, version = "0.11" }
 rayon = "1"
 ron = { optional = true, version = "0.1.7" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 serde_json = { optional = true, version = "1.0" }
 smallvec = "0.6"
 thread_profiler = "0.1.1"
 time = "0.1"
 webrender_api = {path = "../webrender_api"}
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -11,79 +11,137 @@ void brush_vs(
     RectWithSize segment_rect,
     ivec3 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 segment_data
 );
 
+#define VECS_PER_BRUSH_PRIM                 2
 #define VECS_PER_SEGMENT                    2
 
 #define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
 #define BRUSH_FLAG_SEGMENT_RELATIVE             2
 #define BRUSH_FLAG_SEGMENT_REPEAT_X             4
 #define BRUSH_FLAG_SEGMENT_REPEAT_Y             8
 
+//Note: these have to match `gpu_types` constants
+#define INT_BITS    (31)
+#define CLIP_CHAIN_RECT_BITS    (22)
+#define SEGMENT_BITS (INT_BITS - CLIP_CHAIN_RECT_BITS)
+#define EDGE_FLAG_BITS (4)
+#define BRUSH_FLAG_BITS (4)
+#define CLIP_SCROLL_INDEX_BITS (INT_BITS - EDGE_FLAG_BITS - BRUSH_FLAG_BITS)
+
+struct BrushInstance {
+    int picture_address;
+    int prim_address;
+    int clip_chain_rect_index;
+    int scroll_node_id;
+    int clip_address;
+    int z;
+    int segment_index;
+    int edge_mask;
+    int flags;
+    ivec3 user_data;
+};
+
+BrushInstance load_brush() {
+    BrushInstance bi;
+
+    bi.picture_address = aData0.x & 0xffff;
+    bi.clip_address = aData0.x >> 16;
+    bi.prim_address = aData0.y;
+    bi.clip_chain_rect_index = aData0.z  & ((1 << CLIP_CHAIN_RECT_BITS) - 1);
+    bi.segment_index = aData0.z >> CLIP_CHAIN_RECT_BITS;
+    bi.z = aData0.w;
+    bi.scroll_node_id = aData1.x & ((1 << CLIP_SCROLL_INDEX_BITS) - 1);
+    bi.edge_mask = (aData1.x >> CLIP_SCROLL_INDEX_BITS) & 0xf;
+    bi.flags = (aData1.x >> (CLIP_SCROLL_INDEX_BITS + EDGE_FLAG_BITS)) & 0xf;
+    bi.user_data = aData1.yzw;
+
+    return bi;
+}
+
+struct BrushPrimitive {
+    RectWithSize local_rect;
+    RectWithSize local_clip_rect;
+};
+
+BrushPrimitive fetch_brush_primitive(int address, int clip_chain_rect_index) {
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+
+    RectWithSize clip_chain_rect = fetch_clip_chain_rect(clip_chain_rect_index);
+    RectWithSize brush_clip_rect = RectWithSize(data[1].xy, data[1].zw);
+    RectWithSize clip_rect = intersect_rects(clip_chain_rect, brush_clip_rect);
+
+    BrushPrimitive prim = BrushPrimitive(RectWithSize(data[0].xy, data[0].zw), clip_rect);
+
+    return prim;
+}
+
 void main(void) {
     // Load the brush instance from vertex attributes.
-    int prim_header_address = aData.x;
-    int clip_address = aData.y;
-    int segment_index = aData.z & 0xffff;
-    int edge_flags = (aData.z >> 16) & 0xff;
-    int brush_flags = (aData.z >> 24) & 0xff;
-    PrimitiveHeader ph = fetch_prim_header(prim_header_address);
+    BrushInstance brush = load_brush();
+
+    // Load the geometry for this brush. For now, this is simply the
+    // local rect of the primitive. In the future, this will support
+    // loading segment rects, and other rect formats (glyphs).
+    BrushPrimitive brush_prim =
+        fetch_brush_primitive(brush.prim_address, brush.clip_chain_rect_index);
 
     // Fetch the segment of this brush primitive we are drawing.
-    int segment_address = ph.specific_prim_address +
+    int segment_address = brush.prim_address +
+                          VECS_PER_BRUSH_PRIM +
                           VECS_PER_SPECIFIC_BRUSH +
-                          segment_index * VECS_PER_SEGMENT;
+                          brush.segment_index * VECS_PER_SEGMENT;
 
     vec4[2] segment_data = fetch_from_resource_cache_2(segment_address);
     RectWithSize local_segment_rect = RectWithSize(segment_data[0].xy, segment_data[0].zw);
 
     VertexInfo vi;
 
     // Fetch the dynamic picture that we are drawing on.
-    PictureTask pic_task = fetch_picture_task(ph.render_task_index);
-    ClipArea clip_area = fetch_clip_area(clip_address);
+    PictureTask pic_task = fetch_picture_task(brush.picture_address);
+    ClipArea clip_area = fetch_clip_area(brush.clip_address);
 
-    Transform transform = fetch_transform(ph.transform_id);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(brush.scroll_node_id);
 
     // Write the normal vertex information out.
-    if (transform.is_axis_aligned) {
+    if (scroll_node.is_axis_aligned) {
         vi = write_vertex(
             local_segment_rect,
-            ph.local_clip_rect,
-            ph.z,
-            transform,
+            brush_prim.local_clip_rect,
+            float(brush.z),
+            scroll_node,
             pic_task,
-            ph.local_rect
+            brush_prim.local_rect
         );
 
         // TODO(gw): transform bounds may be referenced by
         //           the fragment shader when running in
         //           the alpha pass, even on non-transformed
         //           items. For now, just ensure it has no
         //           effect. We can tidy this up as we move
         //           more items to be brush shaders.
 #ifdef WR_FEATURE_ALPHA_PASS
         init_transform_vs(vec4(vec2(-1000000.0), vec2(1000000.0)));
 #endif
     } else {
-        bvec4 edge_mask = notEqual(edge_flags & ivec4(1, 2, 4, 8), ivec4(0));
-        bool do_perspective_interpolation = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0;
+        bvec4 edge_mask = notEqual(brush.edge_mask & ivec4(1, 2, 4, 8), ivec4(0));
+        bool do_perspective_interpolation = (brush.flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0;
 
         vi = write_transform_vertex(
             local_segment_rect,
-            ph.local_rect,
-            ph.local_clip_rect,
+            brush_prim.local_rect,
+            brush_prim.local_clip_rect,
             mix(vec4(0.0), vec4(1.0), edge_mask),
-            ph.z,
-            transform,
+            float(brush.z),
+            scroll_node,
             pic_task,
             do_perspective_interpolation
         );
     }
 
     // For brush instances in the alpha pass, always write
     // out clip information.
     // TODO(gw): It's possible that we might want alpha
@@ -95,23 +153,23 @@ void main(void) {
         vi.screen_pos,
         clip_area
     );
 #endif
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
-        ph.specific_prim_address,
-        ph.local_rect,
+        brush.prim_address + VECS_PER_BRUSH_PRIM,
+        brush_prim.local_rect,
         local_segment_rect,
-        ph.user_data,
-        transform.m,
+        brush.user_data,
+        scroll_node.transform,
         pic_task,
-        brush_flags,
+        brush.flags,
         segment_data[1]
     );
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 
 struct Fragment {
--- a/gfx/webrender/res/brush_linear_gradient.glsl
+++ b/gfx/webrender/res/brush_linear_gradient.glsl
@@ -44,26 +44,21 @@ 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 texel_rect
+    vec4 unused
 ) {
     Gradient gradient = fetch_gradient(prim_address);
 
-    if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
-        vPos = (vi.local_pos - segment_rect.p0) / segment_rect.size;
-        vPos = vPos * (texel_rect.zw - texel_rect.xy) + texel_rect.xy;
-    } else {
-        vPos = vi.local_pos - local_rect.p0;
-    }
+    vPos = vi.local_pos - local_rect.p0;
 
     vec2 start_point = gradient.start_end_point.xy;
     vec2 end_point = gradient.start_end_point.zw;
     vec2 dir = end_point - start_point;
 
     vStartPoint = start_point;
     vScaledDir = dir / dot(dir, dir);
 
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -44,26 +44,21 @@ 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 texel_rect
+    vec4 unused
 ) {
     RadialGradient gradient = fetch_radial_gradient(prim_address);
 
-    if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
-        vPos = (vi.local_pos - segment_rect.p0) / segment_rect.size;
-        vPos = vPos * (texel_rect.zw - texel_rect.xy) + texel_rect.xy;
-    } else {
-        vPos = vi.local_pos - local_rect.p0;
-    }
+    vPos = vi.local_pos - local_rect.p0;
 
     vCenter = gradient.center_start_end_radius.xy;
     vStartRadius = gradient.center_start_end_radius.z;
     vEndRadius = gradient.center_start_end_radius.w;
 
     // Transform all coordinates by the y scale so the
     // fragment shader can work with circles
     vec2 tile_repeat = local_rect.size / gradient.stretch_size;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/clip_scroll.glsl
@@ -0,0 +1,88 @@
+/* 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
+#define VECS_PER_CLIP_SCROLL_NODE   9
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
+
+struct ClipScrollNode {
+    mat4 transform;
+    mat4 inv_transform;
+    bool is_axis_aligned;
+};
+
+ClipScrollNode fetch_clip_scroll_node(int index) {
+    ClipScrollNode node;
+
+    // Create a UV base coord for each 8 texels.
+    // This is required because trying to use an offset
+    // of more than 8 texels doesn't work on some versions
+    // of OSX.
+    ivec2 uv = get_fetch_uv(index, VECS_PER_CLIP_SCROLL_NODE);
+    ivec2 uv0 = ivec2(uv.x + 0, uv.y);
+    ivec2 uv1 = ivec2(uv.x + 8, uv.y);
+
+    node.transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(0, 0));
+    node.transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(1, 0));
+    node.transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(2, 0));
+    node.transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(3, 0));
+
+    node.inv_transform[0] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(4, 0));
+    node.inv_transform[1] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(5, 0));
+    node.inv_transform[2] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(6, 0));
+    node.inv_transform[3] = TEXEL_FETCH(sClipScrollNodes, uv0, 0, ivec2(7, 0));
+
+    vec4 misc = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(0, 0));
+    node.is_axis_aligned = misc.x == 0.0;
+
+    return node;
+}
+
+// Return the intersection of the plane (set up by "normal" and "point")
+// with the ray (set up by "ray_origin" and "ray_dir"),
+// writing the resulting scaler into "t".
+bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
+{
+    float denom = dot(normal, ray_dir);
+    if (abs(denom) > 1e-6) {
+        vec3 d = pt - ray_origin;
+        t = dot(d, normal) / denom;
+        return t >= 0.0;
+    }
+
+    return false;
+}
+
+// Apply the inverse transform "inv_transform"
+// to the reference point "ref" in CSS space,
+// producing a local point on a ClipScrollNode plane,
+// set by a base point "a" and a normal "n".
+vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) {
+    vec3 p = vec3(ref, -10000.0);
+    vec3 d = vec3(0, 0, 1.0);
+
+    float t = 0.0;
+    // get an intersection of the ClipScrollNode plane with Z axis vector,
+    // originated from the "ref" point
+    ray_plane(n, a, p, d, t);
+    float z = p.z + d.z * t; // Z of the visible point on the ClipScrollNode
+
+    vec4 r = inv_transform * vec4(ref, z, 1.0);
+    return r;
+}
+
+// Given a CSS space position, transform it back into the ClipScrollNode space.
+vec4 get_node_pos(vec2 pos, ClipScrollNode node) {
+    // get a point on the scroll node plane
+    vec4 ah = node.transform * vec4(0.0, 0.0, 0.0, 1.0);
+    vec3 a = ah.xyz / ah.w;
+
+    // get the normal to the scroll node plane
+    vec3 n = transpose(mat3(node.inv_transform)) * vec3(0.0, 0.0, 1.0);
+    return untransform(pos, n, a, node.inv_transform);
+}
+
+#endif //WR_VERTEX_SHADER
+
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -1,40 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include rect,render_task,resource_cache,snap,transform
+#include rect,clip_scroll,render_task,resource_cache,snap,transform
 
 #ifdef WR_VERTEX_SHADER
 
 #define SEGMENT_ALL         0
 #define SEGMENT_CORNER_TL   1
 #define SEGMENT_CORNER_TR   2
 #define SEGMENT_CORNER_BL   3
 #define SEGMENT_CORNER_BR   4
 
 in int aClipRenderTaskAddress;
 in int aScrollNodeId;
 in int aClipSegment;
 in ivec4 aClipDataResourceAddress;
 
 struct ClipMaskInstance {
     int render_task_address;
-    int transform_id;
+    int scroll_node_id;
     int segment;
     ivec2 clip_data_address;
     ivec2 resource_address;
 };
 
 ClipMaskInstance fetch_clip_item() {
     ClipMaskInstance cmi;
 
     cmi.render_task_address = aClipRenderTaskAddress;
-    cmi.transform_id = aScrollNodeId;
+    cmi.scroll_node_id = aScrollNodeId;
     cmi.segment = aClipSegment;
     cmi.clip_data_address = aClipDataResourceAddress.xy;
     cmi.resource_address = aClipDataResourceAddress.zw;
 
     return cmi;
 }
 
 struct ClipVertexInfo {
@@ -46,47 +46,47 @@ struct ClipVertexInfo {
 RectWithSize intersect_rect(RectWithSize a, RectWithSize b) {
     vec4 p = clamp(vec4(a.p0, a.p0 + a.size), b.p0.xyxy, b.p0.xyxy + b.size.xyxy);
     return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
 }
 
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
-                                      Transform transform,
+                                      ClipScrollNode scroll_node,
                                       ClipArea area) {
     vec2 device_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size;
     vec2 actual_pos = device_pos;
 
-    if (transform.is_axis_aligned) {
+    if (scroll_node.is_axis_aligned) {
         vec4 snap_positions = compute_snap_positions(
-            transform.m,
+            scroll_node.transform,
             local_clip_rect
         );
 
         vec2 snap_offsets = compute_snap_offset_impl(
             device_pos,
-            transform.m,
+            scroll_node.transform,
             local_clip_rect,
             RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
             snap_positions,
             vec2(0.5)
         );
 
         actual_pos -= snap_offsets;
     }
 
     vec4 node_pos;
 
     // Select the local position, based on whether we are rasterizing this
     // clip mask in local- or sccreen-space.
     if (area.local_space) {
         node_pos = vec4(actual_pos / uDevicePixelRatio, 0.0, 1.0);
     } else {
-        node_pos = get_node_pos(actual_pos / uDevicePixelRatio, transform);
+        node_pos = get_node_pos(actual_pos / uDevicePixelRatio, scroll_node);
     }
 
     // compute the point position inside the scroll node, in CSS space
     vec2 vertex_pos = device_pos +
                       area.common_data.task_rect.p0 -
                       area.screen_origin;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
--- a/gfx/webrender/res/cs_clip_box_shadow.glsl
+++ b/gfx/webrender/res/cs_clip_box_shadow.glsl
@@ -36,22 +36,22 @@ BoxShadowData fetch_data(ivec2 address) 
         dest_rect
     );
     return bs_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Transform transform = fetch_transform(cmi.transform_id);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
     BoxShadowData bs_data = fetch_data(cmi.clip_data_address);
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(bs_data.dest_rect,
-                                               transform,
+                                               scroll_node,
                                                area);
 
     vLayer = res.layer;
     vPos = vi.local_pos;
     vClipMode = bs_data.clip_mode;
 
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -21,23 +21,23 @@ ImageMaskData fetch_mask_data(ivec2 addr
     RectWithSize local_rect = RectWithSize(data.xy, data.zw);
     ImageMaskData mask_data = ImageMaskData(local_rect);
     return mask_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Transform transform = fetch_transform(cmi.transform_id);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
     ImageMaskData mask = fetch_mask_data(cmi.clip_data_address);
     RectWithSize local_rect = mask.local_rect;
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
-                                               transform,
+                                               scroll_node,
                                                area);
 
     vPos = vi.local_pos;
     vLayer = res.layer;
 
     vClipMaskImageUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(res.uv_rect.p0, res.uv_rect.p1 - res.uv_rect.p0) / texture_size.xyxy;
--- a/gfx/webrender/res/cs_clip_line.glsl
+++ b/gfx/webrender/res/cs_clip_line.glsl
@@ -38,21 +38,21 @@ LineDecorationData fetch_data(ivec2 addr
         data[1].z
     );
     return line_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Transform transform = fetch_transform(cmi.transform_id);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
     LineDecorationData data = fetch_data(cmi.clip_data_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(data.local_rect,
-                                               transform,
+                                               scroll_node,
                                                area);
 
 
     vLocalPos = vi.local_pos;
 
     vec2 pos, size;
 
     switch (int(data.orientation)) {
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -55,21 +55,21 @@ ClipData fetch_clip(ivec2 address) {
     clip.bottom_right = fetch_clip_corner(address, 3.0);
 
     return clip;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
-    Transform transform = fetch_transform(cmi.transform_id);
+    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
     ClipData clip = fetch_clip(cmi.clip_data_address);
     RectWithSize local_rect = clip.rect.rect;
 
-    ClipVertexInfo vi = write_clip_tile_vertex(local_rect, transform, area);
+    ClipVertexInfo vi = write_clip_tile_vertex(local_rect, scroll_node, area);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
 
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
 
     vec2 r_tl = clip.top_left.outer_inner_radius.xy;
     vec2 r_tr = clip.top_right.outer_inner_radius.xy;
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include rect,render_task,resource_cache,snap,transform
+#include rect,clip_scroll,render_task,resource_cache,snap,transform
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 #define SUBPX_DIR_NONE        0
 #define SUBPX_DIR_HORIZONTAL  1
 #define SUBPX_DIR_VERTICAL    2
 #define SUBPX_DIR_MIXED       3
@@ -27,98 +27,73 @@ vec2 clamp_rect(vec2 pt, RectWithSize re
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
 
 
 #ifdef WR_VERTEX_SHADER
 
+#define VECS_PER_LOCAL_CLIP_RECT    1
+#define VECS_PER_PRIM_HEADER        2
+#define VECS_PER_TEXT_RUN           3
+#define VECS_PER_GRADIENT_STOP      2
+
 #define COLOR_MODE_FROM_PASS          0
 #define COLOR_MODE_ALPHA              1
 #define COLOR_MODE_SUBPX_CONST_COLOR  2
 #define COLOR_MODE_SUBPX_BG_PASS0     3
 #define COLOR_MODE_SUBPX_BG_PASS1     4
 #define COLOR_MODE_SUBPX_BG_PASS2     5
 #define COLOR_MODE_SUBPX_DUAL_SOURCE  6
 #define COLOR_MODE_BITMAP             7
 #define COLOR_MODE_COLOR_BITMAP       8
 
-uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF;
-uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI;
+uniform HIGHP_SAMPLER_FLOAT sampler2D sLocalClipRects;
 
 // Instanced attributes
-in ivec4 aData;
-
-#define VECS_PER_PRIM_HEADER_F 2
-#define VECS_PER_PRIM_HEADER_I 2
-
-struct PrimitiveHeader {
-    RectWithSize local_rect;
-    RectWithSize local_clip_rect;
-    float z;
-    int specific_prim_address;
-    int render_task_index;
-    int clip_task_index;
-    int transform_id;
-    ivec3 user_data;
-};
+in ivec4 aData0;
+in ivec4 aData1;
 
-PrimitiveHeader fetch_prim_header(int index) {
-    PrimitiveHeader ph;
-
-    ivec2 uv_f = get_fetch_uv(index, VECS_PER_PRIM_HEADER_F);
-    vec4 local_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(0, 0));
-    vec4 local_clip_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(1, 0));
-    ph.local_rect = RectWithSize(local_rect.xy, local_rect.zw);
-    ph.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
-
-    ivec2 uv_i = get_fetch_uv(index, VECS_PER_PRIM_HEADER_I);
-    ivec4 data0 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(0, 0));
-    ivec4 data1 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(1, 0));
-    ph.z = float(data0.x);
-    ph.render_task_index = data0.y;
-    ph.specific_prim_address = data0.z;
-    ph.clip_task_index = data0.w;
-    ph.transform_id = data1.x;
-    ph.user_data = data1.yzw;
-
-    return ph;
+RectWithSize fetch_clip_chain_rect(int index) {
+    ivec2 uv = get_fetch_uv(index, VECS_PER_LOCAL_CLIP_RECT);
+    vec4 rect = TEXEL_FETCH(sLocalClipRects, uv, 0, ivec2(0, 0));
+    return RectWithSize(rect.xy, rect.zw);
 }
 
 struct VertexInfo {
     vec2 local_pos;
     vec2 screen_pos;
     float w;
     vec2 snapped_device_pos;
 };
 
 VertexInfo write_vertex(RectWithSize instance_rect,
                         RectWithSize local_clip_rect,
                         float z,
-                        Transform transform,
+                        ClipScrollNode scroll_node,
                         PictureTask task,
                         RectWithSize snap_rect) {
 
     // Select the corner of the local rect that we are processing.
     vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
 
     // 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,
+        scroll_node.transform,
         snap_rect,
         vec2(0.5)
     );
 
     // Transform the current vertex to world space.
-    vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
+    vec4 world_pos = scroll_node.transform * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
     // Apply offsets for the render task to get correct screen location.
     vec2 snapped_device_pos = device_pos + snap_offset;
     vec2 final_pos = snapped_device_pos -
                      task.content_origin +
@@ -155,17 +130,17 @@ vec2 intersect_lines(vec2 p0, vec2 p1, v
     return vec2(nx / d, ny / d);
 }
 
 VertexInfo write_transform_vertex(RectWithSize local_segment_rect,
                                   RectWithSize local_prim_rect,
                                   RectWithSize local_clip_rect,
                                   vec4 clip_edge_mask,
                                   float z,
-                                  Transform transform,
+                                  ClipScrollNode scroll_node,
                                   PictureTask task,
                                   bool do_perspective_interpolation) {
     // Calculate a clip rect from local_rect + local clip
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_clip_rect);
     RectWithEndpoint segment_rect = to_rect_with_endpoint(local_segment_rect);
     segment_rect.p0 = clamp(segment_rect.p0, clip_rect.p0, clip_rect.p1);
     segment_rect.p1 = clamp(segment_rect.p1, clip_rect.p0, clip_rect.p1);
 
@@ -188,17 +163,17 @@ VertexInfo write_transform_vertex(RectWi
     vec4 extrude_distance = vec4(extrude_amount) * clip_edge_mask;
     local_segment_rect.p0 -= extrude_distance.xy;
     local_segment_rect.size += extrude_distance.xy + extrude_distance.zw;
 
     // Select the corner of the local rect that we are processing.
     vec2 local_pos = local_segment_rect.p0 + local_segment_rect.size * aPosition.xy;
 
     // Transform the current vertex to the world cpace.
-    vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
+    vec4 world_pos = scroll_node.transform * vec4(local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
     vec2 task_offset = task.common_data.task_rect.p0 - task.content_origin;
 
     // Force w = 1, if we don't want perspective interpolation (for
     // example, drawing a screen-space quad on an element with a
     // perspective transform).
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -39,20 +39,20 @@ struct SplitCompositeInstance {
     int src_task_index;
     int polygons_address;
     float z;
 };
 
 SplitCompositeInstance fetch_composite_instance() {
     SplitCompositeInstance ci;
 
-    ci.render_task_index = aData.x;
-    ci.src_task_index = aData.y;
-    ci.polygons_address = aData.z;
-    ci.z = float(aData.w);
+    ci.render_task_index = aData0.x;
+    ci.src_task_index = aData0.y;
+    ci.polygons_address = aData0.z;
+    ci.z = float(aData0.w);
 
     return ci;
 }
 
 void main(void) {
     SplitCompositeInstance ci = fetch_composite_instance();
     SplitGeometry geometry = fetch_split_geometry(ci.polygons_address);
     PictureTask src_task = fetch_picture_task(ci.src_task_index);
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -10,18 +10,16 @@ flat varying vec4 vUvBorder;
 flat varying vec2 vMaskSwizzle;
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
 varying vec4 vUvClip;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_TEXT_RUN           3
-
 struct Glyph {
     vec2 offset;
 };
 
 Glyph fetch_glyph(int specific_prim_address,
                   int glyph_index) {
     // Two glyphs are packed in each texel in the GPU cache.
     int glyph_address = specific_prim_address +
@@ -54,59 +52,137 @@ struct TextRun {
     vec2 offset;
 };
 
 TextRun fetch_text_run(int address) {
     vec4 data[3] = fetch_from_resource_cache_3(address);
     return TextRun(data[0], data[1], data[2].xy);
 }
 
+struct PrimitiveInstance {
+    int prim_address;
+    int specific_prim_address;
+    int render_task_index;
+    int clip_task_index;
+    int scroll_node_id;
+    int clip_chain_rect_index;
+    int z;
+    int user_data0;
+    int user_data1;
+    int user_data2;
+};
+
+PrimitiveInstance fetch_prim_instance() {
+    PrimitiveInstance pi;
+
+    pi.prim_address = aData0.x;
+    pi.specific_prim_address = pi.prim_address + VECS_PER_PRIM_HEADER;
+    pi.render_task_index = aData0.y % 0x10000;
+    pi.clip_task_index = aData0.y / 0x10000;
+    pi.clip_chain_rect_index = aData0.z;
+    pi.scroll_node_id = aData0.w;
+    pi.z = aData1.x;
+    pi.user_data0 = aData1.y;
+    pi.user_data1 = aData1.z;
+    pi.user_data2 = aData1.w;
+
+    return pi;
+}
+
+struct Primitive {
+    ClipScrollNode scroll_node;
+    ClipArea clip_area;
+    PictureTask task;
+    RectWithSize local_rect;
+    RectWithSize local_clip_rect;
+    int specific_prim_address;
+    int user_data0;
+    int user_data1;
+    int user_data2;
+    float z;
+};
+
+struct PrimitiveGeometry {
+    RectWithSize local_rect;
+    RectWithSize local_clip_rect;
+};
+
+PrimitiveGeometry fetch_primitive_geometry(int address) {
+    vec4 geom[2] = fetch_from_resource_cache_2(address);
+    return PrimitiveGeometry(RectWithSize(geom[0].xy, geom[0].zw),
+                             RectWithSize(geom[1].xy, geom[1].zw));
+}
+
+Primitive load_primitive() {
+    PrimitiveInstance pi = fetch_prim_instance();
+
+    Primitive prim;
+
+    prim.scroll_node = fetch_clip_scroll_node(pi.scroll_node_id);
+    prim.clip_area = fetch_clip_area(pi.clip_task_index);
+    prim.task = fetch_picture_task(pi.render_task_index);
+
+    RectWithSize clip_chain_rect = fetch_clip_chain_rect(pi.clip_chain_rect_index);
+
+    PrimitiveGeometry geom = fetch_primitive_geometry(pi.prim_address);
+    prim.local_rect = geom.local_rect;
+    prim.local_clip_rect = intersect_rects(clip_chain_rect, geom.local_clip_rect);
+
+    prim.specific_prim_address = pi.specific_prim_address;
+    prim.user_data0 = pi.user_data0;
+    prim.user_data1 = pi.user_data1;
+    prim.user_data2 = pi.user_data2;
+    prim.z = float(pi.z);
+
+    return prim;
+}
+
 VertexInfo write_text_vertex(vec2 clamped_local_pos,
                              RectWithSize local_clip_rect,
                              float z,
-                             Transform transform,
+                             ClipScrollNode scroll_node,
                              PictureTask task,
                              vec2 text_offset,
                              RectWithSize snap_rect,
                              vec2 snap_bias) {
     // Transform the current vertex to world space.
-    vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
+    vec4 world_pos = scroll_node.transform * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     float device_scale = uDevicePixelRatio / world_pos.w;
     vec2 device_pos = world_pos.xy * device_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;
 
 #if defined(WR_FEATURE_GLYPH_TRANSFORM)
     bool remove_subpx_offset = true;
 #else
     // Compute the snapping offset only if the scroll node transform is axis-aligned.
-    bool remove_subpx_offset = transform.is_axis_aligned;
+    bool remove_subpx_offset = scroll_node.is_axis_aligned;
 #endif
     if (remove_subpx_offset) {
         // Ensure the transformed text offset does not contain a subpixel translation
         // such that glyph snapping is stable for equivalent glyph subpixel positions.
-        vec2 world_text_offset = mat2(transform.m) * text_offset;
-        vec2 device_text_pos = (transform.m[3].xy + world_text_offset) * device_scale;
+        vec2 world_text_offset = mat2(scroll_node.transform) * text_offset;
+        vec2 device_text_pos = (scroll_node.transform[3].xy + world_text_offset) * device_scale;
         final_pos += floor(device_text_pos + 0.5) - device_text_pos;
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
         // For transformed subpixels, we just need to align the glyph origin to a device pixel.
         // The transformed text offset has already been snapped, so remove it from the glyph
         // origin when snapping the glyph.
         vec2 snap_offset = snap_rect.p0 - world_text_offset * device_scale;
         final_pos += floor(snap_offset + snap_bias) - snap_offset;
 #else
         // The transformed text offset has already been snapped, so remove it from the transform
         // when snapping the glyph.
-        mat4 snap_transform = transform.m;
+        mat4 snap_transform = scroll_node.transform;
         snap_transform[3].xy = -world_text_offset;
         final_pos += compute_snap_offset(
             clamped_local_pos,
             snap_transform,
             snap_rect,
             snap_bias
         );
 #endif
@@ -120,71 +196,65 @@ VertexInfo write_text_vertex(vec2 clampe
         world_pos.w,
         final_pos
     );
 
     return vi;
 }
 
 void main(void) {
-    int prim_header_address = aData.x;
-    int glyph_index = aData.y;
-    int resource_address = aData.z;
-    int subpx_dir = aData.w >> 16;
-    int color_mode = aData.w & 0xffff;
+    Primitive prim = load_primitive();
+    TextRun text = fetch_text_run(prim.specific_prim_address);
 
-    PrimitiveHeader ph = fetch_prim_header(prim_header_address);
-
-    Transform transform = fetch_transform(ph.transform_id);
-    ClipArea clip_area = fetch_clip_area(ph.clip_task_index);
-    PictureTask task = fetch_picture_task(ph.render_task_index);
-
-    TextRun text = fetch_text_run(ph.specific_prim_address);
+    int glyph_index = prim.user_data0;
+    int resource_address = prim.user_data1;
+    int subpx_dir = prim.user_data2 >> 16;
+    int color_mode = prim.user_data2 & 0xffff;
 
     if (color_mode == COLOR_MODE_FROM_PASS) {
         color_mode = uMode;
     }
 
-    Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
+    Glyph glyph = fetch_glyph(prim.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 transform = mat2(prim.scroll_node.transform) * uDevicePixelRatio;
 
     // Compute the glyph rect in glyph space.
-    RectWithSize glyph_rect = RectWithSize(res.offset + glyph_transform * (text.offset + glyph.offset),
+    RectWithSize glyph_rect = RectWithSize(res.offset + transform * (text.offset + glyph.offset),
                                            res.uv_rect.zw - res.uv_rect.xy);
 
     // Transform the glyph rect back to local space.
-    mat2 inv = inverse(glyph_transform);
+    mat2 inv = inverse(transform);
     RectWithSize local_rect = transform_rect(glyph_rect, inv);
 
     // Select the corner of the glyph's local space rect that we are processing.
     vec2 local_pos = local_rect.p0 + local_rect.size * aPosition.xy;
 
     // If the glyph's local rect would fit inside the local clip rect, then select a corner from
     // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
     // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
-    local_pos = rect_inside_rect(local_rect, ph.local_clip_rect) ?
+    local_pos = rect_inside_rect(local_rect, prim.local_clip_rect) ?
                     inv * (glyph_rect.p0 + glyph_rect.size * aPosition.xy) :
-                    clamp_rect(local_pos, ph.local_clip_rect);
+                    clamp_rect(local_pos, prim.local_clip_rect);
 #else
     // Scale from glyph space to local space.
     float scale = res.scale / uDevicePixelRatio;
 
     // 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));
 
     // Select the corner of the glyph rect that we are processing.
     vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
 
     // Clamp to the local clip rect.
-    local_pos = clamp_rect(local_pos, ph.local_clip_rect);
+    local_pos = clamp_rect(local_pos, prim.local_clip_rect);
 #endif
 
     vec2 snap_bias;
     // In subpixel mode, the subpixel offset has already been
     // accounted for while rasterizing the glyph. However, we
     // must still round with a subpixel bias rather than rounding
     // to the nearest whole pixel, depending on subpixel direciton.
     switch (subpx_dir) {
@@ -203,32 +273,32 @@ void main(void) {
             snap_bias = vec2(0.5, 0.125);
             break;
         case SUBPX_DIR_MIXED:
             snap_bias = vec2(0.125);
             break;
     }
 
     VertexInfo vi = write_text_vertex(local_pos,
-                                      ph.local_clip_rect,
-                                      ph.z,
-                                      transform,
-                                      task,
+                                      prim.local_clip_rect,
+                                      prim.z,
+                                      prim.scroll_node,
+                                      prim.task,
                                       text.offset,
                                       glyph_rect,
                                       snap_bias);
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
-    vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
+    vec2 f = (transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
     vUvClip = vec4(f, 1.0 - f);
 #else
     vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size;
 #endif
 
-    write_clip(vi.screen_pos, clip_area);
+    write_clip(vi.screen_pos, prim.clip_area);
 
     switch (color_mode) {
         case COLOR_MODE_ALPHA:
         case COLOR_MODE_BITMAP:
             vMaskSwizzle = vec2(0.0, 1.0);
             vColor = text.color;
             break;
         case COLOR_MODE_SUBPX_BG_PASS2:
--- a/gfx/webrender/res/snap.glsl
+++ b/gfx/webrender/res/snap.glsl
@@ -3,17 +3,17 @@
  * 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) {
     // 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
+    //           is a scroll_node 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));
 
     // 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
@@ -36,17 +36,17 @@ vec2 compute_snap_offset_impl(
     /// Compute the position of this vertex inside the snap rectangle.
     vec2 normalized_snap_pos = (reference_pos - reference_rect.p0) / reference_rect.size;
 
     /// 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.
+// given local position on the scroll_node and a snap rectangle.
 vec2 compute_snap_offset(vec2 local_pos,
                          mat4 transform,
                          RectWithSize snap_rect,
                          vec2 snap_bias) {
     vec4 snap_positions = compute_snap_positions(
         transform,
         snap_rect
     );
--- a/gfx/webrender/res/transform.glsl
+++ b/gfx/webrender/res/transform.glsl
@@ -1,99 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 flat varying vec4 vTransformBounds;
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_TRANSFORM   8
-uniform HIGHP_SAMPLER_FLOAT sampler2D sTransformPalette;
-
 void init_transform_vs(vec4 local_bounds) {
     vTransformBounds = local_bounds;
 }
 
-struct Transform {
-    mat4 m;
-    mat4 inv_m;
-    bool is_axis_aligned;
-};
-
-Transform fetch_transform(int id) {
-    Transform transform;
-
-    transform.is_axis_aligned = (id >> 24) == 0;
-    int index = id & 0x00ffffff;
-
-    // Create a UV base coord for each 8 texels.
-    // This is required because trying to use an offset
-    // of more than 8 texels doesn't work on some versions
-    // of OSX.
-    ivec2 uv = get_fetch_uv(index, VECS_PER_TRANSFORM);
-    ivec2 uv0 = ivec2(uv.x + 0, uv.y);
-
-    transform.m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(0, 0));
-    transform.m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(1, 0));
-    transform.m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(2, 0));
-    transform.m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(3, 0));
-
-    transform.inv_m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(4, 0));
-    transform.inv_m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(5, 0));
-    transform.inv_m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(6, 0));
-    transform.inv_m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(7, 0));
-
-    return transform;
-}
-
-// Return the intersection of the plane (set up by "normal" and "point")
-// with the ray (set up by "ray_origin" and "ray_dir"),
-// writing the resulting scaler into "t".
-bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
-{
-    float denom = dot(normal, ray_dir);
-    if (abs(denom) > 1e-6) {
-        vec3 d = pt - ray_origin;
-        t = dot(d, normal) / denom;
-        return t >= 0.0;
-    }
-
-    return false;
-}
-
-// Apply the inverse transform "inv_transform"
-// to the reference point "ref" in CSS space,
-// producing a local point on a Transform plane,
-// set by a base point "a" and a normal "n".
-vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) {
-    vec3 p = vec3(ref, -10000.0);
-    vec3 d = vec3(0, 0, 1.0);
-
-    float t = 0.0;
-    // get an intersection of the Transform plane with Z axis vector,
-    // originated from the "ref" point
-    ray_plane(n, a, p, d, t);
-    float z = p.z + d.z * t; // Z of the visible point on the Transform
-
-    vec4 r = inv_transform * vec4(ref, z, 1.0);
-    return r;
-}
-
-// Given a CSS space position, transform it back into the Transform space.
-vec4 get_node_pos(vec2 pos, Transform transform) {
-    // get a point on the scroll node plane
-    vec4 ah = transform.m * vec4(0.0, 0.0, 0.0, 1.0);
-    vec3 a = ah.xyz / ah.w;
-
-    // get the normal to the scroll node plane
-    vec3 n = transpose(mat3(transform.inv_m)) * vec3(0.0, 0.0, 1.0);
-    return untransform(pos, n, a, transform.inv_m);
-}
-
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
 float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -6,35 +6,35 @@ use api::{AlphaType, ClipMode, DeviceInt
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering, LayoutRect};
 use api::{DeviceIntPoint, YuvColorSpace, YuvFormat};
 use api::{LayoutToWorldTransform, WorldPixel};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
-use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
-use gpu_types::{PrimitiveInstance, RasterizationSpace, GlyphInstance};
-use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
+use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
+use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, SplitCompositeInstance};
+use gpu_types::{PrimitiveInstance, RasterizationSpace, GlyphInstance, ZBufferId};
+use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
 use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
 use prim_store::{BorderSource};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
-use util::{TransformedRectKind};
+use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -450,17 +450,17 @@ impl AlphaBatchBuilder {
     pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
-        prim_headers: &mut PrimitiveHeaders,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
 
         let task = &render_tasks[task_id];
         let content_origin = match task.kind {
             RenderTaskKind::Picture(ref pic_task) => {
                 pic_task.content_origin
             }
@@ -471,29 +471,29 @@ impl AlphaBatchBuilder {
 
         // Even though most of the time a splitter isn't used or needed,
         // they are cheap to construct so we will always pass one down.
         let mut splitter = BspSplitter::new();
 
         // Add each run in this picture to the batch.
         for run in &pic.runs {
             let scroll_node = &ctx.clip_scroll_tree.nodes[run.clip_and_scroll.scroll_node_id.0];
-            let transform_id = ctx.transforms.get_id(scroll_node.transform_index);
+            let scroll_id = scroll_node.node_data_index;
             self.add_run_to_batch(
                 run,
-                transform_id,
+                scroll_id,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
                 &mut splitter,
                 content_origin,
-                prim_headers,
+                z_generator,
             );
         }
 
         // Flush the accumulated plane splits onto the task tree.
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_index = PrimitiveIndex(poly.anchor);
             debug!("process sorted poly {:?} {:?}", prim_index, poly.points);
@@ -521,88 +521,92 @@ impl AlphaBatchBuilder {
                 .resolve_render_task_id();
             let source_task_address = render_tasks.get_task_address(source_task_id);
             let gpu_address = gpu_cache.get_address(&gpu_handle);
 
             let instance = SplitCompositeInstance::new(
                 task_address,
                 source_task_address,
                 gpu_address,
-                prim_headers.z_generator.next(),
+                z_generator.next(),
             );
 
             batch.push(PrimitiveInstance::from(instance));
         }
     }
 
     // Helper to add an entire primitive run to a batch list.
     // TODO(gw): Restructure this so the param list isn't quite
     //           so daunting!
     fn add_run_to_batch(
         &mut self,
         run: &PrimitiveRun,
-        transform_id: TransformPaletteId,
+        scroll_id: ClipScrollNodeIndex,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
         content_origin: DeviceIntPoint,
-        prim_headers: &mut PrimitiveHeaders,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
         for i in 0 .. run.count {
             let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
             let metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
 
             if metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
-                    transform_id,
+                    metadata.clip_chain_rect_index,
+                    scroll_id,
                     prim_index,
                     ctx,
                     gpu_cache,
                     render_tasks,
                     task_id,
                     task_address,
                     deferred_resolves,
                     splitter,
                     content_origin,
-                    prim_headers,
+                    z_generator,
                 );
             }
         }
     }
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
     // in that picture are being drawn into the same target.
     fn add_prim_to_batch(
         &mut self,
-        transform_id: TransformPaletteId,
+        clip_chain_rect_index: ClipChainRectIndex,
+        scroll_id: ClipScrollNodeIndex,
         prim_index: PrimitiveIndex,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
         content_origin: DeviceIntPoint,
-        prim_headers: &mut PrimitiveHeaders,
+        z_generator: &mut ZBufferIdGenerator,
     ) {
+        let z = z_generator.next();
         let prim_metadata = ctx.prim_store.get_metadata(prim_index);
         #[cfg(debug_assertions)] //TODO: why is this needed?
         debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id());
 
+        let scroll_node = &ctx.node_data[scroll_id.0 as usize];
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
-        let transform_kind = transform_id.transform_kind();
+        let transform_kind = scroll_node.transform.transform_kind();
 
         let screen_rect = prim_metadata.screen_rect.expect("bug");
         let task_relative_bounding_rect = DeviceIntRect::new(
             DeviceIntPoint::new(
                 screen_rect.unclipped.origin.x - content_origin.x,
                 screen_rect.unclipped.origin.y - content_origin.y,
             ),
             screen_rect.unclipped.size,
@@ -639,25 +643,16 @@ impl AlphaBatchBuilder {
         let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque ||
             prim_metadata.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex {
             specified_blend_mode
         } else {
             BlendMode::None
         };
 
-        let prim_header = PrimitiveHeader {
-            local_rect: prim_metadata.local_rect,
-            local_clip_rect: prim_metadata.combined_local_clip_rect,
-            task_address,
-            specific_prim_address: prim_cache_address,
-            clip_task_address,
-            transform_id,
-        };
-
         match prim_metadata.prim_kind {
             PrimitiveKind::Brush => {
                 let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
 
                 match brush.kind {
                     BrushKind::Picture { pic_index, .. } => {
                         let picture =
                             &ctx.prim_store.pictures[pic_index.0];
@@ -701,39 +696,43 @@ impl AlphaBatchBuilder {
                                                         gpu_cache,
                                                     );
                                                 let key = BatchKey::new(
                                                     kind,
                                                     non_segmented_blend_mode,
                                                     textures,
                                                 );
                                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                let prim_header_index = prim_headers.push(&prim_header, [
-                                                    uv_rect_address.as_int(),
-                                                    (ShaderColorMode::ColorBitmap as i32) << 16 |
-                                                    RasterizationSpace::Screen as i32,
-                                                    0,
-                                                ]);
 
                                                 let instance = BrushInstance {
-                                                    prim_header_index,
+                                                    picture_address: task_address,
+                                                    prim_address: prim_cache_address,
+                                                    clip_chain_rect_index,
+                                                    scroll_id,
+                                                    clip_task_address,
+                                                    z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
-                                                    clip_task_address,
+                                                    user_data: [
+                                                        uv_rect_address.as_int(),
+                                                        (ShaderColorMode::ColorBitmap as i32) << 16 |
+                                                        RasterizationSpace::Screen as i32,
+                                                        0,
+                                                    ],
                                                 };
                                                 batch.push(PrimitiveInstance::from(instance));
                                                 false
                                             }
                                             None => {
                                                 true
                                             }
                                         }
                                     }
-                                    FilterOp::DropShadow(offset, ..) => {
+                                    FilterOp::DropShadow(..) => {
                                         // Draw an instance of the shadow first, following by the content.
 
                                         // Both the shadow and the content get drawn as a brush image.
                                         if let Some(ref surface) = picture.surface {
                                             let kind = BatchKind::Brush(
                                                 BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                             );
 
@@ -764,54 +763,43 @@ impl AlphaBatchBuilder {
                                                 .as_int();
                                             let content_uv_rect_address = render_tasks[secondary_id]
                                                 .get_texture_address(gpu_cache)
                                                 .as_int();
 
                                             // Get the GPU cache address of the extra data handle.
                                             let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
 
-                                            let content_prim_header_index = prim_headers.push(&prim_header, [
-                                                content_uv_rect_address,
-                                                (ShaderColorMode::ColorBitmap as i32) << 16 |
-                                                RasterizationSpace::Screen as i32,
-                                                0,
-                                            ]);
-
-                                            let shadow_rect = prim_metadata.local_rect.translate(&offset);
-                                            let shadow_clip_rect = prim_metadata.local_clip_rect.translate(&offset);
-
-                                            let shadow_prim_header = PrimitiveHeader {
-                                                local_rect: shadow_rect,
-                                                local_clip_rect: shadow_clip_rect,
-                                                specific_prim_address: shadow_prim_address,
-                                                ..prim_header
-                                            };
-
-                                            let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, [
-                                                shadow_uv_rect_address,
-                                                (ShaderColorMode::Alpha as i32) << 16 |
-                                                RasterizationSpace::Screen as i32,
-                                                0,
-                                            ]);
-
                                             let shadow_instance = BrushInstance {
-                                                prim_header_index: shadow_prim_header_index,
+                                                picture_address: task_address,
+                                                prim_address: shadow_prim_address,
+                                                clip_chain_rect_index,
+                                                scroll_id,
                                                 clip_task_address,
+                                                z,
                                                 segment_index: 0,
                                                 edge_flags: EdgeAaSegmentMask::empty(),
                                                 brush_flags: BrushFlags::empty(),
+                                                user_data: [
+                                                    shadow_uv_rect_address,
+                                                    (ShaderColorMode::Alpha as i32) << 16 |
+                                                    RasterizationSpace::Screen as i32,
+                                                    0,
+                                                ],
                                             };
 
                                             let content_instance = BrushInstance {
-                                                prim_header_index: content_prim_header_index,
-                                                clip_task_address,
-                                                segment_index: 0,
-                                                edge_flags: EdgeAaSegmentMask::empty(),
-                                                brush_flags: BrushFlags::empty(),
+                                                prim_address: prim_cache_address,
+                                                user_data: [
+                                                    content_uv_rect_address,
+                                                    (ShaderColorMode::ColorBitmap as i32) << 16 |
+                                                    RasterizationSpace::Screen as i32,
+                                                    0,
+                                                ],
+                                                ..shadow_instance
                                             };
 
                                             self.batch_list
                                                 .get_suitable_batch(shadow_key, &task_relative_bounding_rect)
                                                 .push(PrimitiveInstance::from(shadow_instance));
 
                                             self.batch_list
                                                 .get_suitable_batch(content_key, &task_relative_bounding_rect)
@@ -863,28 +851,32 @@ impl AlphaBatchBuilder {
                                                     }
                                                     FilterOp::ColorMatrix(_) => {
                                                         picture.extra_gpu_data_handle.as_int(gpu_cache)
                                                     }
                                                 };
 
                                                 let cache_task_id = surface.resolve_render_task_id();
                                                 let cache_task_address = render_tasks.get_task_address(cache_task_id);
-                                                let prim_header_index = prim_headers.push(&prim_header, [
-                                                    cache_task_address.0 as i32,
-                                                    filter_mode,
-                                                    user_data,
-                                                ]);
 
                                                 let instance = BrushInstance {
-                                                    prim_header_index,
+                                                    picture_address: task_address,
+                                                    prim_address: prim_cache_address,
+                                                    clip_chain_rect_index,
+                                                    scroll_id,
                                                     clip_task_address,
+                                                    z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
+                                                    user_data: [
+                                                        cache_task_address.0 as i32,
+                                                        filter_mode,
+                                                        user_data,
+                                                    ],
                                                 };
 
                                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                                 batch.push(PrimitiveInstance::from(instance));
                                                 false
                                             }
                                             None => {
                                                 true
@@ -910,28 +902,32 @@ impl AlphaBatchBuilder {
                                         },
                                     ),
                                     BlendMode::PremultipliedAlpha,
                                     BatchTextures::no_texture(),
                                 );
                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                 let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
                                 let source_task_address = render_tasks.get_task_address(cache_task_id);
-                                let prim_header_index = prim_headers.push(&prim_header, [
-                                    mode as u32 as i32,
-                                    backdrop_task_address.0 as i32,
-                                    source_task_address.0 as i32,
-                                ]);
 
                                 let instance = BrushInstance {
-                                    prim_header_index,
+                                    picture_address: task_address,
+                                    prim_address: prim_cache_address,
+                                    clip_chain_rect_index,
+                                    scroll_id,
                                     clip_task_address,
+                                    z,
                                     segment_index: 0,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::empty(),
+                                    user_data: [
+                                        mode as u32 as i32,
+                                        backdrop_task_address.0 as i32,
+                                        source_task_address.0 as i32,
+                                    ],
                                 };
 
                                 batch.push(PrimitiveInstance::from(instance));
                                 false
                             }
                             Some(PictureCompositeMode::Blit) => {
                                 let cache_task_id = picture
                                     .surface
@@ -949,29 +945,33 @@ impl AlphaBatchBuilder {
                                 let batch = self.batch_list.get_suitable_batch(
                                     key,
                                     &task_relative_bounding_rect
                                 );
 
                                 let uv_rect_address = render_tasks[cache_task_id]
                                     .get_texture_address(gpu_cache)
                                     .as_int();
-                                let prim_header_index = prim_headers.push(&prim_header, [
-                                    uv_rect_address,
-                                    (ShaderColorMode::ColorBitmap as i32) << 16 |
-                                    RasterizationSpace::Screen as i32,
-                                    0,
-                                ]);
 
                                 let instance = BrushInstance {
-                                    prim_header_index,
+                                    picture_address: task_address,
+                                    prim_address: prim_cache_address,
+                                    clip_chain_rect_index,
+                                    scroll_id,
                                     clip_task_address,
+                                    z,
                                     segment_index: 0,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::empty(),
+                                    user_data: [
+                                        uv_rect_address,
+                                        (ShaderColorMode::ColorBitmap as i32) << 16 |
+                                        RasterizationSpace::Screen as i32,
+                                        0,
+                                    ],
                                 };
                                 batch.push(PrimitiveInstance::from(instance));
                                 false
                             }
                             None => {
                                 true
                             }
                         };
@@ -981,113 +981,121 @@ impl AlphaBatchBuilder {
                         if add_to_parent_pic {
                             self.add_pic_to_batch(
                                 picture,
                                 task_id,
                                 ctx,
                                 gpu_cache,
                                 render_tasks,
                                 deferred_resolves,
-                                prim_headers,
+                                z_generator,
                             );
                         }
                     }
                     BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         for tile in visible_tiles {
                             if let Some((batch_kind, textures, user_data)) = get_image_tile_params(
                                     ctx.resource_cache,
                                     gpu_cache,
                                     deferred_resolves,
                                     request.with_tile(tile.tile_offset),
                             ) {
                                 let prim_cache_address = gpu_cache.get_address(&tile.handle);
-                                let prim_header = PrimitiveHeader {
-                                    specific_prim_address: prim_cache_address,
-                                    local_rect: tile.local_rect,
-                                    local_clip_rect: tile.local_clip_rect,
-                                    ..prim_header
-                                };
-                                let prim_header_index = prim_headers.push(&prim_header, user_data);
-
                                 self.add_image_tile_to_batch(
                                     batch_kind,
                                     specified_blend_mode,
                                     textures,
-                                    prim_header_index,
+                                    clip_chain_rect_index,
                                     clip_task_address,
                                     &task_relative_bounding_rect,
+                                    prim_cache_address,
+                                    scroll_id,
+                                    task_address,
+                                    z,
+                                    user_data,
                                     tile.edge_flags
                                 );
                             }
                         }
                     }
                     BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
                             visible_tiles,
                             stops_handle,
                             BrushBatchKind::LinearGradient,
                             specified_blend_mode,
                             &task_relative_bounding_rect,
+                            clip_chain_rect_index,
+                            scroll_id,
+                            task_address,
                             clip_task_address,
+                            z,
                             gpu_cache,
                             &mut self.batch_list,
-                            &prim_header,
-                            prim_headers,
                         );
                     }
                     BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
                             visible_tiles,
                             stops_handle,
                             BrushBatchKind::RadialGradient,
                             specified_blend_mode,
                             &task_relative_bounding_rect,
+                            clip_chain_rect_index,
+                            scroll_id,
+                            task_address,
                             clip_task_address,
+                            z,
                             gpu_cache,
                             &mut self.batch_list,
-                            &prim_header,
-                            prim_headers,
                         );
                     }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                         ) {
-                            let prim_header_index = prim_headers.push(&prim_header, user_data);
-
                             self.add_brush_to_batch(
                                 brush,
                                 prim_metadata,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
-                                prim_header_index,
+                                clip_chain_rect_index,
                                 clip_task_address,
                                 &task_relative_bounding_rect,
+                                prim_cache_address,
+                                scroll_id,
+                                task_address,
                                 transform_kind,
+                                z,
                                 render_tasks,
+                                user_data,
                             );
                         }
                     }
                 }
             }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
 
-                let subpx_dir = text_cpu.used_font.get_subpx_dir();
+                let font = text_cpu.get_font(
+                    ctx.device_pixel_scale,
+                    scroll_node.transform,
+                );
+                let subpx_dir = font.get_subpx_dir();
 
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let batch_list = &mut self.batch_list;
 
                 ctx.resource_cache.fetch_glyphs(
-                    text_cpu.used_font.clone(),
+                    font,
                     &text_cpu.glyph_keys,
                     glyph_fetch_buffer,
                     gpu_cache,
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, SourceTexture::Invalid);
 
                         // Ignore color and only sample alpha when shadowing.
                         if text_cpu.shadow {
@@ -1104,29 +1112,29 @@ impl AlphaBatchBuilder {
                             ],
                         };
 
                         let kind = BatchKind::TextRun(glyph_format);
 
                         let (blend_mode, color_mode) = match glyph_format {
                             GlyphFormat::Subpixel |
                             GlyphFormat::TransformedSubpixel => {
-                                if text_cpu.used_font.bg_color.a != 0 {
+                                if text_cpu.font.bg_color.a != 0 {
                                     (
                                         BlendMode::SubpixelWithBgColor,
                                         ShaderColorMode::FromRenderPassMode,
                                     )
                                 } else if ctx.use_dual_source_blending {
                                     (
                                         BlendMode::SubpixelDualSource,
                                         ShaderColorMode::SubpixelDualSource,
                                     )
                                 } else {
                                     (
-                                        BlendMode::SubpixelConstantTextColor(text_cpu.used_font.color.into()),
+                                        BlendMode::SubpixelConstantTextColor(text_cpu.font.color.into()),
                                         ShaderColorMode::SubpixelConstantTextColor,
                                     )
                                 }
                             }
                             GlyphFormat::Alpha |
                             GlyphFormat::TransformedAlpha => {
                                 (
                                     BlendMode::PremultipliedAlpha,
@@ -1142,21 +1150,25 @@ impl AlphaBatchBuilder {
                             GlyphFormat::ColorBitmap => {
                                 (
                                     BlendMode::PremultipliedAlpha,
                                     ShaderColorMode::ColorBitmap,
                                 )
                             }
                         };
 
-                        let prim_header_index = prim_headers.push(&prim_header, [0; 3]);
                         let key = BatchKey::new(kind, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                         let base_instance = GlyphInstance::new(
-                            prim_header_index,
+                            prim_cache_address,
+                            task_address,
+                            clip_task_address,
+                            clip_chain_rect_index,
+                            scroll_id,
+                            z,
                         );
 
                         for glyph in glyphs {
                             batch.push(base_instance.build(
                                 glyph.index_in_text_run,
                                 glyph.uv_rect_address.as_int(),
                                 (subpx_dir as u32 as i32) << 16 |
                                 (color_mode as u32 as i32),
@@ -1168,27 +1180,37 @@ impl AlphaBatchBuilder {
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
         batch_kind: BrushBatchKind,
         blend_mode: BlendMode,
         textures: BatchTextures,
-        prim_header_index: PrimitiveHeaderIndex,
+        clip_chain_rect_index: ClipChainRectIndex,
         clip_task_address: RenderTaskAddress,
         task_relative_bounding_rect: &DeviceIntRect,
+        prim_cache_address: GpuCacheAddress,
+        scroll_id: ClipScrollNodeIndex,
+        task_address: RenderTaskAddress,
+        z: ZBufferId,
+        user_data: [i32; 3],
         edge_flags: EdgeAaSegmentMask,
     ) {
         let base_instance = BrushInstance {
-            prim_header_index,
+            picture_address: task_address,
+            prim_address: prim_cache_address,
+            clip_chain_rect_index,
+            scroll_id,
             clip_task_address,
+            z,
             segment_index: 0,
             edge_flags,
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+            user_data,
         };
 
         self.batch_list.add_bounding_rect(task_relative_bounding_rect);
 
         let batch_key = BatchKey {
             blend_mode,
             kind: BatchKind::Brush(batch_kind),
             textures,
@@ -1200,28 +1222,38 @@ impl AlphaBatchBuilder {
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
         prim_metadata: &PrimitiveMetadata,
         batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         textures: BatchTextures,
-        prim_header_index: PrimitiveHeaderIndex,
+        clip_chain_rect_index: ClipChainRectIndex,
         clip_task_address: RenderTaskAddress,
         task_relative_bounding_rect: &DeviceIntRect,
+        prim_cache_address: GpuCacheAddress,
+        scroll_id: ClipScrollNodeIndex,
+        task_address: RenderTaskAddress,
         transform_kind: TransformedRectKind,
+        z: ZBufferId,
         render_tasks: &RenderTaskTree,
+        user_data: [i32; 3],
     ) {
         let base_instance = BrushInstance {
-            prim_header_index,
+            picture_address: task_address,
+            prim_address: prim_cache_address,
+            clip_chain_rect_index,
+            scroll_id,
             clip_task_address,
+            z,
             segment_index: 0,
             edge_flags: EdgeAaSegmentMask::all(),
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+            user_data,
         };
 
         self.batch_list.add_bounding_rect(task_relative_bounding_rect);
 
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 let alpha_batch_key = BatchKey {
                     blend_mode: alpha_blend_mode,
@@ -1289,50 +1321,54 @@ impl AlphaBatchBuilder {
 }
 
 fn add_gradient_tiles(
     visible_tiles: &[VisibleGradientTile],
     stops_handle: &GpuCacheHandle,
     kind: BrushBatchKind,
     blend_mode: BlendMode,
     task_relative_bounding_rect: &DeviceIntRect,
+    clip_chain_rect_index: ClipChainRectIndex,
+    scroll_id: ClipScrollNodeIndex,
+    task_address: RenderTaskAddress,
     clip_task_address: RenderTaskAddress,
+    z: ZBufferId,
     gpu_cache: &GpuCache,
     batch_list: &mut BatchList,
-    base_prim_header: &PrimitiveHeader,
-    prim_headers: &mut PrimitiveHeaders,
 ) {
     batch_list.add_bounding_rect(task_relative_bounding_rect);
     let batch = batch_list.get_suitable_batch(
         BatchKey {
             blend_mode: blend_mode,
             kind: BatchKind::Brush(kind),
             textures: BatchTextures::no_texture(),
         },
         task_relative_bounding_rect
     );
 
     let user_data = [stops_handle.as_int(gpu_cache), 0, 0];
 
+    let base_instance = BrushInstance {
+        picture_address: task_address,
+        prim_address: GpuCacheAddress::invalid(),
+        clip_chain_rect_index,
+        scroll_id,
+        clip_task_address,
+        z,
+        segment_index: 0,
+        edge_flags: EdgeAaSegmentMask::all(),
+        brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+        user_data,
+    };
+
     for tile in visible_tiles {
-        let prim_header = PrimitiveHeader {
-            specific_prim_address: gpu_cache.get_address(&tile.handle),
-            local_rect: tile.local_rect,
-            local_clip_rect: tile.local_clip_rect,
-            ..*base_prim_header
-        };
-        let prim_header_index = prim_headers.push(&prim_header, user_data);
-
         batch.push(PrimitiveInstance::from(
             BrushInstance {
-                prim_header_index,
-                clip_task_address,
-                segment_index: 0,
-                edge_flags: EdgeAaSegmentMask::all(),
-                brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+                prim_address: gpu_cache.get_address(&tile.handle),
+                ..base_instance
             }
         ));
     }
 }
 
 fn get_image_tile_params(
     resource_cache: &ResourceCache,
     gpu_cache: &mut GpuCache,
@@ -1742,40 +1778,39 @@ impl ClipBatcher {
 
     pub fn add_clip_region(
         &mut self,
         task_address: RenderTaskAddress,
         clip_data_address: GpuCacheAddress,
     ) {
         let instance = ClipMaskInstance {
             render_task_address: task_address,
-            transform_id: TransformPaletteId::identity(),
+            scroll_node_data_index: ClipScrollNodeIndex(0),
             segment: 0,
             clip_data_address,
             resource_address: GpuCacheAddress::invalid(),
         };
 
         self.rectangles.push(instance);
     }
 
     pub fn add(
         &mut self,
         task_address: RenderTaskAddress,
         clips: &[ClipWorkItem],
         coordinate_system_id: CoordinateSystemId,
         resource_cache: &ResourceCache,
         gpu_cache: &GpuCache,
         clip_store: &ClipStore,
-        transforms: &TransformPalette,
     ) {
         let mut coordinate_system_id = coordinate_system_id;
         for work_item in clips.iter() {
             let instance = ClipMaskInstance {
                 render_task_address: task_address,
-                transform_id: transforms.get_id(work_item.transform_index),
+                scroll_node_data_index: work_item.scroll_node_data_index,
                 segment: 0,
                 clip_data_address: GpuCacheAddress::invalid(),
                 resource_address: GpuCacheAddress::invalid(),
             };
             let info = clip_store
                 .get_opt(&work_item.clip_sources)
                 .expect("bug: clip handle should be valid");
 
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -2,21 +2,21 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle};
 use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, TransformIndex};
+use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
-use gpu_types::{BoxShadowStretchMode};
+use gpu_types::{BoxShadowStretchMode, ClipScrollNodeIndex};
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use util::{LayoutToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
 use util::{extract_inner_rect_safe, pack_as_float};
 use std::sync::Arc;
 
 #[derive(Debug)]
@@ -620,13 +620,13 @@ impl Iterator for ClipChainNodeIter {
         previous
     }
 }
 
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipWorkItem {
-    pub transform_index: TransformIndex,
+    pub scroll_node_data_index: ClipScrollNodeIndex,
     pub clip_sources: ClipSourcesWeakHandle,
     pub coordinate_system_id: CoordinateSystemId,
 }
 
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -2,20 +2,20 @@
  * 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::{DevicePixelScale, ExternalScrollId, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize};
 use api::{LayoutVector2D, LayoutTransform, PipelineId, PropertyBinding};
 use api::{ScrollClamping, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
 use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
-use clip_scroll_tree::{TransformUpdateState, TransformIndex};
+use clip_scroll_tree::TransformUpdateState;
 use euclid::SideOffsets2D;
 use gpu_cache::GpuCache;
-use gpu_types::{TransformData, TransformPalette};
+use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::{LayoutToWorldFastTransform, LayoutFastTransform};
 use util::{TransformedRectKind};
 
 #[derive(Debug)]
 pub struct StickyFrameInfo {
     pub frame_rect: LayoutRect,
@@ -41,58 +41,51 @@ impl StickyFrameInfo {
             horizontal_offset_bounds,
             previously_applied_offset,
             current_offset: LayoutVector2D::zero(),
         }
     }
 }
 
 #[derive(Debug)]
-pub enum SpatialNodeKind {
-    /// A special kind of node that adjusts its position based on the position
-    /// of its parent node and a given set of sticky positioning offset bounds.
-    /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
-    /// https://www.w3.org/TR/css-position-3/#sticky-pos
-    StickyFrame(StickyFrameInfo),
-
-    /// Transforms it's content, but doesn't clip it. Can also be adjusted
-    /// by scroll events or setting scroll offsets.
-    ScrollFrame(ScrollFrameInfo),
-
+pub enum NodeType {
     /// A reference frame establishes a new coordinate space in the tree.
     ReferenceFrame(ReferenceFrameInfo),
-}
-
-#[derive(Debug)]
-pub enum NodeType {
-    Spatial {
-        kind: SpatialNodeKind,
-    },
 
     /// Other nodes just do clipping, but no transformation.
     Clip {
         handle: ClipSourcesHandle,
         clip_chain_index: ClipChainIndex,
 
         /// A copy of the ClipChainNode this node would produce. We need to keep a copy,
         /// because the ClipChain may not contain our node if is optimized out, but API
         /// defined ClipChains will still need to access it.
         clip_chain_node: Option<ClipChainNode>,
     },
 
+    /// Transforms it's content, but doesn't clip it. Can also be adjusted
+    /// by scroll events or setting scroll offsets.
+    ScrollFrame(ScrollFrameInfo),
+
+    /// A special kind of node that adjusts its position based on the position
+    /// of its parent node and a given set of sticky positioning offset bounds.
+    /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
+    /// https://www.w3.org/TR/css-position-3/#sticky-pos
+    StickyFrame(StickyFrameInfo),
+
     /// An empty node, used to pad the ClipScrollTree's array of nodes so that
     /// we can immediately use each assigned ClipScrollNodeIndex. After display
     /// list flattening this node type should never be used.
     Empty,
 }
 
 impl NodeType {
     fn is_reference_frame(&self) -> bool {
         match *self {
-            NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(_), .. } => true,
+            NodeType::ReferenceFrame(_) => true,
             _ => false,
         }
     }
 }
 
 /// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Debug)]
 pub struct ClipScrollNode {
@@ -128,151 +121,120 @@ pub struct ClipScrollNode {
     /// The axis-aligned coordinate system id of this node.
     pub coordinate_system_id: CoordinateSystemId,
 
     /// The transformation from the coordinate system which established our compatible coordinate
     /// system (same coordinate system id) and us. This can change via scroll offsets and via new
     /// reference frame transforms.
     pub coordinate_system_relative_transform: LayoutFastTransform,
 
-    /// The index of the spatial node that provides positioning information for this node.
-    /// For reference frames, scroll and sticky frames it is a unique identfier.
-    /// For clip nodes, this is the nearest ancestor spatial node.
-    pub transform_index: TransformIndex,
+    /// A linear ID / index of this clip-scroll node. Used as a reference to
+    /// pass to shaders, to allow them to fetch a given clip-scroll node.
+    pub node_data_index: GPUClipScrollNodeIndex,
 }
 
 impl ClipScrollNode {
     pub fn new(
         pipeline_id: PipelineId,
         parent_index: Option<ClipScrollNodeIndex>,
-        node_type: NodeType,
-        transform_index: TransformIndex,
+        node_type: NodeType
     ) -> Self {
         ClipScrollNode {
             world_viewport_transform: LayoutToWorldFastTransform::identity(),
             world_content_transform: LayoutToWorldFastTransform::identity(),
             transform_kind: TransformedRectKind::AxisAligned,
             parent: parent_index,
             children: Vec::new(),
             pipeline_id,
             node_type,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
             coordinate_system_relative_transform: LayoutFastTransform::identity(),
-            transform_index,
+            node_data_index: GPUClipScrollNodeIndex(0),
         }
     }
 
     pub fn empty() -> ClipScrollNode {
-        Self::new(
-            PipelineId::dummy(),
-            None,
-            NodeType::Empty,
-            TransformIndex(0),
-        )
+        Self::new(PipelineId::dummy(), None, NodeType::Empty)
     }
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_index: ClipScrollNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
-        transform_index: TransformIndex,
     ) -> Self {
-        let node_type = NodeType::Spatial {
-            kind: SpatialNodeKind::ScrollFrame(ScrollFrameInfo::new(
-                *frame_rect,
-                scroll_sensitivity,
-                LayoutSize::new(
-                    (content_size.width - frame_rect.size.width).max(0.0),
-                    (content_size.height - frame_rect.size.height).max(0.0)
-                ),
-                external_id,
-            )),
-        };
+        let node_type = NodeType::ScrollFrame(ScrollFrameInfo::new(
+            *frame_rect,
+            scroll_sensitivity,
+            LayoutSize::new(
+                (content_size.width - frame_rect.size.width).max(0.0),
+                (content_size.height - frame_rect.size.height).max(0.0)
+            ),
+            external_id,
+        ));
 
-        Self::new(
-            pipeline_id,
-            Some(parent_index),
-            node_type,
-            transform_index,
-        )
+        Self::new(pipeline_id, Some(parent_index), node_type)
     }
 
     pub fn new_reference_frame(
         parent_index: Option<ClipScrollNodeIndex>,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
         pipeline_id: PipelineId,
-        transform_index: TransformIndex,
     ) -> Self {
         let identity = LayoutTransform::identity();
         let source_perspective = source_perspective.map_or_else(
             LayoutFastTransform::identity, |perspective| perspective.into());
         let info = ReferenceFrameInfo {
             resolved_transform: LayoutFastTransform::identity(),
             source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
             source_perspective,
             origin_in_parent_reference_frame,
             invertible: true,
         };
-        Self::new(
-            pipeline_id,
-            parent_index,
-            NodeType::Spatial {
-                kind: SpatialNodeKind::ReferenceFrame(info),
-            },
-            transform_index,
-        )
+        Self::new(pipeline_id, parent_index, NodeType::ReferenceFrame(info))
     }
 
     pub fn new_sticky_frame(
         parent_index: ClipScrollNodeIndex,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
-        transform_index: TransformIndex,
     ) -> Self {
-        let node_type = NodeType::Spatial {
-            kind: SpatialNodeKind::StickyFrame(sticky_frame_info),
-        };
-        Self::new(
-            pipeline_id,
-            Some(parent_index),
-            node_type,
-            transform_index,
-        )
+        let node_type = NodeType::StickyFrame(sticky_frame_info);
+        Self::new(pipeline_id, Some(parent_index), node_type)
     }
 
 
     pub fn add_child(&mut self, child: ClipScrollNodeIndex) {
         self.children.push(child);
     }
 
     pub fn apply_old_scrolling_state(&mut self, old_scroll_info: &ScrollFrameInfo) {
         match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref mut scrolling), .. } => {
+            NodeType::ScrollFrame(ref mut scrolling) => {
                 *scrolling = scrolling.combine_with_old_scroll_info(old_scroll_info);
             }
             _ if old_scroll_info.offset != LayoutVector2D::zero() => {
                 warn!("Tried to scroll a non-scroll node.")
             }
             _ => {}
         }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayoutPoint, clamp: ScrollClamping) -> bool {
         let scrollable_size = self.scrollable_size();
         let scrollable_width = scrollable_size.width;
         let scrollable_height = scrollable_size.height;
 
         let scrolling = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref mut scrolling), .. } => scrolling,
+            NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => {
                 warn!("Tried to scroll a non-scroll node.");
                 return false;
             }
         };
 
         let new_offset = match clamp {
             ScrollClamping::ToContentBounds => {
@@ -298,42 +260,39 @@ impl ClipScrollNode {
     }
 
     pub fn mark_uninvertible(&mut self) {
         self.invertible = false;
         self.world_content_transform = LayoutToWorldFastTransform::identity();
         self.world_viewport_transform = LayoutToWorldFastTransform::identity();
     }
 
-    pub fn push_gpu_data(
-        &mut self,
-        transform_palette: &mut TransformPalette,
-    ) {
-        if let NodeType::Spatial { .. } = self.node_type {
-            if !self.invertible {
-                transform_palette.set(self.transform_index, TransformData::invalid());
+    pub fn push_gpu_node_data(&mut self, node_data: &mut Vec<ClipScrollNodeData>) {
+        if !self.invertible {
+            node_data.push(ClipScrollNodeData::invalid());
+            return;
+        }
+
+        let inv_transform = match self.world_content_transform.inverse() {
+            Some(inverted) => inverted.to_transform(),
+            None => {
+                node_data.push(ClipScrollNodeData::invalid());
                 return;
             }
-
-            let inv_transform = match self.world_content_transform.inverse() {
-                Some(inverted) => inverted.to_transform(),
-                None => {
-                    transform_palette.set(self.transform_index, TransformData::invalid());
-                    return;
-                }
-            };
+        };
 
-            let data = TransformData {
-                transform: self.world_content_transform.into(),
-                inv_transform,
-            };
+        let data = ClipScrollNodeData {
+            transform: self.world_content_transform.into(),
+            inv_transform,
+            transform_kind: self.transform_kind as u32 as f32,
+            padding: [0.0; 3],
+        };
 
-            // Write the data that will be made available to the GPU for this node.
-            transform_palette.set(self.transform_index, data);
-        }
+        // Write the data that will be made available to the GPU for this node.
+        node_data.push(data);
     }
 
     pub fn update(
         &mut self,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
@@ -356,17 +315,17 @@ impl ClipScrollNode {
         } else {
             TransformedRectKind::Complex
         };
 
         // If this node is a reference frame, we check if it has a non-invertible matrix.
         // For non-reference-frames we assume that they will produce only additional
         // translations which should be invertible.
         match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(info), .. } if !info.invertible => {
+            NodeType::ReferenceFrame(info) if !info.invertible => {
                 self.mark_uninvertible();
                 return;
             }
             _ => self.invertible = true,
         }
 
         self.update_clip_work_item(
             state,
@@ -383,17 +342,17 @@ impl ClipScrollNode {
         state: &mut TransformUpdateState,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         clip_chains: &mut [ClipChain],
     ) {
         let (clip_sources_handle, clip_chain_index, stored_clip_chain_node) = match self.node_type {
-            NodeType::Clip { ref handle, clip_chain_index, ref mut clip_chain_node, .. } =>
+            NodeType::Clip { ref handle, clip_chain_index, ref mut clip_chain_node } =>
                 (handle, clip_chain_index, clip_chain_node),
             _ => {
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
@@ -414,17 +373,17 @@ impl ClipScrollNode {
         // Rectangle ClipSource.
         let screen_outer_rect = screen_outer_rect
             .expect("Clipping node didn't have outer rect.");
         let local_outer_rect = clip_sources.local_outer_rect
             .expect("Clipping node didn't have outer rect.");
 
         let new_node = ClipChainNode {
             work_item: ClipWorkItem {
-                transform_index: self.transform_index,
+                scroll_node_data_index: self.node_data_index,
                 clip_sources: clip_sources_handle.weak(),
                 coordinate_system_id: state.current_coordinate_system_id,
             },
             local_clip_rect:
                 self.coordinate_system_relative_transform.transform_rect(&local_outer_rect),
             screen_outer_rect,
             screen_inner_rect,
             prev: None,
@@ -478,31 +437,32 @@ impl ClipScrollNode {
         } else {
             self.world_viewport_transform
         };
 
         let added_offset = state.parent_accumulated_scroll_offset + sticky_offset + scroll_offset;
         self.coordinate_system_relative_transform =
             state.coordinate_system_relative_transform.offset(added_offset);
 
-        if let NodeType::Spatial { kind: SpatialNodeKind::StickyFrame(ref mut info), .. } = self.node_type {
-            info.current_offset = sticky_offset;
+        match self.node_type {
+            NodeType::StickyFrame(ref mut info) => info.current_offset = sticky_offset,
+            _ => {},
         }
 
         self.coordinate_system_id = state.current_coordinate_system_id;
     }
 
     pub fn update_transform_for_reference_frame(
         &mut self,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         scene_properties: &SceneProperties,
     ) {
         let info = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(ref mut info), .. } => info,
+            NodeType::ReferenceFrame(ref mut info) => info,
             _ => unreachable!("Called update_transform_for_reference_frame on non-ReferenceFrame"),
         };
 
         // Resolve the transform against any property bindings.
         let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
         info.resolved_transform =
             LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
             .pre_mul(&source_transform.into())
@@ -540,17 +500,17 @@ impl ClipScrollNode {
     }
 
     fn calculate_sticky_offset(
         &self,
         viewport_scroll_offset: &LayoutVector2D,
         viewport_rect: &LayoutRect,
     ) -> LayoutVector2D {
         let info = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::StickyFrame(ref info), .. } => info,
+            NodeType::StickyFrame(ref info) => info,
             _ => return LayoutVector2D::zero(),
         };
 
         if info.margins.top.is_none() && info.margins.bottom.is_none() &&
             info.margins.left.is_none() && info.margins.right.is_none() {
             return LayoutVector2D::zero();
         }
 
@@ -654,58 +614,55 @@ impl ClipScrollNode {
             return;
         }
 
         // The transformation we are passing is the transformation of the parent
         // reference frame and the offset is the accumulated offset of all the nodes
         // between us and the parent reference frame. If we are a reference frame,
         // we need to reset both these values.
         match self.node_type {
-            NodeType::Spatial { ref kind, .. } => {
-                match *kind {
-                    SpatialNodeKind::StickyFrame(ref info) => {
-                        // We don't translate the combined rect by the sticky offset, because sticky
-                        // offsets actually adjust the node position itself, whereas scroll offsets
-                        // only apply to contents inside the node.
-                        state.parent_accumulated_scroll_offset =
-                            info.current_offset + state.parent_accumulated_scroll_offset;
-                    }
-                    SpatialNodeKind::ScrollFrame(ref scrolling) => {
-                        state.parent_accumulated_scroll_offset =
-                            scrolling.offset + state.parent_accumulated_scroll_offset;
-                        state.nearest_scrolling_ancestor_offset = scrolling.offset;
-                        state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
-                    }
-                    SpatialNodeKind::ReferenceFrame(ref info) => {
-                        state.parent_reference_frame_transform = self.world_viewport_transform;
-                        state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
-                        state.coordinate_system_relative_transform =
-                            self.coordinate_system_relative_transform.clone();
-                        let translation = -info.origin_in_parent_reference_frame;
-                        state.nearest_scrolling_ancestor_viewport =
-                            state.nearest_scrolling_ancestor_viewport
-                               .translate(&translation);
-                    }
-                }
+            NodeType::ReferenceFrame(ref info) => {
+                state.parent_reference_frame_transform = self.world_viewport_transform;
+                state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
+                state.coordinate_system_relative_transform =
+                    self.coordinate_system_relative_transform.clone();
+                let translation = -info.origin_in_parent_reference_frame;
+                state.nearest_scrolling_ancestor_viewport =
+                    state.nearest_scrolling_ancestor_viewport
+                       .translate(&translation);
             }
             NodeType::Clip{ .. } => { }
+            NodeType::ScrollFrame(ref scrolling) => {
+                state.parent_accumulated_scroll_offset =
+                    scrolling.offset + state.parent_accumulated_scroll_offset;
+                state.nearest_scrolling_ancestor_offset = scrolling.offset;
+                state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
+            }
+            NodeType::StickyFrame(ref info) => {
+                // We don't translate the combined rect by the sticky offset, because sticky
+                // offsets actually adjust the node position itself, whereas scroll offsets
+                // only apply to contents inside the node.
+                state.parent_accumulated_scroll_offset =
+                    info.current_offset + state.parent_accumulated_scroll_offset;
+            }
             NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
         }
     }
 
     pub fn scrollable_size(&self) -> LayoutSize {
         match self.node_type {
-           NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(state), .. } => state.scrollable_size,
+           NodeType:: ScrollFrame(state) => state.scrollable_size,
             _ => LayoutSize::zero(),
         }
     }
 
+
     pub fn scroll(&mut self, scroll_location: ScrollLocation) -> bool {
         let scrolling = match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref mut scrolling), .. } => scrolling,
+            NodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => return false,
         };
 
         let delta = match scroll_location {
             ScrollLocation::Delta(delta) => delta,
             ScrollLocation::Start => {
                 if scrolling.offset.y.round() >= 0.0 {
                     // Nothing to do on this layer.
@@ -745,24 +702,24 @@ impl ClipScrollNode {
                 .round();
         }
 
         scrolling.offset != original_layer_scroll_offset
     }
 
     pub fn scroll_offset(&self) -> LayoutVector2D {
         match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ref scrolling), .. } => scrolling.offset,
+            NodeType::ScrollFrame(ref scrolling) => scrolling.offset,
             _ => LayoutVector2D::zero(),
         }
     }
 
     pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
         match self.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } if info.external_id == Some(external_id) => true,
+            NodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
             _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollFrameInfo {
     /// The rectangle of the viewport of this scroll frame. This is important for
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,19 +1,19 @@
 /* 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::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
 use api::{PipelineId, ScrollClamping, ScrollLocation, ScrollNodeState};
-use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
+use api::WorldPoint;
 use clip::{ClipChain, ClipSourcesHandle, ClipStore};
-use clip_scroll_node::{ClipScrollNode, NodeType, SpatialNodeKind, ScrollFrameInfo, StickyFrameInfo};
+use clip_scroll_node::{ClipScrollNode, NodeType, ScrollFrameInfo, StickyFrameInfo};
 use gpu_cache::GpuCache;
-use gpu_types::TransformPalette;
+use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::{LayoutFastTransform, LayoutToWorldFastTransform};
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
@@ -24,25 +24,16 @@ pub type ScrollStates = FastHashMap<Exte
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct ClipScrollNodeIndex(pub usize);
 
-// Used to index the smaller subset of nodes in the CST that define
-// new transform / positioning.
-// TODO(gw): In the future if we split the CST into a positioning and
-//           clipping tree, this can be tidied up a bit.
-#[derive(Copy, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct TransformIndex(pub u32);
-
 const ROOT_REFERENCE_FRAME_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(0);
 const TOPMOST_SCROLL_NODE_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(1);
 
 impl CoordinateSystemId {
     pub fn root() -> Self {
         CoordinateSystemId(0)
     }
 
@@ -74,23 +65,24 @@ pub struct ClipScrollTree {
     pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
     /// A vector of all ClipChains in this ClipScrollTree including those from
     /// ClipChainDescriptors and also those defined by the clipping node hierarchy.
     pub clip_chains: Vec<ClipChain>,
 
     pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayoutPoint, ScrollClamping)>,
 
+    /// The current frame id, used for giving a unique id to all new dynamically
+    /// added frames and clips. The ClipScrollTree increments this by one every
+    /// time a new dynamic frame is created.
+    current_new_node_item: u64,
+
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
-
-    /// The number of nodes in the CST that are spatial. Currently, this is all
-    /// nodes that are not clip nodes.
-    spatial_node_count: usize,
 }
 
 #[derive(Clone)]
 pub struct TransformUpdateState {
     pub parent_reference_frame_transform: LayoutToWorldFastTransform,
     pub parent_accumulated_scroll_offset: LayoutVector2D,
     pub nearest_scrolling_ancestor_offset: LayoutVector2D,
     pub nearest_scrolling_ancestor_viewport: LayoutRect,
@@ -115,18 +107,18 @@ pub struct TransformUpdateState {
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
             nodes: Vec::new(),
             clip_chains_descriptors: Vec::new(),
             clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
             pending_scroll_offsets: FastHashMap::default(),
+            current_new_node_item: 1,
             pipelines_to_discard: FastHashSet::default(),
-            spatial_node_count: 0,
         }
     }
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```nodes``` being empty.
     pub fn root_reference_frame_index(&self) -> ClipScrollNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.nodes.is_empty());
@@ -139,41 +131,42 @@ impl ClipScrollTree {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(self.nodes.len() >= 1);
         TOPMOST_SCROLL_NODE_INDEX
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         let mut result = vec![];
         for node in &self.nodes {
-            if let NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } = node.node_type {
+            if let NodeType::ScrollFrame(info) = node.node_type {
                 if let Some(id) = info.external_id {
                     result.push(ScrollNodeState { id, scroll_offset: info.offset })
                 }
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
+        self.current_new_node_item = 1;
+
         let mut scroll_states = FastHashMap::default();
         for old_node in &mut self.nodes.drain(..) {
             if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
                 continue;
             }
 
             match old_node.node_type {
-                NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(info), .. } if info.external_id.is_some() => {
+                NodeType::ScrollFrame(info) if info.external_id.is_some() => {
                     scroll_states.insert(info.external_id.unwrap(), info);
                 }
                 _ => {}
             }
         }
 
-        self.spatial_node_count = 0;
         self.pipelines_to_discard.clear();
         self.clip_chains = vec![ClipChain::empty(&DeviceIntRect::zero())];
         self.clip_chains_descriptors.clear();
         scroll_states
     }
 
     pub fn scroll_node(
         &mut self,
@@ -197,17 +190,17 @@ impl ClipScrollTree {
     ) -> ClipScrollNodeIndex {
         let index = match index {
             Some(index) => index,
             None => return self.topmost_scroll_node_index(),
         };
 
         let node = &self.nodes[index.0];
         match node.node_type {
-            NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(state), .. } if state.sensitive_to_input_events() => index,
+            NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index,
             _ => self.find_nearest_scrolling_ancestor(node.parent)
         }
     }
 
     pub fn scroll_nearest_scrolling_ancestor(
         &mut self,
         scroll_location: ScrollLocation,
         node_index: Option<ClipScrollNodeIndex>,
@@ -222,86 +215,88 @@ impl ClipScrollTree {
     pub fn update_tree(
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         pan: WorldPoint,
+        node_data: &mut Vec<ClipScrollNodeData>,
         scene_properties: &SceneProperties,
-    ) -> TransformPalette {
-        let mut transform_palette = TransformPalette::new(self.spatial_node_count);
-
-        if !self.nodes.is_empty() {
-            self.clip_chains[0] = ClipChain::empty(screen_rect);
-
-            let root_reference_frame_index = self.root_reference_frame_index();
-            let mut state = TransformUpdateState {
-                parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
-                parent_accumulated_scroll_offset: LayoutVector2D::zero(),
-                nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
-                nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
-                parent_clip_chain_index: ClipChainIndex(0),
-                current_coordinate_system_id: CoordinateSystemId::root(),
-                coordinate_system_relative_transform: LayoutFastTransform::identity(),
-                invertible: true,
-            };
-            let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
-            self.update_node(
-                root_reference_frame_index,
-                &mut state,
-                &mut next_coordinate_system_id,
-                device_pixel_scale,
-                clip_store,
-                resource_cache,
-                gpu_cache,
-                &mut transform_palette,
-                scene_properties,
-            );
-
-            self.build_clip_chains(screen_rect);
+    ) {
+        if self.nodes.is_empty() {
+            return;
         }
 
-        transform_palette
+        self.clip_chains[0] = ClipChain::empty(screen_rect);
+
+        let root_reference_frame_index = self.root_reference_frame_index();
+        let mut state = TransformUpdateState {
+            parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
+            parent_accumulated_scroll_offset: LayoutVector2D::zero(),
+            nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
+            nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
+            parent_clip_chain_index: ClipChainIndex(0),
+            current_coordinate_system_id: CoordinateSystemId::root(),
+            coordinate_system_relative_transform: LayoutFastTransform::identity(),
+            invertible: true,
+        };
+        let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
+        self.update_node(
+            root_reference_frame_index,
+            &mut state,
+            &mut next_coordinate_system_id,
+            device_pixel_scale,
+            clip_store,
+            resource_cache,
+            gpu_cache,
+            node_data,
+            scene_properties,
+        );
+
+        self.build_clip_chains(screen_rect);
     }
 
     fn update_node(
         &mut self,
         node_index: ClipScrollNodeIndex,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        transform_palette: &mut TransformPalette,
+        gpu_node_data: &mut Vec<ClipScrollNodeData>,
         scene_properties: &SceneProperties,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let mut state = state.clone();
         let node_children = {
             let node = match self.nodes.get_mut(node_index.0) {
                 Some(node) => node,
                 None => return,
             };
 
+            // We set this early so that we can use it to populate the ClipChain.
+            node.node_data_index = GPUClipScrollNodeIndex(gpu_node_data.len() as u32);
+
             node.update(
                 &mut state,
                 next_coordinate_system_id,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 scene_properties,
                 &mut self.clip_chains,
             );
 
-            node.push_gpu_data(transform_palette);
+            node.push_gpu_node_data(gpu_node_data);
 
             if node.children.is_empty() {
                 return;
             }
 
             node.prepare_state_for_children(&mut state);
             node.children.clone()
         };
@@ -310,17 +305,17 @@ impl ClipScrollTree {
             self.update_node(
                 child_node_index,
                 &mut state,
                 next_coordinate_system_id,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
-                transform_palette,
+                gpu_node_data,
                 scene_properties,
             );
         }
     }
 
     pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
         for descriptor in &self.clip_chains_descriptors {
             // A ClipChain is an optional parent (which is another ClipChain) and a list of
@@ -346,117 +341,52 @@ impl ClipScrollTree {
             chain.parent_index = descriptor.parent;
             self.clip_chains[descriptor.index.0] = chain;
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         for node in &mut self.nodes {
             let external_id = match node.node_type {
-                NodeType::Spatial { kind: SpatialNodeKind::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ), .. } => id,
+                NodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
                 _ => continue,
             };
 
             if let Some(scrolling_state) = old_states.get(&external_id) {
                 node.apply_old_scrolling_state(scrolling_state);
             }
 
             if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
                 node.set_scroll_origin(&offset, clamping);
             }
         }
     }
 
-    // Generate the next valid TransformIndex for the CST.
-    fn next_transform_index(&mut self) -> TransformIndex {
-        let transform_index = TransformIndex(self.spatial_node_count as u32);
-        self.spatial_node_count += 1;
-        transform_index
-    }
-
     pub fn add_clip_node(
         &mut self,
         index: ClipScrollNodeIndex,
         parent_index: ClipScrollNodeIndex,
         handle: ClipSourcesHandle,
         pipeline_id: PipelineId,
     )  -> ClipChainIndex {
         let clip_chain_index = self.allocate_clip_chain();
-        let transform_index = self.nodes[parent_index.0].transform_index;
-
-        let node_type = NodeType::Clip {
-            handle,
-            clip_chain_index,
-            clip_chain_node: None,
-        };
-        let node = ClipScrollNode::new(
-            pipeline_id,
-            Some(parent_index),
-            node_type,
-            transform_index,
-        );
+        let node_type = NodeType::Clip { handle, clip_chain_index, clip_chain_node: None };
+        let node = ClipScrollNode::new(pipeline_id, Some(parent_index), node_type);
         self.add_node(node, index);
         clip_chain_index
     }
 
-    pub fn add_scroll_frame(
-        &mut self,
-        index: ClipScrollNodeIndex,
-        parent_index: ClipScrollNodeIndex,
-        external_id: Option<ExternalScrollId>,
-        pipeline_id: PipelineId,
-        frame_rect: &LayoutRect,
-        content_size: &LayoutSize,
-        scroll_sensitivity: ScrollSensitivity,
-    ) {
-        let node = ClipScrollNode::new_scroll_frame(
-            pipeline_id,
-            parent_index,
-            external_id,
-            frame_rect,
-            content_size,
-            scroll_sensitivity,
-            self.next_transform_index(),
-        );
-        self.add_node(node, index);
-    }
-
-    pub fn add_reference_frame(
-        &mut self,
-        index: ClipScrollNodeIndex,
-        parent_index: Option<ClipScrollNodeIndex>,
-        source_transform: Option<PropertyBinding<LayoutTransform>>,
-        source_perspective: Option<LayoutTransform>,
-        origin_in_parent_reference_frame: LayoutVector2D,
-        pipeline_id: PipelineId,
-    ) {
-        let node = ClipScrollNode::new_reference_frame(
-            parent_index,
-            source_transform,
-            source_perspective,
-            origin_in_parent_reference_frame,
-            pipeline_id,
-            self.next_transform_index(),
-        );
-        self.add_node(node, index);
-    }
-
     pub fn add_sticky_frame(
         &mut self,
         index: ClipScrollNodeIndex,
         parent_index: ClipScrollNodeIndex,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) {
-        let node = ClipScrollNode::new_sticky_frame(
-            parent_index,
-            sticky_frame_info,
-            pipeline_id,
-            self.next_transform_index(),
-        );
+        let node = ClipScrollNode::new_sticky_frame(parent_index, sticky_frame_info, pipeline_id);
         self.add_node(node, index);
     }
 
     pub fn add_clip_chain_descriptor(
         &mut self,
         parent: Option<ClipChainIndex>,
         clips: Vec<ClipScrollNodeIndex>
     ) -> ClipChainIndex {
@@ -501,47 +431,43 @@ impl ClipScrollTree {
     fn print_node<T: PrintTreePrinter>(
         &self,
         index: ClipScrollNodeIndex,
         pt: &mut T,
         clip_store: &ClipStore
     ) {
         let node = &self.nodes[index.0];
         match node.node_type {
-            NodeType::Spatial { ref kind, .. } => {
-                match *kind {
-                    SpatialNodeKind::StickyFrame(ref sticky_frame_info) => {
-                        pt.new_level(format!("StickyFrame"));
-                        pt.add_item(format!("index: {:?}", index));
-                        pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
-                    }
-                    SpatialNodeKind::ScrollFrame(scrolling_info) => {
-                        pt.new_level(format!("ScrollFrame"));
-                        pt.add_item(format!("index: {:?}", index));
-                        pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
-                        pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
-                        pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
-                    }
-                    SpatialNodeKind::ReferenceFrame(ref info) => {
-                        pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
-                        pt.add_item(format!("index: {:?}", index));
-                    }
-                }
-            }
             NodeType::Clip { ref handle, .. } => {
                 pt.new_level("Clip".to_owned());
 
                 pt.add_item(format!("index: {:?}", index));
                 let clips = clip_store.get(handle).clips();
                 pt.new_level(format!("Clip Sources [{}]", clips.len()));
                 for source in clips {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
+            NodeType::ReferenceFrame(ref info) => {
+                pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
+                pt.add_item(format!("index: {:?}", index));
+            }
+            NodeType::ScrollFrame(scrolling_info) => {
+                pt.new_level(format!("ScrollFrame"));
+                pt.add_item(format!("index: {:?}", index));
+                pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
+                pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
+                pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
+            }
+            NodeType::StickyFrame(ref sticky_frame_info) => {
+                pt.new_level(format!("StickyFrame"));
+                pt.add_item(format!("index: {:?}", index));
+                pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
+            }
             NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
         }
 
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
         pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
 
         for child_index in &node.children {
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/device.rs
@@ -0,0 +1,2428 @@
+/* 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 super::shader_source;
+use api::{ColorF, ImageFormat};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
+use api::TextureTarget;
+#[cfg(any(feature = "debug_renderer", feature="capture"))]
+use api::ImageDescriptor;
+use euclid::Transform3D;
+use gleam::gl;
+use internal_types::{FastHashMap, RenderTargetInfo};
+use log::Level;
+use smallvec::SmallVec;
+use std::cell::RefCell;
+use std::fs::File;
+use std::io::Read;
+use std::marker::PhantomData;
+use std::mem;
+use std::ops::Add;
+use std::path::PathBuf;
+use std::ptr;
+use std::rc::Rc;
+use std::slice;
+use std::sync::Arc;
+use std::thread;
+
+#[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FrameId(usize);
+
+impl FrameId {
+    pub fn new(value: usize) -> Self {
+        FrameId(value)
+    }
+}
+
+impl Add<usize> for FrameId {
+    type Output = FrameId;
+
+    fn add(self, other: usize) -> FrameId {
+        FrameId(self.0 + other)
+    }
+}
+
+const GL_FORMAT_RGBA: gl::GLuint = gl::RGBA;
+
+const GL_FORMAT_BGRA_GL: gl::GLuint = gl::BGRA;
+
+const GL_FORMAT_BGRA_GLES: gl::GLuint = gl::BGRA_EXT;
+
+const SHADER_VERSION_GL: &str = "#version 150\n";
+const SHADER_VERSION_GLES: &str = "#version 300 es\n";
+
+const SHADER_KIND_VERTEX: &str = "#define WR_VERTEX_SHADER\n";
+const SHADER_KIND_FRAGMENT: &str = "#define WR_FRAGMENT_SHADER\n";
+const SHADER_IMPORT: &str = "#include ";
+
+pub struct TextureSlot(pub usize);
+
+// In some places we need to temporarily bind a texture to any slot.
+const DEFAULT_TEXTURE: TextureSlot = TextureSlot(0);
+
+#[repr(u32)]
+pub enum DepthFunction {
+    #[cfg(feature = "debug_renderer")]
+    Less = gl::LESS,
+    LessEqual = gl::LEQUAL,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum TextureFilter {
+    Nearest,
+    Linear,
+    Trilinear,
+}
+
+#[derive(Debug)]
+pub enum VertexAttributeKind {
+    F32,
+    #[cfg(feature = "debug_renderer")]
+    U8Norm,
+    U16Norm,
+    I32,
+    U16,
+}
+
+#[derive(Debug)]
+pub struct VertexAttribute {
+    pub name: &'static str,
+    pub count: u32,
+    pub kind: VertexAttributeKind,
+}
+
+#[derive(Debug)]
+pub struct VertexDescriptor {
+    pub vertex_attributes: &'static [VertexAttribute],
+    pub instance_attributes: &'static [VertexAttribute],
+}
+
+enum FBOTarget {
+    Read,
+    Draw,
+}
+
+/// Method of uploading texel data from CPU to GPU.
+#[derive(Debug, Clone)]
+pub enum UploadMethod {
+    /// Just call `glTexSubImage` directly with the CPU data pointer
+    Immediate,
+    /// Accumulate the changes in PBO first before transferring to a texture.
+    PixelBuffer(VertexUsageHint),
+}
+
+/// Plain old data that can be used to initialize a texture.
+pub unsafe trait Texel: Copy {}
+unsafe impl Texel for u8 {}
+unsafe impl Texel for f32 {}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ReadPixelsFormat {
+    Standard(ImageFormat),
+    Rgba8,
+}
+
+pub fn get_gl_target(target: TextureTarget) -> gl::GLuint {
+    match target {
+        TextureTarget::Default => gl::TEXTURE_2D,
+        TextureTarget::Array => gl::TEXTURE_2D_ARRAY,
+        TextureTarget::Rect => gl::TEXTURE_RECTANGLE,
+        TextureTarget::External => gl::TEXTURE_EXTERNAL_OES,
+    }
+}
+
+fn supports_extension(extensions: &[String], extension: &str) -> bool {
+    extensions.iter().any(|s| s == extension)
+}
+
+fn get_shader_version(gl: &gl::Gl) -> &'static str {
+    match gl.get_type() {
+        gl::GlType::Gl => SHADER_VERSION_GL,
+        gl::GlType::Gles => SHADER_VERSION_GLES,
+    }
+}
+
+// Get a shader string by name, from the built in resources or
+// an override path, if supplied.
+fn get_shader_source(shader_name: &str, base_path: &Option<PathBuf>) -> Option<String> {
+    if let Some(ref base) = *base_path {
+        let shader_path = base.join(&format!("{}.glsl", shader_name));
+        if shader_path.exists() {
+            let mut source = String::new();
+            File::open(&shader_path)
+                .unwrap()
+                .read_to_string(&mut source)
+                .unwrap();
+            return Some(source);
+        }
+    }
+
+    shader_source::SHADERS
+        .get(shader_name)
+        .map(|s| s.to_string())
+}
+
+// Parse a shader string for imports. Imports are recursively processed, and
+// prepended to the list of outputs.
+fn parse_shader_source(source: String, base_path: &Option<PathBuf>, output: &mut String) {
+    for line in source.lines() {
+        if line.starts_with(SHADER_IMPORT) {
+            let imports = line[SHADER_IMPORT.len() ..].split(',');
+
+            // For each import, get the source, and recurse.
+            for import in imports {
+                if let Some(include) = get_shader_source(import, base_path) {
+                    parse_shader_source(include, base_path, output);
+                }
+            }
+        } else {
+            output.push_str(line);
+            output.push_str("\n");
+        }
+    }
+}
+
+pub fn build_shader_strings(
+    gl_version_string: &str,
+    features: &str,
+    base_filename: &str,
+    override_path: &Option<PathBuf>,
+) -> (String, String) {
+    // Construct a list of strings to be passed to the shader compiler.
+    let mut vs_source = String::new();
+    let mut fs_source = String::new();
+
+    // GLSL requires that the version number comes first.
+    vs_source.push_str(gl_version_string);
+    fs_source.push_str(gl_version_string);
+
+    // Insert the shader name to make debugging easier.
+    let name_string = format!("// {}\n", base_filename);
+    vs_source.push_str(&name_string);
+    fs_source.push_str(&name_string);
+
+    // Define a constant depending on whether we are compiling VS or FS.
+    vs_source.push_str(SHADER_KIND_VERTEX);
+    fs_source.push_str(SHADER_KIND_FRAGMENT);
+
+    // Add any defines that were passed by the caller.
+    vs_source.push_str(features);
+    fs_source.push_str(features);
+
+    // Parse the main .glsl file, including any imports
+    // and append them to the list of sources.
+    let mut shared_result = String::new();
+    if let Some(shared_source) = get_shader_source(base_filename, override_path) {
+        parse_shader_source(shared_source, override_path, &mut shared_result);
+    }
+
+    vs_source.push_str(&shared_result);
+    fs_source.push_str(&shared_result);
+
+    (vs_source, fs_source)
+}
+
+pub trait FileWatcherHandler: Send {
+    fn file_changed(&self, path: PathBuf);
+}
+
+impl VertexAttributeKind {
+    fn size_in_bytes(&self) -> u32 {
+        match *self {
+            VertexAttributeKind::F32 => 4,
+            #[cfg(feature = "debug_renderer")]
+            VertexAttributeKind::U8Norm => 1,
+            VertexAttributeKind::U16Norm => 2,
+            VertexAttributeKind::I32 => 4,
+            VertexAttributeKind::U16 => 2,
+        }
+    }
+}
+
+impl VertexAttribute {
+    fn size_in_bytes(&self) -> u32 {
+        self.count * self.kind.size_in_bytes()
+    }
+
+    fn bind_to_vao(
+        &self,
+        attr_index: gl::GLuint,
+        divisor: gl::GLuint,
+        stride: gl::GLint,
+        offset: gl::GLuint,
+        gl: &gl::Gl,
+    ) {
+        gl.enable_vertex_attrib_array(attr_index);
+        gl.vertex_attrib_divisor(attr_index, divisor);
+
+        match self.kind {
+            VertexAttributeKind::F32 => {
+                gl.vertex_attrib_pointer(
+                    attr_index,
+                    self.count as gl::GLint,
+                    gl::FLOAT,
+                    false,
+                    stride,
+                    offset,
+                );
+            }
+            #[cfg(feature = "debug_renderer")]
+            VertexAttributeKind::U8Norm => {
+                gl.vertex_attrib_pointer(
+                    attr_index,
+                    self.count as gl::GLint,
+                    gl::UNSIGNED_BYTE,
+                    true,
+                    stride,
+                    offset,
+                );
+            }
+            VertexAttributeKind::U16Norm => {
+                gl.vertex_attrib_pointer(
+                    attr_index,
+                    self.count as gl::GLint,
+                    gl::UNSIGNED_SHORT,
+                    true,
+                    stride,
+                    offset,
+                );
+            }
+            VertexAttributeKind::I32 => {
+                gl.vertex_attrib_i_pointer(
+                    attr_index,
+                    self.count as gl::GLint,
+                    gl::INT,
+                    stride,
+                    offset,
+                );
+            }
+            VertexAttributeKind::U16 => {
+                gl.vertex_attrib_i_pointer(
+                    attr_index,
+                    self.count as gl::GLint,
+                    gl::UNSIGNED_SHORT,
+                    stride,
+                    offset,
+                );
+            }
+        }
+    }
+}
+
+impl VertexDescriptor {
+    fn instance_stride(&self) -> u32 {
+        self.instance_attributes
+            .iter()
+            .map(|attr| attr.size_in_bytes())
+            .sum()
+    }
+
+    fn bind_attributes(
+        attributes: &[VertexAttribute],
+        start_index: usize,
+        divisor: u32,
+        gl: &gl::Gl,
+        vbo: VBOId,
+    ) {
+        vbo.bind(gl);
+
+        let stride: u32 = attributes
+            .iter()
+            .map(|attr| attr.size_in_bytes())
+            .sum();
+
+        let mut offset = 0;
+        for (i, attr) in attributes.iter().enumerate() {
+            let attr_index = (start_index + i) as gl::GLuint;
+            attr.bind_to_vao(attr_index, divisor, stride as _, offset, gl);
+            offset += attr.size_in_bytes();
+        }
+    }
+
+    fn bind(&self, gl: &gl::Gl, main: VBOId, instance: VBOId) {
+        Self::bind_attributes(self.vertex_attributes, 0, 0, gl, main);
+
+        if !self.instance_attributes.is_empty() {
+            Self::bind_attributes(
+                self.instance_attributes,
+                self.vertex_attributes.len(),
+                1, gl, instance,
+            );
+        }
+    }
+}
+
+impl VBOId {
+    fn bind(&self, gl: &gl::Gl) {
+        gl.bind_buffer(gl::ARRAY_BUFFER, self.0);
+    }
+}
+
+impl IBOId {
+    fn bind(&self, gl: &gl::Gl) {
+        gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.0);
+    }
+}
+
+impl FBOId {
+    fn bind(&self, gl: &gl::Gl, target: FBOTarget) {
+        let target = match target {
+            FBOTarget::Read => gl::READ_FRAMEBUFFER,
+            FBOTarget::Draw => gl::DRAW_FRAMEBUFFER,
+        };
+        gl.bind_framebuffer(target, self.0);
+    }
+}
+
+pub struct Stream<'a> {
+    attributes: &'a [VertexAttribute],
+    vbo: VBOId,
+}
+
+pub struct VBO<V> {
+    id: gl::GLuint,
+    target: gl::GLenum,
+    allocated_count: usize,
+    marker: PhantomData<V>,
+}
+
+impl<V> VBO<V> {
+    pub fn allocated_count(&self) -> usize {
+        self.allocated_count
+    }
+
+    pub fn stream_with<'a>(&self, attributes: &'a [VertexAttribute]) -> Stream<'a> {
+        debug_assert_eq!(
+            mem::size_of::<V>(),
+            attributes.iter().map(|a| a.size_in_bytes() as usize).sum::<usize>()
+        );
+        Stream {
+            attributes,
+            vbo: VBOId(self.id),
+        }
+    }
+}
+
+impl<T> Drop for VBO<T> {
+    fn drop(&mut self) {
+        debug_assert!(thread::panicking() || self.id == 0);
+    }
+}
+
+#[cfg_attr(feature = "replay", derive(Clone))]
+pub struct ExternalTexture {
+    id: gl::GLuint,
+    target: gl::GLuint,
+}
+
+impl ExternalTexture {
+    pub fn new(id: u32, target: TextureTarget) -> Self {
+        ExternalTexture {
+            id,
+            target: get_gl_target(target),
+        }
+    }
+
+    #[cfg(feature = "replay")]
+    pub fn internal_id(&self) -> gl::GLuint {
+        self.id
+    }
+}
+
+pub struct Texture {
+    id: gl::GLuint,
+    target: gl::GLuint,
+    layer_count: i32,
+    format: ImageFormat,
+    width: u32,
+    height: u32,
+    filter: TextureFilter,
+    render_target: Option<RenderTargetInfo>,
+    fbo_ids: Vec<FBOId>,
+    depth_rb: Option<RBOId>,
+    last_frame_used: FrameId,
+}
+
+impl Texture {
+    pub fn get_dimensions(&self) -> DeviceUintSize {
+        DeviceUintSize::new(self.width, self.height)
+    }
+
+    pub fn get_render_target_layer_count(&self) -> usize {
+        self.fbo_ids.len()
+    }
+
+    pub fn get_layer_count(&self) -> i32 {
+        self.layer_count
+    }
+
+    pub fn get_format(&self) -> ImageFormat {
+        self.format
+    }
+
+    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
+    pub fn get_filter(&self) -> TextureFilter {
+        self.filter
+    }
+
+    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
+    pub fn get_render_target(&self) -> Option<RenderTargetInfo> {
+        self.render_target.clone()
+    }
+
+    pub fn has_depth(&self) -> bool {
+        self.depth_rb.is_some()
+    }
+
+    pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
+        self.render_target.as_ref()
+    }
+
+    pub fn used_in_frame(&self, frame_id: FrameId) -> bool {
+        self.last_frame_used == frame_id
+    }
+
+    #[cfg(feature = "replay")]
+    pub fn into_external(mut self) -> ExternalTexture {
+        let ext = ExternalTexture {
+            id: self.id,
+            target: self.target,
+        };
+        self.id = 0; // don't complain, moved out
+        ext
+    }
+}
+
+impl Drop for Texture {
+    fn drop(&mut self) {
+        debug_assert!(thread::panicking() || self.id == 0);
+    }
+}
+
+pub struct Program {
+    id: gl::GLuint,
+    u_transform: gl::GLint,
+    u_device_pixel_ratio: gl::GLint,
+    u_mode: gl::GLint,
+}
+
+impl Drop for Program {
+    fn drop(&mut self) {
+        debug_assert!(
+            thread::panicking() || self.id == 0,
+            "renderer::deinit not called"
+        );
+    }
+}
+
+pub struct CustomVAO {
+    id: gl::GLuint,
+}
+
+impl Drop for CustomVAO {
+    fn drop(&mut self) {
+        debug_assert!(
+            thread::panicking() || self.id == 0,
+            "renderer::deinit not called"
+        );
+    }
+}
+
+pub struct VAO {
+    id: gl::GLuint,
+    ibo_id: IBOId,
+    main_vbo_id: VBOId,
+    instance_vbo_id: VBOId,
+    instance_stride: usize,
+    owns_vertices_and_indices: bool,
+}
+
+impl Drop for VAO {
+    fn drop(&mut self) {
+        debug_assert!(
+            thread::panicking() || self.id == 0,
+            "renderer::deinit not called"
+        );
+    }
+}
+
+pub struct PBO {
+    id: gl::GLuint,
+}
+
+impl Drop for PBO {
+    fn drop(&mut self) {
+        debug_assert!(
+            thread::panicking() || self.id == 0,
+            "renderer::deinit not called"
+        );
+    }
+}
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct FBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct RBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct VBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+struct IBOId(gl::GLuint);
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
+pub struct ProgramSources {
+    renderer_name: String,
+    vs_source: String,
+    fs_source: String,
+}
+
+impl ProgramSources {
+    fn new(renderer_name: String, vs_source: String, fs_source: String) -> Self {
+        ProgramSources {
+            renderer_name,
+            vs_source,
+            fs_source,
+        }
+    }
+}
+
+#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
+pub struct ProgramBinary {
+    binary: Vec<u8>,
+    format: gl::GLenum,
+    #[cfg(feature = "serialize_program")]
+    sources: ProgramSources,
+}
+
+impl ProgramBinary {
+    #[allow(unused_variables)]
+    fn new(binary: Vec<u8>,
+           format: gl::GLenum,
+           sources: &ProgramSources) -> Self {
+        ProgramBinary {
+            binary,
+            format,
+            #[cfg(feature = "serialize_program")]
+            sources: sources.clone(),
+        }
+    }
+}
+
+/// The interfaces that an application can implement to handle ProgramCache update
+pub trait ProgramCacheObserver {
+    fn notify_binary_added(&self, program_binary: &Arc<ProgramBinary>);
+    fn notify_program_binary_failed(&self, program_binary: &Arc<ProgramBinary>);
+}
+
+pub struct ProgramCache {
+    binaries: RefCell<FastHashMap<ProgramSources, Arc<ProgramBinary>>>,
+
+    /// Optional trait object that allows the client
+    /// application to handle ProgramCache updating
+    program_cache_handler: Option<Box<ProgramCacheObserver>>,
+}
+
+impl ProgramCache {
+    pub fn new(program_cache_observer: Option<Box<ProgramCacheObserver>>) -> Rc<Self> {
+        Rc::new(
+            ProgramCache {
+                binaries: RefCell::new(FastHashMap::default()),
+                program_cache_handler: program_cache_observer,
+            }
+        )
+    }
+    /// Load ProgramBinary to ProgramCache.
+    /// The function is typically used to load ProgramBinary from disk.
+    #[cfg(feature = "serialize_program")]
+    pub fn load_program_binary(&self, program_binary: Arc<ProgramBinary>) {
+        let sources = program_binary.sources.clone();
+        self.binaries.borrow_mut().insert(sources, program_binary);
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum VertexUsageHint {
+    Static,
+    Dynamic,
+    Stream,
+}
+
+impl VertexUsageHint {
+    fn to_gl(&self) -> gl::GLuint {
+        match *self {
+            VertexUsageHint::Static => gl::STATIC_DRAW,
+            VertexUsageHint::Dynamic => gl::DYNAMIC_DRAW,
+            VertexUsageHint::Stream => gl::STREAM_DRAW,
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct UniformLocation(gl::GLint);
+
+impl UniformLocation {
+    pub const INVALID: Self = UniformLocation(-1);
+}
+
+#[cfg(feature = "debug_renderer")]
+pub struct Capabilities {
+    pub supports_multisampling: bool,
+}
+
+#[derive(Clone, Debug)]
+pub enum ShaderError {
+    Compilation(String, String), // name, error message
+    Link(String, String),        // name, error message
+}
+
+pub struct Device {
+    gl: Rc<gl::Gl>,
+    // device state
+    bound_textures: [gl::GLuint; 16],
+    bound_program: gl::GLuint,
+    bound_vao: gl::GLuint,
+    bound_read_fbo: FBOId,
+    bound_draw_fbo: FBOId,
+    program_mode_id: UniformLocation,
+    default_read_fbo: gl::GLuint,
+    default_draw_fbo: gl::GLuint,
+
+    device_pixel_ratio: f32,
+    upload_method: UploadMethod,
+
+    // HW or API capabilities
+    #[cfg(feature = "debug_renderer")]
+    capabilities: Capabilities,
+
+    bgra_format: gl::GLuint,
+
+    // debug
+    inside_frame: bool,
+
+    // resources
+    resource_override_path: Option<PathBuf>,
+
+    max_texture_size: u32,
+    renderer_name: String,
+    cached_programs: Option<Rc<ProgramCache>>,
+
+    // Frame counter. This is used to map between CPU
+    // frames and GPU frames.
+    frame_id: FrameId,
+
+    // GL extensions
+    extensions: Vec<String>,
+}
+
+impl Device {
+    pub fn new(
+        gl: Rc<gl::Gl>,
+        resource_override_path: Option<PathBuf>,
+        upload_method: UploadMethod,
+        _file_changed_handler: Box<FileWatcherHandler>,
+        cached_programs: Option<Rc<ProgramCache>>,
+    ) -> Device {
+        let mut max_texture_size = [0];
+        unsafe {
+            gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
+        }
+        let max_texture_size = max_texture_size[0] as u32;
+        let renderer_name = gl.get_string(gl::RENDERER);
+
+        let mut extension_count = [0];
+        unsafe {
+            gl.get_integer_v(gl::NUM_EXTENSIONS, &mut extension_count);
+        }
+        let extension_count = extension_count[0] as gl::GLuint;
+        let mut extensions = Vec::new();
+        for i in 0 .. extension_count {
+            extensions.push(gl.get_string_i(gl::EXTENSIONS, i));
+        }
+
+        let supports_bgra = supports_extension(&extensions, "GL_EXT_texture_format_BGRA8888");
+        let bgra_format = match gl.get_type() {
+            gl::GlType::Gl => GL_FORMAT_BGRA_GL,
+            gl::GlType::Gles => if supports_bgra {
+                GL_FORMAT_BGRA_GLES
+            } else {
+                GL_FORMAT_RGBA
+            }
+        };
+
+        Device {
+            gl,
+            resource_override_path,
+            // This is initialized to 1 by default, but it is reset
+            // at the beginning of each frame in `Renderer::bind_frame_data`.
+            device_pixel_ratio: 1.0,
+            upload_method,
+            inside_frame: false,
+
+            #[cfg(feature = "debug_renderer")]
+            capabilities: Capabilities {
+                supports_multisampling: false, //TODO
+            },
+
+            bgra_format,
+
+            bound_textures: [0; 16],
+            bound_program: 0,
+            bound_vao: 0,
+            bound_read_fbo: FBOId(0),
+            bound_draw_fbo: FBOId(0),
+            program_mode_id: UniformLocation::INVALID,
+            default_read_fbo: 0,
+            default_draw_fbo: 0,
+
+            max_texture_size,
+            renderer_name,
+            cached_programs,
+            frame_id: FrameId(0),
+            extensions,
+        }
+    }
+
+    pub fn gl(&self) -> &gl::Gl {
+        &*self.gl
+    }
+
+    pub fn rc_gl(&self) -> &Rc<gl::Gl> {
+        &self.gl
+    }
+
+    pub fn set_device_pixel_ratio(&mut self, ratio: f32) {
+        self.device_pixel_ratio = ratio;
+    }
+
+    pub fn update_program_cache(&mut self, cached_programs: Rc<ProgramCache>) {
+        self.cached_programs = Some(cached_programs);
+    }
+
+    pub fn max_texture_size(&self) -> u32 {
+        self.max_texture_size
+    }
+
+    #[cfg(feature = "debug_renderer")]
+    pub fn get_capabilities(&self) -> &Capabilities {
+        &self.capabilities
+    }
+
+    pub fn reset_state(&mut self) {
+        self.bound_textures = [0; 16];
+        self.bound_vao = 0;
+        self.bound_read_fbo = FBOId(0);
+        self.bound_draw_fbo = FBOId(0);
+    }
+
+    #[cfg(debug_assertions)]
+    fn print_shader_errors(source: &str, log: &str) {
+        // hacky way to extract the offending lines
+        if !log.starts_with("0:") {
+            return;
+        }
+        let end_pos = match log[2..].chars().position(|c| !c.is_digit(10)) {
+            Some(pos) => 2 + pos,
+            None => return,
+        };
+        let base_line_number = match log[2 .. end_pos].parse::<usize>() {
+            Ok(number) if number >= 2 => number - 2,
+            _ => return,
+        };
+        for (line, prefix) in source.lines().skip(base_line_number).zip(&["|",">","|"]) {
+            error!("{}\t{}", prefix, line);
+        }
+    }
+
+    pub fn compile_shader(
+        gl: &gl::Gl,
+        name: &str,
+        shader_type: gl::GLenum,
+        source: &String,
+    ) -> Result<gl::GLuint, ShaderError> {
+        debug!("compile {}", name);
+        let id = gl.create_shader(shader_type);
+        gl.shader_source(id, &[source.as_bytes()]);
+        gl.compile_shader(id);
+        let log = gl.get_shader_info_log(id);
+        let mut status = [0];
+        unsafe {
+            gl.get_shader_iv(id, gl::COMPILE_STATUS, &mut status);
+        }
+        if status[0] == 0 {
+            error!("Failed to compile shader: {}\n{}", name, log);
+            #[cfg(debug_assertions)]
+            Self::print_shader_errors(source, &log);
+            Err(ShaderError::Compilation(name.to_string(), log))
+        } else {
+            if !log.is_empty() {
+                warn!("Warnings detected on shader: {}\n{}", name, log);
+            }
+            Ok(id)
+        }
+    }
+
+    pub fn begin_frame(&mut self) -> FrameId {
+        debug_assert!(!self.inside_frame);
+        self.inside_frame = true;
+
+        // Retrieve the currently set FBO.
+        let mut default_read_fbo = [0];
+        unsafe {
+            self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut default_read_fbo);
+        }
+        self.default_read_fbo = default_read_fbo[0] as gl::GLuint;
+        let mut default_draw_fbo = [0];
+        unsafe {
+            self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut default_draw_fbo);
+        }
+        self.default_draw_fbo = default_draw_fbo[0] as gl::GLuint;
+
+        // Texture state
+        for i in 0 .. self.bound_textures.len() {
+            self.bound_textures[i] = 0;
+            self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
+            self.gl.bind_texture(gl::TEXTURE_2D, 0);
+        }
+
+        // Shader state
+        self.bound_program = 0;
+        self.program_mode_id = UniformLocation::INVALID;
+        self.gl.use_program(0);
+
+        // Vertex state
+        self.bound_vao = 0;
+        self.gl.bind_vertex_array(0);
+
+        // FBO state
+        self.bound_read_fbo = FBOId(self.default_read_fbo);
+        self.bound_draw_fbo = FBOId(self.default_draw_fbo);
+
+        // Pixel op state
+        self.gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1);
+        self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+
+        // Default is sampler 0, always
+        self.gl.active_texture(gl::TEXTURE0);
+
+        self.frame_id
+    }
+
+    fn bind_texture_impl(&mut self, slot: TextureSlot, id: gl::GLuint, target: gl::GLenum) {
+        debug_assert!(self.inside_frame);
+
+        if self.bound_textures[slot.0] != id {
+            self.bound_textures[slot.0] = id;
+            self.gl.active_texture(gl::TEXTURE0 + slot.0 as gl::GLuint);
+            self.gl.bind_texture(target, id);
+            self.gl.active_texture(gl::TEXTURE0);
+        }
+    }
+
+    pub fn bind_texture<S>(&mut self, sampler: S, texture: &Texture)
+    where
+        S: Into<TextureSlot>,
+    {
+        self.bind_texture_impl(sampler.into(), texture.id, texture.target);
+    }
+
+    pub fn bind_external_texture<S>(&mut self, sampler: S, external_texture: &ExternalTexture)
+    where
+        S: Into<TextureSlot>,
+    {
+        self.bind_texture_impl(sampler.into(), external_texture.id, external_texture.target);
+    }
+
+    pub fn bind_read_target_impl(&mut self, fbo_id: FBOId) {
+        debug_assert!(self.inside_frame);
+
+        if self.bound_read_fbo != fbo_id {
+            self.bound_read_fbo = fbo_id;
+            fbo_id.bind(self.gl(), FBOTarget::Read);
+        }
+    }
+
+    pub fn bind_read_target(&mut self, texture_and_layer: Option<(&Texture, i32)>) {
+        let fbo_id = texture_and_layer.map_or(FBOId(self.default_read_fbo), |texture_and_layer| {
+            texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
+        });
+
+        self.bind_read_target_impl(fbo_id)
+    }
+
+    fn bind_draw_target_impl(&mut self, fbo_id: FBOId) {
+        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);
+        }
+    }
+
+    pub fn bind_draw_target(
+        &mut self,
+        texture_and_layer: Option<(&Texture, i32)>,
+        dimensions: Option<DeviceUintSize>,
+    ) {
+        let fbo_id = texture_and_layer.map_or(FBOId(self.default_draw_fbo), |texture_and_layer| {
+            texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
+        });
+
+        self.bind_draw_target_impl(fbo_id);
+
+        if let Some(dimensions) = dimensions {
+            self.gl.viewport(
+                0,
+                0,
+                dimensions.width as _,
+                dimensions.height as _,
+            );
+        }
+    }
+
+    pub fn create_fbo_for_external_texture(&mut self, texture_id: u32) -> FBOId {
+        let fbo = FBOId(self.gl.gen_framebuffers(1)[0]);
+        fbo.bind(self.gl(), FBOTarget::Draw);
+        self.gl.framebuffer_texture_2d(
+            gl::DRAW_FRAMEBUFFER,
+            gl::COLOR_ATTACHMENT0,
+            gl::TEXTURE_2D,
+            texture_id,
+            0,
+        );
+        self.bound_draw_fbo.bind(self.gl(), FBOTarget::Draw);
+        fbo
+    }
+
+    pub fn delete_fbo(&mut self, fbo: FBOId) {
+        self.gl.delete_framebuffers(&[fbo.0]);
+    }
+
+    pub fn bind_external_draw_target(&mut self, fbo_id: FBOId) {
+        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);
+        }
+    }
+
+    pub fn bind_program(&mut self, program: &Program) {
+        debug_assert!(self.inside_frame);
+
+        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);
+        }
+    }
+
+    pub fn create_texture(
+        &mut self,
+        target: TextureTarget,
+        format: ImageFormat,
+    ) -> Texture {
+        Texture {
+            id: self.gl.gen_textures(1)[0],
+            target: get_gl_target(target),
+            width: 0,
+            height: 0,
+            layer_count: 0,
+            format,
+            filter: TextureFilter::Nearest,
+            render_target: None,
+            fbo_ids: vec![],
+            depth_rb: None,
+            last_frame_used: self.frame_id,
+        }
+    }
+
+    fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
+        let mag_filter = match filter {
+            TextureFilter::Nearest => gl::NEAREST,
+            TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
+        };
+
+        let min_filter = match filter {
+            TextureFilter::Nearest => gl::NEAREST,
+            TextureFilter::Linear => gl::LINEAR,
+            TextureFilter::Trilinear => gl::LINEAR_MIPMAP_LINEAR,
+        };
+
+        self.gl
+            .tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, mag_filter as gl::GLint);
+        self.gl
+            .tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, min_filter as gl::GLint);
+
+        self.gl
+            .tex_parameter_i(target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
+        self.gl
+            .tex_parameter_i(target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
+    }
+
+    /// Resizes a texture with enabled render target views,
+    /// preserves the data by blitting the old texture contents over.
+    pub fn resize_renderable_texture(
+        &mut self,
+        texture: &mut Texture,
+        new_size: DeviceUintSize,
+    ) {
+        debug_assert!(self.inside_frame);
+
+        let old_size = texture.get_dimensions();
+        let old_fbos = mem::replace(&mut texture.fbo_ids, Vec::new());
+        let old_texture_id = mem::replace(&mut texture.id, self.gl.gen_textures(1)[0]);
+
+        texture.width = new_size.width;
+        texture.height = new_size.height;
+        let rt_info = texture.render_target
+            .clone()
+            .expect("Only renderable textures are expected for resize here");
+
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+        self.set_texture_parameters(texture.target, texture.filter);
+        self.update_target_storage::<u8>(texture, &rt_info, true, None);
+
+        let rect = DeviceIntRect::new(DeviceIntPoint::zero(), old_size.to_i32());
+        for (read_fbo, &draw_fbo) in old_fbos.into_iter().zip(&texture.fbo_ids) {
+            self.bind_read_target_impl(read_fbo);
+            self.bind_draw_target_impl(draw_fbo);
+            self.blit_render_target(rect, rect);
+            self.delete_fbo(read_fbo);
+        }
+        self.gl.delete_textures(&[old_texture_id]);
+        self.bind_read_target(None);
+    }
+
+    pub fn init_texture<T: Texel>(
+        &mut self,
+        texture: &mut Texture,
+        mut width: u32,
+        mut height: u32,
+        filter: TextureFilter,
+        render_target: Option<RenderTargetInfo>,
+        layer_count: i32,
+        pixels: Option<&[T]>,
+    ) {
+        debug_assert!(self.inside_frame);
+
+        if width > self.max_texture_size || height > self.max_texture_size {
+            error!("Attempting to allocate a texture of size {}x{} above the limit, trimming", width, height);
+            width = width.min(self.max_texture_size);
+            height = height.min(self.max_texture_size);
+        }
+
+        let is_resized = texture.width != width || texture.height != height;
+
+        texture.width = width;
+        texture.height = height;
+        texture.filter = filter;
+        texture.layer_count = layer_count;
+        texture.render_target = render_target;
+        texture.last_frame_used = self.frame_id;
+
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+        self.set_texture_parameters(texture.target, filter);
+
+        match render_target {
+            Some(info) => {
+                self.update_target_storage(texture, &info, is_resized, pixels);
+            }
+            None => {
+                self.update_texture_storage(texture, pixels);
+            }
+        }
+    }
+
+    /// Updates the render target storage for the texture, creating FBOs as required.
+    fn update_target_storage<T: Texel>(
+        &mut self,
+        texture: &mut Texture,
+        rt_info: &RenderTargetInfo,
+        is_resized: bool,
+        pixels: Option<&[T]>,
+    ) {
+        assert!(texture.layer_count > 0 || texture.width + texture.height == 0);
+
+        let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
+        let allocate_color = needed_layer_count != 0 || is_resized || pixels.is_some();
+
+        if allocate_color {
+            let desc = self.gl_describe_format(texture.format);
+            match texture.target {
+                gl::TEXTURE_2D_ARRAY => {
+                    self.gl.tex_image_3d(
+                        texture.target,
+                        0,
+                        desc.internal,
+                        texture.width as _,
+                        texture.height as _,
+                        texture.layer_count,
+                        0,
+                        desc.external,
+                        desc.pixel_type,
+                        pixels.map(texels_to_u8_slice),
+                    )
+                }
+                _ => {
+                    assert_eq!(texture.layer_count, 1);
+                    self.gl.tex_image_2d(
+                        texture.target,
+                        0,
+                        desc.internal,
+                        texture.width as _,
+                        texture.height as _,
+                        0,
+                        desc.external,
+                        desc.pixel_type,
+                        pixels.map(texels_to_u8_slice),
+                    )
+                }
+            }
+        }
+
+        if needed_layer_count > 0 {
+            // Create more framebuffers to fill the gap
+            let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
+            texture
+                .fbo_ids
+                .extend(new_fbos.into_iter().map(FBOId));
+        } else if needed_layer_count < 0 {
+            // Remove extra framebuffers
+            for old in texture.fbo_ids.drain(texture.layer_count as usize ..) {
+                self.gl.delete_framebuffers(&[old.0]);
+            }
+        }
+
+        let (mut depth_rb, allocate_depth) = match texture.depth_rb {
+            Some(rbo) => (rbo.0, is_resized || !rt_info.has_depth),
+            None if rt_info.has_depth => {
+                let renderbuffer_ids = self.gl.gen_renderbuffers(1);
+                let depth_rb = renderbuffer_ids[0];
+                texture.depth_rb = Some(RBOId(depth_rb));
+                (depth_rb, true)
+            },
+            None => (0, false),
+        };
+
+        if allocate_depth {
+            if rt_info.has_depth {
+                self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
+                self.gl.renderbuffer_storage(
+                    gl::RENDERBUFFER,
+                    gl::DEPTH_COMPONENT24,
+                    texture.width as _,
+                    texture.height as _,
+                );
+            } else {
+                self.gl.delete_renderbuffers(&[depth_rb]);
+                depth_rb = 0;
+                texture.depth_rb = None;
+            }
+        }
+
+        if allocate_color || allocate_depth {
+            let original_bound_fbo = self.bound_draw_fbo;
+            for (fbo_index, &fbo_id) in texture.fbo_ids.iter().enumerate() {
+                self.bind_external_draw_target(fbo_id);
+                match texture.target {
+                    gl::TEXTURE_2D_ARRAY => {
+                        self.gl.framebuffer_texture_layer(
+                            gl::DRAW_FRAMEBUFFER,
+                            gl::COLOR_ATTACHMENT0,
+                            texture.id,
+                            0,
+                            fbo_index as _,
+                        )
+                    }
+                    _ => {
+                        assert_eq!(fbo_index, 0);
+                        self.gl.framebuffer_texture_2d(
+                            gl::DRAW_FRAMEBUFFER,
+                            gl::COLOR_ATTACHMENT0,
+                            texture.target,
+                            texture.id,
+                            0,
+                        )
+                    }
+                }
+
+                self.gl.framebuffer_renderbuffer(
+                    gl::DRAW_FRAMEBUFFER,
+                    gl::DEPTH_ATTACHMENT,
+                    gl::RENDERBUFFER,
+                    depth_rb,
+                );
+            }
+            self.bind_external_draw_target(original_bound_fbo);
+        }
+    }
+
+    fn update_texture_storage<T: Texel>(&mut self, texture: &Texture, pixels: Option<&[T]>) {
+        let desc = self.gl_describe_format(texture.format);
+        match texture.target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_image_3d(
+                    gl::TEXTURE_2D_ARRAY,
+                    0,
+                    desc.internal,
+                    texture.width as _,
+                    texture.height as _,
+                    texture.layer_count,
+                    0,
+                    desc.external,
+                    desc.pixel_type,
+                    pixels.map(texels_to_u8_slice),
+                );
+            }
+            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
+                self.gl.tex_image_2d(
+                    texture.target,
+                    0,
+                    desc.internal,
+                    texture.width as _,
+                    texture.height as _,
+                    0,
+                    desc.external,
+                    desc.pixel_type,
+                    pixels.map(texels_to_u8_slice),
+                );
+            }
+            _ => panic!("BUG: Unexpected texture target!"),
+        }
+    }
+
+    pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
+        debug_assert!(self.inside_frame);
+
+        self.gl.blit_framebuffer(
+            src_rect.origin.x,
+            src_rect.origin.y,
+            src_rect.origin.x + src_rect.size.width,
+            src_rect.origin.y + src_rect.size.height,
+            dest_rect.origin.x,
+            dest_rect.origin.y,
+            dest_rect.origin.x + dest_rect.size.width,
+            dest_rect.origin.y + dest_rect.size.height,
+            gl::COLOR_BUFFER_BIT,
+            gl::LINEAR,
+        );
+    }
+
+    fn free_texture_storage_impl(&mut self, target: gl::GLenum, desc: FormatDesc) {
+        match target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_image_3d(
+                    gl::TEXTURE_2D_ARRAY,
+                    0,
+                    desc.internal,
+                    0,
+                    0,
+                    0,
+                    0,
+                    desc.external,
+                    desc.pixel_type,
+                    None,
+                );
+            }
+            _ => {
+                self.gl.tex_image_2d(
+                    target,
+                    0,
+                    desc.internal,
+                    0,
+                    0,
+                    0,
+                    desc.external,
+                    desc.pixel_type,
+                    None,
+                );
+            }
+        }
+    }
+
+    pub fn free_texture_storage(&mut self, texture: &mut Texture) {
+        debug_assert!(self.inside_frame);
+
+        if texture.width + texture.height == 0 {
+            return;
+        }
+
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+        let desc = self.gl_describe_format(texture.format);
+
+        self.free_texture_storage_impl(texture.target, desc);
+
+        if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
+            self.gl.delete_renderbuffers(&[depth_rb]);
+        }
+
+        if !texture.fbo_ids.is_empty() {
+            let fbo_ids: Vec<_> = texture
+                .fbo_ids
+                .drain(..)
+                .map(|FBOId(fbo_id)| fbo_id)
+                .collect();
+            self.gl.delete_framebuffers(&fbo_ids[..]);
+        }
+
+        texture.width = 0;
+        texture.height = 0;
+        texture.layer_count = 0;
+    }
+
+    pub fn delete_texture(&mut self, mut texture: Texture) {
+        self.free_texture_storage(&mut texture);
+        self.gl.delete_textures(&[texture.id]);
+
+        for bound_texture in &mut self.bound_textures {
+            if *bound_texture == texture.id {
+                *bound_texture = 0
+            }
+        }
+
+        texture.id = 0;
+    }
+
+    #[cfg(feature = "replay")]
+    pub fn delete_external_texture(&mut self, mut external: ExternalTexture) {
+        self.bind_external_texture(DEFAULT_TEXTURE, &external);
+        //Note: the format descriptor here doesn't really matter
+        self.free_texture_storage_impl(external.target, FormatDesc {
+            internal: gl::R8 as _,
+            external: gl::RED,
+            pixel_type: gl::UNSIGNED_BYTE,
+        });
+        self.gl.delete_textures(&[external.id]);
+        external.id = 0;
+    }
+
+    pub fn delete_program(&mut self, mut program: Program) {
+        self.gl.delete_program(program.id);
+        program.id = 0;
+    }
+
+    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,
+            base_filename,
+            &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;
+
+        if let Some(ref cached_programs) = self.cached_programs {
+            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);
+                }
+            }
+        }
+
+        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,
+        };
+
+        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,
+    {
+        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);
+            }
+        }
+    }
+
+    #[cfg(feature = "debug_renderer")]
+    pub fn get_uniform_location(&self, program: &Program, name: &str) -> UniformLocation {
+        UniformLocation(self.gl.get_uniform_location(program.id, name))
+    }
+
+    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 {
+        let id = self.gl.gen_buffers(1)[0];
+        PBO { id }
+    }
+
+    pub fn delete_pbo(&mut self, mut pbo: PBO) {
+        self.gl.delete_buffers(&[pbo.id]);
+        pbo.id = 0;
+    }
+
+    pub fn upload_texture<'a, T>(
+        &'a mut self,
+        texture: &'a Texture,
+        pbo: &PBO,
+        upload_count: usize,
+    ) -> TextureUploader<'a, T> {
+        debug_assert!(self.inside_frame);
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+
+        let buffer = match self.upload_method {
+            UploadMethod::Immediate => None,
+            UploadMethod::PixelBuffer(hint) => {
+                let upload_size = upload_count * mem::size_of::<T>();
+                self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo.id);
+                if upload_size != 0 {
+                    self.gl.buffer_data_untyped(
+                        gl::PIXEL_UNPACK_BUFFER,
+                        upload_size as _,
+                        ptr::null(),
+                        hint.to_gl(),
+                    );
+                }
+                Some(PixelBuffer::new(hint.to_gl(), upload_size))
+            },
+        };
+
+        TextureUploader {
+            target: UploadTarget {
+                gl: &*self.gl,
+                bgra_format: self.bgra_format,
+                texture,
+            },
+            buffer,
+            marker: PhantomData,
+        }
+    }
+
+    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
+    pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec<u8> {
+        let desc = self.gl_describe_format(img_desc.format);
+        self.gl.read_pixels(
+            0, 0,
+            img_desc.size.width as i32,
+            img_desc.size.height as i32,
+            desc.external,
+            desc.pixel_type,
+        )
+    }
+
+    /// Read rectangle of pixels into the specified output slice.
+    pub fn read_pixels_into(
+        &mut self,
+        rect: DeviceUintRect,
+        format: ReadPixelsFormat,
+        output: &mut [u8],
+    ) {
+        let (bytes_per_pixel, desc) = match format {
+            ReadPixelsFormat::Standard(imf) => {
+                (imf.bytes_per_pixel(), self.gl_describe_format(imf))
+            }
+            ReadPixelsFormat::Rgba8 => {
+                (4, FormatDesc {
+                    external: gl::RGBA,
+                    internal: gl::RGBA8 as _,
+                    pixel_type: gl::UNSIGNED_BYTE,
+                })
+            }
+        };
+        let size_in_bytes = (bytes_per_pixel * rect.size.width * rect.size.height) as usize;
+        assert_eq!(output.len(), size_in_bytes);
+
+        self.gl.flush();
+        self.gl.read_pixels_into_buffer(
+            rect.origin.x as _,
+            rect.origin.y as _,
+            rect.size.width as _,
+            rect.size.height as _,
+            desc.external,
+            desc.pixel_type,
+            output,
+        );
+    }
+
+    /// Get texels of a texture into the specified output slice.
+    #[cfg(feature = "debug_renderer")]
+    pub fn get_tex_image_into(
+        &mut self,
+        texture: &Texture,
+        format: ImageFormat,
+        output: &mut [u8],
+    ) {
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+        let desc = self.gl_describe_format(format);
+        self.gl.get_tex_image_into_buffer(
+            texture.target,
+            0,
+            desc.external,
+            desc.pixel_type,
+            output,
+        );
+    }
+
+    /// Attaches the provided texture to the current Read FBO binding.
+    #[cfg(any(feature = "debug_renderer", feature="capture"))]
+    fn attach_read_texture_raw(
+        &mut self, texture_id: gl::GLuint, target: gl::GLuint, layer_id: i32
+    ) {
+        match target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.framebuffer_texture_layer(
+                    gl::READ_FRAMEBUFFER,
+                    gl::COLOR_ATTACHMENT0,
+                    texture_id,
+                    0,
+                    layer_id,
+                )
+            }
+            _ => {
+                assert_eq!(layer_id, 0);
+                self.gl.framebuffer_texture_2d(
+                    gl::READ_FRAMEBUFFER,
+                    gl::COLOR_ATTACHMENT0,
+                    target,
+                    texture_id,
+                    0,
+                )
+            }
+        }
+    }
+
+    #[cfg(any(feature = "debug_renderer", feature="capture"))]
+    pub fn attach_read_texture_external(
+        &mut self, texture_id: gl::GLuint, target: TextureTarget, layer_id: i32
+    ) {
+        self.attach_read_texture_raw(texture_id, get_gl_target(target), layer_id)
+    }
+
+    #[cfg(any(feature = "debug_renderer", feature="capture"))]
+    pub fn attach_read_texture(&mut self, texture: &Texture, layer_id: i32) {
+        self.attach_read_texture_raw(texture.id, texture.target, layer_id)
+    }
+
+    fn bind_vao_impl(&mut self, id: gl::GLuint) {
+        debug_assert!(self.inside_frame);
+
+        if self.bound_vao != id {
+            self.bound_vao = id;
+            self.gl.bind_vertex_array(id);
+        }
+    }
+
+    pub fn bind_vao(&mut self, vao: &VAO) {
+        self.bind_vao_impl(vao.id)
+    }
+
+    pub fn bind_custom_vao(&mut self, vao: &CustomVAO) {
+        self.bind_vao_impl(vao.id)
+    }
+
+    fn create_vao_with_vbos(
+        &mut self,
+        descriptor: &VertexDescriptor,
+        main_vbo_id: VBOId,
+        instance_vbo_id: VBOId,
+        ibo_id: IBOId,
+        owns_vertices_and_indices: bool,
+    ) -> VAO {
+        debug_assert!(self.inside_frame);
+
+        let instance_stride = descriptor.instance_stride() as usize;
+        let vao_id = self.gl.gen_vertex_arrays(1)[0];
+
+        self.gl.bind_vertex_array(vao_id);
+
+        descriptor.bind(self.gl(), main_vbo_id, instance_vbo_id);
+        ibo_id.bind(self.gl()); // force it to be a part of VAO
+
+        self.gl.bind_vertex_array(0);
+
+        VAO {
+            id: vao_id,
+            ibo_id,
+            main_vbo_id,
+            instance_vbo_id,
+            instance_stride,
+            owns_vertices_and_indices,
+        }
+    }
+
+    pub fn create_custom_vao(
+        &mut self,
+        streams: &[Stream],
+    ) -> CustomVAO {
+        debug_assert!(self.inside_frame);
+
+        let vao_id = self.gl.gen_vertex_arrays(1)[0];
+        self.gl.bind_vertex_array(vao_id);
+
+        let mut attrib_index = 0;
+        for stream in streams {
+            VertexDescriptor::bind_attributes(
+                stream.attributes,
+                attrib_index,
+                0,
+                self.gl(),
+                stream.vbo,
+            );
+            attrib_index += stream.attributes.len();
+        }
+
+        self.gl.bind_vertex_array(0);
+
+        CustomVAO {
+            id: vao_id,
+        }
+    }
+
+    pub fn delete_custom_vao(&mut self, mut vao: CustomVAO) {
+        self.gl.delete_vertex_arrays(&[vao.id]);
+        vao.id = 0;
+    }
+
+    pub fn create_vbo<T>(&mut self) -> VBO<T> {
+        let ids = self.gl.gen_buffers(1);
+        VBO {
+            id: ids[0],
+            target: gl::ARRAY_BUFFER,
+            allocated_count: 0,
+            marker: PhantomData,
+        }
+    }
+
+    pub fn delete_vbo<T>(&mut self, mut vbo: VBO<T>) {
+        self.gl.delete_buffers(&[vbo.id]);
+        vbo.id = 0;
+    }
+
+    pub fn create_vao(&mut self, descriptor: &VertexDescriptor) -> VAO {
+        debug_assert!(self.inside_frame);
+
+        let buffer_ids = self.gl.gen_buffers(3);
+        let ibo_id = IBOId(buffer_ids[0]);
+        let main_vbo_id = VBOId(buffer_ids[1]);
+        let intance_vbo_id = VBOId(buffer_ids[2]);
+
+        self.create_vao_with_vbos(descriptor, main_vbo_id, intance_vbo_id, ibo_id, true)
+    }
+
+    pub fn delete_vao(&mut self, mut vao: VAO) {
+        self.gl.delete_vertex_arrays(&[vao.id]);
+        vao.id = 0;
+
+        if vao.owns_vertices_and_indices {
+            self.gl.delete_buffers(&[vao.ibo_id.0]);
+            self.gl.delete_buffers(&[vao.main_vbo_id.0]);
+        }
+
+        self.gl.delete_buffers(&[vao.instance_vbo_id.0])
+    }
+
+    pub fn allocate_vbo<V>(
+        &mut self,
+        vbo: &mut VBO<V>,
+        count: usize,
+        usage_hint: VertexUsageHint,
+    ) {
+        debug_assert!(self.inside_frame);
+        vbo.allocated_count = count;
+
+        self.gl.bind_buffer(vbo.target, vbo.id);
+        self.gl.buffer_data_untyped(
+            vbo.target,
+            (count * mem::size_of::<V>()) as _,
+            ptr::null(),
+            usage_hint.to_gl(),
+        );
+    }
+
+    pub fn fill_vbo<V>(
+        &mut self,
+        vbo: &VBO<V>,
+        data: &[V],
+        offset: usize,
+    ) {
+        debug_assert!(self.inside_frame);
+        assert!(offset + data.len() <= vbo.allocated_count);
+        let stride = mem::size_of::<V>();
+
+        self.gl.bind_buffer(vbo.target, vbo.id);
+        self.gl.buffer_sub_data_untyped(
+            vbo.target,
+            (offset * stride) as _,
+            (data.len() * stride) as _,
+            data.as_ptr() as _,
+        );
+    }
+
+    fn update_vbo_data<V>(
+        &mut self,
+        vbo: VBOId,
+        vertices: &[V],
+        usage_hint: VertexUsageHint,
+    ) {
+        debug_assert!(self.inside_frame);
+
+        vbo.bind(self.gl());
+        gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, vertices, usage_hint.to_gl());
+    }
+
+    pub fn create_vao_with_new_instances(
+        &mut self,
+        descriptor: &VertexDescriptor,
+        base_vao: &VAO,
+    ) -> VAO {
+        debug_assert!(self.inside_frame);
+
+        let buffer_ids = self.gl.gen_buffers(1);
+        let intance_vbo_id = VBOId(buffer_ids[0]);
+
+        self.create_vao_with_vbos(
+            descriptor,
+            base_vao.main_vbo_id,
+            intance_vbo_id,
+            base_vao.ibo_id,
+            false,
+        )
+    }
+
+    pub fn update_vao_main_vertices<V>(
+        &mut self,
+        vao: &VAO,
+        vertices: &[V],
+        usage_hint: VertexUsageHint,
+    ) {
+        debug_assert_eq!(self.bound_vao, vao.id);
+        self.update_vbo_data(vao.main_vbo_id, vertices, usage_hint)
+    }
+
+    pub fn update_vao_instances<V>(
+        &mut self,
+        vao: &VAO,
+        instances: &[V],
+        usage_hint: VertexUsageHint,
+    ) {
+        debug_assert_eq!(self.bound_vao, vao.id);
+        debug_assert_eq!(vao.instance_stride as usize, mem::size_of::<V>());
+
+        self.update_vbo_data(vao.instance_vbo_id, instances, usage_hint)
+    }
+
+    pub fn update_vao_indices<I>(&mut self, vao: &VAO, indices: &[I], usage_hint: VertexUsageHint) {
+        debug_assert!(self.inside_frame);
+        debug_assert_eq!(self.bound_vao, vao.id);
+
+        vao.ibo_id.bind(self.gl());
+        gl::buffer_data(
+            self.gl(),
+            gl::ELEMENT_ARRAY_BUFFER,
+            indices,
+            usage_hint.to_gl(),
+        );
+    }
+
+    pub fn draw_triangles_u16(&mut self, first_vertex: i32, index_count: i32) {
+        debug_assert!(self.inside_frame);
+        self.gl.draw_elements(
+            gl::TRIANGLES,
+            index_count,
+            gl::UNSIGNED_SHORT,
+            first_vertex as u32 * 2,
+        );
+    }
+
+    #[cfg(feature = "debug_renderer")]
+    pub fn draw_triangles_u32(&mut self, first_vertex: i32, index_count: i32) {
+        debug_assert!(self.inside_frame);
+        self.gl.draw_elements(
+            gl::TRIANGLES,
+            index_count,
+            gl::UNSIGNED_INT,
+            first_vertex as u32 * 4,
+        );
+    }
+
+    pub fn draw_nonindexed_points(&mut self, first_vertex: i32, vertex_count: i32) {
+        debug_assert!(self.inside_frame);
+        self.gl.draw_arrays(gl::POINTS, first_vertex, vertex_count);
+    }
+
+    #[cfg(feature = "debug_renderer")]
+    pub fn draw_nonindexed_lines(&mut self, first_vertex: i32, vertex_count: i32) {
+        debug_assert!(self.inside_frame);
+        self.gl.draw_arrays(gl::LINES, first_vertex, vertex_count);
+    }
+
+    pub fn draw_indexed_triangles_instanced_u16(&mut self, index_count: i32, instance_count: i32) {
+        debug_assert!(self.inside_frame);
+        self.gl.draw_elements_instanced(
+            gl::TRIANGLES,
+            index_count,
+            gl::UNSIGNED_SHORT,
+            0,
+            instance_count,
+        );
+    }
+
+    pub fn end_frame(&mut self) {
+        self.bind_draw_target(None, None);
+        self.bind_read_target(None);
+
+        debug_assert!(self.inside_frame);
+        self.inside_frame = false;
+
+        self.gl.bind_texture(gl::TEXTURE_2D, 0);
+        self.gl.use_program(0);
+
+        for i in 0 .. self.bound_textures.len() {
+            self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
+            self.gl.bind_texture(gl::TEXTURE_2D, 0);
+        }
+
+        self.gl.active_texture(gl::TEXTURE0);
+
+        self.frame_id.0 += 1;
+    }
+
+    pub fn clear_target(
+        &self,
+        color: Option<[f32; 4]>,
+        depth: Option<f32>,
+        rect: Option<DeviceIntRect>,
+    ) {
+        let mut clear_bits = 0;
+
+        if let Some(color) = color {
+            self.gl.clear_color(color[0], color[1], color[2], color[3]);
+            clear_bits |= gl::COLOR_BUFFER_BIT;
+        }
+
+        if let Some(depth) = depth {
+            if cfg!(debug_assertions) {
+                let mut mask = [0];
+                unsafe {
+                    self.gl.get_boolean_v(gl::DEPTH_WRITEMASK, &mut mask);
+                }
+                assert_ne!(mask[0], 0);
+            }
+            self.gl.clear_depth(depth as f64);
+            clear_bits |= gl::DEPTH_BUFFER_BIT;
+        }
+
+        if clear_bits != 0 {
+            match rect {
+                Some(rect) => {
+                    self.gl.enable(gl::SCISSOR_TEST);
+                    self.gl.scissor(
+                        rect.origin.x,
+                        rect.origin.y,
+                        rect.size.width,
+                        rect.size.height,
+                    );
+                    self.gl.clear(clear_bits);
+                    self.gl.disable(gl::SCISSOR_TEST);
+                }
+                None => {
+                    self.gl.clear(clear_bits);
+                }
+            }
+        }
+    }
+
+    pub fn enable_depth(&self) {
+        self.gl.enable(gl::DEPTH_TEST);
+    }
+
+    pub fn disable_depth(&self) {
+        self.gl.disable(gl::DEPTH_TEST);
+    }
+
+    pub fn set_depth_func(&self, depth_func: DepthFunction) {
+        self.gl.depth_func(depth_func as gl::GLuint);
+    }
+
+    pub fn enable_depth_write(&self) {
+        self.gl.depth_mask(true);
+    }
+
+    pub fn disable_depth_write(&self) {
+        self.gl.depth_mask(false);
+    }
+
+    pub fn disable_stencil(&self) {
+        self.gl.disable(gl::STENCIL_TEST);
+    }
+
+    pub fn set_scissor_rect(&self, rect: DeviceIntRect) {
+        self.gl.scissor(
+            rect.origin.x,
+            rect.origin.y,
+            rect.size.width,
+            rect.size.height,
+        );
+    }
+
+    pub fn enable_scissor(&self) {
+        self.gl.enable(gl::SCISSOR_TEST);
+    }
+
+    pub fn disable_scissor(&self) {
+        self.gl.disable(gl::SCISSOR_TEST);
+    }
+
+    pub fn set_blend(&self, enable: bool) {
+        if enable {
+            self.gl.enable(gl::BLEND);
+        } else {
+            self.gl.disable(gl::BLEND);
+        }
+    }
+
+    pub fn set_blend_mode_alpha(&self) {
+        self.gl.blend_func_separate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA,
+                                    gl::ONE, gl::ONE);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+
+    pub fn set_blend_mode_premultiplied_alpha(&self) {
+        self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+
+    pub fn set_blend_mode_premultiplied_dest_out(&self) {
+        self.gl.blend_func(gl::ZERO, gl::ONE_MINUS_SRC_ALPHA);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+
+    pub fn set_blend_mode_multiply(&self) {
+        self.gl
+            .blend_func_separate(gl::ZERO, gl::SRC_COLOR, gl::ZERO, gl::SRC_ALPHA);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_max(&self) {
+        self.gl
+            .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
+        self.gl.blend_equation_separate(gl::MAX, gl::FUNC_ADD);
+    }
+    #[cfg(feature = "debug_renderer")]
+    pub fn set_blend_mode_min(&self) {
+        self.gl
+            .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
+        self.gl.blend_equation_separate(gl::MIN, gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_pass0(&self) {
+        self.gl.blend_func(gl::ZERO, gl::ONE_MINUS_SRC_COLOR);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_pass1(&self) {
+        self.gl.blend_func(gl::ONE, gl::ONE);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) {
+        self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
+        self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
+        self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_constant_text_color(&self, color: ColorF) {
+        // color is an unpremultiplied color.
+        self.gl.blend_color(color.r, color.g, color.b, 1.0);
+        self.gl
+            .blend_func(gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+    pub fn set_blend_mode_subpixel_dual_source(&self) {
+        self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC1_COLOR);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
+
+    pub fn supports_extension(&self, extension: &str) -> bool {
+        supports_extension(&self.extensions, extension)
+    }
+
+    pub fn echo_driver_messages(&self) {
+        for msg in self.gl.get_debug_messages() {
+            let level = match msg.severity {
+                gl::DEBUG_SEVERITY_HIGH => Level::Error,
+                gl::DEBUG_SEVERITY_MEDIUM => Level::Warn,
+                gl::DEBUG_SEVERITY_LOW => Level::Info,
+                gl::DEBUG_SEVERITY_NOTIFICATION => Level::Debug,
+                _ => Level::Trace,
+            };
+            let ty = match msg.ty {
+                gl::DEBUG_TYPE_ERROR => "error",
+                gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "deprecated",
+                gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "undefined",
+                gl::DEBUG_TYPE_PORTABILITY => "portability",
+                gl::DEBUG_TYPE_PERFORMANCE => "perf",
+                gl::DEBUG_TYPE_MARKER => "marker",
+                gl::DEBUG_TYPE_PUSH_GROUP => "group push",
+                gl::DEBUG_TYPE_POP_GROUP => "group pop",
+                gl::DEBUG_TYPE_OTHER => "other",
+                _ => "?",
+            };
+            log!(level, "({}) {}", ty, msg.message);
+        }
+    }
+
+    fn gl_describe_format(&self, format: ImageFormat) -> FormatDesc {
+        match format {
+            ImageFormat::R8 => FormatDesc {
+                internal: gl::RED as _,
+                external: gl::RED,
+                pixel_type: gl::UNSIGNED_BYTE,
+            },
+            ImageFormat::BGRA8 => {
+                let external = self.bgra_format;
+                FormatDesc {
+                    internal: match self.gl.get_type() {
+                        gl::GlType::Gl => gl::RGBA as _,
+                        gl::GlType::Gles => external as _,
+                    },
+                    external,
+                    pixel_type: gl::UNSIGNED_BYTE,
+                }
+            },
+            ImageFormat::RGBAF32 => FormatDesc {
+                internal: gl::RGBA32F as _,
+                external: gl::RGBA,
+                pixel_type: gl::FLOAT,
+            },
+            ImageFormat::RG8 => FormatDesc {
+                internal: gl::RG8 as _,
+                external: gl::RG,
+                pixel_type: gl::UNSIGNED_BYTE,
+            },
+        }
+    }
+}
+
+struct FormatDesc {
+    internal: gl::GLint,
+    external: gl::GLuint,
+    pixel_type: gl::GLuint,
+}
+
+struct UploadChunk {
+    rect: DeviceUintRect,
+    layer_index: i32,
+    stride: Option<u32>,
+    offset: usize,
+}
+
+struct PixelBuffer {
+    usage: gl::GLenum,
+    size_allocated: usize,
+    size_used: usize,
+    // small vector avoids heap allocation for a single chunk
+    chunks: SmallVec<[UploadChunk; 1]>,
+}
+
+impl PixelBuffer {
+    fn new(
+        usage: gl::GLenum,
+        size_allocated: usize,
+    ) -> Self {
+        PixelBuffer {
+            usage,
+            size_allocated,
+            size_used: 0,
+            chunks: SmallVec::new(),
+        }
+    }
+}
+
+struct UploadTarget<'a> {
+    gl: &'a gl::Gl,
+    bgra_format: gl::GLuint,
+    texture: &'a Texture,
+}
+
+pub struct TextureUploader<'a, T> {
+    target: UploadTarget<'a>,
+    buffer: Option<PixelBuffer>,
+    marker: PhantomData<T>,
+}
+
+impl<'a, T> Drop for TextureUploader<'a, T> {
+    fn drop(&mut self) {
+        if let Some(buffer) = self.buffer.take() {
+            for chunk in buffer.chunks {
+                self.target.update_impl(chunk);
+            }
+            self.target.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+        }
+    }
+}
+
+impl<'a, T> TextureUploader<'a, T> {
+    pub fn upload(
+        &mut self,
+        rect: DeviceUintRect,
+        layer_index: i32,
+        stride: Option<u32>,
+        data: &[T],
+    ) {
+        match self.buffer {
+            Some(ref mut buffer) => {
+                let upload_size = mem::size_of::<T>() * data.len();
+                if buffer.size_used + upload_size > buffer.size_allocated {
+                    // flush
+                    for chunk in buffer.chunks.drain() {
+                        self.target.update_impl(chunk);
+                    }
+                    buffer.size_used = 0;
+                }
+
+                if upload_size > buffer.size_allocated {
+                    gl::buffer_data(
+                        self.target.gl,
+                        gl::PIXEL_UNPACK_BUFFER,
+                        data,
+                        buffer.usage,
+                    );
+                    buffer.size_allocated = upload_size;
+                } else {
+                    gl::buffer_sub_data(
+                        self.target.gl,
+                        gl::PIXEL_UNPACK_BUFFER,
+                        buffer.size_used as _,
+                        data,
+                    );
+                }
+
+                buffer.chunks.push(UploadChunk {
+                    rect, layer_index, stride,
+                    offset: buffer.size_used,
+                });
+                buffer.size_used += upload_size;
+            }
+            None => {
+                self.target.update_impl(UploadChunk {
+                    rect, layer_index, stride,
+                    offset: data.as_ptr() as _,
+                });
+            }
+        }
+    }
+}
+
+impl<'a> UploadTarget<'a> {
+    fn update_impl(&mut self, chunk: UploadChunk) {
+        let (gl_format, bpp, data_type) = match self.texture.format {
+            ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
+            ImageFormat::BGRA8 => (self.bgra_format, 4, gl::UNSIGNED_BYTE),
+            ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
+            ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
+        };
+
+        let row_length = match chunk.stride {
+            Some(value) => value / bpp,
+            None => self.texture.width,
+        };
+
+        if chunk.stride.is_some() {
+            self.gl.pixel_store_i(
+                gl::UNPACK_ROW_LENGTH,
+                row_length as _,
+            );
+        }
+
+        let pos = chunk.rect.origin;
+        let size = chunk.rect.size;
+
+        match self.texture.target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_sub_image_3d_pbo(
+                    self.texture.target,
+                    0,
+                    pos.x as _,
+                    pos.y as _,
+                    chunk.layer_index,
+                    size.width as _,
+                    size.height as _,
+                    1,
+                    gl_format,
+                    data_type,
+                    chunk.offset,
+                );
+            }
+            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
+                self.gl.tex_sub_image_2d_pbo(
+                    self.texture.target,
+                    0,
+                    pos.x as _,
+                    pos.y as _,
+                    size.width as _,
+                    size.height as _,
+                    gl_format,
+                    data_type,
+                    chunk.offset,
+                );
+            }
+            _ => panic!("BUG: Unexpected texture target!"),
+        }
+
+        // If using tri-linear filtering, build the mip-map chain for this texture.
+        if self.texture.filter == TextureFilter::Trilinear {
+            self.gl.generate_mipmap(self.texture.target);
+        }
+
+        // Reset row length to 0, otherwise the stride would apply to all texture uploads.
+        if chunk.stride.is_some() {
+            self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _);
+        }
+    }
+}
+
+fn texels_to_u8_slice<T: Texel>(texels: &[T]) -> &[u8] {
+    unsafe {
+        slice::from_raw_parts(texels.as_ptr() as *const u8, texels.len() * mem::size_of::<T>())
+    }
+}
deleted file mode 100644
--- a/gfx/webrender/src/device/gl.rs
+++ /dev/null
@@ -1,2434 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use super::super::shader_source;
-use api::{ColorF, ImageFormat};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
-use api::TextureTarget;
-#[cfg(any(feature = "debug_renderer", feature="capture"))]
-use api::ImageDescriptor;
-use euclid::Transform3D;
-use gleam::gl;
-use internal_types::{FastHashMap, RenderTargetInfo};
-use log::Level;
-use smallvec::SmallVec;
-use std::cell::RefCell;
-use std::fs::File;
-use std::io::Read;
-use std::marker::PhantomData;
-use std::mem;
-use std::ops::Add;
-use std::path::PathBuf;
-use std::ptr;
-use std::rc::Rc;
-use std::slice;
-use std::sync::Arc;
-use std::thread;
-
-#[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FrameId(usize);
-
-impl FrameId {
-    pub fn new(value: usize) -> Self {
-        FrameId(value)
-    }
-}
-
-impl Add<usize> for FrameId {
-    type Output = FrameId;
-
-    fn add(self, other: usize) -> FrameId {
-        FrameId(self.0 + other)
-    }
-}
-
-const GL_FORMAT_RGBA: gl::GLuint = gl::RGBA;
-
-const GL_FORMAT_BGRA_GL: gl::GLuint = gl::BGRA;
-
-const GL_FORMAT_BGRA_GLES: gl::GLuint = gl::BGRA_EXT;
-
-const SHADER_VERSION_GL: &str = "#version 150\n";
-const SHADER_VERSION_GLES: &str = "#version 300 es\n";
-
-const SHADER_KIND_VERTEX: &str = "#define WR_VERTEX_SHADER\n";
-const SHADER_KIND_FRAGMENT: &str = "#define WR_FRAGMENT_SHADER\n";
-const SHADER_IMPORT: &str = "#include ";
-
-pub struct TextureSlot(pub usize);
-
-// In some places we need to temporarily bind a texture to any slot.
-const DEFAULT_TEXTURE: TextureSlot = TextureSlot(0);
-
-#[repr(u32)]
-pub enum DepthFunction {
-    #[cfg(feature = "debug_renderer")]
-    Less = gl::LESS,
-    LessEqual = gl::LEQUAL,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum TextureFilter {
-    Nearest,
-    Linear,
-    Trilinear,
-}
-
-#[derive(Debug)]
-pub enum VertexAttributeKind {
-    F32,
-    #[cfg(feature = "debug_renderer")]
-    U8Norm,
-    U16Norm,
-    I32,
-    U16,
-}
-
-#[derive(Debug)]
-pub struct VertexAttribute {
-    pub name: &'static str,
-    pub count: u32,
-    pub kind: VertexAttributeKind,
-}
-
-#[derive(Debug)]
-pub struct VertexDescriptor {
-    pub vertex_attributes: &'static [VertexAttribute],
-    pub instance_attributes: &'static [VertexAttribute],
-}
-
-enum FBOTarget {
-    Read,
-    Draw,
-}
-
-/// Method of uploading texel data from CPU to GPU.
-#[derive(Debug, Clone)]
-pub enum UploadMethod {
-    /// Just call `glTexSubImage` directly with the CPU data pointer
-    Immediate,
-    /// Accumulate the changes in PBO first before transferring to a texture.
-    PixelBuffer(VertexUsageHint),
-}
-
-/// Plain old data that can be used to initialize a texture.
-pub unsafe trait Texel: Copy {}
-unsafe impl Texel for u8 {}
-unsafe impl Texel for f32 {}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum ReadPixelsFormat {
-    Standard(ImageFormat),
-    Rgba8,
-}
-
-pub fn get_gl_target(target: TextureTarget) -> gl::GLuint {
-    match target {
-        TextureTarget::Default => gl::TEXTURE_2D,
-        TextureTarget::Array => gl::TEXTURE_2D_ARRAY,
-        TextureTarget::Rect => gl::TEXTURE_RECTANGLE,
-        TextureTarget::External => gl::TEXTURE_EXTERNAL_OES,
-    }
-}
-
-fn supports_extension(extensions: &[String], extension: &str) -> bool {
-    extensions.iter().any(|s| s == extension)
-}
-
-fn get_shader_version(gl: &gl::Gl) -> &'static str {
-    match gl.get_type() {
-        gl::GlType::Gl => SHADER_VERSION_GL,
-        gl::GlType::Gles => SHADER_VERSION_GLES,
-    }
-}
-
-// Get a shader string by name, from the built in resources or
-// an override path, if supplied.
-fn get_shader_source(shader_name: &str, base_path: &Option<PathBuf>) -> Option<String> {
-    if let Some(ref base) = *base_path {
-        let shader_path = base.join(&format!("{}.glsl", shader_name));
-        if shader_path.exists() {
-            let mut source = String::new();
-            File::open(&shader_path)
-                .unwrap()
-                .read_to_string(&mut source)
-                .unwrap();
-            return Some(source);
-        }
-    }
-
-    shader_source::SHADERS
-        .get(shader_name)
-        .map(|s| s.to_string())
-}
-
-// Parse a shader string for imports. Imports are recursively processed, and
-// prepended to the list of outputs.
-fn parse_shader_source(source: String, base_path: &Option<PathBuf>, output: &mut String) {
-    for line in source.lines() {
-        if line.starts_with(SHADER_IMPORT) {
-            let imports = line[SHADER_IMPORT.len() ..].split(',');
-
-            // For each import, get the source, and recurse.
-            for import in imports {
-                if let Some(include) = get_shader_source(import, base_path) {
-                    parse_shader_source(include, base_path, output);
-                }
-            }
-        } else {
-            output.push_str(line);
-            output.push_str("\n");
-        }
-    }
-}
-
-pub fn build_shader_strings(
-    gl_version_string: &str,
-    features: &str,
-    base_filename: &str,
-    override_path: &Option<PathBuf>,
-) -> (String, String) {
-    // Construct a list of strings to be passed to the shader compiler.
-    let mut vs_source = String::new();
-    let mut fs_source = String::new();
-
-    // GLSL requires that the version number comes first.
-    vs_source.push_str(gl_version_string);
-    fs_source.push_str(gl_version_string);
-
-    // Insert the shader name to make debugging easier.
-    let name_string = format!("// {}\n", base_filename);
-    vs_source.push_str(&name_string);
-    fs_source.push_str(&name_string);
-
-    // Define a constant depending on whether we are compiling VS or FS.
-    vs_source.push_str(SHADER_KIND_VERTEX);
-    fs_source.push_str(SHADER_KIND_FRAGMENT);
-
-    // Add any defines that were passed by the caller.
-    vs_source.push_str(features);
-    fs_source.push_str(features);
-
-    // Parse the main .glsl file, including any imports
-    // and append them to the list of sources.
-    let mut shared_result = String::new();
-    if let Some(shared_source) = get_shader_source(base_filename, override_path) {
-        parse_shader_source(shared_source, override_path, &mut shared_result);
-    }
-
-    vs_source.push_str(&shared_result);
-    fs_source.push_str(&shared_result);
-
-    (vs_source, fs_source)
-}
-
-pub trait FileWatcherHandler: Send {
-    fn file_changed(&self, path: PathBuf);
-}
-
-impl VertexAttributeKind {
-    fn size_in_bytes(&self) -> u32 {
-        match *self {
-            VertexAttributeKind::F32 => 4,
-            #[cfg(feature = "debug_renderer")]
-            VertexAttributeKind::U8Norm => 1,
-            VertexAttributeKind::U16Norm => 2,
-            VertexAttributeKind::I32 => 4,
-            VertexAttributeKind::U16 => 2,
-        }
-    }
-}
-
-impl VertexAttribute {
-    fn size_in_bytes(&self) -> u32 {
-        self.count * self.kind.size_in_bytes()
-    }
-
-    fn bind_to_vao(
-        &self,
-        attr_index: gl::GLuint,
-        divisor: gl::GLuint,
-        stride: gl::GLint,
-        offset: gl::GLuint,
-        gl: &gl::Gl,
-    ) {
-        gl.enable_vertex_attrib_array(attr_index);
-        gl.vertex_attrib_divisor(attr_index, divisor);
-
-        match self.kind {
-            VertexAttributeKind::F32 => {
-                gl.vertex_attrib_pointer(
-                    attr_index,
-                    self.count as gl::GLint,
-                    gl::FLOAT,
-                    false,
-                    stride,
-                    offset,
-                );
-            }
-            #[cfg(feature = "debug_renderer")]
-            VertexAttributeKind::U8Norm => {
-                gl.vertex_attrib_pointer(
-                    attr_index,
-                    self.count as gl::GLint,
-                    gl::UNSIGNED_BYTE,
-                    true,
-                    stride,
-                    offset,
-                );
-            }
-            VertexAttributeKind::U16Norm => {
-                gl.vertex_attrib_pointer(
-                    attr_index,
-                    self.count as gl::GLint,
-                    gl::UNSIGNED_SHORT,
-                    true,
-                    stride,
-                    offset,
-                );
-            }
-            VertexAttributeKind::I32 => {
-                gl.vertex_attrib_i_pointer(
-                    attr_index,
-                    self.count as gl::GLint,
-                    gl::INT,
-                    stride,
-                    offset,
-                );
-            }
-            VertexAttributeKind::U16 => {
-                gl.vertex_attrib_i_pointer(
-                    attr_index,
-                    self.count as gl::GLint,
-                    gl::UNSIGNED_SHORT,
-                    stride,
-                    offset,
-                );
-            }
-        }
-    }
-}
-
-impl VertexDescriptor {
-    fn instance_stride(&self) -> u32 {
-        self.instance_attributes
-            .iter()
-            .map(|attr| attr.size_in_bytes())
-            .sum()
-    }
-
-    fn bind_attributes(
-        attributes: &[VertexAttribute],
-        start_index: usize,
-        divisor: u32,
-        gl: &gl::Gl,
-        vbo: VBOId,
-    ) {
-        vbo.bind(gl);
-
-        let stride: u32 = attributes
-            .iter()
-            .map(|attr| attr.size_in_bytes())
-            .sum();
-
-        let mut offset = 0;
-        for (i, attr) in attributes.iter().enumerate() {
-            let attr_index = (start_index + i) as gl::GLuint;
-            attr.bind_to_vao(attr_index, divisor, stride as _, offset, gl);
-            offset += attr.size_in_bytes();
-        }
-    }
-
-    fn bind(&self, gl: &gl::Gl, main: VBOId, instance: VBOId) {
-        Self::bind_attributes(self.vertex_attributes, 0, 0, gl, main);
-
-        if !self.instance_attributes.is_empty() {
-            Self::bind_attributes(
-                self.instance_attributes,
-                self.vertex_attributes.len(),
-                1, gl, instance,
-            );
-        }
-    }
-}
-
-impl VBOId {
-    fn bind(&self, gl: &gl::Gl) {
-        gl.bind_buffer(gl::ARRAY_BUFFER, self.0);
-    }
-}
-
-impl IBOId {
-    fn bind(&self, gl: &gl::Gl) {
-        gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.0);
-    }
-}
-
-impl FBOId {
-    fn bind(&self, gl: &gl::Gl, target: FBOTarget) {
-        let target = match target {
-            FBOTarget::Read => gl::READ_FRAMEBUFFER,
-            FBOTarget::Draw => gl::DRAW_FRAMEBUFFER,
-        };
-        gl.bind_framebuffer(target, self.0);
-    }
-}
-
-pub struct Stream<'a> {
-    attributes: &'a [VertexAttribute],
-    vbo: VBOId,
-}
-
-pub struct VBO<V> {
-    id: gl::GLuint,
-    target: gl::GLenum,
-    allocated_count: usize,
-    marker: PhantomData<V>,
-}
-
-impl<V> VBO<V> {
-    pub fn allocated_count(&self) -> usize {
-        self.allocated_count
-    }
-
-    pub fn stream_with<'a>(&self, attributes: &'a [VertexAttribute]) -> Stream<'a> {
-        debug_assert_eq!(
-            mem::size_of::<V>(),
-            attributes.iter().map(|a| a.size_in_bytes() as usize).sum::<usize>()
-        );
-        Stream {
-            attributes,
-            vbo: VBOId(self.id),
-        }
-    }
-}
-
-impl<T> Drop for VBO<T> {
-    fn drop(&mut self) {
-        debug_assert!(thread::panicking() || self.id == 0);
-    }
-}
-
-#[cfg_attr(feature = "replay", derive(Clone))]
-pub struct ExternalTexture {
-    id: gl::GLuint,
-    target: gl::GLuint,
-}
-
-impl ExternalTexture {
-    pub fn new(id: u32, target: TextureTarget) -> Self {
-        ExternalTexture {
-            id,
-            target: get_gl_target(target),
-        }
-    }
-
-    #[cfg(feature = "replay")]
-    pub fn internal_id(&self) -> gl::GLuint {
-        self.id
-    }
-}
-
-pub struct Texture {
-    id: gl::GLuint,
-    target: gl::GLuint,
-    layer_count: i32,
-    format: ImageFormat,
-    width: u32,
-    height: u32,
-    filter: TextureFilter,
-    render_target: Option<RenderTargetInfo>,
-    fbo_ids: Vec<FBOId>,
-    depth_rb: Option<RBOId>,
-    last_frame_used: FrameId,
-}
-
-impl Texture {
-    pub fn get_dimensions(&self) -> DeviceUintSize {
-        DeviceUintSize::new(self.width, self.height)
-    }
-
-    pub fn get_render_target_layer_count(&self) -> usize {
-        self.fbo_ids.len()
-    }
-
-    pub fn get_layer_count(&self) -> i32 {
-        self.layer_count
-    }
-
-    pub fn get_format(&self) -> ImageFormat {
-        self.format
-    }
-
-    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
-    pub fn get_filter(&self) -> TextureFilter {
-        self.filter
-    }
-
-    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
-    pub fn get_render_target(&self) -> Option<RenderTargetInfo> {
-        self.render_target.clone()
-    }
-
-    pub fn has_depth(&self) -> bool {
-        self.depth_rb.is_some()
-    }
-
-    pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
-        self.render_target.as_ref()
-    }
-
-    pub fn used_in_frame(&self, frame_id: FrameId) -> bool {
-        self.last_frame_used == frame_id
-    }
-
-    #[cfg(feature = "replay")]
-    pub fn into_external(mut self) -> ExternalTexture {
-        let ext = ExternalTexture {
-            id: self.id,
-            target: self.target,
-        };
-        self.id = 0; // don't complain, moved out
-        ext
-    }
-}
-
-impl Drop for Texture {
-    fn drop(&mut self) {
-        debug_assert!(thread::panicking() || self.id == 0);
-    }
-}
-
-pub struct Program {
-    id: gl::GLuint,
-    u_transform: gl::GLint,
-    u_device_pixel_ratio: gl::GLint,
-    u_mode: gl::GLint,
-}
-
-impl Drop for Program {
-    fn drop(&mut self) {
-        debug_assert!(
-            thread::panicking() || self.id == 0,
-            "renderer::deinit not called"
-        );
-    }
-}
-
-pub struct CustomVAO {
-    id: gl::GLuint,
-}
-
-impl Drop for CustomVAO {
-    fn drop(&mut self) {
-        debug_assert!(
-            thread::panicking() || self.id == 0,
-            "renderer::deinit not called"
-        );
-    }
-}
-
-pub struct VAO {
-    id: gl::GLuint,
-    ibo_id: IBOId,
-    main_vbo_id: VBOId,
-    instance_vbo_id: VBOId,
-    instance_stride: usize,
-    owns_vertices_and_indices: bool,
-}
-
-impl Drop for VAO {
-    fn drop(&mut self) {
-        debug_assert!(
-            thread::panicking() || self.id == 0,
-            "renderer::deinit not called"
-        );
-    }
-}
-
-pub struct PBO {
-    id: gl::GLuint,
-}
-
-impl Drop for PBO {
-    fn drop(&mut self) {
-        debug_assert!(
-            thread::panicking() || self.id == 0,
-            "renderer::deinit not called"
-        );
-    }
-}
-
-#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-pub struct FBOId(gl::GLuint);
-
-#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-pub struct RBOId(gl::GLuint);
-
-#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-pub struct VBOId(gl::GLuint);
-
-#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-struct IBOId(gl::GLuint);
-
-#[derive(Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
-pub struct ProgramSources {
-    renderer_name: String,
-    vs_source: String,
-    fs_source: String,
-}
-
-impl ProgramSources {
-    fn new(renderer_name: String, vs_source: String, fs_source: String) -> Self {
-        ProgramSources {
-            renderer_name,
-            vs_source,
-            fs_source,
-        }
-    }
-}
-
-#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
-pub struct ProgramBinary {
-    binary: Vec<u8>,
-    format: gl::GLenum,
-    #[cfg(feature = "serialize_program")]
-    sources: ProgramSources,
-}
-
-impl ProgramBinary {
-    #[allow(unused_variables)]
-    fn new(binary: Vec<u8>,
-           format: gl::GLenum,
-           sources: &ProgramSources) -> Self {
-        ProgramBinary {
-            binary,
-            format,
-            #[cfg(feature = "serialize_program")]
-            sources: sources.clone(),
-        }
-    }
-}
-
-/// The interfaces that an application can implement to handle ProgramCache update
-pub trait ProgramCacheObserver {
-    fn notify_binary_added(&self, program_binary: &Arc<ProgramBinary>);
-    fn notify_program_binary_failed(&self, program_binary: &Arc<ProgramBinary>);
-}
-
-pub struct ProgramCache {
-    binaries: RefCell<FastHashMap<ProgramSources, Arc<ProgramBinary>>>,
-
-    /// Optional trait object that allows the client
-    /// application to handle ProgramCache updating
-    program_cache_handler: Option<Box<ProgramCacheObserver>>,
-}
-
-impl ProgramCache {
-    pub fn new(program_cache_observer: Option<Box<ProgramCacheObserver>>) -> Rc<Self> {
-        Rc::new(
-            ProgramCache {
-                binaries: RefCell::new(FastHashMap::default()),
-                program_cache_handler: program_cache_observer,
-            }
-        )
-    }
-    /// Load ProgramBinary to ProgramCache.
-    /// The function is typically used to load ProgramBinary from disk.
-    #[cfg(feature = "serialize_program")]
-    pub fn load_program_binary(&self, program_binary: Arc<ProgramBinary>) {
-        let sources = program_binary.sources.clone();
-        self.binaries.borrow_mut().insert(sources, program_binary);
-    }
-}
-
-#[derive(Debug, Copy, Clone)]
-pub enum VertexUsageHint {
-    Static,
-    Dynamic,
-    Stream,
-}
-
-impl VertexUsageHint {
-    fn to_gl(&self) -> gl::GLuint {
-        match *self {
-            VertexUsageHint::Static => gl::STATIC_DRAW,
-            VertexUsageHint::Dynamic => gl::DYNAMIC_DRAW,
-            VertexUsageHint::Stream => gl::STREAM_DRAW,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct UniformLocation(gl::GLint);
-
-impl UniformLocation {
-    pub const INVALID: Self = UniformLocation(-1);
-}
-
-#[cfg(feature = "debug_renderer")]
-pub struct Capabilities {
-    pub supports_multisampling: bool,
-}
-
-#[derive(Clone, Debug)]
-pub enum ShaderError {
-    Compilation(String, String), // name, error message
-    Link(String, String),        // name, error message
-}
-
-pub struct Device {
-    gl: Rc<gl::Gl>,
-    // device state
-    bound_textures: [gl::GLuint; 16],
-    bound_program: gl::GLuint,
-    bound_vao: gl::GLuint,
-    bound_read_fbo: FBOId,
-    bound_draw_fbo: FBOId,
-    program_mode_id: UniformLocation,
-    default_read_fbo: gl::GLuint,
-    default_draw_fbo: gl::GLuint,
-
-    device_pixel_ratio: f32,
-    upload_method: UploadMethod,
-
-    // HW or API capabilities
-    #[cfg(feature = "debug_renderer")]
-    capabilities: Capabilities,
-
-    bgra_format: gl::GLuint,
-
-    // debug
-    inside_frame: bool,
-
-    // resources
-    resource_override_path: Option<PathBuf>,
-
-    max_texture_size: u32,
-    renderer_name: String,
-    cached_programs: Option<Rc<ProgramCache>>,
-
-    // Frame counter. This is used to map between CPU
-    // frames and GPU frames.
-    frame_id: FrameId,
-
-    // GL extensions
-    extensions: Vec<String>,
-}
-
-impl Device {
-    pub fn new(
-        gl: Rc<gl::Gl>,
-        resource_override_path: Option<PathBuf>,
-        upload_method: UploadMethod,
-        _file_changed_handler: Box<FileWatcherHandler>,
-        cached_programs: Option<Rc<ProgramCache>>,
-    ) -> Device {
-        let mut max_texture_size = [0];
-        unsafe {
-            gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
-        }
-        let max_texture_size = max_texture_size[0] as u32;
-        let renderer_name = gl.get_string(gl::RENDERER);
-
-        let mut extension_count = [0];
-        unsafe {
-            gl.get_integer_v(gl::NUM_EXTENSIONS, &mut extension_count);
-        }
-        let extension_count = extension_count[0] as gl::GLuint;
-        let mut extensions = Vec::new();
-        for i in 0 .. extension_count {
-            extensions.push(gl.get_string_i(gl::EXTENSIONS, i));
-        }
-
-        let supports_bgra = supports_extension(&extensions, "GL_EXT_texture_format_BGRA8888");
-        let bgra_format = match gl.get_type() {
-            gl::GlType::Gl => GL_FORMAT_BGRA_GL,
-            gl::GlType::Gles => if supports_bgra {
-                GL_FORMAT_BGRA_GLES
-            } else {
-                GL_FORMAT_RGBA
-            }
-        };
-
-        Device {
-            gl,
-            resource_override_path,
-            // This is initialized to 1 by default, but it is reset
-            // at the beginning of each frame in `Renderer::bind_frame_data`.
-            device_pixel_ratio: 1.0,
-            upload_method,
-            inside_frame: false,
-
-            #[cfg(feature = "debug_renderer")]
-            capabilities: Capabilities {
-                supports_multisampling: false, //TODO
-            },
-
-            bgra_format,
-
-            bound_textures: [0; 16],
-            bound_program: 0,
-            bound_vao: 0,
-            bound_read_fbo: FBOId(0),
-            bound_draw_fbo: FBOId(0),
-            program_mode_id: UniformLocation::INVALID,
-            default_read_fbo: 0,
-            default_draw_fbo: 0,
-
-            max_texture_size,
-            renderer_name,
-            cached_programs,
-            frame_id: FrameId(0),
-            extensions,
-        }
-    }
-
-    pub fn gl(&self) -> &gl::Gl {
-        &*self.gl
-    }
-
-    pub fn rc_gl(&self) -> &Rc<gl::Gl> {
-        &self.gl
-    }
-
-    pub fn set_device_pixel_ratio(&mut self, ratio: f32) {
-        self.device_pixel_ratio = ratio;
-    }
-
-    pub fn update_program_cache(&mut self, cached_programs: Rc<ProgramCache>) {
-        self.cached_programs = Some(cached_programs);
-    }
-
-    pub fn max_texture_size(&self) -> u32 {
-        self.max_texture_size
-    }
-
-    #[cfg(feature = "debug_renderer")]
-    pub fn get_capabilities(&self) -> &Capabilities {
-        &self.capabilities
-    }
-
-    pub fn reset_state(&mut self) {
-        self.bound_textures = [0; 16];
-        self.bound_vao = 0;
-        self.bound_read_fbo = FBOId(0);
-        self.bound_draw_fbo = FBOId(0);
-    }
-
-    #[cfg(debug_assertions)]
-    fn print_shader_errors(source: &str, log: &str) {
-        // hacky way to extract the offending lines
-        if !log.starts_with("0:") {
-            return;
-        }
-        let end_pos = match log[2..].chars().position(|c| !c.is_digit(10)) {
-            Some(pos) => 2 + pos,
-            None => return,
-        };
-        let base_line_number = match log[2 .. end_pos].parse::<usize>() {
-            Ok(number) if number >= 2 => number - 2,
-            _ => return,
-        };
-        for (line, prefix) in source.lines().skip(base_line_number).zip(&["|",">","|"]) {
-            error!("{}\t{}", prefix, line);
-        }
-    }
-
-    pub fn compile_shader(
-        gl: &gl::Gl,
-        name: &str,
-        shader_type: gl::GLenum,
-        source: &String,
-    ) -> Result<gl::GLuint, ShaderError> {
-        debug!("compile {}", name);
-        let id = gl.create_shader(shader_type);
-        gl.shader_source(id, &[source.as_bytes()]);
-        gl.compile_shader(id);
-        let log = gl.get_shader_info_log(id);
-        let mut status = [0];
-        unsafe {
-            gl.get_shader_iv(id, gl::COMPILE_STATUS, &mut status);
-        }
-        if status[0] == 0 {
-            error!("Failed to compile shader: {}\n{}", name, log);
-            #[cfg(debug_assertions)]
-            Self::print_shader_errors(source, &log);
-            Err(ShaderError::Compilation(name.to_string(), log))
-        } else {
-            if !log.is_empty() {
-                warn!("Warnings detected on shader: {}\n{}", name, log);
-            }
-            Ok(id)
-        }
-    }
-
-    pub fn begin_frame(&mut self) -> FrameId {
-        debug_assert!(!self.inside_frame);
-        self.inside_frame = true;
-
-        // Retrieve the currently set FBO.
-        let mut default_read_fbo = [0];
-        unsafe {
-            self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut default_read_fbo);
-        }
-        self.default_read_fbo = default_read_fbo[0] as gl::GLuint;
-        let mut default_draw_fbo = [0];
-        unsafe {
-            self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut default_draw_fbo);
-        }
-        self.default_draw_fbo = default_draw_fbo[0] as gl::GLuint;
-
-        // Texture state
-        for i in 0 .. self.bound_textures.len() {
-            self.bound_textures[i] = 0;
-            self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
-            self.gl.bind_texture(gl::TEXTURE_2D, 0);
-        }
-
-        // Shader state
-        self.bound_program = 0;
-        self.program_mode_id = UniformLocation::INVALID;
-        self.gl.use_program(0);
-
-        // Vertex state
-        self.bound_vao = 0;
-        self.gl.bind_vertex_array(0);
-
-        // FBO state
-        self.bound_read_fbo = FBOId(self.default_read_fbo);
-        self.bound_draw_fbo = FBOId(self.default_draw_fbo);
-
-        // Pixel op state
-        self.gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1);
-        self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
-
-        // Default is sampler 0, always
-        self.gl.active_texture(gl::TEXTURE0);
-
-        self.frame_id
-    }
-
-    fn bind_texture_impl(&mut self, slot: TextureSlot, id: gl::GLuint, target: gl::GLenum) {
-        debug_assert!(self.inside_frame);
-
-        if self.bound_textures[slot.0] != id {
-            self.bound_textures[slot.0] = id;
-            self.gl.active_texture(gl::TEXTURE0 + slot.0 as gl::GLuint);
-            self.gl.bind_texture(target, id);
-            self.gl.active_texture(gl::TEXTURE0);
-        }
-    }
-
-    pub fn bind_texture<S>(&mut self, sampler: S, texture: &Texture)
-    where
-        S: Into<TextureSlot>,
-    {
-        self.bind_texture_impl(sampler.into(), texture.id, texture.target);
-    }
-
-    pub fn bind_external_texture<S>(&mut self, sampler: S, external_texture: &ExternalTexture)
-    where
-        S: Into<TextureSlot>,
-    {
-        self.bind_texture_impl(sampler.into(), external_texture.id, external_texture.target);
-    }
-
-    pub fn bind_read_target_impl(&mut self, fbo_id: FBOId) {
-        debug_assert!(self.inside_frame);
-
-        if self.bound_read_fbo != fbo_id {
-            self.bound_read_fbo = fbo_id;
-            fbo_id.bind(self.gl(), FBOTarget::Read);
-        }
-    }
-
-    pub fn bind_read_target(&mut self, texture_and_layer: Option<(&Texture, i32)>) {
-        let fbo_id = texture_and_layer.map_or(FBOId(self.default_read_fbo), |texture_and_layer| {
-            texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
-        });
-
-        self.bind_read_target_impl(fbo_id)
-    }
-
-    fn bind_draw_target_impl(&mut self, fbo_id: FBOId) {
-        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);
-        }
-    }
-
-    pub fn bind_draw_target(
-        &mut self,
-        texture_and_layer: Option<(&Texture, i32)>,
-        dimensions: Option<DeviceUintSize>,
-    ) {
-        let fbo_id = texture_and_layer.map_or(FBOId(self.default_draw_fbo), |texture_and_layer| {
-            texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
-        });
-
-        self.bind_draw_target_impl(fbo_id);
-
-        if let Some(dimensions) = dimensions {
-            self.gl.viewport(
-                0,
-                0,
-                dimensions.width as _,
-                dimensions.height as _,
-            );
-        }
-    }
-
-    pub fn create_fbo_for_external_texture(&mut self, texture_id: u32) -> FBOId {
-        let fbo = FBOId(self.gl.gen_framebuffers(1)[0]);
-        fbo.bind(self.gl(), FBOTarget::Draw);
-        self.gl.framebuffer_texture_2d(
-            gl::DRAW_FRAMEBUFFER,
-            gl::COLOR_ATTACHMENT0,
-            gl::TEXTURE_2D,
-            texture_id,
-            0,
-        );
-        self.bound_draw_fbo.bind(self.gl(), FBOTarget::Draw);
-        fbo
-    }
-
-    pub fn delete_fbo(&mut self, fbo: FBOId) {
-        self.gl.delete_framebuffers(&[fbo.0]);
-    }
-
-    pub fn bind_external_draw_target(&mut self, fbo_id: FBOId) {
-        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);
-        }
-    }
-
-    pub fn bind_program(&mut self, program: &Program) {
-        debug_assert!(self.inside_frame);
-
-        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);
-        }
-    }
-
-    pub fn create_texture(
-        &mut self,
-        target: TextureTarget,
-        format: ImageFormat,
-    ) -> Texture {
-        Texture {
-            id: self.gl.gen_textures(1)[0],
-            target: get_gl_target(target),
-            width: 0,
-            height: 0,
-            layer_count: 0,
-            format,
-            filter: TextureFilter::Nearest,
-            render_target: None,
-            fbo_ids: vec![],
-            depth_rb: None,
-            last_frame_used: self.frame_id,
-        }
-    }
-
-    fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
-        let mag_filter = match filter {
-            TextureFilter::Nearest => gl::NEAREST,
-            TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
-        };
-
-        let min_filter = match filter {
-            TextureFilter::Nearest => gl::NEAREST,
-            TextureFilter::Linear => gl::LINEAR,
-            TextureFilter::Trilinear => gl::LINEAR_MIPMAP_LINEAR,
-        };
-
-        self.gl
-            .tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, mag_filter as gl::GLint);
-        self.gl
-            .tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, min_filter as gl::GLint);
-
-        self.gl
-            .tex_parameter_i(target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
-        self.gl
-            .tex_parameter_i(target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
-    }
-
-    /// Resizes a texture with enabled render target views,
-    /// preserves the data by blitting the old texture contents over.
-    pub fn resize_renderable_texture(
-        &mut self,
-        texture: &mut Texture,
-        new_size: DeviceUintSize,
-    ) {
-        debug_assert!(self.inside_frame);
-
-        let old_size = texture.get_dimensions();
-        let old_fbos = mem::replace(&mut texture.fbo_ids, Vec::new());
-        let old_texture_id = mem::replace(&mut texture.id, self.gl.gen_textures(1)[0]);
-
-        texture.width = new_size.width;
-        texture.height = new_size.height;
-        let rt_info = texture.render_target
-            .clone()
-            .expect("Only renderable textures are expected for resize here");
-
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-        self.set_texture_parameters(texture.target, texture.filter);
-        self.update_target_storage::<u8>(texture, &rt_info, true, None);
-
-        let rect = DeviceIntRect::new(DeviceIntPoint::zero(), old_size.to_i32());
-        for (read_fbo, &draw_fbo) in old_fbos.into_iter().zip(&texture.fbo_ids) {
-            self.bind_read_target_impl(read_fbo);
-            self.bind_draw_target_impl(draw_fbo);
-            self.blit_render_target(rect, rect);
-            self.delete_fbo(read_fbo);
-        }
-        self.gl.delete_textures(&[old_texture_id]);
-        self.bind_read_target(None);
-    }
-
-    pub fn init_texture<T: Texel>(
-        &mut self,
-        texture: &mut Texture,
-        mut width: u32,
-        mut height: u32,
-        filter: TextureFilter,
-        render_target: Option<RenderTargetInfo>,
-        layer_count: i32,
-        pixels: Option<&[T]>,
-    ) {
-        debug_assert!(self.inside_frame);
-
-        if width > self.max_texture_size || height > self.max_texture_size {
-            error!("Attempting to allocate a texture of size {}x{} above the limit, trimming", width, height);
-            width = width.min(self.max_texture_size);
-            height = height.min(self.max_texture_size);
-        }
-
-        let is_resized = texture.width != width || texture.height != height;
-
-        texture.width = width;
-        texture.height = height;
-        texture.filter = filter;
-        texture.layer_count = layer_count;
-        texture.render_target = render_target;
-        texture.last_frame_used = self.frame_id;
-
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-        self.set_texture_parameters(texture.target, filter);
-
-        match render_target {
-            Some(info) => {
-                self.update_target_storage(texture, &info, is_resized, pixels);
-            }
-            None => {
-                self.update_texture_storage(texture, pixels);
-            }
-        }
-    }
-
-    /// Updates the render target storage for the texture, creating FBOs as required.
-    fn update_target_storage<T: Texel>(
-        &mut self,
-        texture: &mut Texture,
-        rt_info: &RenderTargetInfo,
-        is_resized: bool,
-        pixels: Option<&[T]>,
-    ) {
-        assert!(texture.layer_count > 0 || texture.width + texture.height == 0);
-
-        let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
-        let allocate_color = needed_layer_count != 0 || is_resized || pixels.is_some();
-
-        if allocate_color {
-            let desc = self.gl_describe_format(texture.format);
-            match texture.target {
-                gl::TEXTURE_2D_ARRAY => {
-                    self.gl.tex_image_3d(
-                        texture.target,
-                        0,
-                        desc.internal,
-                        texture.width as _,
-                        texture.height as _,
-                        texture.layer_count,
-                        0,
-                        desc.external,
-                        desc.pixel_type,
-                        pixels.map(texels_to_u8_slice),
-                    )
-                }
-                _ => {
-                    assert_eq!(texture.layer_count, 1);
-                    self.gl.tex_image_2d(
-                        texture.target,
-                        0,
-                        desc.internal,
-                        texture.width as _,
-                        texture.height as _,
-                        0,
-                        desc.external,
-                        desc.pixel_type,
-                        pixels.map(texels_to_u8_slice),
-                    )
-                }
-            }
-        }
-
-        if needed_layer_count > 0 {
-            // Create more framebuffers to fill the gap
-            let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
-            texture
-                .fbo_ids
-                .extend(new_fbos.into_iter().map(FBOId));
-        } else if needed_layer_count < 0 {
-            // Remove extra framebuffers
-            for old in texture.fbo_ids.drain(texture.layer_count as usize ..) {
-                self.gl.delete_framebuffers(&[old.0]);
-            }
-        }
-
-        let (mut depth_rb, allocate_depth) = match texture.depth_rb {
-            Some(rbo) => (rbo.0, is_resized || !rt_info.has_depth),
-            None if rt_info.has_depth => {
-                let renderbuffer_ids = self.gl.gen_renderbuffers(1);
-                let depth_rb = renderbuffer_ids[0];
-                texture.depth_rb = Some(RBOId(depth_rb));
-                (depth_rb, true)
-            },
-            None => (0, false),
-        };
-
-        if allocate_depth {
-            if rt_info.has_depth {
-                self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
-                self.gl.renderbuffer_storage(
-                    gl::RENDERBUFFER,
-                    gl::DEPTH_COMPONENT24,
-                    texture.width as _,
-                    texture.height as _,
-                );
-            } else {
-                self.gl.delete_renderbuffers(&[depth_rb]);
-                depth_rb = 0;
-                texture.depth_rb = None;
-            }
-        }
-
-        if allocate_color || allocate_depth {
-            let original_bound_fbo = self.bound_draw_fbo;
-            for (fbo_index, &fbo_id) in texture.fbo_ids.iter().enumerate() {
-                self.bind_external_draw_target(fbo_id);
-                match texture.target {
-                    gl::TEXTURE_2D_ARRAY => {
-                        self.gl.framebuffer_texture_layer(
-                            gl::DRAW_FRAMEBUFFER,
-                            gl::COLOR_ATTACHMENT0,
-                            texture.id,
-                            0,
-                            fbo_index as _,
-                        )
-                    }
-                    _ => {
-                        assert_eq!(fbo_index, 0);
-                        self.gl.framebuffer_texture_2d(
-                            gl::DRAW_FRAMEBUFFER,
-                            gl::COLOR_ATTACHMENT0,
-                            texture.target,
-                            texture.id,
-                            0,
-                        )
-                    }
-                }
-
-                self.gl.framebuffer_renderbuffer(
-                    gl::DRAW_FRAMEBUFFER,
-                    gl::DEPTH_ATTACHMENT,
-                    gl::RENDERBUFFER,
-                    depth_rb,
-                );
-            }
-            self.bind_external_draw_target(original_bound_fbo);
-        }
-    }
-
-    fn update_texture_storage<T: Texel>(&mut self, texture: &Texture, pixels: Option<&[T]>) {
-        let desc = self.gl_describe_format(texture.format);
-        match texture.target {
-            gl::TEXTURE_2D_ARRAY => {
-                self.gl.tex_image_3d(
-                    gl::TEXTURE_2D_ARRAY,
-                    0,
-                    desc.internal,
-                    texture.width as _,
-                    texture.height as _,
-                    texture.layer_count,
-                    0,
-                    desc.external,
-                    desc.pixel_type,
-                    pixels.map(texels_to_u8_slice),
-                );
-            }
-            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
-                self.gl.tex_image_2d(
-                    texture.target,
-                    0,
-                    desc.internal,
-                    texture.width as _,
-                    texture.height as _,
-                    0,
-                    desc.external,
-                    desc.pixel_type,
-                    pixels.map(texels_to_u8_slice),
-                );
-            }
-            _ => panic!("BUG: Unexpected texture target!"),
-        }
-    }
-
-    pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
-        debug_assert!(self.inside_frame);
-
-        self.gl.blit_framebuffer(
-            src_rect.origin.x,
-            src_rect.origin.y,
-            src_rect.origin.x + src_rect.size.width,
-            src_rect.origin.y + src_rect.size.height,
-            dest_rect.origin.x,
-            dest_rect.origin.y,
-            dest_rect.origin.x + dest_rect.size.width,
-            dest_rect.origin.y + dest_rect.size.height,
-            gl::COLOR_BUFFER_BIT,
-            gl::LINEAR,
-        );
-    }
-
-    fn free_texture_storage_impl(&mut self, target: gl::GLenum, desc: FormatDesc) {
-        match target {
-            gl::TEXTURE_2D_ARRAY => {
-                self.gl.tex_image_3d(
-                    gl::TEXTURE_2D_ARRAY,
-                    0,
-                    desc.internal,
-                    0,
-                    0,
-                    0,
-                    0,
-                    desc.external,
-                    desc.pixel_type,
-                    None,
-                );
-            }
-            _ => {
-                self.gl.tex_image_2d(
-                    target,
-                    0,
-                    desc.internal,
-                    0,
-                    0,
-                    0,
-                    desc.external,
-                    desc.pixel_type,
-                    None,
-                );
-            }
-        }
-    }
-
-    pub fn free_texture_storage(&mut self, texture: &mut Texture) {
-        debug_assert!(self.inside_frame);
-
-        if texture.width + texture.height == 0 {
-            return;
-        }
-
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-        let desc = self.gl_describe_format(texture.format);
-
-        self.free_texture_storage_impl(texture.target, desc);
-
-        if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
-            self.gl.delete_renderbuffers(&[depth_rb]);
-        }
-
-        if !texture.fbo_ids.is_empty() {
-            let fbo_ids: Vec<_> = texture
-                .fbo_ids
-                .drain(..)
-                .map(|FBOId(fbo_id)| fbo_id)
-                .collect();
-            self.gl.delete_framebuffers(&fbo_ids[..]);
-        }
-
-        texture.width = 0;
-        texture.height = 0;
-        texture.layer_count = 0;
-    }
-
-    pub fn delete_texture(&mut self, mut texture: Texture) {
-        self.free_texture_storage(&mut texture);
-        self.gl.delete_textures(&[texture.id]);
-
-        for bound_texture in &mut self.bound_textures {
-            if *bound_texture == texture.id {
-                *bound_texture = 0
-            }
-        }
-
-        texture.id = 0;
-    }
-
-    #[cfg(feature = "replay")]
-    pub fn delete_external_texture(&mut self, mut external: ExternalTexture) {
-        self.bind_external_texture(DEFAULT_TEXTURE, &external);
-        //Note: the format descriptor here doesn't really matter
-        self.free_texture_storage_impl(external.target, FormatDesc {
-            internal: gl::R8 as _,
-            external: gl::RED,
-            pixel_type: gl::UNSIGNED_BYTE,
-        });
-        self.gl.delete_textures(&[external.id]);
-        external.id = 0;
-    }
-
-    pub fn delete_program(&mut self, mut program: Program) {
-        self.gl.delete_program(program.id);
-        program.id = 0;
-    }
-
-    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,
-            base_filename,
-            &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;
-
-        if let Some(ref cached_programs) = self.cached_programs {
-            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);
-                }
-            }
-        }
-
-        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,
-        };
-
-        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,
-    {
-        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);
-            }
-        }
-    }
-
-    #[cfg(feature = "debug_renderer")]
-    pub fn get_uniform_location(&self, program: &Program, name: &str) -> UniformLocation {
-        UniformLocation(self.gl.get_uniform_location(program.id, name))
-    }
-
-    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 {
-        let id = self.gl.gen_buffers(1)[0];
-        PBO { id }
-    }
-
-    pub fn delete_pbo(&mut self, mut pbo: PBO) {
-        self.gl.delete_buffers(&[pbo.id]);
-        pbo.id = 0;
-    }
-
-    pub fn upload_texture<'a, T>(
-        &'a mut self,
-        texture: &'a Texture,
-        pbo: &PBO,
-        upload_count: usize,
-    ) -> TextureUploader<'a, T> {
-        debug_assert!(self.inside_frame);
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-
-        let buffer = match self.upload_method {
-            UploadMethod::Immediate => None,
-            UploadMethod::PixelBuffer(hint) => {
-                let upload_size = upload_count * mem::size_of::<T>();
-                self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo.id);
-                if upload_size != 0 {
-                    self.gl.buffer_data_untyped(
-                        gl::PIXEL_UNPACK_BUFFER,
-                        upload_size as _,
-                        ptr::null(),
-                        hint.to_gl(),
-                    );
-                }
-                Some(PixelBuffer::new(hint.to_gl(), upload_size))
-            },
-        };
-
-        TextureUploader {
-            target: UploadTarget {
-                gl: &*self.gl,
-                bgra_format: self.bgra_format,
-                texture,
-            },
-            buffer,
-            marker: PhantomData,
-        }
-    }
-
-    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
-    pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec<u8> {
-        let desc = self.gl_describe_format(img_desc.format);
-        self.gl.read_pixels(
-            0, 0,
-            img_desc.size.width as i32,
-            img_desc.size.height as i32,
-            desc.external,
-            desc.pixel_type,
-        )
-    }
-
-    /// Read rectangle of pixels into the specified output slice.
-    pub fn read_pixels_into(
-        &mut self,
-        rect: DeviceUintRect,
-        format: ReadPixelsFormat,
-        output: &mut [u8],
-    ) {
-        let (bytes_per_pixel, desc) = match format {
-            ReadPixelsFormat::Standard(imf) => {
-                (imf.bytes_per_pixel(), self.gl_describe_format(imf))
-            }
-            ReadPixelsFormat::Rgba8 => {
-                (4, FormatDesc {
-                    external: gl::RGBA,
-                    internal: gl::RGBA8 as _,
-                    pixel_type: gl::UNSIGNED_BYTE,
-                })
-            }
-        };
-        let size_in_bytes = (bytes_per_pixel * rect.size.width * rect.size.height) as usize;
-        assert_eq!(output.len(), size_in_bytes);
-
-        self.gl.flush();
-        self.gl.read_pixels_into_buffer(
-            rect.origin.x as _,
-            rect.origin.y as _,
-            rect.size.width as _,
-            rect.size.height as _,
-            desc.external,
-            desc.pixel_type,
-            output,
-        );
-    }
-
-    /// Get texels of a texture into the specified output slice.
-    #[cfg(feature = "debug_renderer")]
-    pub fn get_tex_image_into(
-        &mut self,
-        texture: &Texture,
-        format: ImageFormat,
-        output: &mut [u8],
-    ) {
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-        let desc = self.gl_describe_format(format);
-        self.gl.get_tex_image_into_buffer(
-            texture.target,
-            0,
-            desc.external,
-            desc.pixel_type,
-            output,
-        );
-    }
-
-    /// Attaches the provided texture to the current Read FBO binding.
-    #[cfg(any(feature = "debug_renderer", feature="capture"))]
-    fn attach_read_texture_raw(
-        &mut self, texture_id: gl::GLuint, target: gl::GLuint, layer_id: i32
-    ) {
-        match target {
-            gl::TEXTURE_2D_ARRAY => {
-                self.gl.framebuffer_texture_layer(
-                    gl::READ_FRAMEBUFFER,
-                    gl::COLOR_ATTACHMENT0,
-                    texture_id,
-                    0,
-                    layer_id,
-                )
-            }
-            _ => {
-                assert_eq!(layer_id, 0);
-                self.gl.framebuffer_texture_2d(
-                    gl::READ_FRAMEBUFFER,
-                    gl::COLOR_ATTACHMENT0,
-                    target,
-                    texture_id,
-                    0,
-                )
-            }
-        }
-    }
-
-    #[cfg(any(feature = "debug_renderer", feature="capture"))]
-    pub fn attach_read_texture_external(
-        &mut self, texture_id: gl::GLuint, target: TextureTarget, layer_id: i32
-    ) {
-        self.attach_read_texture_raw(texture_id, get_gl_target(target), layer_id)
-    }
-
-    #[cfg(any(feature = "debug_renderer", feature="capture"))]
-    pub fn attach_read_texture(&mut self, texture: &Texture, layer_id: i32) {
-        self.attach_read_texture_raw(texture.id, texture.target, layer_id)
-    }
-
-    fn bind_vao_impl(&mut self, id: gl::GLuint) {
-        debug_assert!(self.inside_frame);
-
-        if self.bound_vao != id {
-            self.bound_vao = id;
-            self.gl.bind_vertex_array(id);
-        }
-    }
-
-    pub fn bind_vao(&mut self, vao: &VAO) {
-        self.bind_vao_impl(vao.id)
-    }
-
-    pub fn bind_custom_vao(&mut self, vao: &CustomVAO) {
-        self.bind_vao_impl(vao.id)
-    }
-
-    fn create_vao_with_vbos(
-        &mut self,
-        descriptor: &VertexDescriptor,
-        main_vbo_id: VBOId,
-        instance_vbo_id: VBOId,
-        ibo_id: IBOId,
-        owns_vertices_and_indices: bool,
-    ) -> VAO {
-        debug_assert!(self.inside_frame);
-
-        let instance_stride = descriptor.instance_stride() as usize;
-        let vao_id = self.gl.gen_vertex_arrays(1)[0];
-
-        self.gl.bind_vertex_array(vao_id);
-
-        descriptor.bind(self.gl(), main_vbo_id, instance_vbo_id);
-        ibo_id.bind(self.gl()); // force it to be a part of VAO
-
-        self.gl.bind_vertex_array(0);
-
-        VAO {
-            id: vao_id,
-            ibo_id,
-            main_vbo_id,
-            instance_vbo_id,
-            instance_stride,
-            owns_vertices_and_indices,
-        }
-    }
-
-    pub fn create_custom_vao(
-        &mut self,
-        streams: &[Stream],
-    ) -> CustomVAO {
-        debug_assert!(self.inside_frame);
-
-        let vao_id = self.gl.gen_vertex_arrays(1)[0];
-        self.gl.bind_vertex_array(vao_id);
-
-        let mut attrib_index = 0;
-        for stream in streams {
-            VertexDescriptor::bind_attributes(
-                stream.attributes,
-                attrib_index,
-                0,
-                self.gl(),
-                stream.vbo,
-            );
-            attrib_index += stream.attributes.len();
-        }
-
-        self.gl.bind_vertex_array(0);
-
-        CustomVAO {
-            id: vao_id,
-        }
-    }
-
-    pub fn delete_custom_vao(&mut self, mut vao: CustomVAO) {
-        self.gl.delete_vertex_arrays(&[vao.id]);
-        vao.id = 0;
-    }
-
-    pub fn create_vbo<T>(&mut self) -> VBO<T> {
-        let ids = self.gl.gen_buffers(1);
-        VBO {
-            id: ids[0],
-            target: gl::ARRAY_BUFFER,
-            allocated_count: 0,
-            marker: PhantomData,
-        }
-    }
-
-    pub fn delete_vbo<T>(&mut self, mut vbo: VBO<T>) {
-        self.gl.delete_buffers(&[vbo.id]);
-        vbo.id = 0;
-    }
-
-    pub fn create_vao(&mut self, descriptor: &VertexDescriptor) -> VAO {
-        debug_assert!(self.inside_frame);
-
-        let buffer_ids = self.gl.gen_buffers(3);
-        let ibo_id = IBOId(buffer_ids[0]);
-        let main_vbo_id = VBOId(buffer_ids[1]);
-        let intance_vbo_id = VBOId(buffer_ids[2]);
-
-        self.create_vao_with_vbos(descriptor, main_vbo_id, intance_vbo_id, ibo_id, true)
-    }
-
-    pub fn delete_vao(&mut self, mut vao: VAO) {
-        self.gl.delete_vertex_arrays(&[vao.id]);
-        vao.id = 0;
-
-        if vao.owns_vertices_and_indices {
-            self.gl.delete_buffers(&[vao.ibo_id.0]);
-            self.gl.delete_buffers(&[vao.main_vbo_id.0]);
-        }
-
-        self.gl.delete_buffers(&[vao.instance_vbo_id.0])
-    }
-
-    pub fn allocate_vbo<V>(
-        &mut self,
-        vbo: &mut VBO<V>,
-        count: usize,
-        usage_hint: VertexUsageHint,
-    ) {
-        debug_assert!(self.inside_frame);
-        vbo.allocated_count = count;
-
-        self.gl.bind_buffer(vbo.target, vbo.id);
-        self.gl.buffer_data_untyped(
-            vbo.target,
-            (count * mem::size_of::<V>()) as _,
-            ptr::null(),
-            usage_hint.to_gl(),
-        );
-    }
-
-    pub fn fill_vbo<V>(
-        &mut self,
-        vbo: &VBO<V>,
-        data: &[V],
-        offset: usize,
-    ) {
-        debug_assert!(self.inside_frame);
-        assert!(offset + data.len() <= vbo.allocated_count);
-        let stride = mem::size_of::<V>();
-
-        self.gl.bind_buffer(vbo.target, vbo.id);
-        self.gl.buffer_sub_data_untyped(
-            vbo.target,
-            (offset * stride) as _,
-            (data.len() * stride) as _,
-            data.as_ptr() as _,
-        );
-    }
-
-    fn update_vbo_data<V>(
-        &mut self,
-        vbo: VBOId,
-        vertices: &[V],
-        usage_hint: VertexUsageHint,
-    ) {
-        debug_assert!(self.inside_frame);
-
-        vbo.bind(self.gl());
-        gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, vertices, usage_hint.to_gl());
-    }
-
-    pub fn create_vao_with_new_instances(
-        &mut self,
-        descriptor: &VertexDescriptor,
-        base_vao: &VAO,
-    ) -> VAO {
-        debug_assert!(self.inside_frame);
-
-        let buffer_ids = self.gl.gen_buffers(1);
-        let intance_vbo_id = VBOId(buffer_ids[0]);
-
-        self.create_vao_with_vbos(
-            descriptor,
-            base_vao.main_vbo_id,
-            intance_vbo_id,
-            base_vao.ibo_id,
-            false,
-        )
-    }
-
-    pub fn update_vao_main_vertices<V>(
-        &mut self,
-        vao: &VAO,
-        vertices: &[V],
-        usage_hint: VertexUsageHint,
-    ) {
-        debug_assert_eq!(self.bound_vao, vao.id);
-        self.update_vbo_data(vao.main_vbo_id, vertices, usage_hint)
-    }
-
-    pub fn update_vao_instances<V>(
-        &mut self,
-        vao: &VAO,
-        instances: &[V],
-        usage_hint: VertexUsageHint,
-    ) {
-        debug_assert_eq!(self.bound_vao, vao.id);
-        debug_assert_eq!(vao.instance_stride as usize, mem::size_of::<V>());
-
-        self.update_vbo_data(vao.instance_vbo_id, instances, usage_hint)
-    }
-
-    pub fn update_vao_indices<I>(&mut self, vao: &VAO, indices: &[I], usage_hint: VertexUsageHint) {
-        debug_assert!(self.inside_frame);
-        debug_assert_eq!(self.bound_vao, vao.id);
-
-        vao.ibo_id.bind(self.gl());
-        gl::buffer_data(
-            self.gl(),
-            gl::ELEMENT_ARRAY_BUFFER,
-            indices,
-            usage_hint.to_gl(),
-        );
-    }
-
-    pub fn draw_triangles_u16(&mut self, first_vertex: i32, index_count: i32) {
-        debug_assert!(self.inside_frame);
-        self.gl.draw_elements(
-            gl::TRIANGLES,
-            index_count,
-            gl::UNSIGNED_SHORT,
-            first_vertex as u32 * 2,
-        );
-    }
-
-    #[cfg(feature = "debug_renderer")]
-    pub fn draw_triangles_u32(&mut self, first_vertex: i32, index_count: i32) {
-        debug_assert!(self.inside_frame);
-        self.gl.draw_elements(
-            gl::TRIANGLES,
-            index_count,
-            gl::UNSIGNED_INT,
-            first_vertex as u32 * 4,
-        );
-    }
-
-    pub fn draw_nonindexed_points(&mut self, first_vertex: i32, vertex_count: i32) {
-        debug_assert!(self.inside_frame);
-        self.gl.draw_arrays(gl::POINTS, first_vertex, vertex_count);
-    }
-
-    #[cfg(feature = "debug_renderer")]
-    pub fn draw_nonindexed_lines(&mut self, first_vertex: i32, vertex_count: i32) {
-        debug_assert!(self.inside_frame);
-        self.gl.draw_arrays(gl::LINES, first_vertex, vertex_count);
-    }
-
-    pub fn draw_indexed_triangles_instanced_u16(&mut self, index_count: i32, instance_count: i32) {
-        debug_assert!(self.inside_frame);
-        self.gl.draw_elements_instanced(
-            gl::TRIANGLES,
-            index_count,
-            gl::UNSIGNED_SHORT,
-            0,
-            instance_count,
-        );
-    }
-
-    pub fn end_frame(&mut self) {
-        self.bind_draw_target(None, None);
-        self.bind_read_target(None);
-
-        debug_assert!(self.inside_frame);
-        self.inside_frame = false;
-
-        self.gl.bind_texture(gl::TEXTURE_2D, 0);
-        self.gl.use_program(0);
-
-        for i in 0 .. self.bound_textures.len() {
-            self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
-            self.gl.bind_texture(gl::TEXTURE_2D, 0);
-        }
-
-        self.gl.active_texture(gl::TEXTURE0);
-
-        self.frame_id.0 += 1;
-    }
-
-    pub fn clear_target(
-        &self,
-        color: Option<[f32; 4]>,
-        depth: Option<f32>,
-        rect: Option<DeviceIntRect>,
-    ) {
-        let mut clear_bits = 0;
-
-        if let Some(color) = color {
-            self.gl.clear_color(color[0], color[1], color[2], color[3]);
-            clear_bits |= gl::COLOR_BUFFER_BIT;
-        }
-
-        if let Some(depth) = depth {
-            if cfg!(debug_assertions) {
-                let mut mask = [0];
-                unsafe {
-                    self.gl.get_boolean_v(gl::DEPTH_WRITEMASK, &mut mask);
-                }
-                assert_ne!(mask[0], 0);
-            }
-            self.gl.clear_depth(depth as f64);
-            clear_bits |= gl::DEPTH_BUFFER_BIT;
-        }
-
-        if clear_bits != 0 {
-            match rect {
-                Some(rect) => {
-                    self.gl.enable(gl::SCISSOR_TEST);
-                    self.gl.scissor(
-                        rect.origin.x,
-                        rect.origin.y,
-                        rect.size.width,
-                        rect.size.height,
-                    );
-                    self.gl.clear(clear_bits);
-                    self.gl.disable(gl::SCISSOR_TEST);
-                }
-                None => {
-                    self.gl.clear(clear_bits);
-                }
-            }
-        }
-    }
-
-    pub fn enable_depth(&self) {
-        self.gl.enable(gl::DEPTH_TEST);
-    }
-
-    pub fn disable_depth(&self) {
-        self.gl.disable(gl::DEPTH_TEST);
-    }
-
-    pub fn set_depth_func(&self, depth_func: DepthFunction) {
-        self.gl.depth_func(depth_func as gl::GLuint);
-    }
-
-    pub fn enable_depth_write(&self) {
-        self.gl.depth_mask(true);
-    }
-
-    pub fn disable_depth_write(&self) {
-        self.gl.depth_mask(false);
-    }
-
-    pub fn disable_stencil(&self) {
-        self.gl.disable(gl::STENCIL_TEST);
-    }
-
-    pub fn set_scissor_rect(&self, rect: DeviceIntRect) {
-        self.gl.scissor(
-            rect.origin.x,
-            rect.origin.y,
-            rect.size.width,
-            rect.size.height,
-        );
-    }
-
-    pub fn enable_scissor(&self) {
-        self.gl.enable(gl::SCISSOR_TEST);
-    }
-
-    pub fn disable_scissor(&self) {
-        self.gl.disable(gl::SCISSOR_TEST);
-    }
-
-    pub fn set_blend(&self, enable: bool) {
-        if enable {
-            self.gl.enable(gl::BLEND);
-        } else {
-            self.gl.disable(gl::BLEND);
-        }
-    }
-
-    pub fn set_blend_mode_alpha(&self) {
-        self.gl.blend_func_separate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA,
-                                    gl::ONE, gl::ONE);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-
-    pub fn set_blend_mode_premultiplied_alpha(&self) {
-        self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-
-    pub fn set_blend_mode_premultiplied_dest_out(&self) {
-        self.gl.blend_func(gl::ZERO, gl::ONE_MINUS_SRC_ALPHA);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-
-    pub fn set_blend_mode_multiply(&self) {
-        self.gl
-            .blend_func_separate(gl::ZERO, gl::SRC_COLOR, gl::ZERO, gl::SRC_ALPHA);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_max(&self) {
-        self.gl
-            .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
-        self.gl.blend_equation_separate(gl::MAX, gl::FUNC_ADD);
-    }
-    #[cfg(feature = "debug_renderer")]
-    pub fn set_blend_mode_min(&self) {
-        self.gl
-            .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
-        self.gl.blend_equation_separate(gl::MIN, gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_pass0(&self) {
-        self.gl.blend_func(gl::ZERO, gl::ONE_MINUS_SRC_COLOR);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_pass1(&self) {
-        self.gl.blend_func(gl::ONE, gl::ONE);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) {
-        self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
-        self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
-        self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_constant_text_color(&self, color: ColorF) {
-        // color is an unpremultiplied color.
-        self.gl.blend_color(color.r, color.g, color.b, 1.0);
-        self.gl
-            .blend_func(gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-    pub fn set_blend_mode_subpixel_dual_source(&self) {
-        self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC1_COLOR);
-        self.gl.blend_equation(gl::FUNC_ADD);
-    }
-
-    pub fn supports_extension(&self, extension: &str) -> bool {
-        supports_extension(&self.extensions, extension)
-    }
-
-    pub fn echo_driver_messages(&self) {
-        for msg in self.gl.get_debug_messages() {
-            let level = match msg.severity {
-                gl::DEBUG_SEVERITY_HIGH => Level::Error,
-                gl::DEBUG_SEVERITY_MEDIUM => Level::Warn,
-                gl::DEBUG_SEVERITY_LOW => Level::Info,
-                gl::DEBUG_SEVERITY_NOTIFICATION => Level::Debug,
-                _ => Level::Trace,
-            };
-            let ty = match msg.ty {
-                gl::DEBUG_TYPE_ERROR => "error",
-                gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "deprecated",
-                gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "undefined",
-                gl::DEBUG_TYPE_PORTABILITY => "portability",
-                gl::DEBUG_TYPE_PERFORMANCE => "perf",
-                gl::DEBUG_TYPE_MARKER => "marker",
-                gl::DEBUG_TYPE_PUSH_GROUP => "group push",
-                gl::DEBUG_TYPE_POP_GROUP => "group pop",
-                gl::DEBUG_TYPE_OTHER => "other",
-                _ => "?",
-            };
-            log!(level, "({}) {}", ty, msg.message);
-        }
-    }
-
-    fn gl_describe_format(&self, format: ImageFormat) -> FormatDesc {
-        match format {
-            ImageFormat::R8 => FormatDesc {
-                internal: gl::RED as _,
-                external: gl::RED,
-                pixel_type: gl::UNSIGNED_BYTE,
-            },
-            ImageFormat::BGRA8 => {
-                let external = self.bgra_format;
-                FormatDesc {
-                    internal: match self.gl.get_type() {
-                        gl::GlType::Gl => gl::RGBA as _,
-                        gl::GlType::Gles => external as _,
-                    },
-                    external,
-                    pixel_type: gl::UNSIGNED_BYTE,
-                }
-            },
-            ImageFormat::RGBAF32 => FormatDesc {
-                internal: gl::RGBA32F as _,
-                external: gl::RGBA,
-                pixel_type: gl::FLOAT,
-            },
-            ImageFormat::RGBAI32 => FormatDesc {
-                internal: gl::RGBA32I as _,
-                external: gl::RGBA_INTEGER,
-                pixel_type: gl::INT,
-            },
-            ImageFormat::RG8 => FormatDesc {
-                internal: gl::RG8 as _,
-                external: gl::RG,
-                pixel_type: gl::UNSIGNED_BYTE,
-            },
-        }
-    }
-}
-
-struct FormatDesc {
-    internal: gl::GLint,
-    external: gl::GLuint,
-    pixel_type: gl::GLuint,
-}
-
-struct UploadChunk {
-    rect: DeviceUintRect,
-    layer_index: i32,
-    stride: Option<u32>,
-    offset: usize,
-}
-
-struct PixelBuffer {
-    usage: gl::GLenum,
-    size_allocated: usize,
-    size_used: usize,
-    // small vector avoids heap allocation for a single chunk
-    chunks: SmallVec<[UploadChunk; 1]>,
-}
-
-impl PixelBuffer {
-    fn new(
-        usage: gl::GLenum,
-        size_allocated: usize,
-    ) -> Self {
-        PixelBuffer {
-            usage,
-            size_allocated,
-            size_used: 0,
-            chunks: SmallVec::new(),
-        }
-    }
-}
-
-struct UploadTarget<'a> {
-    gl: &'a gl::Gl,
-    bgra_format: gl::GLuint,
-    texture: &'a Texture,
-}
-
-pub struct TextureUploader<'a, T> {
-    target: UploadTarget<'a>,
-    buffer: Option<PixelBuffer>,
-    marker: PhantomData<T>,
-}
-
-impl<'a, T> Drop for TextureUploader<'a, T> {
-    fn drop(&mut self) {
-        if let Some(buffer) = self.buffer.take() {
-            for chunk in buffer.chunks {
-                self.target.update_impl(chunk);
-            }
-            self.target.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
-        }
-    }
-}
-
-impl<'a, T> TextureUploader<'a, T> {
-    pub fn upload(
-        &mut self,
-        rect: DeviceUintRect,
-        layer_index: i32,
-        stride: Option<u32>,
-        data: &[T],
-    ) {
-        match self.buffer {
-            Some(ref mut buffer) => {
-                let upload_size = mem::size_of::<T>() * data.len();
-                if buffer.size_used + upload_size > buffer.size_allocated {
-                    // flush
-                    for chunk in buffer.chunks.drain() {
-                        self.target.update_impl(chunk);
-                    }
-                    buffer.size_used = 0;
-                }
-
-                if upload_size > buffer.size_allocated {
-                    gl::buffer_data(
-                        self.target.gl,
-                        gl::PIXEL_UNPACK_BUFFER,
-                        data,
-                        buffer.usage,
-                    );
-                    buffer.size_allocated = upload_size;
-                } else {
-                    gl::buffer_sub_data(
-                        self.target.gl,
-                        gl::PIXEL_UNPACK_BUFFER,
-                        buffer.size_used as _,
-                        data,
-                    );
-                }
-
-                buffer.chunks.push(UploadChunk {
-                    rect, layer_index, stride,
-                    offset: buffer.size_used,
-                });
-                buffer.size_used += upload_size;
-            }
-            None => {
-                self.target.update_impl(UploadChunk {
-                    rect, layer_index, stride,
-                    offset: data.as_ptr() as _,
-                });
-            }
-        }
-    }
-}
-
-impl<'a> UploadTarget<'a> {
-    fn update_impl(&mut self, chunk: UploadChunk) {
-        let (gl_format, bpp, data_type) = match self.texture.format {
-            ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
-            ImageFormat::BGRA8 => (self.bgra_format, 4, gl::UNSIGNED_BYTE),
-            ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
-            ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
-            ImageFormat::RGBAI32 => (gl::RGBA_INTEGER, 16, gl::INT),
-        };
-
-        let row_length = match chunk.stride {
-            Some(value) => value / bpp,
-            None => self.texture.width,
-        };
-
-        if chunk.stride.is_some() {
-            self.gl.pixel_store_i(
-                gl::UNPACK_ROW_LENGTH,
-                row_length as _,
-            );
-        }
-
-        let pos = chunk.rect.origin;
-        let size = chunk.rect.size;
-
-        match self.texture.target {
-            gl::TEXTURE_2D_ARRAY => {
-                self.gl.tex_sub_image_3d_pbo(
-                    self.texture.target,
-                    0,
-                    pos.x as _,
-                    pos.y as _,
-                    chunk.layer_index,
-                    size.width as _,
-                    size.height as _,
-                    1,
-                    gl_format,
-                    data_type,
-                    chunk.offset,
-                );
-            }
-            gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
-                self.gl.tex_sub_image_2d_pbo(
-                    self.texture.target,
-                    0,
-                    pos.x as _,
-                    pos.y as _,
-                    size.width as _,
-                    size.height as _,
-                    gl_format,
-                    data_type,
-                    chunk.offset,
-                );
-            }
-            _ => panic!("BUG: Unexpected texture target!"),
-        }
-
-        // If using tri-linear filtering, build the mip-map chain for this texture.
-        if self.texture.filter == TextureFilter::Trilinear {
-            self.gl.generate_mipmap(self.texture.target);
-        }
-
-        // Reset row length to 0, otherwise the stride would apply to all texture uploads.
-        if chunk.stride.is_some() {
-            self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _);
-        }
-    }
-}
-
-fn texels_to_u8_slice<T: Texel>(texels: &[T]) -> &[u8] {
-    unsafe {
-        slice::from_raw_parts(texels.as_ptr() as *const u8, texels.len() * mem::size_of::<T>())
-    }
-}
deleted file mode 100644
--- a/gfx/webrender/src/device/mod.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-mod gl;
-
-pub use self::gl::*;
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,20 +9,20 @@ use api::{DevicePixelScale, DeviceUintRe
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
-use clip_scroll_node::{NodeType, SpatialNodeKind, StickyFrameInfo};
+use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
-use euclid::vec2;
-use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
+use euclid::{SideOffsets2D, vec2};
+use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
 use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor};
@@ -623,42 +623,40 @@ impl<'a> DisplayListFlattener<'a> {
                     &prim_info,
                     info.wavy_line_thickness,
                     info.orientation,
                     &info.color,
                     info.style,
                 );
             }
             SpecificDisplayItem::Gradient(ref info) => {
-                let brush_kind = self.create_brush_kind_for_gradient(
+                self.add_gradient(
+                    clip_and_scroll,
                     &prim_info,
                     info.gradient.start_point,
                     info.gradient.end_point,
                     item.gradient_stops(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                 );
-                let prim = PrimitiveContainer::Brush(BrushPrimitive::new(brush_kind, None));
-                self.add_primitive(clip_and_scroll, &prim_info, Vec::new(), prim);
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
-                let brush_kind = self.create_brush_kind_for_radial_gradient(
+                self.add_radial_gradient(
+                    clip_and_scroll,
                     &prim_info,
                     info.gradient.center,
                     info.gradient.start_offset * info.gradient.radius.width,
                     info.gradient.end_offset * info.gradient.radius.width,
                     info.gradient.radius.width / info.gradient.radius.height,
                     item.gradient_stops(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                 );
-                let prim = PrimitiveContainer::Brush(BrushPrimitive::new(brush_kind, None));
-                self.add_primitive(clip_and_scroll, &prim_info, Vec::new(), prim);
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                 let bounds = box_shadow_info
                     .box_bounds
                     .translate(&reference_frame_relative_offset);
                 let mut prim_info = prim_info.clone();
                 prim_info.rect = bounds;
                 self.add_box_shadow(
@@ -872,20 +870,16 @@ impl<'a> DisplayListFlattener<'a> {
                 let shadow_pic = &mut self.prim_store.pictures[shadow_pic_index.0];
                 shadow_pic.add_primitive(shadow_prim_index, clip_and_scroll);
             }
             self.shadow_stack = shadow_stack;
         }
 
         if container.is_visible() {
             let prim_index = self.create_primitive(info, clip_sources, container);
-            if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                println!("Chasing {:?}", prim_index);
-                self.prim_store.chase_id = Some(prim_index);
-            }
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
     }
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
@@ -1198,25 +1192,24 @@ impl<'a> DisplayListFlattener<'a> {
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
     ) -> ClipScrollNodeIndex {
         let index = self.id_to_index_mapper.get_node_index(reference_frame_id);
-        let parent_index = parent_id.map(|id| self.id_to_index_mapper.get_node_index(id));
-        self.clip_scroll_tree.add_reference_frame(
-            index,
-            parent_index,
+        let node = ClipScrollNode::new_reference_frame(
+            parent_id.map(|id| self.id_to_index_mapper.get_node_index(id)),
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
+        self.clip_scroll_tree.add_node(node, index);
         self.reference_frame_stack.push((reference_frame_id, index));
 
         match parent_id {
             Some(ref parent_id) =>
                 self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
             _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainIndex(0)),
         }
         index
@@ -1229,17 +1222,17 @@ impl<'a> DisplayListFlattener<'a> {
     pub fn setup_viewport_offset(
         &mut self,
         inner_rect: DeviceUintRect,
         device_pixel_scale: DevicePixelScale,
     ) {
         let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
         let root_id = self.clip_scroll_tree.root_reference_frame_index();
         let root_node = &mut self.clip_scroll_tree.nodes[root_id.0];
-        if let NodeType::Spatial { kind: SpatialNodeKind::ReferenceFrame(ref mut info), .. } = root_node.node_type {
+        if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
             info.resolved_transform =
                 LayoutVector2D::new(viewport_offset.x, viewport_offset.y).into();
         }
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
@@ -1292,25 +1285,26 @@ impl<'a> DisplayListFlattener<'a> {
         parent_id: ClipId,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
     ) -> ClipScrollNodeIndex {
         let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
-        self.clip_scroll_tree.add_scroll_frame(
-            node_index,
+        let node = ClipScrollNode::new_scroll_frame(
+            pipeline_id,
             self.id_to_index_mapper.get_node_index(parent_id),
             external_id,
-            pipeline_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
         );
+
+        self.clip_scroll_tree.add_node(node, node_index);
         self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
         node_index
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
@@ -1493,16 +1487,61 @@ impl<'a> DisplayListFlattener<'a> {
     pub fn add_border(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         border_item: &BorderDisplayItem,
         gradient_stops: ItemRange<GradientStop>,
     ) {
         let rect = info.rect;
+        let create_segments = |outset: SideOffsets2D<f32>| {
+            // Calculate the modified rect as specific by border-image-outset
+            let origin = LayoutPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
+            let size = LayoutSize::new(
+                rect.size.width + outset.left + outset.right,
+                rect.size.height + outset.top + outset.bottom,
+            );
+            let rect = LayoutRect::new(origin, size);
+
+            let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y);
+            let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
+
+            let tr_outer = LayoutPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+            let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
+
+            let bl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+            let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
+
+            let br_outer = LayoutPoint::new(
+                rect.origin.x + rect.size.width,
+                rect.origin.y + rect.size.height,
+            );
+            let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
+
+            // Build the list of gradient segments
+            vec![
+                // Top left
+                LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+                // Top right
+                LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+                // Bottom right
+                LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+                // Bottom left
+                LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+                // Top
+                LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+                // Bottom
+                LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+                // Left
+                LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+                // Right
+                LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+            ]
+        };
+
         match border_item.details {
             BorderDetails::NinePatch(ref border) => {
                 // Calculate the modified rect as specific by border-image-outset
                 let origin = LayoutPoint::new(
                     rect.origin.x - border.outset.left,
                     rect.origin.y - border.outset.top,
                 );
                 let size = LayoutSize::new(
@@ -1657,75 +1696,94 @@ impl<'a> DisplayListFlattener<'a> {
                     RepeatMode::Stretch,
                     border.repeat_vertical,
                 );
                 let descriptor = BrushSegmentDescriptor {
                     segments,
                     clip_mask_kind: BrushClipMaskKind::Unknown,
                 };
 
-                let brush_kind = match border.source {
+                let prim = PrimitiveContainer::Brush(match border.source {
                     NinePatchBorderSource::Image(image_key) => {
-                        BrushKind::Border {
-                            source: BorderSource::Image(ImageRequest {
-                                key: image_key,
-                                rendering: ImageRendering::Auto,
-                                tile: None,
-                            })
-                        }
-                    }
-                    NinePatchBorderSource::Gradient(gradient) => {
-                        self.create_brush_kind_for_gradient(
-                            &info,
-                            gradient.start_point,
-                            gradient.end_point,
-                            gradient_stops,
-                            gradient.extend_mode,
-                            LayoutSize::new(border.height as f32, border.width as f32),
-                            LayoutSize::zero(),
+                        let source = BorderSource::Image(ImageRequest {
+                            key: image_key,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        });
+
+                        BrushPrimitive::new(
+                            BrushKind::Border {
+                                source
+                            },
+                            Some(descriptor),
                         )
                     }
-                    NinePatchBorderSource::RadialGradient(gradient) => {
-                        self.create_brush_kind_for_radial_gradient(
-                            &info,
-                            gradient.center,
-                            gradient.start_offset * gradient.radius.width,
-                            gradient.end_offset * gradient.radius.width,
-                            gradient.radius.width / gradient.radius.height,
-                            gradient_stops,
-                            gradient.extend_mode,
-                            LayoutSize::new(border.height as f32, border.width as f32),
-                            LayoutSize::zero(),
-                        )
-                    }
-                };
+                });
 
-                let prim = PrimitiveContainer::Brush(
-                    BrushPrimitive::new(brush_kind, Some(descriptor))
-                );
                 self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
             }
+            BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
+                let segment_rel = segment.origin - rect.origin;
+                let mut info = info.clone();
+                info.rect = segment;
+
+                self.add_gradient(
+                    clip_and_scroll,
+                    &info,
+                    border.gradient.start_point - segment_rel,
+                    border.gradient.end_point - segment_rel,
+                    gradient_stops,
+                    border.gradient.extend_mode,
+                    segment.size,
+                    LayoutSize::zero(),
+                );
+            },
+            BorderDetails::RadialGradient(ref border) => {
+                for segment in create_segments(border.outset) {
+                    let segment_rel = segment.origin - rect.origin;
+                    let mut info = info.clone();
+                    info.rect = segment;
+
+                    self.add_radial_gradient(
+                        clip_and_scroll,
+                        &info,
+                        border.gradient.center - segment_rel,
+                        border.gradient.start_offset * border.gradient.radius.width,
+                        border.gradient.end_offset * border.gradient.radius.width,
+                        border.gradient.radius.width / border.gradient.radius.height,
+                        gradient_stops,
+                        border.gradient.extend_mode,
+                        segment.size,
+                        LayoutSize::zero(),
+                    );
+                }
+            }
         }
     }
 
-    pub fn create_brush_kind_for_gradient(
+    pub fn add_gradient(
         &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
-    ) -> BrushKind {
+    ) {
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+        let info = LayoutPrimitiveInfo {
+            rect: prim_rect,
+            .. *info
+        };
 
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
         // are also supplied in reverse that the rendered result will be equivalent. To do this,
         // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
         // just designate the reference orientation as start < end. Aligned gradient rendering
         // manages to produce the same result regardless of orientation, so don't worry about
         // reversing in that case.
         let reverse_stops = start_point.x > end_point.x ||
@@ -1735,56 +1793,78 @@ impl<'a> DisplayListFlattener<'a> {
         // points, it's necessary to reverse the gradient
         // line in some cases.
         let (sp, ep) = if reverse_stops {
             (end_point, start_point)
         } else {
             (start_point, end_point)
         };
 
-        BrushKind::LinearGradient {
-            stops_range: stops,
-            extend_mode,
-            reverse_stops,
-            start_point: sp,
-            end_point: ep,
-            stops_handle: GpuCacheHandle::new(),
-            stretch_size,
-            tile_spacing,
-            visible_tiles: Vec::new(),
-        }
+        let prim = BrushPrimitive::new(
+            BrushKind::LinearGradient {
+                stops_range: stops,
+                extend_mode,
+                reverse_stops,
+                start_point: sp,
+                end_point: ep,
+                stops_handle: GpuCacheHandle::new(),
+                stretch_size,
+                tile_spacing,
+                visible_tiles: Vec::new(),
+            },
+            None,
+        );
+
+        let prim = PrimitiveContainer::Brush(prim);
+
+        self.add_primitive(clip_and_scroll, &info, Vec::new(), prim);
     }
 
-    pub fn create_brush_kind_for_radial_gradient(
+    pub fn add_radial_gradient(
         &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
-    ) -> BrushKind {
+    ) {
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+        let info = LayoutPrimitiveInfo {
+            rect: prim_rect,
+            .. *info
+        };
 
-        BrushKind::RadialGradient {
-            stops_range: stops,
-            extend_mode,
-            center,
-            start_radius,
-            end_radius,
-            ratio_xy,
-            stops_handle: GpuCacheHandle::new(),
-            stretch_size,
-            tile_spacing,
-            visible_tiles: Vec::new(),
-        }
+        let prim = BrushPrimitive::new(
+            BrushKind::RadialGradient {
+                stops_range: stops,
+                extend_mode,
+                center,
+                start_radius,
+                end_radius,
+                ratio_xy,
+                stops_handle: GpuCacheHandle::new(),
+                stretch_size,
+                tile_spacing,
+                visible_tiles: Vec::new(),
+            },
+            None,
+        );
+
+        self.add_primitive(
+            clip_and_scroll,
+            &info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
     }
 
     pub fn add_text(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         run_offset: LayoutVector2D,
         prim_info: &LayoutPrimitiveInfo,
         font_instance_key: &FontInstanceKey,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,60 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
-use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, WorldPoint};
+use api::{LayoutRect, LayoutSize, PipelineId, WorldPoint};
 use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
-use gpu_types::{PrimitiveHeaders, TransformData, UvRectKind};
+use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::PictureSurface;
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
 use tiling::{ScrollbarPrimitive, SpecialRenderPasses};
-use util::{self, WorldToLayoutFastTransform};
-
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum ChasePrimitive {
-    Nothing,
-    LocalRect(LayoutRect),
-}
-
-impl Default for ChasePrimitive {
-    fn default() -> Self {
-        ChasePrimitive::Nothing
-    }
-}
+use util::{self, MaxRect, WorldToLayoutFastTransform};
 
 #[derive(Clone, Copy)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub dual_source_blending_is_supported: bool,
     pub dual_source_blending_is_enabled: bool,
-    pub chase_primitive: ChasePrimitive,
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
@@ -67,24 +51,24 @@ pub struct FrameBuilder {
 
 pub struct FrameBuildingContext<'a> {
     pub scene_id: u64,
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_rect: DeviceIntRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
-    pub transforms: &'a [TransformData],
-    pub max_local_clip: LayoutRect,
+    pub node_data: &'a [ClipScrollNodeData],
 }
 
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
+    pub local_clip_rects: &'a mut Vec<LayoutRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
@@ -110,29 +94,29 @@ impl PictureState {
             local_rect_changed: false,
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
     pub clip_chain: &'a ClipChain,
     pub scroll_node: &'a ClipScrollNode,
-    pub local_clip_rect: LayoutRect,
+    pub clip_chain_rect_index: ClipChainRectIndex,
 }
 
 impl<'a> PrimitiveRunContext<'a> {
     pub fn new(
         clip_chain: &'a ClipChain,
         scroll_node: &'a ClipScrollNode,
-        local_clip_rect: LayoutRect,
+        clip_chain_rect_index: ClipChainRectIndex,
     ) -> Self {
         PrimitiveRunContext {
             clip_chain,
             scroll_node,
-            local_clip_rect,
+            clip_chain_rect_index,
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
@@ -143,17 +127,16 @@ impl FrameBuilder {
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
-                chase_primitive: ChasePrimitive::Nothing,
             },
         }
     }
 
     pub fn with_display_list_flattener(
         screen_rect: DeviceUintRect,
         background_color: Option<ColorF>,
         window_size: DeviceUintSize,
@@ -181,53 +164,49 @@ impl FrameBuilder {
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
-        transforms: &[TransformData],
+        local_clip_rects: &mut Vec<LayoutRect>,
+        node_data: &[ClipScrollNodeData],
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.pictures.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
         let root_clip_scroll_node =
             &clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
 
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        const MAX_CLIP_COORD: f32 = 1.0e9;
-
         let frame_context = FrameBuildingContext {
             scene_id: self.scene_id,
             device_pixel_scale,
             scene_properties,
             pipelines,
             screen_rect: self.screen_rect.to_i32(),
             clip_scroll_tree,
-            transforms,
-            max_local_clip: LayoutRect::new(
-                LayoutPoint::new(-MAX_CLIP_COORD, -MAX_CLIP_COORD),
-                LayoutSize::new(2.0 * MAX_CLIP_COORD, 2.0 * MAX_CLIP_COORD),
-            ),
+            node_data,
         };
 
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
+            local_clip_rects,
             resource_cache,
             gpu_cache,
             special_render_passes,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
@@ -317,23 +296,30 @@ impl FrameBuilder {
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
         gpu_cache.begin_frame();
 
-        let transform_palette = clip_scroll_tree.update_tree(
+        let mut node_data = Vec::with_capacity(clip_scroll_tree.nodes.len());
+        let total_prim_runs =
+            self.prim_store.pictures.iter().fold(1, |count, pic| count + pic.runs.len());
+        let mut clip_chain_local_clip_rects = Vec::with_capacity(total_prim_runs);
+        clip_chain_local_clip_rects.push(LayoutRect::max_rect());
+
+        clip_scroll_tree.update_tree(
             &self.screen_rect.to_i32(),
             device_pixel_scale,
             &mut self.clip_store,
             resource_cache,
             gpu_cache,
             pan,
+            &mut node_data,
             scene_properties,
         );
 
         self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
         let mut render_tasks = RenderTaskTree::new(frame_id);
 
         let screen_size = self.screen_rect.size.to_i32();
@@ -344,17 +330,18 @@ impl FrameBuilder {
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut special_render_passes,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
-            &transform_palette.transforms,
+            &mut clip_chain_local_clip_rects,
+            &node_data,
         );
 
         resource_cache.block_until_all_resources_added(gpu_cache,
                                                        &mut render_tasks,
                                                        texture_cache_profile);
 
         let mut passes = vec![
             special_render_passes.alpha_glyph_pass,
@@ -377,37 +364,35 @@ impl FrameBuilder {
                 main_render_task_id,
                 required_pass_count - 1,
                 &mut passes[2..],
             );
         }
 
         let mut deferred_resolves = vec![];
         let mut has_texture_cache_tasks = false;
-        let mut prim_headers = PrimitiveHeaders::new();
         let use_dual_source_blending = self.config.dual_source_blending_is_enabled &&
                                        self.config.dual_source_blending_is_supported;
 
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 clip_scroll_tree,
                 use_dual_source_blending,
-                transforms: &transform_palette,
+                node_data: &node_data,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
-                &mut prim_headers,
             );
 
             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);
@@ -419,23 +404,23 @@ impl FrameBuilder {
         Frame {
             window_size: self.window_size,
             inner_rect: self.screen_rect,
             device_pixel_ratio: device_pixel_scale.0,
             background_color: self.background_color,
             layer,
             profile_counters,
             passes,
-            transform_palette: transform_palette.transforms,
+            node_data,
+            clip_chain_local_clip_rects,
             render_tasks,
             deferred_resolves,
             gpu_cache_frame_id,
             has_been_rendered: false,
             has_texture_cache_tasks,
-            prim_headers,
         }
     }
 
     pub fn create_hit_tester(&mut self, clip_scroll_tree: &ClipScrollTree) -> HitTester {
         HitTester::new(
             &self.hit_testing_runs,
             clip_scroll_tree,
             &self.clip_store
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,31 +1,35 @@
 /* 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::{DevicePoint, DeviceSize, DeviceRect, LayoutRect, LayoutToWorldTransform};
+use api::{DevicePoint, DeviceSize, DeviceRect, LayoutToWorldTransform};
 use api::{PremultipliedColorF, WorldToLayoutTransform};
-use clip_scroll_tree::TransformIndex;
 use gpu_cache::{GpuCacheAddress, GpuDataRequest};
-use prim_store::{EdgeAaSegmentMask};
+use prim_store::{VECS_PER_SEGMENT, EdgeAaSegmentMask};
 use render_task::RenderTaskAddress;
-use util::{MatrixHelpers, TransformedRectKind};
+use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
+const INT_BITS: usize = 31; //TODO: convert to unsigned
+const CLIP_CHAIN_RECT_BITS: usize = 22;
+const SEGMENT_BITS: usize = INT_BITS - CLIP_CHAIN_RECT_BITS;
+// The guard ensures (at compile time) that the designated number of bits cover
+// the maximum supported segment count for the texture width.
+const _SEGMENT_GUARD: usize = (1 << SEGMENT_BITS) * VECS_PER_SEGMENT - MAX_VERTEX_TEXTURE_WIDTH;
+const EDGE_FLAG_BITS: usize = 4;
+const BRUSH_FLAG_BITS: usize = 4;
+const CLIP_SCROLL_INDEX_BITS: usize = INT_BITS - EDGE_FLAG_BITS - BRUSH_FLAG_BITS;
+
 #[derive(Copy, Clone, Debug)]
 #[repr(C)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ZBufferId(i32);
 
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ZBufferIdGenerator {
     next: i32,
 }
 
 impl ZBufferIdGenerator {
     pub fn new() -> Self {
         ZBufferIdGenerator {
             next: 0
@@ -110,150 +114,76 @@ pub struct BorderInstance {
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipMaskInstance {
     pub render_task_address: RenderTaskAddress,
-    pub transform_id: TransformPaletteId,
+    pub scroll_node_data_index: ClipScrollNodeIndex,
     pub segment: i32,
     pub clip_data_address: GpuCacheAddress,
     pub resource_address: GpuCacheAddress,
 }
 
 /// A border corner dot or dash drawn into the clipping mask.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipMaskBorderCornerDotDash {
     pub clip_mask_instance: ClipMaskInstance,
     pub dot_dash_data: [f32; 8],
 }
 
-// 16 bytes per instance should be enough for anyone!
+// 32 bytes per instance should be enough for anyone!
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveInstance {
-    data: [i32; 4],
-}
-
-#[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PrimitiveHeaderIndex(pub i32);
-
-#[derive(Debug)]
-#[repr(C)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PrimitiveHeaders {
-    // The integer-type headers for a primitive.
-    pub headers_int: Vec<PrimitiveHeaderI>,
-    // The float-type headers for a primitive.
-    pub headers_float: Vec<PrimitiveHeaderF>,
-    // Used to generated a unique z-buffer value per primitive.
-    pub z_generator: ZBufferIdGenerator,
-}
-
-impl PrimitiveHeaders {
-    pub fn new() -> PrimitiveHeaders {
-        PrimitiveHeaders {
-            headers_int: Vec::new(),
-            headers_float: Vec::new(),
-            z_generator: ZBufferIdGenerator::new(),
-        }
-    }
-
-    // Add a new primitive header.
-    pub fn push(
-        &mut self,
-        prim_header: &PrimitiveHeader,
-        user_data: [i32; 3],
-    ) -> PrimitiveHeaderIndex {
-        debug_assert_eq!(self.headers_int.len(), self.headers_float.len());
-        let id = self.headers_float.len();
-
-        self.headers_float.push(PrimitiveHeaderF {
-            local_rect: prim_header.local_rect,
-            local_clip_rect: prim_header.local_clip_rect,
-        });
-
-        self.headers_int.push(PrimitiveHeaderI {
-            z: self.z_generator.next(),
-            task_address: prim_header.task_address,
-            specific_prim_address: prim_header.specific_prim_address.as_int(),
-            clip_task_address: prim_header.clip_task_address,
-            transform_id: prim_header.transform_id,
-            user_data,
-        });
-
-        PrimitiveHeaderIndex(id as i32)
-    }
-}
-
-// This is a convenience type used to make it easier to pass
-// the common parts around during batching.
-pub struct PrimitiveHeader {
-    pub local_rect: LayoutRect,
-    pub local_clip_rect: LayoutRect,
-    pub task_address: RenderTaskAddress,
-    pub specific_prim_address: GpuCacheAddress,
-    pub clip_task_address: RenderTaskAddress,
-    pub transform_id: TransformPaletteId,
-}
-
-// f32 parts of a primitive header
-#[derive(Debug)]
-#[repr(C)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PrimitiveHeaderF {
-    pub local_rect: LayoutRect,
-    pub local_clip_rect: LayoutRect,
-}
-
-// i32 parts of a primitive header
-// TODO(gw): Compress parts of these down to u16
-#[derive(Debug)]
-#[repr(C)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PrimitiveHeaderI {
-    pub z: ZBufferId,
-    pub task_address: RenderTaskAddress,
-    pub specific_prim_address: i32,
-    pub clip_task_address: RenderTaskAddress,
-    pub transform_id: TransformPaletteId,
-    pub user_data: [i32; 3],
+    data: [i32; 8],
 }
 
 pub struct GlyphInstance {
-    pub prim_header_index: PrimitiveHeaderIndex,
+    pub specific_prim_address: GpuCacheAddress,
+    pub task_address: RenderTaskAddress,
+    pub clip_task_address: RenderTaskAddress,
+    pub clip_chain_rect_index: ClipChainRectIndex,
+    pub scroll_id: ClipScrollNodeIndex,
+    pub z: ZBufferId,
 }
 
 impl GlyphInstance {
     pub fn new(
-        prim_header_index: PrimitiveHeaderIndex,
+        specific_prim_address: GpuCacheAddress,
+        task_address: RenderTaskAddress,
+        clip_task_address: RenderTaskAddress,
+        clip_chain_rect_index: ClipChainRectIndex,
+        scroll_id: ClipScrollNodeIndex,
+        z: ZBufferId,
     ) -> Self {
         GlyphInstance {
-            prim_header_index,
+            specific_prim_address,
+            task_address,
+            clip_task_address,
+            clip_chain_rect_index,
+            scroll_id,
+            z,
         }
     }
 
-    // TODO(gw): Some of these fields can be moved to the primitive
-    //           header since they are constant, and some can be
-    //           compressed to a smaller size.
     pub fn build(&self, data0: i32, data1: i32, data2: i32) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
-                self.prim_header_index.0 as i32,
+                self.specific_prim_address.as_int(),
+                self.task_address.0 as i32 | (self.clip_task_address.0 as i32) << 16,
+                self.clip_chain_rect_index.0 as i32,
+                self.scroll_id.0 as i32,
+                self.z.0,
                 data0,
                 data1,
                 data2,
             ],
         }
     }
 }
 
@@ -283,16 +213,20 @@ impl SplitCompositeInstance {
 impl From<SplitCompositeInstance> for PrimitiveInstance {
     fn from(instance: SplitCompositeInstance) -> Self {
         PrimitiveInstance {
             data: [
                 instance.task_address.0 as i32,
                 instance.src_task_address.0 as i32,
                 instance.polygons_address.as_int(),
                 instance.z.0,
+                0,
+                0,
+                0,
+                0,
             ],
         }
     }
 }
 
 bitflags! {
     /// Flags that define how the common brush shader
     /// code should process this instance.
@@ -304,151 +238,90 @@ bitflags! {
         const SEGMENT_RELATIVE = 0x2;
         /// Repeat UVs horizontally.
         const SEGMENT_REPEAT_X = 0x4;
         /// Repeat UVs vertically.
         const SEGMENT_REPEAT_Y = 0x8;
     }
 }
 
-// TODO(gw): Some of these fields can be moved to the primitive
-//           header since they are constant, and some can be
-//           compressed to a smaller size.
+// TODO(gw): While we are converting things over, we
+//           need to have the instance be the same
+//           size as an old PrimitiveInstance. In the
+//           future, we can compress this vertex
+//           format a lot - e.g. z, render task
+//           addresses etc can reasonably become
+//           a u16 type.
 #[repr(C)]
 pub struct BrushInstance {
-    pub prim_header_index: PrimitiveHeaderIndex,
+    pub picture_address: RenderTaskAddress,
+    pub prim_address: GpuCacheAddress,
+    pub clip_chain_rect_index: ClipChainRectIndex,
+    pub scroll_id: ClipScrollNodeIndex,
     pub clip_task_address: RenderTaskAddress,
+    pub z: ZBufferId,
     pub segment_index: i32,
     pub edge_flags: EdgeAaSegmentMask,
     pub brush_flags: BrushFlags,
+    pub user_data: [i32; 3],
 }
 
 impl From<BrushInstance> for PrimitiveInstance {
     fn from(instance: BrushInstance) -> Self {
+        debug_assert_eq!(0, instance.clip_chain_rect_index.0 >> CLIP_CHAIN_RECT_BITS);
+        debug_assert_eq!(0, instance.scroll_id.0 >> CLIP_SCROLL_INDEX_BITS);
+        debug_assert_eq!(0, instance.segment_index >> SEGMENT_BITS);
         PrimitiveInstance {
             data: [
-                instance.prim_header_index.0,
-                instance.clip_task_address.0 as i32,
-                instance.segment_index |
-                ((instance.edge_flags.bits() as i32) << 16) |
-                ((instance.brush_flags.bits() as i32) << 24),
-                0,
+                instance.picture_address.0 as i32 | (instance.clip_task_address.0 as i32) << 16,
+                instance.prim_address.as_int(),
+                instance.clip_chain_rect_index.0 as i32 | (instance.segment_index << CLIP_CHAIN_RECT_BITS),
+                instance.z.0,
+                instance.scroll_id.0 as i32 |
+                    ((instance.edge_flags.bits() as i32) << CLIP_SCROLL_INDEX_BITS) |
+                    ((instance.brush_flags.bits() as i32) << (CLIP_SCROLL_INDEX_BITS + EDGE_FLAG_BITS)),
+                instance.user_data[0],
+                instance.user_data[1],
+                instance.user_data[2],
             ]
         }
     }
 }
 
-// Represents the information about a transform palette
-// entry that is passed to shaders. It includes an index
-// into the transform palette, and a set of flags. The
-// only flag currently used determines whether the
-// transform is axis-aligned (and this should have
-// pixel snapping applied).
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
-pub struct TransformPaletteId(pub u32);
-
-impl TransformPaletteId {
-    // Get the palette ID for an identity transform.
-    pub fn identity() -> TransformPaletteId {
-        TransformPaletteId(0)
-    }
+pub struct ClipScrollNodeIndex(pub u32);
 
-    // Extract the transform kind from the id.
-    pub fn transform_kind(&self) -> TransformedRectKind {
-        if (self.0 >> 24) == 0 {
-            TransformedRectKind::AxisAligned
-        } else {
-            TransformedRectKind::Complex
-        }
-    }
-}
-
-// The GPU data payload for a transform palette entry.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
-pub struct TransformData {
+pub struct ClipScrollNodeData {
     pub transform: LayoutToWorldTransform,
     pub inv_transform: WorldToLayoutTransform,
+    pub transform_kind: f32,
+    pub padding: [f32; 3],
 }
 
-impl TransformData {
+impl ClipScrollNodeData {
     pub fn invalid() -> Self {
-        TransformData {
+        ClipScrollNodeData {
             transform: LayoutToWorldTransform::identity(),
             inv_transform: WorldToLayoutTransform::identity(),
+            transform_kind: 0.0,
+            padding: [0.0; 3],
         }
     }
 }
 
-// Extra data stored about each transform palette entry.
-pub struct TransformMetadata {
-    pub transform_kind: TransformedRectKind,
-}
-
-// Stores a contiguous list of TransformData structs, that
-// are ready for upload to the GPU.
-// TODO(gw): For now, this only stores the complete local
-//           to world transform for each spatial node. In
-//           the future, the transform palette will support
-//           specifying a coordinate system that the transform
-//           should be relative to.
-pub struct TransformPalette {
-    pub transforms: Vec<TransformData>,
-    metadata: Vec<TransformMetadata>,
-}
-
-impl TransformPalette {
-    pub fn new(spatial_node_count: usize) -> TransformPalette {
-        TransformPalette {
-            transforms: Vec::with_capacity(spatial_node_count),
-            metadata: Vec::with_capacity(spatial_node_count),
-        }
-    }
-
-    // Set the local -> world transform for a given spatial
-    // node in the transform palette.
-    pub fn set(
-        &mut self,
-        index: TransformIndex,
-        data: TransformData,
-    ) {
-        let index = index.0 as usize;
-
-        // Pad the vectors out if they are not long enough to
-        // account for this index. This can occur, for instance,
-        // when we stop recursing down the CST due to encountering
-        // a node with an invalid transform.
-        while index >= self.transforms.len() {
-            self.transforms.push(TransformData::invalid());
-            self.metadata.push(TransformMetadata {
-                transform_kind: TransformedRectKind::AxisAligned,
-            });
-        }
-
-        // Store the transform itself, along with metadata about it.
-        self.metadata[index] = TransformMetadata {
-            transform_kind: data.transform.transform_kind(),
-        };
-        self.transforms[index] = data;
-    }
-
-    // Get a transform palette id for the given spatial node.
-    // TODO(gw): In the future, it will be possible to specify
-    //           a coordinate system id here, to allow retrieving
-    //           transforms in the local space of a given spatial node.
-    pub fn get_id(&self, index: TransformIndex) -> TransformPaletteId {
-        let transform_kind = self.metadata[index.0 as usize].transform_kind as u32;
-        TransformPaletteId(index.0 | (transform_kind << 24))
-    }
-}
+#[derive(Copy, Debug, Clone, PartialEq)]
+#[repr(C)]
+pub struct ClipChainRectIndex(pub usize);
 
 // Texture cache resources can be either a simple rect, or define
 // a polygon within a rect by specifying a UV coordinate for each
 // corner. This is useful for rendering screen-space rasterized
 // off-screen surfaces.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -177,16 +177,15 @@ extern crate base64;
 #[cfg(all(feature = "capture", feature = "png"))]
 extern crate png;
 
 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::MAX_VERTEX_TEXTURE_WIDTH;
 pub use webrender_api as api;
 pub use resource_cache::intersect_for_tile;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -271,17 +271,17 @@ impl PicturePrimitive {
         }
     }
 
     // Disallow subpixel AA if an intermediate surface is needed.
     pub fn allow_subpixel_aa(&self) -> bool {
         self.can_draw_directly_to_parent_surface()
     }
 
-    pub fn prepare_for_render(
+    pub fn prepare_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_metadata: &mut PrimitiveMetadata,
         prim_run_context: &PrimitiveRunContext,
         mut pic_state_for_children: PictureState,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
@@ -484,19 +484,26 @@ impl PicturePrimitive {
                     // TODO(gw): This is very hacky code below! It stores an extra
                     //           brush primitive below for the special case of a
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
+                    //  local_rect
+                    //  clip_rect
                     //  [brush specific data]
                     //  [segment_rect, segment data]
                     let shadow_rect = prim_metadata.local_rect.translate(&offset);
+                    let shadow_clip_rect = prim_metadata.local_clip_rect.translate(&offset);
+
+                    // local_rect, clip_rect
+                    request.push(shadow_rect);
+                    request.push(shadow_clip_rect);
 
                     // ImageBrush colors
                     request.push(color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
                         prim_metadata.local_rect.size.width,
                         prim_metadata.local_rect.size.height,
                         0.0,
@@ -580,16 +587,37 @@ impl PicturePrimitive {
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
         }
     }
+
+    pub fn prepare_for_render(
+        &mut self,
+        prim_index: PrimitiveIndex,
+        prim_metadata: &mut PrimitiveMetadata,
+        prim_run_context: &PrimitiveRunContext,
+        pic_state_for_children: PictureState,
+        pic_state: &mut PictureState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        self.prepare_for_render_inner(
+            prim_index,
+            prim_metadata,
+            prim_run_context,
+            pic_state_for_children,
+            pic_state,
+            frame_context,
+            frame_state,
+        );
+    }
 }
 
 // Calculate a single screen-space UV for a picture.
 fn calculate_screen_uv(
     local_pos: &LayoutPoint,
     clip_scroll_node: &ClipScrollNode,
     rendered_rect: &DeviceRect,
     device_pixel_scale: DevicePixelScale,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF};
+use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use box_shadow::BLUR_SAMPLE_SCALE;
@@ -15,17 +15,17 @@ use clip_scroll_tree::{ClipChainIndex, C
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use gpu_types::BrushFlags;
+use gpu_types::{BrushFlags, ClipChainRectIndex};
 use image::{for_each_tile, for_each_repetition};
 use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest};
@@ -45,39 +45,28 @@ pub struct ScrollNodeAndClipChain {
     pub scroll_node_id: ClipScrollNodeIndex,
     pub clip_chain_index: ClipChainIndex,
 }
 
 impl ScrollNodeAndClipChain {
     pub fn new(
         scroll_node_id: ClipScrollNodeIndex,
         clip_chain_index: ClipChainIndex
-    ) -> Self {
+    ) -> ScrollNodeAndClipChain {
         ScrollNodeAndClipChain { scroll_node_id, clip_chain_index }
     }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveRun {
     pub base_prim_index: PrimitiveIndex,
     pub count: usize,
     pub clip_and_scroll: ScrollNodeAndClipChain,
 }
 
-impl PrimitiveRun {
-    pub fn is_chasing(&self, index: Option<PrimitiveIndex>) -> bool {
-        match index {
-            Some(id) if cfg!(debug_assertions) => {
-                self.base_prim_index <= id && id.0 < self.base_prim_index.0 + self.count
-            }
-            _ => false,
-        }
-    }
-}
-
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
 
 impl PrimitiveOpacity {
     pub fn opaque() -> PrimitiveOpacity {
         PrimitiveOpacity { is_opaque: true }
@@ -180,21 +169,17 @@ pub struct PrimitiveMetadata {
     pub gpu_location: GpuCacheHandle,
     pub clip_task_id: Option<RenderTaskId>,
 
     // TODO(gw): In the future, we should just pull these
     //           directly from the DL item, instead of
     //           storing them here.
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
-
-    // The current combined local clip for this primitive, from
-    // the primitive local clip above and the current clip chain.
-    pub combined_local_clip_rect: LayoutRect,
-
+    pub clip_chain_rect_index: ClipChainRectIndex,
     pub is_backface_visible: bool,
     pub screen_rect: Option<ScreenRect>,
 
     /// A tag used to identify this primitive outside of WebRender. This is
     /// used for returning useful data during hit testing.
     pub tag: Option<ItemTag>,
 
     /// The last frame ID (of the `RenderTaskTree`) this primitive
@@ -244,25 +229,21 @@ impl OpacityBinding {
     }
 }
 
 #[derive(Debug)]
 pub struct VisibleImageTile {
     pub tile_offset: TileOffset,
     pub handle: GpuCacheHandle,
     pub edge_flags: EdgeAaSegmentMask,
-    pub local_rect: LayoutRect,
-    pub local_clip_rect: LayoutRect,
 }
 
 #[derive(Debug)]
 pub struct VisibleGradientTile {
     pub handle: GpuCacheHandle,
-    pub local_rect: LayoutRect,
-    pub local_clip_rect: LayoutRect,
 }
 
 #[derive(Debug)]
 pub enum BorderSource {
     Image(ImageRequest),
     Border {
         handle: Option<RenderTaskCacheEntryHandle>,
         cache_key: BorderCacheKey,
@@ -432,24 +413,24 @@ pub struct BrushPrimitive {
     pub kind: BrushKind,
     pub segment_desc: Option<BrushSegmentDescriptor>,
 }
 
 impl BrushPrimitive {
     pub fn new(
         kind: BrushKind,
         segment_desc: Option<BrushSegmentDescriptor>,
-    ) -> Self {
+    ) -> BrushPrimitive {
         BrushPrimitive {
             kind,
             segment_desc,
         }
     }
 
-    pub fn new_picture(pic_index: PictureIndex) -> Self {
+    pub fn new_picture(pic_index: PictureIndex) -> BrushPrimitive {
         BrushPrimitive {
             kind: BrushKind::Picture {
                 pic_index,
             },
             segment_desc: None,
         }
     }
 
@@ -733,16 +714,17 @@ impl<'a> GradientGpuBlockBuilder<'a> {
 
                 cur_color = next_color;
             }
             if cur_idx != GRADIENT_DATA_TABLE_END {
                 error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
                 self.fill_colors(cur_idx, GRADIENT_DATA_TABLE_END, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
             }
 
+
             // Fill in the last entry with the last color stop
             self.fill_colors(
                 GRADIENT_DATA_LAST_STOP,
                 GRADIENT_DATA_LAST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
             );
@@ -752,135 +734,98 @@ impl<'a> GradientGpuBlockBuilder<'a> {
             request.push(entry.start_color);
             request.push(entry.end_color);
         }
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
-    pub specified_font: FontInstance,
-    pub used_font: FontInstance,
+    pub font: FontInstance,
     pub offset: LayoutVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
+    pub glyph_transform: (DevicePixelScale, FontTransform),
     pub shadow: bool,
     pub glyph_raster_space: GlyphRasterSpace,
 }
 
 impl TextRunPrimitiveCpu {
     pub fn new(
         font: FontInstance,
         offset: LayoutVector2D,
         glyph_range: ItemRange<GlyphInstance>,
         glyph_keys: Vec<GlyphKey>,
         shadow: bool,
         glyph_raster_space: GlyphRasterSpace,
     ) -> Self {
         TextRunPrimitiveCpu {
-            specified_font: font.clone(),
-            used_font: font,
+            font,
             offset,
             glyph_range,
             glyph_keys,
             glyph_gpu_blocks: Vec::new(),
+            glyph_transform: (DevicePixelScale::new(1.0), FontTransform::identity()),
             shadow,
             glyph_raster_space,
         }
     }
 
-    pub fn update_font_instance(
-        &mut self,
+    pub fn get_font(
+        &self,
         device_pixel_scale: DevicePixelScale,
-        transform: &LayoutToWorldTransform,
-        allow_subpixel_aa: bool,
-    ) -> bool {
-        // Get the current font size in device pixels
-        let device_font_size = self.specified_font.size.scale_by(device_pixel_scale.0);
-
-        // Determine if rasterizing glyphs in local or screen space.
+        transform: LayoutToWorldTransform,
+    ) -> FontInstance {
+        let mut font = self.font.clone();
+        font.size = font.size.scale_by(device_pixel_scale.0);
         // Only support transforms that can be coerced to simple 2D transforms.
-        let transform_glyphs = if transform.has_perspective_component() ||
+        if transform.has_perspective_component() ||
            !transform.has_2d_inverse() ||
            // Font sizes larger than the limit need to be scaled, thus can't use subpixels.
-           transform.exceeds_2d_scale(FONT_SIZE_LIMIT / device_font_size.to_f64_px()) ||
+           transform.exceeds_2d_scale(FONT_SIZE_LIMIT / font.size.to_f64_px()) ||
            // Otherwise, ensure the font is rasterized in screen-space.
            self.glyph_raster_space != GlyphRasterSpace::Screen {
-            false
-        } else {
-            true
-        };
-
-        // Get the font transform matrix (skew / scale) from the complete transform.
-        let font_transform = if transform_glyphs {
-            // Quantize the transform to minimize thrashing of the glyph cache.
-            FontTransform::from(transform).quantize()
+            font.disable_subpixel_aa();
+            font.disable_subpixel_position();
         } else {
-            FontTransform::identity()
-        };
-
-        // If the transform or device size is different, then the caller of
-        // this method needs to know to rebuild the glyphs.
-        let cache_dirty =
-            self.used_font.transform != font_transform ||
-            self.used_font.size != device_font_size;
-
-        // Construct used font instance from the specified font instance
-        self.used_font = FontInstance {
-            transform: font_transform,
-            size: device_font_size,
-            ..self.specified_font.clone()
-        };
-
-        // If subpixel AA is disabled due to the backing surface the glyphs
-        // are being drawn onto, disable it (unless we are using the
-        // specifial subpixel mode that estimates background color).
-        if !allow_subpixel_aa && self.specified_font.bg_color.a == 0 {
-            self.used_font.disable_subpixel_aa();
+            // Quantize the transform to minimize thrashing of the glyph cache.
+            font.transform = FontTransform::from(&transform).quantize();
         }
-
-        // If using local space glyphs, we don't want subpixel AA
-        // or positioning.
-        if !transform_glyphs {
-            self.used_font.disable_subpixel_aa();
-            self.used_font.disable_subpixel_position();
-        }
-
-        cache_dirty
+        font
     }
 
     fn prepare_for_render(
         &mut self,
         device_pixel_scale: DevicePixelScale,
-        transform: &LayoutToWorldTransform,
+        transform: LayoutToWorldTransform,
         allow_subpixel_aa: bool,
         display_list: &BuiltDisplayList,
         frame_building_state: &mut FrameBuildingState,
     ) {
-        let cache_dirty = self.update_font_instance(
-            device_pixel_scale,
-            transform,
-            allow_subpixel_aa,
-        );
+        if !allow_subpixel_aa && self.font.bg_color.a == 0 {
+            self.font.disable_subpixel_aa();
+        }
+
+        let font = self.get_font(device_pixel_scale, transform);
 
         // Cache the glyph positions, if not in the cache already.
         // TODO(gw): In the future, remove `glyph_instances`
         //           completely, and just reference the glyphs
         //           directly from the display list.
-        if self.glyph_keys.is_empty() || cache_dirty {
-            let subpx_dir = self.used_font.get_subpx_dir();
+        if self.glyph_keys.is_empty() || self.glyph_transform != (device_pixel_scale, font.transform) {
+            let subpx_dir = font.get_subpx_dir();
             let src_glyphs = display_list.get(self.glyph_range);
 
             // TODO(gw): If we support chunks() on AuxIter
             //           in the future, this code below could
             //           be much simpler...
             let mut gpu_block = [0.0; 4];
             for (i, src) in src_glyphs.enumerate() {
-                let world_offset = self.used_font.transform.transform(&src.point);
+                let world_offset = font.transform.transform(&src.point);
                 let device_offset = device_pixel_scale.transform_point(&world_offset);
                 let key = GlyphKey::new(src.index, device_offset, subpx_dir);
                 self.glyph_keys.push(key);
 
                 // Two glyphs are packed per GPU block.
 
                 if (i & 1) == 0 {
                     gpu_block[0] = src.point.x;
@@ -892,30 +837,32 @@ impl TextRunPrimitiveCpu {
                 }
             }
 
             // Ensure the last block is added in the case
             // of an odd number of glyphs.
             if (self.glyph_keys.len() & 1) != 0 {
                 self.glyph_gpu_blocks.push(gpu_block.into());
             }
+
+            self.glyph_transform = (device_pixel_scale, font.transform);
         }
 
         frame_building_state.resource_cache
-                            .request_glyphs(self.used_font.clone(),
+                            .request_glyphs(font,
                                             &self.glyph_keys,
                                             frame_building_state.gpu_cache,
                                             frame_building_state.render_tasks,