Bug 1389497 - Update webrender to commit 1007a65c6dd1fdfb8b39d57d7faff3cae7b32e0c. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 18 Aug 2017 08:51:39 -0400
changeset 375732 8249e9175ae0dc677ae2376dd12325ff42924ceb
parent 375731 434a7c807ffe028cceb458c359ad88a13dde2873
child 375733 1f7b22c5a326ca49d5df29c3c2de036440eca566
push id93967
push userphilringnalda@gmail.com
push dateSat, 19 Aug 2017 22:36:41 +0000
treeherdermozilla-inbound@5ca5691372cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1389497
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1389497 - Update webrender to commit 1007a65c6dd1fdfb8b39d57d7faff3cae7b32e0c. r=jrmuizel MozReview-Commit-ID: LLg2tnX9LYu
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/benches/coalesce.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_clip_border.vs.glsl
gfx/webrender/res/cs_clip_image.fs.glsl
gfx/webrender/res/cs_clip_image.glsl
gfx/webrender/res/cs_clip_image.vs.glsl
gfx/webrender/res/cs_clip_rectangle.vs.glsl
gfx/webrender/res/cs_text_run.glsl
gfx/webrender/res/cs_text_run.vs.glsl
gfx/webrender/res/debug_font.fs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_cache_image.fs.glsl
gfx/webrender/res/ps_image.fs.glsl
gfx/webrender/res/ps_image.glsl
gfx/webrender/res/ps_image.vs.glsl
gfx/webrender/res/ps_text_run.fs.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/ps_text_run.vs.glsl
gfx/webrender/res/ps_yuv_image.fs.glsl
gfx/webrender/res/ps_yuv_image.glsl
gfx/webrender/res/ps_yuv_image.vs.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/record.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/webgl_stubs.rs
gfx/webrender/src/webgl_types.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/image.rs
gfx/webrender_api/src/lib.rs
gfx/webrender_api/src/webgl.rs
gfx/webrender_bindings/Cargo.toml
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 101c69db1a989fe89c308dabd53cf50aedfe4a96
+Latest Commit: 1007a65c6dd1fdfb8b39d57d7faff3cae7b32e0c
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,34 +1,32 @@
 [package]
 name = "webrender"
-version = "0.48.0"
+version = "0.49.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
-default = ["freetype-lib", "webgl"]
+default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
-webgl = ["offscreen_gl_context", "webrender_api/webgl"]
 
 [dependencies]
 app_units = "0.5"
 bincode = "0.8"
 bit-set = "0.4"
 byteorder = "1.0"
 euclid = "0.15.1"
 fxhash = "0.2.1"
-gleam = "0.4.7"
+gleam = "0.4.8"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
-offscreen_gl_context = {version = "0.11", features = ["serde", "osmesa"], optional = true}
 time = "0.1"
 rayon = "0.8"
 webrender_api = {path = "../webrender_api"}
 bitflags = "0.9"
 gamma-lut = "0.2"
 thread_profiler = "0.1.1"
 plane-split = "0.6"
 
deleted file mode 100644
--- a/gfx/webrender/benches/coalesce.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-#![feature(test)]
-
-extern crate rand;
-extern crate test;
-extern crate webrender;
-
-use rand::Rng;
-use test::Bencher;
-use webrender::TexturePage;
-use webrender::api::{DeviceUintSize as Size};
-
-#[bench]
-fn bench_coalesce(b: &mut Bencher) {
-    let mut rng = rand::thread_rng();
-    let mut page = TexturePage::new_dummy(Size::new(10000, 10000));
-    let mut test_page = TexturePage::new_dummy(Size::new(10000, 10000));
-    while page.allocate(&Size::new(rng.gen_range(1, 100), rng.gen_range(1, 100))).is_some() {}
-    b.iter(|| {
-        test_page.fill_from(&page);
-        test_page.coalesce();
-    });
-}
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -4,16 +4,17 @@
 
 use gleam::gl;
 use glutin;
 use std::env;
 use std::path::PathBuf;
 use webrender;
 use webrender::api::*;
 use webrender::renderer::{PROFILER_DBG, RENDER_TARGET_DBG, TEXTURE_CACHE_DBG};
+use webrender::renderer::ExternalImageHandler;
 
 struct Notifier {
     window_proxy: glutin::WindowProxy,
 }
 
 impl Notifier {
     fn new(window_proxy: glutin::WindowProxy) -> Notifier {
         Notifier {
@@ -59,16 +60,19 @@ pub trait Example {
               resources: &mut ResourceUpdates,
               layout_size: LayoutSize,
               pipeline_id: PipelineId,
               document_id: DocumentId);
     fn on_event(&mut self,
                 event: glutin::Event,
                 api: &RenderApi,
                 document_id: DocumentId) -> bool;
+    fn get_external_image_handler(&self) -> Option<Box<ExternalImageHandler>> {
+        None
+    }
 }
 
 pub fn main_wrapper(example: &mut Example,
                     options: Option<webrender::RendererOptions>)
 {
     let args: Vec<String> = env::args().collect();
     let res_path = if args.len() > 1 {
         Some(PathBuf::from(&args[1]))
@@ -111,16 +115,20 @@ pub fn main_wrapper(example: &mut Exampl
     let size = DeviceUintSize::new(width, height);
     let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts).unwrap();
     let api = sender.create_api();
     let document_id = api.add_document(size);
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
+    if let Some(external_image_handler) = example.get_external_image_handler() {
+        renderer.set_external_image_handler(external_image_handler);
+    }
+
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
     let layout_size = LayoutSize::new(width as f32, height as f32);
     let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
     let mut resources = ResourceUpdates::new();
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -0,0 +1,287 @@
+/* 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/. */
+
+extern crate gleam;
+extern crate glutin;
+extern crate webrender;
+
+#[path="common/boilerplate.rs"]
+mod boilerplate;
+
+use boilerplate::{Example, HandyDandyRectBuilder};
+use std::mem;
+use webrender::api::*;
+use webrender::renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
+
+struct ImageGenerator {
+    patterns: [[u8; 3]; 6],
+    next_pattern: usize,
+    current_image: Vec<u8>,
+}
+
+impl ImageGenerator {
+    fn new() -> ImageGenerator {
+        ImageGenerator {
+            next_pattern: 0,
+            patterns: [
+                [1, 0, 0],
+                [0, 1, 0],
+                [0, 0, 1],
+                [1, 1, 0],
+                [0, 1, 1],
+                [1, 0, 1],
+            ],
+            current_image: Vec::new(),
+        }
+    }
+
+    fn generate_image(&mut self, size: u32) {
+        let pattern = &self.patterns[self.next_pattern];
+        self.current_image.clear();
+        for y in 0..size {
+            for x in 0..size {
+                let lum = 255 * (1 - (((x & 8) == 0) ^ ((y & 8) == 0)) as u8);
+                self.current_image.extend_from_slice(&[lum * pattern[0],
+                                                     lum * pattern[1],
+                                                     lum * pattern[2],
+                                                     0xff]);
+            }
+        }
+
+        self.next_pattern = (self.next_pattern + 1) % self.patterns.len();
+    }
+
+    fn take(&mut self) -> Vec<u8> {
+        mem::replace(&mut self.current_image, Vec::new())
+    }
+}
+
+impl ExternalImageHandler for ImageGenerator {
+    fn lock(&mut self, _key: ExternalImageId, channel_index: u8) -> ExternalImage {
+        self.generate_image(channel_index as u32);
+        ExternalImage {
+            u0: 0.0,
+            v0: 0.0,
+            u1: 1.0,
+            v1: 1.0,
+            source: ExternalImageSource::RawData(&self.current_image)
+        }
+    }
+    fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {
+    }
+}
+
+struct App {
+    stress_keys: Vec<ImageKey>,
+    image_key: Option<ImageKey>,
+    image_generator: ImageGenerator,
+    swap_keys: Vec<ImageKey>,
+    swap_index: usize,
+}
+
+impl Example for App {
+    fn render(&mut self,
+              api: &RenderApi,
+              builder: &mut DisplayListBuilder,
+              resources: &mut ResourceUpdates,
+              _layout_size: LayoutSize,
+              _pipeline_id: PipelineId,
+              _document_id: DocumentId) {
+        let bounds = (0,0).to(512, 512);
+        builder.push_stacking_context(ScrollPolicy::Scrollable,
+                                      bounds,
+                                      None,
+                                      TransformStyle::Flat,
+                                      None,
+                                      MixBlendMode::Normal,
+                                      Vec::new());
+
+        let x0 = 50.0;
+        let y0 = 50.0;
+        let image_size = LayoutSize::new(4.0, 4.0);
+
+        if self.swap_keys.is_empty() {
+            let key0 = api.generate_image_key();
+            let key1 = api.generate_image_key();
+
+            self.image_generator.generate_image(128);
+            resources.add_image(
+                key0,
+                ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true),
+                ImageData::new(self.image_generator.take()),
+                None,
+            );
+
+            self.image_generator.generate_image(128);
+            resources.add_image(
+                key1,
+                ImageDescriptor::new(128, 128, ImageFormat::BGRA8, true),
+                ImageData::new(self.image_generator.take()),
+                None,
+            );
+
+            self.swap_keys.push(key0);
+            self.swap_keys.push(key1);
+        }
+
+        for (i, key) in self.stress_keys.iter().enumerate() {
+            let x = (i % 128) as f32;
+            let y = (i / 128) as f32;
+
+            builder.push_image(
+                LayoutRect::new(LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y), image_size),
+                Some(LocalClip::from(bounds)),
+                image_size,
+                LayoutSize::zero(),
+                ImageRendering::Auto,
+                *key
+            );
+        }
+
+        if let Some(image_key) = self.image_key {
+            let image_size = LayoutSize::new(100.0, 100.0);
+
+            builder.push_image(
+                LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
+                Some(LocalClip::from(bounds)),
+                image_size,
+                LayoutSize::zero(),
+                ImageRendering::Auto,
+                image_key
+            );
+        }
+
+        let swap_key = self.swap_keys[self.swap_index];
+        let image_size = LayoutSize::new(64.0, 64.0);
+
+        builder.push_image(
+            LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size),
+            Some(LocalClip::from(bounds)),
+            image_size,
+            LayoutSize::zero(),
+            ImageRendering::Auto,
+            swap_key
+        );
+        self.swap_index = 1 - self.swap_index;
+
+        builder.pop_stacking_context();
+    }
+
+    fn on_event(&mut self,
+                event: glutin::Event,
+                api: &RenderApi,
+                _document_id: DocumentId) -> bool {
+        match event {
+            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+                let mut updates = ResourceUpdates::new();
+
+                match key {
+                    glutin::VirtualKeyCode::S => {
+                        self.stress_keys.clear();
+
+                        for _ in 0..16 {
+                            for _ in 0..16 {
+                                let size = 4;
+
+                                let image_key = api.generate_image_key();
+
+                                self.image_generator.generate_image(size);
+
+                                updates.add_image(
+                                    image_key,
+                                    ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                                    ImageData::new(self.image_generator.take()),
+                                    None,
+                                );
+
+                                self.stress_keys.push(image_key);
+                            }
+                        }
+                    }
+                    glutin::VirtualKeyCode::D => {
+                        if let Some(image_key) = self.image_key.take() {
+                            updates.delete_image(image_key);
+                        }
+                    }
+                    glutin::VirtualKeyCode::U => {
+                        if let Some(image_key) = self.image_key {
+                            let size = 128;
+                            self.image_generator.generate_image(size);
+
+                            updates.update_image(
+                                image_key,
+                                ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                                ImageData::new(self.image_generator.take()),
+                                None,
+                            );
+                        }
+                    }
+                    glutin::VirtualKeyCode::E => {
+                        if let Some(image_key) = self.image_key.take() {
+                            updates.delete_image(image_key);
+                        }
+
+                        let size = 32;
+                        let image_key = api.generate_image_key();
+
+                        let image_data = ExternalImageData {
+                            id: ExternalImageId(0),
+                            channel_index: size as u8,
+                            image_type: ExternalImageType::ExternalBuffer,
+                        };
+
+                        updates.add_image(
+                            image_key,
+                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                            ImageData::External(image_data),
+                            None,
+                        );
+
+                        self.image_key = Some(image_key);
+                    }
+                    glutin::VirtualKeyCode::R => {
+                        if let Some(image_key) = self.image_key.take() {
+                            updates.delete_image(image_key);
+                        }
+
+                        let image_key = api.generate_image_key();
+                        let size = 32;
+                        self.image_generator.generate_image(size);
+
+                        updates.add_image(
+                            image_key,
+                            ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+                            ImageData::new(self.image_generator.take()),
+                            None,
+                        );
+
+                        self.image_key = Some(image_key);
+                    }
+                    _ => {}
+                }
+
+                api.update_resources(updates);
+                return true;
+            }
+            _ => {}
+        }
+
+        false
+    }
+
+    fn get_external_image_handler(&self) -> Option<Box<ExternalImageHandler>> {
+        Some(Box::new(ImageGenerator::new()))
+    }
+}
+
+fn main() {
+    let mut app = App {
+        image_key: None,
+        stress_keys: Vec::new(),
+        image_generator: ImageGenerator::new(),
+        swap_keys: Vec::new(),
+        swap_index: 0,
+    };
+    boilerplate::main_wrapper(&mut app, None);
+}
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -8,63 +8,62 @@
 #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 aClipRenderTaskIndex;
 in int aClipLayerIndex;
-in int aClipDataIndex;
-in int aClipSegmentIndex;
-in int aClipResourceAddress;
+in int aClipSegment;
+in ivec4 aClipDataResourceAddress;
 
 struct CacheClipInstance {
     int render_task_index;
     int layer_index;
-    int data_index;
-    int segment_index;
-    int resource_address;
+    int segment;
+    ivec2 clip_data_address;
+    ivec2 resource_address;
 };
 
 CacheClipInstance fetch_clip_item(int index) {
     CacheClipInstance cci;
 
     cci.render_task_index = aClipRenderTaskIndex;
     cci.layer_index = aClipLayerIndex;
-    cci.data_index = aClipDataIndex;
-    cci.segment_index = aClipSegmentIndex;
-    cci.resource_address = aClipResourceAddress;
+    cci.segment = aClipSegment;
+    cci.clip_data_address = aClipDataResourceAddress.xy;
+    cci.resource_address = aClipDataResourceAddress.zw;
 
     return cci;
 }
 
 struct ClipVertexInfo {
     vec3 local_pos;
     vec2 screen_pos;
     RectWithSize clipped_local_rect;
 };
 
 // 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,
                                       Layer layer,
                                       ClipArea area,
-                                      int segment_index) {
+                                      int segment) {
 
     RectWithSize clipped_local_rect = intersect_rect(local_clip_rect,
                                                      layer.local_clip_rect);
 
     vec2 outer_p0 = area.screen_origin_target_index.xy;
     vec2 outer_p1 = outer_p0 + area.task_bounds.zw - area.task_bounds.xy;
     vec2 inner_p0 = area.inner_rect.xy;
     vec2 inner_p1 = area.inner_rect.zw;
 
     vec2 p0, p1;
-    switch (segment_index) {
+    switch (segment) {
         case SEGMENT_ALL:
             p0 = outer_p0;
             p1 = outer_p1;
             break;
         case SEGMENT_CORNER_TL:
             p0 = outer_p0;
             p1 = inner_p0;
             break;
--- a/gfx/webrender/res/cs_clip_border.vs.glsl
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -16,55 +16,55 @@
 // Header for a border corner clip.
 struct BorderCorner {
     RectWithSize rect;
     vec2 clip_center;
     int corner;
     int clip_mode;
 };
 
-BorderCorner fetch_border_corner(int index) {
-    vec4 data[2] = fetch_from_resource_cache_2(index);
+BorderCorner fetch_border_corner(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
     return BorderCorner(RectWithSize(data[0].xy, data[0].zw),
                         data[1].xy,
                         int(data[1].z),
                         int(data[1].w));
 }
 
 // Per-dash clip information.
 struct BorderClipDash {
     vec4 point_tangent_0;
     vec4 point_tangent_1;
 };
 
-BorderClipDash fetch_border_clip_dash(int index) {
-    vec4 data[2] = fetch_from_resource_cache_2(index);
+BorderClipDash fetch_border_clip_dash(ivec2 address, int segment) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address + ivec2(2 + 2 * (segment - 1), 0));
     return BorderClipDash(data[0], data[1]);
 }
 
 // Per-dot clip information.
 struct BorderClipDot {
     vec3 center_radius;
 };
 
-BorderClipDot fetch_border_clip_dot(int index) {
-    vec4 data = fetch_from_resource_cache_1(index);
+BorderClipDot fetch_border_clip_dot(ivec2 address, int segment) {
+    vec4 data = fetch_from_resource_cache_1_direct(address + ivec2(2 + (segment - 1), 0));
     return BorderClipDot(data.xyz);
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
 
     // Fetch the header information for this corner clip.
-    BorderCorner corner = fetch_border_corner(cci.data_index);
+    BorderCorner corner = fetch_border_corner(cci.clip_data_address);
     vClipCenter = corner.clip_center;
 
-    if (cci.segment_index == 0) {
+    if (cci.segment == 0) {
         // The first segment is used to zero out the border corner.
         vAlphaMask = vec2(0.0);
         vDotParams = vec3(0.0);
         vPoint_Tangent0 = vec4(1.0);
         vPoint_Tangent1 = vec4(1.0);
     } else {
         vec2 sign_modifier;
         switch (corner.corner) {
@@ -80,25 +80,25 @@ void main(void) {
             case CORNER_BOTTOM_LEFT:
                 sign_modifier = vec2(-1.0, 1.0);
                 break;
         };
 
         switch (corner.clip_mode) {
             case CLIP_MODE_DASH: {
                 // Fetch the information about this particular dash.
-                BorderClipDash dash = fetch_border_clip_dash(cci.data_index + 2 + 2 * (cci.segment_index - 1));
+                BorderClipDash dash = fetch_border_clip_dash(cci.clip_data_address, cci.segment);
                 vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
                 vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
                 vDotParams = vec3(0.0);
                 vAlphaMask = vec2(0.0, 1.0);
                 break;
             }
             case CLIP_MODE_DOT: {
-                BorderClipDot cdot = fetch_border_clip_dot(cci.data_index + 2 + (cci.segment_index - 1));
+                BorderClipDot cdot = fetch_border_clip_dot(cci.clip_data_address, cci.segment);
                 vPoint_Tangent0 = vec4(1.0);
                 vPoint_Tangent1 = vec4(1.0);
                 vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
                 vAlphaMask = vec2(1.0, 1.0);
                 break;
             }
         }
     }
--- a/gfx/webrender/res/cs_clip_image.fs.glsl
+++ b/gfx/webrender/res/cs_clip_image.fs.glsl
@@ -6,12 +6,12 @@ void main(void) {
     float alpha = 1.f;
     vec2 local_pos = init_transform_fs(vPos, alpha);
 
     bool repeat_mask = false; //TODO
     vec2 clamped_mask_uv = repeat_mask ? fract(vClipMaskUv.xy) :
         clamp(vClipMaskUv.xy, vec2(0.0, 0.0), vec2(1.0, 1.0));
     vec2 source_uv = clamp(clamped_mask_uv * vClipMaskUvRect.zw + vClipMaskUvRect.xy,
         vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw);
-    float clip_alpha = texture(sColor0, source_uv).r; //careful: texture has type A8
+    float clip_alpha = texture(sColor0, vec3(source_uv, vLayer)).r; //careful: texture has type A8
 
     oFragColor = vec4(min(alpha, clip_alpha), 1.0, 1.0, 1.0);
 }
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -2,8 +2,9 @@
 
 /* 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/. */
 
 varying vec3 vPos;
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
+flat varying float vLayer;
--- a/gfx/webrender/res/cs_clip_image.vs.glsl
+++ b/gfx/webrender/res/cs_clip_image.vs.glsl
@@ -2,35 +2,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 struct ImageMaskData {
     RectWithSize local_rect;
 };
 
-ImageMaskData fetch_mask_data(int index) {
-    vec4 data = fetch_from_resource_cache_1(index);
+ImageMaskData fetch_mask_data(ivec2 address) {
+    vec4 data = fetch_from_resource_cache_1_direct(address);
     return ImageMaskData(RectWithSize(data.xy, data.zw));
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
-    ImageMaskData mask = fetch_mask_data(cci.data_index);
+    ImageMaskData mask = fetch_mask_data(cci.clip_data_address);
     RectWithSize local_rect = mask.local_rect;
-    ImageResource res = fetch_image_resource(cci.resource_address);
+    ImageResource res = fetch_image_resource_direct(cci.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
-                                               cci.segment_index);
+                                               cci.segment);
 
     vPos = vi.local_pos;
+    vLayer = res.layer;
 
     vClipMaskUv = 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.xy, res.uv_rect.zw - res.uv_rect.xy) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
     vec4 inner_rect = vec4(res.uv_rect.xy, res.uv_rect.zw);
     vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
 }
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -3,62 +3,63 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 struct ClipRect {
     RectWithSize rect;
     vec4 mode;
 };
 
-ClipRect fetch_clip_rect(int index) {
-    vec4 data[2] = fetch_from_resource_cache_2(index);
+ClipRect fetch_clip_rect(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
     return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
 }
 
 struct ClipCorner {
     RectWithSize rect;
     vec4 outer_inner_radius;
 };
 
-ClipCorner fetch_clip_corner(int index) {
-    vec4 data[2] = fetch_from_resource_cache_2(index);
+ClipCorner fetch_clip_corner(ivec2 address, int index) {
+    address += ivec2(2 + 2 * index, 0);
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
     return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
 }
 
 struct ClipData {
     ClipRect rect;
     ClipCorner top_left;
     ClipCorner top_right;
     ClipCorner bottom_left;
     ClipCorner bottom_right;
 };
 
-ClipData fetch_clip(int index) {
+ClipData fetch_clip(ivec2 address) {
     ClipData clip;
 
-    clip.rect = fetch_clip_rect(index + 0);
-    clip.top_left = fetch_clip_corner(index + 2);
-    clip.top_right = fetch_clip_corner(index + 4);
-    clip.bottom_left = fetch_clip_corner(index + 6);
-    clip.bottom_right = fetch_clip_corner(index + 8);
+    clip.rect = fetch_clip_rect(address);
+    clip.top_left = fetch_clip_corner(address, 0);
+    clip.top_right = fetch_clip_corner(address, 1);
+    clip.bottom_left = fetch_clip_corner(address, 2);
+    clip.bottom_right = fetch_clip_corner(address, 3);
 
     return clip;
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
-    ClipData clip = fetch_clip(cci.data_index);
+    ClipData clip = fetch_clip(cci.clip_data_address);
     RectWithSize local_rect = clip.rect.rect;
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
-                                               cci.segment_index);
+                                               cci.segment);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
 
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
 
     vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
                                  clip.top_left.outer_inner_radius.xy);
--- a/gfx/webrender/res/cs_text_run.glsl
+++ b/gfx/webrender/res/cs_text_run.glsl
@@ -1,7 +1,7 @@
 #line 1
 /* 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/. */
 
-varying vec2 vUv;
+varying vec3 vUv;
 flat varying vec4 vColor;
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -42,13 +42,13 @@ void main(void) {
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
     vec2 pos = mix(local_rect.xy,
                    local_rect.xy + local_rect.zw,
                    aPosition.xy);
 
-    vUv = mix(st0, st1, aPosition.xy);
+    vUv = vec3(mix(st0, st1, aPosition.xy), res.layer);
     vColor = shadow.color;
 
     gl_Position = uTransform * vec4(pos, 0.0, 1.0);
 }
--- a/gfx/webrender/res/debug_font.fs.glsl
+++ b/gfx/webrender/res/debug_font.fs.glsl
@@ -2,15 +2,11 @@
  * 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/. */
 
 varying vec2 vColorTexCoord;
 varying vec4 vColor;
 
 void main(void)
 {
-#ifdef SERVO_ES2
-    float alpha = texture(sColor0, vColorTexCoord.xy).a;
-#else
-    float alpha = texture(sColor0, vColorTexCoord.xy).r;
-#endif
+    float alpha = texture(sColor0, vec3(vColorTexCoord.xy, 0.0)).r;
     oFragColor = vec4(vColor.xyz, vColor.w * alpha);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -1,32 +1,13 @@
 #line 1
 /* 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/. */
 
-#if defined(GL_ES)
-    #if GL_ES == 1
-        #ifdef GL_FRAGMENT_PRECISION_HIGH
-        precision highp sampler2DArray;
-        #else
-        precision mediump sampler2DArray;
-        #endif
-
-        // Sampler default precision is lowp on mobile GPUs.
-        // This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880).
-        // Define highp precision macro to allow lossless FLOAT texture sampling.
-        #define HIGHP_SAMPLER_FLOAT highp
-    #else
-        #define HIGHP_SAMPLER_FLOAT
-    #endif
-#else
-    #define HIGHP_SAMPLER_FLOAT
-#endif
-
 #define PST_TOP_LEFT     0
 #define PST_TOP          1
 #define PST_TOP_RIGHT    2
 #define PST_RIGHT        3
 #define PST_BOTTOM_RIGHT 4
 #define PST_BOTTOM       5
 #define PST_BOTTOM_LEFT  6
 #define PST_LEFT         7
@@ -124,16 +105,23 @@ varying vec3 vClipMaskUv;
 //           in the vertices.
 ivec2 get_resource_cache_uv(int address) {
     return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
                  address / WR_MAX_VERTEX_TEXTURE_WIDTH);
 }
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sResourceCache;
 
+vec4[2] fetch_from_resource_cache_2_direct(ivec2 address) {
+    return vec4[2](
+        texelFetchOffset(sResourceCache, address, 0, ivec2(0, 0)),
+        texelFetchOffset(sResourceCache, address, 0, ivec2(1, 0))
+    );
+}
+
 vec4[2] fetch_from_resource_cache_2(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[2](
         texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0))
     );
 }
 
@@ -188,16 +176,20 @@ vec4[4] fetch_from_resource_cache_4(int 
     return vec4[4](
         texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(3, 0))
     );
 }
 
+vec4 fetch_from_resource_cache_1_direct(ivec2 address) {
+    return texelFetch(sResourceCache, address, 0);
+}
+
 vec4 fetch_from_resource_cache_1(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return texelFetch(sResourceCache, uv, 0);
 }
 
 struct Layer {
     mat4 transform;
     mat4 inv_transform;
@@ -766,31 +758,38 @@ TransformVertexInfo write_transform_vert
 
     return TransformVertexInfo(layer_pos.xyw, device_pos);
 }
 
 #endif //WR_FEATURE_TRANSFORM
 
 struct GlyphResource {
     vec4 uv_rect;
+    float layer;
     vec2 offset;
 };
 
 GlyphResource fetch_glyph_resource(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return GlyphResource(data[0], data[1].xy);
+    return GlyphResource(data[0], data[1].x, data[1].yz);
 }
 
 struct ImageResource {
     vec4 uv_rect;
+    float layer;
 };
 
 ImageResource fetch_image_resource(int address) {
-    vec4 data = fetch_from_resource_cache_1(address);
-    return ImageResource(data);
+    vec4 data[2] = fetch_from_resource_cache_2(address);
+    return ImageResource(data[0], data[1].x);
+}
+
+ImageResource fetch_image_resource_direct(ivec2 address) {
+    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    return ImageResource(data[0], data[1].x);
 }
 
 struct Rectangle {
     vec4 color;
 };
 
 Rectangle fetch_rectangle(int address) {
     vec4 data = fetch_from_resource_cache_1(address);
--- a/gfx/webrender/res/ps_cache_image.fs.glsl
+++ b/gfx/webrender/res/ps_cache_image.fs.glsl
@@ -1,9 +1,9 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-    oFragColor = texture(sCacheRGBA8, vec3(uv, vUv.z));
+    oFragColor = texture(sColor0, vec3(uv, vUv.z));
 }
--- a/gfx/webrender/res/ps_image.fs.glsl
+++ b/gfx/webrender/res/ps_image.fs.glsl
@@ -23,10 +23,10 @@ void main(void) {
     // account the spacing in between tiles. We only paint if our fragment does
     // not fall into that spacing.
     vec2 position_in_tile = mod(relative_pos_in_rect, vStretchSize + vTileSpacing);
     vec2 st = vTextureOffset + ((position_in_tile / vStretchSize) * vTextureSize);
     st = clamp(st, vStRect.xy, vStRect.zw);
 
     alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize))));
 
-    oFragColor = vec4(alpha) * TEX_SAMPLE(sColor0, st);
+    oFragColor = vec4(alpha) * TEX_SAMPLE(sColor0, vec3(st, vLayer));
 }
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -4,15 +4,16 @@
 
 // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized
 // texture coordinates. Otherwise, it uses normalized texture coordinates. Please
 // check GL_TEXTURE_RECTANGLE.
 flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
 flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
 flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
 flat varying vec4 vStRect;        // Rectangle of valid texture rect.
+flat varying float vLayer;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
 flat varying vec2 vStretchSize;
--- a/gfx/webrender/res/ps_image.vs.glsl
+++ b/gfx/webrender/res/ps_image.vs.glsl
@@ -45,16 +45,17 @@ void main(void) {
         uv0 = res.uv_rect.xy + image.sub_rect.xy;
         uv1 = res.uv_rect.xy + image.sub_rect.zw;
     }
 
     // vUv will contain how many times this image has wrapped around the image size.
     vec2 st0 = uv0 / texture_size_normalization_factor;
     vec2 st1 = uv1 / texture_size_normalization_factor;
 
+    vLayer = res.layer;
     vTextureSize = st1 - st0;
     vTextureOffset = st0;
     vTileSpacing = image.stretch_size_and_tile_spacing.zw;
     vStretchSize = image.stretch_size_and_tile_spacing.xy;
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
     // in order to avoid sampling outside of the texture area.
     vec2 half_texel = vec2(0.5) / texture_size_normalization_factor;
