Bug 1385003 - Update webrender to commit e68c8acb021656440d26ac46e705e7ceb31891e6. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 09 Aug 2017 08:46:18 -0400
changeset 373770 f5b9bc1d974548f6707d6beb807aecf09ff073c5
parent 373769 2c214b5c0ab9c51e23c670dd3a01765e5a5fccb5
child 373771 f612b8179bca445168099c54e2632dba9b8a6ea4
push id93597
push userarchaeopteryx@coole-files.de
push dateThu, 10 Aug 2017 15:43:43 +0000
treeherdermozilla-inbound@17a9aa5732e5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1385003
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 1385003 - Update webrender to commit e68c8acb021656440d26ac46e705e7ceb31891e6. r=jrmuizel MozReview-Commit-ID: Kw49PJqoLn4
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/build.rs
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/examples/nested_display_list.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/cs_text_run.vs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_border_edge.vs.glsl
gfx/webrender/res/ps_text_run.vs.glsl
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/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.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/profiler.rs
gfx/webrender/src/record.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/color.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/lib.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 0748e02d1be5f889fc17de2eb81c0c363ee3aa80
+Latest Commit: e68c8acb021656440d26ac46e705e7ceb31891e6
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -13,17 +13,17 @@ profiler = ["thread_profiler/thread_prof
 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"
-fnv = "1.0"
+fxhash = "0.2.1"
 gleam = "0.4.7"
 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"}
@@ -33,16 +33,16 @@ thread_profiler = "0.1.1"
 plane-split = "0.6"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 rand = "0.3"                # for the benchmarks
 servo-glutin = "0.11"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
-freetype = { version = "0.2", default-features = false }
+freetype = { version = "0.3", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.8.0"
 core-text = { version = "6.1", default-features = false }
--- a/gfx/webrender/build.rs
+++ b/gfx/webrender/build.rs
@@ -9,32 +9,32 @@ use std::fs::{canonicalize, read_dir, Fi
 
 fn write_shaders(glsl_files: Vec<PathBuf>, shader_file_path: &Path) {
     let mut shader_file = File::create(shader_file_path).unwrap();
 
     write!(shader_file, "/// AUTO GENERATED BY build.rs\n\n").unwrap();
     write!(shader_file, "use std::collections::HashMap;\n").unwrap();
     write!(shader_file, "lazy_static! {{\n").unwrap();
     write!(shader_file, "  pub static ref SHADERS: HashMap<&'static str, &'static str> = {{\n").unwrap();
-    write!(shader_file, "    let mut h = HashMap::with_capacity({});\n", glsl_files.len()).unwrap();
+    write!(shader_file, "    let mut h = HashMap::new();\n").unwrap();
     for glsl in glsl_files {
         let shader_name = glsl.file_name().unwrap().to_str().unwrap();
         // strip .glsl
         let shader_name = shader_name.replace(".glsl", "");
         let full_path = canonicalize(&glsl).unwrap();
         let full_name = full_path.as_os_str().to_str().unwrap();
         // if someone is building on a network share, I'm sorry.
         let full_name = full_name.replace("\\\\?\\", "");
         let full_name = full_name.replace("\\", "/");
         write!(shader_file, "    h.insert(\"{}\", include_str!(\"{}\"));\n",
                shader_name, full_name).unwrap();
     }
-    write!(shader_file, "    h\n").unwrap(); 
-    write!(shader_file, "  }};\n").unwrap(); 
-    write!(shader_file, "}}\n").unwrap(); 
+    write!(shader_file, "    h\n").unwrap();
+    write!(shader_file, "  }};\n").unwrap();
+    write!(shader_file, "}}\n").unwrap();
 }
 
 fn main() {
     let out_dir = env::var("OUT_DIR").unwrap_or("out".to_owned());
 
     let shaders_file = Path::new(&out_dir).join("shaders.rs");
     let mut glsl_files = vec![];
 
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -15,17 +15,19 @@ mod boilerplate;
 use boilerplate::HandyDandyRectBuilder;
 use std::sync::Mutex;
 use webrender::api::*;
 
 // This example creates a 100x100 white rect and allows the user to move it
 // around by using the arrow keys. It does this by using the animation API.
 
 fn body(_api: &RenderApi,
+        _document_id: &DocumentId,
         builder: &mut DisplayListBuilder,
+        _resouces: &mut ResourceUpdates,
         _pipeline_id: &PipelineId,
         _layout_size: &LayoutSize) {
     // Create a 100x100 stacking context with an animatable transform property.
     // Note the magic "42" we use as the animation key. That is used to update
     // the transform in the keyboard event handler code.
     let bounds = (0,0).to(100, 100);
     builder.push_stacking_context(ScrollPolicy::Scrollable,
                                   bounds,
@@ -40,33 +42,31 @@ fn body(_api: &RenderApi,
 
     builder.pop_stacking_context();
 }
 
 lazy_static! {
     static ref TRANSFORM: Mutex<LayoutTransform> = Mutex::new(LayoutTransform::identity());
 }
 
-fn event_handler(event: &glutin::Event,
-                 api: &RenderApi)
-{
+fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
     match *event {
         glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
             let offset = match key {
                  glutin::VirtualKeyCode::Down => (0.0, 10.0),
                  glutin::VirtualKeyCode::Up => (0.0, -10.0),
                  glutin::VirtualKeyCode::Right => (10.0, 0.0),
                  glutin::VirtualKeyCode::Left => (-10.0, 0.0),
                  _ => return,
             };
             // Update the transform based on the keyboard input and push it to
             // webrender using the generate_frame API. This will recomposite with
             // the updated transform.
             let new_transform = TRANSFORM.lock().unwrap().post_translate(LayoutVector3D::new(offset.0, offset.1, 0.0));
-            api.generate_frame(Some(DynamicProperties {
+            api.generate_frame(document_id, Some(DynamicProperties {
                 transforms: vec![
                   PropertyValue {
                     key: PropertyBindingKey::new(42),
                     value: new_transform,
                   },
                 ],
                 floats: vec![],
             }));
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -169,33 +169,37 @@ fn load_file(name: &str) -> Vec<u8> {
     buffer
 }
 
 fn main() {
     boilerplate::main_wrapper(body, event_handler, None);
 }
 
 fn body(api: &RenderApi,
+        _document_id: &DocumentId,
         builder: &mut DisplayListBuilder,
+        resources: &mut ResourceUpdates,
         _pipeline_id: &PipelineId,
         layout_size: &LayoutSize) {
     let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
     builder.push_stacking_context(ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   MixBlendMode::Normal,
                                   Vec::new());
 
     let image_mask_key = api.generate_image_key();
-    api.add_image(image_mask_key,
-                  ImageDescriptor::new(2, 2, ImageFormat::A8, true),
-                  ImageData::new(vec![0, 80, 180, 255]),
-                  None);
+    resources.add_image(
+        image_mask_key,
+        ImageDescriptor::new(2, 2, ImageFormat::A8, true),
+        ImageData::new(vec![0, 80, 180, 255]),
+        None
+    );
     let mask = ImageMask {
         image: image_mask_key,
         rect: (75, 75).by(100, 100),
         repeat: false,
     };
     let complex = ComplexClipRegion::new((50, 50).to(150, 150), BorderRadius::uniform(20.0));
     let id = builder.define_clip(None, bounds, vec![complex], Some(mask));
     builder.push_clip_id(id);
@@ -225,17 +229,17 @@ fn body(api: &RenderApi,
 
     let bounds = (100, 100).to(200, 200);
     builder.push_border(bounds, None, border_widths, border_details);
 
 
     if false { // draw text?
         let font_key = api.generate_font_key();
         let font_bytes = load_file("res/FreeSans.ttf");
-        api.add_raw_font(font_key, font_bytes, 0);
+        resources.add_raw_font(font_key, font_bytes, 0);
 
         let text_bounds = (100, 200).by(700, 300);
         let glyphs = vec![
             GlyphInstance {
                 index: 48,
                 point: LayoutPoint::new(100.0, 100.0),
             },
             GlyphInstance {
@@ -317,26 +321,26 @@ fn body(api: &RenderApi,
     builder.pop_clip_id();
     builder.pop_stacking_context();
 }
 
 lazy_static! {
     static ref TOUCH_STATE: Mutex<TouchState> = Mutex::new(TouchState::new());
 }
 
-fn event_handler(event: &glutin::Event, api: &RenderApi) {
+fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
     match *event {
         glutin::Event::Touch(touch) => {
             match TOUCH_STATE.lock().unwrap().handle_event(touch) {
                 TouchResult::Pan(pan) => {
-                    api.set_pan(pan);
-                    api.generate_frame(None);
+                    api.set_pan(document_id, pan);
+                    api.generate_frame(document_id, None);
                 }
                 TouchResult::Zoom(zoom) => {
-                    api.set_pinch_zoom(ZoomFactor::new(zoom));
-                    api.generate_frame(None);
+                    api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
+                    api.generate_frame(document_id, None);
                 }
                 TouchResult::None => {}
             }
         }
         _ => ()
     }
 }
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -208,29 +208,31 @@ impl api::BlobImageRenderer for Checkerb
 
         // If we break out of the loop above it means the channel closed unexpectedly.
         Err(api::BlobImageError::Other("Channel closed".into()))
     }
     fn delete_font(&mut self, _font: api::FontKey) { }
 }
 
 fn body(api: &api::RenderApi,
+        _document_id: &api::DocumentId,
         builder: &mut api::DisplayListBuilder,
+        resources: &mut api::ResourceUpdates,
         _pipeline_id: &api::PipelineId,
         layout_size: &api::LayoutSize) {
     let blob_img1 = api.generate_image_key();
-    api.add_image(
+    resources.add_image(
         blob_img1,
         api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true),
         api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 50, 150, 255))),
         Some(128),
     );
 
     let blob_img2 = api.generate_image_key();
-    api.add_image(
+    resources.add_image(
         blob_img2,
         api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true),
         api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
         None,
     );
 
     let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), *layout_size);
     builder.push_stacking_context(api::ScrollPolicy::Scrollable,
@@ -257,19 +259,17 @@ fn body(api: &api::RenderApi,
         api::LayoutSize::new(0.0, 0.0),
         api::ImageRendering::Auto,
         blob_img2,
     );
 
     builder.pop_stacking_context();
 }
 
-fn event_handler(_event: &glutin::Event,
-                 _api: &api::RenderApi)
-{
+fn event_handler(_event: &glutin::Event, _document_id: api::DocumentId, _api: &api::RenderApi) {
 }
 
 fn main() {
     let worker_config = ThreadPoolConfig::new().thread_name(|idx|{
         format!("WebRender:Worker#{}", idx)
     });
 
     let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 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};
 
 struct Notifier {
     window_proxy: glutin::WindowProxy,
 }
 
 impl Notifier {
     fn new(window_proxy: glutin::WindowProxy) -> Notifier {
         Notifier {
@@ -47,20 +48,23 @@ impl HandyDandyRectBuilder for (i32, i32
 
     fn by(&self, w: i32, h: i32) -> LayoutRect {
         LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
                         LayoutSize::new(w as f32, h as f32))
     }
 }
 
 pub fn main_wrapper(builder_callback: fn(&RenderApi,
+                                         &DocumentId,
                                          &mut DisplayListBuilder,
+                                         &mut ResourceUpdates,
                                          &PipelineId,
                                          &LayoutSize) -> (),
                     event_handler: fn(&glutin::Event,
+                                      DocumentId,
                                       &RenderApi) -> (),
                     options: Option<webrender::RendererOptions>)
 {
     let args: Vec<String> = env::args().collect();
     let res_path = if args.len() > 1 {
         Some(PathBuf::from(&args[1]))
     } else {
         None
@@ -94,39 +98,44 @@ pub fn main_wrapper(builder_callback: fn
         resource_override_path: res_path,
         debug: true,
         precache_shaders: true,
         device_pixel_ratio: window.hidpi_factor(),
         .. options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let size = DeviceUintSize::new(width, height);
-    let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
+    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);
 
     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();
 
-    builder_callback(&api, &mut builder, &pipeline_id, &layout_size);
+    builder_callback(&api, &document_id, &mut builder, &mut resources, &pipeline_id, &layout_size);
 
     api.set_display_list(
+        document_id,
+        epoch,
         Some(root_background_color),
-        epoch,
         LayoutSize::new(width as f32, height as f32),
         builder.finalize(),
-        true);
-    api.set_root_pipeline(pipeline_id);
-    api.generate_frame(None);
+        true,
+        resources
+    );
+    api.set_root_pipeline(document_id, pipeline_id);
+    api.generate_frame(document_id, None);
 
     'outer: for event in window.wait_events() {
         let mut events = Vec::new();
         events.push(event);
 
         for event in window.poll_events() {
             events.push(event);
         }
@@ -134,22 +143,36 @@ pub fn main_wrapper(builder_callback: fn
         for event in events {
             match event {
                 glutin::Event::Closed |
                 glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
                 glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
 
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::P)) => {
-                    let enable_profiler = !renderer.get_profiler_enabled();
-                    renderer.set_profiler_enabled(enable_profiler);
-                    api.generate_frame(None);
-                }
+                    let mut flags = renderer.get_debug_flags();
+                    flags.toggle(PROFILER_DBG);
+                    renderer.set_debug_flags(flags);
+                },
+                glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
+                                             _, Some(glutin::VirtualKeyCode::O)) => {
+                    let mut flags = renderer.get_debug_flags();
+                    flags.toggle(RENDER_TARGET_DBG);
+                    renderer.set_debug_flags(flags);
+                },
+                glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
+                                             _, Some(glutin::VirtualKeyCode::I)) => {
+                    let mut flags = renderer.get_debug_flags();
+                    flags.toggle(TEXTURE_CACHE_DBG);
+                    renderer.set_debug_flags(flags);
+                },
 
-                _ => event_handler(&event, &api),
+                _ => event_handler(&event, document_id, &api),
             }
         }
 
         renderer.update();
         renderer.render(DeviceUintSize::new(width, height));
         window.swap_buffers().ok();
     }
+
+    renderer.deinit();
 }
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/iframe.rs
@@ -0,0 +1,74 @@
+/* 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::HandyDandyRectBuilder;
+use webrender::api::*;
+
+// This example uses the push_iframe API to nest a second pipeline's displaylist
+// inside the root pipeline's display list. When it works, a green square is
+// shown. If it fails, a red square is shown.
+
+fn body(api: &RenderApi,
+        document_id: &DocumentId,
+        builder: &mut DisplayListBuilder,
+        _resources: &mut ResourceUpdates,
+        pipeline_id: &PipelineId,
+        _layout_size: &LayoutSize) {
+
+    // All the sub_* things are for the nested pipeline
+    let sub_size = DeviceUintSize::new(100, 100);
+    let sub_bounds = (0,0).to(sub_size.width as i32, sub_size.height as i32);
+
+    let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
+    let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
+
+    sub_builder.push_stacking_context(ScrollPolicy::Scrollable,
+                                      sub_bounds,
+                                      None,
+                                      TransformStyle::Flat,
+                                      None,
+                                      MixBlendMode::Normal,
+                                      Vec::new());
+    // green rect visible == success
+    sub_builder.push_rect(sub_bounds, None, ColorF::new(0.0, 1.0, 0.0, 1.0));
+    sub_builder.pop_stacking_context();
+
+    api.set_display_list(
+        *document_id,
+        Epoch(0),
+        None,
+        sub_bounds.size,
+        sub_builder.finalize(),
+        true,
+        ResourceUpdates::new(),
+    );
+
+    let bounds = sub_bounds;
+    // And this is for the root pipeline
+    builder.push_stacking_context(ScrollPolicy::Scrollable,
+                                  bounds,
+                                  Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
+                                  TransformStyle::Flat,
+                                  None,
+                                  MixBlendMode::Normal,
+                                  Vec::new());
+    // red rect under the iframe: if this is visible, things have gone wrong
+    builder.push_rect(bounds, None, ColorF::new(1.0, 0.0, 0.0, 1.0));
+    builder.push_iframe(bounds, None, sub_pipeline_id);
+    builder.pop_stacking_context();
+}
+
+fn event_handler(_event: &glutin::Event, _document_id: DocumentId, _api: &RenderApi) {
+}
+
+fn main() {
+    boilerplate::main_wrapper(body, event_handler, None);
+}
--- a/gfx/webrender/examples/nested_display_list.rs
+++ b/gfx/webrender/examples/nested_display_list.rs
@@ -12,17 +12,19 @@ extern crate lazy_static;
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
 use boilerplate::HandyDandyRectBuilder;
 use std::sync::Mutex;
 use webrender::api::*;
 
 fn body(_api: &RenderApi,
+        _document_id: &DocumentId,
         builder: &mut DisplayListBuilder,
+        _resources: &mut ResourceUpdates,
         pipeline_id: &PipelineId,
         layout_size: &LayoutSize) {
     let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
     builder.push_stacking_context(ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
@@ -85,30 +87,29 @@ fn body(_api: &RenderApi,
 
     builder.pop_stacking_context();
 }
 
 lazy_static! {
     static ref CURSOR_POSITION: Mutex<WorldPoint> = Mutex::new(WorldPoint::zero());
 }
 
-fn event_handler(event: &glutin::Event,
-                 api: &RenderApi)
-{
+fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
     match *event {
         glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
             let offset = match key {
                  glutin::VirtualKeyCode::Down => (0.0, -10.0),
                  glutin::VirtualKeyCode::Up => (0.0, 10.0),
                  glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                  glutin::VirtualKeyCode::Left => (10.0, 0.0),
                  _ => return,
             };
 
-            api.scroll(ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
+            api.scroll(document_id,
+                       ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                        *CURSOR_POSITION.lock().unwrap(),
                        ScrollEventPhase::Start);
         }
         glutin::Event::MouseMoved(x, y) => {
             *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
         }
         glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
             if let Some((x, y)) = event_cursor_position {
@@ -116,17 +117,18 @@ fn event_handler(event: &glutin::Event,
             }
 
             const LINE_HEIGHT: f32 = 38.0;
             let (dx, dy) = match delta {
                 glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                 glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
             };
 
-            api.scroll(ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
+            api.scroll(document_id,
+                       ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                        *CURSOR_POSITION.lock().unwrap(),
                        ScrollEventPhase::Start);
         }
         _ => ()
     }
 }
 
 fn main() {
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -12,17 +12,19 @@ extern crate lazy_static;
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
 use boilerplate::HandyDandyRectBuilder;
 use std::sync::Mutex;
 use webrender::api::*;
 
 fn body(_api: &RenderApi,
+        _document_id: &DocumentId,
         builder: &mut DisplayListBuilder,
+        _resources: &mut ResourceUpdates,
         _pipeline_id: &PipelineId,
         layout_size: &LayoutSize) {
     let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
     builder.push_stacking_context(ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
@@ -94,28 +96,29 @@ fn body(_api: &RenderApi,
 
     builder.pop_stacking_context();
 }
 
 lazy_static! {
     static ref CURSOR_POSITION: Mutex<WorldPoint> = Mutex::new(WorldPoint::zero());
 }
 
-fn event_handler(event: &glutin::Event, api: &RenderApi) {
+fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
     match *event {
         glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
             let offset = match key {
                  glutin::VirtualKeyCode::Down => (0.0, -10.0),
                  glutin::VirtualKeyCode::Up => (0.0, 10.0),
                  glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                  glutin::VirtualKeyCode::Left => (10.0, 0.0),
                  _ => return,
             };
 
-            api.scroll(ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
+            api.scroll(document_id,
+                       ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                        *CURSOR_POSITION.lock().unwrap(),
                        ScrollEventPhase::Start);
         }
         glutin::Event::MouseMoved(x, y) => {
             *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
         }
         glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
             if let Some((x, y)) = event_cursor_position {
@@ -123,17 +126,18 @@ fn event_handler(event: &glutin::Event, 
             }
 
             const LINE_HEIGHT: f32 = 38.0;
             let (dx, dy) = match delta {
                 glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                 glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
             };
 
-            api.scroll(ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
+            api.scroll(document_id,
+                       ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                        *CURSOR_POSITION.lock().unwrap(),
                        ScrollEventPhase::Start);
         }
         _ => ()
     }
 }
 
 fn main() {
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -157,52 +157,53 @@ impl TouchState {
     }
 }
 
 fn main() {
     boilerplate::main_wrapper(body, event_handler, None);
 }
 
 fn body(api: &RenderApi,
+        _document_id: &DocumentId,
         builder: &mut DisplayListBuilder,
+        resources: &mut ResourceUpdates,
         _pipeline_id: &PipelineId,
         layout_size: &LayoutSize) {
     let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
     builder.push_stacking_context(ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   MixBlendMode::Normal,
                                   Vec::new());
 
-
     let yuv_chanel1 = api.generate_image_key();
     let yuv_chanel2 = api.generate_image_key();
     let yuv_chanel2_1 = api.generate_image_key();
     let yuv_chanel3 = api.generate_image_key();
-    api.add_image(
+    resources.add_image(
         yuv_chanel1,
         ImageDescriptor::new(100, 100, ImageFormat::A8, true),
         ImageData::new(vec![127; 100 * 100]),
         None,
     );
-    api.add_image(
+    resources.add_image(
         yuv_chanel2,
         ImageDescriptor::new(100, 100, ImageFormat::RG8, true),
         ImageData::new(vec![0; 100 * 100 * 2]),
         None,
     );
-    api.add_image(
+    resources.add_image(
         yuv_chanel2_1,
         ImageDescriptor::new(100, 100, ImageFormat::A8, true),
         ImageData::new(vec![127; 100 * 100]),
         None,
     );
-    api.add_image(
+    resources.add_image(
         yuv_chanel3,
         ImageDescriptor::new(100, 100, ImageFormat::A8, true),
         ImageData::new(vec![127; 100 * 100]),
         None,
     );
 
     builder.push_yuv_image(
         LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
@@ -223,26 +224,26 @@ fn body(api: &RenderApi,
     builder.pop_stacking_context();
 }
 
 lazy_static! {
     static ref TOUCH_STATE: Mutex<TouchState> = Mutex::new(TouchState::new());
 }
 
 
-fn event_handler(event: &glutin::Event, api: &RenderApi) {
+fn event_handler(event: &glutin::Event, document_id: DocumentId, api: &RenderApi) {
     match *event {
         glutin::Event::Touch(touch) => {
             match TOUCH_STATE.lock().unwrap().handle_event(touch) {
                 TouchResult::Pan(pan) => {
-                    api.set_pan(pan);
-                    api.generate_frame(None);
+                    api.set_pan(document_id, pan);
+                    api.generate_frame(document_id, None);
                 }
                 TouchResult::Zoom(zoom) => {
-                    api.set_pinch_zoom(ZoomFactor::new(zoom));
-                    api.generate_frame(None);
+                    api.set_pinch_zoom(document_id, ZoomFactor::new(zoom));
+                    api.generate_frame(document_id, None);
                 }
                 TouchResult::None => {}
             }
         }
         _ => ()
     }
 }
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -19,17 +19,20 @@ void main(void) {
     // below to normalize the glyph offsets relative to the original text
     // shadow rect, which is the union of all elements that make up this
     // text shadow. This allows the text shadow to be rendered at an
     // arbitrary location in a render target (provided by the render
     // task render_target_origin field).
     PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address);
     TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER);
 
-    Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
+    Glyph glyph = fetch_glyph(prim.specific_prim_address,
+                              glyph_index,
+                              text.subpx_dir);
+
     GlyphResource res = fetch_glyph_resource(resource_address);
 
     // Glyphs size is already in device-pixels.
     // The render task origin is in device-pixels. Offset that by
     // the glyph offset, relative to its primitive bounding rect.
     vec2 size = res.uv_rect.zw - res.uv_rect.xy;
     vec2 local_pos = glyph.offset + vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio;
     vec2 origin = prim.task.render_target_origin +
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -54,16 +54,20 @@
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 #define LINE_STYLE_SOLID        0
 #define LINE_STYLE_DOTTED       1
 #define LINE_STYLE_DASHED       2
 #define LINE_STYLE_WAVY         3
 
+#define SUBPX_DIR_NONE        0
+#define SUBPX_DIR_HORIZONTAL  1
+#define SUBPX_DIR_VERTICAL    2
+
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 uniform sampler2D sGradients;
 
 struct RectWithSize {
     vec2 p0;
     vec2 size;
@@ -414,24 +418,40 @@ BorderCorners get_border_corners(Border 
         bl_inner
     );
 }
 
 struct Glyph {
     vec2 offset;
 };
 
-Glyph fetch_glyph(int specific_prim_address, int glyph_index) {
+Glyph fetch_glyph(int specific_prim_address,
+                  int glyph_index,
+                  int subpx_dir) {
     // Two glyphs are packed in each texel in the GPU cache.
     int glyph_address = specific_prim_address +
                         VECS_PER_TEXT_RUN +
                         glyph_index / 2;
     vec4 data = fetch_from_resource_cache_1(glyph_address);
     // Select XY or ZW based on glyph index.
     vec2 glyph = mix(data.xy, data.zw, bvec2(glyph_index % 2 == 1));
+
+    // In subpixel mode, the subpixel offset has already been
+    // accounted for while rasterizing the glyph.
+    switch (subpx_dir) {
+        case SUBPX_DIR_NONE:
+            break;
+        case SUBPX_DIR_HORIZONTAL:
+            glyph.x = trunc(glyph.x);
+            break;
+        case SUBPX_DIR_VERTICAL:
+            glyph.y = trunc(glyph.y);
+            break;
+    }
+
     return Glyph(glyph);
 }
 
 RectWithSize fetch_instance_geometry(int address) {
     vec4 data = fetch_from_resource_cache_1(address);
     return RectWithSize(data.xy, data.zw);
 }
 
@@ -797,21 +817,22 @@ struct Line {
 Line fetch_line(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return Line(data[0], data[1].x, data[1].y);
 }
 
 struct TextRun {
     vec4 color;
     vec2 offset;
+    int subpx_dir;
 };
 
 TextRun fetch_text_run(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
-    return TextRun(data[0], data[1].xy);
+    return TextRun(data[0], data[1].xy, int(data[1].z));
 }
 
 struct Image {
     vec4 stretch_size_and_tile_spacing;  // Size of the actual image and amount of space between
                                          //     tiled instances of this image.
     vec4 sub_rect;                          // If negative, ignored.
 };
 
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -32,17 +32,20 @@ void write_alpha_select(float style) {
             vAlphaSelect = 0.0;
             break;
         default:
             vAlphaSelect = 1.0;
             break;
     }
 }
 
-void write_color(vec4 color, float style, bool flip) {
+// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
+// See https://github.com/servo/webrender/issues/1403 for more info.
+// TODO: convert back to a single function once the driver issues are resolved, if ever.
+void write_color0(vec4 color, float style, bool flip) {
     vec2 modulate;
 
     switch (int(style)) {
         case BORDER_STYLE_GROOVE:
         {
             modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
             break;
         }
@@ -52,16 +55,37 @@ void write_color(vec4 color, float style
             break;
         }
         default:
             modulate = vec2(1.0);
             break;
     }
 
     vColor0 = vec4(color.rgb * modulate.x, color.a);
+}
+
+void write_color1(vec4 color, float style, bool flip) {
+    vec2 modulate;
+
+    switch (int(style)) {
+        case BORDER_STYLE_GROOVE:
+        {
+            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
+            break;
+        }
+        case BORDER_STYLE_RIDGE:
+        {
+            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
+            break;
+        }
+        default:
+            modulate = vec2(1.0);
+            break;
+    }
+
     vColor1 = vec4(color.rgb * modulate.y, color.a);
 }
 
 void write_clip_params(float style,
                        float border_width,
                        float edge_length,
                        float edge_offset,
                        float center_line) {
@@ -171,17 +195,18 @@ void main(void) {
                               segment_rect.size.x,
                               segment_rect.p0.x,
                               segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
         }
     }
 
     write_alpha_select(style);
-    write_color(color, style, color_flip);
+    write_color0(color, style, color_flip);
+    write_color1(color, style, color_flip);
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect);
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -1,39 +1,24 @@
 #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/. */
 
-#define RENDER_MODE_MONO        0
-#define RENDER_MODE_ALPHA       1
-#define RENDER_MODE_SUBPIXEL    2
-
 void main(void) {
     Primitive prim = load_primitive();
     TextRun text = fetch_text_run(prim.specific_prim_address);
 
     int glyph_index = prim.user_data0;
-    int render_mode = prim.user_data1;
-    int resource_address = prim.user_data2;
-
-    Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
-    GlyphResource res = fetch_glyph_resource(resource_address);
+    int resource_address = prim.user_data1;
 
-    switch (render_mode) {
-        case RENDER_MODE_ALPHA:
-            break;
-        case RENDER_MODE_MONO:
-            break;
-        case RENDER_MODE_SUBPIXEL:
-            // In subpixel mode, the subpixel offset has already been
-            // accounted for while rasterizing the glyph.
-            glyph.offset = trunc(glyph.offset);
-            break;
-    }
+    Glyph glyph = fetch_glyph(prim.specific_prim_address,
+                              glyph_index,
+                              text.subpx_dir);
+    GlyphResource res = fetch_glyph_resource(resource_address);
 
     vec2 local_pos = glyph.offset +
                      text.offset +
                      vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio;
 
     RectWithSize local_rect = RectWithSize(local_pos,
                                            (res.uv_rect.zw - res.uv_rect.xy) / uDevicePixelRatio);
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.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 clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
-use fnv::FnvHasher;
+use internal_types::{FastHashSet, FastHashMap};
 use print_tree::PrintTree;
-use std::collections::{HashMap, HashSet};
-use std::hash::BuildHasherDefault;
 use api::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
 use api::{LayerToWorldTransform, PipelineId, ScrollClamping, ScrollEventPhase};
 use api::{LayerVector2D, ScrollLayerState, ScrollLocation, WorldPoint};
 
-pub type ScrollStates = HashMap<ClipId, ScrollingState, BuildHasherDefault<FnvHasher>>;
+pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 pub struct ClipScrollTree {
-    pub nodes: HashMap<ClipId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
-    pub pending_scroll_offsets: HashMap<ClipId, (LayerPoint, ScrollClamping)>,
+    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
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current frame id, used for giving a unique id to all new dynamically
     /// added frames and clips. The ClipScrollTree increments this by one every
     /// time a new dynamic frame is created.
@@ -31,30 +29,30 @@ pub struct ClipScrollTree {
     pub root_reference_frame_id: ClipId,
 
     /// The root scroll node which is the first child of the root reference frame.
     /// 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: HashSet<PipelineId>,
+    pub pipelines_to_discard: FastHashSet<PipelineId>,
 }
 
 impl ClipScrollTree {
     pub fn new() -> ClipScrollTree {
-        let dummy_pipeline = PipelineId(0, 0);
+        let dummy_pipeline = PipelineId::dummy();
         ClipScrollTree {
-            nodes: HashMap::default(),
-            pending_scroll_offsets: HashMap::new(),
+            nodes: FastHashMap::default(),
+            pending_scroll_offsets: FastHashMap::default(),
             currently_scrolling_node_id: None,
             root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
             topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
             current_new_node_item: 1,
-            pipelines_to_discard: HashSet::new(),
+            pipelines_to_discard: FastHashSet::default(),
         }
     }
 
     pub fn root_reference_frame_id(&self) -> ClipId {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.nodes.is_empty());
         debug_assert!(self.nodes.contains_key(&self.root_reference_frame_id));
         self.root_reference_frame_id
@@ -63,18 +61,18 @@ impl ClipScrollTree {
     pub fn topmost_scrolling_node_id(&self) -> ClipId {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.nodes.is_empty());
         debug_assert!(self.nodes.contains_key(&self.topmost_scrolling_node_id));
         self.topmost_scrolling_node_id
     }
 
     pub fn collect_nodes_bouncing_back(&self)
-                                       -> HashSet<ClipId, BuildHasherDefault<FnvHasher>> {
-        let mut nodes_bouncing_back = HashSet::default();
+                                       -> FastHashSet<ClipId> {
+        let mut nodes_bouncing_back = FastHashSet::default();
         for (clip_id, node) in self.nodes.iter() {
             if let NodeType::ScrollFrame(ref scrolling) = node.node_type {
                 if scrolling.bouncing_back {
                     nodes_bouncing_back.insert(*clip_id);
                 }
             }
         }
         nodes_bouncing_back
@@ -118,17 +116,17 @@ impl ClipScrollTree {
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
         self.current_new_node_item = 1;
 
-        let mut scroll_states = HashMap::default();
+        let mut scroll_states = FastHashMap::default();
         for (layer_id, old_node) in &mut self.nodes.drain() {
             if self.pipelines_to_discard.contains(&layer_id.pipeline_id()) {
                 continue;
             }
 
             if let NodeType::ScrollFrame(scrolling) = old_node.node_type {
                 scroll_states.insert(layer_id, scrolling);
             }
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -1,44 +1,99 @@
 /* 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, ProgramId, VAOId, TextureId, VertexFormat};
-use device::{TextureFilter, VertexUsageHint, TextureTarget};
+use device::{Device, GpuMarker, Program, VAOId, 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::{DebugFontVertex, DebugColorVertex, RenderTargetMode};
+use internal_types::RenderTargetMode;
 use std::f32;
 use api::{ColorU, ImageFormat, DeviceUintSize};
 
+const DESC_FONT: VertexDescriptor = VertexDescriptor {
+    vertex_attributes: &[
+        VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
+        VertexAttribute { name: "aColor", count: 4, kind: VertexAttributeKind::U8Norm },
+        VertexAttribute { name: "aColorTexCoord", count: 2, kind: VertexAttributeKind::F32 },
+    ],
+    instance_attributes: &[]
+};
+
+const DESC_COLOR: VertexDescriptor = VertexDescriptor {
+    vertex_attributes: &[
+        VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
+        VertexAttribute { name: "aColor", count: 4, kind: VertexAttributeKind::U8Norm },
+    ],
+    instance_attributes: &[]
+};
+
+#[repr(C)]
+pub struct DebugFontVertex {
+    pub x: f32,
+    pub y: f32,
+    pub color: ColorU,
+    pub u: f32,
+    pub v: f32,
+}
+
+impl DebugFontVertex {
+    pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex {
+        DebugFontVertex {
+            x,
+            y,
+            color,
+            u,
+            v,
+        }
+    }
+}
+
+#[repr(C)]
+pub struct DebugColorVertex {
+    pub x: f32,
+    pub y: f32,
+    pub color: ColorU,
+}
+
+impl DebugColorVertex {
+    pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex {
+        DebugColorVertex {
+            x,
+            y,
+            color,
+        }
+    }
+}
+
 pub struct DebugRenderer {
     font_vertices: Vec<DebugFontVertex>,
     font_indices: Vec<u32>,
-    font_program_id: ProgramId,
+    font_program: Program,
     font_vao: VAOId,
     font_texture_id: TextureId,
 
     tri_vertices: Vec<DebugColorVertex>,
     tri_indices: Vec<u32>,
     tri_vao: VAOId,
     line_vertices: Vec<DebugColorVertex>,
     line_vao: VAOId,
-    color_program_id: ProgramId,
+    color_program: Program,
 }
 
 impl DebugRenderer {
     pub fn new(device: &mut Device) -> DebugRenderer {
-        let font_program_id = device.create_program("debug_font", "shared_other", VertexFormat::DebugFont).unwrap();
-        let color_program_id = device.create_program("debug_color", "shared_other", VertexFormat::DebugColor).unwrap();
+        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(VertexFormat::DebugFont, 32);
-        let line_vao = device.create_vao(VertexFormat::DebugColor, 32);
-        let tri_vao = device.create_vao(VertexFormat::DebugColor, 32);
+        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];
         device.init_texture(font_texture_id,
                             debug_font_data::BMP_WIDTH,
                             debug_font_data::BMP_HEIGHT,
                             ImageFormat::A8,
                             TextureFilter::Linear,
                             RenderTargetMode::None,
@@ -46,24 +101,29 @@ impl DebugRenderer {
 
         DebugRenderer {
             font_vertices: Vec::new(),
             font_indices: Vec::new(),
             line_vertices: Vec::new(),
             tri_vao,
             tri_vertices: Vec::new(),
             tri_indices: Vec::new(),
-            font_program_id,
-            color_program_id,
+            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 line_height(&self) -> f32 {
         debug_font_data::FONT_SIZE as f32 * 1.1
     }
 
     pub fn add_text(&mut self,
                     x: f32,
                     y: f32,
                     text: &str,
@@ -165,40 +225,43 @@ impl DebugRenderer {
                                             viewport_size.width as f32,
                                             viewport_size.height as f32,
                                             0.0,
                                             ORTHO_NEAR_PLANE,
                                             ORTHO_FAR_PLANE);
 
         // Triangles
         if !self.tri_vertices.is_empty() {
-            device.bind_program(self.color_program_id, &projection);
+            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,
                                       &self.tri_indices,
                                       VertexUsageHint::Dynamic);
             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_id, &projection);
+            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,
                                             &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_id, &projection);
+            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,
                                       &self.font_indices,
                                       VertexUsageHint::Dynamic);
             device.update_vao_main_vertices(self.font_vao,
                                             &self.font_vertices,
                                             VertexUsageHint::Dynamic);
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1,32 +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 euclid::Transform3D;
-use fnv::FnvHasher;
 use gleam::gl;
-use internal_types::{PackedVertex, RenderTargetMode, TextureSampler, DEFAULT_TEXTURE};
-use internal_types::{BlurAttribute, ClipAttribute, VertexAttribute};
-use internal_types::{DebugFontVertex, DebugColorVertex};
+use internal_types::{RenderTargetMode, TextureSampler, DEFAULT_TEXTURE, FastHashMap};
 //use notify::{self, Watcher};
 use super::shader_source;
-use std::collections::HashMap;
 use std::fs::File;
-use std::hash::BuildHasherDefault;
 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 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 {
     pub fn new(value: usize) -> FrameId {
@@ -84,23 +79,34 @@ impl TextureTarget {
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureFilter {
     Nearest,
     Linear,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum VertexFormat {
-    Triangles,
-    DebugFont,
-    DebugColor,
-    Blur,
-    Clip,
+#[derive(Debug)]
+pub enum VertexAttributeKind {
+    F32,
+    U8Norm,
+    I32,
+}
+
+#[derive(Debug)]
+pub struct VertexAttribute {
+    pub name: &'static str,
+    pub count: u32,
+    pub kind: VertexAttributeKind,
+}
+
+#[derive(Debug)]
+pub struct VertexDescriptor {
+    pub vertex_attributes: &'static [VertexAttribute],
+    pub instance_attributes: &'static [VertexAttribute],
 }
 
 enum FBOTarget {
     Read,
     Draw,
 }
 
 pub fn get_gl_format_bgra(gl: &gl::Gl) -> gl::GLuint {
@@ -142,156 +148,107 @@ fn get_shader_source(shader_name: &str, 
     get_optional_shader_source(shader_name, base_path)
         .expect(&format!("Couldn't get required shader: {}", shader_name))
 }
 
 pub trait FileWatcherHandler : Send {
     fn file_changed(&self, path: PathBuf);
 }
 
-impl VertexFormat {
-    fn bind(&self, gl: &gl::Gl, main: VBOId, instance: VBOId, offset: gl::GLuint, instance_stride: gl::GLint) {
+impl VertexAttributeKind {
+    fn size_in_bytes(&self) -> u32 {
+        match *self {
+            VertexAttributeKind::F32 => 4,
+            VertexAttributeKind::U8Norm => 1,
+            VertexAttributeKind::I32 => 4,
+        }
+    }
+}
+
+impl VertexAttribute {
+    fn size_in_bytes(&self) -> u32 {
+        self.count * self.kind.size_in_bytes()
+    }
+
+    fn bind_to_vao(&self,
+                   attr_index: gl::GLuint,
+                   divisor: gl::GLuint,
+                   stride: gl::GLint,
+                   offset: gl::GLuint,
+                   gl: &gl::Gl) {
+        gl.enable_vertex_attrib_array(attr_index);
+        gl.vertex_attrib_divisor(attr_index, divisor);
+
+        match self.kind {
+            VertexAttributeKind::F32 => {
+                gl.vertex_attrib_pointer(attr_index,
+                                         self.count as gl::GLint,
+                                         gl::FLOAT,
+                                         false,
+                                         stride,
+                                         offset);
+            }
+            VertexAttributeKind::U8Norm => {
+                gl.vertex_attrib_pointer(attr_index,
+                                         self.count as gl::GLint,
+                                         gl::UNSIGNED_BYTE,
+                                         true,
+                                         stride,
+                                         offset);
+            }
+            VertexAttributeKind::I32 => {
+                gl.vertex_attrib_i_pointer(attr_index,
+                                           self.count as gl::GLint,
+                                           gl::INT,
+                                           stride,
+                                           offset);
+            }
+        }
+    }
+}
+
+impl VertexDescriptor {
+    fn bind(&self,
+            gl: &gl::Gl,
+            main: VBOId,
+            instance: VBOId) {
         main.bind(gl);
 
-        match *self {
-            VertexFormat::DebugFont => {
-                gl.enable_vertex_attrib_array(VertexAttribute::Position as gl::GLuint);
-                gl.enable_vertex_attrib_array(VertexAttribute::Color as gl::GLuint);
-                gl.enable_vertex_attrib_array(VertexAttribute::ColorTexCoord as gl::GLuint);
-
-                gl.vertex_attrib_divisor(VertexAttribute::Position as gl::GLuint, 0);
-                gl.vertex_attrib_divisor(VertexAttribute::Color as gl::GLuint, 0);
-                gl.vertex_attrib_divisor(VertexAttribute::ColorTexCoord as gl::GLuint, 0);
-
-                let vertex_stride = mem::size_of::<DebugFontVertex>() as gl::GLuint;
+        let vertex_stride: u32 = self.vertex_attributes
+                                    .iter()
+                                    .map(|attr| attr.size_in_bytes()).sum();
+        let mut vertex_offset = 0;
 
-                gl.vertex_attrib_pointer(VertexAttribute::Position as gl::GLuint,
-                                          2,
-                                          gl::FLOAT,
-                                          false,
-                                          vertex_stride as gl::GLint,
-                                          0 + vertex_stride * offset);
-                gl.vertex_attrib_pointer(VertexAttribute::Color as gl::GLuint,
-                                          4,
-                                          gl::UNSIGNED_BYTE,
-                                          true,
-                                          vertex_stride as gl::GLint,
-                                          8 + vertex_stride * offset);
-                gl.vertex_attrib_pointer(VertexAttribute::ColorTexCoord as gl::GLuint,
-                                          2,
-                                          gl::FLOAT,
-                                          false,
-                                          vertex_stride as gl::GLint,
-                                          12 + vertex_stride * offset);
-            }
-            VertexFormat::DebugColor => {
-                gl.enable_vertex_attrib_array(VertexAttribute::Position as gl::GLuint);
-                gl.enable_vertex_attrib_array(VertexAttribute::Color as gl::GLuint);
-
-                gl.vertex_attrib_divisor(VertexAttribute::Position as gl::GLuint, 0);
-                gl.vertex_attrib_divisor(VertexAttribute::Color as gl::GLuint, 0);
-
-                let vertex_stride = mem::size_of::<DebugColorVertex>() as gl::GLuint;
-
-                gl.vertex_attrib_pointer(VertexAttribute::Position as gl::GLuint,
-                                          2,
-                                          gl::FLOAT,
-                                          false,
-                                          vertex_stride as gl::GLint,
-                                          0 + vertex_stride * offset);
-                gl.vertex_attrib_pointer(VertexAttribute::Color as gl::GLuint,
-                                          4,
-                                          gl::UNSIGNED_BYTE,
-                                          true,
-                                          vertex_stride as gl::GLint,
-                                          8 + vertex_stride * offset);
-            }
-            VertexFormat::Triangles => {
-                let vertex_stride = mem::size_of::<PackedVertex>() as gl::GLuint;
-                gl.enable_vertex_attrib_array(VertexAttribute::Position as gl::GLuint);
-                gl.vertex_attrib_divisor(VertexAttribute::Position as gl::GLuint, 0);
-
-                gl.vertex_attrib_pointer(VertexAttribute::Position as gl::GLuint,
-                                          2,
-                                          gl::FLOAT,
-                                          false,
-                                          vertex_stride as gl::GLint,
-                                          0);
-
-                instance.bind(gl);
-                let mut offset = 0;
+        for (i, attr) in self.vertex_attributes.iter().enumerate() {
+            let attr_index = i as gl::GLuint;
+            attr.bind_to_vao(attr_index,
+                             0,
+                             vertex_stride as gl::GLint,
+                             vertex_offset,
+                             gl);
+            vertex_offset += attr.size_in_bytes();
+        }
 
-                for &attrib in [VertexAttribute::Data0,
-                                VertexAttribute::Data1,
-                               ].into_iter() {
-                    gl.enable_vertex_attrib_array(attrib as gl::GLuint);
-                    gl.vertex_attrib_divisor(attrib as gl::GLuint, 1);
-                    gl.vertex_attrib_i_pointer(attrib as gl::GLuint,
-                                                4,
-                                                gl::INT,
-                                                instance_stride,
-                                                offset);
-                    offset += 16;
-                }
-            }
-            VertexFormat::Blur => {
-                let vertex_stride = mem::size_of::<PackedVertex>() as gl::GLuint;
-                gl.enable_vertex_attrib_array(BlurAttribute::Position as gl::GLuint);
-                gl.vertex_attrib_divisor(BlurAttribute::Position as gl::GLuint, 0);
+        if !self.instance_attributes.is_empty() {
+            instance.bind(gl);
+            let instance_stride: u32 = self.instance_attributes
+                                           .iter()
+                                           .map(|attr| attr.size_in_bytes()).sum();
+            let mut instance_offset = 0;
 
-                gl.vertex_attrib_pointer(BlurAttribute::Position as gl::GLuint,
-                                          2,
-                                          gl::FLOAT,
-                                          false,
-                                          vertex_stride as gl::GLint,
-                                          0);
-
-                instance.bind(gl);
+            let base_attr = self.vertex_attributes.len() as u32;
 
-                for (i, &attrib) in [BlurAttribute::RenderTaskIndex,
-                                     BlurAttribute::SourceTaskIndex,
-                                     BlurAttribute::Direction,
-                                    ].into_iter().enumerate() {
-                    gl.enable_vertex_attrib_array(attrib as gl::GLuint);
-                    gl.vertex_attrib_divisor(attrib as gl::GLuint, 1);
-                    gl.vertex_attrib_i_pointer(attrib as gl::GLuint,
-                                                1,
-                                                gl::INT,
-                                                instance_stride,
-                                                (i * 4) as gl::GLuint);
-                }
-            }
-            VertexFormat::Clip => {
-                let vertex_stride = mem::size_of::<PackedVertex>() as gl::GLuint;
-                gl.enable_vertex_attrib_array(ClipAttribute::Position as gl::GLuint);
-                gl.vertex_attrib_divisor(ClipAttribute::Position as gl::GLuint, 0);
-
-                gl.vertex_attrib_pointer(ClipAttribute::Position as gl::GLuint,
-                                          2,
-                                          gl::FLOAT,
-                                          false,
-                                          vertex_stride as gl::GLint,
-                                          0);
-
-                instance.bind(gl);
-
-                for (i, &attrib) in [ClipAttribute::RenderTaskIndex,
-                                     ClipAttribute::LayerIndex,
-                                     ClipAttribute::DataIndex,
-                                     ClipAttribute::SegmentIndex,
-                                     ClipAttribute::ResourceAddress,
-                                    ].into_iter().enumerate() {
-                    gl.enable_vertex_attrib_array(attrib as gl::GLuint);
-                    gl.vertex_attrib_divisor(attrib as gl::GLuint, 1);
-                    gl.vertex_attrib_i_pointer(attrib as gl::GLuint,
-                                                1,
-                                                gl::INT,
-                                                instance_stride,
-                                                (i * 4) as gl::GLuint);
-                }
+            for (i, attr) in self.instance_attributes.iter().enumerate() {
+                let attr_index = base_attr + i as u32;
+                attr.bind_to_vao(attr_index,
+                                 1,
+                                 instance_stride as gl::GLint,
+                                 instance_offset,
+                                 gl);
+                instance_offset += attr.size_in_bytes();
             }
         }
     }
 }
 
 impl TextureId {
     pub fn bind(&self, gl: &gl::Gl) {
         gl.bind_texture(self.target, self.name);
@@ -309,40 +266,28 @@ impl TextureId {
             name: 0,
             target: gl::TEXTURE_2D,
         }
     }
 
     pub fn is_valid(&self) -> bool { *self != TextureId::invalid() }
 }
 
-impl ProgramId {
-    fn bind(&self, gl: &gl::Gl) {
-        gl.use_program(self.0);
-    }
-}
-
 impl VBOId {
     fn bind(&self, gl: &gl::Gl) {
         gl.bind_buffer(gl::ARRAY_BUFFER, self.0);
     }
 }
 
 impl IBOId {
     fn bind(&self, gl: &gl::Gl) {
         gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.0);
     }
 }
 
-impl UBOId {
-    fn _bind(&self, gl: &gl::Gl) {
-        gl.bind_buffer(gl::UNIFORM_BUFFER, self.0);
-    }
-}
-
 impl FBOId {
     fn bind(&self, gl: &gl::Gl, target: FBOTarget) {
         let target = match target {
             FBOTarget::Read => gl::READ_FRAMEBUFFER,
             FBOTarget::Draw => gl::DRAW_FRAMEBUFFER,
         };
         gl.bind_framebuffer(target, self.0);
     }
@@ -366,80 +311,62 @@ impl Drop for Texture {
         if !self.fbo_ids.is_empty() {
             let fbo_ids: Vec<_> = self.fbo_ids.iter().map(|&FBOId(fbo_id)| fbo_id).collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
         self.gl.delete_textures(&[self.id]);
     }
 }
 
-struct Program {
-    gl: Rc<gl::Gl>,
+pub struct Program {
     id: gl::GLuint,
     u_transform: gl::GLint,
     u_device_pixel_ratio: gl::GLint,
     name: String,
     vs_source: String,
     fs_source: String,
     prefix: Option<String>,
     vs_id: Option<gl::GLuint>,
     fs_id: Option<gl::GLuint>,
 }
 
 impl Program {
     fn attach_and_bind_shaders(&mut self,
                                vs_id: gl::GLuint,
                                fs_id: gl::GLuint,
-                               vertex_format: VertexFormat) -> Result<(), ShaderError> {
-        self.gl.attach_shader(self.id, vs_id);
-        self.gl.attach_shader(self.id, fs_id);
-
-        match vertex_format {
-            VertexFormat::Triangles |
-            VertexFormat::DebugFont |
-            VertexFormat::DebugColor => {
-                self.gl.bind_attrib_location(self.id, VertexAttribute::Position as gl::GLuint, "aPosition");
-                self.gl.bind_attrib_location(self.id, VertexAttribute::Color as gl::GLuint, "aColor");
-                self.gl.bind_attrib_location(self.id, VertexAttribute::ColorTexCoord as gl::GLuint, "aColorTexCoord");
+                               descriptor: &VertexDescriptor,
+                               gl: &gl::Gl) -> Result<(), ShaderError> {
+        gl.attach_shader(self.id, vs_id);
+        gl.attach_shader(self.id, fs_id);
 
-                self.gl.bind_attrib_location(self.id, VertexAttribute::Data0 as gl::GLuint, "aData0");
-                self.gl.bind_attrib_location(self.id, VertexAttribute::Data1 as gl::GLuint, "aData1");
-            }
-            VertexFormat::Blur => {
-                self.gl.bind_attrib_location(self.id, BlurAttribute::Position as gl::GLuint, "aPosition");
-                self.gl.bind_attrib_location(self.id, BlurAttribute::RenderTaskIndex as gl::GLuint, "aBlurRenderTaskIndex");
-                self.gl.bind_attrib_location(self.id, BlurAttribute::SourceTaskIndex as gl::GLuint, "aBlurSourceTaskIndex");
-                self.gl.bind_attrib_location(self.id, BlurAttribute::Direction as gl::GLuint, "aBlurDirection");
-            }
-            VertexFormat::Clip => {
-                self.gl.bind_attrib_location(self.id, ClipAttribute::Position as gl::GLuint, "aPosition");
-                self.gl.bind_attrib_location(self.id, ClipAttribute::RenderTaskIndex as gl::GLuint, "aClipRenderTaskIndex");
-                self.gl.bind_attrib_location(self.id, ClipAttribute::LayerIndex as gl::GLuint, "aClipLayerIndex");
-                self.gl.bind_attrib_location(self.id, ClipAttribute::DataIndex as gl::GLuint, "aClipDataIndex");
-                self.gl.bind_attrib_location(self.id, ClipAttribute::SegmentIndex as gl::GLuint, "aClipSegmentIndex");
-                self.gl.bind_attrib_location(self.id, ClipAttribute::ResourceAddress as gl::GLuint, "aClipResourceAddress");
-            }
+        for (i, attr) in descriptor.vertex_attributes
+                                   .iter()
+                                   .chain(descriptor.instance_attributes.iter())
+                                   .enumerate() {
+            gl.bind_attrib_location(self.id,
+                                    i as gl::GLuint,
+                                    attr.name);
         }
 
-        self.gl.link_program(self.id);
-        if self.gl.get_program_iv(self.id, gl::LINK_STATUS) == (0 as gl::GLint) {
-            let error_log = self.gl.get_program_info_log(self.id);
+        gl.link_program(self.id);
+        if gl.get_program_iv(self.id, gl::LINK_STATUS) == (0 as gl::GLint) {
+            let error_log = gl.get_program_info_log(self.id);
             println!("Failed to link shader program: {:?}\n{}", self.name, error_log);
-            self.gl.detach_shader(self.id, vs_id);
-            self.gl.detach_shader(self.id, fs_id);
+            gl.detach_shader(self.id, vs_id);
+            gl.detach_shader(self.id, fs_id);
             return Err(ShaderError::Link(self.name.clone(), error_log));
         }
 
         Ok(())
     }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
-        self.gl.delete_program(self.id);
+        debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
 struct VAO {
     gl: Rc<gl::Gl>,
     id: gl::GLuint,
     ibo_id: IBOId,
     main_vbo_id: VBOId,
@@ -469,37 +396,31 @@ impl Drop for VAO {
 
 #[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 ProgramId(pub 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);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 struct IBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-pub struct UBOId(gl::GLuint);
-
-#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct PBOId(gl::GLuint);
 
 const MAX_EVENTS_PER_FRAME: usize = 256;
 const MAX_PROFILE_FRAMES: usize = 4;
 
 pub trait NamedTag {
     fn get_label(&self) -> &str;
 }
@@ -834,50 +755,48 @@ impl FileWatcherThread {
 
     fn add_watch(&self, path: PathBuf) {
         self.api_tx.send(FileWatcherCmd::AddWatch(path)).ok();
     }
 }
 */
 
 pub struct Capabilities {
-    pub max_ubo_size: usize,
     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: ProgramId,
+    bound_program: gl::GLuint,
     bound_vao: VAOId,
     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: HashMap<TextureId, Texture, BuildHasherDefault<FnvHasher>>,
-    programs: HashMap<ProgramId, Program, BuildHasherDefault<FnvHasher>>,
-    vaos: HashMap<VAOId, VAO, BuildHasherDefault<FnvHasher>>,
+    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,
@@ -893,44 +812,41 @@ 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_ubo_size = gl.get_integer_v(gl::MAX_UNIFORM_BLOCK_SIZE) as usize;
         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 {
-                max_ubo_size,
                 supports_multisampling: false, //TODO
             },
 
             bound_textures: [ TextureId::invalid(); 16 ],
-            bound_program: ProgramId(0),
+            bound_program: 0,
             bound_vao: VAOId(0),
             bound_pbo: PBOId(0),
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             default_read_fbo: 0,
             default_draw_fbo: 0,
 
-            textures: HashMap::default(),
-            programs: HashMap::default(),
-            vaos: HashMap::default(),
+            textures: FastHashMap::default(),
+            vaos: FastHashMap::default(),
 
             shader_preamble,
 
             next_vao_id: 1,
             //file_watcher: file_watcher,
 
             max_texture_size,
             frame_id: FrameId(0),
@@ -999,17 +915,17 @@ impl Device {
         // Texture state
         for i in 0..self.bound_textures.len() {
             self.bound_textures[i] = TextureId::invalid();
             self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
             self.gl.bind_texture(gl::TEXTURE_2D, 0);
         }
 
         // Shader state
-        self.bound_program = ProgramId(0);
+        self.bound_program = 0;
         self.gl.use_program(0);
 
         // Vertex state
         self.bound_vao = VAOId(0);
         self.clear_vertex_array();
 
         // FBO state
         self.bound_read_fbo = FBOId(self.default_read_fbo);
@@ -1067,30 +983,23 @@ impl Device {
             fbo_id.bind(self.gl(), FBOTarget::Draw);
         }
 
         if let Some(dimensions) = dimensions {
             self.gl.viewport(0, 0, dimensions.width as gl::GLint, dimensions.height as gl::GLint);
         }
     }
 
-    pub fn bind_program(&mut self,
-                        program_id: ProgramId,
-                        projection: &Transform3D<f32>) {
+    pub fn bind_program(&mut self, program: &Program) {
         debug_assert!(self.inside_frame);
 
-        if self.bound_program != program_id {
-            self.bound_program = program_id;
-            program_id.bind(&*self.gl);
+        if self.bound_program != program.id {
+            self.gl.use_program(program.id);
+            self.bound_program = program.id;
         }
-
-        let program = self.programs.get(&program_id).unwrap();
-        self.set_uniforms(program,
-                          projection,
-                          self.device_pixel_ratio);
     }
 
     pub fn create_texture_ids(&mut self,
                               count: i32,
                               target: TextureTarget) -> Vec<TextureId> {
         let id_list = self.gl.gen_textures(count);
         let mut texture_ids = Vec::new();
 
@@ -1431,25 +1340,33 @@ impl Device {
         texture.format = ImageFormat::Invalid;
         texture.width = 0;
         texture.height = 0;
     }
 
     pub fn create_program(&mut self,
                           base_filename: &str,
                           include_filename: &str,
-                          vertex_format: VertexFormat) -> Result<ProgramId, ShaderError> {
-        self.create_program_with_prefix(base_filename, &[include_filename], None, vertex_format)
+                          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) {
+        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>,
-                                      vertex_format: VertexFormat) -> Result<ProgramId, ShaderError> {
+                                      descriptor: &VertexDescriptor) -> Result<Program, ShaderError> {
         debug_assert!(self.inside_frame);
 
         let pid = self.gl.create_program();
 
         let mut vs_name = String::from(base_filename);
         vs_name.push_str(".vs");
         let mut fs_name = String::from(base_filename);
         fs_name.push_str(".fs");
@@ -1459,47 +1376,39 @@ impl Device {
             let src = get_shader_source(inc_filename, &self.resource_override_path);
             include.push_str(&src);
         }
 
         if let Some(shared_src) = get_optional_shader_source(base_filename, &self.resource_override_path) {
             include.push_str(&shared_src);
         }
 
-        let program = Program {
-            gl: Rc::clone(&self.gl),
+        let mut program = Program {
             name: base_filename.to_owned(),
             id: pid,
             u_transform: -1,
             u_device_pixel_ratio: -1,
             vs_source: get_shader_source(&vs_name, &self.resource_override_path),
             fs_source: get_shader_source(&fs_name, &self.resource_override_path),
             prefix,
             vs_id: None,
             fs_id: None,
         };
 
-        let program_id = ProgramId(pid);
+        try!{ self.load_program(&mut program, include, descriptor) };
 
-        debug_assert!(self.programs.contains_key(&program_id) == false);
-        self.programs.insert(program_id, program);
-
-        try!{ self.load_program(program_id, include, vertex_format) };
-
-        Ok(program_id)
+        Ok(program)
     }
 
     fn load_program(&mut self,
-                    program_id: ProgramId,
+                    program: &mut Program,
                     include: String,
-                    vertex_format: VertexFormat) -> Result<(), ShaderError> {
+                    descriptor: &VertexDescriptor) -> Result<(), ShaderError> {
         debug_assert!(self.inside_frame);
 
-        let program = self.programs.get_mut(&program_id).unwrap();
-
         let mut vs_preamble = Vec::new();
         let mut fs_preamble = Vec::new();
 
         vs_preamble.push("#define WR_VERTEX_SHADER\n".to_owned());
         fs_preamble.push("#define WR_FRAGMENT_SHADER\n".to_owned());
 
         if let Some(ref prefix) = program.prefix {
             vs_preamble.push(prefix.clone());
@@ -1527,19 +1436,19 @@ impl Device {
         if let Some(vs_id) = program.vs_id {
             self.gl.detach_shader(program.id, vs_id);
         }
 
         if let Some(fs_id) = program.fs_id {
             self.gl.detach_shader(program.id, fs_id);
         }
 
-        if let Err(bind_error) = program.attach_and_bind_shaders(vs_id, fs_id, vertex_format) {
+        if let Err(bind_error) = program.attach_and_bind_shaders(vs_id, fs_id, descriptor, &*self.gl) {
             if let (Some(vs_id), Some(fs_id)) = (program.vs_id, program.fs_id) {
-                try! { program.attach_and_bind_shaders(vs_id, fs_id, vertex_format) };
+                try! { program.attach_and_bind_shaders(vs_id, fs_id, descriptor, &*self.gl) };
             } else {
                return Err(bind_error);
             }
         } else {
             if let Some(vs_id) = program.vs_id {
                 self.gl.delete_shader(vs_id);
             }
 
@@ -1549,17 +1458,17 @@ impl Device {
 
             program.vs_id = Some(vs_id);
             program.fs_id = Some(fs_id);
         }
 
         program.u_transform = self.gl.get_uniform_location(program.id, "uTransform");
         program.u_device_pixel_ratio = self.gl.get_uniform_location(program.id, "uDevicePixelRatio");
 
-        program_id.bind(&*self.gl);
+        self.bind_program(program);
         let u_color_0 = self.gl.get_uniform_location(program.id, "sColor0");
         if u_color_0 != -1 {
             self.gl.uniform_1i(u_color_0, TextureSampler::Color0 as i32);
         }
         let u_color1 = self.gl.get_uniform_location(program.id, "sColor1");
         if u_color1 != -1 {
             self.gl.uniform_1i(u_color1, TextureSampler::Color1 as i32);
         }
@@ -1630,36 +1539,34 @@ impl Device {
             }
         }
 
         for program_id in programs_to_update {
             self.load_program(program_id, false);
         }
     }*/
 
-    pub fn get_uniform_location(&self, program_id: ProgramId, name: &str) -> UniformLocation {
-        let ProgramId(program_id) = program_id;
-        UniformLocation(self.gl.get_uniform_location(program_id, name))
+    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);
     }
 
-    fn set_uniforms(&self,
-                    program: &Program,
-                    transform: &Transform3D<f32>,
-                    device_pixel_ratio: f32) {
+    pub fn set_uniforms(&self,
+                        program: &Program,
+                        transform: &Transform3D<f32>) {
         debug_assert!(self.inside_frame);
         self.gl.uniform_matrix_4fv(program.u_transform,
-                               false,
-                               &transform.to_row_major_array());
-        self.gl.uniform_1f(program.u_device_pixel_ratio, device_pixel_ratio);
+                                   false,
+                                   &transform.to_row_major_array());
+        self.gl.uniform_1f(program.u_device_pixel_ratio, self.device_pixel_ratio);
     }
 
     pub fn create_pbo(&mut self) -> PBOId {
         let id = self.gl.gen_buffers(1)[0];
         PBOId(id)
     }
 
     pub fn destroy_pbo(&mut self, id: PBOId) {
@@ -1792,34 +1699,33 @@ impl Device {
             self.bound_vao = vao_id;
 
             let VAOId(id) = vao_id;
             self.gl.bind_vertex_array(id);
         }
     }
 
     fn create_vao_with_vbos(&mut self,
-                            format: VertexFormat,
+                            descriptor: &VertexDescriptor,
                             main_vbo_id: VBOId,
                             instance_vbo_id: VBOId,
                             ibo_id: IBOId,
-                            vertex_offset: gl::GLuint,
                             instance_stride: gl::GLint,
                             owns_vertices: bool,
                             owns_instances: bool,
                             owns_indices: bool)
                             -> VAOId {
         debug_assert!(self.inside_frame);
 
         let vao_ids = self.gl.gen_vertex_arrays(1);
         let vao_id = vao_ids[0];
 
         self.gl.bind_vertex_array(vao_id);
 
-        format.bind(self.gl(), main_vbo_id, instance_vbo_id, vertex_offset, instance_stride);
+        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,
@@ -1834,39 +1740,57 @@ impl Device {
         let vao_id = VAOId(vao_id);
 
         debug_assert!(!self.vaos.contains_key(&vao_id));
         self.vaos.insert(vao_id, vao);
 
         vao_id
     }
 
-    pub fn create_vao(&mut self, format: VertexFormat, inst_stride: gl::GLint) -> VAOId {
+    pub fn create_vao(&mut self,
+                      descriptor: &VertexDescriptor,
+                      inst_stride: gl::GLint) -> VAOId {
         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(format, main_vbo_id, intance_vbo_id, ibo_id, 0, inst_stride, true, true, true)
+        self.create_vao_with_vbos(descriptor,
+                                  main_vbo_id,
+                                  intance_vbo_id,
+                                  ibo_id,
+                                  inst_stride,
+                                  true,
+                                  true,
+                                  true)
     }
 
-    pub fn create_vao_with_new_instances(&mut self, format: VertexFormat, inst_stride: gl::GLint,
+    pub fn create_vao_with_new_instances(&mut self,
+                                         descriptor: &VertexDescriptor,
+                                         inst_stride: gl::GLint,
                                          base_vao: VAOId) -> VAOId {
         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(format, main_vbo_id, intance_vbo_id, ibo_id, 0, inst_stride, false, true, false)
+        self.create_vao_with_vbos(descriptor,
+                                  main_vbo_id,
+                                  intance_vbo_id,
+                                  ibo_id,
+                                  inst_stride,
+                                  false,
+                                  true,
+                                  false)
     }
 
     pub fn update_vao_main_vertices<V>(&mut self,
                                        vao_id: VAOId,
                                        vertices: &[V],
                                        usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
 
@@ -1949,54 +1873,16 @@ impl Device {
             self.gl.bind_texture(gl::TEXTURE_2D, 0);
         }
 
         self.gl.active_texture(gl::TEXTURE0);
 
         self.frame_id.0 += 1;
     }
 
-    pub fn assign_ubo_binding(&self, program_id: ProgramId, name: &str, value: u32) -> u32 {
-        let index = self.gl.get_uniform_block_index(program_id.0, name);
-        self.gl.uniform_block_binding(program_id.0, index, value);
-        index
-    }
-
-    pub fn create_ubo<T>(&self, data: &[T], binding: u32) -> UBOId {
-        let ubo = self.gl.gen_buffers(1)[0];
-        self.gl.bind_buffer(gl::UNIFORM_BUFFER, ubo);
-        gl::buffer_data(self.gl(), gl::UNIFORM_BUFFER, data, gl::STATIC_DRAW);
-        self.gl.bind_buffer_base(gl::UNIFORM_BUFFER, binding, ubo);
-        UBOId(ubo)
-    }
-
-    pub fn reset_ubo(&self, binding: u32) {
-        self.gl.bind_buffer(gl::UNIFORM_BUFFER, 0);
-        self.gl.bind_buffer_base(gl::UNIFORM_BUFFER, binding, 0);
-    }
-
-    pub fn delete_buffer(&self, buffer: UBOId) {
-        self.gl.delete_buffers(&[buffer.0]);
-    }
-
-    #[cfg(target_os = "android")]
-    pub fn set_multisample(&self, enable: bool) {
-    }
-
-    #[cfg(not(target_os = "android"))]
-    pub fn set_multisample(&self, enable: bool) {
-        if self.capabilities.supports_multisampling {
-            if enable {
-                self.gl.enable(gl::MULTISAMPLE);
-            } else {
-                self.gl.disable(gl::MULTISAMPLE);
-            }
-        }
-    }
-
     pub fn clear_target(&self,
                         color: Option<[f32; 4]>,
                         depth: Option<f32>) {
         let mut clear_bits = 0;
 
         if let Some(color) = color {
             self.gl.clear_color(color[0], color[1], color[2], color[3]);
             clear_bits |= gl::COLOR_BUFFER_BIT;
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -6,27 +6,23 @@ use api::{BuiltDisplayList, BuiltDisplay
 use api::{ComplexClipRegion, DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
 use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
 use api::{LayerVector2D, LayoutSize, LayoutTransform, LocalClip, MixBlendMode, PipelineId};
 use api::{PropertyBinding, ScrollClamping, ScrollEventPhase, ScrollLayerState, ScrollLocation};
 use api::{ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext, TileOffset};
 use api::{TransformStyle, WorldPoint};
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
-use fnv::FnvHasher;
 use gpu_cache::GpuCache;
-use internal_types::{RendererFrame};
+use internal_types::{FastHashMap, RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use mask_cache::ClipRegion;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
-use resource_cache::ResourceCache;
+use resource_cache::{ResourceCache, TiledImageMap};
 use scene::{Scene, SceneProperties};
-use std::cmp;
-use std::collections::HashMap;
-use std::hash::BuildHasherDefault;
 use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
 
@@ -86,31 +82,31 @@ impl NestedDisplayListInfo {
             self.convert_id_to_nested(id)
         }
     }
 }
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: &'a mut FrameBuilder,
-    resource_cache: &'a mut ResourceCache,
+    tiled_image_map: TiledImageMap,
     replacements: Vec<(ClipId, ClipId)>,
     nested_display_list_info: Vec<NestedDisplayListInfo>,
     current_nested_display_list_index: u64,
 }
 
 impl<'a> FlattenContext<'a> {
     fn new(scene: &'a Scene,
            builder: &'a mut FrameBuilder,
-           resource_cache: &'a mut ResourceCache)
+           resource_cache: &ResourceCache)
            -> FlattenContext<'a> {
         FlattenContext {
             scene,
             builder,
-            resource_cache,
+            tiled_image_map: resource_cache.get_tiled_image_map(),
             replacements: Vec::new(),
             nested_display_list_info: Vec::new(),
             current_nested_display_list_index: 0,
         }
     }
 
     fn push_nested_display_list_ids(&mut self, info: ClipAndScrollInfo) {
         self.current_nested_display_list_index += 1;
@@ -172,17 +168,17 @@ impl<'a> FlattenContext<'a> {
                                 .get(complex_clips)
                                 .collect()
     }
 }
 
 // TODO: doc
 pub struct Frame {
     pub clip_scroll_tree: ClipScrollTree,
-    pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
+    pub pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
     frame_builder: Option<FrameBuilder>,
 }
 
 trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(&self,
@@ -217,17 +213,17 @@ impl StackingContextHelpers for Stacking
         }
         filters
     }
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
         Frame {
-            pipeline_epoch_map: HashMap::default(),
+            pipeline_epoch_map: FastHashMap::default(),
             clip_scroll_tree: ClipScrollTree::new(),
             id: FrameId(0),
             frame_builder: None,
             frame_builder_config: config,
         }
     }
 
     pub fn reset(&mut self) -> ScrollStates {
@@ -534,29 +530,26 @@ impl Frame {
         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) => {
-                let image = context.resource_cache.get_image_properties(info.image_key);
-                if let Some(tile_size) = image.tiling {
+                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.
-                    let image_size = DeviceUintSize::new(image.descriptor.width,
-                                                         image.descriptor.height);
                     self.decompose_image(clip_and_scroll,
-                                         context,
+                                         &mut context.builder,
                                          &item_rect_with_offset,
                                          &clip_with_offset,
                                          info,
-                                         image_size,
-                                         tile_size as u32);
+                                         tiling.image_size,
+                                         tiling.tile_size as u32);
                 } else {
                     context.builder.add_image(clip_and_scroll,
                                               item_rect_with_offset,
                                               &clip_with_offset,
                                               &info.stretch_size,
                                               &info.tile_spacing,
                                               None,
                                               info.image_key,
@@ -873,27 +866,27 @@ impl Frame {
     /// decomposition. This lets us generate the minimum amount of primitives by, for  example,
     /// decompositing the repetition horizontally while repeating vertically in the shader (for
     /// an image where the width is too bug but the height is not).
     ///
     /// decompose_image and decompose_image_row handle image repetitions while decompose_tiled_image
     /// takes care of the decomposition required by the internal tiling of the image.
     fn decompose_image(&mut self,
                        clip_and_scroll: ClipAndScrollInfo,
-                       context: &mut FlattenContext,
+                       builder: &mut FrameBuilder,
                        item_rect: &LayerRect,
                        item_local_clip: &LocalClip,
                        info: &ImageDisplayItem,
                        image_size: DeviceUintSize,
                        tile_size: u32) {
         let no_vertical_tiling = image_size.height <= tile_size;
         let no_vertical_spacing = info.tile_spacing.height == 0.0;
         if no_vertical_tiling && no_vertical_spacing {
             self.decompose_image_row(clip_and_scroll,
-                                     context,
+                                     builder,
                                      item_rect,
                                      item_local_clip,
                                      info,
                                      image_size,
                                      tile_size);
             return;
         }
 
@@ -903,39 +896,39 @@ impl Frame {
         for i in 0..num_repetitions {
             if let Some(row_rect) = rect(
                 item_rect.origin.x,
                 item_rect.origin.y + (i as f32) * layout_stride,
                 item_rect.size.width,
                 info.stretch_size.height
             ).intersection(item_rect) {
                 self.decompose_image_row(clip_and_scroll,
-                                         context,
+                                         builder,
                                          &row_rect,
                                          item_local_clip,
                                          info,
                                          image_size,
                                          tile_size);
             }
         }
     }
 
     fn decompose_image_row(&mut self,
                            clip_and_scroll: ClipAndScrollInfo,
-                           context: &mut FlattenContext,
+                           builder: &mut FrameBuilder,
                            item_rect: &LayerRect,
                            item_local_clip: &LocalClip,
                            info: &ImageDisplayItem,
                            image_size: DeviceUintSize,
                            tile_size: u32) {
         let no_horizontal_tiling = image_size.width <= tile_size;
         let no_horizontal_spacing = info.tile_spacing.width == 0.0;
         if no_horizontal_tiling && no_horizontal_spacing {
             self.decompose_tiled_image(clip_and_scroll,
-                                       context,
+                                       builder,
                                        item_rect,
                                        item_local_clip,
                                        info,
                                        image_size,
                                        tile_size);
             return;
         }
 
@@ -945,29 +938,29 @@ impl Frame {
         for i in 0..num_repetitions {
             if let Some(decomposed_rect) = rect(
                 item_rect.origin.x + (i as f32) * layout_stride,
                 item_rect.origin.y,
                 info.stretch_size.width,
                 item_rect.size.height,
             ).intersection(item_rect) {
                 self.decompose_tiled_image(clip_and_scroll,
-                                           context,
+                                           builder,
                                            &decomposed_rect,
                                            item_local_clip,
                                            info,
                                            image_size,
                                            tile_size);
             }
         }
     }
 
     fn decompose_tiled_image(&mut self,
                              clip_and_scroll: ClipAndScrollInfo,
-                             context: &mut FlattenContext,
+                             builder: &mut FrameBuilder,
                              item_rect: &LayerRect,
                              item_local_clip: &LocalClip,
                              info: &ImageDisplayItem,
                              image_size: DeviceUintSize,
                              tile_size: u32) {
         // The image resource is tiled. We have to generate an image primitive
         // for each tile.
         // We need to do this because the image is broken up into smaller tiles in the texture
@@ -1000,24 +993,24 @@ impl Frame {
         // Apparently web authors do that...
 
         let mut repeat_x = false;
         let mut repeat_y = false;
 
         if info.stretch_size.width < item_rect.size.width {
             // If this assert blows up it means we haven't properly decomposed the image in decompose_image_row.
             debug_assert!(image_size.width <= tile_size);
-            // we don't actually tile in this dimmension so repeating can be done in the shader.
+            // we don't actually tile in this dimension so repeating can be done in the shader.
             repeat_x = true;
         }
 
         if info.stretch_size.height < item_rect.size.height {
             // If this assert blows up it means we haven't properly decomposed the image in decompose_image.
             debug_assert!(image_size.height <= tile_size);
-            // we don't actually tile in this dimmension so repeating can be done in the shader.
+            // we don't actually tile in this dimension so repeating can be done in the shader.
             repeat_y = true;
         }
 
         let tile_size_f32 = tile_size as f32;
 
         // Note: this rounds down so it excludes the partially filled tiles on the right and
         // bottom edges (we handle them separately below).
         let num_tiles_x = (image_size.width / tile_size) as u16;
@@ -1036,76 +1029,76 @@ impl Frame {
         // The size in pixels of the tiles on the right and bottom edges, smaller
         // than the regular tile size if the image is not a multiple of the tile size.
         // Zero means the image size is a multiple of the tile size.
         let leftover = DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);
 
         for ty in 0..num_tiles_y {
             for tx in 0..num_tiles_x {
                 self.add_tile_primitive(clip_and_scroll,
-                                        context,
+                                        builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(tx, ty),
                                         stretched_tile_size,
                                         1.0, 1.0,
                                         repeat_x, repeat_y);
             }
             if leftover.width != 0 {
                 // Tiles on the right edge that are smaller than the tile size.
                 self.add_tile_primitive(clip_and_scroll,
-                                        context,
+                                        builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(num_tiles_x, ty),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         1.0,
                                         repeat_x, repeat_y);
             }
         }
 
         if leftover.height != 0 {
             for tx in 0..num_tiles_x {
                 // Tiles on the bottom edge that are smaller than the tile size.
                 self.add_tile_primitive(clip_and_scroll,
-                                        context,
+                                        builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(tx, num_tiles_y),
                                         stretched_tile_size,
                                         1.0,
                                         (leftover.height as f32) / tile_size_f32,
                                         repeat_x,
                                         repeat_y);
             }
 
             if leftover.width != 0 {
                 // Finally, the bottom-right tile with a "leftover" size.
                 self.add_tile_primitive(clip_and_scroll,
-                                        context,
+                                        builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(num_tiles_x, num_tiles_y),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         (leftover.height as f32) / tile_size_f32,
                                         repeat_x,
                                         repeat_y);
             }
         }
     }
 
     fn add_tile_primitive(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
-                          context: &mut FlattenContext,
+                          builder: &mut FrameBuilder,
                           item_rect: &LayerRect,
                           item_local_clip: &LocalClip,
                           info: &ImageDisplayItem,
                           tile_offset: TileOffset,
                           stretched_tile_size: LayerSize,
                           tile_ratio_width: f32,
                           tile_ratio_height: f32,
                           repeat_x: bool,
@@ -1139,25 +1132,25 @@ impl Frame {
 
         if repeat_y {
             assert_eq!(tile_offset.y, 0);
             prim_rect.size.height = item_rect.size.height;
         }
 
         // Fix up the primitive's rect if it overflows the original item rect.
         if let Some(prim_rect) = prim_rect.intersection(item_rect) {
-            context.builder.add_image(clip_and_scroll,
-                                      prim_rect,
-                                      item_local_clip,
-                                      &stretched_size,
-                                      &info.tile_spacing,
-                                      None,
-                                      info.image_key,
-                                      info.image_rendering,
-                                      Some(tile_offset));
+            builder.add_image(clip_and_scroll,
+                              prim_rect,
+                              item_local_clip,
+                              &stretched_size,
+                              &info.tile_spacing,
+                              None,
+                              info.image_key,
+                              info.image_rendering,
+                              Some(tile_offset));
         }
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  gpu_cache: &mut GpuCache,
                  display_lists: &DisplayListMap,
                  device_pixel_ratio: f32,
@@ -1167,20 +1160,16 @@ impl Frame {
                  -> RendererFrame {
         self.clip_scroll_tree.update_all_node_transforms(pan);
         let frame = self.build_frame(resource_cache,
                                      gpu_cache,
                                      display_lists,
                                      device_pixel_ratio,
                                      texture_cache_profile,
                                      gpu_cache_profile);
-        // Expire any resources that haven't been used for `cache_expiry_frames`.
-        let num_frames_back = self.frame_builder_config.cache_expiry_frames;
-        let expiry_frame = FrameId(cmp::max(num_frames_back, self.id.0) - num_frames_back);
-        resource_cache.expire_old_resources(expiry_frame);
         frame
     }
 
     fn build_frame(&mut self,
                    resource_cache: &mut ResourceCache,
                    gpu_cache: &mut GpuCache,
                    display_lists: &DisplayListMap,
                    device_pixel_ratio: f32,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,40 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use 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};
+use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, SubpixelDirection};
 use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
 use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, TextShadow, TileOffset};
 use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
-use fnv::FnvHasher;
 use frame::FrameId;
 use gpu_cache::GpuCache;
-use internal_types::HardwareCompositeOp;
+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::{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};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
-use std::collections::HashMap;
-use std::hash::BuildHasherDefault;
 use euclid::{SideOffsets2D, vec2, vec3};
 use tiling::{ContextIsolation, StackingContextIndex};
 use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::{MatrixHelpers, RectHelpers};
 
@@ -113,19 +110,18 @@ pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
-    clip_scroll_group_indices: HashMap<ClipAndScrollInfo,
-                                       ClipScrollGroupIndex,
-                                       BuildHasherDefault<FnvHasher>>,
+    clip_scroll_group_indices: FastHashMap<ClipAndScrollInfo,
+                                           ClipScrollGroupIndex>,
     packed_layers: Vec<PackedLayer>,
 
     // A stack of the current text-shadow primitives.
     shadow_prim_stack: Vec<PrimitiveIndex>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
@@ -146,17 +142,17 @@ impl FrameBuilder {
                screen_size: DeviceUintSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
         match previous {
             Some(prev) => {
                 FrameBuilder {
                     stacking_context_store: recycle_vec(prev.stacking_context_store),
                     clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
-                    clip_scroll_group_indices: HashMap::default(),
+                    clip_scroll_group_indices: FastHashMap::default(),
                     cmds: recycle_vec(prev.cmds),
                     packed_layers: recycle_vec(prev.packed_layers),
                     shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
                     scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                     reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                     stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                     prim_store: prev.prim_store.recycle(),
                     screen_size,
@@ -164,17 +160,17 @@ impl FrameBuilder {
                     config,
                     has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
                     stacking_context_store: Vec::new(),
                     clip_scroll_group_store: Vec::new(),
-                    clip_scroll_group_indices: HashMap::default(),
+                    clip_scroll_group_indices: FastHashMap::default(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
                     shadow_prim_stack: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
                     screen_size,
@@ -903,32 +899,40 @@ impl FrameBuilder {
                 if stacking_context.composite_ops.count() > 0 {
                     normal_render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
         // Shadows never use subpixel AA, but need to respect the alpha/mono flag
         // for reftests.
-        let shadow_render_mode = match self.config.default_font_render_mode {
-            FontRenderMode::Subpixel | FontRenderMode::Alpha => FontRenderMode::Alpha,
-            FontRenderMode::Mono => FontRenderMode::Mono,
+        let (shadow_render_mode, subpx_dir) = match self.config.default_font_render_mode {
+            FontRenderMode::Subpixel | FontRenderMode::Alpha => {
+                // TODO(gw): Expose subpixel direction in API once WR supports
+                //           vertical text runs.
+                (FontRenderMode::Alpha, SubpixelDirection::Horizontal)
+            }
+            FontRenderMode::Mono => {
+                (FontRenderMode::Mono, SubpixelDirection::None)
+            }
         };
 
         let prim = TextRunPrimitiveCpu {
             font_key,
             logical_font_size: size,
             glyph_range,
             glyph_count,
-            glyph_instances: Vec::new(),
+            glyph_gpu_blocks: Vec::new(),
+            glyph_keys: Vec::new(),
             glyph_options,
             normal_render_mode,
             shadow_render_mode,
             offset: run_offset,
             color: *color,
+            subpx_dir,
         };
 
         // Text shadows that have a blur radius of 0 need to be rendered as normal
         // text elements to get pixel perfect results for reftests. It's also a big
         // performance win to avoid blurs and render target allocations where
         // possible. For any text shadows that have zero blur, create a normal text
         // primitive with the shadow's color and offset. These need to be added
         // *before* the visual text primitive in order to get the correct paint
@@ -1358,17 +1362,17 @@ impl FrameBuilder {
         // A stack of the alpha batcher tasks. We create them on the way down,
         // and then actually populate with items and dependencies on the way up.
         let mut alpha_task_stack = Vec::new();
         // A map of "preserve-3d" contexts. We are baking these into render targets
         // and only compositing once we are out of "preserve-3d" hierarchy.
         // The stacking contexts that fall into this category are
         //  - ones with `ContextIsolation::Items`, for their actual items to be backed
         //  - immediate children of `ContextIsolation::Items`
-        let mut preserve_3d_map: HashMap<StackingContextIndex, RenderTask> = HashMap::new();
+        let mut preserve_3d_map: FastHashMap<StackingContextIndex, RenderTask> = FastHashMap::default();
         // The plane splitter stack, using a simple BSP tree.
         let mut splitter_stack = Vec::new();
 
         debug!("build_render_task()");
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -0,0 +1,104 @@
+/* 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 internal_types::FastHashMap;
+use resource_cache::{Resource, ResourceClassCache};
+use texture_cache::{TextureCache, TextureCacheItemId};
+
+pub struct CachedGlyphInfo {
+    pub texture_cache_id: Option<TextureCacheItemId>,
+    pub last_access: FrameId,
+}
+
+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 {
+            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 struct GlyphCache {
+    pub glyph_key_caches: FastHashMap<FontInstanceKey, 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 {
+        self.glyph_key_caches
+            .entry(font)
+            .or_insert(ResourceClassCache::new())
+    }
+
+    pub fn get_glyph_key_cache_for_font(&self,
+                                        font: &FontInstanceKey) -> &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_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
+    where for<'r> F: Fn(&'r &FontInstanceKey) -> 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);
+        }
+    }
+}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -1,37 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[cfg(test)]
 use app_units::Au;
 use device::TextureFilter;
-use fnv::FnvHasher;
 use frame::FrameId;
+use glyph_cache::{CachedGlyphInfo, GlyphCache};
+use internal_types::FastHashSet;
 use platform::font::{FontContext, RasterizedGlyph};
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
 use rayon::prelude::*;
-use resource_cache::ResourceClassCache;
-use std::hash::BuildHasherDefault;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::collections::hash_map::Entry;
-use std::collections::HashSet;
 use std::mem;
-use texture_cache::{TextureCacheItemId, TextureCache};
+use texture_cache::TextureCache;
 #[cfg(test)]
-use api::{ColorF, FontRenderMode, IdNamespace};
-use api::{FontInstanceKey, LayoutPoint};
+use api::{ColorF, LayoutPoint, FontRenderMode, IdNamespace, SubpixelDirection};
+use api::{FontInstanceKey};
 use api::{FontKey, FontTemplate};
 use api::{ImageData, ImageDescriptor, ImageFormat};
-use api::{GlyphKey, GlyphInstance, GlyphDimensions};
-
-pub type GlyphCache = ResourceClassCache<GlyphRequest, Option<TextureCacheItemId>>;
+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>>,
 
     // This worker should be accessed by threads that don't belong to thre thread pool
     // (in theory that's only the render backend thread so no contention expected either).
@@ -74,27 +70,27 @@ impl FontContexts {
         self.workers.current_thread_index()
     }
 }
 
 pub struct GlyphRasterizer {
     workers: Arc<ThreadPool>,
     font_contexts: Arc<FontContexts>,
 
-    // Receives the rendered glyphs.
-    glyph_rx: Receiver<Vec<GlyphRasterJob>>,
-    glyph_tx: Sender<Vec<GlyphRasterJob>>,
-
     // Maintain a set of glyphs that have been requested this
     // frame. This ensures the glyph thread won't rasterize
     // the same glyph more than once in a frame. This is required
     // because the glyph cache hash table is not updated
     // until the end of the frame when we wait for glyph requests
     // to be resolved.
-    pending_glyphs: HashSet<GlyphRequest>,
+    pending_glyphs: FastHashSet<GlyphRequest>,
+
+    // Receives the rendered glyphs.
+    glyph_rx: Receiver<Vec<GlyphRasterJob>>,
+    glyph_tx: Sender<Vec<GlyphRasterJob>>,
 
     // We defer removing fonts to the end of the frame so that:
     // - this work is done outside of the critical path,
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
 }
 
@@ -112,19 +108,19 @@ impl GlyphRasterizer {
         GlyphRasterizer {
             font_contexts: Arc::new(
                 FontContexts {
                     worker_contexts: contexts,
                     shared_context: Mutex::new(FontContext::new()),
                     workers: Arc::clone(&workers),
                 }
             ),
+            pending_glyphs: FastHashSet::default(),
             glyph_rx,
             glyph_tx,
-            pending_glyphs: HashSet::new(),
             workers,
             fonts_to_remove: Vec::new(),
         }
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         let font_contexts = Arc::clone(&self.font_contexts);
         // It's important to synchronously add the font for the shared context because
@@ -146,39 +142,30 @@ impl GlyphRasterizer {
         self.fonts_to_remove.push(font_key);
     }
 
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         current_frame_id: FrameId,
         font: FontInstanceKey,
-        glyph_instances: &[GlyphInstance],
-        requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
-    ) {
+        glyph_keys: &[GlyphKey]) {
         assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
+        let mut glyphs = Vec::new();
 
-        let mut glyphs = Vec::with_capacity(glyph_instances.len());
+        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
 
         // select glyphs that have not been requested yet.
-        for glyph in glyph_instances {
-            let glyph_request = GlyphRequest::new(font.clone(),
-                                                  glyph.index,
-                                                  glyph.point);
-
-            match glyph_cache.entry(glyph_request.clone(), current_frame_id) {
-                Entry::Occupied(entry) => {
-                    if let &Some(texture_cache_item_id) = entry.get() {
-                        requested_items.insert(texture_cache_item_id);
-                    }
-                }
+        for key in glyph_keys {
+            match glyph_key_cache.entry(key.clone(), current_frame_id) {
+                Entry::Occupied(..) => {}
                 Entry::Vacant(..) => {
-                    if !self.pending_glyphs.contains(&glyph_request) {
-                        self.pending_glyphs.insert(glyph_request.clone());
-                        glyphs.push(glyph_request.clone());
+                    let request = GlyphRequest::new(&font, key);
+                    if self.pending_glyphs.insert(request.clone()) {
+                        glyphs.push(request);
                     }
                 }
             }
         }
 
         if glyphs.is_empty() {
             return;
         }
@@ -220,22 +207,22 @@ impl GlyphRasterizer {
         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,
-        requested_items: &mut HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
         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
             // of the fact that this thread is alive and we avoid the added latency
             // of blocking it.
             let raster_jobs = self.glyph_rx.recv().expect("BUG: Should be glyphs pending!");
             for job in raster_jobs {
                 debug_assert!(self.pending_glyphs.contains(&job.request));
@@ -266,24 +253,28 @@ impl GlyphRasterizer {
                             is_opaque: false,
                             offset: 0,
                         },
                         TextureFilter::Linear,
                         ImageData::Raw(Arc::new(glyph.bytes)),
                         [glyph.left, glyph.top],
                         texture_cache_profile,
                     );
-                    requested_items.insert(image_id);
                     Some(image_id)
                 } else {
                     None
                 }
             );
 
-            glyph_cache.insert(job.request, image_id, current_frame_id);
+            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,
+            });
         }
 
         // 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 || {
@@ -316,23 +307,20 @@ impl FontContext {
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 pub struct GlyphRequest {
     pub key: GlyphKey,
     pub font: FontInstanceKey,
 }
 
 impl GlyphRequest {
-    pub fn new(
-        font: FontInstanceKey,
-        index: u32,
-        point: LayoutPoint) -> Self {
+    pub fn new(font: &FontInstanceKey, key: &GlyphKey) -> Self {
         GlyphRequest {
-            key: GlyphKey::new(index, point, font.render_mode),
-            font,
+            key: key.clone(),
+            font: font.clone(),
         }
     }
 }
 
 struct GlyphRasterJob {
     request: GlyphRequest,
     result: Option<RasterizedGlyph>,
 }
@@ -344,55 +332,50 @@ 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 requested_items = HashSet::default();
 
     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 mut glyph_instances = Vec::with_capacity(200);
-    for i in 0..200 {
-        glyph_instances.push(GlyphInstance {
-            index: i, // It doesn't matter which glyphs we are actually rendering.
-            point: LayoutPoint::new(0.0, 0.0),
-        });
-    }
-
     let font = FontInstanceKey {
         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_instances[(50 * i)..(50 * (i + 1))],
-            &mut requested_items,
+            &glyph_keys[(50 * i)..(50 * (i + 1))],
         );
     }
 
     glyph_rasterizer.delete_font(font_key);
 
     glyph_rasterizer.resolve_glyphs(
         frame_id,
         &mut glyph_cache,
         &mut TextureCache::new(4096),
-        &mut requested_items,
         &mut TextureCacheProfileCounters::new(),
     );
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,25 +1,29 @@
 /* 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 fnv::FnvHasher;
+use fxhash::FxHasher;
 use profiler::BackendProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
-use api::{ClipId, ColorU, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
-use api::{DevicePoint, ImageData, ImageFormat, PipelineId};
+use api::{ClipId, DevicePoint, DeviceUintRect, DocumentId, Epoch};
+use api::{ExternalImageData, ExternalImageId};
+use api::{ImageData, 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
 // to be added to an atlas). The texture cache
 // manages the allocation and freeing of these
 // IDs, and the rendering thread maintains a
@@ -87,94 +91,16 @@ impl BatchTextures {
             colors: [SourceTexture::Invalid; 3],
         }
     }
 }
 
 // In some places we need to temporarily bind a texture to any slot.
 pub const DEFAULT_TEXTURE: TextureSampler = TextureSampler::Color0;
 
-#[derive(Clone, Copy, Debug)]
-pub enum VertexAttribute {
-    // vertex-frequency basic attributes
-    Position,
-    Color,
-    ColorTexCoord,
-    // instance-frequency primitive attributes
-    Data0,
-    Data1,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum BlurAttribute {
-    // vertex frequency
-    Position,
-    // instance frequency
-    RenderTaskIndex,
-    SourceTaskIndex,
-    Direction,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum ClipAttribute {
-    // vertex frequency
-    Position,
-    // instance frequency
-    RenderTaskIndex,
-    LayerIndex,
-    DataIndex,
-    SegmentIndex,
-    ResourceAddress,
-}
-
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub struct PackedVertex {
-    pub pos: [f32; 2],
-}
-
-#[derive(Debug)]
-#[repr(C)]
-pub struct DebugFontVertex {
-    pub x: f32,
-    pub y: f32,
-    pub color: ColorU,
-    pub u: f32,
-    pub v: f32,
-}
-
-impl DebugFontVertex {
-    pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex {
-        DebugFontVertex {
-            x,
-            y,
-            color,
-            u,
-            v,
-        }
-    }
-}
-
-#[repr(C)]
-pub struct DebugColorVertex {
-    pub x: f32,
-    pub y: f32,
-    pub color: ColorU,
-}
-
-impl DebugColorVertex {
-    pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex {
-        DebugColorVertex {
-            x,
-            y,
-            color,
-        }
-    }
-}
-
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RenderTargetMode {
     None,
     SimpleRenderTarget,
     LayerRenderTarget(i32),      // Number of texture layers
 }
 
 #[derive(Debug)]
@@ -236,39 +162,39 @@ impl TextureUpdateList {
     }
 }
 
 /// Mostly wraps a tiling::Frame, adding a bit of extra information.
 pub struct RendererFrame {
     /// The last rendered epoch for each pipeline present in the frame.
     /// This information is used to know if a certain transformation on the layout has
     /// been rendered, which is necessary for reftests.
-    pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
+    pub pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
     /// The layers that are currently affected by the over-scrolling animation.
-    pub layers_bouncing_back: HashSet<ClipId, BuildHasherDefault<FnvHasher>>,
+    pub layers_bouncing_back: FastHashSet<ClipId>,
 
     pub frame: Option<tiling::Frame>,
 }
 
 impl RendererFrame {
-    pub fn new(pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
-               layers_bouncing_back: HashSet<ClipId, BuildHasherDefault<FnvHasher>>,
+    pub fn new(pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
+               layers_bouncing_back: FastHashSet<ClipId>,
                frame: Option<tiling::Frame>)
                -> RendererFrame {
         RendererFrame {
             pipeline_epoch_map,
             layers_bouncing_back,
             frame,
         }
     }
 }
 
 pub enum ResultMsg {
     RefreshShader(PathBuf),
-    NewFrame(RendererFrame, TextureUpdateList, BackendProfileCounters),
+    NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
 }
 
 #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
 pub struct StackingContextIndex(pub usize);
 
 #[derive(Clone, Copy, Debug)]
 pub struct UvRect {
     pub uv0: DevicePoint,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -1,45 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//! A GPU based renderer for the web.
-//!
-//! It serves as an experimental render backend for [Servo](https://servo.org/),
-//! but it can also be used as such in a standalone application.
-//!
-//! # External dependencies
-//! WebRender currently depends on [FreeType](https://www.freetype.org/)
-//!
-//! # Api Structure
-//! The main entry point to WebRender is the `webrender::renderer::Renderer`.
-//!
-//! By calling `Renderer::new(...)` you get a `Renderer`, as well as a `RenderApiSender`.
-//! Your `Renderer` is responsible to render the previously processed frames onto the screen.
-//!
-//! By calling `yourRenderApiSenderInstance.create_api()`, you'll get a `RenderApi` instance,
-//! which is responsible for the processing of new frames. A worker thread is used internally to
-//! untie the workload from the application thread and therefore be able
-//! to make better use of multicore systems.
-//!
-//! What is referred to as a `frame`, is the current geometry on the screen.
-//! A new Frame is created by calling [`set_display_list()`][newframe] on the `RenderApi`.
-//! When the geometry is processed, the application will be informed via a `RenderNotifier`,
-//! a callback which you employ with [set_render_notifier][notifier] on the `Renderer`
-//! More information about [stacking contexts][stacking_contexts].
-//!
-//! `set_display_list()` also needs to be supplied with `BuiltDisplayList`s.
-//! These are obtained by finalizing a `DisplayListBuilder`. These are used to draw your geometry.
-//! But it doesn't only contain trivial geometry, it can also store another StackingContext, as
-//! they're nestable.
-//!
-//! [stacking_contexts]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
-//! [newframe]: ../webrender_api/struct.RenderApi.html#method.set_display_list
-//! [notifier]: renderer/struct.Renderer.html#method.set_render_notifier
+/*!
+A GPU based renderer for the web.
+
+It serves as an experimental render backend for [Servo](https://servo.org/),
+but it can also be used as such in a standalone application.
+
+# External dependencies
+WebRender currently depends on [FreeType](https://www.freetype.org/)
+
+# Api Structure
+The main entry point to WebRender is the `webrender::renderer::Renderer`.
+
+By calling `Renderer::new(...)` you get a `Renderer`, as well as a `RenderApiSender`.
+Your `Renderer` is responsible to render the previously processed frames onto the screen.
+
+By calling `yourRenderApiSender.create_api()`, you'll get a `RenderApi` instance,
+which is responsible for managing resources and documents. A worker thread is used internally
+to untie the workload from the application thread and therefore be able to make better use of
+multicore systems.
+
+## Frame
+
+What is referred to as a `frame`, is the current geometry on the screen.
+A new Frame is created by calling [`set_display_list()`][newframe] on the `RenderApi`.
+When the geometry is processed, the application will be informed via a `RenderNotifier`,
+a callback which you employ with [set_render_notifier][notifier] on the `Renderer`
+More information about [stacking contexts][stacking_contexts].
+
+`set_display_list()` also needs to be supplied with `BuiltDisplayList`s.
+These are obtained by finalizing a `DisplayListBuilder`. These are used to draw your geometry.
+But it doesn't only contain trivial geometry, it can also store another StackingContext, as
+they're nestable.
+
+[stacking_contexts]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
+[newframe]: ../webrender_api/struct.RenderApi.html#method.set_display_list
+[notifier]: renderer/struct.Renderer.html#method.set_render_notifier
+*/
 
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
@@ -52,16 +56,17 @@ mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 mod device;
 mod ellipse;
 mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
+mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
 mod internal_types;
 mod mask_cache;
 mod prim_store;
 mod print_tree;
 mod profiler;
 mod record;
@@ -123,17 +128,17 @@ extern crate core_text;
 extern crate freetype;
 
 #[cfg(target_os = "windows")]
 extern crate dwrote;
 
 extern crate app_units;
 extern crate bincode;
 extern crate euclid;
-extern crate fnv;
+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;
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -8,27 +8,27 @@ use core_graphics::base::kCGBitmapByteOr
 use core_graphics::color_space::CGColorSpace;
 use core_graphics::context::{CGContext, CGTextDrawingMode};
 use core_graphics::data_provider::CGDataProvider;
 use core_graphics::font::{CGFont, CGGlyph};
 use core_graphics::geometry::{CGPoint, CGSize, CGRect};
 use core_text::font::CTFont;
 use core_text::font_descriptor::kCTFontDefaultOrientation;
 use core_text;
-use std::collections::HashMap;
+use internal_types::FastHashMap;
 use std::collections::hash_map::Entry;
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
-use api::{GlyphKey, SubpixelPoint};
+use api::{GlyphKey, SubpixelDirection};
 use api::{FontInstanceKey, NativeFontHandle};
 use gamma_lut::{GammaLut, Color as ColorLut};
 use std::ptr;
 
 pub struct FontContext {
-    cg_fonts: HashMap<FontKey, CGFont>,
-    ct_fonts: HashMap<(FontKey, Au), CTFont>,
+    cg_fonts: FastHashMap<FontKey, CGFont>,
+    ct_fonts: FastHashMap<(FontKey, Au), CTFont>,
     gamma_lut: GammaLut,
 }
 
 // core text is safe to use on multiple threads and non-shareable resources are
 // all hidden inside their font context.
 unsafe impl Send for FontContext {}
 
 pub struct RasterizedGlyph {
@@ -78,17 +78,18 @@ fn supports_subpixel_aa() -> bool {
     let glyph = '|' as CGGlyph;
     ct_font.draw_glyphs(&[glyph], &[point], cg_context.clone());
     let data = cg_context.data();
     data[0] != data[1] || data[1] != data[2]
 }
 
 fn get_glyph_metrics(ct_font: &CTFont,
                      glyph: CGGlyph,
-                     subpixel_point: &SubpixelPoint) -> GlyphMetrics {
+                     x_offset: f64,
+                     y_offset: f64) -> GlyphMetrics {
     let bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);
 
     if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() ||
        bounds.size.width.is_nan() || bounds.size.height.is_nan() {
         // If an unexpected glyph index is requested, core text will return NaN values
         // which causes us to do bad thing as the value is cast into an integer and
         // overflow when expanding the bounds a few lines below.
         // Instead we are better off returning zero-sized metrics because this special
@@ -98,18 +99,16 @@ fn get_glyph_metrics(ct_font: &CTFont,
             rasterized_width: 0,
             rasterized_height: 0,
             rasterized_ascent: 0,
             rasterized_descent: 0,
             advance: 0.0,
         };
     }
 
-    let (x_offset, y_offset) = subpixel_point.to_f64();
-
     // First round out to pixel boundaries
     // CG Origin is bottom left
     let mut left = bounds.origin.x.floor() as i32;
     let mut bottom = bounds.origin.y.floor() as i32;
     let mut right = (bounds.origin.x
                     + bounds.size.width
                     + x_offset).ceil() as i32;
     let mut top = (bounds.origin.y
@@ -149,18 +148,18 @@ impl FontContext {
     pub fn new() -> FontContext {
         debug!("Test for subpixel AA support: {}", supports_subpixel_aa());
 
         // Force CG to use sRGB color space to gamma correct.
         let contrast = 0.0;
         let gamma = 0.0;
 
         FontContext {
-            cg_fonts: HashMap::new(),
-            ct_fonts: HashMap::new(),
+            cg_fonts: FastHashMap::default(),
+            ct_fonts: FastHashMap::default(),
             gamma_lut: GammaLut::new(contrast, gamma, gamma),
         }
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.cg_fonts.contains_key(font_key)
     }
 
@@ -237,17 +236,18 @@ impl FontContext {
             })
     }
 
     pub fn get_glyph_dimensions(&mut self,
                                 font: &FontInstanceKey,
                                 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 metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
+            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 {
                 Some(GlyphDimensions {
                     left: metrics.rasterized_left,
                     top: metrics.rasterized_ascent,
                     width: metrics.rasterized_width as u32,
                     height: metrics.rasterized_height as u32,
@@ -302,17 +302,18 @@ impl FontContext {
                            -> Option<RasterizedGlyph> {
 
         let ct_font = match self.get_ct_font(font.font_key, font.size) {
             Some(font) => font,
             None => return Some(RasterizedGlyph::blank())
         };
 
         let glyph = key.index as CGGlyph;
-        let metrics = get_glyph_metrics(&ct_font, glyph, &key.subpixel_point);
+        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 {
             return Some(RasterizedGlyph::blank())
         }
 
         let context_flags = match font.render_mode {
             FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
             FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
         };
@@ -360,18 +361,16 @@ impl FontContext {
         cg_context.set_allows_font_subpixel_quantization(false);
         cg_context.set_should_subpixel_quantize_fonts(false);
 
         cg_context.set_allows_font_smoothing(smooth);
         cg_context.set_should_smooth_fonts(smooth);
         cg_context.set_allows_antialiasing(antialias);
         cg_context.set_should_antialias(antialias);
 
-        let (x_offset, y_offset) = key.subpixel_point.to_f64();
-
         // CG Origin is bottom left, WR is top left. Need -y offset
         let rasterization_origin = CGPoint {
             x: -metrics.rasterized_left as f64 + x_offset,
             y: metrics.rasterized_descent as f64 - y_offset,
         };
 
         // Always draw black text on a white background
         // Fill the background
@@ -435,9 +434,8 @@ impl FontContext {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             bytes: rasterized_pixels,
         })
     }
 }
-
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -1,27 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use app_units::Au;
 use api::{FontInstanceKey, FontKey, FontRenderMode, GlyphDimensions};
-use api::{NativeFontHandle};
+use api::{NativeFontHandle, SubpixelDirection};
 use api::{GlyphKey};
+use internal_types::FastHashMap;
 
-use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
-use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter};
-use freetype::freetype::{FT_Library, FT_Set_Char_Size};
-use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
+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};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
 use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32, FT_Get_Char_Index};
 
-use std::{cmp, mem, ptr, slice};
-use std::collections::HashMap;
+use std::{mem, ptr, slice};
 
 // This constant is not present in the freetype
 // bindings due to bindgen not handling the way
 // the macro is defined.
 const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
 
 // Default to slight hinting, which is what most
 // Linux distros use by default, and is a better
@@ -30,17 +29,18 @@ const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1
 const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_TARGET_LIGHT;
 
 struct Face {
     face: FT_Face,
 }
 
 pub struct FontContext {
     lib: FT_Library,
-    faces: HashMap<FontKey, Face>,
+    faces: FastHashMap<FontKey, Face>,
+    lcd_extra_pixels: i64,
 }
 
 // FreeType resources are safe to move between threads as long as they
 // are not concurrently accessed. In our case, everything is hidden inside
 // a given FontContext so it is safe to move the latter between threads.
 unsafe impl Send for FontContext {}
 
 pub struct RasterizedGlyph {
@@ -51,30 +51,37 @@ pub struct RasterizedGlyph {
     pub bytes: Vec<u8>,
 }
 
 const SUCCESS: FT_Error = FT_Error(0);
 
 impl FontContext {
     pub fn new() -> FontContext {
         let mut lib: FT_Library = ptr::null_mut();
+
+        // Per Skia, using a filter adds one full pixel to each side.
+        let mut lcd_extra_pixels = 1;
+
         unsafe {
             let result = FT_Init_FreeType(&mut lib);
             assert!(result.succeeded(), "Unable to initialize FreeType library {:?}", result);
 
             // TODO(gw): Check result of this to determine if freetype build supports subpixel.
             let result = FT_Library_SetLcdFilter(lib, FT_LcdFilter::FT_LCD_FILTER_DEFAULT);
+
             if !result.succeeded() {
                 println!("WARN: Initializing a FreeType library build without subpixel AA enabled!");
+                lcd_extra_pixels = 0;
             }
         }
 
         FontContext {
             lib,
-            faces: HashMap::new(),
+            faces: FastHashMap::default(),
+            lcd_extra_pixels: lcd_extra_pixels,
         }
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.faces.contains_key(font_key)
     }
 
     pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8], index: u32) {
@@ -107,59 +114,122 @@ impl FontContext {
             let result = unsafe {
                 FT_Done_Face(face.face)
             };
             assert!(result.succeeded());
         }
     }
 
     fn load_glyph(&self,
-                  font_key: FontKey,
-                  size: Au,
-                  character: u32) -> Option<FT_GlyphSlot> {
+                  font: &FontInstanceKey,
+                  glyph: &GlyphKey) -> Option<FT_GlyphSlot> {
 
-        debug_assert!(self.faces.contains_key(&font_key));
-        let face = self.faces.get(&font_key).unwrap();
-        let char_size = size.to_f64_px() * 64.0 + 0.5;
+        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)
         });
 
         let result = unsafe {
             FT_Load_Glyph(face.face,
-                          character as FT_UInt,
+                          glyph.index as FT_UInt,
                           GLYPH_LOAD_FLAGS)
         };
 
         if result == SUCCESS {
             let slot = unsafe { (*face.face).glyph };
             assert!(slot != ptr::null_mut());
-            Some(slot)
+
+            // TODO(gw): We use the FT_Outline_* APIs to manage sub-pixel offsets.
+            //           We will need a custom code path for bitmap fonts (which
+            //           are very rare).
+            match unsafe { (*slot).format } {
+                FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
+                    Some(slot)
+                }
+                _ => {
+                    error!("TODO: Support bitmap fonts!");
+                    None
+                }
+            }
         } else {
             error!("Unable to load glyph for {} of size {:?} from font {:?}, {:?}",
-                character, size, font_key, result);
+                glyph.index, font.size, font.font_key, result);
             None
         }
     }
 
-    fn get_glyph_dimensions_impl(slot: FT_GlyphSlot) -> Option<GlyphDimensions> {
+    // Get the bounding box for a glyph, accounting for sub-pixel positioning.
+    fn get_bounding_box(&self,
+                        slot: FT_GlyphSlot,
+                        font: &FontInstanceKey,
+                        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);
+        }
+
+        // Convert the subpixel offset to floats.
+        let (dx, dy) = font.get_subpx_offset(glyph);
+
+        // Apply extra pixel of padding for subpixel AA, due to the filter.
+        let padding = match font.render_mode {
+            FontRenderMode::Subpixel => self.lcd_extra_pixels * 64,
+            FontRenderMode::Alpha | FontRenderMode::Mono => 0,
+        };
+        cbox.xMin -= padding as FT_Pos;
+        cbox.xMax += padding as FT_Pos;
+
+        // Offset the bounding box by subpixel positioning.
+        // Convert to 26.6 fixed point format for FT.
+        match font.subpx_dir {
+            SubpixelDirection::None => {}
+            SubpixelDirection::Horizontal => {
+                let dx = (dx * 64.0 + 0.5) as FT_Long;
+                cbox.xMin += dx;
+                cbox.xMax += dx;
+            }
+            SubpixelDirection::Vertical => {
+                let dy = (dy * 64.0 + 0.5) as FT_Long;
+                cbox.yMin += dy;
+                cbox.yMax += dy;
+            }
+        }
+
+        // Outset the box to device pixel boundaries
+        cbox.xMin &= !63;
+        cbox.yMin &= !63;
+        cbox.xMax = (cbox.xMax + 63) & !63;
+        cbox.yMax = (cbox.yMax + 63) & !63;
+
+        cbox
+    }
+
+    fn get_glyph_dimensions_impl(&self,
+                                 slot: FT_GlyphSlot,
+                                 font: &FontInstanceKey,
+                                 glyph: &GlyphKey) -> Option<GlyphDimensions> {
         let metrics = unsafe { &(*slot).metrics };
-        if metrics.width == 0 || metrics.height == 0 {
+
+        // If there's no advance, no need to consider this glyph
+        // for layout.
+        if metrics.horiAdvance == 0 {
             None
         } else {
-            let left = metrics.horiBearingX >> 6;
-            let top = metrics.horiBearingY >> 6;
-            let right = (metrics.horiBearingX + metrics.width + 0x3f) >> 6;
-            let bottom = (metrics.horiBearingY + metrics.height + 0x3f) >> 6;
+            let cbox = self.get_bounding_box(slot, font, glyph);
+
             Some(GlyphDimensions {
-                left: left as i32,
-                top: top as i32,
-                width: (right - left) as u32,
-                height: (bottom - top) as u32,
+                left: (cbox.xMin >> 6) as i32,
+                top: (cbox.yMax >> 6) as i32,
+                width: ((cbox.xMax - cbox.xMin) >> 6) as u32,
+                height: ((cbox.yMax - cbox.yMin) >> 6) as u32,
                 advance: metrics.horiAdvance as f32 / 64.0,
             })
         }
     }
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         let face = self.faces
                        .get(&font_key)
@@ -172,127 +242,132 @@ impl FontContext {
                 None
             }
         }
     }
 
     pub fn get_glyph_dimensions(&mut self,
                                 font: &FontInstanceKey,
                                 key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.load_glyph(font.font_key,
-                        font.size,
-                        key.index)
-            .and_then(Self::get_glyph_dimensions_impl)
+        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,
                            key: &GlyphKey)
                            -> Option<RasterizedGlyph> {
 
-        let slot = match self.load_glyph(font.font_key,
-                                         font.size,
-                                         key.index) {
+        let slot = match self.load_glyph(font, key) {
             Some(slot) => slot,
             None => return None,
         };
+
         let render_mode = match font.render_mode {
             FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
             FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
             FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
         };
 
+        // Get dimensions of the glyph, to see if we need to rasterize it.
+        let dimensions = match self.get_glyph_dimensions_impl(slot, font, key) {
+            Some(val) => val,
+            None => return None,
+        };
+
+        // For spaces and other non-printable characters, early out.
+        if dimensions.width == 0 || dimensions.height == 0 {
+            return None;
+        }
+
+        // Get the subpixel offsets in FT 26.6 format.
+        let (dx, dy) = font.get_subpx_offset(key);
+        let dx = (dx * 64.0 + 0.5) as FT_Long;
+        let dy = (dy * 64.0 + 0.5) as FT_Long;
+
+        // Move the outline curves to be at the origin, taking
+        // into account the subpixel positioning.
+        unsafe {
+            let outline = &(*slot).outline;
+            let mut cbox: FT_BBox = mem::uninitialized();
+            FT_Outline_Get_CBox(outline, &mut cbox);
+            FT_Outline_Translate(outline, dx - ((cbox.xMin + dx) & !63),
+                                          dy - ((cbox.yMin + dy) & !63));
+        }
+
         let result = unsafe { FT_Render_Glyph(slot, render_mode) };
         if result != SUCCESS {
             error!("Unable to rasterize {:?} with {:?}, {:?}", key, render_mode, result);
             return None;
         }
 
-        let dimensions = match Self::get_glyph_dimensions_impl(slot) {
-            Some(val) => val,
-            None => return None,
-        };
-
         let bitmap = unsafe { &(*slot).bitmap };
         let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
         info!("Rasterizing {:?} as {:?} with dimensions {:?}", key, render_mode, dimensions);
-        // we may be filling only a part of the buffer, so initialize the whole thing with 0
-        let mut final_buffer = Vec::with_capacity(dimensions.width as usize *
-                                                  dimensions.height as usize *
-                                                  4);
 
-        let offset_x = dimensions.left - unsafe { (*slot).bitmap_left };
-        let src_pixel_width = match pixel_mode {
+        let actual_width = match pixel_mode {
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
                 assert!(bitmap.width % 3 == 0);
                 bitmap.width / 3
             },
-            _ => bitmap.width,
-        };
-        // determine the destination range of texels that `bitmap` provides data for
-        let dst_start = cmp::max(0, -offset_x);
-        let dst_end = cmp::min(dimensions.width as i32, src_pixel_width as i32 - offset_x);
-
-        for y in 0 .. dimensions.height {
-            let src_y = y as i32 - dimensions.top + unsafe { (*slot).bitmap_top };
-            if src_y < 0 || src_y >= bitmap.rows as i32 {
-                for _x in 0 .. dimensions.width {
-                    final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, 0]);
-                }
-                continue
+            FT_Pixel_Mode::FT_PIXEL_MODE_MONO |
+            FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
+                bitmap.width
             }
-            let base = unsafe {
-                bitmap.buffer.offset((src_y * bitmap.pitch) as isize)
-            };
-            for _x in 0 .. dst_start {
-                final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, 0]);
+            _ => {
+                panic!("Unexpected pixel mode!");
             }
-            match pixel_mode {
-                FT_Pixel_Mode::FT_PIXEL_MODE_MONO => {
-                    for x in dst_start .. dst_end {
-                        let src_x = x + offset_x;
-                        let mask = 0x80 >> (src_x & 0x7);
-                        let byte = unsafe {
-                            *base.offset((src_x >> 3) as isize)
-                        };
+        } as i32;
+
+        let actual_height = bitmap.rows as i32;
+        let top = unsafe { (*slot).bitmap_top };
+        let left = unsafe { (*slot).bitmap_left };
+        let mut final_buffer = vec![0; (actual_width * actual_height * 4) as usize];
+
+        // Extract the final glyph from FT format into RGBA8 format, which is
+        // what WR expects.
+        for y in 0..actual_height {
+            // Get pointer to the bytes for this row.
+            let mut src = unsafe {
+                bitmap.buffer.offset((y * bitmap.pitch) as isize)
+            };
+
+            for x in 0..actual_width {
+                let value = match pixel_mode {
+                    FT_Pixel_Mode::FT_PIXEL_MODE_MONO => {
+                        let mask = 0x80 >> (x & 0x7);
+                        let byte = unsafe { *src.offset((x >> 3) as isize) };
                         let alpha = if byte & mask != 0 { 0xff } else { 0 };
-                        final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, alpha]);
+                        [0xff, 0xff, 0xff, alpha]
                     }
-                }
-                FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
-                    for x in dst_start .. dst_end {
-                        let alpha = unsafe {
-                            *base.offset((x + offset_x) as isize)
-                        };
-                        final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, alpha]);
+                    FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
+                        let alpha = unsafe { *src };
+                        src = unsafe { src.offset(1) };
+                        [0xff, 0xff, 0xff, alpha]
                     }
-                }
-                FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
-                    for x in dst_start .. dst_end {
-                        let src_x = ((x + offset_x) * 3) as isize;
-                        assert!(src_x+2 < bitmap.pitch as isize);
-                        let t = unsafe {
-                            slice::from_raw_parts(base.offset(src_x), 3)
-                        };
-                        final_buffer.extend_from_slice(&[t[2], t[1], t[0], 0xff]);
+                    FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
+                        let t = unsafe { slice::from_raw_parts(src, 3) };
+                        src = unsafe { src.offset(3) };
+                        [t[2], t[1], t[0], 0xff]
                     }
-                }
-                _ => panic!("Unsupported {:?}", pixel_mode)
+                    _ => panic!("Unsupported {:?}", pixel_mode)
+                };
+                let i = 4 * (y * actual_width + x) as usize;
+                let dest = &mut final_buffer[i..i+4];
+                dest.clone_from_slice(&value);
             }
-            for _x in dst_end .. dimensions.width as i32 {
-                final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, 0]);
-            }
-            assert_eq!(final_buffer.len(), ((y+1) * dimensions.width * 4) as usize);
         }
 
         Some(RasterizedGlyph {
-            left: dimensions.left as f32,
-            top: dimensions.top as f32,
-            width: dimensions.width as u32,
-            height: dimensions.height as u32,
+            left: (dimensions.left + left) as f32,
+            top: (dimensions.top + top - actual_height) as f32,
+            width: actual_width as u32,
+            height: actual_height as u32,
             bytes: final_buffer,
         })
     }
 }
 
 impl Drop for FontContext {
     fn drop(&mut self) {
         unsafe {
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.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/. */
 
-use std::collections::HashMap;
 use api::{FontKey, FontRenderMode, GlyphDimensions};
-use api::{FontInstanceKey, GlyphKey, GlyphOptions};
+use api::{FontInstanceKey, GlyphKey, GlyphOptions, SubpixelDirection};
 use gamma_lut::{GammaLut, Color as ColorLut};
+use internal_types::FastHashMap;
 
 use dwrote;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
         stretch: dwrote::FontStretch::Normal,
         style: dwrote::FontStyle::Normal,
     };
 }
 
 pub struct FontContext {
-    fonts: HashMap<FontKey, dwrote::FontFace>,
+    fonts: FastHashMap<FontKey, dwrote::FontFace>,
     gamma_lut: GammaLut,
     gdi_gamma_lut: GammaLut,
 }
 
 // DirectWrite is safe to use on multiple threads and non-shareable resources are
 // all hidden inside their font context.
 unsafe impl Send for FontContext {}
 
@@ -90,17 +90,17 @@ impl FontContext {
     pub fn new() -> FontContext {
         // These are the default values we use in Gecko.
         // We use a gamma value of 2.3 for gdi fonts
         // TODO: Fetch this data from Gecko itself.
         let contrast = 1.0;
         let gamma = 1.8;
         let gdi_gamma = 2.3;
         FontContext {
-            fonts: HashMap::new(),
+            fonts: FastHashMap::default(),
             gamma_lut: GammaLut::new(contrast, gamma, gamma),
             gdi_gamma_lut: GammaLut::new(contrast, gdi_gamma, gdi_gamma),
         }
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.fonts.contains_key(font_key)
     }
@@ -176,17 +176,17 @@ impl FontContext {
         let dwrite_measure_mode = dwrite_measure_mode(font.render_mode,
                                                       font.glyph_options);
         let dwrite_render_mode = dwrite_render_mode(face,
                                                     font.render_mode,
                                                     font.size.to_f32_px(),
                                                     dwrite_measure_mode,
                                                     font.glyph_options);
 
-        let (x_offset, y_offset) = key.subpixel_point.to_f64();
+        let (x_offset, y_offset) = font.get_subpx_offset(key);
         let transform = Some(
                         dwrote::DWRITE_MATRIX { m11: 1.0, m12: 0.0, m21: 0.0, m22: 1.0,
                                                 dx: x_offset as f32, dy: y_offset as f32 }
                         );
 
         dwrote::GlyphRunAnalysis::create(&glyph_run, 1.0, transform,
                                          dwrite_render_mode,
                                          dwrite_measure_mode,
--- 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::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
-use api::{device_length, FontInstanceKey, LayerVector2D, LineOrientation, LineStyle};
+use api::{GlyphKey, LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
+use api::{device_length, FontInstanceKey, 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};
@@ -513,82 +513,99 @@ pub struct TextShadowPrimitiveCpu {
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font_key: FontKey,
     pub offset: LayerVector2D,
     pub logical_font_size: Au,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
-    // TODO(gw): Maybe make this an Arc for sharing with resource cache
-    pub glyph_instances: Vec<GlyphInstance>,
+    pub glyph_keys: Vec<GlyphKey>,
+    pub glyph_gpu_blocks: Vec<GpuBlockData>,
     pub glyph_options: Option<GlyphOptions>,
     pub normal_render_mode: FontRenderMode,
     pub shadow_render_mode: FontRenderMode,
     pub color: ColorF,
+    pub subpx_dir: SubpixelDirection,
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum TextRunMode {
     Normal,
     Shadow,
 }
 
 impl TextRunPrimitiveCpu {
     fn prepare_for_render(&mut self,
                           resource_cache: &mut ResourceCache,
                           device_pixel_ratio: f32,
                           display_list: &BuiltDisplayList,
                           run_mode: TextRunMode) {
-        // 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 displaty list.
-        if self.glyph_instances.is_empty() {
-            let src_glyphs = display_list.get(self.glyph_range);
-            for src in src_glyphs {
-                self.glyph_instances.push(GlyphInstance {
-                    index: src.index,
-                    point: src.point,
-                });
-            }
-        }
-
         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.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);
+
+            // TODO(gw): If we support chunks() on AuxIter
+            //           in the future, this code below could
+            //           be much simpler...
+            let mut gpu_block = GpuBlockData::empty();
 
-        resource_cache.request_glyphs(font, &self.glyph_instances);
+            for (i, src) in src_glyphs.enumerate() {
+                let key = GlyphKey::new(src.index,
+                                        src.point,
+                                        font.render_mode,
+                                        font.subpx_dir);
+                self.glyph_keys.push(key);
+
+                // Two glyphs are packed per GPU block.
+
+                if (i & 1) == 0 {
+                    gpu_block.data[0] = src.point.x;
+                    gpu_block.data[1] = src.point.y;
+                } else {
+                    gpu_block.data[2] = src.point.x;
+                    gpu_block.data[3] = src.point.y;
+                    self.glyph_gpu_blocks.push(gpu_block);
+                }
+            }
+
+            // 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);
     }
 
     fn write_gpu_blocks(&self,
                         request: &mut GpuDataRequest) {
         request.push(self.color);
-        request.push([self.offset.x, self.offset.y, 0.0, 0.0]);
-
-        // Two glyphs are packed per GPU block.
-        for glyph_chunk in self.glyph_instances.chunks(2) {
-            // In the case of an odd number of glyphs, the
-            // last glyph will get duplicated in the final
-            // GPU block.
-            let first_glyph = glyph_chunk.first().unwrap();
-            let second_glyph = glyph_chunk.last().unwrap();
-            request.push([first_glyph.point.x,
-                          first_glyph.point.y,
-                          second_glyph.point.x,
-                          second_glyph.point.y]);
-        }
+        request.push([self.offset.x,
+                      self.offset.y,
+                      self.subpx_dir as u32 as f32,
+                      0.0]);
+        request.extend_from_slice(&self.glyph_gpu_blocks);
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct GlyphPrimitive {
     offset: LayerPoint,
     padding: LayerPoint,
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -126,16 +126,28 @@ impl ProfileCounter for ResourceProfileC
 
 #[derive(Clone)]
 pub struct TimeProfileCounter {
     description: &'static str,
     nanoseconds: u64,
     invert: bool,
 }
 
+pub struct Timer<'a> {
+    start: u64,
+    result: &'a mut u64,
+}
+
+impl<'a> Drop for Timer<'a> {
+    fn drop(&mut self) {
+        let end = precise_time_ns();
+        *self.result += end - self.start;
+    }
+}
+
 impl TimeProfileCounter {
     pub fn new(description: &'static str, invert: bool) -> TimeProfileCounter {
         TimeProfileCounter {
             description,
             nanoseconds: 0,
             invert,
         }
     }
@@ -153,16 +165,23 @@ impl TimeProfileCounter {
         let t0 = precise_time_ns();
         let val = callback();
         let t1 = precise_time_ns();
         let ns = t1 - t0;
         self.nanoseconds += ns;
         val
     }
 
+    pub fn timer(&mut self) -> Timer {
+        Timer {
+            start: precise_time_ns(),
+            result: &mut self.nanoseconds,
+        }
+    }
+
     pub fn inc(&mut self, ns: u64) {
         self.nanoseconds += ns;
     }
 
     pub fn get(&self) -> u64 {
         self.nanoseconds
     }
 }
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -59,25 +59,17 @@ impl ApiRecordingReceiver for BinaryReco
         // signal payload with a 0 length
         self.file.write_u32::<LittleEndian>(0).ok();
         self.write_length_and_data(data);
     }
 }
 
 pub fn should_record_msg(msg: &ApiMsg) -> bool {
     match *msg {
-        ApiMsg::AddRawFont(..) |
-        ApiMsg::AddNativeFont(..) |
-        ApiMsg::DeleteFont(..) |
-        ApiMsg::AddImage(..) |
-        ApiMsg::GenerateFrame(..) |
-        ApiMsg::UpdateImage(..) |
-        ApiMsg::DeleteImage(..) |
-        ApiMsg::SetDisplayList(..) |
-        ApiMsg::SetRootPipeline(..) |
-        ApiMsg::Scroll(..) |
-        ApiMsg::TickScrollingBounce |
-        ApiMsg::WebGLCommand(..) |
-        ApiMsg::SetWindowParameters(..) =>
+        ApiMsg::UpdateResources(..) |
+        ApiMsg::AddDocument{..} |
+        ApiMsg::UpdateDocument(..) |
+        ApiMsg::DeleteDocument(..) |
+        ApiMsg::WebGLCommand(..) =>
             true,
         _ => false
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,562 +1,621 @@
 /* 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::{SourceTexture, ResultMsg, RendererFrame};
-use profiler::{BackendProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
+use internal_types::{FastHashMap, SourceTexture, ResultMsg, RendererFrame};
+use profiler::{BackendProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
-use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 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, IdNamespace, ImageData};
-use api::{LayerPoint, PipelineId, RenderDispatcher, RenderNotifier};
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, DocumentMsg};
+use api::{IdNamespace, LayerPoint, RenderDispatcher, RenderNotifier};
 use api::{VRCompositorCommand, VRCompositorHandler, WebGLCommand, WebGLContextId};
-use api::{FontTemplate};
 
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::GLContextDispatcher;
 
 #[cfg(not(feature = "webgl"))]
 use webgl_types::GLContextDispatcher;
 
+struct Document {
+    scene: Scene,
+    frame: Frame,
+    window_size: DeviceUintSize,
+    inner_rect: DeviceUintRect,
+    pan: DeviceIntPoint,
+    page_zoom_factor: f32,
+    pinch_zoom_factor: f32,
+    // A helper switch to prevent any frames rendering triggered by scrolling
+    // messages between `SetDisplayList` and `GenerateFrame`.
+    // If we allow them, then a reftest that scrolls a few layers before generating
+    // the first frame would produce inconsistent rendering results, because
+    // scroll events are not necessarily received in deterministic order.
+    render_on_scroll: Option<bool>,
+}
+
+impl Document {
+    pub fn new(
+        config: FrameBuilderConfig,
+        initial_size: DeviceUintSize,
+        enable_render_on_scroll: bool,
+    ) -> Self {
+        let render_on_scroll = if enable_render_on_scroll {
+            Some(false)
+        } else {
+            None
+        };
+        Document {
+            scene: Scene::new(),
+            frame: Frame::new(config),
+            window_size: initial_size,
+            inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_size),
+            pan: DeviceIntPoint::zero(),
+            page_zoom_factor: 1.0,
+            pinch_zoom_factor: 1.0,
+            render_on_scroll,
+        }
+    }
+
+    fn accumulated_scale_factor(&self, hidpi_factor: f32) -> f32 {
+        hidpi_factor * self.page_zoom_factor * self.pinch_zoom_factor
+    }
+
+    fn build_scene(&mut self, resource_cache: &mut ResourceCache, hidpi_factor: f32) {
+        let accumulated_scale_factor = self.accumulated_scale_factor(hidpi_factor);
+        self.frame.create(&self.scene,
+                          resource_cache,
+                          self.window_size,
+                          self.inner_rect,
+                          accumulated_scale_factor);
+    }
+
+    fn render(&mut self,
+        resource_cache: &mut ResourceCache,
+        gpu_cache: &mut GpuCache,
+        resource_profile: &mut ResourceProfileCounters,
+        hidpi_factor: f32,
+    )-> RendererFrame {
+        let accumulated_scale_factor = self.accumulated_scale_factor(hidpi_factor);
+        let pan = LayerPoint::new(self.pan.x as f32 / accumulated_scale_factor,
+                                  self.pan.y as f32 / accumulated_scale_factor);
+        self.frame.build(resource_cache,
+                         gpu_cache,
+                         &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),
+}
+
 /// The render backend is responsible for transforming high level display lists into
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
     api_rx: MsgReceiver<ApiMsg>,
     payload_rx: PayloadReceiver,
     payload_tx: PayloadSender,
     result_tx: Sender<ResultMsg>,
 
     // TODO(gw): Consider using strongly typed units here.
     hidpi_factor: f32,
-    page_zoom_factor: f32,
-    pinch_zoom_factor: f32,
-    pan: DeviceIntPoint,
-    window_size: DeviceUintSize,
-    inner_rect: DeviceUintRect,
     next_namespace_id: IdNamespace,
 
     gpu_cache: GpuCache,
     resource_cache: ResourceCache,
 
-    scene: Scene,
-    frame: Frame,
+    frame_config: FrameBuilderConfig,
+    documents: FastHashMap<DocumentId, Document>,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
     webrender_context_handle: Option<GLContextHandleWrapper>,
-    webgl_contexts: HashMap<WebGLContextId, GLContextWrapper>,
-    current_bound_webgl_context_id: Option<WebGLContextId>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
 
-    next_webgl_id: usize,
-
     vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
+    webgl: WebGL,
 
     enable_render_on_scroll: bool,
-
-    // A helper switch to prevent any frames rendering triggered by scrolling
-    // messages between `SetDisplayList` and `GenerateFrame`.
-    // If we allow them, then a reftest that scrolls a few layers before generating
-    // the first frame would produce inconsistent rendering results, because
-    // scroll events are not necessarily received in deterministic order.
-    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>,
-               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>>>>,
-               initial_window_size: DeviceUintSize,
-               enable_render_on_scroll: bool) -> 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);
+        let resource_cache = ResourceCache::new(texture_cache,
+                                                workers,
+                                                blob_image_renderer,
+                                                frame_config.cache_expiry_frames);
 
         register_thread_with_profiler("Backend".to_string());
 
         RenderBackend {
             api_rx,
             payload_rx,
             payload_tx,
             result_tx,
             hidpi_factor,
-            page_zoom_factor: 1.0,
-            pinch_zoom_factor: 1.0,
-            pan: DeviceIntPoint::zero(),
+
             resource_cache,
             gpu_cache: GpuCache::new(),
-            scene: Scene::new(),
-            frame: Frame::new(config),
+            frame_config,
+            documents: FastHashMap::default(),
             next_namespace_id: IdNamespace(1),
             notifier,
             webrender_context_handle,
-            webgl_contexts: HashMap::new(),
-            current_bound_webgl_context_id: None,
             recorder,
             main_thread_dispatcher,
-            next_webgl_id: 0,
+
             vr_compositor_handler,
-            window_size: initial_window_size,
-            inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_window_size),
+            webgl: WebGL::new(),
+
             enable_render_on_scroll,
-            render_on_scroll: false,
         }
     }
 
-    fn scroll_frame(&mut self, frame_maybe: Option<RendererFrame>,
-                    profile_counters: &mut BackendProfileCounters) {
-        match frame_maybe {
-            Some(frame) => {
-                self.publish_frame(frame, profile_counters);
-                self.notify_compositor_of_new_scroll_frame(true)
+    fn process_document(&mut self, document_id: DocumentId, message: DocumentMsg,
+                        frame_counter: u32, mut profile_counters: &mut BackendProfileCounters)
+                        -> DocumentOp
+    {
+        let doc = self.documents.get_mut(&document_id).expect("No document?");
+
+        match message {
+            DocumentMsg::SetPageZoom(factor) => {
+                doc.page_zoom_factor = factor.get();
+                DocumentOp::Nop
+            }
+            DocumentMsg::SetPinchZoom(factor) => {
+                doc.pinch_zoom_factor = factor.get();
+                DocumentOp::Nop
+            }
+            DocumentMsg::SetPan(pan) => {
+                doc.pan = pan;
+                DocumentOp::Nop
+            }
+            DocumentMsg::SetWindowParameters{ window_size, inner_rect } => {
+                doc.window_size = window_size;
+                doc.inner_rect = inner_rect;
+                DocumentOp::Nop
+            }
+            DocumentMsg::SetDisplayList {
+                epoch,
+                pipeline_id,
+                background,
+                viewport_size,
+                content_size,
+                list_descriptor,
+                preserve_frame_state,
+                resources,
+            } => {
+                profile_scope!("SetDisplayList");
+
+                self.resource_cache.update_resources(resources, &mut profile_counters.resources);
+
+                let mut data;
+                while {
+                    data = self.payload_rx.recv_payload().unwrap();
+                    data.epoch != epoch || data.pipeline_id != pipeline_id
+                }{
+                    self.payload_tx.send_payload(data).unwrap()
+                }
+
+                if let Some(ref mut r) = self.recorder {
+                    r.write_payload(frame_counter, &data.to_data());
+                }
+
+                let built_display_list = BuiltDisplayList::from_data(
+                    data.display_list_data,
+                    list_descriptor
+                );
+
+                if !preserve_frame_state {
+                    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
+                    );
+                    doc.build_scene(&mut self.resource_cache, self.hidpi_factor);
+                }
+
+                if let Some(ref mut ros) = doc.render_on_scroll {
+                    *ros = false; //wait for `GenerateFrame`
+                }
+
+                // Note: this isn't quite right as auxiliary values will be
+                // pulled out somewhere in the prim_store, but aux values are
+                // really simple and cheap to access, so it's not a big deal.
+                let display_list_consumed_time = precise_time_ns();
+
+                profile_counters.ipc.set(builder_start_time,
+                                         builder_finish_time,
+                                         send_start_time,
+                                         display_list_received_time,
+                                         display_list_consumed_time,
+                                         display_list_len);
+
+                DocumentOp::Built
             }
-            None => self.notify_compositor_of_new_scroll_frame(false),
+            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) => {
+                profile_scope!("Scroll");
+                let _timer = profile_counters.total_time.timer();
+
+                if doc.frame.scroll(delta, cursor, move_phase) && doc.render_on_scroll == Some(true) {
+                    let frame = doc.render(&mut self.resource_cache,
+                                           &mut self.gpu_cache,
+                                           &mut profile_counters.resources,
+                                           self.hidpi_factor);
+                    DocumentOp::Scrolled(frame)
+                } else {
+                    DocumentOp::ScrolledNop
+                }
+            }
+            DocumentMsg::ScrollNodeWithId(origin, id, clamp) => {
+                profile_scope!("ScrollNodeWithScrollId");
+                let _timer = profile_counters.total_time.timer();
+
+                if doc.frame.scroll_node(origin, id, clamp) && doc.render_on_scroll == Some(true) {
+                    let frame = doc.render(&mut self.resource_cache,
+                                           &mut self.gpu_cache,
+                                           &mut profile_counters.resources,
+                                           self.hidpi_factor);
+                    DocumentOp::Scrolled(frame)
+                } else {
+                    DocumentOp::ScrolledNop
+                }
+            }
+            DocumentMsg::TickScrollingBounce => {
+                profile_scope!("TickScrollingBounce");
+                let _timer = profile_counters.total_time.timer();
+
+                doc.frame.tick_scrolling_bounce_animations();
+                if doc.render_on_scroll == Some(true) {
+                    let frame = doc.render(&mut self.resource_cache,
+                                           &mut self.gpu_cache,
+                                           &mut profile_counters.resources,
+                                           self.hidpi_factor);
+                    DocumentOp::Scrolled(frame)
+                } else {
+                    DocumentOp::ScrolledNop
+                }
+            }
+            DocumentMsg::GetScrollNodeState(tx) => {
+                profile_scope!("GetScrollNodeState");
+                tx.send(doc.frame.get_scroll_node_state()).unwrap();
+                DocumentOp::Nop
+            }
+            DocumentMsg::GenerateFrame(property_bindings) => {
+                profile_scope!("GenerateFrame");
+                let _timer = profile_counters.total_time.timer();
+
+                // Ideally, when there are property bindings present,
+                // we won't need to rebuild the entire frame here.
+                // However, to avoid conflicts with the ongoing work to
+                // 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;
+                }
+
+                if doc.scene.root_pipeline_id.is_some() {
+                    let frame = doc.render(&mut self.resource_cache,
+                                           &mut self.gpu_cache,
+                                           &mut profile_counters.resources,
+                                           self.hidpi_factor);
+                    DocumentOp::Rendered(frame)
+                } else {
+                    DocumentOp::ScrolledNop
+                }
+            }
         }
     }
 
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
         let mut frame_counter: u32 = 0;
 
         loop {
-            let msg = self.api_rx.recv();
             profile_scope!("handle_msg");
-            match msg {
+
+            let msg = match self.api_rx.recv() {
                 Ok(msg) => {
                     if let Some(ref mut r) = self.recorder {
                         r.write_msg(frame_counter, &msg);
                     }
-                    match msg {
-                        ApiMsg::AddRawFont(id, bytes, index) => {
-                            profile_counters.resources.font_templates.inc(bytes.len());
-                            self.resource_cache
-                                .add_font_template(id, FontTemplate::Raw(Arc::new(bytes), index));
-                        }
-                        ApiMsg::AddNativeFont(id, native_font_handle) => {
-                            self.resource_cache
-                                .add_font_template(id, FontTemplate::Native(native_font_handle));
-                        }
-                        ApiMsg::DeleteFont(id) => {
-                            self.resource_cache.delete_font_template(id);
-                        }
-                        ApiMsg::GetGlyphDimensions(font, glyph_keys, tx) => {
-                            let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
-                            for glyph_key in &glyph_keys {
-                                let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
-                                glyph_dimensions.push(glyph_dim);
-                            };
-                            tx.send(glyph_dimensions).unwrap();
-                        }
-                        ApiMsg::GetGlyphIndices(font_key, text, tx) => {
-                            let mut glyph_indices = Vec::new();
-                            for ch in text.chars() {
-                                let index = self.resource_cache.get_glyph_index(font_key, ch);
-                                glyph_indices.push(index);
-                            };
-                            tx.send(glyph_indices).unwrap();
-                        }
-                        ApiMsg::AddImage(id, descriptor, data, tiling) => {
-                            if let ImageData::Raw(ref bytes) = data {
-                                profile_counters.resources.image_templates.inc(bytes.len());
-                            }
-                            self.resource_cache.add_image_template(id, descriptor, data, tiling);
-                        }
-                        ApiMsg::UpdateImage(id, descriptor, bytes, dirty_rect) => {
-                            self.resource_cache.update_image_template(id, descriptor, bytes, dirty_rect);
-                        }
-                        ApiMsg::DeleteImage(id) => {
-                            self.resource_cache.delete_image_template(id);
-                        }
-                        ApiMsg::SetPageZoom(factor) => {
-                            self.page_zoom_factor = factor.get();
-                        }
-                        ApiMsg::SetPinchZoom(factor) => {
-                            self.pinch_zoom_factor = factor.get();
-                        }
-                        ApiMsg::SetPan(pan) => {
-                            self.pan = pan;
-                        }
-                        ApiMsg::SetWindowParameters(window_size, inner_rect) => {
-                            self.window_size = window_size;
-                            self.inner_rect = inner_rect;
-                        }
-                        ApiMsg::CloneApi(sender) => {
-                            let result = self.next_namespace_id;
-
-                            let IdNamespace(id_namespace) = self.next_namespace_id;
-                            self.next_namespace_id = IdNamespace(id_namespace + 1);
+                    msg
+                }
+                Err(..) => {
+                    let notifier = self.notifier.lock();
+                    notifier.unwrap()
+                            .as_mut()
+                            .unwrap()
+                            .shut_down();
+                    break;
+                }
+            };
 
-                            sender.send(result).unwrap();
-                        }
-                        ApiMsg::SetDisplayList(background_color,
-                                               epoch,
-                                               pipeline_id,
-                                               viewport_size,
-                                               content_size,
-                                               display_list_descriptor,
-                                               preserve_frame_state) => {
-                            profile_scope!("SetDisplayList");
-                            let mut leftover_data = vec![];
-                            let mut data;
-                            loop {
-                                data = self.payload_rx.recv_payload().unwrap();
-                                {
-                                    if data.epoch == epoch &&
-                                       data.pipeline_id == pipeline_id {
-                                        break
-                                    }
-                                }
-                                leftover_data.push(data)
-                            }
-                            for leftover_data in leftover_data {
-                                self.payload_tx.send_payload(leftover_data).unwrap()
-                            }
-                            if let Some(ref mut r) = self.recorder {
-                                r.write_payload(frame_counter, &data.to_data());
-                            }
-
-                            let built_display_list =
-                                BuiltDisplayList::from_data(data.display_list_data,
-                                                            display_list_descriptor);
-
-                            if !preserve_frame_state {
-                                self.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();
-
-                            profile_counters.total_time.profile(|| {
-                                self.scene.set_display_list(pipeline_id,
-                                                            epoch,
-                                                            built_display_list,
-                                                            background_color,
-                                                            viewport_size,
-                                                            content_size);
-                                self.build_scene();
-                            });
-
-                            self.render_on_scroll = false; //wait for `GenerateFrame`
-
-                            // Note: this isn't quite right as auxiliary values will be
-                            // pulled out somewhere in the prim_store, but aux values are
-                            // really simple and cheap to access, so it's not a big deal.
-                            let display_list_consumed_time = precise_time_ns();
-
-                            profile_counters.ipc.set(builder_start_time,
-                                                     builder_finish_time,
-                                                     send_start_time,
-                                                     display_list_received_time,
-                                                     display_list_consumed_time,
-                                                     display_list_len);
-                        }
-                        ApiMsg::SetRootPipeline(pipeline_id) => {
-                            profile_scope!("SetRootPipeline");
-                            self.scene.set_root_pipeline_id(pipeline_id);
-
-                            if self.scene.display_lists.get(&pipeline_id).is_none() {
-                                continue;
-                            }
-
-                            profile_counters.total_time.profile(|| {
-                                self.build_scene();
-                            })
-                        }
-                        ApiMsg::Scroll(delta, cursor, move_phase) => {
-                            profile_scope!("Scroll");
-                            let frame = {
-                                let counters = &mut profile_counters.resources.texture_cache;
-                                let gpu_cache_counters = &mut profile_counters.resources.gpu_cache;
-                                profile_counters.total_time.profile(|| {
-                                    if self.frame.scroll(delta, cursor, move_phase) && self.render_on_scroll {
-                                        Some(self.render(counters, gpu_cache_counters))
-                                    } else {
-                                        None
-                                    }
-                                })
-                            };
-
-                            self.scroll_frame(frame, &mut profile_counters);
+            match msg {
+                ApiMsg::UpdateResources(updates) => {
+                    self.resource_cache.update_resources(updates, &mut profile_counters.resources);
+                }
+                ApiMsg::GetGlyphDimensions(font, glyph_keys, tx) => {
+                    let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
+                    for glyph_key in &glyph_keys {
+                        let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
+                        glyph_dimensions.push(glyph_dim);
+                    };
+                    tx.send(glyph_dimensions).unwrap();
+                }
+                ApiMsg::GetGlyphIndices(font_key, text, tx) => {
+                    let mut glyph_indices = Vec::new();
+                    for ch in text.chars() {
+                        let index = self.resource_cache.get_glyph_index(font_key, ch);
+                        glyph_indices.push(index);
+                    };
+                    tx.send(glyph_indices).unwrap();
+                }
+                ApiMsg::CloneApi(sender) => {
+                    let namespace = self.next_namespace_id;
+                    self.next_namespace_id = IdNamespace(namespace.0 + 1);
+                    sender.send(namespace).unwrap();
+                }
+                ApiMsg::AddDocument(document_id, initial_size) => {
+                    let document = Document::new(self.frame_config.clone(),
+                                                 initial_size,
+                                                 self.enable_render_on_scroll);
+                    self.documents.insert(document_id, document);
+                }
+                ApiMsg::UpdateDocument(document_id, doc_msg) => {
+                    match self.process_document(document_id, doc_msg, frame_counter, &mut profile_counters) {
+                        DocumentOp::Nop => {}
+                        DocumentOp::Built => {}
+                        DocumentOp::ScrolledNop => {
+                            self.notify_compositor_of_new_scroll_frame(false);
                         }
-                        ApiMsg::ScrollNodeWithId(origin, id, clamp) => {
-                            profile_scope!("ScrollNodeWithScrollId");
-                            let frame = {
-                                let counters = &mut profile_counters.resources.texture_cache;
-                                let gpu_cache_counters = &mut profile_counters.resources.gpu_cache;
-                                profile_counters.total_time.profile(|| {
-                                    if self.frame.scroll_node(origin, id, clamp) && self.render_on_scroll {
-                                        Some(self.render(counters, gpu_cache_counters))
-                                    } else {
-                                        None
-                                    }
-                                })
-                            };
-
-                            self.scroll_frame(frame, &mut profile_counters);
-                        }
-                        ApiMsg::TickScrollingBounce => {
-                            profile_scope!("TickScrollingBounce");
-                            let frame = {
-                                let counters = &mut profile_counters.resources.texture_cache;
-                                let gpu_cache_counters = &mut profile_counters.resources.gpu_cache;
-                                profile_counters.total_time.profile(|| {
-                                    self.frame.tick_scrolling_bounce_animations();
-                                    if self.render_on_scroll {
-                                        Some(self.render(counters, gpu_cache_counters))
-                                    } else {
-                                        None
-                                    }
-                                })
-                            };
-
-                            self.scroll_frame(frame, &mut profile_counters);
-                        }
-                        ApiMsg::TranslatePointToLayerSpace(..) => {
-                            panic!("unused api - remove from webrender_api");
-                        }
-                        ApiMsg::GetScrollNodeState(tx) => {
-                            profile_scope!("GetScrollNodeState");
-                            tx.send(self.frame.get_scroll_node_state()).unwrap()
-                        }
-                        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);
-                                // 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.current_bound_webgl_context_id = None;
-
-                                match result {
-                                    Ok(ctx) => {
-                                        let id = WebGLContextId(self.next_webgl_id);
-                                        self.next_webgl_id += 1;
-
-                                        let (real_size, texture_id, limits) = ctx.get_info();
-
-                                        self.webgl_contexts.insert(id, 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();
-                            }
+                        DocumentOp::Scrolled(frame) => {
+                            self.publish_frame(document_id, frame, &mut profile_counters);
+                            self.notify_compositor_of_new_scroll_frame(true);
                         }
-                        ApiMsg::ResizeWebGLContext(context_id, size) => {
-                            let ctx = self.webgl_contexts.get_mut(&context_id).unwrap();
-                            if Some(context_id) != self.current_bound_webgl_context_id {
-                                ctx.make_current();
-                                self.current_bound_webgl_context_id = Some(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_contexts[&context_id];
-                            if Some(context_id) != self.current_bound_webgl_context_id {
-                                ctx.make_current();
-                                self.current_bound_webgl_context_id = Some(context_id);
-                            }
-                            ctx.apply_command(command);
-                        },
-
-                        ApiMsg::VRCompositorCommand(context_id, command) => {
-                            if Some(context_id) != self.current_bound_webgl_context_id {
-                                self.webgl_contexts[&context_id].make_current();
-                                self.current_bound_webgl_context_id = Some(context_id);
-                            }
-                            self.handle_vr_compositor_command(context_id, command);
-                        }
-                        ApiMsg::GenerateFrame(property_bindings) => {
-                            profile_scope!("GenerateFrame");
-
-                            // Ideally, when there are property bindings present,
-                            // we won't need to rebuild the entire frame here.
-                            // However, to avoid conflicts with the ongoing work to
-                            // 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.scene.properties.set_properties(property_bindings);
-                                profile_counters.total_time.profile(|| {
-                                    self.build_scene();
-                                });
-                            }
-
-                            self.render_on_scroll = self.enable_render_on_scroll;
-
-                            let frame = {
-                                let counters = &mut profile_counters.resources.texture_cache;
-                                let gpu_cache_counters = &mut profile_counters.resources.gpu_cache;
-                                profile_counters.total_time.profile(|| {
-                                    self.render(counters, gpu_cache_counters)
-                                })
-                            };
-                            if self.scene.root_pipeline_id.is_some() {
-                                self.publish_frame_and_notify_compositor(frame, &mut profile_counters);
-                                frame_counter += 1;
-                            }
-                        }
-                        ApiMsg::ExternalEvent(evt) => {
-                            let notifier = self.notifier.lock();
-                            notifier.unwrap()
-                                    .as_mut()
-                                    .unwrap()
-                                    .external_event(evt);
-                        }
-                        ApiMsg::ClearNamespace(namespace) => {
-                            self.resource_cache.clear_namespace(namespace);
-                        }
-                        ApiMsg::ShutDown => {
-                            let notifier = self.notifier.lock();
-                            notifier.unwrap()
-                                    .as_mut()
-                                    .unwrap()
-                                    .shut_down();
-                            break;
+                        DocumentOp::Rendered(frame) => {
+                            frame_counter += 1;
+                            self.publish_frame_and_notify_compositor(document_id, frame, &mut profile_counters);
                         }
                     }
                 }
-                Err(..) => {
+                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) => {
+                    self.resource_cache.clear_namespace(namespace_id);
+                    let document_ids = self.documents.keys()
+                                                     .filter(|did| did.0 == namespace_id)
+                                                     .cloned()
+                                                     .collect::<Vec<_>>();
+                    for document in document_ids {
+                        self.documents.remove(&document);
+                    }
+                }
+                ApiMsg::ShutDown => {
                     let notifier = self.notifier.lock();
                     notifier.unwrap()
                             .as_mut()
                             .unwrap()
                             .shut_down();
                     break;
                 }
             }
         }
     }
 
-    fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
-        self.frame.discard_frame_state_for_pipeline(pipeline_id);
-    }
-
-    fn accumulated_scale_factor(&self) -> f32 {
-        self.hidpi_factor * self.page_zoom_factor * self.pinch_zoom_factor
-    }
-
-    fn build_scene(&mut self) {
-        // Flatten the stacking context hierarchy
-        if let Some(id) = self.current_bound_webgl_context_id {
-            self.webgl_contexts[&id].unbind();
-            self.current_bound_webgl_context_id = None;
-        }
-
-        // 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 (_, webgl_context) in &self.webgl_contexts {
-            webgl_context.make_current();
-            webgl_context.apply_command(WebGLCommand::Flush);
-            webgl_context.unbind();
-        }
-
-        let accumulated_scale_factor = self.accumulated_scale_factor();
-        self.frame.create(&self.scene,
-                          &mut self.resource_cache,
-                          self.window_size,
-                          self.inner_rect,
-                          accumulated_scale_factor);
-    }
-
-    fn render(&mut self,
-              texture_cache_profile: &mut TextureCacheProfileCounters,
-              gpu_cache_profile: &mut GpuCacheProfileCounters)
-              -> RendererFrame {
-        let accumulated_scale_factor = self.accumulated_scale_factor();
-        let pan = LayerPoint::new(self.pan.x as f32 / accumulated_scale_factor,
-                                  self.pan.y as f32 / accumulated_scale_factor);
-        let frame = self.frame.build(&mut self.resource_cache,
-                                     &mut self.gpu_cache,
-                                     &self.scene.display_lists,
-                                     accumulated_scale_factor,
-                                     pan,
-                                     texture_cache_profile,
-                                     gpu_cache_profile);
-        frame
-    }
-
     fn publish_frame(&mut self,
+                     document_id: DocumentId,
                      frame: RendererFrame,
                      profile_counters: &mut BackendProfileCounters) {
         let pending_update = self.resource_cache.pending_updates();
-        let msg = ResultMsg::NewFrame(frame, pending_update, profile_counters.clone());
+        let msg = ResultMsg::NewFrame(document_id, frame, pending_update, profile_counters.clone());
         self.result_tx.send(msg).unwrap();
         profile_counters.reset();
     }
 
     fn publish_frame_and_notify_compositor(&mut self,
+                                           document_id: DocumentId,
                                            frame: RendererFrame,
                                            profile_counters: &mut BackendProfileCounters) {
-        self.publish_frame(frame, profile_counters);
+        self.publish_frame(document_id, frame, profile_counters);
 
         // 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_frame_ready();
     }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -178,17 +178,17 @@ impl RenderTask {
         }
     }
 
     pub fn new_mask(task_rect: DeviceIntRect,
                     mask_key: MaskCacheKey,
                     raw_clips: &[ClipWorkItem],
                     extra_clip: Option<ClipWorkItem>)
                     -> Option<RenderTask> {
-        /// Filter out all the clip instances that don't contribute to the result
+        // Filter out all the clip instances that don't contribute to the result
         let mut inner_rect = Some(task_rect);
         let clips: Vec<_> = raw_clips.iter()
                                      .chain(extra_clip.iter())
                                      .filter(|&&(_, ref clip_info)| {
             match clip_info.bounds.inner {
                 Some(ref inner) if !inner.device_rect.is_empty() => {
                     inner_rect = inner_rect.and_then(|r| r.intersection(&inner.device_rect));
                     !inner.device_rect.contains_rect(&task_rect)
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -6,56 +6,54 @@
 //!
 //! 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, ProgramId, TextureId, VertexFormat, GpuMarker, GpuProfiler, PBOId};
+use device::{DepthFunction, Device, FrameId, Program, TextureId, VertexDescriptor, GpuMarker, GpuProfiler, PBOId};
 use device::{GpuSample, TextureFilter, VAOId, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
-use device::get_gl_format_bgra;
-use euclid::Transform3D;
-use fnv::FnvHasher;
+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::{CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
-use internal_types::{TextureUpdateList, PackedVertex, RenderTargetMode};
+use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
+use internal_types::{TextureUpdateList, RenderTargetMode};
 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;
 use std::cmp;
-use std::collections::{HashMap, VecDeque};
+use std::collections::VecDeque;
 use std::f32;
-use std::hash::BuildHasherDefault;
 use std::marker::PhantomData;
 use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use 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, RenderNotifier, RenderDispatcher};
-use api::{ExternalImageId, ExternalImageType, ImageData, ImageFormat, RenderApiSender};
+use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier, RenderDispatcher};
+use api::{ExternalImageId, ExternalImageType, ImageData, 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;
@@ -79,16 +77,72 @@ const GPU_TAG_PRIM_GRADIENT: GpuProfileT
 const GPU_TAG_PRIM_ANGLE_GRADIENT: GpuProfileTag = GpuProfileTag { label: "AngleGradient", color: debug_colors::POWDERBLUE };
 const GPU_TAG_PRIM_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag { label: "RadialGradient", color: debug_colors::LIGHTPINK };
 const GPU_TAG_PRIM_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "BoxShadow", color: debug_colors::CYAN };
 const GPU_TAG_PRIM_BORDER_CORNER: GpuProfileTag = GpuProfileTag { label: "BorderCorner", color: debug_colors::DARKSLATEGREY };
 const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag { label: "BorderEdge", color: debug_colors::LAVENDER };
 const GPU_TAG_PRIM_CACHE_IMAGE: GpuProfileTag = GpuProfileTag { label: "CacheImage", color: debug_colors::SILVER };
 const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag { label: "Blur", color: debug_colors::VIOLET };
 
+bitflags! {
+    #[derive(Default)]
+    pub struct DebugFlags: u32 {
+        const PROFILER_DBG      = 1 << 0;
+        const RENDER_TARGET_DBG = 1 << 1;
+        const TEXTURE_CACHE_DBG = 1 << 2;
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct PackedVertex {
+    pub pos: [f32; 2],
+}
+
+const DESC_PRIM_INSTANCES: VertexDescriptor = VertexDescriptor {
+    vertex_attributes: &[
+        VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
+    ],
+    instance_attributes: &[
+        VertexAttribute { name: "aData0", count: 4, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aData1", count: 4, kind: VertexAttributeKind::I32 },
+    ]
+};
+
+const DESC_BLUR: VertexDescriptor = VertexDescriptor {
+    vertex_attributes: &[
+        VertexAttribute { name: "aPosition", count: 2, kind: VertexAttributeKind::F32 },
+    ],
+    instance_attributes: &[
+        VertexAttribute { name: "aBlurRenderTaskIndex", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aBlurSourceTaskIndex", count: 1, kind: VertexAttributeKind::I32 },
+        VertexAttribute { name: "aBlurDirection", count: 1, kind: VertexAttributeKind::I32 },
+    ]
+};
+
+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 },
+    ]
+};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum VertexFormat {
+    PrimitiveInstances,
+    Blur,
+    Clip,
+}
+
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
 pub struct GraphicsApiInfo {
     pub kind: GraphicsApi,
@@ -97,16 +151,17 @@ pub struct GraphicsApiInfo {
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum ImageBufferKind {
     Texture2D = 0,
     TextureRect = 1,
     TextureExternal = 2,
 }
+
 pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 3] = [
     ImageBufferKind::Texture2D,
     ImageBufferKind::TextureRect,
     ImageBufferKind::TextureExternal
 ];
 
 impl ImageBufferKind {
     pub fn get_feature_string(&self) -> &'static str {
@@ -429,67 +484,80 @@ const CLIP_FEATURE: &str = "CLIP";
 
 enum ShaderKind {
     Primitive,
     Cache(VertexFormat),
     ClipCache,
 }
 
 struct LazilyCompiledShader {
-    id: Option<ProgramId>,
+    program: Option<Program>,
     name: &'static str,
     kind: ShaderKind,
     features: Vec<&'static str>,
 }
 
 impl LazilyCompiledShader {
     fn new(kind: ShaderKind,
            name: &'static str,
            features: &[&'static str],
            device: &mut Device,
            precache: bool) -> Result<LazilyCompiledShader, ShaderError> {
         let mut shader = LazilyCompiledShader {
-            id: None,
+            program: None,
             name,
             kind,
             features: features.to_vec(),
         };
 
         if precache {
             try!{ shader.get(device) };
         }
 
         Ok(shader)
     }
 
-    fn get(&mut self, device: &mut Device) -> Result<ProgramId, ShaderError> {
-        if self.id.is_none() {
-            let id = try!{
+    fn bind(&mut self, device: &mut Device, projection: &Transform3D<f32>) {
+        let program = self.get(device)
+                          .expect("Unable to get shader!");
+        device.bind_program(program);
+        device.set_uniforms(program, projection);
+    }
+
+    fn get(&mut self, device: &mut Device) -> Result<&Program, ShaderError> {
+        if self.program.is_none() {
+            let program = try!{
                 match self.kind {
                     ShaderKind::Primitive => {
                         create_prim_shader(self.name,
                                            device,
                                            &self.features,
-                                           VertexFormat::Triangles)
+                                           VertexFormat::PrimitiveInstances)
                     }
                     ShaderKind::Cache(format) => {
                         create_prim_shader(self.name,
                                            device,
                                            &self.features,
                                            format)
                     }
                     ShaderKind::ClipCache => {
                         create_clip_shader(self.name, device)
                     }
                 }
             };
-            self.id = Some(id);
+            self.program = Some(program);
         }
 
-        Ok(self.id.unwrap())
+        Ok(self.program.as_ref().unwrap())
+    }
+
+    fn deinit(&mut self, device: &mut Device) {
+        if let &mut Some(ref mut program) = &mut self.program {
+            device.delete_program(program);
+        }
     }
 }
 
 struct PrimitiveShader {
     simple: LazilyCompiledShader,
     transform: LazilyCompiledShader,
 }
 
@@ -501,27 +569,16 @@ struct FileWatcher {
 impl FileWatcherHandler for FileWatcher {
     fn file_changed(&self, path: PathBuf) {
         self.result_tx.send(ResultMsg::RefreshShader(path)).ok();
         let mut notifier = self.notifier.lock();
         notifier.as_mut().unwrap().as_mut().unwrap().new_frame_ready();
     }
 }
 
-fn _get_ubo_max_len<T>(max_ubo_size: usize) -> usize {
-    let item_size = mem::size_of::<T>();
-    let max_items = max_ubo_size / item_size;
-
-    // TODO(gw): Clamping to 1024 since some shader compilers
-    //           seem to go very slow when you have high
-    //           constants for array lengths. Investigate
-    //           whether this clamping actually hurts performance!
-    cmp::min(max_items, 1024)
-}
-
 impl PrimitiveShader {
     fn new(name: &'static str,
            device: &mut Device,
            features: &[&'static str],
            precache: bool) -> Result<PrimitiveShader, ShaderError> {
         let simple = try!{
             LazilyCompiledShader::new(ShaderKind::Primitive,
                                       name,
@@ -542,52 +599,68 @@ impl PrimitiveShader {
         };
 
         Ok(PrimitiveShader {
             simple,
             transform,
         })
     }
 
-    fn get(&mut self,
-           device: &mut Device,
-           transform_kind: TransformedRectKind) -> Result<ProgramId, ShaderError> {
+    fn bind(&mut self,
+            device: &mut Device,
+            transform_kind: TransformedRectKind,
+            projection: &Transform3D<f32>) {
         match transform_kind {
-            TransformedRectKind::AxisAligned => self.simple.get(device),
-            TransformedRectKind::Complex => self.transform.get(device),
+            TransformedRectKind::AxisAligned => self.simple.bind(device, projection),
+            TransformedRectKind::Complex => self.transform.bind(device, projection),
         }
     }
+
+    fn deinit(&mut 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],
-                      vertex_format: VertexFormat) -> Result<ProgramId, ShaderError> {
+                      vertex_format: VertexFormat) -> Result<Program, ShaderError> {
     let mut prefix = format!("#define WR_MAX_VERTEX_TEXTURE_WIDTH {}\n",
                               MAX_VERTEX_TEXTURE_WIDTH);
 
     for feature in features {
         prefix.push_str(&format!("#define WR_FEATURE_{}\n", feature));
     }
 
     debug!("PrimShader {}", name);
 
     let includes = &["prim_shared"];
-    device.create_program_with_prefix(name, includes, Some(prefix), vertex_format)
+
+    let vertex_descriptor = match vertex_format {
+        VertexFormat::PrimitiveInstances => DESC_PRIM_INSTANCES,
+        VertexFormat::Blur => DESC_BLUR,
+        VertexFormat::Clip => DESC_CLIP,
+    };
+
+    device.create_program_with_prefix(name,
+                                      includes,
+                                      Some(prefix),
+                                      &vertex_descriptor)
 }
 
-fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<ProgramId, ShaderError> {
+fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<Program, ShaderError> {
     let prefix = format!("#define WR_MAX_VERTEX_TEXTURE_WIDTH {}\n
                           #define WR_FEATURE_TRANSFORM",
                           MAX_VERTEX_TEXTURE_WIDTH);
 
     debug!("ClipShader {}", name);
 
     let includes = &["prim_shared", "clip_shared"];
-    device.create_program_with_prefix(name, includes, Some(prefix), VertexFormat::Clip)
+    device.create_program_with_prefix(name, includes, Some(prefix), &DESC_CLIP)
 }
 
 struct GpuDataTextures {
     layer_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
 }
 
 impl GpuDataTextures {
@@ -662,23 +735,24 @@ pub struct Renderer {
 
     ps_blend: LazilyCompiledShader,
     ps_hw_composite: LazilyCompiledShader,
     ps_split_composite: LazilyCompiledShader,
     ps_composite: LazilyCompiledShader,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
 
-    enable_profiler: bool,
+    max_texture_size: u32,
+
     max_recorded_profiles: usize,
     clear_framebuffer: bool,
     clear_color: ColorF,
     enable_clear_scissor: bool,
     debug: DebugRenderer,
-    render_target_debug: bool,
+    debug_flags: DebugFlags,
     enable_batcher: bool,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     profiler: Profiler,
     last_time: u64,
 
     color_render_targets: Vec<TextureId>,
     alpha_render_targets: Vec<TextureId>,
@@ -688,17 +762,17 @@ pub struct Renderer {
     blur_vao_id: VAOId,
     clip_vao_id: VAOId,
 
     gdt_index: usize,
     gpu_data_textures: [GpuDataTextures; GPU_DATA_TEXTURE_POOL],
 
     gpu_cache_texture: CacheTexture,
 
-    pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
+    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
@@ -715,17 +789,17 @@ pub struct Renderer {
 
     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: HashMap<(ExternalImageId, u8), TextureId, BuildHasherDefault<FnvHasher>>,
+    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>,
@@ -755,24 +829,21 @@ impl Renderer {
     ///
     /// ```rust,ignore
     /// # use webrender::renderer::Renderer;
     /// # use std::path::PathBuf;
     /// let opts = webrender::RendererOptions {
     ///    device_pixel_ratio: 1.0,
     ///    resource_override_path: None,
     ///    enable_aa: false,
-    ///    enable_profiler: false,
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
     /// [rendereroptions]: struct.RendererOptions.html
-    pub fn new(gl: Rc<gl::Gl>,
-               mut options: RendererOptions,
-               initial_window_size: DeviceUintSize) -> Result<(Renderer, RenderApiSender), InitError> {
+    pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), InitError> {
         let (api_tx, api_rx) = try!{ channel::msg_channel() };
         let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         register_thread_with_profiler("Compositor".to_owned());
 
         let notifier = Arc::new(Mutex::new(None));
@@ -784,33 +855,33 @@ impl Renderer {
 
         let mut device = Device::new(gl,
                                      options.resource_override_path.clone(),
                                      Box::new(file_watch_handler));
         // device-pixel ratio doesn't matter here - we are just creating resources.
         device.begin_frame(1.0);
 
         let cs_box_shadow = try!{
-            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
+            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::PrimitiveInstances),
                                       "cs_box_shadow",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
         let cs_text_run = try!{
-            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
+            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::PrimitiveInstances),
                                       "cs_text_run",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
         let cs_line = try!{
-            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Triangles),
+            LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::PrimitiveInstances),
                                       "ps_line",
                                       &["CACHE"],
                                       &mut device,
                                       options.precache_shaders)
         };
 
         let cs_blur = try!{
             LazilyCompiledShader::new(ShaderKind::Cache(VertexFormat::Blur),
@@ -1108,23 +1179,23 @@ impl Renderer {
             PackedVertex {
                 pos: [x0, y1],
             },
             PackedVertex {
                 pos: [x1, y1],
             },
         ];
 
-        let prim_vao_id = device.create_vao(VertexFormat::Triangles, mem::size_of::<PrimitiveInstance>() as i32);
+        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 blur_vao_id = device.create_vao_with_new_instances(VertexFormat::Blur, mem::size_of::<BlurCommand>() as i32, prim_vao_id);
-        let clip_vao_id = device.create_vao_with_new_instances(VertexFormat::Clip, mem::size_of::<CacheClipInstance>() as i32, prim_vao_id);
+        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);
 
         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));
@@ -1146,17 +1217,17 @@ impl Renderer {
         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 render_target_debug = options.render_target_debug;
+        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) })
             .start_handler(|idx| { register_thread_with_profiler(format!("WebRender:Worker#{}", idx)); });
         let workers = options.workers.take().unwrap_or_else(||{
             Arc::new(ThreadPool::new(worker_config).unwrap())
         });
@@ -1173,17 +1244,16 @@ impl Renderer {
                                                  workers,
                                                  backend_notifier,
                                                  context_handle,
                                                  config,
                                                  recorder,
                                                  backend_main_thread_dispatcher,
                                                  blob_image_renderer,
                                                  backend_vr_compositor,
-                                                 initial_window_size,
                                                  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());
 
@@ -1216,52 +1286,56 @@ impl Renderer {
             ps_cache_image,
             ps_blend,
             ps_hw_composite,
             ps_split_composite,
             ps_composite,
             ps_line,
             notifier,
             debug: debug_renderer,
-            render_target_debug,
+            debug_flags,
             enable_batcher: options.enable_batcher,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
-            enable_profiler: options.enable_profiler,
+            max_texture_size: max_texture_size,
             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,
             gdt_index: 0,
             gpu_data_textures,
-            pipeline_epoch_map: HashMap::default(),
+            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: HashMap::default(),
+            external_images: FastHashMap::default(),
             vr_compositor_handler: vr_compositor,
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
+    pub fn get_max_texture_size(&self) -> u32 {
+        self.max_texture_size
+    }
+
     pub fn get_graphics_api_info(&self) -> GraphicsApiInfo {
         GraphicsApiInfo {
             kind: GraphicsApi::OpenGL,
             version: self.device.gl().get_string(gl::VERSION),
             renderer: self.device.gl().get_string(gl::RENDERER),
         }
     }
 
@@ -1297,30 +1371,31 @@ impl Renderer {
 
     /// 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) -> HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>> {
-        mem::replace(&mut self.pipeline_epoch_map, HashMap::default())
+    pub fn flush_rendered_epochs(&mut self) -> FastHashMap<PipelineId, Epoch> {
+        mem::replace(&mut self.pipeline_epoch_map, FastHashMap::default())
     }
 
     /// Processes the result queue.
     ///
     /// Should be called before `render()`, as texture cache updates are done here.
     pub fn update(&mut self) {
         profile_scope!("update");
 
         // Pull any pending results and return the most recent.
         while let Ok(msg) = self.result_rx.try_recv() {
             match msg {
-                ResultMsg::NewFrame(mut frame, texture_update_list, profile_counters) => {
+                ResultMsg::NewFrame(_document_id, mut frame, texture_update_list, profile_counters) => {
+                    //TODO: associate `document_id` with target window
                     self.pending_texture_updates.push(texture_update_list);
                     if let Some(ref mut frame) = frame.frame {
                         // TODO(gw): This whole message / Frame / RendererFrame stuff
                         //           is really messy and needs to be refactored!!
                         if let Some(update_list) = frame.gpu_cache_updates.take() {
                             self.pending_gpu_cache_updates.push(update_list);
                         }
                     }
@@ -1371,18 +1446,18 @@ impl Renderer {
     pub fn get_frame_profiles(&mut self) -> (Vec<CpuProfile>, Vec<GpuProfile>) {
         let cpu_profiles = self.cpu_profiles.drain(..).collect();
         let gpu_profiles = self.gpu_profiles.drain(..).collect();
         (cpu_profiles, gpu_profiles)
     }
 
     /// Renders the current frame.
     ///
-    /// A Frame is supplied by calling [`set_display_list()`][newframe].
-    /// [newframe]: ../../webrender_api/struct.RenderApi.html#method.set_display_list
+    /// A Frame is supplied by calling [`generate_frame()`][genframe].
+    /// [genframe]: ../../webrender_api/struct.DocumentApi.html#method.generate_frame
     pub fn render(&mut self, framebuffer_size: DeviceUintSize) {
         profile_scope!("render");
 
         if let Some(mut frame) = self.current_frame.take() {
             if let Some(ref mut frame) = frame.frame {
                 let mut profile_timers = RendererProfileTimers::new();
 
                 {
@@ -1437,17 +1512,17 @@ impl Renderer {
                     }
                     let cpu_profile = CpuProfile::new(cpu_frame_id,
                                                       self.backend_profile_counters.total_time.get(),
                                                       profile_timers.cpu_time.get(),
                                                       self.profile_counters.draw_calls.get());
                     self.cpu_profiles.push_back(cpu_profile);
                 }
 
-                if self.enable_profiler {
+                if self.debug_flags.contains(PROFILER_DBG) {
                     self.profiler.draw_profile(&mut self.device,
                                                &frame.profile_counters,
                                                &self.backend_profile_counters,
                                                &self.profile_counters,
                                                &mut profile_timers,
                                                &mut self.debug);
                 }
 
@@ -1614,21 +1689,18 @@ impl Renderer {
                 }
             }
         }
     }
 
     fn draw_instanced_batch<T>(&mut self,
                                data: &[T],
                                vao: VAOId,
-                               shader: ProgramId,
-                               textures: &BatchTextures,
-                               projection: &Transform3D<f32>) {
+                               textures: &BatchTextures) {
         self.device.bind_vao(vao);
-        self.device.bind_program(shader, projection);
 
         for i in 0..textures.colors.len() {
             let texture_id = self.resolve_source_texture(&textures.colors[i]);
             self.device.bind_texture(TextureSampler::color(i), texture_id);
         }
 
         // TODO: this probably isn't the best place for this.
         if let Some(id) = self.dither_matrix_texture_id {
@@ -1662,90 +1734,102 @@ impl Renderer {
         debug_assert!(!needs_clipping ||
                       match batch.key.blend_mode {
                           BlendMode::Alpha |
                           BlendMode::PremultipliedAlpha |
                           BlendMode::Subpixel(..) => true,
                           BlendMode::None => false,
                       });
 
-        let (marker, shader) = match batch.key.kind {
+        let marker = match batch.key.kind {
             AlphaBatchKind::Composite => {
-                let shader = self.ps_composite.get(&mut self.device);
-                (GPU_TAG_PRIM_COMPOSITE, shader)
+                self.ps_composite.bind(&mut self.device, projection);
+                GPU_TAG_PRIM_COMPOSITE
             }
             AlphaBatchKind::HardwareComposite => {
-                let shader = self.ps_hw_composite.get(&mut self.device);
-                (GPU_TAG_PRIM_HW_COMPOSITE, shader)
+                self.ps_hw_composite.bind(&mut self.device, projection);
+                GPU_TAG_PRIM_HW_COMPOSITE
             }
             AlphaBatchKind::SplitComposite => {
-                let shader = self.ps_split_composite.get(&mut self.device);
-                (GPU_TAG_PRIM_SPLIT_COMPOSITE, shader)
+                self.ps_split_composite.bind(&mut self.device, projection);
+                GPU_TAG_PRIM_SPLIT_COMPOSITE
             }
             AlphaBatchKind::Blend => {
-                let shader = self.ps_blend.get(&mut self.device);
-                (GPU_TAG_PRIM_BLEND, shader)
+                self.ps_blend.bind(&mut self.device, projection);
+                GPU_TAG_PRIM_BLEND
             }
             AlphaBatchKind::Rectangle => {
-                let shader = if needs_clipping {
-                    self.ps_rectangle_clip.get(&mut self.device, transform_kind)
+                if needs_clipping {
+                    self.ps_rectangle_clip.bind(&mut self.device, transform_kind, projection);
                 } else {
-                    self.ps_rectangle.get(&mut self.device, transform_kind)
-                };
-                (GPU_TAG_PRIM_RECT, shader)
+                    self.ps_rectangle.bind(&mut self.device, transform_kind, projection);
+                }
+                GPU_TAG_PRIM_RECT
             }
             AlphaBatchKind::Line => {
-                let shader = self.ps_line.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_LINE, shader)
+                self.ps_line.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_LINE
             }
             AlphaBatchKind::TextRun => {
-                let shader = match batch.key.blend_mode {
-                    BlendMode::Subpixel(..) => self.ps_text_run_subpixel.get(&mut self.device, transform_kind),
-                    BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::None => self.ps_text_run.get(&mut self.device, transform_kind),
+                match batch.key.blend_mode {
+                    BlendMode::Subpixel(..) => {
+                        self.ps_text_run_subpixel.bind(&mut self.device, transform_kind, projection);
+                    }
+                    BlendMode::Alpha |
+                    BlendMode::PremultipliedAlpha |
+                    BlendMode::None => {
+                        self.ps_text_run.bind(&mut self.device, transform_kind, projection);
+                    }
                 };
-                (GPU_TAG_PRIM_TEXT_RUN, shader)
+                GPU_TAG_PRIM_TEXT_RUN
             }
             AlphaBatchKind::Image(image_buffer_kind) => {
-                let shader = self.ps_image[image_buffer_kind as usize].as_mut().unwrap().get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_IMAGE, shader)
+                self.ps_image[image_buffer_kind as usize]
+                    .as_mut()
+                    .expect("Unsupported image shader kind")
+                    .bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_IMAGE
             }
             AlphaBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
                 let shader_index = Renderer::get_yuv_shader_index(image_buffer_kind,
                                                                   format,
                                                                   color_space);
-                let shader = self.ps_yuv_image[shader_index].as_mut().unwrap().get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_YUV_IMAGE, shader)
+                self.ps_yuv_image[shader_index]
+                    .as_mut()
+                    .expect("Unsupported YUV shader kind")
+                    .bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_YUV_IMAGE
             }
             AlphaBatchKind::BorderCorner => {
-                let shader = self.ps_border_corner.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_BORDER_CORNER, shader)
+                self.ps_border_corner.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_BORDER_CORNER
             }
             AlphaBatchKind::BorderEdge => {
-                let shader = self.ps_border_edge.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_BORDER_EDGE, shader)
+                self.ps_border_edge.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_BORDER_EDGE
             }
             AlphaBatchKind::AlignedGradient => {
-                let shader = self.ps_gradient.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_GRADIENT, shader)
+                self.ps_gradient.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_GRADIENT
             }
             AlphaBatchKind::AngleGradient => {
-                let shader = self.ps_angle_gradient.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_ANGLE_GRADIENT, shader)
+                self.ps_angle_gradient.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_ANGLE_GRADIENT
             }
             AlphaBatchKind::RadialGradient => {
-                let shader = self.ps_radial_gradient.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_RADIAL_GRADIENT, shader)
+                self.ps_radial_gradient.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_RADIAL_GRADIENT
             }
             AlphaBatchKind::BoxShadow => {
-                let shader = self.ps_box_shadow.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_BOX_SHADOW, shader)
+                self.ps_box_shadow.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_BOX_SHADOW
             }
             AlphaBatchKind::CacheImage => {
-                let shader = self.ps_cache_image.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_CACHE_IMAGE, shader)
+                self.ps_cache_image.bind(&mut self.device, transform_kind, projection);
+                GPU_TAG_PRIM_CACHE_IMAGE
             }
         };
 
         // 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);
@@ -1798,24 +1882,21 @@ impl Renderer {
             self.device.blit_render_target(render_target,
                                            Some(src),
                                            dest);
 
             // Restore draw target to current pass render target + layer.
             self.device.bind_draw_target(render_target, Some(target_dimensions));
         }
 
-        let shader = shader.unwrap();
         let _gm = self.gpu_profile.add_marker(marker);
         let vao = self.prim_vao_id;
         self.draw_instanced_batch(&batch.instances,
                                   vao,
-                                  shader,
-                                  &batch.key.textures,
-                                  projection);
+                                  &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]>,
@@ -1853,83 +1934,71 @@ impl Renderer {
         // 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);
-            let shader = self.cs_blur.get(&mut self.device).unwrap();
+            self.cs_blur.bind(&mut self.device, projection);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(&target.vertical_blurs,
                                           vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
+                                          &BatchTextures::no_texture());
             }
 
             if !target.horizontal_blurs.is_empty() {
                 self.draw_instanced_batch(&target.horizontal_blurs,
                                           vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
+                                          &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;
-            let shader = self.cs_box_shadow.get(&mut self.device).unwrap();
+            self.cs_box_shadow.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.box_shadow_cache_prims,
                                       vao,
-                                      shader,
-                                      &BatchTextures::no_texture(),
-                                      &projection);
+                                      &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;
-            let shader = self.cs_text_run.get(&mut self.device).unwrap();
-
+            self.cs_text_run.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.text_run_cache_prims,
                                       vao,
-                                      shader,
-                                      &target.text_run_textures,
-                                      &projection);
+                                      &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;
-            let shader = self.cs_line.get(&mut self.device).unwrap();
-
+            self.cs_line.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.line_cache_prims,
                                       vao,
-                                      shader,
-                                      &BatchTextures::no_texture(),
-                                      &projection);
+                                      &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;
 
             //Note: depth equality is needed for split planes
@@ -2017,71 +2086,63 @@ impl Renderer {
             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);
-                let shader = self.cs_clip_border.get(&mut self.device).unwrap();
+                self.cs_clip_border.bind(&mut self.device, projection);
                 self.draw_instanced_batch(&target.clip_batcher.border_clears,
                                           vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
+                                          &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();
-                let shader = self.cs_clip_border.get(&mut self.device).unwrap();
+                self.cs_clip_border.bind(&mut self.device, projection);
                 self.draw_instanced_batch(&target.clip_batcher.borders,
                                           vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
+                                          &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");
-                let shader = self.cs_clip_rectangle.get(&mut self.device).unwrap();
+                self.cs_clip_rectangle.bind(&mut self.device, projection);
                 self.draw_instanced_batch(&target.clip_batcher.rectangles,
                                           vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
+                                          &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,
                     ]
                 };
-                let shader = self.cs_clip_image.get(&mut self.device).unwrap();
+                self.cs_clip_image.bind(&mut self.device, projection);
                 self.draw_instanced_batch(items,
                                           vao,
-                                          shader,
-                                          &textures,
-                                          &projection);
+                                          &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
         // custom item. Then we patch the resource_rects structure
@@ -2285,69 +2346,98 @@ impl Renderer {
                 if let Some(texture_id) = pass.alpha_texture_id.take() {
                     self.alpha_render_targets.push(texture_id);
                 }
             }
 
             self.color_render_targets.reverse();
             self.alpha_render_targets.reverse();
             self.draw_render_target_debug(framebuffer_size);
+            self.draw_texture_cache_debug(framebuffer_size);
         }
 
         self.unlock_external_images();
     }
 
     pub fn debug_renderer<'a>(&'a mut self) -> &'a mut DebugRenderer {
         &mut self.debug
     }
 
-    pub fn get_profiler_enabled(&mut self) -> bool {
-        self.enable_profiler
+    pub fn get_debug_flags(&self) -> DebugFlags {
+        self.debug_flags
     }
 
-    pub fn set_profiler_enabled(&mut self, enabled: bool) {
-        self.enable_profiler = enabled;
+    pub fn set_debug_flags(&mut self, flags: DebugFlags) {
+        self.debug_flags = flags;
     }
 
     pub fn save_cpu_profile(&self, filename: &str) {
         write_profile(filename);
     }
 
     fn draw_render_target_debug(&mut self,
                                 framebuffer_size: &DeviceUintSize) {
-        if self.render_target_debug {
-            // TODO(gw): Make the layout of the render targets a bit more sophisticated.
-            // Right now, it just draws them in one row at the bottom of the screen,
-            // with a fixed size.
-            let rt_debug_x0 = 16;
-            let rt_debug_y0 = 16;
-            let rt_debug_spacing = 16;
-            let rt_debug_size = 512;
-            let mut current_target = 0;
+        if !self.debug_flags.contains(RENDER_TARGET_DBG) {
+            return;
+        }
+
+        let mut spacing = 16;
+        let mut size = 512;
+        let fb_width = framebuffer_size.width as i32;
+        let num_textures = self.color_render_targets.iter().chain(self.alpha_render_targets.iter()).count() as i32;
+
+        if num_textures * (size + spacing) > fb_width {
+            let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32;
+            size = (size as f32 * factor) as i32;
+            spacing = (spacing as f32 * factor) as i32;
+        }
+
+        for (i, texture_id) in self.color_render_targets.iter().chain(self.alpha_render_targets.iter()).enumerate() {
+            let layer_count = self.device.get_render_target_layer_count(*texture_id);
+            for layer_index in 0..layer_count {
+                let x = fb_width - (spacing + size) * (i as i32 + 1);
+                let y = spacing;
 
-            for texture_id in self.color_render_targets.iter().chain(self.alpha_render_targets.iter()) {
-                let layer_count = self.device.get_render_target_layer_count(*texture_id);
-                for layer_index in 0..layer_count {
-                    let x0 = rt_debug_x0 + (rt_debug_spacing + rt_debug_size) * current_target;
-                    let y0 = rt_debug_y0;
+                let dest_rect = rect(x, y, size, size);
+                self.device.blit_render_target(
+                    Some((*texture_id, layer_index as i32)),
+                    None,
+                    dest_rect
+                );
+            }
+        }
+    }
+
+    fn draw_texture_cache_debug(&mut self, framebuffer_size: &DeviceUintSize) {
+        if !self.debug_flags.contains(TEXTURE_CACHE_DBG) {
+            return;
+        }
 
-                    // If we have more targets than fit on one row in screen, just early exit.
-                    if x0 > framebuffer_size.width as i32 {
-                        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;
+
+        if num_textures * (size + spacing) > fb_width {
+            let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32;
+            size = (size as f32 * factor) as i32;
+            spacing = (spacing as f32 * factor) as i32;
+        }
 
-                    let dest_rect = DeviceIntRect::new(DeviceIntPoint::new(x0, y0),
-                                                       DeviceIntSize::new(rt_debug_size, rt_debug_size));
-                    self.device.blit_render_target(Some((*texture_id, layer_index as i32)),
-                                                   None,
-                                                   dest_rect);
+        for (i, texture_id) in self.cache_texture_id_map.iter().enumerate() {
+            let x = fb_width - (spacing + size) * (i as i32 + 1);
+            let y = spacing + if self.debug_flags.contains(RENDER_TARGET_DBG) { 528 } else { 0 };
 
-                    current_target += 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, 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
     }
@@ -2371,16 +2461,50 @@ impl Renderer {
                                                  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.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 {
+                shader.deinit(&mut self.device);
+            }
+        }
+        for shader in &mut self.ps_yuv_image {
+            if let &mut Some(ref mut 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);
+        self.ps_box_shadow.deinit(&mut self.device);
+        self.ps_cache_image.deinit(&mut self.device);
+        self.ps_line.deinit(&mut self.device);
+        self.ps_blend.deinit(&mut self.device);
+        self.ps_hw_composite.deinit(&mut self.device);
+        self.ps_split_composite.deinit(&mut self.device);
+        self.ps_composite.deinit(&mut self.device);
         self.device.end_frame();
     }
 }
 
 pub enum ExternalImageSource<'a> {
     RawData(&'a [u8]),      // raw buffers.
     NativeTexture(u32),     // Is a gl::GLuint texture handle
 }
@@ -2418,55 +2542,53 @@ pub trait ExternalImageHandler {
     fn unlock(&mut self, key: ExternalImageId, channel_index: u8);
 }
 
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
-    pub enable_profiler: bool,
     pub max_recorded_profiles: usize,
     pub debug: bool,
     pub enable_scrollbars: bool,
     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 render_target_debug: 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 {
     fn default() -> RendererOptions {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
             enable_dithering: true,
-            enable_profiler: false,
+            debug_flags: DebugFlags::empty(),
             max_recorded_profiles: 0,
             debug: false,
             enable_scrollbars: false,
             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,
-            render_target_debug: false,
             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
@@ -1,35 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use device::TextureFilter;
-use fnv::FnvHasher;
 use frame::FrameId;
+use glyph_cache::GlyphCache;
 use gpu_cache::{GpuCache, GpuCacheHandle};
-use internal_types::{SourceTexture, TextureUpdateList};
-use profiler::TextureCacheProfileCounters;
-use std::collections::{HashMap, HashSet};
+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::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheItemId};
-use api::{Epoch, FontInstanceKey, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
-use api::{ImageData, GlyphDimensions, WebGLContextId, IdNamespace};
-use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor};
-use api::{GlyphInstance, SubpixelPoint, TileOffset, TileSize};
-use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
-use api::BlobImageResources;
-use api::{ExternalImageData, ExternalImageType, LayoutPoint};
+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::{GlyphDimensions, GlyphKey, IdNamespace};
+use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
+use api::{TileOffset, TileSize};
+use api::{ExternalImageData, ExternalImageType, WebGLContextId};
 use rayon::ThreadPool;
-use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
+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
 // for this is that the texture may change
 // dimensions (e.g. the pages in a texture
@@ -60,24 +60,32 @@ enum State {
 struct ImageResource {
     data: ImageData,
     descriptor: ImageDescriptor,
     epoch: Epoch,
     tiling: Option<TileSize>,
     dirty_rect: Option<DeviceUintRect>
 }
 
+#[derive(Debug)]
+pub struct ImageTiling {
+    pub image_size: DeviceUintSize,
+    pub tile_size: TileSize,
+}
+
+pub type TiledImageMap = FastHashMap<ImageKey, ImageTiling>;
+
 struct ImageTemplates {
-    images: HashMap<ImageKey, ImageResource, BuildHasherDefault<FnvHasher>>,
+    images: FastHashMap<ImageKey, ImageResource>,
 }
 
 impl ImageTemplates {
     fn new() -> Self {
         ImageTemplates {
-            images: HashMap::with_hasher(Default::default())
+            images: FastHashMap::default()
         }
     }
 
     fn insert(&mut self, key: ImageKey, resource: ImageResource) {
         self.images.insert(key, resource);
     }
 
     fn remove(&mut self, key: ImageKey) -> Option<ImageResource> {
@@ -91,90 +99,102 @@ impl ImageTemplates {
     fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> {
         self.images.get_mut(&key)
     }
 }
 
 struct CachedImageInfo {
     texture_cache_id: TextureCacheItemId,
     epoch: Epoch,
+    last_access: FrameId,
 }
 
 pub struct ResourceClassCache<K,V> {
-    resources: HashMap<K, V, BuildHasherDefault<FnvHasher>>,
-    last_access_times: HashMap<K, FrameId, BuildHasherDefault<FnvHasher>>,
+    resources: FastHashMap<K, V>,
 }
 
 impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug, V: Resource {
     pub fn new() -> ResourceClassCache<K,V> {
         ResourceClassCache {
-            resources: HashMap::default(),
-            last_access_times: HashMap::default(),
+            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, *self.last_access_times
-                                     .get(key)
-                                     .expect("Didn't find the access time for a cached resource \
-                                              with that ID!"));
-        self.resources.get(key).expect("Didn't find a cached resource with that ID!")
+        debug_assert_eq!(frame, resource.get_last_access_time());
+
+        resource
     }
 
-    pub fn insert(&mut self, key: K, value: V, frame: FrameId) {
-        self.last_access_times.insert(key.clone(), frame);
+    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> {
-        self.last_access_times.insert(key.clone(), frame);
-        self.resources.entry(key)
+        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 mark_as_needed(&mut self, key: &K, frame: FrameId) {
-        self.last_access_times.insert((*key).clone(), frame);
+    pub fn is_empty(&self) -> bool {
+        self.resources.is_empty()
     }
 
-    fn expire_old_resources(&mut self, texture_cache: &mut TextureCache, frame_id: FrameId) {
-        //TODO: use retain when available
-        let resources_to_destroy = self.last_access_times.iter()
-            .filter_map(|(key, this_frame_id)| {
-                if *this_frame_id < frame_id {
-                    Some(key.clone())
-                } else {
-                    None
-                }
-            }).collect::<Vec<_>>();
+    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`!");
-            self.last_access_times.remove(&key);
-            if let Some(texture_cache_item_id) = resource.texture_cache_item_id() {
-                texture_cache.free(texture_cache_item_id)
-            }
+            resource.free(texture_cache);
+        }
+    }
+
+    pub fn clear(&mut self, texture_cache: &mut TextureCache) {
+        for (_, resource) in self.resources.drain() {
+            resource.free(texture_cache);
         }
     }
 
     fn clear_keys<F>(&mut self, texture_cache: &mut TextureCache, 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();
-            if let Some(texture_cache_item_id) = resource.texture_cache_item_id() {
-                texture_cache.free(texture_cache_item_id)
-            }
+            resource.free(texture_cache);
         }
     }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 struct ImageRequest {
     key: ImageKey,
@@ -192,17 +212,17 @@ impl Into<BlobImageRequest> for ImageReq
 }
 
 pub struct WebGLTexture {
     pub id: SourceTexture,
     pub size: DeviceIntSize,
 }
 
 struct Resources {
-    font_templates: HashMap<FontKey, FontTemplate, BuildHasherDefault<FnvHasher>>,
+    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()
     }
     fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)> {
@@ -210,60 +230,59 @@ impl BlobImageResources for Resources {
     }
 }
 
 pub struct ResourceCache {
     cached_glyphs: GlyphCache,
     cached_images: ResourceClassCache<ImageRequest, CachedImageInfo>,
 
     // TODO(pcwalton): Figure out the lifecycle of these.
-    webgl_textures: HashMap<WebGLContextId, WebGLTexture, BuildHasherDefault<FnvHasher>>,
+    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: HashMap<GlyphRequest, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
-    pending_image_requests: Vec<ImageRequest>,
+    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>>,
-    blob_image_requests: HashSet<ImageRequest>,
 
-    requested_glyphs: HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
-    requested_images: HashSet<TextureCacheItemId, BuildHasherDefault<FnvHasher>>,
+    cache_expiry_frames: u32,
 }
 
 impl ResourceCache {
     pub fn new(texture_cache: TextureCache,
                workers: Arc<ThreadPool>,
-               blob_image_renderer: Option<Box<BlobImageRenderer>>) -> ResourceCache {
+               blob_image_renderer: Option<Box<BlobImageRenderer>>,
+               cache_expiry_frames: u32) -> ResourceCache {
         ResourceCache {
-            cached_glyphs: ResourceClassCache::new(),
+            cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
-            webgl_textures: HashMap::default(),
+            webgl_textures: FastHashMap::default(),
             resources: Resources {
-                font_templates: HashMap::default(),
+                font_templates: FastHashMap::default(),
                 image_templates: ImageTemplates::new(),
             },
-            cached_glyph_dimensions: HashMap::default(),
+            cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
             current_frame_id: FrameId(0),
-            pending_image_requests: Vec::new(),
+            pending_image_requests: FastHashSet::default(),
             glyph_rasterizer: GlyphRasterizer::new(workers),
-
             blob_image_renderer,
-            blob_image_requests: HashSet::new(),
-
-            requested_glyphs: HashSet::default(),
-            requested_images: HashSet::default(),
+            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 {
@@ -274,16 +293,57 @@ impl ResourceCache {
             ImageData::External(info) => {
                 // External handles already represent existing textures so it does
                 // not make sense to tile them into smaller ones.
                 info.image_type == ExternalImageType::ExternalBuffer && size_check
             },
         }
     }
 
+    pub fn update_resources(
+        &mut self,
+        updates: ResourceUpdates,
+        profile_counters: &mut ResourceProfileCounters
+    ) {
+        // TODO, there is potential for optimization here, by processing updates in
+        // bulk rather than one by one (for example by sorting allocations by size or
+        // in a way that reduces fragmentation in the atlas).
+
+        for update in updates.updates {
+            match update {
+                ResourceUpdate::AddImage(img) => {
+                    if let ImageData::Raw(ref bytes) = img.data {
+                        profile_counters.image_templates.inc(bytes.len());
+                    }
+                    self.add_image_template(img.key, img.descriptor, img.data, img.tiling);
+                }
+                ResourceUpdate::UpdateImage(img) => {
+                    self.update_image_template(img.key, img.descriptor, img.data, img.dirty_rect);
+                }
+                ResourceUpdate::DeleteImage(img) => {
+                    self.delete_image_template(img);
+                }
+                ResourceUpdate::AddFont(font) => {
+                    match font {
+                        AddFont::Raw(id, bytes, index) => {
+                            profile_counters.font_templates.inc(bytes.len());
+                            self.add_font_template(id, FontTemplate::Raw(Arc::new(bytes), index));
+                        }
+                        AddFont::Native(id, native_font_handle) => {
+                            self.add_font_template(id, FontTemplate::Native(native_font_handle));
+                        }
+                    }
+                }
+                ResourceUpdate::DeleteFont(font) => {
+                    self.delete_font_template(font);
+                }
+            }
+        }
+    }
+
     pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
         // Push the new font to the font renderer, and also store
         // it locally for glyph metric requests.
         self.glyph_rasterizer.add_font(font_key, template.clone());
         self.resources.font_templates.insert(font_key, template);
     }
 
     pub fn delete_font_template(&mut self, font_key: FontKey) {
@@ -361,16 +421,18 @@ 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);
+
         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);
@@ -401,38 +463,41 @@ impl ResourceCache {
         debug_assert_eq!(self.state, State::AddResources);
         let request = ImageRequest {
             key,
             rendering,
             tile,
         };
 
         let template = self.resources.image_templates.get(key).unwrap();
-        if template.data.uses_texture_cache() {
-            self.cached_images.mark_as_needed(&request, self.current_frame_id);
+
+        // Images that don't use the texture cache can early out.
+        if !template.data.uses_texture_cache() {
+            return;
         }
-        if template.data.is_blob() {
-            if let Some(ref mut renderer) = self.blob_image_renderer {
-                let (same_epoch, texture_cache_id) = match self.cached_images.resources
-                                                               .get(&request) {
-                    Some(entry) => {
-                        (entry.epoch == template.epoch, Some(entry.texture_cache_id))
-                    }
-                    None => {
-                        (false, None)
-                    }
-                };
 
-                // Ensure that blobs are added to the list of requested items
-                // foe the GPU cache, even if the cached blob image is up to date.
-                if let Some(texture_cache_id) = texture_cache_id {
-                    self.requested_images.insert(texture_cache_id);
+        // 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;
                 }
+            }
+            Vacant(..) => {}
+        }
 
-                if !same_epoch && self.blob_image_requests.insert(request) {
+        // 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,
                             );
@@ -452,75 +517,63 @@ impl ResourceCache {
                             height: h,
                             offset,
                             format: template.descriptor.format,
                         },
                         template.dirty_rect,
                     );
                 }
             }
-        } else {
-            self.pending_image_requests.push(request);
         }
     }
 
     pub fn request_glyphs(&mut self,
                           font: FontInstanceKey,
-                          glyph_instances: &[GlyphInstance]) {
+                          glyph_keys: &[GlyphKey]) {
         debug_assert_eq!(self.state, State::AddResources);
 
         self.glyph_rasterizer.request_glyphs(
             &mut self.cached_glyphs,
             self.current_frame_id,
             font,
-            glyph_instances,
-            &mut self.requested_glyphs,
+            glyph_keys,
         );
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         self.texture_cache.pending_updates()
     }
 
     pub fn get_glyphs<F>(&self,
                          font: FontInstanceKey,
-                         glyph_instances: &[GlyphInstance],
+                         glyph_keys: &[GlyphKey],
                          mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
         debug_assert_eq!(self.state, State::QueryResources);
-        let mut glyph_request = GlyphRequest::new(
-            font,
-            0,
-            LayoutPoint::zero(),
-        );
         let mut texture_id = None;
-        for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
-            glyph_request.key.index = glyph_instance.index;
-            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point,
-                                                                  glyph_request.font.render_mode);
+
+        let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
 
-            let image_id = self.cached_glyphs.get(&glyph_request, self.current_frame_id);
-            let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
+        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.map(|image_id| self.texture_cache.get(image_id));
             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)
     }
 
     pub fn get_glyph_dimensions(&mut self,
                                 font: &FontInstanceKey,
-                                glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
-        let key = GlyphRequest {
-            font: font.clone(),
-            key: glyph_key.clone(),
-        };
+                                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))
             }
         }
     }
@@ -569,218 +622,180 @@ impl ResourceCache {
 
         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 expire_old_resources(&mut self, frame_id: FrameId) {
-        self.cached_images.expire_old_resources(&mut self.texture_cache, frame_id);
-        self.cached_glyphs.expire_old_resources(&mut self.texture_cache, frame_id);
-    }
-
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.current_frame_id = frame_id;
-        debug_assert!(self.requested_glyphs.is_empty());
-        debug_assert!(self.requested_images.is_empty());
     }
 
     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,
-            &mut self.requested_glyphs,
             texture_cache_profile,
         );
 
-        let mut image_requests = mem::replace(&mut self.pending_image_requests, Vec::new());
-        for request in image_requests.drain(..) {
-            self.finalize_image_request(request, None, texture_cache_profile);
-        }
+        // Apply any updates of new / updated images (incl. blobs) to the texture cache.
+        self.update_texture_cache(texture_cache_profile);
 
-        let mut blob_image_requests = mem::replace(&mut self.blob_image_requests, HashSet::new());
-        if self.blob_image_renderer.is_some() {
-            for request in blob_image_requests.drain() {
-                match self.blob_image_renderer.as_mut().unwrap().resolve(request.into()) {
-                    Ok(image) => {
-                        self.finalize_image_request(request,
-                                                    Some(ImageData::new(image.data)),
-                                                    texture_cache_profile);
-                    }
-                    // TODO(nical): I think that we should handle these somewhat gracefully,
-                    // at least in the out-of-memory scenario.
-                    Err(BlobImageError::Oom) => {
-                        // This one should be recoverable-ish.
-                        panic!("Failed to render a vector image (OOM)");
-                    }
-                    Err(BlobImageError::InvalidKey) => {
-                        panic!("Invalid vector image key");
-                    }
-                    Err(BlobImageError::InvalidData) => {
-                        // TODO(nical): If we run into this we should kill the content process.
-                        panic!("Invalid vector image data");
-                    }
-                    Err(BlobImageError::Other(msg)) => {
-                        panic!("Vector image error {}", msg);
-                    }
-                }
-            }
-        }
-
-        for texture_cache_item_id in self.requested_images.drain() {
-            let item = self.texture_cache.get_mut(texture_cache_item_id);
-            if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) {
-                request.push(item.uv_rect);
-            }
-        }
-
-        for texture_cache_item_id in self.requested_glyphs.drain() {
-            let item = self.texture_cache.get_mut(texture_cache_item_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]);
-            }
-        }
+        // 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);
     }
 
-    fn update_texture_cache(&mut self,
-                            request: &ImageRequest,
-                            image_data: Option<ImageData>,
-                            texture_cache_profile: &mut TextureCacheProfileCounters) {
-        let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
-        let image_data = image_data.unwrap_or_else(||{
-            image_template.data.clone()
-        });
-
-        let filter = match request.rendering {
-            ImageRendering::Pixelated => TextureFilter::Nearest,
-            ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
-        };
+    fn update_texture_cache(&mut self, 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 descriptor = if let Some(tile) = request.tile {
-            let tile_size = image_template.tiling.unwrap();
-            let image_descriptor = &image_template.descriptor;
-
-            let (actual_width, actual_height) = compute_tile_size(image_descriptor, tile_size, tile);
+            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.
+                    image_template.data.clone()
+                }
+                ImageData::Blob(..) => {
+                    // Extract the rasterized image from the blob renderer.
+                    match self.blob_image_renderer.as_mut().unwrap().resolve(request.into()) {
+                        Ok(image) => ImageData::new(image.data),
+                        // TODO(nical): I think that we should handle these somewhat gracefully,
+                        // at least in the out-of-memory scenario.
+                        Err(BlobImageError::Oom) => {
+                            // This one should be recoverable-ish.
+                            panic!("Failed to render a vector image (OOM)");
+                        }
+                        Err(BlobImageError::InvalidKey) => {
+                            panic!("Invalid vector image key");
+                        }
+                        Err(BlobImageError::InvalidData) => {
+                            // TODO(nical): If we run into this we should kill the content process.
+                            panic!("Invalid vector image data");
+                        }
+                        Err(BlobImageError::Other(msg)) => {
+                            panic!("Vector image error {}", msg);
+                        }
+                    }
+                }
+            };
 
-            // The tiled image could be stored on the CPU as one large image or be
-            // already broken up into tiles. This affects the way we compute the stride
-            // and offset.
-            let tiled_on_cpu = image_template.data.is_blob();
-
-            let (stride, offset) = if tiled_on_cpu {
-                (image_descriptor.stride, 0)
-            } else {
-                let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
-                let stride = image_descriptor.compute_stride();
-                let offset = image_descriptor.offset + tile.y as u32 * tile_size as u32 * stride
-                                                     + tile.x as u32 * tile_size as u32 * bpp;
-                (Some(stride), offset)
+            let filter = match request.rendering {
+                ImageRendering::Pixelated => TextureFilter::Nearest,
+                ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
             };
 
-            ImageDescriptor {
-                width: actual_width,
-                height: actual_height,
-                stride,
-                offset,
-                format: image_descriptor.format,
-                is_opaque: image_descriptor.is_opaque,
-            }
-        } else {
-            image_template.descriptor.clone()
-        };
+            let descriptor = if let Some(tile) = request.tile {
+                let tile_size = image_template.tiling.unwrap();
+                let image_descriptor = &image_template.descriptor;
+
+                let (actual_width, actual_height) = compute_tile_size(image_descriptor, tile_size, tile);
+
+                // The tiled image could be stored on the CPU as one large image or be
+                // already broken up into tiles. This affects the way we compute the stride
+                // and offset.
+                let tiled_on_cpu = image_template.data.is_blob();
 
-        let image_id = match self.cached_images.entry(*request, self.current_frame_id) {
-            Occupied(entry) => {
-                let image_id = entry.get().texture_cache_id;
+                let (stride, offset) = if tiled_on_cpu {
+                    (image_descriptor.stride, 0)
+                } else {
+                    let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
+                    let stride = image_descriptor.compute_stride();
+                    let offset = image_descriptor.offset + tile.y as u32 * tile_size as u32 * stride
+                                                         + tile.x as u32 * tile_size as u32 * bpp;
+                    (Some(stride), offset)
+                };
 
-                if entry.get().epoch != image_template.epoch {
+                ImageDescriptor {
+                    width: actual_width,
+                    height: actual_height,
+                    stride,
+                    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(entry) => {
+                    let image_id = entry.get().texture_cache_id;
+
+                    // We should only get to this code path if the image
+                    // definitely needs to be updated.
+                    debug_assert!(entry.get().epoch != image_template.epoch);
                     self.texture_cache.update(image_id,
                                               descriptor,
                                               filter,
                                               image_data,
                                               image_template.dirty_rect);
 
                     // Update the cached epoch
+                    debug_assert_eq!(self.current_frame_id, entry.get().last_access);
                     *entry.into_mut() = CachedImageInfo {
                         texture_cache_id: image_id,
                         epoch: image_template.epoch,
+                        last_access: self.current_frame_id,
                     };
                     image_template.dirty_rect = None;
                 }
-
-                image_id
-            }
-            Vacant(entry) => {
-                let filter = match request.rendering {
-                    ImageRendering::Pixelated => TextureFilter::Nearest,
-                    ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
-                };
-
-                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,
-                });
-
-                image_id
-            }
-        };
+                Vacant(entry) => {
+                    let image_id = self.texture_cache.insert(descriptor,
+                                                             filter,
+                                                             image_data,
+                                                             [0.0; 2],
+                                                             texture_cache_profile);
 
-        self.requested_images.insert(image_id);
-    }
-    fn finalize_image_request(&mut self,
-                              request: ImageRequest,
-                              image_data: Option<ImageData>,
-                              texture_cache_profile: &mut TextureCacheProfileCounters) {
-        match self.resources.image_templates.get(request.key).unwrap().data {
-            ImageData::External(ext_image) => {
-                match ext_image.image_type {
-                    ExternalImageType::Texture2DHandle |
-                    ExternalImageType::TextureRectHandle |
-                    ExternalImageType::TextureExternalHandle => {
-                        // external handle doesn't need to update the texture_cache.
-                    }
-                    ExternalImageType::ExternalBuffer => {
-                        self.update_texture_cache(&request,
-                                                  image_data,
-                                                  texture_cache_profile);
-                    }
+                    entry.insert(CachedImageInfo {
+                        texture_cache_id: image_id,
+                        epoch: image_template.epoch,
+                        last_access: self.current_frame_id,
+                    });
                 }
-            }
-            ImageData::Raw(..) | ImageData::Blob(..) => {
-                self.update_texture_cache(&request,
-                                           image_data,
-                                           texture_cache_profile);
-            }
+            };
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
 
@@ -798,43 +813,49 @@ impl ResourceCache {
                                                              .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_keys(&mut self.texture_cache, |request| request.font.font_key.0 == namespace);
+        self.cached_glyphs.clear_fonts(&mut self.texture_cache, |font| font.font_key.0 == namespace);
     }
 }
 
 pub trait Resource {
-    fn texture_cache_item_id(&self) -> Option<TextureCacheItemId>;
-}
-
-impl Resource for TextureCacheItemId {
-    fn texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
-        Some(*self)
-    }
-}
-
-impl Resource for Option<TextureCacheItemId> {
-    fn texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
-        *self
-    }
+    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 texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
-        Some(self.texture_cache_id)
+    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);
+        }
     }
 }
 
-
 // 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;
     // Most tiles are going to have base_size as width and height,
     // except for tiles around the edges that are shrunk to fit the mage data
     // (See decompose_tiled_image in frame.rs).
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -1,31 +1,29 @@
 /* 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 fnv::FnvHasher;
-use std::collections::HashMap;
-use std::hash::BuildHasherDefault;
 use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch, LayerSize, LayoutSize};
 use api::{LayoutTransform, PipelineId, PropertyBinding, PropertyBindingId};
+use internal_types::FastHashMap;
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
 pub struct SceneProperties {
-    transform_properties: HashMap<PropertyBindingId, LayoutTransform>,
-    float_properties: HashMap<PropertyBindingId, f32>,
+    transform_properties: FastHashMap<PropertyBindingId, LayoutTransform>,
+    float_properties: FastHashMap<PropertyBindingId, f32>,
 }
 
 impl SceneProperties {
     pub fn new() -> SceneProperties {
         SceneProperties {
-            transform_properties: HashMap::default(),
-            float_properties: HashMap::default(),
+            transform_properties: FastHashMap::default(),
+            float_properties: FastHashMap::default(),
         }
     }
 
     /// Set the current property list for this display list.
     pub fn set_properties(&mut self, properties: DynamicProperties) {
         self.transform_properties.clear();
         self.float_properties.clear();
 
@@ -86,27 +84,27 @@ pub struct ScenePipeline {
     pub viewport_size: LayerSize,
     pub content_size: LayoutSize,
     pub background_color: Option<ColorF>,
 }
 
 /// A complete representation of the layout bundling visible pipelines together.
 pub struct Scene {
     pub root_pipeline_id: Option<PipelineId>,
-    pub pipeline_map: HashMap<PipelineId, ScenePipeline, BuildHasherDefault<FnvHasher>>,
-    pub display_lists: HashMap<PipelineId, BuiltDisplayList, BuildHasherDefault<FnvHasher>>,
+    pub pipeline_map: FastHashMap<PipelineId, ScenePipeline>,
+    pub display_lists: FastHashMap<PipelineId, BuiltDisplayList>,
     pub properties: SceneProperties,
 }
 
 impl Scene {
     pub fn new() -> Scene {
         Scene {
             root_pipeline_id: None,
-            pipeline_map: HashMap::default(),
-            display_lists: HashMap::default(),
+            pipeline_map: FastHashMap::default(),
+            display_lists: FastHashMap::default(),
             properties: SceneProperties::new(),
         }
     }
 
     pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
         self.root_pipeline_id = Some(pipeline_id);
     }
 
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -1,23 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use device::TextureFilter;
-use fnv::FnvHasher;
 use freelist::{FreeList, FreeListItem, FreeListItemId};
 use gpu_cache::GpuCacheHandle;
-use internal_types::{TextureUpdate, TextureUpdateOp, UvRect};
+use internal_types::{FastHashMap, TextureUpdate, TextureUpdateOp, UvRect};
 use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList};
 use profiler::TextureCacheProfileCounters;
 use std::cmp;
-use std::collections::HashMap;
 use std::collections::hash_map::Entry;
-use std::hash::BuildHasherDefault;
 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};
 
@@ -570,17 +567,17 @@ impl CacheTextureIdList {
 
     fn free(&mut self, id: CacheTextureId) {
         self.free_list.push(id.0);
     }
 }
 
 pub struct TextureCache {
     cache_id_list: CacheTextureIdList,
-    free_texture_levels: HashMap<ImageFormat, Vec<FreeTextureLevel>, BuildHasherDefault<FnvHasher>>,
+    free_texture_levels: FastHashMap<ImageFormat, Vec<FreeTextureLevel>>,
     items: FreeList<TextureCacheItem>,
     arena: TextureCacheArena,
     pending_updates: TextureUpdateList,
     max_texture_size: u32,
 }
 
 #[derive(PartialEq, Eq, Debug)]
 pub enum AllocationKind {
@@ -598,17 +595,17 @@ pub struct AllocationResult {
 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;
         }
 
         TextureCache {
             cache_id_list: CacheTextureIdList::new(),
-            free_texture_levels: HashMap::default(),
+            free_texture_levels: FastHashMap::default(),
             items: FreeList::new(),
             pending_updates: TextureUpdateList::new(),
             arena: TextureCacheArena::new(),
             max_texture_size,
         }
     }
 
     pub fn max_texture_size(&self) -> u32 {
@@ -683,19 +680,28 @@ impl TextureCache {
         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!(),
         };
 
+
         // TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
-        assert!(requested_size.width <= self.max_texture_size);
-        assert!(requested_size.height <= self.max_texture_size);
+        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
+        );
 
         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;
             }
             // try to coalesce it
@@ -811,18 +817,33 @@ impl TextureCache {
 
             // 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 {
-            ImageData::External(..) => {
-                panic!("Doesn't support Update() for external image.");
+            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 => {
+                        TextureUpdateOp::UpdateForExternalBuffer {
+                            rect: existing_item.allocated_rect,
+                            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();
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,47 +1,42 @@
 /* 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 fnv::FnvHasher;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheUpdateList};
 use internal_types::{BatchTextures, CacheTextureId};
-use internal_types::SourceTexture;
+use internal_types::{FastHashMap, SourceTexture};
 use mask_cache::MaskCacheInfo;
 use prim_store::{CLIP_DATA_GPU_BLOCKS, DeferredResolve, ImagePrimitiveKind, 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 std::collections::HashMap;
-use std::hash::BuildHasherDefault;
 use texture_cache::TexturePage;
 use util::{TransformedRect, TransformedRectKind};
 use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
 use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstanceKey};
 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);
 
 
-pub type DisplayListMap = HashMap<PipelineId,
-                                  BuiltDisplayList,
-                                  BuildHasherDefault<FnvHasher>>;
+pub type DisplayListMap = FastHashMap<PipelineId, BuiltDisplayList>;
 
 trait AlphaBatchHelpers {
     fn get_blend_mode(&self,
                       needs_blending: bool,
                       metadata: &PrimitiveMetadata) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
@@ -105,24 +100,24 @@ pub struct RenderPassIndex(isize);
 struct DynamicTaskInfo {
     index: RenderTaskIndex,
     rect: DeviceIntRect,
 }
 
 #[derive(Debug)]
 pub struct RenderTaskCollection {
     pub render_task_data: Vec<RenderTaskData>,
-    dynamic_tasks: HashMap<(RenderTaskKey, RenderPassIndex), DynamicTaskInfo, BuildHasherDefault<FnvHasher>>,
+    dynamic_tasks: FastHashMap<(RenderTaskKey, RenderPassIndex), DynamicTaskInfo>,
 }
 
 impl RenderTaskCollection {
     pub fn new(static_render_task_count: usize) -> RenderTaskCollection {
         RenderTaskCollection {
             render_task_data: vec![RenderTaskData::empty(); static_render_task_count],
-            dynamic_tasks: HashMap::default(),
+            dynamic_tasks: FastHashMap::default(),
         }
     }
 
     fn add(&mut self, task: &RenderTask, pass: RenderPassIndex) -> RenderTaskIndex {
         match task.id {
             RenderTaskId::Static(index) => {
                 self.render_task_data[index.0] = task.write_task_data();
                 index
@@ -488,25 +483,24 @@ impl AlphaRenderItem {
 
                         // 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.glyph_options,
+                                                        text_cpu.subpx_dir);
 
                         let texture_id = ctx.resource_cache.get_glyphs(font,
-                                                                       &text_cpu.glyph_instances,
+                                                                       &text_cpu.glyph_keys,
                                                                        |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
-                            instances.push(base_instance.build(index as i32,
-                                                               text_cpu.normal_render_mode as i32,
-                                                               uv_address));
+                            instances.push(base_instance.build(index as i32, uv_address, 0));
                         });
 
                         if texture_id != SourceTexture::Invalid {
                             let textures = BatchTextures {
                                 colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                             };
 
                             let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures);
@@ -679,26 +673,26 @@ impl AlphaBatcher {
 }
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<CacheClipInstance>,
     /// Image draws apply the image masking.
-    pub images: HashMap<SourceTexture, Vec<CacheClipInstance>>,
+    pub images: FastHashMap<SourceTexture, Vec<CacheClipInstance>>,
     pub border_clears: Vec<CacheClipInstance>,
     pub borders: Vec<CacheClipInstance>,
 }
 
 impl ClipBatcher {
     fn new() -> ClipBatcher {
         ClipBatcher {
             rectangles: Vec::new(),
-            images: HashMap::new(),
+            images: FastHashMap::default(),
             border_clears: Vec::new(),
             borders: Vec::new(),
         }
     }
 
     fn add<'a>(&mut self,
                task_index: RenderTaskIndex,
                clips: &[(PackedLayerIndex, MaskCacheInfo)],
@@ -1007,29 +1001,27 @@ impl RenderTarget for ColorRenderTarget 
                 //           correct id here.
                 let child_pass_index = RenderPassIndex(pass_index.0 - 1);
                 let task_key = RenderTaskKey::CachePrimitive(PrimitiveCacheKey::TextShadow(prim_index));
                 let src_id = RenderTaskId::Dynamic(task_key);
                 self.vertical_blurs.push(BlurCommand {
                     task_id: render_tasks.get_task_index(&task.id, pass_index).0 as i32,
                     src_task_id: render_tasks.get_task_index(&src_id, child_pass_index).0 as i32,
                     blur_direction: BlurDirection::Vertical as i32,
-                    padding: 0,
                 });
             }
             RenderTaskKind::HorizontalBlur(blur_radius, prim_index) => {
                 // Find the child render task that we are applying
                 // a horizontal blur on.
                 let child_pass_index = RenderPassIndex(pass_index.0 - 1);
                 let src_id = RenderTaskId::Dynamic(RenderTaskKey::VerticalBlur(blur_radius.0, prim_index));
                 self.horizontal_blurs.push(BlurCommand {
                     task_id: render_tasks.get_task_index(&task.id, pass_index).0 as i32,
                     src_task_id: render_tasks.get_task_index(&src_id, child_pass_index).0 as i32,
                     blur_direction: BlurDirection::Horizontal as i32,
-                    padding: 0,
                 });
             }
             RenderTaskKind::CachePrimitive(prim_index) => {
                 let prim_metadata = ctx.prim_store.get_metadata(prim_index);
 
                 let prim_address = prim_metadata.gpu_location.as_int(gpu_cache);
 
                 match prim_metadata.prim_kind {
@@ -1065,20 +1057,21 @@ impl RenderTarget for ColorRenderTarget 
                                     // the shader to fetch the text-shadow parameters.
                                     let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0];
                                     let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
                                     let font = FontInstanceKey::new(text.font_key,
                                                                     font_size_dp,
                                                                     text.color,
                                                                     text.shadow_render_mode,
-                                                                    text.glyph_options);
+                                                                    text.glyph_options,
+                                                                    text.subpx_dir);
 
                                     let texture_id = ctx.resource_cache.get_glyphs(font,
-                                                                                   &text.glyph_instances,
+                                                                                   &text.glyph_keys,
                                                                                    |index, handle| {
                                         let uv_address = handle.as_int(gpu_cache);
                                         instances.push(instance.build(index as i32,
                                                                       uv_address,
                                                                       prim_address));
                                     });
 
                                     if texture_id != SourceTexture::Invalid {
@@ -1371,17 +1364,16 @@ fn textures_compatible(t1: SourceTexture
 }
 
 // All Packed Primitives below must be 16 byte aligned.
 #[derive(Debug)]
 pub struct BlurCommand {
     task_id: i32,
     src_task_id: i32,
     blur_direction: i32,
-    padding: i32,
 }
 
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Clone, Copy, Debug)]
 pub struct CacheClipInstance {
     task_id: i32,
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -10,16 +10,17 @@ nightly = ["euclid/unstable", "serde/uns
 ipc = ["ipc-channel"]
 webgl = ["offscreen_gl_context"]
 
 [dependencies]
 app_units = "0.5"
 bincode = "0.8"
 byteorder = "1.0"
 euclid = "0.15"
+fxhash = "0.2.1"
 gleam = "0.4.5"
 heapsize = ">= 0.3.6, < 0.5"
 ipc-channel = {version = "0.8", optional = true}
 offscreen_gl_context = {version = "0.11", features = ["serde"], optional = true}
 serde = { version = "1.0", features = ["rc", "derive"] }
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -12,99 +12,189 @@ use {BuiltDisplayList, BuiltDisplayListD
 use {DeviceUintRect, DeviceUintSize, FontKey, GlyphDimensions, GlyphKey};
 use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutVector2D, LayoutSize, LayoutTransform};
 use {FontInstanceKey, NativeFontHandle, WorldPoint};
 #[cfg(feature = "webgl")]
 use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
+/// The resource updates for a given transaction (they must be applied in the same frame).
+#[derive(Clone, Deserialize, Serialize)]
+pub struct ResourceUpdates {
+    pub updates: Vec<ResourceUpdate>,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub enum ResourceUpdate {
+    AddImage(AddImage),
+    UpdateImage(UpdateImage),
+    DeleteImage(ImageKey),
+    AddFont(AddFont),
+    DeleteFont(FontKey),
+}
+
+impl ResourceUpdates {
+    pub fn new() -> Self {
+        ResourceUpdates {
+            updates: Vec::new(),
+        }
+    }
+
+    pub fn add_image(
+        &mut self,
+        key: ImageKey,
+        descriptor: ImageDescriptor,
+        data: ImageData,
+        tiling: Option<TileSize>
+    ) {
+        self.updates.push(ResourceUpdate::AddImage(AddImage { key, descriptor, data, tiling }));
+    }
+
+    pub fn update_image(
+        &mut self,
+        key: ImageKey,
+        descriptor: ImageDescriptor,
+        data: ImageData,
+        dirty_rect: Option<DeviceUintRect>
+    ) {
+        self.updates.push(ResourceUpdate::UpdateImage(UpdateImage { key, descriptor, data, dirty_rect }));
+    }
+
+    pub fn delete_image(&mut self, key: ImageKey) {
+        self.updates.push(ResourceUpdate::DeleteImage(key));
+    }
+
+    pub fn add_raw_font(&mut self, key: FontKey, bytes: Vec<u8>, index: u32) {
+        self.updates.push(ResourceUpdate::AddFont(AddFont::Raw(key, bytes, index)));
+    }
+
+    pub fn add_native_font(&mut self, key: FontKey, native_handle: NativeFontHandle) {
+        self.updates.push(ResourceUpdate::AddFont(AddFont::Native(key, native_handle)));
+    }
+
+    pub fn delete_font(&mut self, key: FontKey) {
+        self.updates.push(ResourceUpdate::DeleteFont(key));
+    }
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub struct AddImage {
+    pub key: ImageKey,
+    pub descriptor: ImageDescriptor,
+    pub data: ImageData,
+    pub tiling: Option<TileSize>,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub struct UpdateImage {
+    pub key: ImageKey,
+    pub descriptor: ImageDescriptor,
+    pub data: ImageData,
+    pub dirty_rect: Option<DeviceUintRect>,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub enum AddFont {
+    Raw(FontKey, Vec<u8>, u32),
+    Native(FontKey, NativeFontHandle),
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub enum DocumentMsg {
+    SetDisplayList {
+        list_descriptor: BuiltDisplayListDescriptor,
+        epoch: Epoch,
+        pipeline_id: PipelineId,
+        background: Option<ColorF>,
+        viewport_size: LayoutSize,
+        content_size: LayoutSize,
+        preserve_frame_state: bool,
+        resources: ResourceUpdates,
+    },
+    SetPageZoom(ZoomFactor),
+    SetPinchZoom(ZoomFactor),
+    SetPan(DeviceIntPoint),
+    SetRootPipeline(PipelineId),
+    SetWindowParameters {
+        window_size: DeviceUintSize,
+        inner_rect: DeviceUintRect,
+    },
+    Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
+    ScrollNodeWithId(LayoutPoint, ClipId, ScrollClamping),
+    TickScrollingBounce,
+    GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
+    GenerateFrame(Option<DynamicProperties>),
+}
+
+impl fmt::Debug for DocumentMsg {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(match *self {
+            DocumentMsg::SetDisplayList{..} => "DocumentMsg::SetDisplayList",
+            DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
+            DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
+            DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
+            DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
+            DocumentMsg::SetWindowParameters{..} => "DocumentMsg::SetWindowParameters",
+            DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
+            DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
+            DocumentMsg::TickScrollingBounce => "DocumentMsg::TickScrollingBounce",
+            DocumentMsg::GetScrollNodeState(..) => "DocumentMsg::GetScrollNodeState",
+            DocumentMsg::GenerateFrame(..) => "DocumentMsg::GenerateFrame",
+        })
+    }
+}
+
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
-    AddRawFont(FontKey, Vec<u8>, u32),
-    AddNativeFont(FontKey, NativeFontHandle),
-    DeleteFont(FontKey),
+    /// Add/remove/update images and fonts.
+    UpdateResources(ResourceUpdates),
     /// Gets the glyph dimensions
     GetGlyphDimensions(FontInstanceKey, Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
     /// Gets the glyph indices from a string
     GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
-    /// Adds an image from the resource cache.
-    AddImage(ImageKey, ImageDescriptor, ImageData, Option<TileSize>),
-    /// Updates the the resource cache with the new image data.
-    UpdateImage(ImageKey, ImageDescriptor, ImageData, Option<DeviceUintRect>),
-    /// Drops an image from the resource cache.
-    DeleteImage(ImageKey),
+    /// Adds a new document namespace.
     CloneApi(MsgSender<IdNamespace>),
-    /// Supplies a new frame to WebRender.
-    ///
-    /// After receiving this message, WebRender will read the display list from the payload channel.
-    // TODO: We should consider using named members to avoid confusion.
-    SetDisplayList(Option<ColorF>,
-                   Epoch,
-                   PipelineId,
-                   LayoutSize, // viewport_size
-                   LayoutSize, // content size
-                   BuiltDisplayListDescriptor,
-                   bool),
-    SetPageZoom(ZoomFactor),
-    SetPinchZoom(ZoomFactor),
-    SetPan(DeviceIntPoint),
-    SetRootPipeline(PipelineId),
-    SetWindowParameters(DeviceUintSize, DeviceUintRect),
-    Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
-    ScrollNodeWithId(LayoutPoint, ClipId, ScrollClamping),
-    TickScrollingBounce,
-    TranslatePointToLayerSpace(WorldPoint, MsgSender<(LayoutPoint, PipelineId)>),
-    GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
+    /// Adds a new document with given initial size.
+    AddDocument(DocumentId, DeviceUintSize),
+    /// A message targeted at a particular document.
+    UpdateDocument(DocumentId, DocumentMsg),
+    /// Deletes an existing document.
+    DeleteDocument(DocumentId),
     RequestWebGLContext(DeviceIntSize, GLContextAttributes, MsgSender<Result<(WebGLContextId, GLLimits), String>>),
     ResizeWebGLContext(WebGLContextId, DeviceIntSize),
     WebGLCommand(WebGLContextId, WebGLCommand),
-    GenerateFrame(Option<DynamicProperties>),
     // WebVR commands that must be called in the WebGL render thread.
     VRCompositorCommand(WebGLContextId, VRCompositorCommand),
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
-    /// Remove all resources associated with this namespace.
+    /// Removes all resources associated with a namespace.
     ClearNamespace(IdNamespace),
     ShutDown,
 }
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
-            ApiMsg::AddRawFont(..) => "ApiMsg::AddRawFont",
-            ApiMsg::AddNativeFont(..) => "ApiMsg::AddNativeFont",
-            ApiMsg::DeleteFont(..) => "ApiMsg::DeleteFont",
+            ApiMsg::UpdateResources(..) => "ApiMsg::UpdateResources",
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
             ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
-            ApiMsg::AddImage(..) => "ApiMsg::AddImage",
-            ApiMsg::UpdateImage(..) => "ApiMsg::UpdateImage",
-            ApiMsg::DeleteImage(..) => "ApiMsg::DeleteImage",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
-            ApiMsg::SetDisplayList(..) => "ApiMsg::SetDisplayList",
-            ApiMsg::SetRootPipeline(..) => "ApiMsg::SetRootPipeline",
-            ApiMsg::Scroll(..) => "ApiMsg::Scroll",
-            ApiMsg::ScrollNodeWithId(..) => "ApiMsg::ScrollNodeWithId",
-            ApiMsg::TickScrollingBounce => "ApiMsg::TickScrollingBounce",
-            ApiMsg::TranslatePointToLayerSpace(..) => "ApiMsg::TranslatePointToLayerSpace",
-            ApiMsg::GetScrollNodeState(..) => "ApiMsg::GetScrollNodeState",
+            ApiMsg::AddDocument(..) => "ApiMsg::AddDocument",
+            ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
+            ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
             ApiMsg::RequestWebGLContext(..) => "ApiMsg::RequestWebGLContext",
             ApiMsg::ResizeWebGLContext(..) => "ApiMsg::ResizeWebGLContext",
             ApiMsg::WebGLCommand(..) => "ApiMsg::WebGLCommand",
-            ApiMsg::GenerateFrame(..) => "ApiMsg::GenerateFrame",
             ApiMsg::VRCompositorCommand(..) => "ApiMsg::VRCompositorCommand",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
+            ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
-            ApiMsg::SetPageZoom(..) => "ApiMsg::SetPageZoom",
-            ApiMsg::SetPinchZoom(..) => "ApiMsg::SetPinchZoom",
-            ApiMsg::SetPan(..) => "ApiMsg::SetPan",
-            ApiMsg::SetWindowParameters(..) => "ApiMsg::SetWindowParameters",
-            ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
         })
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
@@ -122,22 +212,41 @@ pub struct GLLimits([u8; 0]);
 
 #[cfg(not(feature = "webgl"))]
 #[derive(Clone, Deserialize, Serialize)]
 pub enum WebGLCommand {
     Flush,
 }
 
 #[repr(C)]
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub struct PipelineId(pub u32, pub u32);
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
+pub struct IdNamespace(pub u32);
 
 #[repr(C)]
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
-pub struct IdNamespace(pub u32);
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct DocumentId(pub IdNamespace, pub u32);
+
+/// This type carries no valuable semantics for WR. However, it reflects the fact that
+/// clients (Servo) may generate pipelines by different semi-independent sources.
+/// These pipelines still belong to the same `IdNamespace` and the same `DocumentId`.
+/// Having this extra Id field enables them to generate `PipelineId` without collision.
+pub type PipelineSourceId = u32;
+
+/// From the point of view of WR, `PipelineId` is completely opaque and generic as long as
+/// it's clonable, serializable, comparable, and hashable.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct PipelineId(pub PipelineSourceId, pub u32);
+
+impl PipelineId {
+    pub fn dummy() -> Self {
+        PipelineId(0, 0)
+    }
+}
+
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct ResourceId(pub u32);
 
 /// An opaque pointer-sized value.
 #[repr(C)]
 #[derive(Clone, Deserialize, Serialize)]
@@ -163,70 +272,71 @@ pub enum ScrollClamping {
 pub struct RenderApiSender {
     api_sender: MsgSender<ApiMsg>,
     payload_sender: PayloadSender,
 }
 
 impl RenderApiSender {
     pub fn new(api_sender: MsgSender<ApiMsg>,
                payload_sender: PayloadSender)
-               -> RenderApiSender {
+               -> Self {
         RenderApiSender {
             api_sender,
             payload_sender,
         }
     }
 
+    /// Creates a new resource API object with a dedicated namespace.
     pub fn create_api(&self) -> RenderApi {
-        let RenderApiSender {
-            ref api_sender,
-            ref payload_sender
-        } = *self;
         let (sync_tx, sync_rx) = channel::msg_channel().unwrap();
         let msg = ApiMsg::CloneApi(sync_tx);
-        api_sender.send(msg).unwrap();
+        self.api_sender.send(msg).unwrap();
         RenderApi {
-            api_sender: api_sender.clone(),
-            payload_sender: payload_sender.clone(),
-            id_namespace: sync_rx.recv().unwrap(),
+            api_sender: self.api_sender.clone(),
+            payload_sender: self.payload_sender.clone(),
+            namespace_id: sync_rx.recv().unwrap(),
             next_id: Cell::new(ResourceId(0)),
         }
     }
 }
 
 pub struct RenderApi {
-    pub api_sender: MsgSender<ApiMsg>,
-    pub payload_sender: PayloadSender,
-    pub id_namespace: IdNamespace,
-    pub next_id: Cell<ResourceId>,
+    api_sender: MsgSender<ApiMsg>,
+    payload_sender: PayloadSender,
+    namespace_id: IdNamespace,
+    next_id: Cell<ResourceId>,
 }
 
 impl RenderApi {
+    pub fn get_namespace_id(&self) -> IdNamespace {
+        self.namespace_id
+    }
+
     pub fn clone_sender(&self) -> RenderApiSender {
         RenderApiSender::new(self.api_sender.clone(), self.payload_sender.clone())
     }
 
+    pub fn add_document(&self, initial_size: DeviceUintSize) -> DocumentId {
+        let new_id = self.next_unique_id();
+        let document_id = DocumentId(self.namespace_id, new_id);
+
+        let msg = ApiMsg::AddDocument(document_id, initial_size);
+        self.api_sender.send(msg).unwrap();
+
+        document_id
+    }
+
+    pub fn delete_document(&self, document_id: DocumentId) {
+        let msg = ApiMsg::DeleteDocument(document_id);
+        self.api_sender.send(msg).unwrap();
+    }
+
     pub fn generate_font_key(&self) -> FontKey {
         let new_id = self.next_unique_id();
-        FontKey::new(new_id.0, new_id.1)
-    }
-
-    pub fn add_raw_font(&self, key: FontKey, bytes: Vec<u8>, index: u32) {
-        let msg = ApiMsg::AddRawFont(key, bytes, index);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn add_native_font(&self, key: FontKey, native_font_handle: NativeFontHandle) {
-        let msg = ApiMsg::AddNativeFont(key, native_font_handle);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn delete_font(&self, key: FontKey) {
-        let msg = ApiMsg::DeleteFont(key);
-        self.api_sender.send(msg).unwrap();
+        FontKey::new(self.namespace_id, new_id)
     }
 
     /// Gets the dimensions for the supplied glyph keys
     ///
     /// Note: Internally, the internal texture cache doesn't store
     /// 'empty' textures (height or width = 0)
     /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None.
     pub fn get_glyph_dimensions(&self,
@@ -248,165 +358,22 @@ impl RenderApi {
         let msg = ApiMsg::GetGlyphIndices(font_key, text.to_string(), tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
     /// Creates an `ImageKey`.
     pub fn generate_image_key(&self) -> ImageKey {
         let new_id = self.next_unique_id();
-        ImageKey::new(new_id.0, new_id.1)
+        ImageKey::new(self.namespace_id, new_id)
     }
 
     /// Adds an image identified by the `ImageKey`.
-    pub fn add_image(&self,
-                     key: ImageKey,
-                     descriptor: ImageDescriptor,
-                     data: ImageData,
-                     tiling: Option<TileSize>) {
-        let msg = ApiMsg::AddImage(key, descriptor, data, tiling);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    /// Updates a specific image.
-    ///
-    /// Currently doesn't support changing dimensions or format by updating.
-    // TODO: Support changing dimensions (and format) during image update?
-    pub fn update_image(&self,
-                        key: ImageKey,
-                        descriptor: ImageDescriptor,
-                        data: ImageData,
-                        dirty_rect: Option<DeviceUintRect>) {
-        let msg = ApiMsg::UpdateImage(key, descriptor, data, dirty_rect);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    /// Deletes the specific image.
-    pub fn delete_image(&self, key: ImageKey) {
-        let msg = ApiMsg::DeleteImage(key);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    /// Sets the root pipeline.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use webrender_api::{PipelineId, RenderApiSender};
-    /// # fn example(sender: RenderApiSender) {
-    /// let api = sender.create_api();
-    /// // ...
-    /// let pipeline_id = PipelineId(0, 0);
-    /// api.set_root_pipeline(pipeline_id);
-    /// # }
-    /// ```
-    pub fn set_root_pipeline(&self, pipeline_id: PipelineId) {
-        let msg = ApiMsg::SetRootPipeline(pipeline_id);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    /// Supplies a new frame to WebRender.
-    ///
-    /// Non-blocking, it notifies a worker process which processes the display list.
-    /// When it's done and a RenderNotifier has been set in `webrender::renderer::Renderer`,
-    /// [new_frame_ready()][notifier] gets called.
-    ///
-    /// Note: Scrolling doesn't require an own Frame.
-    ///
-    /// Arguments:
-    ///
-    /// * `background_color`: The background color of this pipeline.
-    /// * `epoch`: The unique Frame ID, monotonically increasing.
-    /// * `viewport_size`: The size of the viewport for this frame.
-    /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
-    /// * `content_size`: The total screen space size of this display list's display items.
-    /// * `display_list`: The root Display list used in this frame.
-    /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline
-    ///                           id, this setting determines if frame state (such as scrolling
-    ///                           position) should be preserved for this new display list.
-    ///
-    /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
-    pub fn set_display_list(&self,
-                            background_color: Option<ColorF>,
-                            epoch: Epoch,
-                            viewport_size: LayoutSize,
-                            (pipeline_id, content_size, display_list): (PipelineId, LayoutSize, BuiltDisplayList),
-                            preserve_frame_state: bool) {
-        let (display_list_data, display_list_descriptor) = display_list.into_data();
-        let msg = ApiMsg::SetDisplayList(background_color,
-                                         epoch,
-                                         pipeline_id,
-                                         viewport_size,
-                                         content_size,
-                                         display_list_descriptor,
-                                         preserve_frame_state);
-        self.api_sender.send(msg).unwrap();
-
-        self.payload_sender.send_payload(Payload {
-            epoch,
-            pipeline_id,
-            display_list_data,
-        }).unwrap();
-    }
-
-    /// Scrolls the scrolling layer under the `cursor`
-    ///
-    /// WebRender looks for the layer closest to the user
-    /// which has `ScrollPolicy::Scrollable` set.
-    pub fn scroll(&self, scroll_location: ScrollLocation, cursor: WorldPoint, phase: ScrollEventPhase) {
-        let msg = ApiMsg::Scroll(scroll_location, cursor, phase);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn scroll_node_with_id(&self, origin: LayoutPoint, id: ClipId, clamp: ScrollClamping) {
-        let msg = ApiMsg::ScrollNodeWithId(origin, id, clamp);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn set_page_zoom(&self, page_zoom: ZoomFactor) {
-        let msg = ApiMsg::SetPageZoom(page_zoom);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn set_pinch_zoom(&self, pinch_zoom: ZoomFactor) {
-        let msg = ApiMsg::SetPinchZoom(pinch_zoom);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn set_pan(&self, pan: DeviceIntPoint) {
-        let msg = ApiMsg::SetPan(pan);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn set_window_parameters(&self,
-                                 window_size: DeviceUintSize,
-                                 inner_rect: DeviceUintRect) {
-        let msg = ApiMsg::SetWindowParameters(window_size, inner_rect);
-        self.api_sender.send(msg).unwrap();
-    }
-
-    pub fn tick_scrolling_bounce_animations(&self) {
-        let msg = ApiMsg::TickScrollingBounce;
-        self.api_sender.send(msg).unwrap();
-    }
-
-    /// Translates a point from viewport coordinates to layer space
-    pub fn translate_point_to_layer_space(&self, point: &WorldPoint)
-                                          -> (LayoutPoint, PipelineId) {
-        let (tx, rx) = channel::msg_channel().unwrap();
-        let msg = ApiMsg::TranslatePointToLayerSpace(*point, tx);
-        self.api_sender.send(msg).unwrap();
-        rx.recv().unwrap()
-    }
-
-    pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
-        let (tx, rx) = channel::msg_channel().unwrap();
-        let msg = ApiMsg::GetScrollNodeState(tx);
-        self.api_sender.send(msg).unwrap();
-        rx.recv().unwrap()
+    pub fn update_resources(&self, resources: ResourceUpdates) {
+        self.api_sender.send(ApiMsg::UpdateResources(resources)).unwrap();
     }
 
     pub fn request_webgl_context(&self, size: &DeviceIntSize, attributes: GLContextAttributes)
                                  -> Result<(WebGLContextId, GLLimits), String> {
         let (tx, rx) = channel::msg_channel().unwrap();
         let msg = ApiMsg::RequestWebGLContext(*size, attributes, tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
@@ -417,24 +384,16 @@ impl RenderApi {
         self.api_sender.send(msg).unwrap();
     }
 
     pub fn send_webgl_command(&self, context_id: WebGLContextId, command: WebGLCommand) {
         let msg = ApiMsg::WebGLCommand(context_id, command);
         self.api_sender.send(msg).unwrap();
     }
 
-    /// Generate a new frame. Optionally, supply a list of animated
-    /// property bindings that should be used to resolve bindings
-    /// in the current display list.
-    pub fn generate_frame(&self, property_bindings: Option<DynamicProperties>) {
-        let msg = ApiMsg::GenerateFrame(property_bindings);
-        self.api_sender.send(msg).unwrap();
-    }
-
     pub fn send_vr_compositor_command(&self, context_id: WebGLContextId, command: VRCompositorCommand) {
         let msg = ApiMsg::VRCompositorCommand(context_id, command);
         self.api_sender.send(msg).unwrap();
     }
 
     pub fn send_external_event(&self, evt: ExternalEvent) {
         let msg = ApiMsg::ExternalEvent(evt);
         self.api_sender.send(msg).unwrap();
@@ -445,34 +404,179 @@ impl RenderApi {
     }
 
     /// Create a new unique key that can be used for
     /// animated property bindings.
     pub fn generate_property_binding_key<T: Copy>(&self) -> PropertyBindingKey<T> {
         let new_id = self.next_unique_id();
         PropertyBindingKey {
             id: PropertyBindingId {
-                namespace: new_id.0,
-                uid: new_id.1,
+                namespace: self.namespace_id,
+                uid: new_id,
             },
             _phantom: PhantomData,
         }
     }
 
     #[inline]
-    fn next_unique_id(&self) -> (IdNamespace, u32) {
+    fn next_unique_id(&self) -> u32 {
         let ResourceId(id) = self.next_id.get();
         self.next_id.set(ResourceId(id + 1));
-        (self.id_namespace, id)
+        id
+    }
+
+    // For use in Wrench only
+    #[doc(hidden)]
+    pub fn send_message(&self, msg: ApiMsg) {
+        self.api_sender.send(msg).unwrap();
+    }
+
+    // For use in Wrench only
+    #[doc(hidden)]
+    pub fn send_payload(&self, data: &[u8]) {
+        self.payload_sender.send_payload(Payload::from_data(data)).unwrap();
+    }
+
+    /// A helper method to send document messages.
+    fn send(&self, document_id: DocumentId, msg: DocumentMsg) {
+        // This assertion fails on Servo use-cases, because it creates different
+        // `RenderApi` instances for layout and compositor.
+        //assert_eq!(document_id.0, self.namespace_id);
+        self.api_sender.send(ApiMsg::UpdateDocument(document_id, msg)).unwrap()
+    }
+
+        /// Sets the root pipeline.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use webrender_api::{DeviceUintSize, PipelineId, RenderApiSender};
+    /// # fn example(sender: RenderApiSender) {
+    /// let api = sender.create_api();
+    /// let document_id = api.add_document(DeviceUintSize::zero());
+    /// let pipeline_id = PipelineId(0, 0);
+    /// api.set_root_pipeline(document_id, pipeline_id);
+    /// # }
+    /// ```
+    pub fn set_root_pipeline(&self, document_id: DocumentId, pipeline_id: PipelineId) {
+        self.send(document_id, DocumentMsg::SetRootPipeline(pipeline_id));
+    }
+
+    /// Supplies a new frame to WebRender.
+    ///
+    /// Non-blocking, it notifies a worker process which processes the display list.
+    /// When it's done and a RenderNotifier has been set in `webrender::renderer::Renderer`,
+    /// [new_frame_ready()][notifier] gets called.
+    ///
+    /// Note: Scrolling doesn't require an own Frame.
+    ///
+    /// Arguments:
+    ///
+    /// * `document_id`: Target Document ID.
+    /// * `epoch`: The unique Frame ID, monotonically increasing.
+    /// * `background`: The background color of this pipeline.
+    /// * `viewport_size`: The size of the viewport for this frame.
+    /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
+    /// * `content_size`: The total screen space size of this display list's display items.
+    /// * `display_list`: The root Display list used in this frame.
+    /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline
+    ///                           id, this setting determines if frame state (such as scrolling
+    ///                           position) should be preserved for this new display list.
+    /// * `resources`: A set of resource updates that must be applied at the same time as the
+    ///                display list.
+    ///
+    /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
+    pub fn set_display_list(
+        &self,
+        document_id: DocumentId,
+        epoch: Epoch,
+        background: Option<ColorF>,
+        viewport_size: LayoutSize,
+        (pipeline_id, content_size, display_list): (PipelineId, LayoutSize, BuiltDisplayList),
+        preserve_frame_state: bool,
+        resources: ResourceUpdates,
+    ) {
+        let (display_list_data, list_descriptor) = display_list.into_data();
+        self.send(document_id, DocumentMsg::SetDisplayList {
+            epoch,
+            pipeline_id,
+            background,
+            viewport_size,
+            content_size,
+            list_descriptor,
+            preserve_frame_state,
+            resources,
+        });
+
+        self.payload_sender.send_payload(Payload {
+            epoch,
+            pipeline_id,
+            display_list_data,
+        }).unwrap();
+    }
+
+    /// Scrolls the scrolling layer under the `cursor`
+    ///
+    /// WebRender looks for the layer closest to the user
+    /// which has `ScrollPolicy::Scrollable` set.
+    pub fn scroll(&self, document_id: DocumentId, scroll_location: ScrollLocation,
+                  cursor: WorldPoint, phase: ScrollEventPhase) {
+        self.send(document_id, DocumentMsg::Scroll(scroll_location, cursor, phase));
+    }
+
+    pub fn scroll_node_with_id(&self, document_id: DocumentId, origin: LayoutPoint,
+                               id: ClipId, clamp: ScrollClamping) {
+        self.send(document_id, DocumentMsg::ScrollNodeWithId(origin, id, clamp));
+    }
+
+    pub fn set_page_zoom(&self, document_id: DocumentId, page_zoom: ZoomFactor) {
+        self.send(document_id, DocumentMsg::SetPageZoom(page_zoom));
+    }
+
+    pub fn set_pinch_zoom(&self, document_id: DocumentId, pinch_zoom: ZoomFactor) {
+        self.send(document_id, DocumentMsg::SetPinchZoom(pinch_zoom));
+    }
+
+    pub fn set_pan(&self, document_id: DocumentId, pan: DeviceIntPoint) {
+        self.send(document_id, DocumentMsg::SetPan(pan));
+    }
+
+    pub fn set_window_parameters(&self,
+                                 document_id: DocumentId,
+                                 window_size: DeviceUintSize,
+                                 inner_rect: DeviceUintRect) {
+        self.send(document_id, DocumentMsg::SetWindowParameters {
+            window_size,
+            inner_rect,
+        });
+    }
+
+    pub fn tick_scrolling_bounce_animations(&self, document_id: DocumentId) {
+        self.send(document_id, DocumentMsg::TickScrollingBounce);
+    }
+
+    pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollLayerState> {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        self.send(document_id, DocumentMsg::GetScrollNodeState(tx));
+        rx.recv().unwrap()
+    }
+
+    /// Generate a new frame. Optionally, supply a list of animated
+    /// property bindings that should be used to resolve bindings
+    /// in the current display list.
+    pub fn generate_frame(&self, document_id: DocumentId,
+                          property_bindings: Option<DynamicProperties>) {
+        self.send(document_id, DocumentMsg::GenerateFrame(property_bindings));
     }
 }
 
 impl Drop for RenderApi {
     fn drop(&mut self) {
-        let _ = self.api_sender.send(ApiMsg::ClearNamespace(self.id_namespace));
+        let msg = ApiMsg::ClearNamespace(self.namespace_id);
+        let _ = self.api_sender.send(msg);
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum ScrollEventPhase {
     /// The user started scrolling.
     Start,
     /// The user performed a scroll. The Boolean flag indicates whether the user's fingers are
--- a/gfx/webrender_api/src/color.rs
+++ b/gfx/webrender_api/src/color.rs
@@ -1,12 +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 std::hash::{Hash, Hasher};
+
 /// Represents RGBA screen colors with floating point numbers.
 ///
 /// All components must be between 0.0 and 1.0.
 /// An alpha value of 1.0 is opaque while 0.0 is fully transparent.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ColorF {
     pub r: f32,
@@ -45,16 +47,36 @@ impl ColorF {
     ///
     /// In premultiplied colors transistions to transparent always look "nice"
     /// therefore they are used in CSS gradients.
     pub fn premultiplied(&self) -> ColorF {
         self.scale_rgb(self.a)
     }
 }
 
+// Floats don't impl Hash/Eq...
+impl Eq for ColorF { }
+impl Hash for ColorF {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        // Note: this is inconsistent with the Eq impl for -0.0 (don't care).
+        self.r._to_bits().hash(state);
+        self.g._to_bits().hash(state);
+        self.b._to_bits().hash(state);
+        self.a._to_bits().hash(state);
+    }
+}
+
+// FIXME: remove this when Rust 1.21 is stable (float_bits_conv)
+trait ToBits {
+    fn _to_bits(self) -> u32;
+}
+impl ToBits for f32 {
+    fn _to_bits(self) -> u32 { unsafe { ::std::mem::transmute(self) } }
+}
+
 /// Represents RGBA screen colors with one byte per channel.
 ///
 /// If the alpha value `a` is 255 the color is opaque.
 #[repr(C)]
 #[derive(Clone, Copy, Hash, Eq, Debug, Deserialize, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct ColorU {
     pub r: u8,
     pub g: u8,
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -665,8 +665,9 @@ macro_rules! define_empty_heap_size_of {
 
 define_empty_heap_size_of!(ClipId);
 define_empty_heap_size_of!(RepeatMode);
 define_empty_heap_size_of!(ImageKey);
 define_empty_heap_size_of!(MixBlendMode);
 define_empty_heap_size_of!(TransformStyle);
 define_empty_heap_size_of!(LocalClip);
 define_empty_heap_size_of!(ScrollSensitivity);
+define_empty_heap_size_of!(ClipAndScrollInfo);
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -4,24 +4,25 @@
 
 use app_units::Au;
 use bincode;
 use serde::{Deserialize, Serialize, Serializer};
 use serde::ser::{SerializeSeq, SerializeMap};
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
-use {ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem};
-use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LineDisplayItem};
-use {LineOrientation, LineStyle, LocalClip, MixBlendMode, PipelineId, PropertyBinding};
-use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
+use {ExtendMode, FilterOp, FontKey, GlyphIndex, GlyphInstance, GlyphOptions, Gradient};
+use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
+use {ImageRendering, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
+use {LineDisplayItem, LineOrientation, LineStyle, LocalClip, MixBlendMode, PipelineId};
+use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
 use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
 use {SpecificDisplayItem, StackingContext, TextDisplayItem, TextShadow, TransformStyle};
 use {WebGLContextId, WebGLDisplayItem, YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {FastHashMap, FastHashSet};
 use std::marker::PhantomData;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
@@ -56,16 +57,18 @@ pub struct BuiltDisplayList {
 #[derive(Copy, Clone, Default, Deserialize, Serialize)]
 pub struct BuiltDisplayListDescriptor {
     /// The first IPC time stamp: before any work has been done
     builder_start_time: u64,
     /// The second IPC time stamp: after serialization
     builder_finish_time: u64,
     /// The third IPC time stamp: just before sending
     send_start_time: u64,
+    /// The offset where DisplayItems stop and the Glyph list starts
+    glyph_offset: usize,
 }
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: DisplayItem,
     cur_stops: ItemRange<GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
@@ -73,16 +76,22 @@ pub struct BuiltDisplayListIter<'a> {
     cur_complex_clip: (ItemRange<ComplexClipRegion>, usize),
     peeking: Peek,
 }
 
 pub struct DisplayItemRef<'a: 'b, 'b> {
     iter: &'b BuiltDisplayListIter<'a>,
 }
 
+pub struct GlyphsIter<'a> {
+    list: &'a BuiltDisplayList,
+    data: &'a [u8],
+}
+
+
 #[derive(PartialEq)]
 enum Peek {
     StartPeeking,
     IsPeeking,
     NotPeeking,
 }
 
 #[derive(Clone)]
@@ -107,49 +116,83 @@ impl BuiltDisplayList {
         self.descriptor.send_start_time = precise_time_ns();
         (self.data, self.descriptor)
     }
 
     pub fn data(&self) -> &[u8] {
         &self.data[..]
     }
 
+    pub fn item_slice(&self) -> &[u8] {
+        &self.data[..self.descriptor.glyph_offset]
+    }
+
+    pub fn glyph_slice(&self) -> &[u8] {
+        &self.data[self.descriptor.glyph_offset..]
+    }
+
     pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
         &self.descriptor
     }
 
     pub fn times(&self) -> (u64, u64, u64) {
       (self.descriptor.builder_start_time,
        self.descriptor.builder_finish_time,
        self.descriptor.send_start_time)
     }
 
     pub fn iter(&self) -> BuiltDisplayListIter {
         BuiltDisplayListIter::new(self)
     }
 
+    pub fn glyphs(&self) -> GlyphsIter {
+        GlyphsIter {
+            list: self,
+            data: self.glyph_slice(),
+        }
+    }
+
     pub fn get<'de, T: Deserialize<'de>>(&self, range: ItemRange<T>) -> AuxIter<T> {
         AuxIter::new(&self.data[range.start .. range.start + range.length])
     }
 }
 
+/// Returns the byte-range the slice occupied, and the number of elements
+/// in the slice.
+fn skip_slice<T: for<'de> Deserialize<'de>>(list: &BuiltDisplayList, data: &mut &[u8]) -> (ItemRange<T>, usize) {
+    let base = list.data.as_ptr() as usize;
+    let start = data.as_ptr() as usize;
+
+    // Read through the values (this is a bit of a hack to reuse logic)
+    let mut iter = AuxIter::<T>::new(*data);
+    let count = iter.len();
+    for _ in &mut iter {}
+    let end = iter.data.as_ptr() as usize;
+
+    let range = ItemRange { start: start - base, length: end - start, _boo: PhantomData };
+
+    // Adjust data pointer to skip read values
+    *data = &data[range.length..];
+    (range, count)
+}
+
 impl<'a> BuiltDisplayListIter<'a> {
     pub fn new(list: &'a BuiltDisplayList) -> Self {
-        Self::new_with_list_and_data(list, &list.data)
+        Self::new_with_list_and_data(list, list.item_slice())
     }
 
     pub fn new_with_list_and_data(list: &'a BuiltDisplayList, data: &'a [u8]) -> Self {
         BuiltDisplayListIter {
             list,
             data: &data,
             cur_item: DisplayItem { // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
                 rect: LayoutRect::zero(),
                 local_clip: LocalClip::from(LayoutRect::zero()),
-                clip_and_scroll: ClipAndScrollInfo::simple(ClipId::new(0, PipelineId(0, 0))),
+                clip_and_scroll: ClipAndScrollInfo::simple(ClipId::new(0, PipelineId::dummy())),
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
         }
     }
@@ -181,17 +224,17 @@ impl<'a> BuiltDisplayListIter<'a> {
                 return None
             }
 
             self.cur_item = bincode::deserialize_from(&mut self.data, bincode::Infinite)
                                     .expect("MEH: malicious process?");
 
             match self.cur_item.item {
                 SetGradientStops => {
-                    self.cur_stops = self.skip_slice::<GradientStop>().0;
+                    self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
 
                     // This is a dummy item, skip over it
                     continue;
                 }
                 Clip(_) | ScrollFrame(_) =>
                     self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>(),
                 Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
                 PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
@@ -199,33 +242,18 @@ impl<'a> BuiltDisplayListIter<'a> {
             }
 
             break;
         }
 
         Some(self.as_ref())
     }
 
-    /// Returns the byte-range the slice occupied, and the number of elements
-    /// in the slice.
     fn skip_slice<T: for<'de> Deserialize<'de>>(&mut self) -> (ItemRange<T>, usize) {
-        let base = self.list.data.as_ptr() as usize;
-        let start = self.data.as_ptr() as usize;
-
-        // Read through the values (this is a bit of a hack to reuse logic)
-        let mut iter = AuxIter::<T>::new(self.data);
-        let count = iter.len();
-        for _ in &mut iter {}
-        let end = iter.data.as_ptr() as usize;
-
-        let range = ItemRange { start: start - base, length: end - start, _boo: PhantomData };
-
-        // Adjust data pointer to skip read values
-        self.data = &self.data[range.length..];
-        (range, count)
+        skip_slice::<T>(self.list, &mut self.data)
     }
 
     pub fn as_ref<'b>(&'b self) -> DisplayItemRef<'a, 'b> {
         DisplayItemRef { iter: self }
     }
 
     pub fn starting_stacking_context(&mut self)
         -> Option<(StackingContext, LayoutRect, ItemRange<FilterOp>)> {
@@ -263,16 +291,29 @@ impl<'a> BuiltDisplayListIter<'a> {
             self.peeking = Peek::StartPeeking;
             self.next()
         } else {
             Some(self.as_ref())
         }
     }
 }
 
+impl<'a> Iterator for GlyphsIter<'a> {
+    type Item = (FontKey, ColorF, ItemRange<GlyphIndex>);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.data.len() == 0 { return None; }
+
+        let (font_key, color) = bincode::deserialize_from(&mut self.data, bincode::Infinite)
+                                        .expect("MEH: malicious process?");
+        let glyph_indices = skip_slice::<GlyphIndex>(self.list, &mut self.data).0;
+        Some((font_key, color, glyph_indices))
+    }
+}
+
 // Some of these might just become ItemRanges
 impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn display_item(&self) -> &DisplayItem {
         &self.iter.cur_item
     }
 
     pub fn rect(&self) -> LayoutRect {
         self.iter.cur_item.rect
@@ -406,16 +447,18 @@ impl<'a, 'b> Serialize for DisplayItemRe
     }
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ClipAndScrollInfo>,
+    // FIXME: audit whether fast hashers (FNV?) are safe here
+    glyphs: FastHashMap<(FontKey, ColorF), FastHashSet<GlyphIndex>>,
     next_clip_id: u64,
     builder_start_time: u64,
 
     /// The size of the content of this display list. This is used to allow scrolling
     /// outside the bounds of the display list items themselves.
     content_size: LayoutSize,
 }
 
@@ -431,16 +474,17 @@ impl DisplayListBuilder {
 
         // We start at 1 here, because the root scroll id is always 0.
         const FIRST_CLIP_ID : u64 = 1;
 
         DisplayListBuilder {
             data: Vec::with_capacity(capacity),
             pipeline_id,
             clip_stack: vec![ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id))],
+            glyphs: FastHashMap::default(),
             next_clip_id: FIRST_CLIP_ID,
             builder_start_time: start_time,
             content_size,
         }
     }
 
     pub fn print_display_list(&mut self) {
         let mut temp = BuiltDisplayList::default();
@@ -582,19 +626,32 @@ impl DisplayListBuilder {
                 color,
                 font_key,
                 size,
                 glyph_options,
             });
 
             self.push_item(item, rect, local_clip);
             self.push_iter(glyphs);
+
+            // Remember that we've seen these glyphs
+            self.cache_glyphs(font_key, color, glyphs.iter().map(|glyph| glyph.index));
         }
     }
 
+    fn cache_glyphs<I: Iterator<Item=GlyphIndex>>(&mut self,
+                                                     font_key: FontKey,
+                                                     color: ColorF,
+                                                     glyphs: I) {
+        let mut font_glyphs = self.glyphs.entry((font_key, color))
+                                         .or_insert(FastHashSet::default());
+
+        font_glyphs.extend(glyphs);
+    }
+
     // Gradients can be defined with stops outside the range of [0, 1]
     // when this happens the gradient needs to be normalized by adjusting
     // the gradient stops and gradient line into an equivalent gradient
     // with stops in the range [0, 1]. this is done by moving the beginning
     // of the gradient line to where stop[0] and the end of the gradient line
     // to stop[n-1]. this function adjusts the stops in place, and returns
     // the amount to adjust the gradient line start and stop
     fn normalize_stops(stops: &mut Vec<GradientStop>, extend_mode: ExtendMode) -> (f32, f32) {
@@ -935,40 +992,62 @@ impl DisplayListBuilder {
     // Don't use this function. It will go away.
     //
     // We're using this method as a hack in Gecko to retain parts sub-parts of display
     // lists so that we can regenerate them without building Gecko display items. WebRender
     // will replace references to the root scroll frame id with the current scroll frame
     // id.
     pub fn push_nested_display_list(&mut self, built_display_list: &BuiltDisplayList) {
         self.push_new_empty_item(SpecificDisplayItem::PushNestedDisplayList);
-        self.data.extend_from_slice(&built_display_list.data);
+
+        // Need to read out all the glyph data to update the cache
+        for (font_key, color, glyphs) in built_display_list.glyphs() {
+            self.cache_glyphs(font_key, color, built_display_list.get(glyphs));
+        }
+
+        // Only append the actual items, not any caches
+        self.data.extend_from_slice(built_display_list.item_slice());
         self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList);
     }
 
     pub fn push_text_shadow(&mut self,
                             rect: LayoutRect,
                             local_clip: Option<LocalClip>,
                             shadow: TextShadow) {
         self.push_item(SpecificDisplayItem::PushTextShadow(shadow),
                        rect,
                        local_clip);
     }
 
     pub fn pop_text_shadow(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopTextShadow);
     }
 
-    pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
+    pub fn finalize(mut self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
+
+        let glyph_offset = self.data.len();
+
+        // Want to use self.push_iter, so can't borrow self
+        let glyphs = ::std::mem::replace(&mut self.glyphs, FastHashMap::default());
+
+        // Append glyph data to the end
+        for ((font_key, color), sub_glyphs) in glyphs {
+            bincode::serialize_into(&mut self.data, &font_key, bincode::Infinite).unwrap();
+            bincode::serialize_into(&mut self.data, &color, bincode::Infinite).unwrap();
+            self.push_iter(sub_glyphs);
+        }
+
         let end_time = precise_time_ns();
 
+
         (self.pipeline_id,
          self.content_size,
          BuiltDisplayList {
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: self.builder_start_time,
                 builder_finish_time: end_time,
                 send_start_time: 0,
+                glyph_offset,
             },
             data: self.data,
          })
     }
 }
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -75,32 +75,36 @@ pub enum FontTemplate {
 #[repr(C)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono = 0,
     Alpha,
     Subpixel,
 }
 
+#[repr(C)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
+pub enum SubpixelDirection {
+    None = 0,
+    Horizontal,
+    Vertical,
+}
+
 const FIXED16_SHIFT: i32 = 16;
 
 // This matches the behaviour of SkScalarToFixed
 fn f32_truncate_to_fixed16(x: f32) -> i32 {
     let fixed1 = (1 << FIXED16_SHIFT) as f32;
     (x * fixed1) as i32
 }
 
 impl FontRenderMode {
     // Skia quantizes subpixel offets into 1/4 increments.
     // Given the absolute position, return the quantized increment
     fn subpixel_quantize_offset(&self, pos: f32) -> SubpixelOffset {
-        if *self != FontRenderMode::Subpixel {
-            return SubpixelOffset::Zero;
-        }
-
         const SUBPIXEL_BITS: i32 = 2;
         const SUBPIXEL_FIXED16_MASK: i32 = ((1 << SUBPIXEL_BITS) - 1) << (FIXED16_SHIFT - SUBPIXEL_BITS);
 
         const SUBPIXEL_ROUNDING: f32 = 0.5 / (1 << SUBPIXEL_BITS) as f32;
         let pos = pos + SUBPIXEL_ROUNDING;
         let fraction = (f32_truncate_to_fixed16(pos) & SUBPIXEL_FIXED16_MASK) >> (FIXED16_SHIFT - SUBPIXEL_BITS);
 
         match fraction {
@@ -128,36 +132,16 @@ impl Into<f64> for SubpixelOffset {
             SubpixelOffset::Zero => 0.0,
             SubpixelOffset::Quarter => 0.25,
             SubpixelOffset::Half => 0.5,
             SubpixelOffset::ThreeQuarters => 0.75,
         }
     }
 }
 
-#[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
-pub struct SubpixelPoint {
-    pub x: SubpixelOffset,
-    pub y: SubpixelOffset,
-}
-
-impl SubpixelPoint {
-    pub fn new(point: LayoutPoint,
-               render_mode: FontRenderMode) -> SubpixelPoint {
-        SubpixelPoint {
-            x: render_mode.subpixel_quantize_offset(point.x),
-            y: render_mode.subpixel_quantize_offset(point.y),
-        }
-    }
-
-    pub fn to_f64(&self) -> (f64, f64) {
-        (self.x.into(), self.y.into())
-    }
-}
-
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct GlyphOptions {
     // These are currently only used on windows for dwrite fonts.
     pub use_embedded_bitmap: bool,
     pub force_gdi_rendering: bool,
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
@@ -167,56 +151,76 @@ pub struct FontInstanceKey {
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
     // TODO(gw): Perhaps consider having LogicalAu and DeviceAu
     //           or something similar to that.
     pub size: Au,
     pub color: ColorU,
     pub render_mode: FontRenderMode,
     pub glyph_options: Option<GlyphOptions>,
+    pub subpx_dir: SubpixelDirection,
 }
 
 impl FontInstanceKey {
     pub fn new(font_key: FontKey,
                size: Au,
                mut color: ColorF,
                render_mode: FontRenderMode,
-               glyph_options: Option<GlyphOptions>) -> FontInstanceKey {
+               glyph_options: Option<GlyphOptions>,
+               subpx_dir: SubpixelDirection) -> FontInstanceKey {
         // In alpha/mono mode, the color of the font is irrelevant.
         // Forcing it to black in those cases saves rasterizing glyphs
         // of different colors when not needed.
         if render_mode != FontRenderMode::Subpixel {
             color = ColorF::new(0.0, 0.0, 0.0, 1.0);
         }
 
         FontInstanceKey {
             font_key,
             size,
             color: color.into(),
             render_mode,
             glyph_options,
+            subpx_dir,
+        }
+    }
+
+    pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) {
+        match self.subpx_dir {
+            SubpixelDirection::None => (0.0, 0.0),
+            SubpixelDirection::Horizontal => (glyph.subpixel_offset.into(), 0.0),
+            SubpixelDirection::Vertical => (0.0, glyph.subpixel_offset.into()),
         }
     }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
 pub struct GlyphKey {
     pub index: u32,
-    pub subpixel_point: SubpixelPoint,
+    pub subpixel_offset: SubpixelOffset,
 }
 
 impl GlyphKey {
     pub fn new(index: u32,
                point: LayoutPoint,
-               render_mode: FontRenderMode) -> GlyphKey {
+               render_mode: FontRenderMode,
+               subpx_dir: SubpixelDirection) -> GlyphKey {
+        let pos = match subpx_dir {
+            SubpixelDirection::None => 0.0,
+            SubpixelDirection::Horizontal => point.x,
+            SubpixelDirection::Vertical => point.y,
+        };
+
         GlyphKey {
             index,
-            subpixel_point: SubpixelPoint::new(point, render_mode),
+            subpixel_offset: render_mode.subpixel_quantize_offset(pos),
         }
     }
 }
 
+pub type GlyphIndex = u32;
+
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GlyphInstance {
-    pub index: u32,
+    pub index: GlyphIndex,
     pub point: LayoutPoint,
 }
--- a/gfx/webrender_api/src/lib.rs
+++ b/gfx/webrender_api/src/lib.rs
@@ -6,16 +6,17 @@
 #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments, float_cmp))]
 
 extern crate app_units;
 extern crate bincode;
 extern crate byteorder;
 #[cfg(feature = "nightly")]
 extern crate core;
 extern crate euclid;
+extern crate fxhash;
 extern crate gleam;
 #[macro_use]
 extern crate heapsize;
 #[cfg(feature = "ipc")]
 extern crate ipc_channel;
 #[cfg(feature = "webgl")]
 extern crate offscreen_gl_context;
 #[macro_use]
@@ -46,8 +47,13 @@ pub use api::*;
 pub use color::*;
 pub use display_item::*;
 pub use display_list::*;
 pub use font::*;
 pub use image::*;
 pub use units::*;
 #[cfg(feature = "webgl")]
 pub use webgl::*;
+
+use std::hash::BuildHasherDefault;
+use std::collections::{HashMap, HashSet};
+type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<fxhash::FxHasher>>;
+type FastHashSet<T> = HashSet<T, BuildHasherDefault<fxhash::FxHasher>>;