--- a/gfx/webrender/res/ps_text_run.fs.glsl
+++ b/gfx/webrender/res/ps_text_run.fs.glsl
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
-    vec2 tc = clamp(vUv, vUvBorder.xy, vUvBorder.zw);
+    vec3 tc = vec3(clamp(vUv.xy, vUvBorder.xy, vUvBorder.zw), vUv.z);
 #ifdef WR_FEATURE_SUBPIXEL_AA
     //note: the blend mode is not compatible with clipping
     oFragColor = texture(sColor0, tc);
 #else
     float alpha = texture(sColor0, tc).a;
 #ifdef WR_FEATURE_TRANSFORM
     float a = 0.0;
     init_transform_fs(vLocalPos, a);
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -1,11 +1,11 @@
 /* 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 vColor;
-varying vec2 vUv;
+varying vec3 vUv;
 flat varying vec4 vUvBorder;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -43,11 +43,11 @@ void main(void) {
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
     vColor = text.color;
-    vUv = mix(st0, st1, f);
+    vUv = vec3(mix(st0, st1, f), res.layer);
     vUvBorder = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
 }
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -68,25 +68,25 @@ void main(void) {
     vec2 st_u = vTextureOffsetU + uv_offset;
 #endif
 
     vec3 yuv_value;
 #ifdef WR_FEATURE_INTERLEAVED_Y_CB_CR
     // "The Y, Cb and Cr color channels within the 422 data are mapped into
     // the existing green, blue and red color channels."
     // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
-    yuv_value = TEX_SAMPLE(sColor0, st_y).gbr;
+    yuv_value = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).gbr;
 #elif defined(WR_FEATURE_NV12)
-    yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
-    yuv_value.yz = TEX_SAMPLE(sColor1, st_u).rg;
+    yuv_value.x = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).r;
+    yuv_value.yz = TEX_SAMPLE(sColor1, vec3(st_u, vLayers.y)).rg;
 #else
     // The yuv_planar format should have this third texture coordinate.
     vec2 st_v = vTextureOffsetV + uv_offset;
 
-    yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
-    yuv_value.y = TEX_SAMPLE(sColor1, st_u).r;
-    yuv_value.z = TEX_SAMPLE(sColor2, st_v).r;
+    yuv_value.x = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).r;
+    yuv_value.y = TEX_SAMPLE(sColor1, vec3(st_u, vLayers.y)).r;
+    yuv_value.z = TEX_SAMPLE(sColor2, vec3(st_v, vLayers.z)).r;
 #endif
 
     // See the YuvColorMatrix definition for an explanation of where the constants come from.
     vec3 rgb = YuvColorMatrix * (yuv_value - vec3(0.06275, 0.50196, 0.50196));
     oFragColor = vec4(rgb, alpha);
 }
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ b/gfx/webrender/res/ps_yuv_image.glsl
@@ -8,14 +8,15 @@
 flat varying vec2 vTextureOffsetY; // Offset of the y plane into the texture atlas.
 flat varying vec2 vTextureOffsetU; // Offset of the u plane into the texture atlas.
 flat varying vec2 vTextureOffsetV; // Offset of the v plane into the texture atlas.
 flat varying vec2 vTextureSizeY;   // Size of the y plane in the texture atlas.
 flat varying vec2 vTextureSizeUv;  // Size of the u and v planes in the texture atlas.
 flat varying vec2 vStretchSize;
 flat varying vec2 vHalfTexelY;     // Normalized length of the half of a Y texel.
 flat varying vec2 vHalfTexelUv;    // Normalized length of the half of u and v texels.
+flat varying vec3 vLayers;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -21,20 +21,24 @@ void main(void) {
                                  prim.task,
                                  prim.local_rect);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     ImageResource y_rect = fetch_image_resource(prim.user_data0);
+    vLayers = vec3(y_rect.layer, 0.0, 0.0);
+
 #ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR  // only 1 channel
     ImageResource u_rect = fetch_image_resource(prim.user_data1);
+    vLayers.y = u_rect.layer;
 #ifndef WR_FEATURE_NV12 // 2 channel
     ImageResource v_rect = fetch_image_resource(prim.user_data2);
+    vLayers.z = v_rect.layer;
 #endif
 #endif
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 y_texture_size_normalization_factor = vec2(1, 1);
 #else
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -9,18 +9,18 @@
 #endif
 
 // The textureLod() doesn't support samplerExternalOES for WR_FEATURE_TEXTURE_EXTERNAL.
 // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external_essl3.txt
 //
 // The textureLod() doesn't support sampler2DRect for WR_FEATURE_TEXTURE_RECT, too.
 //
 // Use texture() instead.
-#if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT)
-#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord)
+#if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT) || defined(WR_FEATURE_TEXTURE_2D)
+#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy)
 #else
 // In normal case, we use textureLod(). We haven't used the lod yet. So, we always pass 0.0 now.
 #define TEX_SAMPLE(sampler, tex_coord) textureLod(sampler, tex_coord, 0.0)
 #endif
 
 //======================================================================================
 // Vertex shader attributes and uniforms
 //======================================================================================
@@ -47,28 +47,51 @@
 
     // Fragment shader outputs
     out vec4 oFragColor;
 #endif
 
 //======================================================================================
 // Shared shader uniforms
 //======================================================================================
-#ifdef WR_FEATURE_TEXTURE_RECT
+#if defined(GL_ES)
+    #if GL_ES == 1
+        #ifdef GL_FRAGMENT_PRECISION_HIGH
+        precision highp sampler2DArray;
+        #else
+        precision mediump sampler2DArray;
+        #endif
+
+        // Sampler default precision is lowp on mobile GPUs.
+        // This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880).
+        // Define highp precision macro to allow lossless FLOAT texture sampling.
+        #define HIGHP_SAMPLER_FLOAT highp
+    #else
+        #define HIGHP_SAMPLER_FLOAT
+    #endif
+#else
+    #define HIGHP_SAMPLER_FLOAT
+#endif
+
+#ifdef WR_FEATURE_TEXTURE_2D
+uniform sampler2D sColor0;
+uniform sampler2D sColor1;
+uniform sampler2D sColor2;
+#elif defined WR_FEATURE_TEXTURE_RECT
 uniform sampler2DRect sColor0;
 uniform sampler2DRect sColor1;
 uniform sampler2DRect sColor2;
 #elif defined WR_FEATURE_TEXTURE_EXTERNAL
 uniform samplerExternalOES sColor0;
 uniform samplerExternalOES sColor1;
 uniform samplerExternalOES sColor2;
 #else
-uniform sampler2D sColor0;
-uniform sampler2D sColor1;
-uniform sampler2D sColor2;
+uniform sampler2DArray sColor0;
+uniform sampler2DArray sColor1;
+uniform sampler2DArray sColor2;
 #endif
 
 #ifdef WR_FEATURE_DITHERING
 uniform sampler2D sDither;
 #endif
 
 //======================================================================================
 // Interpolator definitions
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, WorldPoint};
+use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyFrameInfo};
+use api::WorldPoint;
+use clip_scroll_tree::TransformUpdateState;
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipRegion, ClipSource, MaskCacheInfo};
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::{MatrixHelpers, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
@@ -56,25 +58,31 @@ impl ClipInfo {
             screen_inner_rect: DeviceIntRect::zero(),
             clip_rect: clip_rect,
         }
     }
 }
 
 #[derive(Clone, Debug)]
 pub enum NodeType {
-    /// Transform for this layer, relative to parent reference frame. A reference
-    /// frame establishes a new coordinate space in the tree.
-    ReferenceFrame(LayerToScrollTransform),
+    /// A reference frame establishes a new coordinate space in the tree.
+    ReferenceFrame(ReferenceFrameInfo),
 
     /// Other nodes just do clipping, but no transformation.
     Clip(ClipInfo),
 
-    /// Other nodes just do clipping, but no transformation.
+    /// Transforms it's content, but doesn't clip it. Can also be adjusted
+    /// by scroll events or setting scroll offsets.
     ScrollFrame(ScrollingState),
+
+    /// A special kind of node that adjusts its position based on the position
+    /// of its parent node and a given set of sticky positioning constraints.
+    /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
+    /// https://www.w3.org/TR/css-position-3/#sticky-pos
+    StickyFrame(StickyFrameInfo),
 }
 
 /// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Clone, Debug)]
 pub struct ClipScrollNode {
     /// Size of the content inside the scroll region (in logical pixels)
     pub content_size: LayerSize,
 
@@ -154,63 +162,88 @@ impl ClipScrollNode {
             pipeline_id,
             node_type: NodeType::Clip(clip_info),
         }
     }
 
     pub fn new_reference_frame(parent_id: Option<ClipId>,
                                local_viewport_rect: &LayerRect,
                                content_size: LayerSize,
-                               local_transform: &LayerToScrollTransform,
+                               transform: &LayerToScrollTransform,
+                               origin_in_parent_reference_frame: LayerVector2D,
                                pipeline_id: PipelineId)
                                -> ClipScrollNode {
+        let info = ReferenceFrameInfo {
+            transform: *transform,
+            origin_in_parent_reference_frame,
+        };
+
         ClipScrollNode {
             content_size,
             local_viewport_rect: *local_viewport_rect,
             local_clip_rect: *local_viewport_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: parent_id,
             children: Vec::new(),
             pipeline_id,
-            node_type: NodeType::ReferenceFrame(*local_transform),
+            node_type: NodeType::ReferenceFrame(info),
         }
     }
 
+    pub fn new_sticky_frame(parent_id: ClipId,
+                            frame_rect: LayerRect,
+                            sticky_frame_info: StickyFrameInfo,
+                            pipeline_id: PipelineId)
+                            -> ClipScrollNode {
+        ClipScrollNode {
+            content_size: frame_rect.size,
+            local_viewport_rect: frame_rect,
+            local_clip_rect: frame_rect,
+            combined_local_viewport_rect: LayerRect::zero(),
+            world_viewport_transform: LayerToWorldTransform::identity(),
+            world_content_transform: LayerToWorldTransform::identity(),
+            reference_frame_relative_scroll_offset: LayerVector2D::zero(),
+            parent: Some(parent_id),
+            children: Vec::new(),
+            pipeline_id,
+            node_type: NodeType::StickyFrame(sticky_frame_info),
+        }
+    }
+
+
     pub fn add_child(&mut self, child: ClipId) {
         self.children.push(child);
     }
 
     pub fn apply_old_scrolling_state(&mut self, new_scrolling: &ScrollingState) {
         match self.node_type {
-            NodeType::ReferenceFrame(_) | NodeType::Clip(_) => {
-                if new_scrolling.offset != LayerVector2D::zero() {
-                    warn!("Tried to scroll a non-scroll node.");
-                }
-            }
             NodeType::ScrollFrame(ref mut scrolling) => {
                 let scroll_sensitivity = scrolling.scroll_sensitivity;
                 *scrolling = *new_scrolling;
                 scrolling.scroll_sensitivity = scroll_sensitivity;
             }
+            _ if new_scrolling.offset != LayerVector2D::zero() =>
+                warn!("Tried to scroll a non-scroll node."),
+            _ => {}
         }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
         let scrollable_height = self.scrollable_height();
         let scrollable_width = self.scrollable_width();
 
         let scrolling = match self.node_type {
-            NodeType::ReferenceFrame(_) | NodeType::Clip(_) => {
+            NodeType::ScrollFrame(ref mut scrolling) => scrolling,
+            _ => {
                 warn!("Tried to scroll a non-scroll node.");
                 return false;
             }
-             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
         };
 
         let new_offset = match clamp {
             ScrollClamping::ToContentBounds => {
                 if scrollable_height <= 0. && scrollable_width <= 0. {
                     return false;
                 }
 
@@ -226,75 +259,125 @@ impl ClipScrollNode {
         }
 
         scrolling.offset = new_offset;
         scrolling.bouncing_back = false;
         scrolling.started_bouncing_back = false;
         true
     }
 
-    pub fn update_transform(&mut self,
-                            parent_reference_frame_transform: &LayerToWorldTransform,
-                            parent_combined_viewport_rect: &LayerRect,
-                            parent_scroll_offset: LayerVector2D,
-                            parent_accumulated_scroll_offset: LayerVector2D) {
+    pub fn update_transform(&mut self, state: &TransformUpdateState) {
+        let scrolled_parent_combined_clip = state.parent_combined_viewport_rect
+            .translate(&-state.parent_scroll_offset);
 
-        let scrolled_parent_combined_clip = parent_combined_viewport_rect
-            .translate(&-parent_scroll_offset);
-
-        let (local_transform, combined_clip, reference_frame_scroll_offset) = match self.node_type {
-            NodeType::ReferenceFrame(transform) => {
-                let combined_clip = transform.with_destination::<LayerPixel>()
-                                             .inverse_rect_footprint(&scrolled_parent_combined_clip);
-                (transform, combined_clip, LayerVector2D::zero())
+        let (local_transform, accumulated_scroll_offset) = match self.node_type {
+            NodeType::ReferenceFrame(ref info) => {
+                self.combined_local_viewport_rect =
+                    info.transform.with_destination::<LayerPixel>()
+                             .inverse_rect_footprint(&scrolled_parent_combined_clip);
+                self.reference_frame_relative_scroll_offset = LayerVector2D::zero();
+                (info.transform, state.parent_accumulated_scroll_offset)
             }
             NodeType::Clip(_) | NodeType::ScrollFrame(_) => {
                 // Move the parent's viewport into the local space (of the node origin)
                 // and intersect with the local clip rectangle to get the local viewport.
-                let combined_clip = scrolled_parent_combined_clip.intersection(&self.local_clip_rect)
-                                                                 .unwrap_or(LayerRect::zero());
-                (LayerToScrollTransform::identity(), combined_clip, parent_accumulated_scroll_offset)
+                self.combined_local_viewport_rect =
+                    scrolled_parent_combined_clip.intersection(&self.local_clip_rect)
+                                                 .unwrap_or(LayerRect::zero());
+                self.reference_frame_relative_scroll_offset =
+                    state.parent_accumulated_scroll_offset;
+                (LayerToScrollTransform::identity(), self.reference_frame_relative_scroll_offset)
+            }
+            NodeType::StickyFrame(sticky_frame_info) => {
+                let sticky_offset =
+                    self.calculate_sticky_offset(&self.local_viewport_rect,
+                                                 &sticky_frame_info,
+                                                 &state.nearest_scrolling_ancestor_offset,
+                                                 &state.nearest_scrolling_ancestor_viewport);
+
+                self.combined_local_viewport_rect =
+                    scrolled_parent_combined_clip.translate(&-sticky_offset)
+                                                 .intersection(&self.local_clip_rect)
+                                                 .unwrap_or(LayerRect::zero());
+                self.reference_frame_relative_scroll_offset =
+                    state.parent_accumulated_scroll_offset + sticky_offset;
+                (LayerToScrollTransform::identity(), self.reference_frame_relative_scroll_offset)
             }
         };
 
-        self.combined_local_viewport_rect = combined_clip;
-        self.reference_frame_relative_scroll_offset = reference_frame_scroll_offset;
-
         // The transformation for this viewport in world coordinates is the transformation for
         // our parent reference frame, plus any accumulated scrolling offsets from nodes
         // between our reference frame and this node. For reference frames, we also include
         // whatever local transformation this reference frame provides. This can be combined
         // with the local_viewport_rect to get its position in world space.
         self.world_viewport_transform =
-            parent_reference_frame_transform
-                .pre_translate(parent_accumulated_scroll_offset.to_3d())
+            state.parent_reference_frame_transform
+                .pre_translate(accumulated_scroll_offset.to_3d())
                 .pre_mul(&local_transform.with_destination::<LayerPixel>());
 
         // The transformation for any content inside of us is the viewport transformation, plus
         // whatever scrolling offset we supply as well.
         let scroll_offset = self.scroll_offset();
         self.world_content_transform =
             self.world_viewport_transform.pre_translate(scroll_offset.to_3d());
     }
 
+    fn calculate_sticky_offset(&self,
+                               sticky_rect: &LayerRect,
+                               sticky_frame_info: &StickyFrameInfo,
+                               viewport_scroll_offset: &LayerVector2D,
+                               viewport_rect: &LayerRect)
+                               -> LayerVector2D {
+        let sticky_rect = sticky_rect.translate(viewport_scroll_offset);
+        let mut sticky_offset = LayerVector2D::zero();
+
+        if let Some(info) = sticky_frame_info.top {
+            sticky_offset.y = viewport_rect.min_y() + info.margin - sticky_rect.min_y();
+            sticky_offset.y = sticky_offset.y.max(0.0).min(info.max_offset);
+        }
+
+        if sticky_offset.y == 0.0  {
+            if let Some(info) = sticky_frame_info.bottom {
+                sticky_offset.y = (viewport_rect.max_y() - info.margin) -
+                                  (sticky_offset.y + sticky_rect.min_y() + sticky_rect.size.height);
+                sticky_offset.y = sticky_offset.y.min(0.0).max(info.max_offset);
+            }
+        }
+
+        if let Some(info) = sticky_frame_info.left {
+            sticky_offset.x = viewport_rect.min_x() + info.margin - sticky_rect.min_x();
+            sticky_offset.x = sticky_offset.x.max(0.0).min(info.max_offset);
+        }
+
+        if sticky_offset.x == 0.0  {
+            if let Some(info) = sticky_frame_info.right {
+                sticky_offset.x = (viewport_rect.max_x() - info.margin) -
+                                  (sticky_offset.x + sticky_rect.min_x() + sticky_rect.size.width);
+                sticky_offset.x = sticky_offset.x.min(0.0).max(info.max_offset);
+            }
+        }
+
+        sticky_offset
+    }
+
     pub fn scrollable_height(&self) -> f32 {
         self.content_size.height - self.local_viewport_rect.size.height
     }
 
     pub fn scrollable_width(&self) -> f32 {
         self.content_size.width - self.local_viewport_rect.size.width
     }
 
     pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
         let scrollable_width = self.scrollable_width();
         let scrollable_height = self.scrollable_height();
 
         let scrolling = match self.node_type {
-            NodeType::ReferenceFrame(_) | NodeType::Clip(_) => return false,
             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
+            _ => return false,
         };
 
         if scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
             return false;
         }
 
         let mut delta = match scroll_location {
             ScrollLocation::Delta(delta) => delta,
@@ -465,8 +548,20 @@ impl ScrollingState {
         } else {
             0.0
         };
 
         LayerVector2D::new(overscroll_x, overscroll_y)
     }
 }
 
+/// Contains information about reference frames.
+#[derive(Copy, Clone, Debug)]
+pub struct ReferenceFrameInfo {
+    /// The transformation that establishes this reference frame, relative to the parent
+    /// reference frame. The origin of the reference frame is included in the transformation.
+    pub transform: LayerToScrollTransform,
+
+    /// The original, not including the transform and relative to the parent reference frame,
+    /// origin of this reference frame. This is already rolled into the `transform' property, but
+    /// we also store it here to properly transform the viewport for sticky positioning.
+    pub origin_in_parent_reference_frame: LayerVector2D,
+}
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use internal_types::{FastHashSet, FastHashMap};
 use print_tree::PrintTree;
-use api::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
-use api::{LayerToWorldTransform, PipelineId, ScrollClamping, ScrollEventPhase};
-use api::{LayerVector2D, ScrollLayerState, ScrollLocation, WorldPoint};
+use api::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform, LayerToWorldTransform};
+use api::{LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
+use api::{ScrollLocation, StickyFrameInfo, WorldPoint};
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 pub struct ClipScrollTree {
     pub nodes: FastHashMap<ClipId, ClipScrollNode>,
     pub pending_scroll_offsets: FastHashMap<ClipId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
@@ -32,16 +32,25 @@ pub struct ClipScrollTree {
     /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
     pub topmost_scrolling_node_id: ClipId,
 
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
 }
 
+pub struct TransformUpdateState {
+    pub parent_reference_frame_transform: LayerToWorldTransform,
+    pub parent_combined_viewport_rect: LayerRect,
+    pub parent_scroll_offset: LayerVector2D,
+    pub parent_accumulated_scroll_offset: LayerVector2D,
+    pub nearest_scrolling_ancestor_offset: LayerVector2D,
+    pub nearest_scrolling_ancestor_viewport: LayerRect,
+}
+
 impl ClipScrollTree {
     pub fn new() -> ClipScrollTree {
         let dummy_pipeline = PipelineId::dummy();
         ClipScrollTree {
             nodes: FastHashMap::default(),
             pending_scroll_offsets: FastHashMap::default(),
             currently_scrolling_node_id: None,
             root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
@@ -221,78 +230,72 @@ impl ClipScrollTree {
 
     pub fn update_all_node_transforms(&mut self, pan: LayerPoint) {
         if self.nodes.is_empty() {
             return;
         }
 
         let root_reference_frame_id = self.root_reference_frame_id();
         let root_viewport = self.nodes[&root_reference_frame_id].local_clip_rect;
-        self.update_node_transform(root_reference_frame_id,
-                                   &LayerToWorldTransform::create_translation(pan.x, pan.y, 0.0),
-                                   &root_viewport,
-                                   LayerVector2D::zero(),
-                                   LayerVector2D::zero());
+        let state = TransformUpdateState {
+            parent_reference_frame_transform:
+                LayerToWorldTransform::create_translation(pan.x, pan.y, 0.0),
+            parent_combined_viewport_rect: root_viewport,
+            parent_scroll_offset: LayerVector2D::zero(),
+            parent_accumulated_scroll_offset: LayerVector2D::zero(),
+            nearest_scrolling_ancestor_offset: LayerVector2D::zero(),
+            nearest_scrolling_ancestor_viewport: LayerRect::zero(),
+        };
+        self.update_node_transform(root_reference_frame_id, &state);
     }
 
-    fn update_node_transform(&mut self,
-                             layer_id: ClipId,
-                             parent_reference_frame_transform: &LayerToWorldTransform,
-                             parent_viewport_rect: &LayerRect,
-                             parent_scroll_offset: LayerVector2D,
-                             parent_accumulated_scroll_offset: LayerVector2D) {
+    fn update_node_transform(&mut self, layer_id: ClipId, state: &TransformUpdateState) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
-        let (reference_frame_transform,
-             combined_local_viewport_rect,
-             scroll_offset,
-             accumulated_scroll_offset,
-             node_children) = {
-
+        let (state, node_children) = {
             let mut node = match self.nodes.get_mut(&layer_id) {
                 Some(node) => node,
                 None => return,
             };
-            node.update_transform(parent_reference_frame_transform,
-                                  parent_viewport_rect,
-                                  parent_scroll_offset,
-                                  parent_accumulated_scroll_offset);
+            node.update_transform(&state);
 
             // 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.
-            let (reference_frame_transform, scroll_offset, accumulated_scroll_offset) = match node.node_type {
-                NodeType::ReferenceFrame(..) =>
-                    (node.world_viewport_transform,
-                     LayerVector2D::zero(),
-                     LayerVector2D::zero()),
-                NodeType::Clip(..) =>
-                    (*parent_reference_frame_transform,
-                     LayerVector2D::zero(),
-                     parent_accumulated_scroll_offset),
-                NodeType::ScrollFrame(ref scrolling) =>
-                    (*parent_reference_frame_transform,
-                     scrolling.offset,
-                     scrolling.offset + parent_accumulated_scroll_offset),
+            let state = match node.node_type {
+                NodeType::ReferenceFrame(ref info) => TransformUpdateState {
+                    parent_reference_frame_transform: node.world_viewport_transform,
+                    parent_combined_viewport_rect: node.combined_local_viewport_rect,
+                    parent_scroll_offset: LayerVector2D::zero(),
+                    parent_accumulated_scroll_offset: LayerVector2D::zero(),
+                    nearest_scrolling_ancestor_viewport:
+                        state.nearest_scrolling_ancestor_viewport.translate(&info.origin_in_parent_reference_frame),
+                    ..*state
+                },
+                NodeType::Clip(..) | NodeType::StickyFrame(..) => TransformUpdateState {
+                    parent_combined_viewport_rect: node.combined_local_viewport_rect,
+                    parent_scroll_offset: LayerVector2D::zero(),
+                    ..*state
+                },
+                NodeType::ScrollFrame(ref scrolling) => TransformUpdateState {
+                    parent_combined_viewport_rect: node.combined_local_viewport_rect,
+                    parent_scroll_offset: scrolling.offset,
+                    parent_accumulated_scroll_offset: scrolling.offset + state.parent_accumulated_scroll_offset,
+                    nearest_scrolling_ancestor_offset: scrolling.offset,
+                    nearest_scrolling_ancestor_viewport: node.local_viewport_rect,
+                    ..*state
+                },
             };
 
-            (reference_frame_transform,
-             node.combined_local_viewport_rect,
-             scroll_offset,
-             accumulated_scroll_offset,
-             node.children.clone())
+            (state, node.children.clone())
         };
 
         for child_layer_id in node_children {
-            self.update_node_transform(child_layer_id,
-                                       &reference_frame_transform,
-                                       &combined_local_viewport_rect,
-                                       scroll_offset,
-                                       accumulated_scroll_offset);
+            self.update_node_transform(child_layer_id, &state);
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
     }
@@ -315,29 +318,43 @@ impl ClipScrollTree {
         let new_id = ClipId::DynamicallyAddedNode(self.current_new_node_item, pipeline_id);
         self.current_new_node_item += 1;
         new_id
     }
 
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
                                transform: &LayerToScrollTransform,
+                               origin_in_parent_reference_frame: LayerVector2D,
                                pipeline_id: PipelineId,
                                parent_id: Option<ClipId>)
                                -> ClipId {
         let reference_frame_id = self.generate_new_clip_id(pipeline_id);
         let node = ClipScrollNode::new_reference_frame(parent_id,
                                                        rect,
                                                        rect.size,
                                                        transform,
+                                                       origin_in_parent_reference_frame,
                                                        pipeline_id);
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
 
+    pub fn add_sticky_frame(&mut self,
+                            id: ClipId,
+                            parent_id: ClipId,
+                            frame_rect: LayerRect,
+                            sticky_frame_info: StickyFrameInfo) {
+        let node = ClipScrollNode::new_sticky_frame(parent_id,
+                                                    frame_rect,
+                                                    sticky_frame_info,
+                                                    id.pipeline_id());
+        self.add_node(node, id);
+    }
+
     pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
         // When the parent node is None this means we are adding the root.
         match node.parent {
             Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),
             None => self.root_reference_frame_id = id,
         }
 
         debug_assert!(!self.nodes.contains_key(&id));
@@ -363,23 +380,27 @@ impl ClipScrollTree {
                 pt.add_item(format!("screen_inner_rect: {:?}", info.screen_inner_rect));
 
                 pt.new_level(format!("Clip Sources [{}]", info.clip_sources.len()));
                 for source in &info.clip_sources {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
-            NodeType::ReferenceFrame(ref transform) => {
-                pt.new_level(format!("ReferenceFrame {:?}", transform));
+            NodeType::ReferenceFrame(ref info) => {
+                pt.new_level(format!("ReferenceFrame {:?}", info.transform));
             }
             NodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
                 pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
             }
+            NodeType::StickyFrame(sticky_frame_info) => {
+                pt.new_level(format!("StickyFrame"));
+                pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
+            }
         }
 
         pt.add_item(format!("content_size: {:?}", node.content_size));
         pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
         pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
         pt.add_item(format!("combined_local_viewport_rect: {:?}", node.combined_local_viewport_rect));
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use debug_font_data;
-use device::{Device, GpuMarker, Program, VAOId, TextureId, VertexDescriptor};
+use device::{Device, GpuMarker, Program, VAO, TextureId, VertexDescriptor};
 use device::{TextureFilter, VertexAttribute, VertexUsageHint, VertexAttributeKind, TextureTarget};
 use euclid::{Transform3D, Point2D, Size2D, Rect};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, TextureSampler};
 use internal_types::RenderTargetMode;
 use std::f32;
 use api::{ColorU, ImageFormat, DeviceUintSize};
 
 const DESC_FONT: VertexDescriptor = VertexDescriptor {
@@ -65,43 +65,44 @@ impl DebugColorVertex {
         }
     }
 }
 
 pub struct DebugRenderer {
     font_vertices: Vec<DebugFontVertex>,
     font_indices: Vec<u32>,
     font_program: Program,
-    font_vao: VAOId,
+    font_vao: VAO,
     font_texture_id: TextureId,
 
     tri_vertices: Vec<DebugColorVertex>,
     tri_indices: Vec<u32>,
-    tri_vao: VAOId,
+    tri_vao: VAO,
     line_vertices: Vec<DebugColorVertex>,
-    line_vao: VAOId,
+    line_vao: VAO,
     color_program: Program,
 }
 
 impl DebugRenderer {
     pub fn new(device: &mut Device) -> DebugRenderer {
         let font_program = device.create_program("debug_font", "shared_other", &DESC_FONT).unwrap();
         let color_program = device.create_program("debug_color", "shared_other", &DESC_COLOR).unwrap();
 
         let font_vao = device.create_vao(&DESC_FONT, 32);
         let line_vao = device.create_vao(&DESC_COLOR, 32);
         let tri_vao = device.create_vao(&DESC_COLOR, 32);
 
-        let font_texture_id = device.create_texture_ids(1, TextureTarget::Default)[0];
+        let font_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
         device.init_texture(font_texture_id,
                             debug_font_data::BMP_WIDTH,
                             debug_font_data::BMP_HEIGHT,
                             ImageFormat::A8,
                             TextureFilter::Linear,
                             RenderTargetMode::None,
+                            1,
                             Some(&debug_font_data::FONT_BITMAP));
 
         DebugRenderer {
             font_vertices: Vec::new(),
             font_indices: Vec::new(),
             line_vertices: Vec::new(),
             tri_vao,
             tri_vertices: Vec::new(),
@@ -109,19 +110,22 @@ impl DebugRenderer {
             font_program,
             color_program,
             font_vao,
             line_vao,
             font_texture_id,
         }
     }
 
-    pub fn deinit(&mut self, device: &mut Device) {
-        device.delete_program(&mut self.font_program);
-        device.delete_program(&mut self.color_program);
+    pub fn deinit(self, device: &mut Device) {
+        device.delete_program(self.font_program);
+        device.delete_program(self.color_program);
+        device.delete_vao(self.tri_vao);
+        device.delete_vao(self.line_vao);
+        device.delete_vao(self.font_vao);
     }
 
     pub fn line_height(&self) -> f32 {
         debug_font_data::FONT_SIZE as f32 * 1.1
     }
 
     pub fn add_text(&mut self,
                     x: f32,
@@ -227,47 +231,47 @@ impl DebugRenderer {
                                             0.0,
                                             ORTHO_NEAR_PLANE,
                                             ORTHO_FAR_PLANE);
 
         // Triangles
         if !self.tri_vertices.is_empty() {
             device.bind_program(&self.color_program);
             device.set_uniforms(&self.color_program, &projection);
-            device.bind_vao(self.tri_vao);
-            device.update_vao_indices(self.tri_vao,
+            device.bind_vao(&self.tri_vao);
+            device.update_vao_indices(&self.tri_vao,
                                       &self.tri_indices,
                                       VertexUsageHint::Dynamic);
-            device.update_vao_main_vertices(self.tri_vao,
+            device.update_vao_main_vertices(&self.tri_vao,
                                             &self.tri_vertices,
                                             VertexUsageHint::Dynamic);
             device.draw_triangles_u32(0, self.tri_indices.len() as i32);
         }
 
         // Lines
         if !self.line_vertices.is_empty() {
             device.bind_program(&self.color_program);
             device.set_uniforms(&self.color_program, &projection);
-            device.bind_vao(self.line_vao);
-            device.update_vao_main_vertices(self.line_vao,
+            device.bind_vao(&self.line_vao);
+            device.update_vao_main_vertices(&self.line_vao,
                                             &self.line_vertices,
                                             VertexUsageHint::Dynamic);
             device.draw_nonindexed_lines(0, self.line_vertices.len() as i32);
         }
 
         // Glyph
         if !self.font_indices.is_empty() {
             device.bind_program(&self.font_program);
             device.set_uniforms(&self.font_program, &projection);
             device.bind_texture(TextureSampler::Color0, self.font_texture_id);
-            device.bind_vao(self.font_vao);
-            device.update_vao_indices(self.font_vao,
+            device.bind_vao(&self.font_vao);
+            device.update_vao_indices(&self.font_vao,
                                       &self.font_indices,
                                       VertexUsageHint::Dynamic);
-            device.update_vao_main_vertices(self.font_vao,
+            device.update_vao_main_vertices(&self.font_vao,
                                             &self.font_vertices,
                                             VertexUsageHint::Dynamic);
             device.draw_triangles_u32(0, self.font_indices.len() as i32);
         }
 
         self.font_indices.clear();
         self.font_vertices.clear();
         self.line_vertices.clear();
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1,26 +1,24 @@
 /* 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 euclid::Transform3D;
 use gleam::gl;
 use internal_types::{RenderTargetMode, TextureSampler, DEFAULT_TEXTURE, FastHashMap};
-//use notify::{self, Watcher};
 use super::shader_source;
 use std::fs::File;
 use std::io::Read;
 use std::iter::repeat;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
-//use std::sync::mpsc::{channel, Sender};
 use std::thread;
 use api::{ColorF, ImageFormat};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintSize};
 
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
 pub struct FrameId(usize);
 
 impl FrameId {
@@ -84,16 +82,17 @@ pub enum TextureFilter {
     Linear,
 }
 
 #[derive(Debug)]
 pub enum VertexAttributeKind {
     F32,
     U8Norm,
     I32,
+    U16,
 }
 
 #[derive(Debug)]
 pub struct VertexAttribute {
     pub name: &'static str,
     pub count: u32,
     pub kind: VertexAttributeKind,
 }
@@ -154,16 +153,17 @@ pub trait FileWatcherHandler : Send {
 }
 
 impl VertexAttributeKind {
     fn size_in_bytes(&self) -> u32 {
         match *self {
             VertexAttributeKind::F32 => 4,
             VertexAttributeKind::U8Norm => 1,
             VertexAttributeKind::I32 => 4,
+            VertexAttributeKind::U16 => 2,
         }
     }
 }
 
 impl VertexAttribute {
     fn size_in_bytes(&self) -> u32 {
         self.count * self.kind.size_in_bytes()
     }
@@ -196,16 +196,23 @@ impl VertexAttribute {
             }
             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 bind(&self,
             gl: &gl::Gl,
             main: VBOId,
@@ -291,16 +298,17 @@ impl FBOId {
         };
         gl.bind_framebuffer(target, self.0);
     }
 }
 
 struct Texture {
     gl: Rc<gl::Gl>,
     id: gl::GLuint,
+    layer_count: i32,
     format: ImageFormat,
     width: u32,
     height: u32,
 
     filter: TextureFilter,
     mode: RenderTargetMode,
     fbo_ids: Vec<FBOId>,
     depth_rb: Option<RBOId>,
@@ -356,59 +364,42 @@ impl Program {
         }
 
         Ok(())
     }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
-        debug_assert!(thread::panicking() || self.id == 0);
+        debug_assert!(thread::panicking() || self.id == 0, "renderer::deinit not called");
     }
 }
 
-struct VAO {
-    gl: Rc<gl::Gl>,
+pub struct VAO {
     id: gl::GLuint,
     ibo_id: IBOId,
     main_vbo_id: VBOId,
     instance_vbo_id: VBOId,
     instance_stride: gl::GLint,
-    owns_indices: bool,
-    owns_vertices: bool,
-    owns_instances: bool,
+    owns_vertices_and_indices: bool,
 }
 
 impl Drop for VAO {
     fn drop(&mut self) {
-        self.gl.delete_vertex_arrays(&[self.id]);
-
-        if self.owns_indices {
-            // todo(gw): maybe make these their own type with hashmap?
-            self.gl.delete_buffers(&[self.ibo_id.0]);
-        }
-        if self.owns_vertices {
-            self.gl.delete_buffers(&[self.main_vbo_id.0]);
-        }
-        if self.owns_instances {
-            self.gl.delete_buffers(&[self.instance_vbo_id.0])
-        }
+        debug_assert!(thread::panicking() || self.id == 0, "renderer::deinit not called");
     }
 }
 
 #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Copy, Clone)]
 pub struct TextureId {
     name: gl::GLuint,
     target: gl::GLuint,
 }
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-pub struct VAOId(gl::GLuint);
-
-#[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);
 
@@ -685,174 +676,91 @@ impl VertexUsageHint {
 pub struct UniformLocation(gl::GLint);
 
 impl UniformLocation {
     pub fn invalid() -> UniformLocation {
         UniformLocation(-1)
     }
 }
 
-// TODO(gw): Fix up notify cargo deps and re-enable this!
-/*
-enum FileWatcherCmd {
-    AddWatch(PathBuf),
-    Exit,
-}
-
-struct FileWatcherThread {
-    api_tx: Sender<FileWatcherCmd>,
-}
-
-impl FileWatcherThread {
-    fn new(handler: Box<FileWatcherHandler>) -> FileWatcherThread {
-        let (api_tx, api_rx) = channel();
-
-        thread::spawn(move || {
-
-            let (watch_tx, watch_rx) = channel();
-
-            enum Request {
-                Watcher(notify::Event),
-                Command(FileWatcherCmd),
-            }
-
-            let mut file_watcher: notify::RecommendedWatcher = notify::Watcher::new(watch_tx).unwrap();
-
-            loop {
-                let request = {
-                    let receiver_from_api = &api_rx;
-                    let receiver_from_watcher = &watch_rx;
-                    select! {
-                        msg = receiver_from_api.recv() => Request::Command(msg.unwrap()),
-                        msg = receiver_from_watcher.recv() => Request::Watcher(msg.unwrap())
-                    }
-                };
-
-                match request {
-                    Request::Watcher(event) => {
-                        handler.file_changed(event.path.unwrap());
-                    }
-                    Request::Command(cmd) => {
-                        match cmd {
-                            FileWatcherCmd::AddWatch(path) => {
-                                file_watcher.watch(path).ok();
-                            }
-                            FileWatcherCmd::Exit => {
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-        });
-
-        FileWatcherThread {
-            api_tx,
-        }
-    }
-
-    fn exit(&self) {
-        self.api_tx.send(FileWatcherCmd::Exit).ok();
-    }
-
-    fn add_watch(&self, path: PathBuf) {
-        self.api_tx.send(FileWatcherCmd::AddWatch(path)).ok();
-    }
-}
-*/
-
 pub struct Capabilities {
     pub supports_multisampling: bool,
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
     Compilation(String, String), // name, error mssage
     Link(String, String), // name, error message
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [TextureId; 16],
     bound_program: gl::GLuint,
-    bound_vao: VAOId,
+    bound_vao: gl::GLuint,
     bound_pbo: PBOId,
     bound_read_fbo: FBOId,
     bound_draw_fbo: FBOId,
     default_read_fbo: gl::GLuint,
     default_draw_fbo: gl::GLuint,
     device_pixel_ratio: f32,
 
     // HW or API capabilties
     capabilities: Capabilities,
 
     // debug
     inside_frame: bool,
 
     // resources
     resource_override_path: Option<PathBuf>,
     textures: FastHashMap<TextureId, Texture>,
-    vaos: FastHashMap<VAOId, VAO>,
 
     // misc.
     shader_preamble: String,
-    //file_watcher: FileWatcherThread,
-
-    // Used on android only
-    #[allow(dead_code)]
-    next_vao_id: gl::GLuint,
 
     max_texture_size: u32,
 
     // Frame counter. This is used to map between CPU
     // frames and GPU frames.
     frame_id: FrameId,
 }
 
 impl Device {
     pub fn new(gl: Rc<gl::Gl>,
                resource_override_path: Option<PathBuf>,
                _file_changed_handler: Box<FileWatcherHandler>) -> Device {
-        //let file_watcher = FileWatcherThread::new(file_changed_handler);
-
         let shader_preamble = get_shader_source(SHADER_PREAMBLE, &resource_override_path);
-        //file_watcher.add_watch(resource_path);
-
         let max_texture_size = gl.get_integer_v(gl::MAX_TEXTURE_SIZE) as u32;
 
         Device {
             gl,
             resource_override_path,
             // This is initialized to 1 by default, but it is set
             // every frame by the call to begin_frame().
             device_pixel_ratio: 1.0,
             inside_frame: false,
 
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
             },
 
             bound_textures: [ TextureId::invalid(); 16 ],
             bound_program: 0,
-            bound_vao: VAOId(0),
+            bound_vao: 0,
             bound_pbo: PBOId(0),
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             default_read_fbo: 0,
             default_draw_fbo: 0,
 
             textures: FastHashMap::default(),
-            vaos: FastHashMap::default(),
 
             shader_preamble,
 
-            next_vao_id: 1,
-            //file_watcher: file_watcher,
-
             max_texture_size,
             frame_id: FrameId(0),
         }
     }
 
     pub fn gl(&self) -> &gl::Gl {
         &*self.gl
     }
@@ -919,18 +827,18 @@ impl Device {
             self.gl.bind_texture(gl::TEXTURE_2D, 0);
         }
 
         // Shader state
         self.bound_program = 0;
         self.gl.use_program(0);
 
         // Vertex state
-        self.bound_vao = VAOId(0);
-        self.clear_vertex_array();
+        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.bound_pbo = PBOId(0);
@@ -1009,32 +917,38 @@ impl Device {
                 target: target.to_gl_target(),
             };
 
             let texture = Texture {
                 gl: Rc::clone(&self.gl),
                 id,
                 width: 0,
                 height: 0,
+                layer_count: 0,
                 format: ImageFormat::Invalid,
                 filter: TextureFilter::Nearest,
                 mode: RenderTargetMode::None,
                 fbo_ids: vec![],
                 depth_rb: None,
             };
 
             debug_assert!(self.textures.contains_key(&texture_id) == false);
             self.textures.insert(texture_id, texture);
 
             texture_ids.push(texture_id);
         }
 
         texture_ids
     }
 
+    pub fn get_texture_layer_count(&self, texture_id: TextureId) -> i32 {
+        let texture = &self.textures[&texture_id];
+        texture.layer_count
+    }
+
     pub fn get_texture_dimensions(&self, texture_id: TextureId) -> DeviceUintSize {
         let texture = &self.textures[&texture_id];
         DeviceUintSize::new(texture.width, texture.height)
     }
 
     fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
         let filter = match filter {
             TextureFilter::Nearest => {
@@ -1047,193 +961,164 @@ impl Device {
 
         self.gl.tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, filter as gl::GLint);
         self.gl.tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, 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);
     }
 
-    fn upload_texture_image(&mut self,
-                            target: gl::GLuint,
-                            width: u32,
-                            height: u32,
-                            internal_format: u32,
-                            format: u32,
-                            type_: u32,
-                            pixels: Option<&[u8]>) {
-        self.gl.tex_image_2d(target,
-                              0,
-                              internal_format as gl::GLint,
-                              width as gl::GLint, height as gl::GLint,
-                              0,
-                              format,
-                              type_,
-                              pixels);
-    }
-
     pub fn init_texture(&mut self,
                         texture_id: TextureId,
                         width: u32,
                         height: u32,
                         format: ImageFormat,
                         filter: TextureFilter,
                         mode: RenderTargetMode,
+                        layer_count: i32,
                         pixels: Option<&[u8]>) {
         debug_assert!(self.inside_frame);
 
         let resized;
         {
             let texture = self.textures.get_mut(&texture_id).expect("Didn't find texture!");
             texture.format = format;
             resized = texture.width != width || texture.height != height;
             texture.width = width;
             texture.height = height;
             texture.filter = filter;
+            texture.layer_count = layer_count;
             texture.mode = mode;
         }
 
         let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format);
         let type_ = gl_type_for_texture_format(format);
 
         match mode {
-            RenderTargetMode::SimpleRenderTarget => {
+            RenderTargetMode::RenderTarget => {
                 self.bind_texture(DEFAULT_TEXTURE, texture_id);
                 self.set_texture_parameters(texture_id.target, filter);
-                self.upload_texture_image(texture_id.target,
-                                          width,
-                                          height,
-                                          internal_format as u32,
-                                          gl_format,
-                                          type_,
-                                          None);
-                self.update_texture_storage(texture_id, None, resized);
-            }
-            RenderTargetMode::LayerRenderTarget(layer_count) => {
-                self.bind_texture(DEFAULT_TEXTURE, texture_id);
-                self.set_texture_parameters(texture_id.target, filter);
-                self.update_texture_storage(texture_id, Some(layer_count), resized);
+                self.update_texture_storage(texture_id, layer_count, resized);
             }
             RenderTargetMode::None => {
                 self.bind_texture(DEFAULT_TEXTURE, texture_id);
                 self.set_texture_parameters(texture_id.target, filter);
                 let expanded_data: Vec<u8>;
                 let actual_pixels = if pixels.is_some() &&
                                        format == ImageFormat::A8 &&
                                        cfg!(any(target_arch="arm", target_arch="aarch64")) {
                     expanded_data = pixels.unwrap().iter().flat_map(|&byte| repeat(byte).take(4)).collect();
                     Some(expanded_data.as_slice())
                 } else {
                     pixels
                 };
-                self.upload_texture_image(texture_id.target,
-                                          width,
-                                          height,
-                                          internal_format as u32,
-                                          gl_format,
-                                          type_,
-                                          actual_pixels);
+
+                match texture_id.target {
+                    gl::TEXTURE_2D_ARRAY => {
+                        self.gl.tex_image_3d(gl::TEXTURE_2D_ARRAY,
+                                             0,
+                                             internal_format as gl::GLint,
+                                             width as gl::GLint,
+                                             height as gl::GLint,
+                                             layer_count,
+                                             0,
+                                             gl_format,
+                                             type_,
+                                             actual_pixels);
+                    }
+                    gl::TEXTURE_2D |
+                    gl::TEXTURE_RECTANGLE |
+                    gl::TEXTURE_EXTERNAL_OES => {
+                        self.gl.tex_image_2d(texture_id.target,
+                                             0,
+                                             internal_format as gl::GLint,
+                                             width as gl::GLint, height as gl::GLint,
+                                             0,
+                                             gl_format,
+                                             type_,
+                                             actual_pixels);
+                    }
+                    _ => panic!("BUG: Unexpected texture target!"),
+                }
             }
         }
     }
 
     pub fn get_render_target_layer_count(&self, texture_id: TextureId) -> usize {
         self.textures[&texture_id].fbo_ids.len()
     }
 
     /// Updates the texture storage for the texture, creating
     /// FBOs as required.
     pub fn update_texture_storage(&mut self,
                                   texture_id: TextureId,
-                                  layer_count: Option<i32>,
+                                  layer_count: i32,
                                   resized: bool) {
         let texture = self.textures.get_mut(&texture_id).unwrap();
 
-        match layer_count {
-            Some(layer_count) => {
-                assert!(layer_count > 0);
-                assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY);
+        assert!(layer_count > 0);
+        assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY);
 
-                let current_layer_count = texture.fbo_ids.len() as i32;
-                // If the texture is already the required size skip.
-                if current_layer_count == layer_count && !resized {
-                    return;
-                }
-
-                let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
-                let type_ = gl_type_for_texture_format(texture.format);
+        let current_layer_count = texture.fbo_ids.len() as i32;
+        // If the texture is already the required size skip.
+        if current_layer_count == layer_count && !resized {
+            return;
+        }
 
-                self.gl.tex_image_3d(texture_id.target,
-                                     0,
-                                     internal_format as gl::GLint,
-                                     texture.width as gl::GLint,
-                                     texture.height as gl::GLint,
-                                     layer_count,
-                                     0,
-                                     gl_format,
-                                     type_,
-                                     None);
+        let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
+        let type_ = gl_type_for_texture_format(texture.format);
 
-                let needed_layer_count = layer_count - current_layer_count;
-                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(|id| FBOId(id)));
-                } else if needed_layer_count < 0 {
-                    // Remove extra framebuffers
-                    for old in texture.fbo_ids.drain(layer_count as usize ..) {
-                        self.gl.delete_framebuffers(&[old.0]);
-                    }
-                }
+        self.gl.tex_image_3d(texture_id.target,
+                             0,
+                             internal_format as gl::GLint,
+                             texture.width as gl::GLint,
+                             texture.height as gl::GLint,
+                             layer_count,
+                             0,
+                             gl_format,
+                             type_,
+                             None);
 
-                let depth_rb = if let Some(rbo) = texture.depth_rb {
-                    rbo.0
-                } else {
-                    let renderbuffer_ids = self.gl.gen_renderbuffers(1);
-                    let depth_rb = renderbuffer_ids[0];
-                    texture.depth_rb = Some(RBOId(depth_rb));
-                    depth_rb
-                };
-                self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
-                self.gl.renderbuffer_storage(gl::RENDERBUFFER,
-                                             gl::DEPTH_COMPONENT24,
-                                             texture.width as gl::GLsizei,
-                                             texture.height as gl::GLsizei);
+        let needed_layer_count = layer_count - current_layer_count;
+        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(|id| FBOId(id)));
+        } else if needed_layer_count < 0 {
+            // Remove extra framebuffers
+            for old in texture.fbo_ids.drain(layer_count as usize ..) {
+                self.gl.delete_framebuffers(&[old.0]);
+            }
+        }
 
-                for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
-                    self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
-                    self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
-                                                      gl::COLOR_ATTACHMENT0,
-                                                      texture_id.name,
-                                                      0,
-                                                      fbo_index as gl::GLint);
-                    self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER,
-                                                     gl::DEPTH_ATTACHMENT,
-                                                     gl::RENDERBUFFER,
-                                                     depth_rb);
-                }
-            }
-            None => {
-                if texture.fbo_ids.is_empty() {
-                    assert!(texture_id.target != gl::TEXTURE_2D_ARRAY);
+        let depth_rb = if let Some(rbo) = texture.depth_rb {
+            rbo.0
+        } else {
+            let renderbuffer_ids = self.gl.gen_renderbuffers(1);
+            let depth_rb = renderbuffer_ids[0];
+            texture.depth_rb = Some(RBOId(depth_rb));
+            depth_rb
+        };
+        self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
+        self.gl.renderbuffer_storage(gl::RENDERBUFFER,
+                                     gl::DEPTH_COMPONENT24,
+                                     texture.width as gl::GLsizei,
+                                     texture.height as gl::GLsizei);
 
-                    let new_fbo = self.gl.gen_framebuffers(1)[0];
-                    self.gl.bind_framebuffer(gl::FRAMEBUFFER, new_fbo);
-
-                    self.gl.framebuffer_texture_2d(gl::FRAMEBUFFER,
-                                                   gl::COLOR_ATTACHMENT0,
-                                                   texture_id.target,
-                                                   texture_id.name,
-                                                   0);
-
-                    texture.fbo_ids.push(FBOId(new_fbo));
-                } else {
-                    assert_eq!(texture.fbo_ids.len(), 1);
-                }
-            }
+        for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
+            self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
+            self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
+                                              gl::COLOR_ATTACHMENT0,
+                                              texture_id.name,
+                                              0,
+                                              fbo_index as gl::GLint);
+            self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER,
+                                             gl::DEPTH_ATTACHMENT,
+                                             gl::RENDERBUFFER,
+                                             depth_rb);
         }
 
         // TODO(gw): Hack! Modify the code above to use the normal binding interfaces the device exposes.
         self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.bound_read_fbo.0);
         self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.bound_draw_fbo.0);
     }
 
     pub fn blit_render_target(&mut self,
@@ -1258,106 +1143,78 @@ impl Device {
                                   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);
     }
 
-    pub fn resize_texture(&mut self,
-                          texture_id: TextureId,
-                          new_width: u32,
-                          new_height: u32,
-                          format: ImageFormat,
-                          filter: TextureFilter,
-                          mode: RenderTargetMode) {
-        debug_assert!(self.inside_frame);
-
-        let old_size = self.get_texture_dimensions(texture_id);
-
-        let temp_texture_id = self.create_texture_ids(1, TextureTarget::Default)[0];
-        self.init_texture(temp_texture_id, old_size.width, old_size.height, format, filter, mode, None);
-        self.update_texture_storage(temp_texture_id, None, true);
-
-        self.bind_read_target(Some((texture_id, 0)));
-        self.bind_texture(DEFAULT_TEXTURE, temp_texture_id);
-
-        self.gl.copy_tex_sub_image_2d(temp_texture_id.target,
-                                       0,
-                                       0,
-                                       0,
-                                       0,
-                                       0,
-                                       old_size.width as i32,
-                                       old_size.height as i32);
-
-        self.deinit_texture(texture_id);
-        self.init_texture(texture_id, new_width, new_height, format, filter, mode, None);
-        self.update_texture_storage(texture_id, None, true);
-        self.bind_read_target(Some((temp_texture_id, 0)));
-        self.bind_texture(DEFAULT_TEXTURE, texture_id);
-
-        self.gl.copy_tex_sub_image_2d(texture_id.target,
-                                       0,
-                                       0,
-                                       0,
-                                       0,
-                                       0,
-                                       old_size.width as i32,
-                                       old_size.height as i32);
-
-        self.bind_read_target(None);
-        self.deinit_texture(temp_texture_id);
-    }
-
     pub fn deinit_texture(&mut self, texture_id: TextureId) {
         debug_assert!(self.inside_frame);
 
         self.bind_texture(DEFAULT_TEXTURE, texture_id);
 
         let texture = self.textures.get_mut(&texture_id).unwrap();
         let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
         let type_ = gl_type_for_texture_format(texture.format);
 
-        self.gl.tex_image_2d(texture_id.target,
-                              0,
-                              internal_format,
-                              0,
-                              0,
-                              0,
-                              gl_format,
-                              type_,
-                              None);
+        match texture_id.target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_image_3d(gl::TEXTURE_2D_ARRAY,
+                                     0,
+                                     internal_format as gl::GLint,
+                                     0,
+                                     0,
+                                     0,
+                                     0,
+                                     gl_format,
+                                     type_,
+                                     None);
+            }
+            _ => {
+                self.gl.tex_image_2d(texture_id.target,
+                                     0,
+                                     internal_format,
+                                     0,
+                                     0,
+                                     0,
+                                     gl_format,
+                                     type_,
+                                     None);
+            }
+        }
+
 
         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.format = ImageFormat::Invalid;
         texture.width = 0;
         texture.height = 0;
+        texture.layer_count = 0;
     }
 
     pub fn create_program(&mut self,
                           base_filename: &str,
                           include_filename: &str,
                           descriptor: &VertexDescriptor) -> Result<Program, ShaderError> {
         self.create_program_with_prefix(base_filename,
                                         &[include_filename],
                                         None,
                                         descriptor)
     }
 
-    pub fn delete_program(&mut self, program: &mut Program) {
+    pub fn delete_program(&mut self, mut program: Program) {
         self.gl.delete_program(program.id);
         program.id = 0;
     }
 
     pub fn create_program_with_prefix(&mut self,
                                       base_filename: &str,
                                       include_filenames: &[&str],
                                       prefix: Option<String>,
@@ -1502,53 +1359,16 @@ impl Device {
         let u_resource_cache = self.gl.get_uniform_location(program.id, "sResourceCache");
         if u_resource_cache != -1 {
             self.gl.uniform_1i(u_resource_cache, TextureSampler::ResourceCache as i32);
         }
 
         Ok(())
     }
 
-/*
-    pub fn refresh_shader(&mut self, path: PathBuf) {
-        let mut vs_preamble_path = self.resource_path.clone();
-        vs_preamble_path.push(VERTEX_SHADER_PREAMBLE);
-
-        let mut fs_preamble_path = self.resource_path.clone();
-        fs_preamble_path.push(FRAGMENT_SHADER_PREAMBLE);
-
-        let mut refresh_all = false;
-
-        if path == vs_preamble_path {
-            let mut f = File::open(&vs_preamble_path).unwrap();
-            self.vertex_shader_preamble = String::new();
-            f.read_to_string(&mut self.vertex_shader_preamble).unwrap();
-            refresh_all = true;
-        }
-
-        if path == fs_preamble_path {
-            let mut f = File::open(&fs_preamble_path).unwrap();
-            self.fragment_shader_preamble = String::new();
-            f.read_to_string(&mut self.fragment_shader_preamble).unwrap();
-            refresh_all = true;
-        }
-
-        let mut programs_to_update = Vec::new();
-
-        for (program_id, program) in &mut self.programs {
-            if refresh_all || program.vs_path == path || program.fs_path == path {
-                programs_to_update.push(*program_id)
-            }
-        }
-
-        for program_id in programs_to_update {
-            self.load_program(program_id, false);
-        }
-    }*/
-
     pub fn get_uniform_location(&self, program: &Program, name: &str) -> UniformLocation {
         UniformLocation(self.gl.get_uniform_location(program.id, name))
     }
 
     pub fn set_uniform_2f(&self, uniform: UniformLocation, x: f32, y: f32) {
         debug_assert!(self.inside_frame);
         let UniformLocation(location) = uniform;
         self.gl.uniform_2f(location, x, y);
@@ -1605,229 +1425,193 @@ impl Device {
     }
 
     pub fn update_texture_from_pbo(&mut self,
                                    texture_id: TextureId,
                                    x0: u32,
                                    y0: u32,
                                    width: u32,
                                    height: u32,
+                                   layer_index: i32,
+                                   stride: Option<u32>,
                                    offset: usize) {
         debug_assert!(self.inside_frame);
-        debug_assert_eq!(self.textures.get(&texture_id).unwrap().format, ImageFormat::RGBAF32);
 
-        self.bind_texture(DEFAULT_TEXTURE, texture_id);
-
-        self.gl.tex_sub_image_2d_pbo(texture_id.target,
-                                     0,
-                                     x0 as gl::GLint,
-                                     y0 as gl::GLint,
-                                     width as gl::GLint,
-                                     height as gl::GLint,
-                                     gl::RGBA,
-                                     gl::FLOAT,
-                                     offset);
-    }
-
-    pub fn update_texture(&mut self,
-                          texture_id: TextureId,
-                          x0: u32,
-                          y0: u32,
-                          width: u32,
-                          height: u32,
-                          stride: Option<u32>,
-                          data: &[u8]) {
-        debug_assert!(self.inside_frame);
-
-        let mut expanded_data = Vec::new();
-
-        let (gl_format, bpp, data, data_type) = match self.textures.get(&texture_id).unwrap().format {
-            ImageFormat::A8 => {
-                if cfg!(any(target_arch="arm", target_arch="aarch64")) {
-                    expanded_data.extend(data.iter().flat_map(|byte| repeat(*byte).take(4)));
-                    (get_gl_format_bgra(self.gl()), 4, expanded_data.as_slice(), gl::UNSIGNED_BYTE)
-                } else {
-                    (GL_FORMAT_A, 1, data, gl::UNSIGNED_BYTE)
-                }
-            }
-            ImageFormat::RGB8 => (gl::RGB, 3, data, gl::UNSIGNED_BYTE),
-            ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl()), 4, data, gl::UNSIGNED_BYTE),
-            ImageFormat::RG8 => (gl::RG, 2, data, gl::UNSIGNED_BYTE),
-            ImageFormat::RGBAF32 => (gl::RGBA, 16, data, gl::FLOAT),
+        let (gl_format, bpp, data_type) = match self.textures.get(&texture_id).unwrap().format {
+            ImageFormat::A8 => (GL_FORMAT_A, 1, gl::UNSIGNED_BYTE),
+            ImageFormat::RGB8 => (gl::RGB, 3, gl::UNSIGNED_BYTE),
+            ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl()), 4, gl::UNSIGNED_BYTE),
+            ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE),
+            ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT),
             ImageFormat::Invalid => unreachable!(),
         };
 
         let row_length = match stride {
             Some(value) => value / bpp,
             None => width,
         };
 
-        // Take the stride into account for all rows, except the last one.
-        let len = bpp * row_length * (height - 1)
-                + width * bpp;
-        let data = &data[0..len as usize];
-
         if let Some(..) = stride {
             self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint);
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture_id);
 
-        self.gl.tex_sub_image_2d(texture_id.target,
-                                 0,
-                                 x0 as gl::GLint,
-                                 y0 as gl::GLint,
-                                 width as gl::GLint,
-                                 height as gl::GLint,
-                                 gl_format,
-                                 data_type,
-                                 data);
+        match texture_id.target {
+            gl::TEXTURE_2D_ARRAY => {
+                self.gl.tex_sub_image_3d_pbo(texture_id.target,
+                                             0,
+                                             x0 as gl::GLint,
+                                             y0 as gl::GLint,
+                                             layer_index,
+                                             width as gl::GLint,
+                                             height as gl::GLint,
+                                             1,
+                                             gl_format,
+                                             data_type,
+                                             offset);
+            }
+            gl::TEXTURE_2D |
+            gl::TEXTURE_RECTANGLE |
+            gl::TEXTURE_EXTERNAL_OES => {
+                self.gl.tex_sub_image_2d_pbo(texture_id.target,
+                                             0,
+                                             x0 as gl::GLint,
+                                             y0 as gl::GLint,
+                                             width as gl::GLint,
+                                             height as gl::GLint,
+                                             gl_format,
+                                             data_type,
+                                             offset);
+            }
+            _ => panic!("BUG: Unexpected texture target!"),
+        }
 
         // Reset row length to 0, otherwise the stride would apply to all texture uploads.
         if let Some(..) = stride {
             self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as gl::GLint);
         }
     }
 
-    fn clear_vertex_array(&mut self) {
-        debug_assert!(self.inside_frame);
-        self.gl.bind_vertex_array(0);
-    }
-
-    pub fn bind_vao(&mut self, vao_id: VAOId) {
+    pub fn bind_vao(&mut self, vao: &VAO) {
         debug_assert!(self.inside_frame);
 
-        if self.bound_vao != vao_id {
-            self.bound_vao = vao_id;
-
-            let VAOId(id) = vao_id;
-            self.gl.bind_vertex_array(id);
+        if self.bound_vao != vao.id {
+            self.bound_vao = vao.id;
+            self.gl.bind_vertex_array(vao.id);
         }
     }
 
     fn create_vao_with_vbos(&mut self,
                             descriptor: &VertexDescriptor,
                             main_vbo_id: VBOId,
                             instance_vbo_id: VBOId,
                             ibo_id: IBOId,
                             instance_stride: gl::GLint,
-                            owns_vertices: bool,
-                            owns_instances: bool,
-                            owns_indices: bool)
-                            -> VAOId {
+                            owns_vertices_and_indices: bool)
+                            -> VAO {
         debug_assert!(self.inside_frame);
 
-        let vao_ids = self.gl.gen_vertex_arrays(1);
-        let vao_id = vao_ids[0];
+        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
 
         let vao = VAO {
-            gl: Rc::clone(&self.gl),
             id: vao_id,
             ibo_id,
             main_vbo_id,
             instance_vbo_id,
             instance_stride,
-            owns_indices,
-            owns_vertices,
-            owns_instances,
+            owns_vertices_and_indices,
         };
 
         self.gl.bind_vertex_array(0);
 
-        let vao_id = VAOId(vao_id);
-
-        debug_assert!(!self.vaos.contains_key(&vao_id));
-        self.vaos.insert(vao_id, vao);
-
-        vao_id
+        vao
     }
 
     pub fn create_vao(&mut self,
                       descriptor: &VertexDescriptor,
-                      inst_stride: gl::GLint) -> VAOId {
+                      inst_stride: gl::GLint) -> 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,
                                   inst_stride,
-                                  true,
-                                  true,
                                   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 create_vao_with_new_instances(&mut self,
                                          descriptor: &VertexDescriptor,
                                          inst_stride: gl::GLint,
-                                         base_vao: VAOId) -> VAOId {
+                                         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]);
-        let (main_vbo_id, ibo_id) = {
-            let vao = self.vaos.get(&base_vao).unwrap();
-            (vao.main_vbo_id, vao.ibo_id)
-        };
 
         self.create_vao_with_vbos(descriptor,
-                                  main_vbo_id,
+                                  base_vao.main_vbo_id,
                                   intance_vbo_id,
-                                  ibo_id,
+                                  base_vao.ibo_id,
                                   inst_stride,
-                                  false,
-                                  true,
                                   false)
     }
 
     pub fn update_vao_main_vertices<V>(&mut self,
-                                       vao_id: VAOId,
+                                       vao: &VAO,
                                        vertices: &[V],
                                        usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
-
-        let vao = self.vaos.get(&vao_id).unwrap();
-        debug_assert_eq!(self.bound_vao, vao_id);
+        debug_assert_eq!(self.bound_vao, vao.id);
 
         vao.main_vbo_id.bind(self.gl());
         gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, vertices, usage_hint.to_gl());
     }
 
     pub fn update_vao_instances<V>(&mut self,
-                                   vao_id: VAOId,
+                                   vao: &VAO,
                                    instances: &[V],
                                    usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
-
-        let vao = self.vaos.get(&vao_id).unwrap();
-        debug_assert_eq!(self.bound_vao, vao_id);
+        debug_assert_eq!(self.bound_vao, vao.id);
         debug_assert_eq!(vao.instance_stride as usize, mem::size_of::<V>());
 
         vao.instance_vbo_id.bind(self.gl());
         gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, instances, usage_hint.to_gl());
     }
 
     pub fn update_vao_indices<I>(&mut self,
-                                 vao_id: VAOId,
+                                 vao: &VAO,
                                  indices: &[I],
                                  usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
-
-        let vao = self.vaos.get(&vao_id).unwrap();
-        debug_assert_eq!(self.bound_vao, vao_id);
+        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,
@@ -1986,22 +1770,16 @@ impl Device {
     }
     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);
     }
 }
 
-impl Drop for Device {
-    fn drop(&mut self) {
-        //self.file_watcher.exit();
-    }
-}
-
 /// return (gl_internal_format, gl_format)
 fn gl_texture_formats_for_image_format(gl: &gl::Gl, format: ImageFormat) -> (gl::GLint, gl::GLuint) {
     match format {
         ImageFormat::A8 => {
             if cfg!(any(target_arch="arm", target_arch="aarch64")) {
                 (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl))
             } else {
                 (GL_FORMAT_A as gl::GLint, GL_FORMAT_A)
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -16,17 +16,17 @@ use internal_types::{FastHashMap, Render
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use mask_cache::ClipRegion;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{ResourceCache, TiledImageMap};
 use scene::{Scene, SceneProperties};
 use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
 
-#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
+#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
 
 /// Nested display lists cause two types of replacements to ClipIds inside the nesting:
 ///     1. References to the root scroll frame are replaced by the ClipIds that
 ///        contained the nested display list.
 ///     2. Other ClipIds (that aren't custom or reference frames) are assumed to be
@@ -405,30 +405,29 @@ impl Frame {
         // that fixed position stacking contexts are positioned relative to us.
         let is_reference_frame = stacking_context.transform.is_some() ||
                                  stacking_context.perspective.is_some();
         if is_reference_frame {
             let transform = stacking_context.transform.as_ref();
             let transform = context.scene.properties.resolve_layout_transform(transform);
             let perspective =
                 stacking_context.perspective.unwrap_or_else(LayoutTransform::identity);
+            let origin = reference_frame_relative_offset + bounds.origin.to_vector();
             let transform =
-                LayerToScrollTransform::create_translation(reference_frame_relative_offset.x,
-                                                           reference_frame_relative_offset.y,
-                                                           0.0)
-                                        .pre_translate(bounds.origin.to_vector().to_3d())
+                LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0)
                                         .pre_mul(&transform)
                                         .pre_mul(&perspective);
 
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
             let mut clip_id = context.apply_scroll_frame_id_replacement(context_scroll_node_id);
             clip_id = context.builder.push_reference_frame(Some(clip_id),
                                                            pipeline_id,
                                                            &reference_frame_bounds,
                                                            &transform,
+                                                           origin,
                                                            &mut self.clip_scroll_tree);
             context.replacements.push((context_scroll_node_id, clip_id));
             reference_frame_relative_offset = LayerVector2D::zero();
         } else {
             reference_frame_relative_offset = LayerVector2D::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
@@ -480,26 +479,24 @@ impl Frame {
                                       parent_id,
                                       parent_pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
 
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
-        let transform = LayerToScrollTransform::create_translation(
-            reference_frame_relative_offset.x + bounds.origin.x,
-            reference_frame_relative_offset.y + bounds.origin.y,
-            0.0);
-
+        let origin = reference_frame_relative_offset + bounds.origin.to_vector();
+        let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0);
         let iframe_reference_frame_id =
             context.builder.push_reference_frame(Some(clip_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
+                                                 origin,
                                                  &mut self.clip_scroll_tree);
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
@@ -523,22 +520,16 @@ impl Frame {
         let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
         clip_and_scroll.scroll_node_id =
             context.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
 
 
         let item_rect_with_offset = item.rect().translate(&reference_frame_relative_offset);
         let clip_with_offset = item.local_clip_with_offset(&reference_frame_relative_offset);
         match *item.item() {
-            SpecificDisplayItem::WebGL(ref info) => {
-                context.builder.add_webgl_rectangle(clip_and_scroll,
-                                                    item_rect_with_offset,
-                                                    &clip_with_offset,
-                                                    info.context_id);
-            }
             SpecificDisplayItem::Image(ref info) => {
                 if let Some(tiling) = context.tiled_image_map.get(&info.image_key) {
                     // The image resource is tiled. We have to generate an image primitive
                     // for each tile.
                     self.decompose_image(clip_and_scroll,
                                          &mut context.builder,
                                          &item_rect_with_offset,
                                          &clip_with_offset,
@@ -688,31 +679,42 @@ impl Frame {
                 let complex_clips = context.get_complex_clips(pipeline_id, item.complex_clip().0);
                 let mut clip_region =
                     ClipRegion::create_for_clip_node(*item.local_clip().clip_rect(),
                                                      complex_clips,
                                                      info.image_mask);
                 clip_region.origin += reference_frame_relative_offset;
 
                 // Just use clip rectangle as the frame rect for this scroll frame.
-                // This is only interesting when calculating scroll extents for the
-                // ClipScrollNode::scroll(..) API
+                // This is useful when calculating scroll extents for the
+                // ClipScrollNode::scroll(..) API as well as for properly setting sticky
+                // positioning offsets.
                 let frame_rect = item.local_clip()
                                      .clip_rect()
                                      .translate(&reference_frame_relative_offset);
                 let content_rect = item.rect().translate(&reference_frame_relative_offset);
                 self.flatten_scroll_frame(context,
                                           pipeline_id,
                                           &clip_and_scroll.scroll_node_id,
                                           &info.id,
                                           &frame_rect,
                                           &content_rect,
                                           clip_region,
                                           info.scroll_sensitivity);
             }
+            SpecificDisplayItem::StickyFrame(ref info) => {
+                let frame_rect = item.rect().translate(&reference_frame_relative_offset);
+                let new_clip_id = context.convert_new_id_to_nested(&info.id);
+                self.clip_scroll_tree.add_sticky_frame(
+                    new_clip_id,
+                    clip_and_scroll.scroll_node_id, /* parent id */
+                    frame_rect,
+                    info.sticky_frame_info);
+
+            }
             SpecificDisplayItem::PushNestedDisplayList => {
                 // Using the clip and scroll already processed for nesting here
                 // means that in the case of multiple nested display lists, we
                 // will enter the outermost ids into the table and avoid having
                 // to do a replacement for every level of nesting.
                 context.push_nested_display_list_ids(clip_and_scroll);
             }
             SpecificDisplayItem::PopNestedDisplayList => context.pop_nested_display_list_ids(),
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,27 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, SubpixelDirection};
+use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
-use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, TextShadow, TileOffset};
-use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
+use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
+use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, HardwareCompositeOp};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
-use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
+use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
@@ -98,17 +98,16 @@ fn make_polygon(stacking_context: &Stack
     Polygon::from_transformed_rect(bounds, node.world_content_transform, anchor)
 }
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
-    pub cache_expiry_frames: u32,
 }
 
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
@@ -318,19 +317,24 @@ impl FrameBuilder {
             "Found unpopped text shadows when popping stacking context!");
     }
 
     pub fn push_reference_frame(&mut self,
                                 parent_id: Option<ClipId>,
                                 pipeline_id: PipelineId,
                                 rect: &LayerRect,
                                 transform: &LayerToScrollTransform,
+                                origin_in_parent_reference_frame: LayerVector2D,
                                 clip_scroll_tree: &mut ClipScrollTree)
                                 -> ClipId {
-        let new_id = clip_scroll_tree.add_reference_frame(rect, transform, pipeline_id, parent_id);
+        let new_id = clip_scroll_tree.add_reference_frame(rect,
+                                                          transform,
+                                                          origin_in_parent_reference_frame,
+                                                          pipeline_id,
+                                                          parent_id);
         self.reference_frame_stack.push(new_id);
         new_id
     }
 
     pub fn current_reference_frame_id(&self) -> ClipId {
         *self.reference_frame_stack.last().unwrap()
     }
 
@@ -348,20 +352,20 @@ impl FrameBuilder {
         let clip_size = LayerSize::new(outer_size.width + 2.0 * viewport_offset.x,
                                        outer_size.height + 2.0 * viewport_offset.y);
 
         let viewport_clip = LayerRect::new(LayerPoint::new(-viewport_offset.x, -viewport_offset.y),
                                            LayerSize::new(clip_size.width, clip_size.height));
 
         let root_id = clip_scroll_tree.root_reference_frame_id();
         if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&root_id) {
-            if let NodeType::ReferenceFrame(ref mut transform) = root_node.node_type {
-                *transform = LayerToScrollTransform::create_translation(viewport_offset.x,
-                                                                        viewport_offset.y,
-                                                                        0.0);
+            if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
+                info.transform = LayerToScrollTransform::create_translation(viewport_offset.x,
+                                                                            viewport_offset.y,
+                                                                            0.0);
             }
             root_node.local_clip_rect = viewport_clip;
         }
 
         let clip_id = clip_scroll_tree.topmost_scrolling_node_id();
         if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&clip_id) {
             root_node.local_clip_rect = viewport_clip;
         }
@@ -370,17 +374,22 @@ impl FrameBuilder {
     pub fn push_root(&mut self,
                      pipeline_id: PipelineId,
                      viewport_size: &LayerSize,
                      content_size: &LayerSize,
                      clip_scroll_tree: &mut ClipScrollTree)
                      -> ClipId {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
         let identity = &LayerToScrollTransform::identity();
-        self.push_reference_frame(None, pipeline_id, &viewport_rect, identity, clip_scroll_tree);
+        self.push_reference_frame(None,
+                                  pipeline_id,
+                                  &viewport_rect,
+                                  identity,
+                                  LayerVector2D::zero(),
+                                  clip_scroll_tree);
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
         self.add_scroll_frame(topmost_scrolling_node_id,
                               clip_scroll_tree.root_reference_frame_id,
                               pipeline_id,
                               &viewport_rect,
@@ -1191,51 +1200,33 @@ impl FrameBuilder {
                                    &outer_rect,
                                    local_clip,
                                    extra_clips.as_slice(),
                                    PrimitiveContainer::BoxShadow(prim_cpu));
             }
         }
     }
 
-    pub fn add_webgl_rectangle(&mut self,
-                               clip_and_scroll: ClipAndScrollInfo,
-                               rect: LayerRect,
-                               local_clip: &LocalClip,
-                               context_id: WebGLContextId) {
-        let prim_cpu = ImagePrimitiveCpu {
-            kind: ImagePrimitiveKind::WebGL(context_id),
-            gpu_blocks: [ [rect.size.width, rect.size.height, 0.0, 0.0].into(),
-                          TexelRect::invalid().into() ],
-        };
-
-        self.add_primitive(clip_and_scroll,
-                           &rect,
-                           local_clip,
-                           &[],
-                           PrimitiveContainer::Image(prim_cpu));
-    }
-
     pub fn add_image(&mut self,
                      clip_and_scroll: ClipAndScrollInfo,
                      rect: LayerRect,
                      local_clip: &LocalClip,
                      stretch_size: &LayerSize,
                      tile_spacing: &LayerSize,
                      sub_rect: Option<TexelRect>,
                      image_key: ImageKey,
                      image_rendering: ImageRendering,
                      tile: Option<TileOffset>) {
         let sub_rect_block = sub_rect.unwrap_or(TexelRect::invalid()).into();
 
         let prim_cpu = ImagePrimitiveCpu {
-            kind: ImagePrimitiveKind::Image(image_key,
-                                            image_rendering,
-                                            tile,
-                                            *tile_spacing),
+            image_key,
+            image_rendering,
+            tile_offset: tile,
+            tile_spacing: *tile_spacing,
             gpu_blocks: [ [ stretch_size.width,
                             stretch_size.height,
                             tile_spacing.width,
                             tile_spacing.height ].into(),
                             sub_rect_block,
                         ],
         };
 
@@ -1768,17 +1759,20 @@ impl<'a> LayerRectCalculationAndCullingP
             node_clip_info.screen_inner_rect = bounds.inner.as_ref()
                .and_then(|inner| inner.device_rect.intersection(&inner_rect))
                .unwrap_or(DeviceIntRect::zero());
 
             for clip_source in &node_clip_info.clip_sources {
                 if let Some(mask) = clip_source.image_mask() {
                     // We don't add the image mask for resolution, because
                     // layer masks are resolved later.
-                    self.resource_cache.request_image(mask.image, ImageRendering::Auto, None);
+                    self.resource_cache.request_image(mask.image,
+                                                      ImageRendering::Auto,
+                                                      None,
+                                                      self.gpu_cache);
                 }
             }
         }
     }
 
     fn recalculate_clip_scroll_groups(&mut self) {
         debug!("recalculate_clip_scroll_groups");
         for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
@@ -1874,19 +1868,19 @@ impl<'a> LayerRectCalculationAndCullingP
         // Indicates if the next non-reference-frame that we encounter needs to have its
         // local combined clip rectangle backed into the clip mask.
         let mut next_node_needs_region_mask = false;
         while let Some(id) = current_id {
             let node = &self.clip_scroll_tree.nodes.get(&id).unwrap();
             current_id = node.parent;
 
             let clip = match node.node_type {
-                NodeType::ReferenceFrame(transform) => {
+                NodeType::ReferenceFrame(ref info) => {
                     // if the transform is non-aligned, bake the next LCCR into the clip mask
-                    next_node_needs_region_mask |= !transform.preserves_2d_axis_alignment();
+                    next_node_needs_region_mask |= !info.transform.preserves_2d_axis_alignment();
                     continue
                 },
                 NodeType::Clip(ref clip) if clip.mask_cache_info.is_masking() => clip,
                 _ => continue,
             };
 
             // apply the screen bounds of the clip node
             //Note: these are based on the local combined viewport, so can be tighter
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -1,106 +1,150 @@
 /* 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 std::marker::PhantomData;
-use std::mem;
-
-// TODO(gw): Add a weak free list handle. This is like a strong
-//           free list handle below, but will contain an epoch
-//           field. Weak handles will use a get_opt style API
-//           which returns an Option<T> instead of T.
 
 // TODO(gw): Add an occupied list head, for fast
 //           iteration of the occupied list to implement
 //           retain() style functionality.
 
+#[derive(Debug, Copy, Clone, PartialEq)]
+struct Epoch(u32);
+
 #[derive(Debug)]
 pub struct FreeListHandle<T> {
     index: u32,
     _marker: PhantomData<T>,
 }
 
-enum SlotValue<T> {
-    Free,
-    Occupied(T),
-}
-
-impl<T> SlotValue<T> {
-    fn take(&mut self) -> T {
-        match mem::replace(self, SlotValue::Free) {
-            SlotValue::Free => unreachable!(),
-            SlotValue::Occupied(data) => data,
-        }
-    }
+#[derive(Debug)]
+pub struct WeakFreeListHandle<T> {
+    index: u32,
+    epoch: Epoch,
+    _marker: PhantomData<T>,
 }
 
 struct Slot<T> {
     next: Option<u32>,
-    value: SlotValue<T>,
+    epoch: Epoch,
+    value: Option<T>,
 }
 
 pub struct FreeList<T> {
     slots: Vec<Slot<T>>,
     free_list_head: Option<u32>,
 }
 
+pub enum UpsertResult<T> {
+    Updated(T),
+    Inserted(FreeListHandle<T>),
+}
+
 impl<T> FreeList<T> {
     pub fn new() -> FreeList<T> {
         FreeList {
             slots: Vec::new(),
             free_list_head: None,
         }
     }
 
+    #[allow(dead_code)]
     pub fn get(&self, id: &FreeListHandle<T>) -> &T {
-        match self.slots[id.index as usize].value {
-            SlotValue::Free => unreachable!(),
-            SlotValue::Occupied(ref data) => data,
+        self.slots[id.index as usize]
+            .value
+            .as_ref()
+            .unwrap()
+    }
+
+    #[allow(dead_code)]
+    pub fn get_mut(&mut self, id: &FreeListHandle<T>) -> &mut T {
+        self.slots[id.index as usize]
+            .value
+            .as_mut()
+            .unwrap()
+    }
+
+    pub fn get_opt(&self, id: &WeakFreeListHandle<T>) -> Option<&T> {
+        let slot = &self.slots[id.index as usize];
+        if slot.epoch == id.epoch {
+            slot.value.as_ref()
+        } else {
+            None
         }
     }
 
-    pub fn get_mut(&mut self, id: &FreeListHandle<T>) -> &mut T {
-        match self.slots[id.index as usize].value {
-            SlotValue::Free => unreachable!(),
-            SlotValue::Occupied(ref mut data) => data,
+    pub fn get_opt_mut(&mut self, id: &WeakFreeListHandle<T>) -> Option<&mut T> {
+        let slot = &mut self.slots[id.index as usize];
+        if slot.epoch == id.epoch {
+            slot.value.as_mut()
+        } else {
+            None
+        }
+    }
+
+    pub fn create_weak_handle(&self, id: &FreeListHandle<T>) -> WeakFreeListHandle<T> {
+        let slot = &self.slots[id.index as usize];
+        WeakFreeListHandle {
+            index: id.index,
+            epoch: slot.epoch,
+            _marker: PhantomData,
+        }
+    }
+
+    // Perform a database style UPSERT operation. If the provided
+    // handle is a valid entry, update the value and return the
+    // previous data. If the provided handle is invalid, then
+    // insert the data into a new slot and return the new handle.
+    pub fn upsert(&mut self,
+                  id: &WeakFreeListHandle<T>,
+                  data: T) -> UpsertResult<T> {
+        if self.slots[id.index as usize].epoch == id.epoch {
+            let slot = &mut self.slots[id.index as usize];
+            let result = UpsertResult::Updated(slot.value.take().unwrap());
+            slot.value = Some(data);
+            result
+        } else {
+            UpsertResult::Inserted(self.insert(data))
         }
     }
 
     pub fn insert(&mut self, item: T) -> FreeListHandle<T> {
         match self.free_list_head {
             Some(free_index) => {
                 let slot = &mut self.slots[free_index as usize];
 
                 // Remove from free list.
                 self.free_list_head = slot.next;
                 slot.next = None;
-                slot.value = SlotValue::Occupied(item);
+                slot.value = Some(item);
 
                 FreeListHandle {
                     index: free_index,
                     _marker: PhantomData,
                 }
             }
             None => {
                 let index = self.slots.len() as u32;
 
                 self.slots.push(Slot {
                     next: None,
-                    value: SlotValue::Occupied(item),
+                    epoch: Epoch(0),
+                    value: Some(item),
                 });
 
                 FreeListHandle {
                     index,
                     _marker: PhantomData,
                 }
             }
         }
     }
 
     pub fn free(&mut self, id: FreeListHandle<T>) -> T {
         let slot = &mut self.slots[id.index as usize];
         slot.next = self.free_list_head;
+        slot.epoch = Epoch(slot.epoch.0 + 1);
         self.free_list_head = Some(id.index);
-        slot.value.take()
+        slot.value.take().unwrap()
     }
 }
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,113 +1,66 @@
 /* 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::{FontInstanceKey, GlyphKey};
-use frame::FrameId;
-use gpu_cache::GpuCache;
+use api::{DevicePoint, DeviceUintSize, FontInstance, GlyphKey};
 use internal_types::FastHashMap;
-use resource_cache::{Resource, ResourceClassCache};
-use texture_cache::{TextureCache, TextureCacheItemId};
+use resource_cache::ResourceClassCache;
+use std::sync::Arc;
+use texture_cache::TextureCacheHandle;
 
 pub struct CachedGlyphInfo {
-    pub texture_cache_id: Option<TextureCacheItemId>,
-    pub last_access: FrameId,
+    pub texture_cache_handle: TextureCacheHandle,
+    pub glyph_bytes: Arc<Vec<u8>>,
+    pub size: DeviceUintSize,
+    pub offset: DevicePoint,
 }
 
-impl Resource for CachedGlyphInfo {
-    fn free(self, texture_cache: &mut TextureCache) {
-        if let Some(id) = self.texture_cache_id {
-            texture_cache.free(id);
-        }
-    }
-    fn get_last_access_time(&self) -> FrameId {
-        self.last_access
-    }
-    fn set_last_access_time(&mut self, frame_id: FrameId) {
-        self.last_access = frame_id;
-    }
-    fn add_to_gpu_cache(&self,
-                        texture_cache: &mut TextureCache,
-                        gpu_cache: &mut GpuCache) {
-        if let Some(texture_cache_id) = self.texture_cache_id.as_ref() {
-            let item = texture_cache.get_mut(texture_cache_id);
-            if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) {
-                request.push(item.uv_rect);
-                request.push([item.user_data[0], item.user_data[1], 0.0, 0.0]);
-            }
-        }
-    }
-}
-
-pub type GlyphKeyCache = ResourceClassCache<GlyphKey, CachedGlyphInfo>;
+pub type GlyphKeyCache = ResourceClassCache<GlyphKey, Option<CachedGlyphInfo>>;
 
 pub struct GlyphCache {
-    pub glyph_key_caches: FastHashMap<FontInstanceKey, GlyphKeyCache>,
+    pub glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
 }
 
 impl GlyphCache {
     pub fn new() -> GlyphCache {
         GlyphCache {
             glyph_key_caches: FastHashMap::default(),
         }
     }
 
     pub fn get_glyph_key_cache_for_font_mut(&mut self,
-                                            font: FontInstanceKey) -> &mut GlyphKeyCache {
+                                            font: FontInstance) -> &mut GlyphKeyCache {
         self.glyph_key_caches
             .entry(font)
             .or_insert(ResourceClassCache::new())
     }
 
     pub fn get_glyph_key_cache_for_font(&self,
-                                        font: &FontInstanceKey) -> &GlyphKeyCache {
+                                        font: &FontInstance) -> &GlyphKeyCache {
         self.glyph_key_caches
             .get(font)
             .expect("BUG: Unable to find glyph key cache!")
     }
 
-    pub fn update(&mut self,
-                  texture_cache: &mut TextureCache,
-                  gpu_cache: &mut GpuCache,
-                  current_frame_id: FrameId,
-                  expiry_frame_id: FrameId) {
-        let mut caches_to_remove = Vec::new();
-
-        for (font, glyph_key_cache) in &mut self.glyph_key_caches {
-            glyph_key_cache.update(texture_cache,
-                                   gpu_cache,
-                                   current_frame_id,
-                                   expiry_frame_id);
-
-            if glyph_key_cache.is_empty() {
-                caches_to_remove.push(font.clone());
-            }
-        }
-
-        for key in caches_to_remove {
-            self.glyph_key_caches.remove(&key).unwrap();
-        }
-    }
-
-    pub fn clear(&mut self, texture_cache: &mut TextureCache) {
+    pub fn clear(&mut self) {
         for (_, glyph_key_cache) in &mut self.glyph_key_caches {
-            glyph_key_cache.clear(texture_cache)
+            glyph_key_cache.clear()
         }
         // We use this in on_memory_pressure where retaining memory allocations
         // isn't desirable, so we completely remove the hash map instead of clearing it.
         self.glyph_key_caches = FastHashMap::default();
     }
 
-    pub fn clear_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
-    where for<'r> F: Fn(&'r &FontInstanceKey) -> bool
+    pub fn clear_fonts<F>(&mut self, key_fun: F)
+    where for<'r> F: Fn(&'r &FontInstance) -> bool
     {
         let caches_to_destroy = self.glyph_key_caches.keys()
             .filter(&key_fun)
             .cloned()
             .collect::<Vec<_>>();
         for key in caches_to_destroy {
             let mut cache = self.glyph_key_caches.remove(&key).unwrap();
-            cache.clear(texture_cache);
+            cache.clear();
         }
     }
 }
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -1,30 +1,30 @@
 /* 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/. */
 
 #[cfg(test)]
 use app_units::Au;
 use device::TextureFilter;
-use frame::FrameId;
 use glyph_cache::{CachedGlyphInfo, GlyphCache};
+use gpu_cache::GpuCache;
 use internal_types::FastHashSet;
 use platform::font::{FontContext, RasterizedGlyph};
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
 use rayon::prelude::*;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::collections::hash_map::Entry;
 use std::mem;
-use texture_cache::TextureCache;
+use texture_cache::{TextureCache, TextureCacheHandle};
 #[cfg(test)]
 use api::{ColorF, LayoutPoint, FontRenderMode, IdNamespace, SubpixelDirection};
-use api::{FontInstanceKey};
+use api::{DevicePoint, DeviceUintSize, FontInstance};
 use api::{FontKey, FontTemplate};
 use api::{ImageData, ImageDescriptor, ImageFormat};
 use api::{GlyphKey, GlyphDimensions};
 
 pub struct FontContexts {
     // These worker are mostly accessed from their corresponding worker threads.
     // The goal is that there should be no noticeable contention on the muteces.
     worker_contexts: Vec<Mutex<FontContext>>,
@@ -140,28 +140,52 @@ impl GlyphRasterizer {
 
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
-        current_frame_id: FrameId,
-        font: FontInstanceKey,
-        glyph_keys: &[GlyphKey]) {
+        font: FontInstance,
+        glyph_keys: &[GlyphKey],
+        texture_cache: &mut TextureCache,
+        gpu_cache: &mut GpuCache) {
         assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
         let mut glyphs = Vec::new();
 
         let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
 
         // select glyphs that have not been requested yet.
         for key in glyph_keys {
-            match glyph_key_cache.entry(key.clone(), current_frame_id) {
-                Entry::Occupied(..) => {}
+            match glyph_key_cache.entry(key.clone()) {
+                Entry::Occupied(mut entry) => {
+                    if let Some(ref mut glyph_info) = *entry.get_mut() {
+                        if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) {
+                            // This case gets hit when we have already rasterized
+                            // the glyph and stored it in CPU memory, the the glyph
+                            // has been evicted from the texture cache. In which case
+                            // we need to re-upload it to the GPU.
+                            texture_cache.update(&mut glyph_info.texture_cache_handle,
+                                                 ImageDescriptor {
+                                                    width: glyph_info.size.width,
+                                                    height: glyph_info.size.height,
+                                                    stride: None,
+                                                    format: ImageFormat::BGRA8,
+                                                    is_opaque: false,
+                                                    offset: 0,
+                                                 },
+                                                 TextureFilter::Linear,
+                                                 ImageData::Raw(glyph_info.glyph_bytes.clone()),
+                                                 [glyph_info.offset.x, glyph_info.offset.y],
+                                                 None,
+                                                 gpu_cache);
+                        }
+                    }
+                }
                 Entry::Vacant(..) => {
                     let request = GlyphRequest::new(&font, key);
                     if self.pending_glyphs.insert(request.clone()) {
                         glyphs.push(request);
                     }
                 }
             }
         }
@@ -193,31 +217,31 @@ impl GlyphRasterizer {
                 job
             }).collect();
 
             glyph_tx.send(jobs).unwrap();
         });
     }
 
     pub fn get_glyph_dimensions(&mut self,
-                                font: &FontInstanceKey,
+                                font: &FontInstance,
                                 glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
         self.font_contexts.lock_shared_context().get_glyph_dimensions(font, glyph_key)
     }
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts.lock_shared_context().get_glyph_index(font_key, ch)
     }
 
     pub fn resolve_glyphs(
         &mut self,
-        current_frame_id: FrameId,
         glyph_cache: &mut GlyphCache,
         texture_cache: &mut TextureCache,
-        texture_cache_profile: &mut TextureCacheProfileCounters,
+        gpu_cache: &mut GpuCache,
+        _texture_cache_profile: &mut TextureCacheProfileCounters,
     ) {
         let mut rasterized_glyphs = Vec::with_capacity(self.pending_glyphs.len());
 
         // Pull rasterized glyphs from the queue.
 
         while !self.pending_glyphs.is_empty() {
             // TODO: rather than blocking until all pending glyphs are available
             // we could try_recv and steal work from the thread pool to take advantage
@@ -236,45 +260,52 @@ impl GlyphRasterizer {
         // order for a given text run (since iterating a hash set doesn't
         // guarantee order). This can show up as very small float inaccuacry
         // differences in rasterizers due to the different coordinates
         // that text runs get associated with by the texture cache allocator.
         rasterized_glyphs.sort_by(|a, b| a.request.cmp(&b.request));
 
         // Update the caches.
         for job in rasterized_glyphs {
-            let image_id = job.result.and_then(
+            let glyph_info = job.result.and_then(
                 |glyph| if glyph.width > 0 && glyph.height > 0 {
                     assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
-                    let image_id = texture_cache.insert(
+                    let glyph_bytes = Arc::new(glyph.bytes);
+                    let mut texture_cache_handle = TextureCacheHandle::new();
+                    texture_cache.request(&mut texture_cache_handle, gpu_cache);
+                    texture_cache.update(
+                        &mut texture_cache_handle,
                         ImageDescriptor {
                             width: glyph.width,
                             height: glyph.height,
                             stride: None,
                             format: ImageFormat::BGRA8,
                             is_opaque: false,
                             offset: 0,
                         },
                         TextureFilter::Linear,
-                        ImageData::Raw(Arc::new(glyph.bytes)),
+                        ImageData::Raw(glyph_bytes.clone()),
                         [glyph.left, glyph.top],
-                        texture_cache_profile,
+                        None,
+                        gpu_cache,
                     );
-                    Some(image_id)
+                    Some(CachedGlyphInfo {
+                        texture_cache_handle,
+                        glyph_bytes,
+                        size: DeviceUintSize::new(glyph.width, glyph.height),
+                        offset: DevicePoint::new(glyph.left, glyph.top),
+                    })
                 } else {
                     None
                 }
             );
 
             let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(job.request.font);
 
-            glyph_key_cache.insert(job.request.key, CachedGlyphInfo {
-                texture_cache_id: image_id,
-                last_access: current_frame_id,
-            });
+            glyph_key_cache.insert(job.request.key, glyph_info);
         }
 
         // Now that we are done with the critical path (rendering the glyphs),
         // we can schedule removing the fonts if needed.
         if !self.fonts_to_remove.is_empty() {
             let font_contexts = Arc::clone(&self.font_contexts);
             let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
             self.workers.spawn(move || {
@@ -303,21 +334,21 @@ impl FontContext {
             }
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 pub struct GlyphRequest {
     pub key: GlyphKey,
-    pub font: FontInstanceKey,
+    pub font: FontInstance,
 }
 
 impl GlyphRequest {
-    pub fn new(font: &FontInstanceKey, key: &GlyphKey) -> Self {
+    pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
         GlyphRequest {
             key: key.clone(),
             font: font.clone(),
         }
     }
 }
 
 struct GlyphRasterJob {
@@ -332,50 +363,51 @@ fn raterize_200_glyphs() {
 
     use rayon::Configuration;
     use std::fs::File;
     use std::io::Read;
 
     let workers = Arc::new(ThreadPool::new(Configuration::new()).unwrap());
     let mut glyph_rasterizer = GlyphRasterizer::new(workers);
     let mut glyph_cache = GlyphCache::new();
+    let mut gpu_cache = GpuCache::new();
+    let mut texture_cache = TextureCache::new(2048);
 
     let mut font_file = File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
     let mut font_data = vec![];
     font_file.read_to_end(&mut font_data).expect("failed to read font file");
 
     let font_key = FontKey::new(IdNamespace(0), 0);
     glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
 
-    let frame_id = FrameId(1);
-
-    let font = FontInstanceKey {
+    let font = FontInstance {
         font_key,
         color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(),
         size: Au::from_px(32),
         render_mode: FontRenderMode::Subpixel,
         glyph_options: None,
         subpx_dir: SubpixelDirection::Horizontal,
     };
 
     let mut glyph_keys = Vec::with_capacity(200);
     for i in 0..200 {
         glyph_keys.push(GlyphKey::new(i, LayoutPoint::zero(), font.render_mode, font.subpx_dir));
     }
 
     for i in 0..4 {
         glyph_rasterizer.request_glyphs(
             &mut glyph_cache,
-            frame_id,
             font.clone(),
             &glyph_keys[(50 * i)..(50 * (i + 1))],
+            &mut texture_cache,
+            &mut gpu_cache
         );
     }
 
     glyph_rasterizer.delete_font(font_key);
 
     glyph_rasterizer.resolve_glyphs(
-        frame_id,
         &mut glyph_cache,
         &mut TextureCache::new(4096),
+        &mut gpu_cache,
         &mut TextureCacheProfileCounters::new(),
     );
 }
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -23,17 +23,18 @@
 //! use the ```get_address``` API to get the allocated
 //! address in the GPU cache of a given resource slot
 //! for this frame.
 
 use device::FrameId;
 use internal_types::UvRect;
 use profiler::GpuCacheProfileCounters;
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
-use std::{mem, u32};
+use std::{mem, u16, u32};
+use std::ops::Add;
 use api::{ColorF, LayerRect};
 
 pub const GPU_CACHE_INITIAL_HEIGHT: u32 = 512;
 const FRAMES_BEFORE_EVICTION: usize = 10;
 const NEW_ROWS_PER_RESIZE: u32 = 512;
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 struct Epoch(u32);
@@ -135,16 +136,34 @@ pub struct GpuCacheAddress {
 
 impl GpuCacheAddress {
     fn new(u: usize, v: usize) -> GpuCacheAddress {
         GpuCacheAddress {
             u: u as u16,
             v: v as u16,
         }
     }
+
+    pub fn invalid() -> GpuCacheAddress {
+        GpuCacheAddress {
+            u: u16::MAX,
+            v: u16::MAX,
+        }
+    }
+}
+
+impl Add<usize> for GpuCacheAddress {
+    type Output = GpuCacheAddress;
+
+    fn add(self, other: usize) -> GpuCacheAddress {
+        GpuCacheAddress {
+            u: self.u + other as u16,
+            v: self.v,
+        }
+    }
 }
 
 // An entry in a free-list of blocks in the GPU cache.
 #[derive(Debug)]
 struct Block {
     // The location in the cache of this block.
     address: GpuCacheAddress,
     // Index of the next free block in the list it
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -10,17 +10,17 @@ use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
 use api::{ClipId, DevicePoint, DeviceUintRect, DocumentId, Epoch};
 use api::{ExternalImageData, ExternalImageId};
-use api::{ImageData, ImageFormat, PipelineId};
+use api::{ImageFormat, PipelineId};
 
 pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
 pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
@@ -38,20 +38,18 @@ pub struct CacheTextureId(pub usize);
 // thread, where they are resolved to a
 // native texture ID.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 pub enum SourceTexture {
     Invalid,
     TextureCache(CacheTextureId),
     External(ExternalImageData),
-    #[cfg_attr(not(feature = "webgl"), allow(dead_code))]
-    /// This is actually a gl::GLuint, with the shared texture id between the
-    /// main context and the WebGL context.
-    WebGL(u32),
+    CacheA8,
+    CacheRGBA8,
 }
 
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
 #[derive(Debug, PartialEq, Eq)]
 pub enum TextureSampler {
     Color0,
@@ -86,60 +84,59 @@ pub struct BatchTextures {
 }
 
 impl BatchTextures {
     pub fn no_texture() -> Self {
         BatchTextures {
             colors: [SourceTexture::Invalid; 3],
         }
     }
+
+    pub fn render_target_cache() -> Self {
+        BatchTextures {
+            colors: [
+                SourceTexture::CacheRGBA8,
+                SourceTexture::Invalid,
+                SourceTexture::Invalid,
+            ]
+        }
+    }
 }
 
 // In some places we need to temporarily bind a texture to any slot.
 pub const DEFAULT_TEXTURE: TextureSampler = TextureSampler::Color0;
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RenderTargetMode {
     None,
-    SimpleRenderTarget,
-    LayerRenderTarget(i32),      // Number of texture layers
+    RenderTarget,
+}
+
+#[derive(Debug)]
+pub enum TextureUpdateSource {
+    External { id: ExternalImageId, channel_index: u8 },
+    Bytes { data: Arc<Vec<u8>> },
 }
 
 #[derive(Debug)]
 pub enum TextureUpdateOp {
     Create {
-      width: u32,
-      height: u32,
-      format: ImageFormat,
-      filter: TextureFilter,
-      mode: RenderTargetMode,
-      data: Option<ImageData>,
-    },
-    Update {
-        page_pos_x: u32,    // the texture page position which we want to upload
-        page_pos_y: u32,
-        width: u32,
-        height: u32,
-        data: Arc<Vec<u8>>,
-        stride: Option<u32>,
-        offset: u32,
-    },
-    UpdateForExternalBuffer {
-        rect: DeviceUintRect,
-        id: ExternalImageId,
-        channel_index: u8,
-        stride: Option<u32>,
-        offset: u32,
-    },
-    Grow {
         width: u32,
         height: u32,
         format: ImageFormat,
         filter: TextureFilter,
         mode: RenderTargetMode,
+        layer_count: i32,
+    },
+    Update {
+        rect: DeviceUintRect,
+        stride: Option<u32>,
+        offset: u32,
+        layer_index: i32,
+        source: TextureUpdateSource,
     },
     Free,
 }
 
 #[derive(Debug)]
 pub struct TextureUpdate {
     pub id: CacheTextureId,
     pub op: TextureUpdateOp,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -75,26 +75,16 @@ mod render_task;
 mod resource_cache;
 mod scene;
 mod spring;
 mod texture_allocator;
 mod texture_cache;
 mod tiling;
 mod util;
 
-#[doc(hidden)] // for benchmarks
-pub use texture_cache::TexturePage;
-
-#[cfg(feature = "webgl")]
-mod webgl_types;
-
-#[cfg(not(feature = "webgl"))]
-#[path = "webgl_stubs.rs"]
-mod webgl_types;
-
 mod shader_source {
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
 }
 
 pub use record::{ApiRecordingReceiver, BinaryRecorder, WEBRENDER_RECORDING_HEADER};
 
 mod platform {
     #[cfg(target_os="macos")]
@@ -135,18 +125,16 @@ extern crate app_units;
 extern crate bincode;
 extern crate euclid;
 extern crate fxhash;
 extern crate gleam;
 extern crate num_traits;
 //extern crate notify;
 extern crate time;
 pub extern crate webrender_api;
-#[cfg(feature = "webgl")]
-extern crate offscreen_gl_context;
 extern crate byteorder;
 extern crate rayon;
 extern crate plane_split;
 
 #[cfg(any(target_os="macos", target_os="windows"))]
 extern crate gamma_lut;
 
 pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -115,16 +115,20 @@ pub struct ClipAddressRange {
 impl ClipAddressRange {
     fn new(count: usize) -> Self {
         ClipAddressRange {
             location: GpuCacheHandle::new(),
             item_count: count,
         }
     }
 
+    pub fn is_empty(&self) -> bool {
+        self.item_count == 0
+    }
+
     pub fn get_count(&self) -> usize {
         self.item_count
     }
 
     fn get_block_count(&self) -> Option<usize> {
         if self.item_count != 0 {
             Some(self.item_count * CLIP_DATA_GPU_BLOCKS)
         } else {
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -12,17 +12,17 @@ use core_graphics::font::{CGFont, CGGlyp
 use core_graphics::geometry::{CGPoint, CGSize, CGRect};
 use core_text::font::CTFont;
 use core_text::font_descriptor::kCTFontDefaultOrientation;
 use core_text;
 use internal_types::FastHashMap;
 use std::collections::hash_map::Entry;
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
 use api::{GlyphKey};
-use api::{FontInstanceKey, NativeFontHandle};
+use api::{FontInstance, NativeFontHandle};
 use gamma_lut::{GammaLut, Color as ColorLut};
 use std::ptr;
 use std::sync::Arc;
 
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
     ct_fonts: FastHashMap<(FontKey, Au), CTFont>,
     gamma_lut: GammaLut,
@@ -233,17 +233,17 @@ impl FontContext {
                     Some(glyph as u32)
                 } else {
                     None
                 }
             })
     }
 
     pub fn get_glyph_dimensions(&mut self,
-                                font: &FontInstanceKey,
+                                font: &FontInstance,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
         self.get_ct_font(font.font_key, font.size).and_then(|ref ct_font| {
             let glyph = key.index as CGGlyph;
             let (x_offset, y_offset) = font.get_subpx_offset(key);
             let metrics = get_glyph_metrics(ct_font, glyph, x_offset, y_offset);
             if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                 None
             } else {
@@ -293,17 +293,17 @@ impl FontContext {
                 let a = pixel[3];
                 print!("({}, {}, {}, {}) ", r, g, b, a);
             }
             println!("");
         }
     }
 
     pub fn rasterize_glyph(&mut self,
-                           font: &FontInstanceKey,
+                           font: &FontInstance,
                            key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
         let ct_font = match self.get_ct_font(font.font_key, font.size) {
             Some(font) => font,
             None => return Some(RasterizedGlyph::blank())
         };
 
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.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::{FontInstanceKey, FontKey, FontRenderMode, GlyphDimensions};
+use api::{FontInstance, FontKey, FontRenderMode, GlyphDimensions};
 use api::{NativeFontHandle, SubpixelDirection};
 use api::{GlyphKey};
 use internal_types::FastHashMap;
 
 use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode, FT_BBox, FT_Outline_Translate};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos};
 use freetype::freetype::{FT_Library, FT_Set_Char_Size, FT_Outline_Get_CBox};
 use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6, FT_Glyph_Format};
@@ -118,17 +118,17 @@ impl FontContext {
             let result = unsafe {
                 FT_Done_Face(face.face)
             };
             assert!(result.succeeded());
         }
     }
 
     fn load_glyph(&self,
-                  font: &FontInstanceKey,
+                  font: &FontInstance,
                   glyph: &GlyphKey) -> Option<FT_GlyphSlot> {
 
         debug_assert!(self.faces.contains_key(&font.font_key));
         let face = self.faces.get(&font.font_key).unwrap();
         let char_size = font.size.to_f64_px() * 64.0 + 0.5;
 
         assert_eq!(SUCCESS, unsafe {
             FT_Set_Char_Size(face.face, char_size as FT_F26Dot6, 0, 0, 0)
@@ -161,17 +161,17 @@ impl FontContext {
                 glyph.index, font.size, font.font_key, result);
             None
         }
     }
 
     // Get the bounding box for a glyph, accounting for sub-pixel positioning.
     fn get_bounding_box(&self,
                         slot: FT_GlyphSlot,
-                        font: &FontInstanceKey,
+                        font: &FontInstance,
                         glyph: &GlyphKey) -> FT_BBox {
         let mut cbox: FT_BBox = unsafe { mem::uninitialized() };
 
         // Get the estimated bounding box from FT (control points).
         unsafe {
             FT_Outline_Get_CBox(&(*slot).outline, &mut cbox);
         }
 
@@ -208,17 +208,17 @@ impl FontContext {
         cbox.xMax = (cbox.xMax + 63) & !63;
         cbox.yMax = (cbox.yMax + 63) & !63;
 
         cbox
     }
 
     fn get_glyph_dimensions_impl(&self,
                                  slot: FT_GlyphSlot,
-                                 font: &FontInstanceKey,
+                                 font: &FontInstance,
                                  glyph: &GlyphKey) -> Option<GlyphDimensions> {
         let metrics = unsafe { &(*slot).metrics };
 
         // If there's no advance, no need to consider this glyph
         // for layout.
         if metrics.horiAdvance == 0 {
             None
         } else {
@@ -244,26 +244,26 @@ impl FontContext {
                 Some(idx)
             } else {
                 None
             }
         }
     }
 
     pub fn get_glyph_dimensions(&mut self,
-                                font: &FontInstanceKey,
+                                font: &FontInstance,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
         let slot = self.load_glyph(font, key);
         slot.and_then(|slot| {
             self.get_glyph_dimensions_impl(slot, font, key)
         })
     }
 
     pub fn rasterize_glyph(&mut self,
-                           font: &FontInstanceKey,
+                           font: &FontInstance,
                            key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
         let slot = match self.load_glyph(font, key) {
             Some(slot) => slot,
             None => return None,
         };
 
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{FontInstanceKey, GlyphKey, GlyphOptions, SubpixelDirection};
+use api::{FontInstance, GlyphKey, GlyphOptions, SubpixelDirection};
 use gamma_lut::{GammaLut, Color as ColorLut};
 use internal_types::FastHashMap;
 
 use dwrote;
 use std::sync::Arc;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
@@ -150,17 +150,17 @@ impl FontContext {
                 let b = pixel[2];
                 print!("({}, {}, {}) ", r, g, b, );
             }
             println!("");
         }
     }
 
     fn create_glyph_analysis(&self,
-                             font: &FontInstanceKey,
+                             font: &FontInstance,
                              key: &GlyphKey) ->
                             dwrote::GlyphRunAnalysis {
         let face = self.fonts.get(&font.font_key).unwrap();
         let glyph = key.index as u16;
         let advance = 0.0f32;
         let offset = dwrote::GlyphOffset { advanceOffset: 0.0, ascenderOffset: 0.0 };
 
         let glyph_run = dwrote::DWRITE_GLYPH_RUN {
@@ -197,17 +197,17 @@ impl FontContext {
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         let face = self.fonts.get(&font_key).unwrap();
         let indices = face.get_glyph_indices(&[ch as u32]);
         indices.first().map(|idx| *idx as u32)
     }
 
     // TODO: Pipe GlyphOptions into glyph_dimensions too
     pub fn get_glyph_dimensions(&self,
-                                font: &FontInstanceKey,
+                                font: &FontInstance,
                                 key: &GlyphKey)
                                 -> Option<GlyphDimensions> {
         // Probably have to default to something else here.
         let render_mode = FontRenderMode::Subpixel;
         let analysis = self.create_glyph_analysis(font, key);
 
         let texture_type = dwrite_texture_type(render_mode);
 
@@ -278,17 +278,17 @@ impl FontContext {
                     rgba_pixels[i*4+3] = 0xff;
                 }
                 rgba_pixels
             }
         }
     }
 
     pub fn rasterize_glyph(&mut self,
-                           font: &FontInstanceKey,
+                           font: &FontInstance,
                            key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
         let analysis = self.create_glyph_analysis(font, key);
         let texture_type = dwrite_texture_type(font.render_mode);
 
         let bounds = analysis.get_alpha_texture_bounds(texture_type);
         let width = (bounds.right - bounds.left) as usize;
         let height = (bounds.bottom - bounds.top) as usize;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
-use api::{GlyphKey, LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
-use api::{device_length, FontInstanceKey, LayerVector2D, LineOrientation, LineStyle, SubpixelDirection};
+use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
+use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle, SubpixelDirection};
 use app_units::Au;
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{ImageProperties, ResourceCache};
@@ -195,24 +195,21 @@ impl ToGpuBlocks for LinePrimitive {
         request.push([pack_as_float(self.style as u32),
                       pack_as_float(self.orientation as u32),
                       0.0,
                       0.0]);
     }
 }
 
 #[derive(Debug)]
-pub enum ImagePrimitiveKind {
-    Image(ImageKey, ImageRendering, Option<TileOffset>, LayerSize),
-    WebGL(WebGLContextId),
-}
-
-#[derive(Debug)]
 pub struct ImagePrimitiveCpu {
-    pub kind: ImagePrimitiveKind,
+    pub image_key: ImageKey,
+    pub image_rendering: ImageRendering,
+    pub tile_offset: Option<TileOffset>,
+    pub tile_spacing: LayerSize,
     // TODO(gw): Build on demand
     pub gpu_blocks: [GpuBlockData; 2],
 }
 
 impl ToGpuBlocks for ImagePrimitiveCpu {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.extend_from_slice(&self.gpu_blocks);
     }
@@ -533,29 +530,30 @@ pub enum TextRunMode {
     Shadow,
 }
 
 impl TextRunPrimitiveCpu {
     fn prepare_for_render(&mut self,
                           resource_cache: &mut ResourceCache,
                           device_pixel_ratio: f32,
                           display_list: &BuiltDisplayList,
-                          run_mode: TextRunMode) {
+                          run_mode: TextRunMode,
+                          gpu_cache: &mut GpuCache) {
         let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
         let render_mode = match run_mode {
             TextRunMode::Normal => self.normal_render_mode,
             TextRunMode::Shadow => self.shadow_render_mode,
         };
 
-        let font = FontInstanceKey::new(self.font_key,
-                                        font_size_dp,
-                                        self.color,
-                                        render_mode,
-                                        self.glyph_options,
-                                        self.subpx_dir);
+        let font = FontInstance::new(self.font_key,
+                                     font_size_dp,
+                                     self.color,
+                                     render_mode,
+                                     self.glyph_options,
+                                     self.subpx_dir);
 
         // 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() {
             let src_glyphs = display_list.get(self.glyph_range);
 
@@ -585,17 +583,17 @@ 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);
             }
         }
 
-        resource_cache.request_glyphs(font, &self.glyph_keys);
+        resource_cache.request_glyphs(font, &self.glyph_keys, gpu_cache);
     }
 
     fn write_gpu_blocks(&self,
                         request: &mut GpuDataRequest) {
         request.push(self.color);
         request.push([self.offset.x,
                       self.offset.y,
                       self.subpx_dir as u32 as f32,
@@ -1125,17 +1123,20 @@ impl PrimitiveStore {
         if let Some(ref mut clip_info) = metadata.clip_cache_info {
             clip_info.update(&metadata.clips, layer_transform, gpu_cache, device_pixel_ratio);
 
             //TODO-LCCR: we could tighten up the `local_clip_rect` here
             // but that would require invalidating the whole GPU block
 
             for clip in &metadata.clips {
                 if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
-                    resource_cache.request_image(mask.image, ImageRendering::Auto, None);
+                    resource_cache.request_image(mask.image,
+                                                 ImageRendering::Auto,
+                                                 None,
+                                                 gpu_cache);
                 }
             }
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
             PrimitiveKind::Border |
             PrimitiveKind::Line => {}
@@ -1172,44 +1173,47 @@ impl PrimitiveStore {
                                                                  blur_radius,
                                                                  prim_index));
             }
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[cpu_prim_index.0];
                 text.prepare_for_render(resource_cache,
                                         device_pixel_ratio,
                                         display_list,
-                                        text_run_mode);
+                                        text_run_mode,
+                                        gpu_cache);
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[cpu_prim_index.0];
 
-                match image_cpu.kind {
-                    ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, tile_spacing) => {
-                        resource_cache.request_image(image_key, image_rendering, tile_offset);
+                resource_cache.request_image(image_cpu.image_key,
+                                             image_cpu.image_rendering,
+                                             image_cpu.tile_offset,
+                                             gpu_cache);
 
-                        // TODO(gw): This doesn't actually need to be calculated each frame.
-                        // It's cheap enough that it's not worth introducing a cache for images
-                        // right now, but if we introduce a cache for images for some other
-                        // reason then we might as well cache this with it.
-                        let image_properties = resource_cache.get_image_properties(image_key);
-                        metadata.opacity.is_opaque = image_properties.descriptor.is_opaque &&
-                                                     tile_spacing.width == 0.0 &&
-                                                     tile_spacing.height == 0.0;
-                    }
-                    ImagePrimitiveKind::WebGL(..) => {}
+                // TODO(gw): This doesn't actually need to be calculated each frame.
+                // It's cheap enough that it's not worth introducing a cache for images
+                // right now, but if we introduce a cache for images for some other
+                // reason then we might as well cache this with it.
+                if let Some(image_properties) = resource_cache.get_image_properties(image_cpu.image_key) {
+                    metadata.opacity.is_opaque = image_properties.descriptor.is_opaque &&
+                                                 image_cpu.tile_spacing.width == 0.0 &&
+                                                 image_cpu.tile_spacing.height == 0.0;
                 }
             }
             PrimitiveKind::YuvImage => {
                 let image_cpu = &mut self.cpu_yuv_images[cpu_prim_index.0];
 
                 let channel_num = image_cpu.format.get_plane_num();
                 debug_assert!(channel_num <= 3);
                 for channel in 0..channel_num {
-                    resource_cache.request_image(image_cpu.yuv_key[channel], image_cpu.image_rendering, None);
+                    resource_cache.request_image(image_cpu.yuv_key[channel],
+                                                 image_cpu.image_rendering,
+                                                 None,
+                                                 gpu_cache);
                 }
             }
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient => {}
         }
 
         // Mark this GPU resource as required for this frame.
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -62,14 +62,13 @@ impl ApiRecordingReceiver for BinaryReco
     }
 }
 
 pub fn should_record_msg(msg: &ApiMsg) -> bool {
     match *msg {
         ApiMsg::UpdateResources(..) |
         ApiMsg::AddDocument{..} |
         ApiMsg::UpdateDocument(..) |
-        ApiMsg::DeleteDocument(..) |
-        ApiMsg::WebGLCommand(..) =>
+        ApiMsg::DeleteDocument(..) =>
             true,
         _ => false
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,40 +1,32 @@
 /* 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 frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use gpu_cache::GpuCache;
-use internal_types::{FastHashMap, SourceTexture, ResultMsg, RendererFrame};
+use internal_types::{FastHashMap, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use std::u32;
 use texture_cache::TextureCache;
 use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
 use rayon::ThreadPool;
-use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
 use api::channel::{PayloadSender, PayloadSenderHelperMethods};
 use api::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, DocumentMsg};
-use api::{IdNamespace, LayerPoint, RenderDispatcher, RenderNotifier};
-use api::{VRCompositorCommand, VRCompositorHandler, WebGLCommand, WebGLContextId};
-
-#[cfg(feature = "webgl")]
-use offscreen_gl_context::GLContextDispatcher;
-
-#[cfg(not(feature = "webgl"))]
-use webgl_types::GLContextDispatcher;
+use api::{IdNamespace, LayerPoint, RenderNotifier};
 
 struct Document {
     scene: Scene,
     frame: Frame,
     window_size: DeviceUintSize,
     inner_rect: DeviceUintRect,
     pan: DeviceIntPoint,
     page_zoom_factor: f32,
@@ -97,69 +89,16 @@ impl Document {
                          &self.scene.display_lists,
                          accumulated_scale_factor,
                          pan,
                          &mut resource_profile.texture_cache,
                          &mut resource_profile.gpu_cache)
     }
 }
 
-struct WebGL {
-    last_id: WebGLContextId,
-    contexts: FastHashMap<WebGLContextId, GLContextWrapper>,
-    active_id: Option<WebGLContextId>,
-}
-
-impl WebGL {
-    fn new() -> Self {
-        WebGL {
-            last_id: WebGLContextId(0),
-            contexts: FastHashMap::default(),
-            active_id: None,
-        }
-    }
-
-    fn register(&mut self, context: GLContextWrapper) -> WebGLContextId {
-        // Creating a new GLContext may make the current bound context_id dirty.
-        // Clear it to ensure that  make_current() is called in subsequent commands.
-        self.active_id = None;
-        self.last_id.0 += 1;
-        self.contexts.insert(self.last_id, context);
-        self.last_id
-    }
-
-    fn activate(&mut self, id: WebGLContextId) -> &mut GLContextWrapper {
-        let ctx = self.contexts.get_mut(&id).unwrap();
-        if Some(id) != self.active_id {
-            ctx.make_current();
-            self.active_id = Some(id);
-        }
-        ctx
-    }
-
-    fn flush(&mut self) {
-        if let Some(id) = self.active_id.take() {
-            self.contexts[&id].unbind();
-        }
-
-        // When running in OSMesa mode with texture sharing,
-        // a flush is required on any GL contexts to ensure
-        // that read-back from the shared texture returns
-        // valid data! This should be fine to have run on all
-        // implementations - a single flush for each webgl
-        // context at the start of a render frame should
-        // incur minimal cost.
-        for (_, context) in &self.contexts {
-            context.make_current();
-            context.apply_command(WebGLCommand::Flush);
-            context.unbind();
-        }
-    }
-}
-
 enum DocumentOp {
     Nop,
     Built,
     ScrolledNop,
     Scrolled(RendererFrame),
     Rendered(RendererFrame),
 }
 
@@ -179,71 +118,57 @@ pub struct RenderBackend {
 
     gpu_cache: GpuCache,
     resource_cache: ResourceCache,
 
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, Document>,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
-    webrender_context_handle: Option<GLContextHandleWrapper>,
     recorder: Option<Box<ApiRecordingReceiver>>,
-    main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
-
-    vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
-    webgl: WebGL,
 
     enable_render_on_scroll: bool,
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: PayloadReceiver,
         payload_tx: PayloadSender,
         result_tx: Sender<ResultMsg>,
         hidpi_factor: f32,
         texture_cache: TextureCache,
         workers: Arc<ThreadPool>,
         notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
-        webrender_context_handle: Option<GLContextHandleWrapper>,
         frame_config: FrameBuilderConfig,
         recorder: Option<Box<ApiRecordingReceiver>>,
-        main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
         blob_image_renderer: Option<Box<BlobImageRenderer>>,
-        vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
         enable_render_on_scroll: bool,
     ) -> RenderBackend {
 
         let resource_cache = ResourceCache::new(texture_cache,
                                                 workers,
-                                                blob_image_renderer,
-                                                frame_config.cache_expiry_frames);
+                                                blob_image_renderer);
 
         register_thread_with_profiler("Backend".to_string());
 
         RenderBackend {
             api_rx,
             payload_rx,
             payload_tx,
             result_tx,
             hidpi_factor,
 
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             next_namespace_id: IdNamespace(1),
             notifier,
-            webrender_context_handle,
             recorder,
-            main_thread_dispatcher,
-
-            vr_compositor_handler,
-            webgl: WebGL::new(),
 
             enable_render_on_scroll,
         }
     }
 
     fn process_document(&mut self, document_id: DocumentId, message: DocumentMsg,
                         frame_counter: u32, mut profile_counters: &mut BackendProfileCounters)
                         -> DocumentOp
@@ -303,17 +228,16 @@ impl RenderBackend {
                     doc.frame.discard_frame_state_for_pipeline(pipeline_id);
                 }
 
                 let display_list_len = built_display_list.data().len();
                 let (builder_start_time, builder_finish_time, send_start_time) = built_display_list.times();
                 let display_list_received_time = precise_time_ns();
 
                 {
-                    self.webgl.flush();
                     let _timer = profile_counters.total_time.timer();
                     doc.scene.set_display_list(
                         pipeline_id,
                         epoch,
                         built_display_list,
                         background,
                         viewport_size,
                         content_size
@@ -339,17 +263,16 @@ impl RenderBackend {
 
                 DocumentOp::Built
             }
             DocumentMsg::SetRootPipeline(pipeline_id) => {
                 profile_scope!("SetRootPipeline");
 
                 doc.scene.set_root_pipeline_id(pipeline_id);
                 if doc.scene.display_lists.get(&pipeline_id).is_some() {
-                    self.webgl.flush();
                     let _timer = profile_counters.total_time.timer();
                     doc.build_scene(&mut self.resource_cache, self.hidpi_factor);
                     DocumentOp::Built
                 } else {
                     DocumentOp::Nop
                 }
             }
             DocumentMsg::Scroll(delta, cursor, move_phase) => {
@@ -410,17 +333,16 @@ impl RenderBackend {
                 // refactor how scroll roots + transforms work, this
                 // just rebuilds the frame if there are animated property
                 // bindings present for now.
                 // TODO(gw): Once the scrolling / reference frame changes
                 //           are completed, optimize the internals of
                 //           animated properties to not require a full
                 //           rebuild of the frame!
                 if let Some(property_bindings) = property_bindings {
-                    self.webgl.flush();
                     doc.scene.properties.set_properties(property_bindings);
                     doc.build_scene(&mut self.resource_cache, self.hidpi_factor);
                 }
 
                 if let Some(ref mut ros) = doc.render_on_scroll {
                     *ros = true;
                 }
 
@@ -506,73 +428,16 @@ impl RenderBackend {
                             frame_counter += 1;
                             self.publish_frame_and_notify_compositor(document_id, frame, &mut profile_counters);
                         }
                     }
                 }
                 ApiMsg::DeleteDocument(document_id) => {
                     self.documents.remove(&document_id);
                 }
-                ApiMsg::RequestWebGLContext(size, attributes, tx) => {
-                    if let Some(ref wrapper) = self.webrender_context_handle {
-                        let dispatcher: Option<Box<GLContextDispatcher>> = if cfg!(target_os = "windows") {
-                            Some(Box::new(WebRenderGLDispatcher {
-                                dispatcher: Arc::clone(&self.main_thread_dispatcher)
-                            }))
-                        } else {
-                            None
-                        };
-
-                        let result = wrapper.new_context(size, attributes, dispatcher);
-
-                        match result {
-                            Ok(ctx) => {
-                                let (real_size, texture_id, limits) = ctx.get_info();
-                                let id = self.webgl.register(ctx);
-
-                                self.resource_cache
-                                    .add_webgl_texture(id, SourceTexture::WebGL(texture_id),
-                                                       real_size);
-
-                                tx.send(Ok((id, limits))).unwrap();
-                            },
-                            Err(msg) => {
-                                tx.send(Err(msg.to_owned())).unwrap();
-                            }
-                        }
-                    } else {
-                        tx.send(Err("Not implemented yet".to_owned())).unwrap();
-                    }
-                }
-                ApiMsg::ResizeWebGLContext(context_id, size) => {
-                    let ctx = self.webgl.activate(context_id);
-                    match ctx.resize(&size) {
-                        Ok(_) => {
-                            // Update webgl texture size. Texture id may change too.
-                            let (real_size, texture_id, _) = ctx.get_info();
-                            self.resource_cache
-                                .update_webgl_texture(context_id, SourceTexture::WebGL(texture_id),
-                                                      real_size);
-                        },
-                        Err(msg) => {
-                            error!("Error resizing WebGLContext: {}", msg);
-                        }
-                    }
-                }
-                ApiMsg::WebGLCommand(context_id, command) => {
-                    // TODO: Buffer the commands and only apply them here if they need to
-                    // be synchronous.
-                    let ctx = self.webgl.activate(context_id);
-                    ctx.apply_command(command);
-                },
-
-                ApiMsg::VRCompositorCommand(context_id, command) => {
-                    self.webgl.activate(context_id);
-                    self.handle_vr_compositor_command(context_id, command);
-                }
                 ApiMsg::ExternalEvent(evt) => {
                     let notifier = self.notifier.lock();
                     notifier.unwrap()
                             .as_mut()
                             .unwrap()
                             .external_event(evt);
                 }
                 ApiMsg::ClearNamespace(namespace_id) => {
@@ -635,37 +500,9 @@ impl RenderBackend {
     fn notify_compositor_of_new_scroll_frame(&mut self, composite_needed: bool) {
         // TODO(gw): This is kindof bogus to have to lock the notifier
         //           each time it's used. This is due to some nastiness
         //           in initialization order for Servo. Perhaps find a
         //           cleaner way to do this, or use the OnceMutex on crates.io?
         let mut notifier = self.notifier.lock();
         notifier.as_mut().unwrap().as_mut().unwrap().new_scroll_frame_ready(composite_needed);
     }
-
-    fn handle_vr_compositor_command(&mut self, ctx_id: WebGLContextId, cmd: VRCompositorCommand) {
-        let texture = match cmd {
-            VRCompositorCommand::SubmitFrame(..) => {
-                    match self.resource_cache.get_webgl_texture(&ctx_id).id {
-                        SourceTexture::WebGL(texture_id) => {
-                            let size = self.resource_cache.get_webgl_texture_size(&ctx_id);
-                            Some((texture_id, size))
-                        },
-                        _=> None
-                    }
-            },
-            _ => None
-        };
-        let mut handler = self.vr_compositor_handler.lock();
-        handler.as_mut().unwrap().as_mut().unwrap().handle(cmd, texture);
-    }
 }
-
-struct WebRenderGLDispatcher {
-    dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>
-}
-
-impl GLContextDispatcher for WebRenderGLDispatcher {
-    fn dispatch(&self, f: Box<Fn() + Send>) {
-        let mut dispatcher = self.dispatcher.lock();
-        dispatcher.as_mut().unwrap().as_mut().unwrap().dispatch(f);
-    }
-}
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -7,24 +7,24 @@
 //! The `webrender::renderer` module provides the interface to webrender, which
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
 use debug_colors;
 use debug_render::DebugRenderer;
 use device::{DepthFunction, Device, FrameId, Program, TextureId, VertexDescriptor, GpuMarker, GpuProfiler, PBOId};
-use device::{GpuSample, TextureFilter, VAOId, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
+use device::{GpuSample, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
 use device::{get_gl_format_bgra, VertexAttribute, VertexAttributeKind};
 use euclid::{Transform3D, rect};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
-use internal_types::{TextureUpdateList, RenderTargetMode};
+use internal_types::{TextureUpdateList, RenderTargetMode, TextureUpdateSource};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
 use internal_types::{BatchTextures, TextureSampler};
 use profiler::{Profiler, BackendProfileCounters};
 use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::RenderTaskData;
 use std;
@@ -41,22 +41,20 @@ use std::thread;
 use texture_cache::TextureCache;
 use rayon::ThreadPool;
 use rayon::Configuration as ThreadPoolConfig;
 use tiling::{AlphaBatchKind, BlurCommand, CompositePrimitiveInstance, Frame, PrimitiveBatch, RenderTarget};
 use tiling::{AlphaRenderTarget, CacheClipInstance, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
-use webgl_types::GLContextHandleWrapper;
-use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier, RenderDispatcher};
-use api::{ExternalImageId, ExternalImageType, ImageData, ImageFormat};
+use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier};
+use api::{ExternalImageId, ExternalImageType, ImageFormat};
 use api::{DeviceIntRect, DeviceUintRect, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
 use api::{BlobImageRenderer, channel, FontRenderMode};
-use api::VRCompositorHandler;
 use api::{YuvColorSpace, YuvFormat};
 use api::{YUV_COLOR_SPACES, YUV_FORMATS};
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { label: "C_Clip", color: debug_colors::PURPLE };
@@ -120,22 +118,27 @@ const DESC_BLUR: VertexDescriptor = Vert
 
 const DESC_CLIP: VertexDescriptor = VertexDescriptor {
     vertex_attributes: &[
         VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
     ],
     instance_attributes: &[
         VertexAttribute { name: "aClipRenderTaskIndex", count: 1, kind: VertexAttributeKind::I32 },
         VertexAttribute { name: "aClipLayerIndex", count: 1, kind: VertexAttributeKind::I32 },
-        VertexAttribute { name: "aClipDataIndex", count: 1, kind: VertexAttributeKind::I32 },
-        VertexAttribute { name: "aClipSegmentIndex", count: 1, kind: VertexAttributeKind::I32 },
-        VertexAttribute { name: "aClipResourceAddress", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aClipSegment", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aClipDataResourceAddress", count: 4, kind: VertexAttributeKind::U16 },
     ]
 };
 
+enum VertexArrayKind {
+    Primitive,
+    Blur,
+    Clip,
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum VertexFormat {
     PrimitiveInstances,
     Blur,
     Clip,
 }
 
 #[derive(Clone, Debug, PartialEq)]
@@ -150,45 +153,50 @@ pub struct GraphicsApiInfo {
     pub version: String,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum ImageBufferKind {
     Texture2D = 0,
     TextureRect = 1,
     TextureExternal = 2,
+    Texture2DArray = 3,
 }
 
-pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 3] = [
+pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [
     ImageBufferKind::Texture2D,
     ImageBufferKind::TextureRect,
-    ImageBufferKind::TextureExternal
+    ImageBufferKind::TextureExternal,
+    ImageBufferKind::Texture2DArray,
 ];
 
 impl ImageBufferKind {
     pub fn get_feature_string(&self) -> &'static str {
         match *self {
-            ImageBufferKind::Texture2D => "",
+            ImageBufferKind::Texture2D => "TEXTURE_2D",
+            ImageBufferKind::Texture2DArray => "",
             ImageBufferKind::TextureRect => "TEXTURE_RECT",
             ImageBufferKind::TextureExternal => "TEXTURE_EXTERNAL",
         }
     }
 
     pub fn has_platform_support(&self, gl_type: &gl::GlType) -> bool {
         match *gl_type {
             gl::GlType::Gles => {
                 match *self {
                     ImageBufferKind::Texture2D => true,
+                    ImageBufferKind::Texture2DArray => true,
                     ImageBufferKind::TextureRect => true,
                     ImageBufferKind::TextureExternal => true,
                 }
             }
             gl::GlType::Gl => {
                 match *self {
                     ImageBufferKind::Texture2D => true,
+                    ImageBufferKind::Texture2DArray => true,
                     ImageBufferKind::TextureRect => true,
                     ImageBufferKind::TextureExternal => false,
                 }
             }
         }
     }
 }
 
@@ -234,16 +242,98 @@ impl CpuProfile {
             frame_id,
             backend_time_ns,
             composite_time_ns,
             draw_calls,
         }
     }
 }
 
+struct SourceTextureResolver {
+    /// A vector for fast resolves of texture cache IDs to
+    /// native texture IDs. This maps to a free-list managed
+    /// by the backend thread / texture cache. We free the
+    /// texture memory associated with a TextureId when its
+    /// texture cache ID is freed by the texture cache, but
+    /// reuse the TextureId when the texture caches's free
+    /// list reuses the texture cache ID. This saves having to
+    /// use a hashmap, and allows a flat vector for performance.
+    cache_texture_id_map: Vec<TextureId>,
+
+    /// Map of external image IDs to native textures.
+    external_images: FastHashMap<(ExternalImageId, u8), TextureId>,
+
+    /// A special 1x1 dummy cache texture used for shaders that expect to work
+    /// with the cache but are actually running in the first pass
+    /// when no target is yet provided as a cache texture input.
+    dummy_cache_texture_id: TextureId,
+
+    /// The current cache textures.
+    cache_rgba8_texture: Option<TextureId>,
+    cache_a8_texture: Option<TextureId>,
+}
+
+impl SourceTextureResolver {
+    fn new(device: &mut Device) -> SourceTextureResolver {
+        let dummy_cache_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
+        device.init_texture(dummy_cache_texture_id,
+                            1,
+                            1,
+                            ImageFormat::BGRA8,
+                            TextureFilter::Linear,
+                            RenderTargetMode::RenderTarget,
+                            1,
+                            None);
+
+        SourceTextureResolver {
+            cache_texture_id_map: Vec::new(),
+            external_images: FastHashMap::default(),
+            dummy_cache_texture_id,
+            cache_a8_texture: None,
+            cache_rgba8_texture: None,
+        }
+    }
+
+    fn deinit(self, device: &mut Device) {
+        device.deinit_texture(self.dummy_cache_texture_id);
+    }
+
+    fn set_cache_textures(&mut self,
+                          a8_texture: Option<TextureId>,
+                          rgba8_texture: Option<TextureId>) {
+        self.cache_a8_texture = a8_texture;
+        self.cache_rgba8_texture = rgba8_texture;
+    }
+
+    // Get the real (OpenGL) texture ID for a given source texture.
+    // For a texture cache texture, the IDs are stored in a vector
+    // map for fast access.
+    fn resolve(&self, texture_id: &SourceTexture) -> TextureId {
+        match *texture_id {
+            SourceTexture::Invalid => {
+                TextureId::invalid()
+            }
+            SourceTexture::CacheA8 => {
+                self.cache_a8_texture.unwrap_or(self.dummy_cache_texture_id)
+            }
+            SourceTexture::CacheRGBA8 => {
+                self.cache_rgba8_texture.unwrap_or(self.dummy_cache_texture_id)
+            }
+            SourceTexture::External(external_image) => {
+                *self.external_images
+                     .get(&(external_image.id, external_image.channel_index))
+                     .expect("BUG: External image should be resolved by now!")
+            }
+            SourceTexture::TextureCache(index) => {
+                self.cache_texture_id_map[index.0]
+            }
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BlendMode {
     None,
     Alpha,
     PremultipliedAlpha,
 
     // Use the color of the text itself as a constant color blend factor.
     Subpixel(ColorF),
@@ -319,16 +409,17 @@ impl CacheTexture {
             // Create a f32 texture that can be used for the vertex shader
             // to fetch data from.
             device.init_texture(self.texture_id,
                                 MAX_VERTEX_TEXTURE_WIDTH as u32,
                                 updates.height as u32,
                                 ImageFormat::RGBAF32,
                                 TextureFilter::Nearest,
                                 RenderTargetMode::None,
+                                1,
                                 None);
 
             // Copy the current texture into the newly resized texture.
             if current_dimensions.height > 0 {
                 // If we had to resize the texture, just mark all rows
                 // as dirty so they will be uploaded to the texture
                 // during the next flush.
                 for row in &mut self.rows {
@@ -356,16 +447,18 @@ impl CacheTexture {
 
                 // Insert a command to copy the PBO data to the right place in
                 // the GPU-side cache texture.
                 device.update_texture_from_pbo(self.texture_id,
                                                0,
                                                row_index as u32,
                                                MAX_VERTEX_TEXTURE_WIDTH as u32,
                                                1,
+                                               0,
+                                               None,
                                                0);
 
                 // Orphan the PBO. This is the recommended way to hint to the
                 // driver to detach the underlying storage from this PBO id.
                 // Keeping the size the same gives the driver a hint for future
                 // use of this PBO.
                 device.orphan_pbo(mem::size_of::<GpuBlockData>() * MAX_VERTEX_TEXTURE_WIDTH);
 
@@ -451,16 +544,17 @@ impl<L: GpuStoreLayout> GpuDataTexture<L
         };
 
         device.init_texture(self.id,
                             L::texture_width::<T>() as u32,
                             height as u32,
                             L::image_format(),
                             L::texture_filter(),
                             RenderTargetMode::None,
+                            1,
                             Some(unsafe { mem::transmute(data.as_slice()) } ));
     }
 }
 
 pub struct VertexDataTextureLayout {}
 
 impl GpuStoreLayout for VertexDataTextureLayout {
     fn image_format() -> ImageFormat {
@@ -544,18 +638,18 @@ impl LazilyCompiledShader {
                 }
             };
             self.program = Some(program);
         }
 
         Ok(self.program.as_ref().unwrap())
     }
 
-    fn deinit(&mut self, device: &mut Device) {
-        if let &mut Some(ref mut program) = &mut self.program {
+    fn deinit(self, device: &mut Device) {
+        if let Some(program) = self.program {
             device.delete_program(program);
         }
     }
 }
 
 struct PrimitiveShader {
     simple: LazilyCompiledShader,
     transform: LazilyCompiledShader,
@@ -609,17 +703,17 @@ impl PrimitiveShader {
             transform_kind: TransformedRectKind,
             projection: &Transform3D<f32>) {
         match transform_kind {
             TransformedRectKind::AxisAligned => self.simple.bind(device, projection),
             TransformedRectKind::Complex => self.transform.bind(device, projection),
         }
     }
 
-    fn deinit(&mut self, device: &mut Device) {
+    fn deinit(self, device: &mut Device) {
         self.simple.deinit(device);
         self.transform.deinit(device);
     }
 }
 
 fn create_prim_shader(name: &'static str,
                       device: &mut Device,
                       features: &[&'static str],
@@ -753,58 +847,39 @@ pub struct Renderer {
     profile_counters: RendererProfileCounters,
     profiler: Profiler,
     last_time: u64,
 
     color_render_targets: Vec<TextureId>,
     alpha_render_targets: Vec<TextureId>,
 
     gpu_profile: GpuProfiler<GpuProfileTag>,
-    prim_vao_id: VAOId,
-    blur_vao_id: VAOId,
-    clip_vao_id: VAOId,
+    prim_vao: VAO,
+    blur_vao: VAO,
+    clip_vao: VAO,
 
     gdt_index: usize,
     gpu_data_textures: [GpuDataTextures; GPU_DATA_TEXTURE_POOL],
 
     gpu_cache_texture: CacheTexture,
 
     pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
-    /// Used to dispatch functions to the main thread's event loop.
-    /// Required to allow GLContext sharing in some implementations like WGL.
-    main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
 
-    /// A vector for fast resolves of texture cache IDs to
-    /// native texture IDs. This maps to a free-list managed
-    /// by the backend thread / texture cache. We free the
-    /// texture memory associated with a TextureId when its
-    /// texture cache ID is freed by the texture cache, but
-    /// reuse the TextureId when the texture caches's free
-    /// list reuses the texture cache ID. This saves having to
-    /// use a hashmap, and allows a flat vector for performance.
-    cache_texture_id_map: Vec<TextureId>,
+    // Manages and resolves source textures IDs to real texture IDs.
+    texture_resolver: SourceTextureResolver,
 
-    /// A special 1x1 dummy cache texture used for shaders that expect to work
-    /// with the cache but are actually running in the first pass
-    /// when no target is yet provided as a cache texture input.
-    dummy_cache_texture_id: TextureId,
+    // A PBO used to do asynchronous texture cache uploads.
+    texture_cache_upload_pbo: PBOId,
 
     dither_matrix_texture_id: Option<TextureId>,
 
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
-    /// Map of external image IDs to native textures.
-    external_images: FastHashMap<(ExternalImageId, u8), TextureId>,
-
-    // Optional trait object that handles WebVR commands.
-    // Some WebVR commands such as SubmitFrame must be synced with the WebGL render thread.
-    vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
-
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 }
 
 #[derive(Debug)]
 pub enum InitError {
@@ -1131,25 +1206,16 @@ impl Renderer {
                                      options.precache_shaders)
         };
 
         let texture_cache = TextureCache::new(max_device_size);
         let max_texture_size = texture_cache.max_texture_size();
 
         let backend_profile_counters = BackendProfileCounters::new();
 
-        let dummy_cache_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
-        device.init_texture(dummy_cache_texture_id,
-                            1,
-                            1,
-                            ImageFormat::BGRA8,
-                            TextureFilter::Linear,
-                            RenderTargetMode::LayerRenderTarget(1),
-                            None);
-
         let dither_matrix_texture_id = if options.enable_dithering {
             let dither_matrix: [u8; 64] = [
                 00, 48, 12, 60, 03, 51, 15, 63,
                 32, 16, 44, 28, 35, 19, 47, 31,
                 08, 56, 04, 52, 11, 59, 07, 55,
                 40, 24, 36, 20, 43, 27, 39, 23,
                 02, 50, 14, 62, 01, 49, 13, 61,
                 34, 18, 46, 30, 33, 17, 45, 29,
@@ -1159,16 +1225,17 @@ impl Renderer {
 
             let id = device.create_texture_ids(1, TextureTarget::Default)[0];
             device.init_texture(id,
                                 8,
                                 8,
                                 ImageFormat::A8,
                                 TextureFilter::Nearest,
                                 RenderTargetMode::None,
+                                1,
                                 Some(&dither_matrix));
 
             Some(id)
         } else {
             None
         };
 
         let debug_renderer = DebugRenderer::new(&mut device);
@@ -1197,51 +1264,51 @@ impl Renderer {
             PackedVertex {
                 pos: [x0, y1],
             },
             PackedVertex {
                 pos: [x1, y1],
             },
         ];
 
-        let prim_vao_id = device.create_vao(&DESC_PRIM_INSTANCES, mem::size_of::<PrimitiveInstance>() as i32);
-        device.bind_vao(prim_vao_id);
-        device.update_vao_indices(prim_vao_id, &quad_indices, VertexUsageHint::Static);
-        device.update_vao_main_vertices(prim_vao_id, &quad_vertices, VertexUsageHint::Static);
+        let prim_vao = device.create_vao(&DESC_PRIM_INSTANCES,
+                                         mem::size_of::<PrimitiveInstance>() as i32);
+        device.bind_vao(&prim_vao);
+        device.update_vao_indices(&prim_vao,
+                                  &quad_indices,
+                                  VertexUsageHint::Static);
+        device.update_vao_main_vertices(&prim_vao,
+                                        &quad_vertices,
+                                        VertexUsageHint::Static);
 
-        let blur_vao_id = device.create_vao_with_new_instances(&DESC_BLUR, mem::size_of::<BlurCommand>() as i32, prim_vao_id);
-        let clip_vao_id = device.create_vao_with_new_instances(&DESC_CLIP, mem::size_of::<CacheClipInstance>() as i32, prim_vao_id);
+        let blur_vao = device.create_vao_with_new_instances(&DESC_BLUR,
+                                                            mem::size_of::<BlurCommand>() as i32,
+                                                            &prim_vao);
+        let clip_vao = device.create_vao_with_new_instances(&DESC_CLIP,
+                                                            mem::size_of::<CacheClipInstance>() as i32,
+                                                            &prim_vao);
+
+        let texture_cache_upload_pbo = device.create_pbo();
+
+        let texture_resolver = SourceTextureResolver::new(&mut device);
 
         device.end_frame();
 
-        let main_thread_dispatcher = Arc::new(Mutex::new(None));
         let backend_notifier = Arc::clone(&notifier);
-        let backend_main_thread_dispatcher = Arc::clone(&main_thread_dispatcher);
-
-        let vr_compositor = Arc::new(Mutex::new(None));
-        let backend_vr_compositor = Arc::clone(&vr_compositor);
-
-        // We need a reference to the webrender context from the render backend in order to share
-        // texture ids
-        let context_handle = match options.renderer_kind {
-            RendererKind::Native => GLContextHandleWrapper::current_native_handle(),
-            RendererKind::OSMesa => GLContextHandleWrapper::current_osmesa_handle(),
-        };
 
         let default_font_render_mode = match (options.enable_aa, options.enable_subpixel_aa) {
             (true, true) => FontRenderMode::Subpixel,
             (true, false) => FontRenderMode::Alpha,
             (false, _) => FontRenderMode::Mono,
         };
 
         let config = FrameBuilderConfig {
             enable_scrollbars: options.enable_scrollbars,
             default_font_render_mode,
             debug: options.debug,
-            cache_expiry_frames: options.cache_expiry_frames,
         };
 
         let device_pixel_ratio = options.device_pixel_ratio;
         let debug_flags = options.debug_flags;
         let payload_tx_for_backend = payload_tx.clone();
         let recorder = options.recorder;
         let worker_config = ThreadPoolConfig::new()
             .thread_name(|idx|{ format!("WebRender:Worker#{}", idx) })
@@ -1256,22 +1323,19 @@ impl Renderer {
             let mut backend = RenderBackend::new(api_rx,
                                                  payload_rx,
                                                  payload_tx_for_backend,
                                                  result_tx,
                                                  device_pixel_ratio,
                                                  texture_cache,
                                                  workers,
                                                  backend_notifier,
-                                                 context_handle,
                                                  config,
                                                  recorder,
-                                                 backend_main_thread_dispatcher,
                                                  blob_image_renderer,
-                                                 backend_vr_compositor,
                                                  enable_render_on_scroll);
             backend.run(backend_profile_counters);
         })};
 
         let gpu_cache_texture = CacheTexture::new(&mut device);
 
         let gpu_profile = GpuProfiler::new(device.rc_gl());
 
@@ -1318,32 +1382,29 @@ impl Renderer {
             max_recorded_profiles: options.max_recorded_profiles,
             clear_framebuffer: options.clear_framebuffer,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             color_render_targets: Vec::new(),
             alpha_render_targets: Vec::new(),
             gpu_profile,
-            prim_vao_id,
-            blur_vao_id,
-            clip_vao_id,
+            prim_vao,
+            blur_vao,
+            clip_vao,
             gdt_index: 0,
             gpu_data_textures,
             pipeline_epoch_map: FastHashMap::default(),
-            main_thread_dispatcher,
-            cache_texture_id_map: Vec::new(),
-            dummy_cache_texture_id,
             dither_matrix_texture_id,
             external_image_handler: None,
-            external_images: FastHashMap::default(),
-            vr_compositor_handler: vr_compositor,
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
+            texture_cache_upload_pbo,
+            texture_resolver,
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
     pub fn get_max_texture_size(&self) -> u32 {
         self.max_texture_size
@@ -1365,33 +1426,16 @@ impl Renderer {
     ///
     /// The RenderNotifier will be called when processing e.g. of a (scrolling) frame is done,
     /// and therefore the screen should be updated.
     pub fn set_render_notifier(&self, notifier: Box<RenderNotifier>) {
         let mut notifier_arc = self.notifier.lock().unwrap();
         *notifier_arc = Some(notifier);
     }
 
-    /// Sets the new main thread dispatcher.
-    ///
-    /// Allows to dispatch functions to the main thread's event loop.
-    pub fn set_main_thread_dispatcher(&self, dispatcher: Box<RenderDispatcher>) {
-        let mut dispatcher_arc = self.main_thread_dispatcher.lock().unwrap();
-        *dispatcher_arc = Some(dispatcher);
-    }
-
-    /// Sets the VRCompositorHandler.
-    ///
-    /// It's used to handle WebVR render commands.
-    /// Some WebVR commands such as Vsync and SubmitFrame must be called in the WebGL render thread.
-    pub fn set_vr_compositor_handler(&self, creator: Box<VRCompositorHandler>) {
-        let mut handler_arc = self.vr_compositor_handler.lock().unwrap();
-        *handler_arc = Some(creator);
-    }
-
     /// Returns the Epoch of the current frame in a pipeline.
     pub fn current_epoch(&self, pipeline_id: PipelineId) -> Option<Epoch> {
         self.pipeline_epoch_map.get(&pipeline_id).cloned()
     }
 
     /// Returns a HashMap containing the pipeline ids that have been received by the renderer and
     /// their respective epochs since the last time the method was called.
     pub fn flush_rendered_epochs(&mut self) -> FastHashMap<PipelineId, Epoch> {
@@ -1440,37 +1484,16 @@ impl Renderer {
                 }
                 ResultMsg::RefreshShader(path) => {
                     self.pending_shader_updates.push(path);
                 }
             }
         }
     }
 
-    // Get the real (OpenGL) texture ID for a given source texture.
-    // For a texture cache texture, the IDs are stored in a vector
-    // map for fast access. For WebGL textures, the native texture ID
-    // is stored inline. When we add support for external textures,
-    // we will add a callback here that is able to ask the caller
-    // for the image data.
-    fn resolve_source_texture(&mut self, texture_id: &SourceTexture) -> TextureId {
-        match *texture_id {
-            SourceTexture::Invalid => TextureId::invalid(),
-            SourceTexture::WebGL(id) => TextureId::new(id, TextureTarget::Default),
-            SourceTexture::External(external_image) => {
-                *self.external_images
-                     .get(&(external_image.id, external_image.channel_index))
-                     .expect("BUG: External image should be resolved by now!")
-            }
-            SourceTexture::TextureCache(index) => {
-                self.cache_texture_id_map[index.0]
-            }
-        }
-    }
-
     /// Set a callback for handling external images.
     pub fn set_external_image_handler(&mut self, handler: Box<ExternalImageHandler>) {
         self.external_image_handler = Some(handler);
     }
 
     /// Retrieve (and clear) the current list of recorded frame profiles.
     pub fn get_frame_profiles(&mut self) -> (Vec<CpuProfile>, Vec<GpuProfile>) {
         let cpu_profiles = self.cpu_profiles.drain(..).collect();
@@ -1602,145 +1625,110 @@ impl Renderer {
         }
         self.update_deferred_resolves(frame);
         self.gpu_cache_texture.flush(&mut self.device);
     }
 
     fn update_texture_cache(&mut self) {
         let _gm = GpuMarker::new(self.device.rc_gl(), "texture cache update");
         let mut pending_texture_updates = mem::replace(&mut self.pending_texture_updates, vec![]);
+
         for update_list in pending_texture_updates.drain(..) {
             for update in update_list.updates {
                 match update.op {
-                    TextureUpdateOp::Create { width, height, format, filter, mode, data } => {
+                    TextureUpdateOp::Create { width, height, layer_count, format, filter, mode } => {
                         let CacheTextureId(cache_texture_index) = update.id;
-                        if self.cache_texture_id_map.len() == cache_texture_index {
+                        if self.texture_resolver.cache_texture_id_map.len() == cache_texture_index {
                             // Create a new native texture, as requested by the texture cache.
                             let texture_id = self.device
-                                                 .create_texture_ids(1, TextureTarget::Default)[0];
-                            self.cache_texture_id_map.push(texture_id);
+                                                 .create_texture_ids(1, TextureTarget::Array)[0];
+                            self.texture_resolver.cache_texture_id_map.push(texture_id);
                         }
-                        let texture_id = self.cache_texture_id_map[cache_texture_index];
-
-                        if let Some(image) = data {
-                            match image {
-                                ImageData::Raw(raw) => {
-                                    self.device.init_texture(texture_id,
-                                                             width,
-                                                             height,
-                                                             format,
-                                                             filter,
-                                                             mode,
-                                                             Some(raw.as_slice()));
-                                }
-                                ImageData::External(ext_image) => {
-                                    match ext_image.image_type {
-                                        ExternalImageType::ExternalBuffer => {
-                                            let handler = self.external_image_handler
-                                                              .as_mut()
-                                                              .expect("Found external image, but no handler set!");
+                        let texture_id = self.texture_resolver.cache_texture_id_map[cache_texture_index];
 
-                                            match handler.lock(ext_image.id, ext_image.channel_index).source {
-                                                ExternalImageSource::RawData(raw) => {
-                                                    self.device.init_texture(texture_id,
-                                                                             width,
-                                                                             height,
-                                                                             format,
-                                                                             filter,
-                                                                             mode,
-                                                                             Some(raw));
-                                                }
-                                                _ => panic!("No external buffer found"),
-                                            };
-                                            handler.unlock(ext_image.id, ext_image.channel_index);
-                                        }
-                                        ExternalImageType::Texture2DHandle |
-                                        ExternalImageType::TextureRectHandle |
-                                        ExternalImageType::TextureExternalHandle => {
-                                            panic!("External texture handle should not use TextureUpdateOp::Create.");
-                                        }
-                                    }
-                                }
-                                _ => {
-                                    panic!("No suitable image buffer for TextureUpdateOp::Create.");
-                                }
+                        // Ensure no PBO is bound when creating the texture storage,
+                        // or GL will attempt to read data from there.
+                        self.device.bind_pbo(None);
+                        self.device.init_texture(texture_id,
+                                                 width,
+                                                 height,
+                                                 format,
+                                                 filter,
+                                                 mode,
+                                                 layer_count,
+                                                 None);
+                    }
+                    TextureUpdateOp::Update { rect, source, stride, layer_index, offset } => {
+                        let texture_id = self.texture_resolver.cache_texture_id_map[update.id.0];
+
+                        // Bind a PBO to do the texture upload.
+                        // Updating the texture via PBO avoids CPU-side driver stalls.
+                        self.device.bind_pbo(Some(self.texture_cache_upload_pbo));
+
+                        match source {
+                            TextureUpdateSource::Bytes { data }  => {
+                                self.device.update_pbo_data(&data[offset as usize..]);
                             }
-                        } else {
-                            self.device.init_texture(texture_id,
-                                                     width,
-                                                     height,
-                                                     format,
-                                                     filter,
-                                                     mode,
-                                                     None);
+                            TextureUpdateSource::External { id, channel_index } => {
+                                let handler = self.external_image_handler
+                                                  .as_mut()
+                                                  .expect("Found external image, but no handler set!");
+                                match handler.lock(id, channel_index).source {
+                                    ExternalImageSource::RawData(data) => {
+                                        self.device.update_pbo_data(&data[offset as usize..]);
+                                    }
+                                    _ => panic!("No external buffer found"),
+                                };
+                                handler.unlock(id, channel_index);
+                            }
                         }
-                    }
-                    TextureUpdateOp::Grow { width, height, format, filter, mode } => {
-                        let texture_id = self.cache_texture_id_map[update.id.0];
-                        self.device.resize_texture(texture_id,
-                                                   width,
-                                                   height,
-                                                   format,
-                                                   filter,
-                                                   mode);
-                    }
-                    TextureUpdateOp::Update { page_pos_x, page_pos_y, width, height, data, stride, offset } => {
-                        let texture_id = self.cache_texture_id_map[update.id.0];
-                        self.device.update_texture(texture_id,
-                                                   page_pos_x,
-                                                   page_pos_y,
-                                                   width, height, stride,
-                                                   &data[offset as usize..]);
-                    }
-                    TextureUpdateOp::UpdateForExternalBuffer { rect, id, channel_index, stride, offset } => {
-                        let handler = self.external_image_handler
-                                          .as_mut()
-                                          .expect("Found external image, but no handler set!");
-                        let device = &mut self.device;
-                        let cached_id = self.cache_texture_id_map[update.id.0];
 
-                        match handler.lock(id, channel_index).source {
-                            ExternalImageSource::RawData(data) => {
-                                device.update_texture(cached_id,
-                                                      rect.origin.x,
-                                                      rect.origin.y,
-                                                      rect.size.width,
-                                                      rect.size.height,
-                                                      stride,
-                                                      &data[offset as usize..]);
-                            }
-                            _ => panic!("No external buffer found"),
-                        };
-                        handler.unlock(id, channel_index);
+                        self.device.update_texture_from_pbo(texture_id,
+                                                            rect.origin.x,
+                                                            rect.origin.y,
+                                                            rect.size.width,
+                                                            rect.size.height,
+                                                            layer_index,
+                                                            stride,
+                                                            0);
                     }
                     TextureUpdateOp::Free => {
-                        let texture_id = self.cache_texture_id_map[update.id.0];
+                        let texture_id = self.texture_resolver.cache_texture_id_map[update.id.0];
                         self.device.deinit_texture(texture_id);
                     }
                 }
             }
         }
+
+        // Ensure that other texture updates won't read from this PBO.
+        self.device.bind_pbo(None);
     }
 
     fn draw_instanced_batch<T>(&mut self,
                                data: &[T],
-                               vao: VAOId,
+                               vertex_array_kind: VertexArrayKind,
                                textures: &BatchTextures) {
-        self.device.bind_vao(vao);
-
         for i in 0..textures.colors.len() {
-            let texture_id = self.resolve_source_texture(&textures.colors[i]);
+            let texture_id = self.texture_resolver.resolve(&textures.colors[i]);
             self.device.bind_texture(TextureSampler::color(i), texture_id);
         }
 
         // TODO: this probably isn't the best place for this.
         if let Some(id) = self.dither_matrix_texture_id {
             self.device.bind_texture(TextureSampler::Dither, id);
         }
 
+        let vao = match vertex_array_kind {
+            VertexArrayKind::Primitive => &self.prim_vao,
+            VertexArrayKind::Clip => &self.clip_vao,
+            VertexArrayKind::Blur => &self.blur_vao,
+        };
+
+        self.device.bind_vao(vao);
+
         if self.enable_batcher {
             self.device.update_vao_instances(vao, data, VertexUsageHint::Stream);
             self.device.draw_indexed_triangles_instanced_u16(6, data.len() as i32);
             self.profile_counters.draw_calls.inc();
         } else {
             for i in 0 .. data.len() {
                 self.device.update_vao_instances(vao, &data[i..i+1], VertexUsageHint::Stream);
                 self.device.draw_triangles_u16(0, 6);
@@ -1750,17 +1738,16 @@ impl Renderer {
 
         self.profile_counters.vertices.add(6 * data.len());
     }
 
     fn submit_batch(&mut self,
                     batch: &PrimitiveBatch,
                     projection: &Transform3D<f32>,
                     render_task_data: &[RenderTaskData],
-                    cache_texture: TextureId,
                     render_target: Option<(TextureId, i32)>,
                     target_dimensions: DeviceUintSize) {
         let transform_kind = batch.key.flags.transform_kind();
         let needs_clipping = batch.key.flags.needs_clipping();
         debug_assert!(!needs_clipping ||
                       match batch.key.blend_mode {
                           BlendMode::Alpha |
                           BlendMode::PremultipliedAlpha |
@@ -1858,16 +1845,17 @@ impl Renderer {
         };
 
         // Handle special case readback for composites.
         if batch.key.kind == AlphaBatchKind::Composite {
             // composites can't be grouped together because
             // they may overlap and affect each other.
             debug_assert!(batch.instances.len() == 1);
             let instance = CompositePrimitiveInstance::from(&batch.instances[0]);
+            let cache_texture = self.texture_resolver.resolve(&SourceTexture::CacheRGBA8);
 
             // TODO(gw): This code branch is all a bit hacky. We rely
             // on pulling specific values from the render target data
             // and also cloning the single primitive instance to be
             // able to pass to draw_instanced_batch(). We should
             // think about a cleaner way to achieve this!
 
             // Before submitting the composite batch, do the
@@ -1912,27 +1900,25 @@ impl Renderer {
                                            Some(src),
                                            dest);
 
             // Restore draw target to current pass render target + layer.
             self.device.bind_draw_target(render_target, Some(target_dimensions));
         }
 
         let _gm = self.gpu_profile.add_marker(marker);
-        let vao = self.prim_vao_id;
         self.draw_instanced_batch(&batch.instances,
-                                  vao,
+                                  VertexArrayKind::Primitive,
                                   &batch.key.textures);
     }
 
     fn draw_color_target(&mut self,
                          render_target: Option<(TextureId, i32)>,
                          target: &ColorRenderTarget,
                          target_size: DeviceUintSize,
-                         color_cache_texture: TextureId,
                          clear_color: Option<[f32; 4]>,
                          render_task_data: &[RenderTaskData],
                          projection: &Transform3D<f32>) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
             self.device.disable_depth();
             self.device.enable_depth_write();
@@ -1960,73 +1946,69 @@ impl Renderer {
         // Draw any blurs for this target.
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_BLUR);
-            let vao = self.blur_vao_id;
 
             self.device.set_blend(false);
             self.cs_blur.bind(&mut self.device, projection);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(&target.vertical_blurs,
-                                          vao,
+                                          VertexArrayKind::Blur,
                                           &BatchTextures::no_texture());
             }
 
             if !target.horizontal_blurs.is_empty() {
                 self.draw_instanced_batch(&target.horizontal_blurs,
-                                          vao,
+                                          VertexArrayKind::Blur,
                                           &BatchTextures::no_texture());
             }
         }
 
         // Draw any box-shadow caches for this target.
         if !target.box_shadow_cache_prims.is_empty() {
             self.device.set_blend(false);
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
-            let vao = self.prim_vao_id;
             self.cs_box_shadow.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.box_shadow_cache_prims,
-                                      vao,
+                                      VertexArrayKind::Primitive,
                                       &BatchTextures::no_texture());
         }
 
         // Draw any textrun caches for this target. For now, this
         // is only used to cache text runs that are to be blurred
         // for text-shadow support. In the future it may be worth
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_TEXT_RUN);
-            let vao = self.prim_vao_id;
             self.cs_text_run.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.text_run_cache_prims,
-                                      vao,
+                                      VertexArrayKind::Primitive,
                                       &target.text_run_textures);
         }
         if !target.line_cache_prims.is_empty() {
             // TODO(gw): Technically, we don't need blend for solid
             //           lines. We could check that here?
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
-            let vao = self.prim_vao_id;
             self.cs_line.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.line_cache_prims,
-                                      vao,
+                                      VertexArrayKind::Primitive,
                                       &BatchTextures::no_texture());
         }
 
         if !target.alpha_batcher.is_empty() {
             let _gm2 = GpuMarker::new(self.device.rc_gl(), "alpha batches");
             self.device.set_blend(false);
             let mut prev_blend_mode = BlendMode::None;
 
@@ -2040,17 +2022,16 @@ impl Renderer {
             for batch in target.alpha_batcher
                                .batch_list
                                .opaque_batches
                                .iter()
                                .rev() {
                 self.submit_batch(batch,
                                   &projection,
                                   render_task_data,
-                                  color_cache_texture,
                                   render_target,
                                   target_size);
             }
 
             self.device.disable_depth_write();
 
             for batch in &target.alpha_batcher.batch_list.alpha_batches {
                 if batch.key.blend_mode != prev_blend_mode {
@@ -2072,17 +2053,16 @@ impl Renderer {
                         }
                     }
                     prev_blend_mode = batch.key.blend_mode;
                 }
 
                 self.submit_batch(batch,
                                   &projection,
                                   render_task_data,
-                                  color_cache_texture,
                                   render_target,
                                   target_size);
             }
 
             self.device.disable_depth();
             self.device.set_blend(false);
         }
     }
@@ -2107,70 +2087,69 @@ impl Renderer {
             self.device.clear_target_rect(Some(clear_color),
                                           None,
                                           target.used_rect());
         }
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
-            let vao = self.clip_vao_id;
 
             // If we have border corner clips, the first step is to clear out the
             // area in the clip mask. This allows drawing multiple invididual clip
             // in regions below.
             if !target.clip_batcher.border_clears.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders [clear]");
                 self.device.set_blend(false);
                 self.cs_clip_border.bind(&mut self.device, projection);
                 self.draw_instanced_batch(&target.clip_batcher.border_clears,
-                                          vao,
+                                          VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
 
             // Draw any dots or dashes for border corners.
             if !target.clip_batcher.borders.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders");
                 // We are masking in parts of the corner (dots or dashes) here.
                 // Blend mode is set to max to allow drawing multiple dots.
                 // The individual dots and dashes in a border never overlap, so using
                 // a max blend mode here is fine.
                 self.device.set_blend(true);
                 self.device.set_blend_mode_max();
                 self.cs_clip_border.bind(&mut self.device, projection);
                 self.draw_instanced_batch(&target.clip_batcher.borders,
-                                          vao,
+                                          VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
 
             // switch to multiplicative blending
             self.device.set_blend(true);
             self.device.set_blend_mode_multiply();
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip rectangles");
                 self.cs_clip_rectangle.bind(&mut self.device, projection);
                 self.draw_instanced_batch(&target.clip_batcher.rectangles,
-                                          vao,
+                                          VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
             // draw image masks
             for (mask_texture_id, items) in target.clip_batcher.images.iter() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         SourceTexture::Invalid,
                         SourceTexture::Invalid,
                     ]
                 };
                 self.cs_clip_image.bind(&mut self.device, projection);
                 self.draw_instanced_batch(items,
-                                          vao,
+                                          VertexArrayKind::Clip,
                                           &textures);
             }
         }
     }
 
     fn update_deferred_resolves(&mut self, frame: &mut Frame) {
         // The first thing we do is run through any pending deferred
         // resolves, and use a callback to get the UV rect for this
@@ -2184,49 +2163,53 @@ impl Renderer {
             for deferred_resolve in &frame.deferred_resolves {
                 GpuMarker::fire(self.device.gl(), "deferred resolve");
                 let props = &deferred_resolve.image_properties;
                 let ext_image = props.external_image
                                      .expect("BUG: Deferred resolves must be external images!");
                 let image = handler.lock(ext_image.id, ext_image.channel_index);
                 let texture_target = match ext_image.image_type {
                     ExternalImageType::Texture2DHandle => TextureTarget::Default,
+                    ExternalImageType::Texture2DArrayHandle => TextureTarget::Array,
                     ExternalImageType::TextureRectHandle => TextureTarget::Rect,
                     ExternalImageType::TextureExternalHandle => TextureTarget::External,
                     ExternalImageType::ExternalBuffer => {
                         panic!("{:?} is not a suitable image type in update_deferred_resolves().",
                             ext_image.image_type);
                     }
                 };
 
                 let texture_id = match image.source {
                     ExternalImageSource::NativeTexture(texture_id) => TextureId::new(texture_id, texture_target),
                     _ => panic!("No native texture found."),
                 };
 
-                self.external_images.insert((ext_image.id, ext_image.channel_index), texture_id);
+                self.texture_resolver
+                    .external_images
+                    .insert((ext_image.id, ext_image.channel_index), texture_id);
 
                 let update = GpuCacheUpdate::Copy {
                     block_index: 0,
                     block_count: 1,
                     address: deferred_resolve.address,
                 };
-                let blocks = [ [image.u0, image.v0, image.u1, image.v1].into() ];
+
+                let blocks = [ [image.u0, image.v0, image.u1, image.v1].into(), [0.0; 4].into() ];
                 self.gpu_cache_texture.apply_patch(&update, &blocks);
             }
         }
     }
 
     fn unlock_external_images(&mut self) {
-        if !self.external_images.is_empty() {
+        if !self.texture_resolver.external_images.is_empty() {
             let handler = self.external_image_handler
                               .as_mut()
                               .expect("Found external image, but no handler set!");
 
-            for (ext_data, _) in self.external_images.drain() {
+            for (ext_data, _) in self.texture_resolver.external_images.drain() {
                 handler.unlock(ext_data.0, ext_data.1);
             }
         }
     }
 
     fn start_frame(&mut self, frame: &mut Frame) {
         let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_DATA);
 
@@ -2259,37 +2242,40 @@ impl Renderer {
         for pass in &frame.passes {
             if let Some(texture_id) = pass.color_texture_id {
                 let target_count = pass.required_target_count(RenderTargetKind::Color);
                 self.device.init_texture(texture_id,
                                          frame.cache_size.width as u32,
                                          frame.cache_size.height as u32,
                                          ImageFormat::BGRA8,
                                          TextureFilter::Linear,
-                                         RenderTargetMode::LayerRenderTarget(target_count as i32),
+                                         RenderTargetMode::RenderTarget,
+                                         target_count as i32,
                                          None);
             }
             if let Some(texture_id) = pass.alpha_texture_id {
                 let target_count = pass.required_target_count(RenderTargetKind::Alpha);
                 self.device.init_texture(texture_id,
                                          frame.cache_size.width as u32,
                                          frame.cache_size.height as u32,
                                          ImageFormat::A8,
                                          TextureFilter::Nearest,
-                                         RenderTargetMode::LayerRenderTarget(target_count as i32),
+                                         RenderTargetMode::RenderTarget,
+                                         target_count as i32,
                                          None);
             }
         }
 
         // TODO(gw): This is a hack / workaround for #728.
         // We should find a better way to implement these updates rather
         // than wasting this extra memory, but for now it removes a large
         // number of driver stalls.
         self.gpu_data_textures[self.gdt_index].init_frame(&mut self.device, frame);
         self.gdt_index = (self.gdt_index + 1) % GPU_DATA_TEXTURE_POOL;
+        self.texture_resolver.set_cache_textures(None, None);
     }
 
     fn draw_tile_frame(&mut self,
                        frame: &mut Frame,
                        framebuffer_size: &DeviceUintSize) {
         let _gm = GpuMarker::new(self.device.rc_gl(), "tile frame draw");
 
         // Some tests use a restricted viewport smaller than the main screen size.
@@ -2302,19 +2288,16 @@ impl Renderer {
         self.device.disable_stencil();
         self.device.set_blend(false);
 
         if frame.passes.is_empty() {
             self.device.clear_target(Some(self.clear_color.to_array()), Some(1.0));
         } else {
             self.start_frame(frame);
 
-            let mut src_color_id = self.dummy_cache_texture_id;
-            let mut src_alpha_id = self.dummy_cache_texture_id;
-
             for pass in &mut frame.passes {
                 let size;
                 let clear_color;
                 let projection;
 
                 if pass.is_framebuffer {
                     clear_color = if self.clear_framebuffer || needs_clear {
                         Some(frame.background_color.map_or(self.clear_color.to_array(), |color| {
@@ -2336,42 +2319,42 @@ impl Renderer {
                     projection = Transform3D::ortho(0.0,
                                                  size.width as f32,
                                                  0.0,
                                                  size.height as f32,
                                                  ORTHO_NEAR_PLANE,
                                                  ORTHO_FAR_PLANE);
                 }
 
-                self.device.bind_texture(TextureSampler::CacheA8, src_alpha_id);
-                self.device.bind_texture(TextureSampler::CacheRGBA8, src_color_id);
+                let cache_a8_texture = self.texture_resolver.resolve(&SourceTexture::CacheA8);
+                let cache_rgba8_texture = self.texture_resolver.resolve(&SourceTexture::CacheRGBA8);
+                self.device.bind_texture(TextureSampler::CacheA8, cache_a8_texture);
+                self.device.bind_texture(TextureSampler::CacheRGBA8, cache_rgba8_texture);
 
                 for (target_index, target) in pass.alpha_targets.targets.iter().enumerate() {
                     self.draw_alpha_target((pass.alpha_texture_id.unwrap(), target_index as i32),
                                            target,
                                            *size,
                                            &projection);
                 }
 
                 for (target_index, target) in pass.color_targets.targets.iter().enumerate() {
                     let render_target = pass.color_texture_id.map(|texture_id| {
                         (texture_id, target_index as i32)
                     });
                     self.draw_color_target(render_target,
                                            target,
                                            *size,
-                                           src_color_id,
                                            clear_color,
                                            &frame.render_task_data,
                                            &projection);
 
                 }
 
-                src_color_id = pass.color_texture_id.unwrap_or(self.dummy_cache_texture_id);
-                src_alpha_id = pass.alpha_texture_id.unwrap_or(self.dummy_cache_texture_id);
+                self.texture_resolver.set_cache_textures(pass.alpha_texture_id, pass.color_texture_id);
 
                 // Return the texture IDs to the pool for next frame.
                 if let Some(texture_id) = pass.color_texture_id.take() {
                     self.color_render_targets.push(texture_id);
                 }
                 if let Some(texture_id) = pass.alpha_texture_id.take() {
                     self.alpha_render_targets.push(texture_id);
                 }
@@ -2438,35 +2421,47 @@ impl Renderer {
     fn draw_texture_cache_debug(&mut self, framebuffer_size: &DeviceUintSize) {
         if !self.debug_flags.contains(TEXTURE_CACHE_DBG) {
             return;
         }
 
         let mut spacing = 16;
         let mut size = 512;
         let fb_width = framebuffer_size.width as i32;
-        let num_textures = self.cache_texture_id_map.len() as i32;
+        let num_layers: i32 = self.texture_resolver
+                                  .cache_texture_id_map
+                                  .iter()
+                                  .map(|id| {
+                                      self.device.get_texture_layer_count(*id)
+                                  })
+                                  .sum();
 
-        if num_textures * (size + spacing) > fb_width {
-            let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32;
+        if num_layers * (size + spacing) > fb_width {
+            let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32;
             size = (size as f32 * factor) as i32;
             spacing = (spacing as f32 * factor) as i32;
         }
 
-        for (i, texture_id) in self.cache_texture_id_map.iter().enumerate() {
-            let x = fb_width - (spacing + size) * (i as i32 + 1);
+        let mut i = 0;
+        for texture_id in &self.texture_resolver.cache_texture_id_map {
             let y = spacing + if self.debug_flags.contains(RENDER_TARGET_DBG) { 528 } else { 0 };
 
-            // If we have more targets than fit on one row in screen, just early exit.
-            if x > fb_width {
-                return;
+            let layer_count = self.device.get_texture_layer_count(*texture_id);
+            for layer_index in 0..layer_count {
+                let x = fb_width - (spacing + size) * (i as i32 + 1);
+
+                // If we have more targets than fit on one row in screen, just early exit.
+                if x > fb_width {
+                    return;
+                }
+
+                let dest_rect = rect(x, y, size, size);
+                self.device.blit_render_target(Some((*texture_id, layer_index)), None, dest_rect);
+                i += 1;
             }
-
-            let dest_rect = rect(x, y, size, size);
-            self.device.blit_render_target(Some((*texture_id, 0)), None, dest_rect);
         }
     }
 
     pub fn read_pixels_rgba8(&self, rect: DeviceUintRect) -> Vec<u8> {
         let mut pixels = vec![0u8; (4 * rect.size.width * rect.size.height) as usize];
         self.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels);
         pixels
     }
@@ -2489,36 +2484,39 @@ impl Renderer {
                                                  gl_type,
                                                  output);
     }
 
     // De-initialize the Renderer safely, assuming the GL is still alive and active.
     pub fn deinit(mut self) {
         //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame
         self.device.begin_frame(1.0);
-        self.device.deinit_texture(self.dummy_cache_texture_id);
+        self.texture_resolver.deinit(&mut self.device);
+        self.device.delete_vao(self.prim_vao);
+        self.device.delete_vao(self.clip_vao);
+        self.device.delete_vao(self.blur_vao);
         self.debug.deinit(&mut self.device);
         self.cs_box_shadow.deinit(&mut self.device);
         self.cs_text_run.deinit(&mut self.device);
         self.cs_line.deinit(&mut self.device);
         self.cs_blur.deinit(&mut self.device);
         self.cs_clip_rectangle.deinit(&mut self.device);
         self.cs_clip_image.deinit(&mut self.device);
         self.cs_clip_border.deinit(&mut self.device);
         self.ps_rectangle.deinit(&mut self.device);
         self.ps_rectangle_clip.deinit(&mut self.device);
         self.ps_text_run.deinit(&mut self.device);
         self.ps_text_run_subpixel.deinit(&mut self.device);
-        for shader in &mut self.ps_image {
-            if let &mut Some(ref mut shader) = shader {
+        for shader in self.ps_image {
+            if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
         }
-        for shader in &mut self.ps_yuv_image {
-            if let &mut Some(ref mut shader) = shader {
+        for shader in self.ps_yuv_image {
+            if let Some(shader) = shader {
                 shader.deinit(&mut self.device);
             }
         }
         self.ps_border_corner.deinit(&mut self.device);
         self.ps_border_edge.deinit(&mut self.device);
         self.ps_gradient.deinit(&mut self.device);
         self.ps_angle_gradient.deinit(&mut self.device);
         self.ps_radial_gradient.deinit(&mut self.device);
@@ -2582,17 +2580,16 @@ pub struct RendererOptions {
     pub precache_shaders: bool,
     pub renderer_kind: RendererKind,
     pub enable_subpixel_aa: bool,
     pub clear_framebuffer: bool,
     pub clear_color: ColorF,
     pub enable_clear_scissor: bool,
     pub enable_batcher: bool,
     pub max_texture_size: Option<u32>,
-    pub cache_expiry_frames: u32,
     pub workers: Option<Arc<ThreadPool>>,
     pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
     pub enable_render_on_scroll: bool,
     pub debug_flags: DebugFlags,
 }
 
 impl Default for RendererOptions {
@@ -2609,16 +2606,15 @@ impl Default for RendererOptions {
             precache_shaders: false,
             renderer_kind: RendererKind::Native,
             enable_subpixel_aa: false,
             clear_framebuffer: true,
             clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0),
             enable_clear_scissor: true,
             enable_batcher: true,
             max_texture_size: None,
-            cache_expiry_frames: 600, // roughly, 10 seconds
             workers: None,
             blob_image_renderer: None,
             recorder: None,
             enable_render_on_scroll: true,
         }
     }
 }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -3,31 +3,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use device::TextureFilter;
 use frame::FrameId;
 use glyph_cache::GlyphCache;
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
-use std::cmp;
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
-use texture_cache::{TextureCache, TextureCacheItemId};
+use texture_cache::{TextureCache, TextureCacheHandle};
 use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest};
 use api::{BlobImageResources, BlobImageData, ResourceUpdates, ResourceUpdate, AddFont};
-use api::{DevicePoint, DeviceIntSize, DeviceUintRect, DeviceUintSize};
-use api::{Epoch, FontInstanceKey, FontKey, FontTemplate};
+use api::{DevicePoint, DeviceUintRect, DeviceUintSize};
+use api::{Epoch, FontInstance, FontKey, FontTemplate};
 use api::{GlyphDimensions, GlyphKey, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
-use api::{ExternalImageData, ExternalImageType, WebGLContextId};
+use api::{ExternalImageData, ExternalImageType};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
 // These coordinates are always in texels.
 // They are converted to normalized ST
 // values in the vertex shader. The reason
@@ -97,104 +96,62 @@ impl ImageTemplates {
     }
 
     fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> {
         self.images.get_mut(&key)
     }
 }
 
 struct CachedImageInfo {
-    texture_cache_id: TextureCacheItemId,
+    texture_cache_handle: TextureCacheHandle,
     epoch: Epoch,
-    last_access: FrameId,
 }
 
 pub struct ResourceClassCache<K,V> {
     resources: FastHashMap<K, V>,
 }
 
-impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug, V: Resource {
+impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug {
     pub fn new() -> ResourceClassCache<K,V> {
         ResourceClassCache {
             resources: FastHashMap::default(),
         }
     }
 
-    fn get(&self, key: &K, frame: FrameId) -> &V {
-        let resource = self.resources
-                           .get(key)
-                           .expect("Didn't find a cached resource with that ID!");
-
-        // This assert catches cases in which we accidentally request a resource that we forgot to
-        // mark as needed this frame.
-        debug_assert_eq!(frame, resource.get_last_access_time());
-
-        resource
+    fn get(&self, key: &K) -> &V {
+        self.resources
+            .get(key)
+            .expect("Didn't find a cached resource with that ID!")
     }
 
     pub fn insert(&mut self, key: K, value: V) {
         self.resources.insert(key, value);
     }
 
-    pub fn entry(&mut self, key: K, frame: FrameId) -> Entry<K,V> {
-        let mut entry = self.resources.entry(key);
-        match entry {
-            Occupied(ref mut entry) => {
-                entry.get_mut().set_last_access_time(frame);
-            }
-            Vacant(..) => {}
-        }
-        entry
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.resources.is_empty()
+    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
+        self.resources.get_mut(key)
     }
 
-    pub fn update(&mut self,
-                  texture_cache: &mut TextureCache,
-                  gpu_cache: &mut GpuCache,
-                  current_frame_id: FrameId,
-                  expiry_frame_id: FrameId) {
-        let mut resources_to_destroy = Vec::new();
-
-        for (key, resource) in &self.resources {
-            let last_access = resource.get_last_access_time();
-            if last_access < expiry_frame_id {
-                resources_to_destroy.push(key.clone());
-            } else if last_access == current_frame_id {
-                resource.add_to_gpu_cache(texture_cache, gpu_cache);
-            }
-        }
-
-        for key in resources_to_destroy {
-            let resource =
-                self.resources
-                    .remove(&key)
-                    .expect("Resource was in `last_access_times` but not in `resources`!");
-            resource.free(texture_cache);
-        }
+    pub fn entry(&mut self, key: K) -> Entry<K,V> {
+        self.resources.entry(key)
     }
 
-    pub fn clear(&mut self, texture_cache: &mut TextureCache) {
-        for (_, resource) in self.resources.drain() {
-            resource.free(texture_cache);
-        }
+    pub fn clear(&mut self) {
+        self.resources.clear();
     }
 
-    fn clear_keys<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
+    fn clear_keys<F>(&mut self, key_fun: F)
     where for<'r> F: Fn(&'r &K) -> bool
     {
         let resources_to_destroy = self.resources.keys()
             .filter(&key_fun)
             .cloned()
             .collect::<Vec<_>>();
         for key in resources_to_destroy {
-            let resource = self.resources.remove(&key).unwrap();
-            resource.free(texture_cache);
+            self.resources.remove(&key).unwrap();
         }
     }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 struct ImageRequest {
     key: ImageKey,
@@ -206,21 +163,16 @@ impl Into<BlobImageRequest> for ImageReq
     fn into(self) -> BlobImageRequest {
         BlobImageRequest {
             key: self.key,
             tile: self.tile,
         }
     }
 }
 
-pub struct WebGLTexture {
-    pub id: SourceTexture,
-    pub size: DeviceIntSize,
-}
-
 struct Resources {
     font_templates: FastHashMap<FontKey, FontTemplate>,
     image_templates: ImageTemplates,
 }
 
 impl BlobImageResources for Resources {
     fn get_font_data(&self, key: FontKey) -> &FontTemplate {
         self.font_templates.get(&key).unwrap()
@@ -229,60 +181,52 @@ impl BlobImageResources for Resources {
         self.image_templates.get(key).map(|resource| { (&resource.data, &resource.descriptor) })
     }
 }
 
 pub struct ResourceCache {
     cached_glyphs: GlyphCache,
     cached_images: ResourceClassCache<ImageRequest, CachedImageInfo>,
 
-    // TODO(pcwalton): Figure out the lifecycle of these.
-    webgl_textures: FastHashMap<WebGLContextId, WebGLTexture>,
-
     resources: Resources,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
     cached_glyph_dimensions: FastHashMap<GlyphRequest, Option<GlyphDimensions>>,
     glyph_rasterizer: GlyphRasterizer,
 
     // The set of images that aren't present or valid in the texture cache,
     // and need to be rasterized and/or uploaded this frame. This includes
     // both blobs and regular images.
     pending_image_requests: FastHashSet<ImageRequest>,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
-
-    cache_expiry_frames: u32,
 }
 
 impl ResourceCache {
     pub fn new(texture_cache: TextureCache,
                workers: Arc<ThreadPool>,
-               blob_image_renderer: Option<Box<BlobImageRenderer>>,
-               cache_expiry_frames: u32) -> ResourceCache {
+               blob_image_renderer: Option<Box<BlobImageRenderer>>) -> ResourceCache {
         ResourceCache {
             cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
-            webgl_textures: FastHashMap::default(),
             resources: Resources {
                 font_templates: FastHashMap::default(),
                 image_templates: ImageTemplates::new(),
             },
             cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
             current_frame_id: FrameId(0),
             pending_image_requests: FastHashSet::default(),
             glyph_rasterizer: GlyphRasterizer::new(workers),
             blob_image_renderer,
-            cache_expiry_frames,
         }
     }
 
     pub fn max_texture_size(&self) -> u32 {
         self.texture_cache.max_texture_size()
     }
 
     fn should_tile(&self, descriptor: &ImageDescriptor, data: &ImageData) -> bool {
@@ -421,159 +365,161 @@ impl ResourceCache {
         };
 
         self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         let value = self.resources.image_templates.remove(image_key);
 
-        self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key == image_key);
+        self.cached_images.clear_keys(|request| request.key == image_key);
 
         match value {
             Some(image) => {
                 if image.data.is_blob() {
                     self.blob_image_renderer.as_mut().unwrap().delete(image_key);
                 }
             }
             None => {
                 println!("Delete the non-exist key:{:?}", image_key);
             }
         }
     }
 
-    pub fn add_webgl_texture(&mut self, id: WebGLContextId, texture_id: SourceTexture, size: DeviceIntSize) {
-        self.webgl_textures.insert(id, WebGLTexture {
-            id: texture_id,
-            size,
-        });
-    }
-
-    pub fn update_webgl_texture(&mut self, id: WebGLContextId, texture_id: SourceTexture, size: DeviceIntSize) {
-        let webgl_texture = self.webgl_textures.get_mut(&id).unwrap();
-
-        // Update new texture id and size
-        webgl_texture.id = texture_id;
-        webgl_texture.size = size;
-    }
-
     pub fn request_image(&mut self,
                          key: ImageKey,
                          rendering: ImageRendering,
-                         tile: Option<TileOffset>) {
+                         tile: Option<TileOffset>,
+                         gpu_cache: &mut GpuCache) {
 
         debug_assert_eq!(self.state, State::AddResources);
         let request = ImageRequest {
             key,
             rendering,
             tile,
         };
 
-        let template = self.resources.image_templates.get(key).unwrap();
+        match self.resources.image_templates.get(key) {
+            Some(template) => {
+                // Images that don't use the texture cache can early out.
+                if !template.data.uses_texture_cache() {
+                    return;
+                }
 
-        // Images that don't use the texture cache can early out.
-        if !template.data.uses_texture_cache() {
-            return;
-        }
+                // If this image exists in the texture cache, *and* the epoch
+                // in the cache matches that of the template, then it is
+                // valid to use as-is.
+                let (entry, needs_update) = match self.cached_images.entry(request) {
+                    Occupied(entry) => {
+                        let needs_update = entry.get().epoch != template.epoch;
+                        (entry.into_mut(), needs_update)
+                    }
+                    Vacant(entry) => {
+                        (entry.insert(CachedImageInfo {
+                            epoch: template.epoch,
+                            texture_cache_handle: TextureCacheHandle::new(),
+                        }), true)
+                    }
+                };
+
+                let needs_upload = self.texture_cache
+                                       .request(&mut entry.texture_cache_handle,
+                                                gpu_cache);
+
+                if !needs_upload && !needs_update {
+                    return;
+                }
 
-        // If this image exists in the texture cache, *and* the epoch
-        // in the cache matches that of the template, then it is
-        // valid to use as-is.
-        match self.cached_images.entry(request, self.current_frame_id) {
-            Occupied(entry) => {
-                let cached_image = entry.get();
-                if cached_image.epoch == template.epoch {
-                    return;
+                // We can start a worker thread rasterizing right now, if:
+                //  - The image is a blob.
+                //  - The blob hasn't already been requested this frame.
+                if self.pending_image_requests.insert(request) {
+                    if template.data.is_blob() {
+                        if let Some(ref mut renderer) = self.blob_image_renderer {
+                            let (offset, w, h) = match template.tiling {
+                                Some(tile_size) => {
+                                    let tile_offset = request.tile.unwrap();
+                                    let (w, h) = compute_tile_size(&template.descriptor, tile_size, tile_offset);
+                                    let offset = DevicePoint::new(
+                                        tile_offset.x as f32 * tile_size as f32,
+                                        tile_offset.y as f32 * tile_size as f32,
+                                    );
+
+                                    (offset, w, h)
+                                }
+                                None => {
+                                    (DevicePoint::zero(), template.descriptor.width, template.descriptor.height)
+                                }
+                            };
+
+                            renderer.request(
+                                &self.resources,
+                                request.into(),
+                                &BlobImageDescriptor {
+                                    width: w,
+                                    height: h,
+                                    offset,
+                                    format: template.descriptor.format,
+                                },
+                                template.dirty_rect,
+                            );
+                        }
+                    }
                 }
             }
-            Vacant(..) => {}
-        }
-
-        // We can start a worker thread rasterizing right now, if:
-        //  - The image is a blob.
-        //  - The blob hasn't already been requested this frame.
-        if self.pending_image_requests.insert(request) {
-            if template.data.is_blob() {
-                if let Some(ref mut renderer) = self.blob_image_renderer {
-                    let (offset, w, h) = match template.tiling {
-                        Some(tile_size) => {
-                            let tile_offset = request.tile.unwrap();
-                            let (w, h) = compute_tile_size(&template.descriptor, tile_size, tile_offset);
-                            let offset = DevicePoint::new(
-                                tile_offset.x as f32 * tile_size as f32,
-                                tile_offset.y as f32 * tile_size as f32,
-                            );
-
-                            (offset, w, h)
-                        }
-                        None => {
-                            (DevicePoint::zero(), template.descriptor.width, template.descriptor.height)
-                        }
-                    };
-
-                    renderer.request(
-                        &self.resources,
-                        request.into(),
-                        &BlobImageDescriptor {
-                            width: w,
-                            height: h,
-                            offset,
-                            format: template.descriptor.format,
-                        },
-                        template.dirty_rect,
-                    );
-                }
+            None => {
+                warn!("ERROR: Trying to render deleted / non-existent key {:?}", key);
             }
         }
     }
 
     pub fn request_glyphs(&mut self,
-                          font: FontInstanceKey,
-                          glyph_keys: &[GlyphKey]) {
+                          font: FontInstance,
+                          glyph_keys: &[GlyphKey],
+                          gpu_cache: &mut GpuCache) {
         debug_assert_eq!(self.state, State::AddResources);
 
         self.glyph_rasterizer.request_glyphs(
             &mut self.cached_glyphs,
-            self.current_frame_id,
             font,
             glyph_keys,
+            &mut self.texture_cache,
+            gpu_cache,
         );
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         self.texture_cache.pending_updates()
     }
 
     pub fn get_glyphs<F>(&self,
-                         font: FontInstanceKey,
+                         font: FontInstance,
                          glyph_keys: &[GlyphKey],
                          mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
         debug_assert_eq!(self.state, State::QueryResources);
         let mut texture_id = None;
 
         let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
 
         for (loop_index, key) in glyph_keys.iter().enumerate() {
-            let glyph = glyph_key_cache.get(key, self.current_frame_id);
-            let cache_item = glyph.texture_cache_id
-                                  .as_ref()
-                                  .map(|image_id| self.texture_cache.get(image_id));
+            let glyph = glyph_key_cache.get(key);
+            let cache_item = glyph.as_ref().map(|info| self.texture_cache.get(&info.texture_cache_handle));
             if let Some(cache_item) = cache_item {
                 f(loop_index, &cache_item.uv_rect_handle);
                 debug_assert!(texture_id == None ||
                               texture_id == Some(cache_item.texture_id));
                 texture_id = Some(cache_item.texture_id);
             }
         }
 
-        texture_id.map_or(SourceTexture::Invalid, SourceTexture::TextureCache)
+        texture_id.unwrap_or(SourceTexture::Invalid)
     }
 
     pub fn get_glyph_dimensions(&mut self,
-                                font: &FontInstanceKey,
+                                font: &FontInstance,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
         let key = GlyphRequest::new(font, key);
 
         match self.cached_glyph_dimensions.entry(key.clone()) {
             Occupied(entry) => *entry.get(),
             Vacant(entry) => {
                 *entry.insert(self.glyph_rasterizer.get_glyph_dimensions(&key.font, &key.key))
             }
@@ -590,106 +536,89 @@ impl ResourceCache {
                             image_rendering: ImageRendering,
                             tile: Option<TileOffset>) -> CacheItem {
         debug_assert_eq!(self.state, State::QueryResources);
         let key = ImageRequest {
             key: image_key,
             rendering: image_rendering,
             tile,
         };
-        let image_info = &self.cached_images.get(&key, self.current_frame_id);
-        let item = self.texture_cache.get(&image_info.texture_cache_id);
-        CacheItem {
-            texture_id: SourceTexture::TextureCache(item.texture_id),
-            uv_rect_handle: item.uv_rect_handle,
-        }
+        let image_info = &self.cached_images.get(&key);
+        self.texture_cache.get(&image_info.texture_cache_handle)
     }
 
-    pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties {
-        let image_template = &self.resources.image_templates.get(image_key).unwrap();
+    pub fn get_image_properties(&self, image_key: ImageKey) -> Option<ImageProperties> {
+        let image_template = &self.resources.image_templates.get(image_key);
 
-        let external_image = match image_template.data {
-            ImageData::External(ext_image) => {
-                match ext_image.image_type {
-                    ExternalImageType::Texture2DHandle |
-                    ExternalImageType::TextureRectHandle |
-                    ExternalImageType::TextureExternalHandle => {
-                        Some(ext_image)
-                    },
-                    // external buffer uses resource_cache.
-                    ExternalImageType::ExternalBuffer => None,
-                }
-            },
-            // raw and blob image are all using resource_cache.
-            ImageData::Raw(..) | ImageData::Blob(..) => None,
-        };
+        image_template.map(|image_template| {
+            let external_image = match image_template.data {
+                ImageData::External(ext_image) => {
+                    match ext_image.image_type {
+                        ExternalImageType::Texture2DHandle |
+                        ExternalImageType::Texture2DArrayHandle |
+                        ExternalImageType::TextureRectHandle |
+                        ExternalImageType::TextureExternalHandle => {
+                            Some(ext_image)
+                        },
+                        // external buffer uses resource_cache.
+                        ExternalImageType::ExternalBuffer => None,
+                    }
+                },
+                // raw and blob image are all using resource_cache.
+                ImageData::Raw(..) | ImageData::Blob(..) => None,
+            };
 
-        ImageProperties {
-            descriptor: image_template.descriptor,
-            external_image,
-            tiling: image_template.tiling,
-        }
+            ImageProperties {
+                descriptor: image_template.descriptor,
+                external_image,
+                tiling: image_template.tiling,
+            }
+        })
     }
 
     pub fn get_tiled_image_map(&self) -> TiledImageMap {
         self.resources.image_templates.images.iter().filter_map(|(&key, template)|
             template.tiling.map(|tile_size| (key, ImageTiling {
                 image_size: DeviceUintSize::new(template.descriptor.width,
                                                 template.descriptor.height),
                 tile_size,
             }))
         ).collect()
     }
 
-    pub fn get_webgl_texture(&self, context_id: &WebGLContextId) -> &WebGLTexture {
-        &self.webgl_textures[context_id]
-    }
-
-    pub fn get_webgl_texture_size(&self, context_id: &WebGLContextId) -> DeviceIntSize {
-        self.webgl_textures[context_id].size
-    }
-
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
+        self.texture_cache.begin_frame(frame_id);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(&mut self,
                                            gpu_cache: &mut GpuCache,
                                            texture_cache_profile: &mut TextureCacheProfileCounters) {
         profile_scope!("block_until_all_resources_added");
 
         debug_assert_eq!(self.state, State::AddResources);
         self.state = State::QueryResources;
 
         self.glyph_rasterizer.resolve_glyphs(
-            self.current_frame_id,
             &mut self.cached_glyphs,
             &mut self.texture_cache,
+            gpu_cache,
             texture_cache_profile,
         );
 
         // Apply any updates of new / updated images (incl. blobs) to the texture cache.
-        self.update_texture_cache(texture_cache_profile);
-
-        // Expire any resources that haven't been used for `cache_expiry_frames`.
-        let num_frames_back = self.cache_expiry_frames;
-        let expiry_frame = FrameId(cmp::max(num_frames_back, self.current_frame_id.0) - num_frames_back);
-        self.cached_images.update(&mut self.texture_cache,
-                                  gpu_cache,
-                                  self.current_frame_id,
-                                  expiry_frame);
-        self.cached_glyphs.update(&mut self.texture_cache,
-                                  gpu_cache,
-                                  self.current_frame_id,
-                                  expiry_frame);
+        self.update_texture_cache(gpu_cache, texture_cache_profile);
+        self.texture_cache.end_frame();
     }
 
-    fn update_texture_cache(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
+    fn update_texture_cache(&mut self,
+                            gpu_cache: &mut GpuCache,
+                            _texture_cache_profile: &mut TextureCacheProfileCounters) {
         for request in self.pending_image_requests.drain() {
             let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
             debug_assert!(image_template.data.uses_texture_cache());
 
             let image_data = match image_template.data {
                 ImageData::Raw(..) | ImageData::External(..) => {
                     // Safe to clone here since the Raw image data is an
                     // Arc, and the external image data is small.
@@ -752,48 +681,25 @@ impl ResourceCache {
                     offset,
                     format: image_descriptor.format,
                     is_opaque: image_descriptor.is_opaque,
                 }
             } else {
                 image_template.descriptor.clone()
             };
 
-            match self.cached_images.entry(request, self.current_frame_id) {
-                Occupied(mut entry) => {
-                    let entry = entry.get_mut();
-
-                    // We should only get to this code path if the image
-                    // definitely needs to be updated.
-                    debug_assert!(entry.epoch != image_template.epoch);
-                    self.texture_cache.update(&entry.texture_cache_id,
-                                              descriptor,
-                                              filter,
-                                              image_data,
-                                              image_template.dirty_rect);
-
-                    // Update the cached epoch
-                    debug_assert_eq!(self.current_frame_id, entry.last_access);
-                    entry.epoch = image_template.epoch;
-                    image_template.dirty_rect = None;
-                }
-                Vacant(entry) => {
-                    let image_id = self.texture_cache.insert(descriptor,
-                                                             filter,
-                                                             image_data,
-                                                             [0.0; 2],
-                                                             texture_cache_profile);
-
-                    entry.insert(CachedImageInfo {
-                        texture_cache_id: image_id,
-                        epoch: image_template.epoch,
-                        last_access: self.current_frame_id,
-                    });
-                }
-            };
+            let entry = self.cached_images.get_mut(&request).unwrap();
+            self.texture_cache.update(&mut entry.texture_cache_handle,
+                                      descriptor,
+                                      filter,
+                                      image_data,
+                                      [0.0; 2],
+                                      image_template.dirty_rect,
+                                      gpu_cache);
+            image_template.dirty_rect = None;
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
 
@@ -801,18 +707,18 @@ impl ResourceCache {
         // This is drastic. It will basically flush everything out of the cache,
         // and the next frame will have to rebuild all of its resources.
         // We may want to look into something less extreme, but on the other hand this
         // should only be used in situations where are running low enough on memory
         // that we risk crashing if we don't do something about it.
         // The advantage of clearing the cache completely is that it gets rid of any
         // remaining fragmentation that could have persisted if we kept around the most
         // recently used resources.
-        self.cached_images.clear(&mut self.texture_cache);
-        self.cached_glyphs.clear(&mut self.texture_cache);
+        self.cached_images.clear();
+        self.cached_glyphs.clear();
     }
 
     pub fn clear_namespace(&mut self, namespace: IdNamespace) {
         //TODO: use `retain` when we are on Rust-1.18
         let image_keys: Vec<_> = self.resources.image_templates.images.keys()
                                                                       .filter(|&key| key.0 == namespace)
                                                                       .cloned()
                                                                       .collect();
@@ -823,47 +729,18 @@ impl ResourceCache {
         let font_keys: Vec<_> = self.resources.font_templates.keys()
                                                              .filter(|&key| key.0 == namespace)
                                                              .cloned()
                                                              .collect();
         for key in &font_keys {
             self.resources.font_templates.remove(key);
         }
 
-        self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key.0 == namespace);
-        self.cached_glyphs.clear_fonts(&mut self.texture_cache, |font| font.font_key.0 == namespace);
-    }
-}
-
-pub trait Resource {
-    fn free(self, texture_cache: &mut TextureCache);
-    fn get_last_access_time(&self) -> FrameId;
-    fn set_last_access_time(&mut self, frame_id: FrameId);
-    fn add_to_gpu_cache(&self,
-                        texture_cache: &mut TextureCache,
-                        gpu_cache: &mut GpuCache);
-}
-
-impl Resource for CachedImageInfo {
-    fn free(self, texture_cache: &mut TextureCache) {
-        texture_cache.free(self.texture_cache_id);
-    }
-    fn get_last_access_time(&self) -> FrameId {
-        self.last_access
-    }
-    fn set_last_access_time(&mut self, frame_id: FrameId) {
-        self.last_access = frame_id;
-    }
-    fn add_to_gpu_cache(&self,
-                        texture_cache: &mut TextureCache,
-                        gpu_cache: &mut GpuCache) {
-        let item = texture_cache.get_mut(&self.texture_cache_id);
-        if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) {
-            request.push(item.uv_rect);
-        }
+        self.cached_images.clear_keys(|request| request.key.0 == namespace);
+        self.cached_glyphs.clear_fonts(|font| font.font_key.0 == namespace);
     }
 }
 
 // Compute the width and height of a tile depending on its position in the image.
 pub fn compute_tile_size(descriptor: &ImageDescriptor,
                          base_size: TileSize,
                          tile: TileOffset) -> (u32, u32) {
     let base_size = base_size as u32;
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -1,1079 +1,1000 @@
 /* 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 device::TextureFilter;
-use freelist::{FreeList, FreeListHandle};
-use gpu_cache::GpuCacheHandle;
-use internal_types::{FastHashMap, TextureUpdate, TextureUpdateOp, UvRect};
-use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList};
-use profiler::TextureCacheProfileCounters;
+use frame::FrameId;
+use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
+use gpu_cache::{GpuCache, GpuCacheHandle};
+use internal_types::{SourceTexture, TextureUpdate, TextureUpdateOp};
+use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList, TextureUpdateSource};
+use resource_cache::CacheItem;
 use std::cmp;
-use std::collections::hash_map::Entry;
 use std::mem;
-use std::slice::Iter;
-use time;
-use util;
 use api::{ExternalImageType, ImageData, ImageFormat};
 use api::{DeviceUintRect, DeviceUintSize, DeviceUintPoint};
-use api::{DevicePoint, ImageDescriptor};
-
-/// The number of bytes we're allowed to use for a texture.
-const MAX_BYTES_PER_TEXTURE: u32 = 1024 * 1024 * 256;  // 256MB
-
-/// The number of RGBA pixels we're allowed to use for a texture.
-const MAX_RGBA_PIXELS_PER_TEXTURE: u32 = MAX_BYTES_PER_TEXTURE / 4;
-
-/// The desired initial size of each texture, in pixels.
-const INITIAL_TEXTURE_SIZE: u32 = 1024;
-
-/// The square root of the number of RGBA pixels we're allowed to use for a texture, rounded down.
-/// to the next power of two.
-const SQRT_MAX_RGBA_PIXELS_PER_TEXTURE: u32 = 8192;
-
-/// The minimum number of pixels on each side that we require for rects to be classified as
-/// "medium" within the free list.
-const MINIMUM_MEDIUM_RECT_SIZE: u32 = 16;
-
-/// The minimum number of pixels on each side that we require for rects to be classified as
-/// "large" within the free list.
-const MINIMUM_LARGE_RECT_SIZE: u32 = 32;
-
-/// The amount of time in milliseconds we give ourselves to coalesce rects before giving up.
-const COALESCING_TIMEOUT: u64 = 100;
-
-/// The number of items that we process in the coalescing work list before checking whether we hit
-/// the timeout.
-const COALESCING_TIMEOUT_CHECKING_INTERVAL: usize = 256;
-
-pub type TextureCacheItemId = FreeListHandle<TextureCacheItem>;
-
-enum CoalescingStatus {
-    Changed,
-    Unchanged,
-    Timeout,
-}
-
-/// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See
-/// sections 2.2 and 2.2.5 in "A Thousand Ways to Pack the Bin - A Practical Approach to Two-
-/// Dimensional Rectangle Bin Packing":
-///
-///    http://clb.demon.fi/files/RectangleBinPack.pdf
-///
-/// This approach was chosen because of its simplicity, good performance, and easy support for
-/// dynamic texture deallocation.
-pub struct TexturePage {
-    texture_id: CacheTextureId,
-    texture_size: DeviceUintSize,
-    free_list: FreeRectList,
-    coalesce_vec: Vec<DeviceUintRect>,
-    allocations: u32,
-    dirty: bool,
-}
-
-impl TexturePage {
-    fn new(texture_id: CacheTextureId, texture_size: DeviceUintSize) -> TexturePage {
-        let mut page = TexturePage {
-            texture_id,
-            texture_size,
-            free_list: FreeRectList::new(),
-            coalesce_vec: Vec::new(),
-            allocations: 0,
-            dirty: false,
-        };
-        page.clear();
-        page
-    }
-
-    fn find_index_of_best_rect_in_bin(&self, bin: FreeListBin, requested_dimensions: &DeviceUintSize)
-                                      -> Option<FreeListIndex> {
-        let mut smallest_index_and_area = None;
-        for (candidate_index, candidate_rect) in self.free_list.iter(bin).enumerate() {
-            if !requested_dimensions.fits_inside(&candidate_rect.size) {
-                continue
-            }
-
-            let candidate_area = candidate_rect.size.width * candidate_rect.size.height;
-            smallest_index_and_area = Some((candidate_index, candidate_area));
-            break
-        }
-
-        smallest_index_and_area.map(|(index, _)| FreeListIndex(bin, index))
-    }
-
-    /// Find a suitable rect in the free list. We choose the smallest such rect
-    /// in terms of area (Best-Area-Fit, BAF).
-    fn find_index_of_best_rect(&self, requested_dimensions: &DeviceUintSize)
-                               -> Option<FreeListIndex> {
-        let bin = FreeListBin::for_size(requested_dimensions);
-        for &target_bin in &[FreeListBin::Small, FreeListBin::Medium, FreeListBin::Large] {
-            if bin <= target_bin {
-                if let Some(index) = self.find_index_of_best_rect_in_bin(target_bin,
-                                                                         requested_dimensions) {
-                    return Some(index);
-                }
-            }
-        }
-        None
-    }
-
-    fn can_allocate(&self, requested_dimensions: &DeviceUintSize) -> bool {
-        self.find_index_of_best_rect(requested_dimensions).is_some()
-    }
-
-    pub fn allocate(&mut self,
-                    requested_dimensions: &DeviceUintSize) -> Option<DeviceUintPoint> {
-        if requested_dimensions.width == 0 || requested_dimensions.height == 0 {
-            return Some(DeviceUintPoint::new(0, 0))
-        }
-        let index = match self.find_index_of_best_rect(requested_dimensions) {
-            None => return None,
-            Some(index) => index,
-        };
+use api::{ImageDescriptor};
 
-        // Remove the rect from the free list and decide how to guillotine it. We choose the split
-        // that results in the single largest area (Min Area Split Rule, MINAS).
-        let chosen_rect = self.free_list.remove(index);
-        let candidate_free_rect_to_right =
-            DeviceUintRect::new(
-                DeviceUintPoint::new(chosen_rect.origin.x + requested_dimensions.width, chosen_rect.origin.y),
-                DeviceUintSize::new(chosen_rect.size.width - requested_dimensions.width, requested_dimensions.height));
-        let candidate_free_rect_to_bottom =
-            DeviceUintRect::new(
-                DeviceUintPoint::new(chosen_rect.origin.x, chosen_rect.origin.y + requested_dimensions.height),
-                DeviceUintSize::new(requested_dimensions.width, chosen_rect.size.height - requested_dimensions.height));
-        let candidate_free_rect_to_right_area = candidate_free_rect_to_right.size.width *
-            candidate_free_rect_to_right.size.height;
-        let candidate_free_rect_to_bottom_area = candidate_free_rect_to_bottom.size.width *
-            candidate_free_rect_to_bottom.size.height;
-
-        // Guillotine the rectangle.
-        let new_free_rect_to_right;
-        let new_free_rect_to_bottom;
-        if candidate_free_rect_to_right_area > candidate_free_rect_to_bottom_area {
-            new_free_rect_to_right = DeviceUintRect::new(
-                candidate_free_rect_to_right.origin,
-                DeviceUintSize::new(candidate_free_rect_to_right.size.width,
-                                    chosen_rect.size.height));
-            new_free_rect_to_bottom = candidate_free_rect_to_bottom
-        } else {
-            new_free_rect_to_right = candidate_free_rect_to_right;
-            new_free_rect_to_bottom =
-                DeviceUintRect::new(candidate_free_rect_to_bottom.origin,
-                          DeviceUintSize::new(chosen_rect.size.width,
-                                              candidate_free_rect_to_bottom.size.height))
-        }
-
-        // Add the guillotined rects back to the free list. If any changes were made, we're now
-        // dirty since coalescing might be able to defragment.
-        if !util::rect_is_empty(&new_free_rect_to_right) {
-            self.free_list.push(&new_free_rect_to_right);
-            self.dirty = true
-        }
-        if !util::rect_is_empty(&new_free_rect_to_bottom) {
-            self.free_list.push(&new_free_rect_to_bottom);
-            self.dirty = true
-        }
-
-        // Bump the allocation counter.
-        self.allocations += 1;
-
-        // Return the result.
-        Some(chosen_rect.origin)
-    }
-
-    fn coalesce_impl<F, U>(rects: &mut [DeviceUintRect], deadline: u64, fun_key: F, fun_union: U)
-                    -> CoalescingStatus where
-        F: Fn(&DeviceUintRect) -> (u32, u32),
-        U: Fn(&mut DeviceUintRect, &mut DeviceUintRect) -> usize,
-    {
-        let mut num_changed = 0;
-        rects.sort_by_key(&fun_key);
-
-        for work_index in 0..rects.len() {
-            if work_index % COALESCING_TIMEOUT_CHECKING_INTERVAL == 0 &&
-                    time::precise_time_ns() >= deadline {
-                return CoalescingStatus::Timeout
-            }
+// The fixed number of layers for the shared texture cache.
+// There is one array texture per image format, allocated lazily.
+const TEXTURE_ARRAY_LAYERS: i32 = 2;
 
-            let (left, candidates) = rects.split_at_mut(work_index + 1);
-            let mut item = left.last_mut().unwrap();
-            if util::rect_is_empty(item) {
-                continue
-            }
-
-            let key = fun_key(item);
-            for candidate in candidates.iter_mut()
-                                       .take_while(|r| key == fun_key(r)) {
-                num_changed += fun_union(item, candidate);
-            }
-        }
-
-        if num_changed > 0 {
-            CoalescingStatus::Changed
-        } else {
-            CoalescingStatus::Unchanged
-        }
-    }
-
-    /// Combine rects that have the same width and are adjacent.
-    fn coalesce_horisontal(rects: &mut [DeviceUintRect], deadline: u64) -> CoalescingStatus {
-        Self::coalesce_impl(rects, deadline,
-                            |item| (item.size.width, item.origin.x),
-                            |item, candidate| {
-            if item.origin.y == candidate.max_y() || item.max_y() == candidate.origin.y {
-                *item = item.union(candidate);
-                candidate.size.width = 0;
-                1
-            } else { 0 }
-        })
-    }
-
-    /// Combine rects that have the same height and are adjacent.
-    fn coalesce_vertical(rects: &mut [DeviceUintRect], deadline: u64) -> CoalescingStatus {
-        Self::coalesce_impl(rects, deadline,
-                            |item| (item.size.height, item.origin.y),
-                            |item, candidate| {
-            if item.origin.x == candidate.max_x() || item.max_x() == candidate.origin.x {
-                *item = item.union(candidate);
-                candidate.size.height = 0;
-                1
-            } else { 0 }
-        })
-    }
-
-    pub fn coalesce(&mut self) -> bool {
-        if !self.dirty {
-            return false
-        }
-
-        // Iterate to a fixed point or until a timeout is reached.
-        let deadline = time::precise_time_ns() + COALESCING_TIMEOUT;
-        self.free_list.copy_to_vec(&mut self.coalesce_vec);
-        let mut changed = false;
-
-        //Note: we might want to consider try to use the last sorted order first
-        // but the elements get shuffled around a bit anyway during the bin placement
-
-        match Self::coalesce_horisontal(&mut self.coalesce_vec, deadline) {
-            CoalescingStatus::Changed => changed = true,
-            CoalescingStatus::Unchanged => (),
-            CoalescingStatus::Timeout => {
-                self.free_list.init_from_slice(&self.coalesce_vec);
-                return true
-            }
-        }
+// The dimensions of each layer in the texture cache.
+const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
 
-        match Self::coalesce_vertical(&mut self.coalesce_vec, deadline) {
-            CoalescingStatus::Changed => changed = true,
-            CoalescingStatus::Unchanged => (),
-            CoalescingStatus::Timeout => {
-                self.free_list.init_from_slice(&self.coalesce_vec);
-                return true
-            }
-        }
-
-        if changed {
-            self.free_list.init_from_slice(&self.coalesce_vec);
-        }
-        self.dirty = changed;
-        changed
-    }
-
-    fn clear(&mut self) {
-        self.free_list = FreeRectList::new();
-        self.free_list.push(&DeviceUintRect::new(
-            DeviceUintPoint::zero(),
-            self.texture_size));
-        self.allocations = 0;
-        self.dirty = false;
-    }
-
-    fn free(&mut self, rect: &DeviceUintRect) {
-        if util::rect_is_empty(rect) {
-            return
-        }
-        debug_assert!(self.allocations > 0);
-        self.allocations -= 1;
-        if self.allocations == 0 {
-            self.clear();
-            return
-        }
-
-        self.free_list.push(rect);
-        self.dirty = true
-    }
-
-    fn grow(&mut self, new_texture_size: DeviceUintSize) {
-        assert!(new_texture_size.width >= self.texture_size.width);
-        assert!(new_texture_size.height >= self.texture_size.height);
-
-        let new_rects = [
-            DeviceUintRect::new(DeviceUintPoint::new(self.texture_size.width, 0),
-                                DeviceUintSize::new(new_texture_size.width - self.texture_size.width,
-                                                    new_texture_size.height)),
-
-            DeviceUintRect::new(DeviceUintPoint::new(0, self.texture_size.height),
-                                DeviceUintSize::new(self.texture_size.width,
-                                                    new_texture_size.height - self.texture_size.height)),
-        ];
-
-        for rect in &new_rects {
-            if rect.size.width > 0 && rect.size.height > 0 {
-                self.free_list.push(rect);
-            }
-        }
-
-        self.texture_size = new_texture_size
-    }
-
-    fn can_grow(&self, max_size: u32) -> bool {
-        self.texture_size.width < max_size || self.texture_size.height < max_size
-    }
-}
-
-// testing functionality
-impl TexturePage {
-    #[doc(hidden)]
-    pub fn new_dummy(size: DeviceUintSize) -> TexturePage {
-        Self::new(CacheTextureId(0), size)
-    }
-
-    #[doc(hidden)]
-    pub fn fill_from(&mut self, other: &TexturePage) {
-        self.dirty = true;
-        self.free_list.small.clear();
-        self.free_list.small.extend_from_slice(&other.free_list.small);
-        self.free_list.medium.clear();
-        self.free_list.medium.extend_from_slice(&other.free_list.medium);
-        self.free_list.large.clear();
-        self.free_list.large.extend_from_slice(&other.free_list.large);
-    }
-}
-
-/// A binning free list. Binning is important to avoid sifting through lots of small strips when
-/// allocating many texture items.
-struct FreeRectList {
-    small: Vec<DeviceUintRect>,
-    medium: Vec<DeviceUintRect>,
-    large: Vec<DeviceUintRect>,
-}
-
-impl FreeRectList {
-    fn new() -> FreeRectList {
-        FreeRectList {
-            small: vec![],
-            medium: vec![],
-            large: vec![],
-        }
-    }
-
-    fn init_from_slice(&mut self, rects: &[DeviceUintRect]) {
-        self.small.clear();
-        self.medium.clear();
-        self.large.clear();
-        for rect in rects {
-            if !util::rect_is_empty(rect) {
-                self.push(rect)
-            }
-        }
-    }
-
-    fn push(&mut self, rect: &DeviceUintRect) {
-        match FreeListBin::for_size(&rect.size) {
-            FreeListBin::Small => self.small.push(*rect),
-            FreeListBin::Medium => self.medium.push(*rect),
-            FreeListBin::Large => self.large.push(*rect),
-        }
-    }
+// The size of each region (page) in a texture layer.
+const TEXTURE_REGION_DIMENSIONS: u32 = 512;
 
-    fn remove(&mut self, index: FreeListIndex) -> DeviceUintRect {
-        match index.0 {
-            FreeListBin::Small => self.small.swap_remove(index.1),
-            FreeListBin::Medium => self.medium.swap_remove(index.1),
-            FreeListBin::Large => self.large.swap_remove(index.1),
-        }
-    }
-
-    fn iter(&self, bin: FreeListBin) -> Iter<DeviceUintRect> {
-        match bin {
-            FreeListBin::Small => self.small.iter(),
-            FreeListBin::Medium => self.medium.iter(),
-            FreeListBin::Large => self.large.iter(),
-        }
-    }
-
-    fn copy_to_vec(&self, rects: &mut Vec<DeviceUintRect>) {
-        rects.clear();
-        rects.extend_from_slice(&self.small);
-        rects.extend_from_slice(&self.medium);
-        rects.extend_from_slice(&self.large);
-    }
-}
-
-#[derive(Debug, Clone, Copy)]
-struct FreeListIndex(FreeListBin, usize);
-
-#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
-enum FreeListBin {
-    Small,
-    Medium,
-    Large,
-}
-
-impl FreeListBin {
-    fn for_size(size: &DeviceUintSize) -> FreeListBin {
-        if size.width >= MINIMUM_LARGE_RECT_SIZE && size.height >= MINIMUM_LARGE_RECT_SIZE {
-            FreeListBin::Large
-        } else if size.width >= MINIMUM_MEDIUM_RECT_SIZE &&
-                size.height >= MINIMUM_MEDIUM_RECT_SIZE {
-            FreeListBin::Medium
-        } else {
-            debug_assert!(size.width > 0 && size.height > 0);
-            FreeListBin::Small
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct TextureCacheItem {
-    // Identifies the texture and array slice
-    pub texture_id: CacheTextureId,
-
-    // The texture coordinates for this item
-    pub uv_rect: UvRect,
-
-    // The size of the allocated rectangle.
-    pub allocated_rect: DeviceUintRect,
-
-    // Handle to the location of the UV rect for this item in GPU cache.
-    pub uv_rect_handle: GpuCacheHandle,
-
-    pub format: ImageFormat,
-
-    // Some arbitrary data associated with this item.
-    // In the case of glyphs, it is the top / left offset
-    // from the rasterized glyph.
-    pub user_data: [f32; 2],
-}
-
-impl TextureCacheItem {
-    fn new(texture_id: CacheTextureId,
-           rect: DeviceUintRect,
-           format: ImageFormat,
-           user_data: [f32; 2])
-           -> TextureCacheItem {
-        TextureCacheItem {
-            texture_id,
-            uv_rect: UvRect {
-                uv0: DevicePoint::new(rect.origin.x as f32,
-                                      rect.origin.y as f32),
-                uv1: DevicePoint::new((rect.origin.x + rect.size.width) as f32,
-                                      (rect.origin.y + rect.size.height) as f32),
-            },
-            allocated_rect: rect,
-            uv_rect_handle: GpuCacheHandle::new(),
-            format,
-            user_data,
-        }
-    }
-}
-
-struct TextureCacheArena {
-    pages_a8: Vec<TexturePage>,
-    pages_rgb8: Vec<TexturePage>,
-    pages_rgba8: Vec<TexturePage>,
-    pages_rg8: Vec<TexturePage>,
-}
-
-impl TextureCacheArena {
-    fn new() -> TextureCacheArena {
-        TextureCacheArena {
-            pages_a8: Vec::new(),
-            pages_rgb8: Vec::new(),
-            pages_rgba8: Vec::new(),
-            pages_rg8: Vec::new(),
-        }
-    }
-
-    fn texture_page_for_id(&mut self, id: CacheTextureId) -> Option<&mut TexturePage> {
-        for page in self.pages_a8.iter_mut().chain(self.pages_rgb8.iter_mut())
-                                            .chain(self.pages_rgba8.iter_mut())
-                                            .chain(self.pages_rg8.iter_mut()) {
-            if page.texture_id == id {
-                return Some(page)
-            }
-        }
-        None
-    }
-}
-
-pub struct CacheTextureIdList {
+// Maintains a simple freelist of texture IDs that are mapped
+// to real API-specific texture IDs in the renderer.
+struct CacheTextureIdList {
+    free_list: Vec<CacheTextureId>,
     next_id: usize,
-    free_list: Vec<usize>,
 }
 
 impl CacheTextureIdList {
     fn new() -> CacheTextureIdList {
         CacheTextureIdList {
             next_id: 0,
             free_list: Vec::new(),
         }
     }
 
     fn allocate(&mut self) -> CacheTextureId {
         // If nothing on the free list of texture IDs,
         // allocate a new one.
-        if self.free_list.is_empty() {
-            self.free_list.push(self.next_id);
-            self.next_id += 1;
+        match self.free_list.pop() {
+            Some(id) => {
+                id
+            }
+            None => {
+                let id = CacheTextureId(self.next_id);
+                self.next_id += 1;
+                id
+            }
         }
-
-        let id = self.free_list.pop().unwrap();
-        CacheTextureId(id)
     }
 
     fn free(&mut self, id: CacheTextureId) {
-        self.free_list.push(id.0);
+        self.free_list.push(id);
+    }
+}
+
+// Items in the texture cache can either be standalone textures,
+// or a sub-rect inside the shared cache.
+#[derive(Debug)]
+enum EntryKind {
+    Standalone,
+    Cache {
+        // Origin within the texture layer where this item exists.
+        origin: DeviceUintPoint,
+        // The layer index of the texture array.
+        layer_index: u16,
+        // The region that this entry belongs to in the layer.
+        region_index: u16,
+    }
+}
+
+// Stores information related to a single entry in the texture
+// cache. This is stored for each item whether it's in the shared
+// cache or a standalone texture.
+#[derive(Debug)]
+struct CacheEntry {
+    // Size the requested item, in device pixels.
+    size: DeviceUintSize,
+    // Details specific to standalone or shared items.
+    kind: EntryKind,
+    // Arbitrary user data associated with this item.
+    user_data: [f32; 2],
+    // The last frame this item was requested for rendering.
+    last_access: FrameId,
+    // Handle to the resource rect in the GPU cache.
+    uv_rect_handle: GpuCacheHandle,
+    // Image format of the item.
+    format: ImageFormat,
+    // The actual device texture ID this is part of.
+    texture_id: CacheTextureId,
+}
+
+impl CacheEntry {
+    // Create a new entry for a standalone texture.
+    fn new_standalone(texture_id: CacheTextureId,
+                      size: DeviceUintSize,
+                      format: ImageFormat,
+                      user_data: [f32; 2],
+                      last_access: FrameId) -> CacheEntry {
+        CacheEntry {
+            size,
+            user_data,
+            last_access,
+            kind: EntryKind::Standalone,
+            texture_id,
+            format,
+            uv_rect_handle: GpuCacheHandle::new(),
+        }
+    }
+
+    // Update the GPU cache for this texture cache entry.
+    // This ensures that the UV rect, and texture layer index
+    // are up to date in the GPU cache for vertex shaders
+    // to fetch from.
+    fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
+        if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) {
+            let (origin, layer_index) = match self.kind {
+                EntryKind::Standalone { .. } => {
+                    (DeviceUintPoint::zero(), 0.0)
+                }
+                EntryKind::Cache { origin, layer_index, .. } => {
+                    (origin, layer_index as f32)
+                }
+            };
+            request.push([origin.x as f32,
+                          origin.y as f32,
+                          (origin.x + self.size.width) as f32,
+                          (origin.y + self.size.height) as f32]);
+            request.push([layer_index,
+                          self.user_data[0],
+                          self.user_data[1],
+                          0.0]);
+        }
+    }
+}
+
+type WeakCacheEntryHandle = WeakFreeListHandle<CacheEntry>;
+
+// A texture cache handle is a weak reference to a cache entry.
+// If the handle has not been inserted into the cache yet, the
+// value will be None. Even when the value is Some(), the location
+// may not actually be valid if it has been evicted by the cache.
+// In this case, the cache handle needs to re-upload this item
+// to the texture cache (see request() below).
+#[derive(Debug)]
+pub struct TextureCacheHandle {
+    entry: Option<WeakCacheEntryHandle>,
+}
+
+impl TextureCacheHandle {
+    pub fn new() -> TextureCacheHandle {
+        TextureCacheHandle {
+            entry: None,
+        }
     }
 }
 
 pub struct TextureCache {
-    cache_id_list: CacheTextureIdList,
-    free_texture_levels: FastHashMap<ImageFormat, Vec<FreeTextureLevel>>,
-    items: FreeList<TextureCacheItem>,
-    arena: TextureCacheArena,
-    pending_updates: TextureUpdateList,
+    // A lazily allocated, fixed size, texture array for
+    // each format the texture cache supports.
+    // TODO(gw): Do we actually need RG8 and RGB8 or
+    // are they only used by external textures?
+    array_a8: TextureArray,
+    array_rgba8: TextureArray,
+    array_rg8: TextureArray,
+    array_rgb8: TextureArray,
+
+    // Maximum texture size supported by hardware.
     max_texture_size: u32,
-}
+
+    // A list of texture IDs that represent native
+    // texture handles. This indirection allows the texture
+    // cache to create / destroy / reuse texture handles
+    // without knowing anything about the device code.
+    cache_textures: CacheTextureIdList,
 
-#[derive(PartialEq, Eq, Debug)]
-pub enum AllocationKind {
-    TexturePage,
-    Standalone,
-}
+    // A list of updates that need to be applied to the
+    // texture cache in the rendering thread this frame.
+    pending_updates: TextureUpdateList,
+
+    // The current frame ID. Used for cache eviction policies.
+    frame_id: FrameId,
 
-#[derive(Debug)]
-pub struct AllocationResult {
-    new_image_id: Option<TextureCacheItemId>,
-    kind: AllocationKind,
-    item: TextureCacheItem,
+    // Maintains the list of all current items in
+    // the texture cache.
+    entries: FreeList<CacheEntry>,
+
+    // A list of the strong handles of items that were
+    // allocated in the standalone texture pool. Used
+    // for evicting old standalone textures.
+    standalone_entry_handles: Vec<FreeListHandle<CacheEntry>>,
+
+    // A list of the strong handles of items that were
+    // allocated in the shared texture cache. Used
+    // for evicting old cache items.
+    shared_entry_handles: Vec<FreeListHandle<CacheEntry>>,
 }
 
 impl TextureCache {
-    pub fn new(mut max_texture_size: u32) -> TextureCache {
-        if max_texture_size * max_texture_size > MAX_RGBA_PIXELS_PER_TEXTURE {
-            max_texture_size = SQRT_MAX_RGBA_PIXELS_PER_TEXTURE;
-        }
-
+    pub fn new(max_texture_size: u32) -> TextureCache {
         TextureCache {
-            cache_id_list: CacheTextureIdList::new(),
-            free_texture_levels: FastHashMap::default(),
-            items: FreeList::new(),
+            max_texture_size,
+            array_a8: TextureArray::new(ImageFormat::A8),
+            array_rgba8: TextureArray::new(ImageFormat::BGRA8),
+            array_rg8: TextureArray::new(ImageFormat::RG8),
+            array_rgb8: TextureArray::new(ImageFormat::RGB8),
+            cache_textures: CacheTextureIdList::new(),
             pending_updates: TextureUpdateList::new(),
-            arena: TextureCacheArena::new(),
-            max_texture_size,
+            frame_id: FrameId(0),
+            entries: FreeList::new(),
+            standalone_entry_handles: Vec::new(),
+            shared_entry_handles: Vec::new(),
+        }
+    }
+
+    pub fn begin_frame(&mut self, frame_id: FrameId) {
+        self.frame_id = frame_id;
+    }
+
+    pub fn end_frame(&mut self) {
+        self.expire_old_standalone_entries();
+    }
+
+    // Request an item in the texture cache. All images that will
+    // be used on a frame *must* have request() called on their
+    // handle, to update the last used timestamp and ensure
+    // that resources are not flushed from the cache too early.
+    //
+    // Returns true if the image needs to be uploaded to the
+    // texture cache (either never uploaded, or has been
+    // evicted on a previous frame).
+    pub fn request(&mut self,
+                   handle: &mut TextureCacheHandle,
+                   gpu_cache: &mut GpuCache) -> bool {
+        match handle.entry {
+            Some(ref handle) => {
+                match self.entries.get_opt_mut(handle) {
+                    // If an image is requested that is already in the cache,
+                    // refresh the GPU cache data associated with this item.
+                    Some(entry) => {
+                        entry.last_access = self.frame_id;
+                        entry.update_gpu_cache(gpu_cache);
+                        false
+                    }
+                    None => {
+                        true
+                    }
+                }
+            }
+            None => {
+                true
+            }
         }
     }
 
     pub fn max_texture_size(&self) -> u32 {
         self.max_texture_size
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         mem::replace(&mut self.pending_updates, TextureUpdateList::new())
     }
 
-    pub fn allocate(
+    // Update the data stored by a given texture cache handle.
+    pub fn update(
         &mut self,
-        requested_width: u32,
-        requested_height: u32,
-        format: ImageFormat,
+        handle: &mut TextureCacheHandle,
+        descriptor: ImageDescriptor,
         filter: TextureFilter,
+        data: ImageData,
         user_data: [f32; 2],
-        profile: &mut TextureCacheProfileCounters
-    ) -> AllocationResult {
-        self.allocate_impl(
-            requested_width,
-            requested_height,
-            format,
-            filter,
-            user_data,
-            profile,
-            None,
-        )
+        mut dirty_rect: Option<DeviceUintRect>,
+        gpu_cache: &mut GpuCache) {
+
+        // Determine if we need to allocate texture cache memory
+        // for this item. We need to reallocate if any of the following
+        // is true:
+        // - Never been in the cache
+        // - Has been in the cache but was evicted.
+        // - Exists in the cache but dimensions / format have changed.
+        let realloc = match handle.entry {
+            Some(ref handle) => {
+                match self.entries.get_opt(handle) {
+                    Some(entry) => {
+                        entry.size.width != descriptor.width ||
+                        entry.size.height != descriptor.height ||
+                        entry.format != descriptor.format
+                    }
+                    None => {
+                        // Was previously allocated but has been evicted.
+                        true
+                    }
+                }
+            }
+            None => {
+                // This handle has not been allocated yet.
+                true
+            }
+        };
+
+        if realloc {
+            self.allocate(handle,
+                          descriptor,
+                          filter,
+                          user_data);
+
+            // If we reallocated, we need to upload the whole item again.
+            dirty_rect = None;
+        }
+
+        let entry = self.entries
+                        .get_opt_mut(handle.entry.as_ref().unwrap())
+                        .expect("BUG: handle must be valid now");
+
+        // Invalidate the contents of the resource rect in the GPU cache.
+        // This ensures that the update_gpu_cache below will add
+        // the new information to the GPU cache.
+        gpu_cache.invalidate(&entry.uv_rect_handle);
+
+        // Upload the resource rect and texture array layer.
+        entry.update_gpu_cache(gpu_cache);
+
+        // Create an update command, which the render thread processes
+        // to upload the new image data into the correct location
+        // in GPU memory.
+        let (layer_index, origin) = match entry.kind {
+            EntryKind::Standalone { .. } => {
+                (0, DeviceUintPoint::zero())
+            }
+            EntryKind::Cache { layer_index, origin, .. } => {
+                (layer_index, origin)
+            }
+        };
+
+        let op = TextureUpdate::new_update(data,
+                                           &descriptor,
+                                           origin,
+                                           entry.size,
+                                           entry.texture_id,
+                                           layer_index as i32,
+                                           dirty_rect);
+        self.pending_updates.push(op);
+    }
+
+    // Get a specific region by index from a shared texture array.
+    fn get_region_mut(&mut self, format: ImageFormat, region_index: u16) -> &mut TextureRegion {
+        let texture_array = match format {
+            ImageFormat::A8 => &mut self.array_a8,
+            ImageFormat::BGRA8 => &mut self.array_rgba8,
+            ImageFormat::RGB8 => &mut self.array_rgb8,
+            ImageFormat::RG8 => &mut self.array_rg8,
+            ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
+        };
+
+        &mut texture_array.regions[region_index as usize]
+    }
+
+    // Retrieve the details of an item in the cache. This is used
+    // during batch creation to provide the resource rect address
+    // to the shaders and texture ID to the batching logic.
+    // This function will asssert in debug modes if the caller
+    // tries to get a handle that was not requested this frame.
+    pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem {
+        match handle.entry {
+            Some(ref handle) => {
+                let entry = self.entries
+                                .get_opt(handle)
+                                .expect("BUG: was dropped from cache or not updated!");
+                debug_assert_eq!(entry.last_access, self.frame_id);
+                CacheItem {
+                    uv_rect_handle: entry.uv_rect_handle,
+                    texture_id: SourceTexture::TextureCache(entry.texture_id),
+                }
+            }
+            None => {
+                panic!("BUG: handle not requested earlier in frame")
+            }
+        }
     }
 
-    // If existing_item_id is None, create a new id, otherwise reuse it.
-    fn allocate_impl(
-        &mut self,
-        requested_width: u32,
-        requested_height: u32,
-        format: ImageFormat,
-        filter: TextureFilter,
-        user_data: [f32; 2],
-        profile: &mut TextureCacheProfileCounters,
-        existing_item_id: Option<&TextureCacheItemId>
-    ) -> AllocationResult {
-        let requested_size = DeviceUintSize::new(requested_width, requested_height);
+    // Expire old standalone textures.
+    fn expire_old_standalone_entries(&mut self) {
+        let mut eviction_candidates = Vec::new();
+        let mut retained_entries = Vec::new();
+
+        // Build a list of eviction candidates (which are
+        // anything not used this frame).
+        for handle in self.standalone_entry_handles.drain(..) {
+            let entry = self.entries.get(&handle);
+            if entry.last_access == self.frame_id {
+                retained_entries.push(handle);
+            } else {
+                eviction_candidates.push(handle);
+            }
+        }
+
+        // Sort by access time so we remove the oldest ones first.
+        eviction_candidates.sort_by_key(|handle| {
+            let entry = self.entries.get(handle);
+            entry.last_access
+        });
+
+        // We only allow an arbitrary number of unused
+        // standalone textures to remain in GPU memory.
+        // TODO(gw): We should make this a better heuristic,
+        //           for example based on total memory size.
+        if eviction_candidates.len() > 32 {
+            let entries_to_keep = eviction_candidates.split_off(32);
+            retained_entries.extend(entries_to_keep);
+        }
+
+        // Free the selected items
+        for handle in eviction_candidates {
+            let entry = self.entries.free(handle);
+            self.free(entry);
+        }
+
+        // Keep a record of the remaining handles for next frame.
+        self.standalone_entry_handles = retained_entries;
+    }
+
+    // Expire old shared items. Pass in the allocation size
+    // that is being requested, so we know when we've evicted
+    // enough items to guarantee we can fit this allocation in
+    // the cache.
+    fn expire_old_shared_entries(&mut self, required_alloc: &ImageDescriptor) {
+        let mut eviction_candidates = Vec::new();
+        let mut retained_entries = Vec::new();
+
+        // Build a list of eviction candidates (which are
+        // anything not used this frame).
+        for handle in self.shared_entry_handles.drain(..) {
+            let entry = self.entries.get(&handle);
+            if entry.last_access == self.frame_id {
+                retained_entries.push(handle);
+            } else {
+                eviction_candidates.push(handle);
+            }
+        }
+
+        // Sort by access time so we remove the oldest ones first.
+        eviction_candidates.sort_by_key(|handle| {
+            let entry = self.entries.get(handle);
+            entry.last_access
+        });
+
+        // Doing an eviction is quite expensive, so we don't want to
+        // do it all the time. To avoid this, try and evict a
+        // significant number of items each cycle. However, we don't
+        // want to evict everything we can, since that will result in
+        // more items being uploaded than necessary.
+        // Instead, we say we will keep evicting until both of these
+        // consitions are met:
+        // - We have evicted some arbitrary number of items (512 currently).
+        //   AND
+        // - We have freed an item that will definitely allow us to
+        //   fit the currently requested allocation.
+        let needed_slab_size = SlabSize::new(required_alloc.width,
+                                             required_alloc.height).get_size();
+        let mut found_matching_slab = false;
+        let mut freed_complete_page = false;
+        let mut evicted_items = 0;
+
+        for handle in eviction_candidates {
+            if evicted_items > 512 &&
+               (found_matching_slab || freed_complete_page) {
+                retained_entries.push(handle);
+            } else {
+                let entry = self.entries.free(handle);
+                if let Some(region) = self.free(entry) {
+                    found_matching_slab |= region.slab_size == needed_slab_size;
+                    freed_complete_page |= region.is_empty();
+                }
+                evicted_items += 1;
+            }
+        }
+
+        // Keep a record of the remaining handles for next eviction cycle.
+        self.shared_entry_handles = retained_entries;
+    }
+
+    // Free a cache entry from the standalone list or shared cache.
+    fn free(&mut self, entry: CacheEntry) -> Option<&TextureRegion> {
+        match entry.kind {
+            EntryKind::Standalone { .. }  => {
+                // This is a standalone texture allocation. Just push it back onto the free
+                // list.
+                self.pending_updates.push(TextureUpdate {
+                    id: entry.texture_id,
+                    op: TextureUpdateOp::Free,
+                });
+                self.cache_textures.free(entry.texture_id);
+                None
+            }
+            EntryKind::Cache { origin, region_index, .. } => {
+                // Free the block in the given region.
+                let region = self.get_region_mut(entry.format, region_index);
+                region.free(origin);
+                Some(region)
+            }
+        }
+    }
+
+    // Attempt to allocate a block from the shared cache.
+    fn allocate_from_shared_cache(&mut self,
+                                  descriptor: &ImageDescriptor,
+                                  user_data: [f32; 2]) -> Option<CacheEntry> {
+        // Work out which cache it goes in, based on format.
+        let texture_array = match descriptor.format {
+            ImageFormat::A8 => &mut self.array_a8,
+            ImageFormat::BGRA8 => &mut self.array_rgba8,
+            ImageFormat::RGB8 => &mut self.array_rgb8,
+            ImageFormat::RG8 => &mut self.array_rg8,
+            ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
+        };
+
+        // Lazy initialize this texture array if required.
+        if texture_array.texture_id.is_none() {
+            let texture_id = self.cache_textures.allocate();
+
+            let update_op = TextureUpdate {
+                id: texture_id,
+                op: TextureUpdateOp::Create {
+                    width: TEXTURE_LAYER_DIMENSIONS,
+                    height: TEXTURE_LAYER_DIMENSIONS,
+                    format: descriptor.format,
+                    filter: TextureFilter::Linear,
+                    layer_count: TEXTURE_ARRAY_LAYERS,
+                    mode: RenderTargetMode::RenderTarget,           // todo: !!!! remove me!?
+                }
+            };
+            self.pending_updates.push(update_op);
+
+            texture_array.texture_id = Some(texture_id);
+        }
+
+        // Do the allocation. This can fail and return None
+        // if there are no free slots or regions available.
+        texture_array.alloc(descriptor.width,
+                            descriptor.height,
+                            user_data,
+                            self.frame_id)
+    }
+
+    // Allocate storage for a given image. This attempts to allocate
+    // from the shared cache, but falls back to standalone texture
+    // if the image is too large, or the cache is full.
+    fn allocate(&mut self,
+                handle: &mut TextureCacheHandle,
+                descriptor: ImageDescriptor,
+                filter: TextureFilter,
+                user_data: [f32; 2]) {
+        assert!(descriptor.width > 0 && descriptor.height > 0);
+
+        // Work out if this image qualifies to go in the shared (batching) cache.
+        let mut allowed_in_shared_cache = true;
+        let mut allocated_in_shared_cache = true;
+        let mut new_cache_entry = None;
+        let size = DeviceUintSize::new(descriptor.width, descriptor.height);
+        let frame_id = self.frame_id;
 
         // TODO(gw): For now, anything that requests nearest filtering
         //           just fails to allocate in a texture page, and gets a standalone
         //           texture. This isn't ideal, as it causes lots of batch breaks,
         //           but is probably rare enough that it can be fixed up later (it's also
         //           fairly trivial to implement, just tedious).
         if filter == TextureFilter::Nearest {
-            // Fall back to standalone texture allocation.
-            let texture_id = self.cache_id_list.allocate();
+            allowed_in_shared_cache = false;
+        }
 
-            let update_op = TextureUpdate {
-                id: texture_id,
-                op: texture_create_op(DeviceUintSize::new(requested_width,
-                                                          requested_height),
-                                      format,
-                                      RenderTargetMode::None,
-                                      filter),
-            };
-            self.pending_updates.push(update_op);
+        // Anything larger than 512 goes in a standalone texture.
+        // TODO(gw): If we find pages that suffer from batch breaks in this
+        //           case, add support for storing these in a standalone
+        //           texture array.
+        if descriptor.width > 512 || descriptor.height > 512 {
+            allowed_in_shared_cache = false;
+        }
 
-            let cache_item = TextureCacheItem::new(
-                texture_id,
-                DeviceUintRect::new(DeviceUintPoint::zero(), requested_size),
-                format,
-                user_data
-            );
+        // If it's allowed in the cache, see if there is a spot for it.
+        if allowed_in_shared_cache {
+            new_cache_entry = self.allocate_from_shared_cache(&descriptor, user_data);
 
-            let new_image_id = match existing_item_id {
-                Some(id) => {
-                    *self.items.get_mut(id) = cache_item.clone();
-                    None
-                }
-                None => {
-                    let id = self.items.insert(cache_item.clone());
-                    Some(id)
-                }
-            };
+            // If we failed to allocate in the shared cache, run an
+            // eviction cycle, and then try to allocate again.
+            if new_cache_entry.is_none() {
+                self.expire_old_shared_entries(&descriptor);
 
-            return AllocationResult {
-                item: cache_item,
-                kind: AllocationKind::Standalone,
-                new_image_id,
+                new_cache_entry = self.allocate_from_shared_cache(&descriptor, user_data);
             }
         }
 
-        let mode = RenderTargetMode::SimpleRenderTarget;
-        let (page_list, page_profile) = match format {
-            ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
-            ImageFormat::BGRA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
-            ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
-            ImageFormat::RG8 => (&mut self.arena.pages_rg8, &mut profile.pages_rg8),
-            ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
+        // If not allowed in the cache, or if the shared cache is full, then it
+        // will just have to be in a unique texture. This hurts batching but should
+        // only occur on a small number of images (or pathological test cases!).
+        if new_cache_entry.is_none() {
+            let texture_id = self.cache_textures.allocate();
+
+            // Create an update operation to allocate device storage
+            // of the right size / format.
+            let update_op = TextureUpdate {
+                id: texture_id,
+                op: TextureUpdateOp::Create {
+                    width: descriptor.width,
+                    height: descriptor.height,
+                    format: descriptor.format,
+                    filter,
+                    mode: RenderTargetMode::RenderTarget,
+                    layer_count: 1,
+                }
+            };
+            self.pending_updates.push(update_op);
+
+            new_cache_entry = Some(CacheEntry::new_standalone(texture_id,
+                                                              size,
+                                                              descriptor.format,
+                                                              user_data,
+                                                              frame_id));
+
+            allocated_in_shared_cache = false;
+        }
+
+        let new_cache_entry = new_cache_entry.expect("BUG: must have allocated by now");
+
+        // We need to update the texture cache handle now, so that it
+        // points to the correct location.
+        let new_entry_handle = match handle.entry {
+            Some(ref existing_entry) => {
+                // If the handle already exists, there's two possibilities:
+                // 1) It points to a valid entry in the freelist.
+                // 2) It points to a stale entry in the freelist (i.e. has been evicted).
+                //
+                // For (1) we want to replace the cache entry with our
+                // newly updated location. We also need to ensure that
+                // the storage (region or standalone) associated with the
+                // previous entry here gets freed.
+                //
+                // For (2) we need to add the data to a new location
+                // in the freelist.
+                //
+                // This is managed with a database style upsert operation.
+                match self.entries.upsert(existing_entry, new_cache_entry) {
+                    UpsertResult::Updated(old_entry) => {
+                        self.free(old_entry);
+                        None
+                    }
+                    UpsertResult::Inserted(new_handle) => {
+                        Some(new_handle)
+                    }
+                }
+            }
+            None => {
+                // This handle has never been allocated, so just
+                // insert a new cache entry.
+                Some(self.entries.insert(new_cache_entry))
+            }
         };
 
+        // If the cache entry is new, update it in the cache handle.
+        if let Some(new_entry_handle) = new_entry_handle {
+            handle.entry = Some(self.entries.create_weak_handle(&new_entry_handle));
+            // Store the strong handle in the list that we scan for
+            // cache evictions.
+            if allocated_in_shared_cache {
+                self.shared_entry_handles.push(new_entry_handle);
+            } else {
+                self.standalone_entry_handles.push(new_entry_handle);
+            }
+        }
+    }
+}
 
-        // TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
-        assert!(
-            requested_size.width <= self.max_texture_size,
-            "Width {:?} > max texture size (format: {:?}).",
-            requested_size.width, format
-        );
-        assert!(
-            requested_size.height <= self.max_texture_size,
-            "Height {:?} > max texture size (format: {:?}).",
-            requested_size.height, format
-        );
+// A list of the block sizes that a region can be initialized with.
+#[derive(Copy, Clone, PartialEq)]
+enum SlabSize {
+    Size16x16,
+    Size32x32,
+    Size64x64,
+    Size128x128,
+    Size256x256,
+    Size512x512,
+}
+
+impl SlabSize {
+    fn new(width: u32, height: u32) -> SlabSize {
+        // TODO(gw): Consider supporting non-square
+        //           allocator sizes here.
+        let max_dim = cmp::max(width, height);
+
+        match max_dim {
+            0 => unreachable!(),
+            1 ... 16 => SlabSize::Size16x16,
+            17 ... 32 => SlabSize::Size32x32,
+            33 ... 64 => SlabSize::Size64x64,
+            65 ... 128 => SlabSize::Size128x128,
+            129 ... 256 => SlabSize::Size256x256,
+            257 ... 512 => SlabSize::Size512x512,
+            _ => panic!("Invalid dimensions for cache!"),
+        }
+    }
+
+    fn get_size(&self) -> u32 {
+        match *self {
+            SlabSize::Size16x16 => 16,
+            SlabSize::Size32x32 => 32,
+            SlabSize::Size64x64 => 64,
+            SlabSize::Size128x128 => 128,
+            SlabSize::Size256x256 => 256,
+            SlabSize::Size512x512 => 512,
+        }
+    }
+}
+
+// The x/y location within a texture region of an allocation.
+struct TextureLocation {
+    x: u8,
+    y: u8,
+}
 
-        let mut page_id = None; //using ID here to please the borrow checker
-        for (i, page) in page_list.iter_mut().enumerate() {
-            if page.can_allocate(&requested_size) {
-                page_id = Some(i);
-                break;
+impl TextureLocation {
+    fn new(x: u32, y: u32) -> TextureLocation {
+        debug_assert!(x < 256 && y < 256);
+        TextureLocation {
+            x: x as u8,
+            y: y as u8,
+        }
+    }
+}
+
+// A region is a sub-rect of a texture array layer.
+// All allocations within a region are of the same size.
+struct TextureRegion {
+    layer_index: i32,
+    region_size: u32,
+    slab_size: u32,
+    free_slots: Vec<TextureLocation>,
+    slots_per_axis: u32,
+    total_slot_count: usize,
+    origin: DeviceUintPoint,
+}
+
+impl TextureRegion {
+    fn new(region_size: u32,
+           layer_index: i32,
+           origin: DeviceUintPoint) -> TextureRegion {
+        TextureRegion {
+            layer_index,
+            region_size,
+            slab_size: 0,
+            free_slots: Vec::new(),
+            slots_per_axis: 0,
+            total_slot_count: 0,
+            origin,
+        }
+    }
+
+    // Initialize a region to be an allocator for a specific slab size.
+    fn init(&mut self, slab_size: SlabSize) {
+        debug_assert!(self.slab_size == 0);
+        debug_assert!(self.free_slots.is_empty());
+
+        self.slab_size = slab_size.get_size();
+        self.slots_per_axis = self.region_size / self.slab_size;
+
+        // Add each block to a freelist.
+        for y in 0..self.slots_per_axis {
+            for x in 0..self.slots_per_axis {
+                self.free_slots.push(TextureLocation::new(x, y));
             }
-            // try to coalesce it
-            if page.coalesce() && page.can_allocate(&requested_size) {
-                page_id = Some(i);
-                break;
+        }
+
+        self.total_slot_count = self.free_slots.len();
+    }
+
+    // Deinit a region, allowing it to become a region with
+    // a different allocator size.
+    fn deinit(&mut self) {
+        self.slab_size = 0;
+        self.free_slots.clear();
+        self.slots_per_axis = 0;
+        self.total_slot_count = 0;
+    }
+
+    fn is_empty(&self) -> bool {
+        self.slab_size == 0
+    }
+
+    // Attempt to allocate a fixed size block from this region.
+    fn alloc(&mut self) -> Option<DeviceUintPoint> {
+        self.free_slots
+            .pop()
+            .map(|location| {
+                DeviceUintPoint::new(self.origin.x + self.slab_size * location.x as u32,
+                                     self.origin.y + self.slab_size * location.y as u32)
+            })
+    }
+
+    // Free a block in this region.
+    fn free(&mut self, point: DeviceUintPoint) {
+        let x = (point.x - self.origin.x) / self.slab_size;
+        let y = (point.y - self.origin.y) / self.slab_size;
+        self.free_slots.push(TextureLocation::new(x, y));
+
+        // If this region is completely unused, deinit it
+        // so that it can become a different slab size
+        // as required.
+        if self.free_slots.len() == self.total_slot_count {
+            self.deinit();
+        }
+    }
+}
+
+// A texture array contains a number of texture layers, where
+// each layer contains one or more regions that can act
+// as slab allocators.
+struct TextureArray {
+    format: ImageFormat,
+    is_allocated: bool,
+    regions: Vec<TextureRegion>,
+    texture_id: Option<CacheTextureId>,
+}
+
+impl TextureArray {
+    fn new(format: ImageFormat) -> TextureArray {
+        TextureArray {
+            format,
+            is_allocated: false,
+            regions: Vec::new(),
+            texture_id: None,
+        }
+    }
+
+    // Allocate space in this texture array.
+    fn alloc(&mut self,
+             width: u32,
+             height: u32,
+             user_data: [f32; 2],
+             frame_id: FrameId) -> Option<CacheEntry> {
+        // Lazily allocate the regions if not already created.
+        // This means that very rarely used image formats can be
+        // added but won't allocate a cache if never used.
+        if !self.is_allocated {
+            debug_assert!(TEXTURE_LAYER_DIMENSIONS % TEXTURE_REGION_DIMENSIONS == 0);
+            let regions_per_axis = TEXTURE_LAYER_DIMENSIONS / TEXTURE_REGION_DIMENSIONS;
+            for layer_index in 0..TEXTURE_ARRAY_LAYERS {
+                for y in 0..regions_per_axis {
+                    for x in 0..regions_per_axis {
+                        let origin = DeviceUintPoint::new(x * TEXTURE_REGION_DIMENSIONS,
+                                                          y * TEXTURE_REGION_DIMENSIONS);
+                        let region = TextureRegion::new(TEXTURE_REGION_DIMENSIONS,
+                                                        layer_index,
+                                                        origin);
+                        self.regions.push(region);
+                    }
+                }
             }
-            if page.can_grow(self.max_texture_size) {
-                // try to grow it
-                let new_width = cmp::min(page.texture_size.width * 2, self.max_texture_size);
-                let new_height = cmp::min(page.texture_size.height * 2, self.max_texture_size);
-                let texture_size = DeviceUintSize::new(new_width, new_height);
-                self.pending_updates.push(TextureUpdate {
-                    id: page.texture_id,
-                    op: texture_grow_op(texture_size, format, mode),
-                });
+            self.is_allocated = true;
+        }
+
+        // Quantize the size of the allocation to select a region to
+        // allocate from.
+        let slab_size = SlabSize::new(width, height);
+        let slab_size_dim = slab_size.get_size();
+
+        // TODO(gw): For simplicity, the initial implementation just
+        //           has a single vec<> of regions. We could easily
+        //           make this more efficient by storing a list of
+        //           regions for each slab size specifically...
 
-                let extra_texels = new_width * new_height - page.texture_size.width * page.texture_size.height;
-                let extra_bytes = extra_texels * format.bytes_per_pixel().unwrap_or(0);
-                page_profile.inc(extra_bytes as usize);
+        // Keep track of the location of an empty region,
+        // in case we need to select a new empty region
+        // after the loop.
+        let mut empty_region_index = None;
+        let mut entry_kind = None;
 
-                page.grow(texture_size);
-
-                if page.can_allocate(&requested_size) {
-                    page_id = Some(i);
+        // Run through the existing regions of this size, and see if
+        // we can find a free block in any of them.
+        for (i, region) in self.regions.iter_mut().enumerate() {
+            if region.slab_size == 0 {
+                empty_region_index = Some(i);
+            } else if region.slab_size == slab_size_dim {
+                if let Some(location) = region.alloc() {
+                    entry_kind = Some(EntryKind::Cache {
+                        layer_index: region.layer_index as u16,
+                        region_index: i as u16,
+                        origin: location,
+                    });
                     break;
                 }
             }
         }
 
-        let mut page = match page_id {
-            Some(index) => &mut page_list[index],
-            None => {
-                let init_texture_size = initial_texture_size(self.max_texture_size);
-                let texture_size = DeviceUintSize::new(cmp::max(requested_width, init_texture_size.width),
-                                                       cmp::max(requested_height, init_texture_size.height));
-                let extra_bytes = texture_size.width * texture_size.height * format.bytes_per_pixel().unwrap_or(0);
-                page_profile.inc(extra_bytes as usize);
-
-                let free_texture_levels_entry = self.free_texture_levels.entry(format);
-                let mut free_texture_levels = match free_texture_levels_entry {
-                    Entry::Vacant(entry) => entry.insert(Vec::new()),
-                    Entry::Occupied(entry) => entry.into_mut(),
-                };
-                if free_texture_levels.is_empty() {
-                    let texture_id = self.cache_id_list.allocate();
-
-                    let update_op = TextureUpdate {
-                        id: texture_id,
-                        op: texture_create_op(texture_size,
-                                              format,
-                                              mode,
-                                              filter),
-                    };
-                    self.pending_updates.push(update_op);
-
-                    free_texture_levels.push(FreeTextureLevel {
-                        texture_id,
-                    });
-                }
-                let free_texture_level = free_texture_levels.pop().unwrap();
-                let texture_id = free_texture_level.texture_id;
-
-                let page = TexturePage::new(texture_id, texture_size);
-                page_list.push(page);
-                page_list.last_mut().unwrap()
-            },
-        };
-
-        let location = page.allocate(&requested_size)
-                           .expect("All the checks have passed till now, there is no way back.");
-        let cache_item = TextureCacheItem::new(
-            page.texture_id,
-            DeviceUintRect::new(location, requested_size),
-            format,
-            user_data
-        );
-
-        let new_image_id = match existing_item_id {
-            Some(id) => {
-                *self.items.get_mut(id) = cache_item.clone();
-                None
+        // Find a region of the right size and try to allocate from it.
+        if entry_kind.is_none() {
+            if let Some(empty_region_index) = empty_region_index {
+                let region = &mut self.regions[empty_region_index];
+                region.init(slab_size);
+                entry_kind = region.alloc().map(|location| {
+                    EntryKind::Cache {
+                        layer_index: region.layer_index as u16,
+                        region_index: empty_region_index as u16,
+                        origin: location,
+                    }
+                });
             }
-            None => {
-                let id = self.items.insert(cache_item.clone());
-                Some(id)
-            }
-        };
-
-        AllocationResult {
-            item: cache_item,
-            kind: AllocationKind::TexturePage,
-            new_image_id,
-        }
-    }
-
-    pub fn update(
-        &mut self,
-        image_id: &TextureCacheItemId,
-        descriptor: ImageDescriptor,
-        filter: TextureFilter,
-        data: ImageData,
-        mut dirty_rect: Option<DeviceUintRect>,
-    ) {
-        let mut existing_item = self.items.get(image_id).clone();
-
-        if existing_item.allocated_rect.size.width != descriptor.width ||
-           existing_item.allocated_rect.size.height != descriptor.height ||
-           existing_item.format != descriptor.format {
-
-            self.free_item_rect(existing_item.clone());
-
-            self.allocate_impl(
-                descriptor.width,
-                descriptor.height,
-                descriptor.format,
-                filter,
-                existing_item.user_data,
-                &mut TextureCacheProfileCounters::new(),
-                Some(image_id),
-            );
-
-            // Fetch the item again because the rect most likely changed during reallocation.
-            existing_item = self.items.get(image_id).clone();
-            // If we reallocated, we need to upload the whole item again.
-            dirty_rect = None;
         }
 
-        let op = match data {
+        entry_kind.map(|kind| {
+            CacheEntry {
+                size: DeviceUintSize::new(width, height),
+                user_data,
+                last_access: frame_id,
+                kind,
+                uv_rect_handle: GpuCacheHandle::new(),
+                format: self.format,
+                texture_id: self.texture_id.unwrap(),
+            }
+        })
+    }
+}
+
+impl TextureUpdate {
+    // Constructs a TextureUpdate operation to be passed to the
+    // rendering thread in order to do an upload to the right
+    // location in the texture cache.
+    fn new_update(data: ImageData,
+                  descriptor: &ImageDescriptor,
+                  origin: DeviceUintPoint,
+                  size: DeviceUintSize,
+                  texture_id: CacheTextureId,
+                  layer_index: i32,
+                  dirty_rect: Option<DeviceUintRect>) -> TextureUpdate {
+        let data_src = match data {
+            ImageData::Blob(..) => {
+                panic!("The vector image should have been rasterized.");
+            }
             ImageData::External(ext_image) => {
                 match ext_image.image_type {
                     ExternalImageType::Texture2DHandle |
+                    ExternalImageType::Texture2DArrayHandle |
                     ExternalImageType::TextureRectHandle |
                     ExternalImageType::TextureExternalHandle => {
                         panic!("External texture handle should not go through texture_cache.");
                     }
                     ExternalImageType::ExternalBuffer => {
-                        TextureUpdateOp::UpdateForExternalBuffer {
-                            rect: existing_item.allocated_rect,
+                        TextureUpdateSource::External {
                             id: ext_image.id,
                             channel_index: ext_image.channel_index,
-                            stride: descriptor.stride,
-                            offset: descriptor.offset,
                         }
                     }
                 }
             }
-            ImageData::Blob(..) => {
-                panic!("The vector image should have been rasterized into a raw image.");
-            }
             ImageData::Raw(bytes) => {
-                match dirty_rect {
-                    Some(dirty) => {
-                        let stride = descriptor.compute_stride();
-                        let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x;
-                        TextureUpdateOp::Update {
-                            page_pos_x: existing_item.allocated_rect.origin.x + dirty.origin.x,
-                            page_pos_y: existing_item.allocated_rect.origin.y + dirty.origin.y,
-                            width: dirty.size.width,
-                            height: dirty.size.height,
-                            data: bytes,
-                            stride: Some(stride),
-                            offset,
-                        }
-                    }
-                    None => {
-                        TextureUpdateOp::Update {
-                            page_pos_x: existing_item.allocated_rect.origin.x,
-                            page_pos_y: existing_item.allocated_rect.origin.y,
-                            width: descriptor.width,
-                            height: descriptor.height,
-                            data: bytes,
-                            stride: descriptor.stride,
-                            offset: descriptor.offset,
-                        }
-                    }
+                let finish = descriptor.offset +
+                             descriptor.width * descriptor.format.bytes_per_pixel().unwrap_or(0) +
+                             (descriptor.height-1) * descriptor.compute_stride();
+                assert!(bytes.len() >= finish as usize);
+
+                TextureUpdateSource::Bytes {
+                    data: bytes
                 }
             }
         };
 
-        let update_op = TextureUpdate {
-            id: existing_item.texture_id,
-            op,
+        let update_op = match dirty_rect {
+            Some(dirty) => {
+                let stride = descriptor.compute_stride();
+                let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x;
+                let origin = DeviceUintPoint::new(origin.x + dirty.origin.x,
+                                                  origin.y + dirty.origin.y);
+                TextureUpdateOp::Update {
+                    rect: DeviceUintRect::new(origin, dirty.size),
+                    source: data_src,
+                    stride: Some(stride),
+                    offset,
+                    layer_index,
+                }
+            }
+            None => {
+                TextureUpdateOp::Update {
+                    rect: DeviceUintRect::new(origin, size),
+                    source: data_src,
+                    stride: descriptor.stride,
+                    offset: descriptor.offset,
+                    layer_index,
+                }
+            }
         };
 
-        self.pending_updates.push(update_op);
-    }
-
-    pub fn insert(&mut self,
-                  descriptor: ImageDescriptor,
-                  filter: TextureFilter,
-                  data: ImageData,
-                  user_data: [f32; 2],
-                  profile: &mut TextureCacheProfileCounters) -> TextureCacheItemId {
-        if let ImageData::Blob(..) = data {
-            panic!("must rasterize the vector image before adding to the cache");
-        }
-
-        let width = descriptor.width;
-        let height = descriptor.height;
-        let format = descriptor.format;
-        let stride = descriptor.stride;
-
-        if let ImageData::Raw(ref vec) = data {
-            let finish = descriptor.offset +
-                         width * format.bytes_per_pixel().unwrap_or(0) +
-                         (height-1) * descriptor.compute_stride();
-            assert!(vec.len() >= finish as usize);
-        }
-
-        let result = self.allocate(width,
-                                   height,
-                                   format,
-                                   filter,
-                                   user_data,
-                                   profile);
-
-        match result.kind {
-            AllocationKind::TexturePage => {
-                match data {
-                    ImageData::External(ext_image) => {
-                        match ext_image.image_type {
-                            ExternalImageType::Texture2DHandle |
-                            ExternalImageType::TextureRectHandle |
-                            ExternalImageType::TextureExternalHandle => {
-                                panic!("External texture handle should not go through texture_cache.");
-                            }
-                            ExternalImageType::ExternalBuffer => {
-                                let update_op = TextureUpdate {
-                                    id: result.item.texture_id,
-                                    op: TextureUpdateOp::UpdateForExternalBuffer {
-                                        rect: result.item.allocated_rect,
-                                        id: ext_image.id,
-                                        channel_index: ext_image.channel_index,
-                                        stride,
-                                        offset: descriptor.offset,
-                                    },
-                                };
-
-                                self.pending_updates.push(update_op);
-                            }
-                        }
-                    }
-                    ImageData::Blob(..) => {
-                        panic!("The vector image should have been rasterized.");
-                    }
-                    ImageData::Raw(bytes) => {
-                        let update_op = TextureUpdate {
-                            id: result.item.texture_id,
-                            op: TextureUpdateOp::Update {
-                                page_pos_x: result.item.allocated_rect.origin.x,
-                                page_pos_y: result.item.allocated_rect.origin.y,
-                                width: result.item.allocated_rect.size.width,
-                                height: result.item.allocated_rect.size.height,
-                                data: bytes,
-                                stride,
-                                offset: descriptor.offset,
-                            },
-                        };
-
-                        self.pending_updates.push(update_op);
-                    }
-                }
-            }
-            AllocationKind::Standalone => {
-                match data {
-                    ImageData::External(ext_image) => {
-                        match ext_image.image_type {
-                            ExternalImageType::Texture2DHandle |
-                            ExternalImageType::TextureRectHandle |
-                            ExternalImageType::TextureExternalHandle => {
-                                panic!("External texture handle should not go through texture_cache.");
-                            }
-                            ExternalImageType::ExternalBuffer => {
-                                let update_op = TextureUpdate {
-                                    id: result.item.texture_id,
-                                    op: TextureUpdateOp::Create {
-                                        width,
-                                        height,
-                                        format,
-                                        filter,
-                                        mode: RenderTargetMode::None,
-                                        data: Some(data),
-                                    },
-                                };
-
-                                self.pending_updates.push(update_op);
-                            }
-                        }
-                    }
-                    _ => {
-                        let update_op = TextureUpdate {
-                            id: result.item.texture_id,
-                            op: TextureUpdateOp::Create {
-                                width,
-                                height,
-                                format,
-                                filter,
-                                mode: RenderTargetMode::None,
-                                data: Some(data),
-                            },
-                        };
-
-                        self.pending_updates.push(update_op);
-                    }
-                }
-            }
-        }
-
-        result.new_image_id.unwrap()
-    }
-
-    pub fn get(&self, id: &TextureCacheItemId) -> &TextureCacheItem {
-        self.items.get(id)
-    }
-
-    pub fn get_mut(&mut self, id: &TextureCacheItemId) -> &mut TextureCacheItem {
-        self.items.get_mut(id)
-    }
-
-    pub fn free(&mut self, id: TextureCacheItemId) {
-        let item = self.items.free(id);
-        self.free_item_rect(item);
-    }
-
-    fn free_item_rect(&mut self, item: TextureCacheItem) {
-        match self.arena.texture_page_for_id(item.texture_id) {
-            Some(texture_page) => texture_page.free(&item.allocated_rect),
-            None => {
-                // This is a standalone texture allocation. Just push it back onto the free
-                // list.
-                self.pending_updates.push(TextureUpdate {
-                    id: item.texture_id,
-                    op: TextureUpdateOp::Free,
-                });
-                self.cache_id_list.free(item.texture_id);
-            }
+        TextureUpdate {
+            id: texture_id,
+            op: update_op,
         }
     }
 }
-
-fn texture_create_op(texture_size: DeviceUintSize,
-                     format: ImageFormat,
-                     mode: RenderTargetMode,
-                     filter: TextureFilter)
-                     -> TextureUpdateOp {
-    TextureUpdateOp::Create {
-        width: texture_size.width,
-        height: texture_size.height,
-        format,
-        filter,
-        mode,
-        data: None,
-    }
-}
-
-fn texture_grow_op(texture_size: DeviceUintSize,
-                   format: ImageFormat,
-                   mode: RenderTargetMode)
-                   -> TextureUpdateOp {
-    TextureUpdateOp::Grow {
-        width: texture_size.width,
-        height: texture_size.height,
-        format,
-        filter: TextureFilter::Linear,
-        mode,
-    }
-}
-
-trait FitsInside {
-    fn fits_inside(&self, other: &Self) -> bool;
-}
-
-impl FitsInside for DeviceUintSize {
-    fn fits_inside(&self, other: &DeviceUintSize) -> bool {
-        self.width <= other.width && self.height <= other.height
-    }
-}
-
-/// FIXME(pcwalton): Would probably be more efficient as a bit vector.
-#[derive(Clone, Copy)]
-pub struct FreeTextureLevel {
-    texture_id: CacheTextureId,
-}
-
-/// Returns the number of pixels on a side we start out with for our texture atlases.
-fn initial_texture_size(max_texture_size: u32) -> DeviceUintSize {
-    let initial_size = cmp::min(max_texture_size, INITIAL_TEXTURE_SIZE);
-    DeviceUintSize::new(initial_size, initial_size)
-}
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,32 +1,32 @@
 /* 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 border::{BorderCornerInstance, BorderCornerSide};
 use device::TextureId;
-use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheUpdateList};
+use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuCacheUpdateList};
 use internal_types::BatchTextures;
 use internal_types::{FastHashMap, SourceTexture};
 use mask_cache::MaskCacheInfo;
-use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve, ImagePrimitiveKind, PrimitiveCacheKey};
+use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve, PrimitiveCacheKey};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use profiler::FrameProfileCounters;
 use render_task::{AlphaRenderItem, MaskGeometryKind, MaskSegment, RenderTask, RenderTaskData};
 use render_task::{RenderTaskId, RenderTaskIndex, RenderTaskKey, RenderTaskKind};
 use render_task::RenderTaskLocation;
 use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::ResourceCache;
 use std::{f32, i32, mem, usize};
 use texture_allocator::GuillotineAllocator;
 use util::{TransformedRect, TransformedRectKind};
 use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
-use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstanceKey};
+use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstance};
 use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
 use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
 use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_INDEX: RenderTaskIndex = RenderTaskIndex(i32::MAX as usize);
 
@@ -426,51 +426,43 @@ impl AlphaRenderItem {
                     PrimitiveKind::Line => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Line, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(0, 0, 0));
                     }
                     PrimitiveKind::Image => {
                         let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
-                        let (color_texture_id, uv_address) = match image_cpu.kind {
-                            ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, _) => {
-                                resolve_image(image_key,
-                                              image_rendering,
-                                              tile_offset,
-                                              ctx.resource_cache,
-                                              gpu_cache,
-                                              deferred_resolves)
-                            }
-                            ImagePrimitiveKind::WebGL(context_id) => {
-                                let webgl_texture = ctx.resource_cache.get_webgl_texture(&context_id);
-                                let uv_rect = [ 0.0,
-                                                webgl_texture.size.height as f32,
-                                                webgl_texture.size.width as f32,
-                                                0.0];
-                                let cache_handle = gpu_cache.push_per_frame_blocks(&[uv_rect.into()]);
-                                (webgl_texture.id, cache_handle)
-                            }
-                        };
+                        let (color_texture_id, uv_address) = resolve_image(image_cpu.image_key,
+                                                                           image_cpu.image_rendering,
+                                                                           image_cpu.tile_offset,
+                                                                           ctx.resource_cache,
+                                                                           gpu_cache,
+                                                                           deferred_resolves);
+
+                        if color_texture_id == SourceTexture::Invalid {
+                            return;
+                        }
 
                         let batch_kind = match color_texture_id {
                             SourceTexture::External(ext_image) => {
                                 match ext_image.image_type {
                                     ExternalImageType::Texture2DHandle => AlphaBatchKind::Image(ImageBufferKind::Texture2D),
+                                    ExternalImageType::Texture2DArrayHandle => AlphaBatchKind::Image(ImageBufferKind::Texture2DArray),
                                     ExternalImageType::TextureRectHandle => AlphaBatchKind::Image(ImageBufferKind::TextureRect),
                                     ExternalImageType::TextureExternalHandle => AlphaBatchKind::Image(ImageBufferKind::TextureExternal),
                                     ExternalImageType::ExternalBuffer => {
                                         // The ExternalImageType::ExternalBuffer should be handled by resource_cache.
                                         // It should go through the non-external case.
                                         panic!("Non-texture handle type should be handled in other way.");
                                     }
                                 }
                             }
                             _ => {
-                                AlphaBatchKind::Image(ImageBufferKind::Texture2D)
+                                AlphaBatchKind::Image(ImageBufferKind::Texture2DArray)
                             }
                         };
 
                         let textures = BatchTextures {
                             colors: [color_texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                         };
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
@@ -479,22 +471,22 @@ impl AlphaRenderItem {
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                         let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
-                        let font = FontInstanceKey::new(text_cpu.font_key,
-                                                        font_size_dp,
-                                                        text_cpu.color,
-                                                        text_cpu.normal_render_mode,
-                                                        text_cpu.glyph_options,
-                                                        text_cpu.subpx_dir);
+                        let font = FontInstance::new(text_cpu.font_key,
+                                                     font_size_dp,
+                                                     text_cpu.color,
+                                                     text_cpu.normal_render_mode,
+                                                     text_cpu.glyph_options,
+                                                     text_cpu.subpx_dir);
 
                         let texture_id = ctx.resource_cache.get_glyphs(font,
                                                                        &text_cpu.glyph_keys,
                                                                        |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
                             instances.push(base_instance.build(index as i32, uv_address, 0));
                         });
 
@@ -508,17 +500,18 @@ impl AlphaRenderItem {
 
                             batch.add_instances(&instances);
                         }
                     }
                     PrimitiveKind::TextShadow => {
                         let cache_task_id = prim_metadata.render_task.as_ref().expect("no render task!").id;
                         let cache_task_index = render_tasks.get_task_index(&cache_task_id,
                                                                            child_pass_index);
-                        let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, no_textures);
+                        let textures = BatchTextures::render_target_cache();
+                        let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(0, cache_task_index.0 as i32, 0));
                     }
                     PrimitiveKind::AlignedGradient => {
                         let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         for part_index in 0..(gradient_cpu.stops_count - 1) {
@@ -547,36 +540,42 @@ impl AlphaRenderItem {
                             let image_key = image_yuv_cpu.yuv_key[channel];
 
                             let (texture, address) = resolve_image(image_key,
                                                                    image_yuv_cpu.image_rendering,
                                                                    None,
                                                                    ctx.resource_cache,
                                                                    gpu_cache,
                                                                    deferred_resolves);
+
+                            if texture == SourceTexture::Invalid {
+                                return;
+                            }
+
                             textures.colors[channel] = texture;
                             uv_rect_addresses[channel] = address.as_int(gpu_cache);
                         }
 
                         let get_buffer_kind = |texture: SourceTexture| {
                             match texture {
                                 SourceTexture::External(ext_image) => {
                                     match ext_image.image_type {
                                         ExternalImageType::Texture2DHandle => ImageBufferKind::Texture2D,
+                                        ExternalImageType::Texture2DArrayHandle => ImageBufferKind::Texture2DArray,
                                         ExternalImageType::TextureRectHandle => ImageBufferKind::TextureRect,
                                         ExternalImageType::TextureExternalHandle => ImageBufferKind::TextureExternal,
                                         ExternalImageType::ExternalBuffer => {
                                             // The ExternalImageType::ExternalBuffer should be handled by resource_cache.
                                             // It should go through the non-external case.
                                             panic!("Non-texture handle type should be handled in other way.");
                                         }
                                     }
                                 }
                                 _ => {
-                                    ImageBufferKind::Texture2D
+                                    ImageBufferKind::Texture2DArray
                                 }
                             }
                         };
 
                         // All yuv textures should be the same type.
                         let buffer_kind = get_buffer_kind(textures.colors[0]);
                         assert!(textures.colors[1.. image_yuv_cpu.format.get_plane_num()].iter().all(
                             |&tid| buffer_kind == get_buffer_kind(tid)
@@ -697,92 +696,98 @@ impl ClipBatcher {
                task_index: RenderTaskIndex,
                clips: &[(PackedLayerIndex, MaskCacheInfo)],
                resource_cache: &ResourceCache,
                gpu_cache: &GpuCache,
                geometry_kind: MaskGeometryKind) {
 
         for &(packed_layer_index, ref info) in clips.iter() {
             let instance = CacheClipInstance {
-                task_id: task_index.0 as i32,
+                render_task_index: task_index.0 as i32,
                 layer_index: packed_layer_index.0 as i32,
-                address: 0,
                 segment: 0,
-                resource_address: 0,
+                clip_data_address: GpuCacheAddress::invalid(),
+                resource_address: GpuCacheAddress::invalid(),
             };
 
-            for clip_index in 0 .. info.complex_clip_range.get_count() {
-                let gpu_address = info.complex_clip_range.location.as_int(gpu_cache) +
-                                  (CLIP_DATA_GPU_BLOCKS * clip_index) as i32;
-                match geometry_kind {
-                    MaskGeometryKind::Default => {
-                        self.rectangles.push(CacheClipInstance {
-                            address: gpu_address,
-                            segment: MaskSegment::All as i32,
-                            ..instance
-                        });
-                    }
-                    MaskGeometryKind::CornersOnly => {
-                        self.rectangles.extend_from_slice(&[
-                            CacheClipInstance {
-                                address: gpu_address,
-                                segment: MaskSegment::TopLeftCorner as i32,
+            if !info.complex_clip_range.is_empty() {
+                let base_gpu_address = gpu_cache.get_address(&info.complex_clip_range.location);
+
+                for clip_index in 0 .. info.complex_clip_range.get_count() {
+                    let gpu_address = base_gpu_address + CLIP_DATA_GPU_BLOCKS * clip_index;
+                    match geometry_kind {
+                        MaskGeometryKind::Default => {
+                            self.rectangles.push(CacheClipInstance {
+                                clip_data_address: gpu_address,
+                                segment: MaskSegment::All as i32,
                                 ..instance
-                            },
-                            CacheClipInstance {
-                                address: gpu_address,
-                                segment: MaskSegment::TopRightCorner as i32,
-                                ..instance
-                            },
-                            CacheClipInstance {
-                                address: gpu_address,
-                                segment: MaskSegment::BottomLeftCorner as i32,
-                                ..instance