Bug 1370430 - Update webrender to cset 6752684fcc7402b0a5480e0b9f73152b2f9ed1e5. r=jrmuizel
☠☠ backed out by 7f894f791cdf ☠ ☠
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 13 Jun 2017 11:10:14 -0400
changeset 363832 5df8f9f0a5b9fbe57d99515d3b9851f1273e5123
parent 363831 4e48771504aac768a1eb5f3bd3483aa5e8d0b1b3
child 363833 dda52d6a97060aaf16e1fc9e1bc24036ff7db3d4
push id32027
push usercbook@mozilla.com
push dateWed, 14 Jun 2017 12:45:45 +0000
treeherdermozilla-central@45fde181a497 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1370430
milestone56.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 1370430 - Update webrender to cset 6752684fcc7402b0a5480e0b9f73152b2f9ed1e5. r=jrmuizel In addition to update webrender/ and webrender_traits/, this updates the webrender_bindings dependency on euclid to euclid-0.14.4. It also updates some webrender_bindings code for this dependency update, and for the BlobImageRenderer API change in WR cset 36a9117. MozReview-Commit-ID: JlmTHrFdfUM
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/res/cs_text_run.vs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_angle_gradient.fs.glsl
gfx/webrender/res/ps_angle_gradient.glsl
gfx/webrender/res/ps_angle_gradient.vs.glsl
gfx/webrender/res/ps_border_corner.vs.glsl
gfx/webrender/res/ps_border_edge.vs.glsl
gfx/webrender/res/ps_box_shadow.vs.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_image.vs.glsl
gfx/webrender/res/ps_radial_gradient.fs.glsl
gfx/webrender/res/ps_radial_gradient.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/res/ps_rectangle.vs.glsl
gfx/webrender/res/ps_split_composite.vs.glsl
gfx/webrender/res/ps_text_run.vs.glsl
gfx/webrender/res/ps_yuv_image.vs.glsl
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_render.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/geometry.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/src/moz2d_renderer.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/font.rs
gfx/webrender_traits/src/image.rs
gfx/webrender_traits/src/units.rs
third_party/rust/euclid-0.13.0/.cargo-checksum.json
third_party/rust/euclid-0.13.0/.cargo-ok
third_party/rust/euclid-0.13.0/.gitignore
third_party/rust/euclid-0.13.0/.travis.yml
third_party/rust/euclid-0.13.0/COPYRIGHT
third_party/rust/euclid-0.13.0/Cargo.toml
third_party/rust/euclid-0.13.0/LICENSE-APACHE
third_party/rust/euclid-0.13.0/LICENSE-MIT
third_party/rust/euclid-0.13.0/README.md
third_party/rust/euclid-0.13.0/src/approxeq.rs
third_party/rust/euclid-0.13.0/src/length.rs
third_party/rust/euclid-0.13.0/src/lib.rs
third_party/rust/euclid-0.13.0/src/macros.rs
third_party/rust/euclid-0.13.0/src/matrix2d.rs
third_party/rust/euclid-0.13.0/src/matrix4d.rs
third_party/rust/euclid-0.13.0/src/num.rs
third_party/rust/euclid-0.13.0/src/point.rs
third_party/rust/euclid-0.13.0/src/rect.rs
third_party/rust/euclid-0.13.0/src/scale_factor.rs
third_party/rust/euclid-0.13.0/src/side_offsets.rs
third_party/rust/euclid-0.13.0/src/size.rs
third_party/rust/euclid-0.13.0/src/trig.rs
third_party/rust/euclid/.cargo-checksum.json
third_party/rust/euclid/.travis.yml
third_party/rust/euclid/Cargo.toml
third_party/rust/euclid/src/length.rs
third_party/rust/euclid/src/lib.rs
third_party/rust/euclid/src/matrix2d.rs
third_party/rust/euclid/src/matrix4d.rs
third_party/rust/euclid/src/point.rs
third_party/rust/euclid/src/rect.rs
third_party/rust/euclid/src/scale_factor.rs
third_party/rust/euclid/src/side_offsets.rs
third_party/rust/euclid/src/size.rs
third_party/rust/euclid/src/transform2d.rs
third_party/rust/euclid/src/transform3d.rs
third_party/rust/euclid/src/vector.rs
third_party/rust/plane-split/.cargo-checksum.json
third_party/rust/plane-split/Cargo.toml
third_party/rust/plane-split/benches/split.rs
third_party/rust/plane-split/src/bsp.rs
third_party/rust/plane-split/src/lib.rs
third_party/rust/plane-split/src/naive.rs
third_party/rust/plane-split/tests/main.rs
third_party/rust/plane-split/tests/split.rs
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- 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: b2614e4eb58f9dee08b8c38f96bc3bac834c837b
+Latest Commit: 6752684fcc7402b0a5480e0b9f73152b2f9ed1e5
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -12,49 +12,37 @@ freetype-lib = ["freetype/servo-freetype
 profiler = ["thread_profiler/thread_profiler"]
 webgl = ["offscreen_gl_context", "webrender_traits/webgl"]
 
 [dependencies]
 app_units = "0.4"
 bincode = "1.0.0-alpha6"
 bit-set = "0.4"
 byteorder = "1.0"
-euclid = "0.13"
+euclid = "0.14.4"
 fnv = "1.0"
 gleam = "0.4.3"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
-offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"], optional = true}
+offscreen_gl_context = {version = "0.9.0", features = ["serde", "osmesa"], optional = true}
 time = "0.1"
 rayon = {version = "0.7", features = ["unstable"]}
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
 gamma-lut = "0.2"
 thread_profiler = "0.1.1"
-plane-split = "0.4"
+plane-split = "0.5"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 rand = "0.3"                # for the benchmarks
 servo-glutin = "0.10.1"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.2", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.3"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.7.0"
 core-text = "4.0"
-
-[[example]]
-name = "basic"
-
-[[example]]
-name = "blob"
-
-[[example]]
-name = "scrolling"
-
-[[example]]
-name = "yuv"
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/animation.rs
@@ -0,0 +1,86 @@
+/* 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;
+extern crate webrender_traits;
+
+#[macro_use]
+extern crate lazy_static;
+
+#[path="common/boilerplate.rs"]
+mod boilerplate;
+
+use boilerplate::HandyDandyRectBuilder;
+use std::sync::Mutex;
+use webrender_traits::*;
+
+// 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,
+        builder: &mut DisplayListBuilder,
+        _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(webrender_traits::ScrollPolicy::Scrollable,
+                                  bounds,
+                                  Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
+                                  TransformStyle::Flat,
+                                  None,
+                                  webrender_traits::MixBlendMode::Normal,
+                                  Vec::new());
+
+    // Fill it with a white rect
+    let clip = builder.push_clip_region(&bounds, vec![], None);
+    builder.push_rect(bounds,
+                      clip,
+                      ColorF::new(1.0, 1.0, 1.0, 1.0));
+
+    builder.pop_stacking_context();
+}
+
+lazy_static! {
+    static ref TRANSFORM: Mutex<LayoutTransform> = Mutex::new(LayoutTransform::identity());
+}
+
+fn event_handler(event: &glutin::Event,
+                 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 {
+                transforms: vec![
+                  PropertyValue {
+                    key: PropertyBindingKey::new(42),
+                    value: new_transform,
+                  },
+                ],
+                floats: vec![],
+            }));
+            *TRANSFORM.lock().unwrap() = new_transform;
+        }
+        _ => ()
+    }
+}
+
+fn main() {
+    boilerplate::main_wrapper(body, event_handler, None);
+}
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -16,16 +16,17 @@ use std::collections::HashMap;
 use std::env;
 use std::fs::File;
 use std::io::Read;
 use std::path::PathBuf;
 use webrender_traits::{ClipRegionToken, ColorF, DisplayListBuilder, Epoch, GlyphInstance};
 use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
 use webrender_traits::{PipelineId, RenderApi, TransformStyle, BoxShadowClipMode};
+use euclid::vec2;
 
 #[derive(Debug)]
 enum Gesture {
     None,
     Pan,
     Zoom,
 }
 
@@ -376,17 +377,17 @@ fn main() {
                           0.0,
                           None);
     }
 
     if false { // draw box shadow?
         let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(0.0, 0.0));
         let simple_box_bounds = LayoutRect::new(LayoutPoint::new(20.0, 200.0),
                                                 LayoutSize::new(50.0, 50.0));
-        let offset = LayoutPoint::new(10.0, 10.0);
+        let offset = vec2(10.0, 10.0);
         let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
         let blur_radius = 0.0;
         let spread_radius = 0.0;
         let simple_border_radius = 8.0;
         let box_shadow_type = BoxShadowClipMode::Inset;
         let full_screen_clip = builder.push_clip_region(&bounds, Vec::new(), None);
 
         builder.push_box_shadow(rect,
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -5,58 +5,57 @@
 extern crate app_units;
 extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
 extern crate rayon;
 
-use gleam::gl;
+#[path="common/boilerplate.rs"]
+mod boilerplate;
+
+use boilerplate::HandyDandyRectBuilder;
 use rayon::ThreadPool;
 use rayon::Configuration as ThreadPoolConfig;
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 use std::sync::mpsc::{channel, Sender, Receiver};
-use webrender_traits::{BlobImageData, BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
-use webrender_traits::{BlobImageResult, TileOffset, ImageStore, ColorF, ColorU, Epoch};
-use webrender_traits::{DeviceUintSize, DeviceUintRect, LayoutPoint, LayoutRect, LayoutSize};
-use webrender_traits::{ImageData, ImageDescriptor, ImageFormat, ImageRendering, ImageKey, TileSize};
-use webrender_traits::{PipelineId, RasterizedBlobImage, TransformStyle};
+use webrender_traits as wt;
 
 // This example shows how to implement a very basic BlobImageRenderer that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
-type ImageRenderingCommands = ColorU;
+type ImageRenderingCommands = wt::ColorU;
 
 // Serialize/deserialze the blob.
 // Ror real usecases you should probably use serde rather than doing it by hand.
 
-fn serialize_blob(color: ColorU) -> Vec<u8> {
+fn serialize_blob(color: wt::ColorU) -> Vec<u8> {
     vec![color.r, color.g, color.b, color.a]
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> {
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
-        (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
-        (Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
+        (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(wt::ColorU::new(r, g, b, a)),
+        (Some(&a), None, None, None) => Ok(wt::ColorU::new(a, a, a, a)),
         _ => Err(()),
     }
 }
 
 // This is the function that applies the deserialized drawing commands and generates
 // actual image data.
 fn render_blob(
     commands: Arc<ImageRenderingCommands>,
-    descriptor: &BlobImageDescriptor,
-    tile: Option<TileOffset>,
-) -> BlobImageResult {
+    descriptor: &wt::BlobImageDescriptor,
+    tile: Option<wt::TileOffset>,
+) -> wt::BlobImageResult {
     let color = *commands;
 
     // Allocate storage for the result. Right now the resource cache expects the
     // tiles to have have no stride or offset.
     let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
 
     // Generate a per-tile pattern to see it in the demo. For a real use case it would not
     // make sense for the rendered content to depend on its tile.
@@ -73,97 +72,97 @@ fn render_blob(
             let y2 = y + descriptor.offset.y as u32;
 
             // Render a simple checkerboard pattern
             let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { 1 } else { 0 };
             // ..nested in the per-tile cherkerboard pattern
             let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
 
             match descriptor.format {
-                ImageFormat::RGBA8 => {
+                wt::ImageFormat::RGBA8 => {
                     texels.push(color.b * checker + tc);
                     texels.push(color.g * checker + tc);
                     texels.push(color.r * checker + tc);
                     texels.push(color.a * checker + tc);
                 }
-                ImageFormat::A8 => {
+                wt::ImageFormat::A8 => {
                     texels.push(color.a * checker + tc);
                 }
                 _ => {
-                    return Err(BlobImageError::Other(format!(
+                    return Err(wt::BlobImageError::Other(format!(
                         "Usupported image format {:?}",
                         descriptor.format
                     )));
                 }
             }
         }
     }
 
-    Ok(RasterizedBlobImage {
+    Ok(wt::RasterizedBlobImage {
         data: texels,
         width: descriptor.width,
         height: descriptor.height,
     })
 }
 
 struct CheckerboardRenderer {
     // We are going to defer the rendering work to worker threads.
     // Using a pre-built Arc<ThreadPool> rather than creating our own threads
     // makes it possible to share the same thread pool as the glyph renderer (if we
     // want to).
     workers: Arc<ThreadPool>,
 
     // the workers will use an mpsc channel to communicate the result.
-    tx: Sender<(BlobImageRequest, BlobImageResult)>,
-    rx: Receiver<(BlobImageRequest, BlobImageResult)>,
+    tx: Sender<(wt::BlobImageRequest, wt::BlobImageResult)>,
+    rx: Receiver<(wt::BlobImageRequest, wt::BlobImageResult)>,
 
     // The deserialized drawing commands.
     // In this example we store them in Arcs. This isn't necessary since in this simplified
     // case the command list is a simple 32 bits value and would be cheap to clone before sending
     // to the workers. But in a more realistic scenario the commands would typically be bigger
     // and more expensive to clone, so let's pretend it is also the case here.
-    image_cmds: HashMap<ImageKey, Arc<ImageRenderingCommands>>,
+    image_cmds: HashMap<wt::ImageKey, Arc<ImageRenderingCommands>>,
 
     // The images rendered in the current frame (not kept here between frames).
-    rendered_images: HashMap<BlobImageRequest, Option<BlobImageResult>>,
+    rendered_images: HashMap<wt::BlobImageRequest, Option<wt::BlobImageResult>>,
 }
 
 impl CheckerboardRenderer {
     fn new(workers: Arc<ThreadPool>) -> Self {
         let (tx, rx) = channel();
         CheckerboardRenderer {
             image_cmds: HashMap::new(),
             rendered_images: HashMap::new(),
             workers: workers,
             tx: tx,
             rx: rx,
         }
     }
 }
 
-impl BlobImageRenderer for CheckerboardRenderer {
-    fn add(&mut self, key: ImageKey, cmds: BlobImageData, _: Option<TileSize>) {
+impl wt::BlobImageRenderer for CheckerboardRenderer {
+    fn add(&mut self, key: wt::ImageKey, cmds: wt::BlobImageData, _: Option<wt::TileSize>) {
         self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
-    fn update(&mut self, key: ImageKey, cmds: BlobImageData) {
+    fn update(&mut self, key: wt::ImageKey, cmds: wt::BlobImageData) {
         // Here, updating is just replacing the current version of the commands with
         // the new one (no incremental updates).
         self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
-    fn delete(&mut self, key: ImageKey) {
+    fn delete(&mut self, key: wt::ImageKey) {
         self.image_cmds.remove(&key);
     }
 
     fn request(&mut self,
-               request: BlobImageRequest,
-               descriptor: &BlobImageDescriptor,
-               _dirty_rect: Option<DeviceUintRect>,
-               _images: &ImageStore) {
+               resources: &wt::BlobImageResources,
+               request: wt::BlobImageRequest,
+               descriptor: &wt::BlobImageDescriptor,
+               _dirty_rect: Option<wt::DeviceUintRect>) {
         // This method is where we kick off our rendering jobs.
         // It should avoid doing work on the calling thread as much as possible.
         // In this example we will use the thread pool to render individual tiles.
 
         // Gather the input data to send to a worker thread.
         let cmds = Arc::clone(&self.image_cmds.get(&request.key).unwrap());
         let tx = self.tx.clone();
         let descriptor = descriptor.clone();
@@ -175,25 +174,25 @@ impl BlobImageRenderer for CheckerboardR
 
         // Add None in the map of rendered images. This makes it possible to differentiate
         // between commands that aren't finished yet (entry in the map is equal to None) and
         // keys that have never been requested (entry not in the map), which would cause deadlocks
         // if we were to block upon receing their result in resolve!
         self.rendered_images.insert(request, None);
     }
 
-    fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
+    fn resolve(&mut self, request: wt::BlobImageRequest) -> wt::BlobImageResult {
         // In this method we wait until the work is complete on the worker threads and
         // gather the results.
 
         // First look at whether we have already received the rendered image
         // that we are looking for.
         match self.rendered_images.entry(request) {
             Entry::Vacant(_) => {
-                return Err(BlobImageError::InvalidKey);
+                return Err(wt::BlobImageError::InvalidKey);
             }
             Entry::Occupied(entry) => {
                 // None means we haven't yet received the result.
                 if entry.get().is_some() {
                     let result = entry.remove();
                     return result.unwrap();
                 }
             }
@@ -204,174 +203,88 @@ impl BlobImageRenderer for CheckerboardR
             if req == request {
                 // There it is!
                 return result
             }
             self.rendered_images.insert(req, Some(result));
         }
 
         // If we break out of the loop above it means the channel closed unexpectedly.
-        Err(BlobImageError::Other("Channel closed".into()))
+        Err(wt::BlobImageError::Other("Channel closed".into()))
     }
+    fn delete_font(&mut self, font: wt::FontKey) {}
+}
+
+fn body(api: &wt::RenderApi,
+        builder: &mut wt::DisplayListBuilder,
+        _pipeline_id: &wt::PipelineId,
+        layout_size: &wt::LayoutSize)
+{
+    let blob_img1 = api.generate_image_key();
+    api.add_image(
+        blob_img1,
+        wt::ImageDescriptor::new(500, 500, wt::ImageFormat::RGBA8, true),
+        wt::ImageData::new_blob_image(serialize_blob(wt::ColorU::new(50, 50, 150, 255))),
+        Some(128),
+    );
+
+    let blob_img2 = api.generate_image_key();
+    api.add_image(
+        blob_img2,
+        wt::ImageDescriptor::new(200, 200, wt::ImageFormat::RGBA8, true),
+        wt::ImageData::new_blob_image(serialize_blob(wt::ColorU::new(50, 150, 50, 255))),
+        None,
+    );
+
+    let bounds = wt::LayoutRect::new(wt::LayoutPoint::zero(), *layout_size);
+    builder.push_stacking_context(wt::ScrollPolicy::Scrollable,
+                                  bounds,
+                                  None,
+                                  wt::TransformStyle::Flat,
+                                  None,
+                                  wt::MixBlendMode::Normal,
+                                  Vec::new());
+
+    let clip = builder.push_clip_region(&bounds, vec![], None);
+    builder.push_image(
+        (30, 30).by(500, 500),
+        clip,
+        wt::LayoutSize::new(500.0, 500.0),
+        wt::LayoutSize::new(0.0, 0.0),
+        wt::ImageRendering::Auto,
+        blob_img1,
+    );
+
+    let clip = builder.push_clip_region(&bounds, vec![], None);
+    builder.push_image(
+        (600, 600).by(200, 200),
+        clip,
+        wt::LayoutSize::new(200.0, 200.0),
+        wt::LayoutSize::new(0.0, 0.0),
+        wt::ImageRendering::Auto,
+        blob_img2,
+    );
+
+    builder.pop_stacking_context();
+}
+
+fn event_handler(_event: &glutin::Event,
+                 _api: &wt::RenderApi)
+{
 }
 
 fn main() {
-    let window = glutin::WindowBuilder::new()
-                .with_title("WebRender Sample (BlobImageRenderer)")
-                .with_multitouch()
-                .with_gl(glutin::GlRequest::GlThenGles {
-                    opengl_version: (3, 2),
-                    opengles_version: (3, 0)
-                })
-                .build()
-                .unwrap();
-
-    unsafe {
-        window.make_current().ok();
-    }
-
-    let gl = match gl::GlType::default() {
-        gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
-        gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
-    };
-
-    println!("OpenGL version {}", gl.get_string(gl::VERSION));
-
-    let (width, height) = window.get_inner_size_pixels().unwrap();
-
     let worker_config = ThreadPoolConfig::new().thread_name(|idx|{
         format!("WebRender:Worker#{}", idx)
     });
 
     let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
 
     let opts = webrender::RendererOptions {
-        debug: true,
         workers: Some(Arc::clone(&workers)),
         // Register our blob renderer, so that WebRender integrates it in the resource cache..
         // Share the same pool of worker threads between WebRender and our blob renderer.
         blob_image_renderer: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
-        device_pixel_ratio: window.hidpi_factor(),
         .. Default::default()
     };
 
-    let size = DeviceUintSize::new(width, height);
-    let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
-    let api = sender.create_api();
-
-    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.2, 0.2, 0.2, 1.0);
-
-    let blob_img1 = api.generate_image_key();
-    api.add_image(
-        blob_img1,
-        ImageDescriptor::new(500, 500, ImageFormat::RGBA8, true),
-        ImageData::new_blob_image(serialize_blob(ColorU::new(50, 50, 150, 255))),
-        Some(128),
-    );
-
-    let blob_img2 = api.generate_image_key();
-    api.add_image(
-        blob_img2,
-        ImageDescriptor::new(200, 200, ImageFormat::RGBA8, true),
-        ImageData::new_blob_image(serialize_blob(ColorU::new(50, 150, 50, 255))),
-        None,
-    );
-
-    let pipeline_id = PipelineId(0, 0);
-    let layout_size = LayoutSize::new(width as f32, height as f32);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
-
-    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
-    builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
-                                  bounds,
-                                  None,
-                                  TransformStyle::Flat,
-                                  None,
-                                  webrender_traits::MixBlendMode::Normal,
-                                  Vec::new());
-
-    let clip = builder.push_clip_region(&bounds, vec![], None);
-    builder.push_image(
-        LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutSize::new(500.0, 500.0)),
-        clip,
-        LayoutSize::new(500.0, 500.0),
-        LayoutSize::new(0.0, 0.0),
-        ImageRendering::Auto,
-        blob_img1,
-    );
-
-    let clip = builder.push_clip_region(&bounds, vec![], None);
-    builder.push_image(
-        LayoutRect::new(LayoutPoint::new(600.0, 60.0), LayoutSize::new(200.0, 200.0)),
-        clip,
-        LayoutSize::new(200.0, 200.0),
-        LayoutSize::new(0.0, 0.0),
-        ImageRendering::Auto,
-        blob_img2,
-    );
-
-    builder.pop_stacking_context();
-
-    api.set_display_list(
-        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);
-
-    'outer: for event in window.wait_events() {
-        let mut events = Vec::new();
-        events.push(event);
-
-        for event in window.poll_events() {
-            events.push(event);
-        }
-
-        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);
-                }
-                _ => ()
-            }
-        }
-
-        renderer.update();
-        renderer.render(DeviceUintSize::new(width, height));
-        window.swap_buffers().ok();
-    }
+    boilerplate::main_wrapper(body, event_handler, Some(opts));
 }
-
-struct Notifier {
-    window_proxy: glutin::WindowProxy,
-}
-
-impl Notifier {
-    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
-        Notifier {
-            window_proxy: window_proxy,
-        }
-    }
-}
-
-impl webrender_traits::RenderNotifier for Notifier {
-    fn new_frame_ready(&mut self) {
-        #[cfg(not(target_os = "android"))]
-        self.window_proxy.wakeup_event_loop();
-    }
-
-    fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
-        #[cfg(not(target_os = "android"))]
-        self.window_proxy.wakeup_event_loop();
-    }
-}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -0,0 +1,155 @@
+/* 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 gleam::gl;
+use glutin;
+use std::env;
+use std::path::PathBuf;
+use webrender;
+use webrender_traits::*;
+
+struct Notifier {
+    window_proxy: glutin::WindowProxy,
+}
+
+impl Notifier {
+    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
+        Notifier {
+            window_proxy: window_proxy,
+        }
+    }
+}
+
+impl RenderNotifier for Notifier {
+    fn new_frame_ready(&mut self) {
+        #[cfg(not(target_os = "android"))]
+        self.window_proxy.wakeup_event_loop();
+    }
+
+    fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
+        #[cfg(not(target_os = "android"))]
+        self.window_proxy.wakeup_event_loop();
+    }
+}
+
+pub trait HandyDandyRectBuilder {
+    fn to(&self, x2: i32, y2: i32) -> LayoutRect;
+    fn by(&self, w: i32, h: i32) -> LayoutRect;
+}
+// Allows doing `(x, y).to(x2, y2)` or `(x, y).by(width, height)` with i32
+// values to build a f32 LayoutRect
+impl HandyDandyRectBuilder for (i32, i32) {
+    fn to(&self, x2: i32, y2: i32) -> LayoutRect {
+        LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
+                        LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32))
+    }
+
+    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,
+                                         &mut DisplayListBuilder,
+                                         &PipelineId,
+                                         &LayoutSize) -> (),
+                    event_handler: fn(&glutin::Event,
+                                      &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
+    };
+
+    let window = glutin::WindowBuilder::new()
+                .with_title("WebRender Sample App")
+                .with_multitouch()
+                .with_gl(glutin::GlRequest::GlThenGles {
+                    opengl_version: (3, 2),
+                    opengles_version: (3, 0)
+                })
+                .build()
+                .unwrap();
+
+    unsafe {
+        window.make_current().ok();
+    }
+
+    let gl = match gl::GlType::default() {
+        gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
+        gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
+    };
+
+    println!("OpenGL version {}", gl.get_string(gl::VERSION));
+    println!("Shader resource path: {:?}", res_path);
+
+    let (width, height) = window.get_inner_size_pixels().unwrap();
+
+    let opts = webrender::RendererOptions {
+        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 api = sender.create_api();
+
+    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);
+
+    builder_callback(&api, &mut builder, &pipeline_id, &layout_size);
+
+    api.set_display_list(
+        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);
+
+    'outer: for event in window.wait_events() {
+        let mut events = Vec::new();
+        events.push(event);
+
+        for event in window.poll_events() {
+            events.push(event);
+        }
+
+        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);
+                }
+
+                _ => event_handler(&event, &api),
+            }
+        }
+
+        renderer.update();
+        renderer.render(DeviceUintSize::new(width, height));
+        window.swap_buffers().ok();
+    }
+}
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -2,113 +2,32 @@
  * 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;
 extern crate webrender_traits;
 
-use gleam::gl;
-use std::env;
-use std::path::PathBuf;
-use webrender_traits::{ClipId, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
-use webrender_traits::{LayoutSize, PipelineId, ScrollEventPhase, ScrollLocation, TransformStyle};
-use webrender_traits::WorldPoint;
-
-struct Notifier {
-    window_proxy: glutin::WindowProxy,
-}
-
-impl Notifier {
-    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
-        Notifier {
-            window_proxy: window_proxy,
-        }
-    }
-}
+#[macro_use]
+extern crate lazy_static;
 
-impl webrender_traits::RenderNotifier for Notifier {
-    fn new_frame_ready(&mut self) {
-        #[cfg(not(target_os = "android"))]
-        self.window_proxy.wakeup_event_loop();
-    }
-
-    fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
-        #[cfg(not(target_os = "android"))]
-        self.window_proxy.wakeup_event_loop();
-    }
-}
-
-trait HandyDandyRectBuilder {
-    fn to(&self, x2: i32, y2: i32) -> LayoutRect;
-}
-// Allows doing `(x, y).to(x2, y2)` to build a LayoutRect
-impl HandyDandyRectBuilder for (i32, i32) {
-    fn to(&self, x2: i32, y2: i32) -> LayoutRect {
-        LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
-                        LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32))
-    }
-}
-
+#[path="common/boilerplate.rs"]
+mod boilerplate;
 
-fn main() {
-    let args: Vec<String> = env::args().collect();
-    let res_path = if args.len() > 1 {
-        Some(PathBuf::from(&args[1]))
-    } else {
-        None
-    };
-
-    let window = glutin::WindowBuilder::new()
-                .with_title("WebRender Scrolling Sample")
-                .with_gl(glutin::GlRequest::GlThenGles {
-                    opengl_version: (3, 2),
-                    opengles_version: (3, 0)
-                })
-                .build()
-                .unwrap();
-
-    unsafe {
-        window.make_current().ok();
-    }
-
-    let gl = match gl::GlType::default() {
-        gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
-        gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
-    };
+use boilerplate::HandyDandyRectBuilder;
+use std::sync::Mutex;
+use webrender_traits::*;
 
-    println!("OpenGL version {}", gl.get_string(gl::VERSION));
-    println!("Shader resource path: {:?}", res_path);
-
-    let (width, height) = window.get_inner_size_pixels().unwrap();
-
-    let opts = webrender::RendererOptions {
-        resource_override_path: res_path,
-        debug: true,
-        precache_shaders: true,
-        device_pixel_ratio: window.hidpi_factor(),
-        .. Default::default()
-    };
-
-    let size = DeviceUintSize::new(width, height);
-    let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
-    let api = sender.create_api();
-
-    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 = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
-
-    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
+fn body(_api: &RenderApi,
+        builder: &mut DisplayListBuilder,
+        pipeline_id: &PipelineId,
+        layout_size: &LayoutSize)
+{
+    let bounds = LayoutRect::new(LayoutPoint::zero(), *layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
@@ -122,17 +41,17 @@ fn main() {
                                       TransformStyle::Flat,
                                       None,
                                       webrender_traits::MixBlendMode::Normal,
                                       Vec::new());
         // set the scrolling clip
         let clip = builder.push_clip_region(&scrollbox, vec![], None);
         let clip_id = builder.define_clip((0, 0).to(1000, 1000),
                                           clip,
-                                          Some(ClipId::new(42, pipeline_id)));
+                                          Some(ClipId::new(42, *pipeline_id)));
         builder.push_clip_id(clip_id);
         // now put some content into it.
         // start with a white background
         let clip = builder.push_clip_region(&(0, 0).to(1000, 1000), vec![], None);
         builder.push_rect((0, 0).to(500, 500),
                           clip,
                           ColorF::new(1.0, 1.0, 1.0, 1.0));
         // let's make a 50x50 blue square as a visual reference
@@ -148,17 +67,17 @@ fn main() {
                           ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         // Below the above rectangles, set up a nested scrollbox. It's still in
         // the same stacking context, so note that the rects passed in need to
         // be relative to the stacking context.
         let clip = builder.push_clip_region(&(0, 100).to(200, 300), vec![], None);
         let nested_clip_id = builder.define_clip((0, 100).to(300, 400),
                                                  clip,
-                                                 Some(ClipId::new(43, pipeline_id)));
+                                                 Some(ClipId::new(43, *pipeline_id)));
         builder.push_clip_id(nested_clip_id);
         // give it a giant gray background just to distinguish it and to easily
         // visually identify the nested scrollbox
         let clip = builder.push_clip_region(&(-1000, -1000).to(5000, 5000), vec![], None);
         builder.push_rect((-1000, -1000).to(5000, 5000),
                           clip,
                           ColorF::new(0.5, 0.5, 0.5, 1.0));
         // add a teal square to visualize the scrolling/clipping behaviour
@@ -176,73 +95,56 @@ fn main() {
                           ColorF::new(0.0, 1.0, 1.0, 1.0));
         builder.pop_clip_id(); // nested_clip_id
 
         builder.pop_clip_id(); // clip_id
         builder.pop_stacking_context();
     }
 
     builder.pop_stacking_context();
+}
 
-    api.set_display_list(
-        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);
+lazy_static! {
+    static ref CURSOR_POSITION: Mutex<WorldPoint> = Mutex::new(WorldPoint::zero());
+}
 
-    let mut cursor_position = WorldPoint::zero();
-
-    'outer: for event in window.wait_events() {
-        let mut events = Vec::new();
-        events.push(event);
-
-        for event in window.poll_events() {
-            events.push(event);
-        }
+fn event_handler(event: &glutin::Event,
+                 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,
+            };
 
-        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(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),
-                         _ => continue,
-                    };
+            api.scroll(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 {
+                *CURSOR_POSITION.lock().unwrap() = WorldPoint::new(x as f32, y as f32);
+            }
 
-                    api.scroll(ScrollLocation::Delta(LayoutPoint::new(offset.0, offset.1)),
-                               cursor_position,
-                               ScrollEventPhase::Start);
-                }
-                glutin::Event::MouseMoved(x, y) => {
-                    cursor_position = WorldPoint::new(x as f32, y as f32);
-                }
-                glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
-                    if let Some((x, y)) = event_cursor_position {
-                        cursor_position = WorldPoint::new(x as f32, y as f32);
-                    }
+            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),
+            };
 
-                    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(LayoutPoint::new(dx, dy)),
-                               cursor_position,
-                               ScrollEventPhase::Start);
-                }
-                _ => ()
-            }
+            api.scroll(ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
+                       *CURSOR_POSITION.lock().unwrap(),
+                       ScrollEventPhase::Start);
         }
-
-        renderer.update();
-        renderer.render(DeviceUintSize::new(width, height));
-        window.swap_buffers().ok();
+        _ => ()
     }
 }
+
+fn main() {
+    boilerplate::main_wrapper(body, event_handler, None);
+}
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -3,28 +3,29 @@
  * 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/. */
 
 // Draw a text run to a cache target. These are always
 // drawn un-transformed. These are used for effects such
 // as text-shadow.
 
 void main(void) {
-    PrimitiveInstance pi = fetch_prim_instance();
-    RenderTaskData task = fetch_render_task(pi.render_task_index);
-    TextRun text = fetch_text_run(pi.specific_prim_address);
-    Glyph glyph = fetch_glyph(pi.user_data0);
-    PrimitiveGeometry pg = fetch_prim_geometry(pi.global_prim_index);
-    ResourceRect res = fetch_resource_rect(pi.user_data1);
+    Primitive prim = load_primitive();
+    TextRun text = fetch_text_run(prim.specific_prim_address);
+
+    int glyph_index = prim.user_data0;
+    int resource_address = prim.user_data1;
+    Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
+    ResourceRect res = fetch_resource_rect(resource_address + glyph_index);
 
     // 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 origin = task.data0.xy + uDevicePixelRatio * (glyph.offset.xy - pg.local_rect.p0);
+    vec2 origin = prim.task.screen_space_origin + uDevicePixelRatio * (glyph.offset - prim.local_rect.p0);
     vec4 local_rect = vec4(origin, size);
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
     vec2 pos = mix(local_rect.xy,
                    local_rect.xy + local_rect.zw,
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -101,31 +101,53 @@ float distance_to_line(vec2 p0, vec2 per
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
 #ifdef WR_FEATURE_TRANSFORM
     flat varying vec4 vLocalBounds;
 #endif
 
+// TODO(gw): This is here temporarily while we have
+//           both GPU store and cache. When the GPU
+//           store code is removed, we can change the
+//           PrimitiveInstance instance structure to
+//           use 2x unsigned shorts as vertex attributes
+//           instead of an int, and encode the UV directly
+//           in the vertices.
+ivec2 get_resource_cache_uv(int address) {
+    return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
+                 address / WR_MAX_VERTEX_TEXTURE_WIDTH);
+}
+
+uniform sampler2D sResourceCache;
+
+vec4[2] fetch_from_resource_cache_2(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
+    return vec4[2](
+        texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
+        texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0))
+    );
+}
+
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_LAYER              9
 #define VECS_PER_RENDER_TASK        3
-#define VECS_PER_PRIM_GEOM          2
-#define VECS_PER_SPLIT_GEOM         3
+#define VECS_PER_PRIM_HEADER        2
+#define VECS_PER_TEXT_RUN           1
+#define VECS_PER_GRADIENT           3
+#define VECS_PER_GRADIENT_STOP      2
 
 uniform sampler2D sLayers;
 uniform sampler2D sRenderTasks;
-uniform sampler2D sPrimGeometry;
 
 uniform sampler2D sData16;
 uniform sampler2D sData32;
 uniform sampler2D sResourceRects;
-uniform sampler2D sResourceCache;
 
 // Instanced attributes
 in ivec4 aData0;
 in ivec4 aData1;
 
 // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
 // TODO: convert back to a function once the driver issues are resolved, if ever.
 // https://github.com/servo/webrender/pull/623
@@ -140,28 +162,16 @@ vec4 fetch_data_1(int index) {
 vec4[2] fetch_data_2(int index) {
     ivec2 uv = get_fetch_uv(index, 2);
     return vec4[2](
         texelFetchOffset(sData32, uv, 0, ivec2(0, 0)),
         texelFetchOffset(sData32, uv, 0, ivec2(1, 0))
     );
 }
 
-// TODO(gw): This is here temporarily while we have
-//           both GPU store and cache. When the GPU
-//           store code is removed, we can change the
-//           PrimitiveInstance instance structure to
-//           use 2x unsigned shorts as vertex attributes
-//           instead of an int, and encode the UV directly
-//           in the vertices.
-ivec2 get_resource_cache_uv(int address) {
-    return ivec2(address % WR_MAX_VERTEX_TEXTURE_WIDTH,
-                 address / WR_MAX_VERTEX_TEXTURE_WIDTH);
-}
-
 vec4[8] fetch_from_resource_cache_8(int address) {
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[8](
         texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(3, 0)),
         texelFetchOffset(sResourceCache, uv, 0, ivec2(4, 0)),
@@ -316,18 +326,18 @@ Gradient fetch_gradient(int address) {
     return Gradient(data[0], data[1], data[2]);
 }
 
 struct GradientStop {
     vec4 color;
     vec4 offset;
 };
 
-GradientStop fetch_gradient_stop(int index) {
-    vec4 data[2] = fetch_data_2(index);
+GradientStop fetch_gradient_stop(int address) {
+    vec4 data[2] = fetch_from_resource_cache_2(address);
     return GradientStop(data[0], data[1]);
 }
 
 struct RadialGradient {
     vec4 start_end_center;
     vec4 start_end_radius_ratio_xy_extend_mode;
     vec4 tile_size_repeat;
 };
@@ -412,69 +422,57 @@ BorderCorners get_border_corners(Border 
         br_outer,
         br_inner,
         bl_outer,
         bl_inner
     );
 }
 
 struct Glyph {
-    vec4 offset;
+    vec2 offset;
 };
 
-Glyph fetch_glyph(int index) {
-    vec4 data = fetch_data_1(index);
-    return Glyph(data);
+Glyph fetch_glyph(int specific_prim_address, int glyph_index) {
+    // Two glyphs are packed in each texel in the GPU cache.
+    int glyph_address = specific_prim_address +
+                        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));
+    return Glyph(glyph);
 }
 
 RectWithSize fetch_instance_geometry(int address) {
     vec4 data = fetch_from_resource_cache_1(address);
     return RectWithSize(data.xy, data.zw);
 }
 
-struct PrimitiveGeometry {
-    RectWithSize local_rect;
-    RectWithSize local_clip_rect;
-};
-
-PrimitiveGeometry fetch_prim_geometry(int index) {
-    PrimitiveGeometry pg;
-
-    ivec2 uv = get_fetch_uv(index, VECS_PER_PRIM_GEOM);
-
-    vec4 local_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(0, 0));
-    pg.local_rect = RectWithSize(local_rect.xy, local_rect.zw);
-    vec4 local_clip_rect = texelFetchOffset(sPrimGeometry, uv, 0, ivec2(1, 0));
-    pg.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
-
-    return pg;
-}
-
 struct PrimitiveInstance {
-    int global_prim_index;
+    int prim_address;
     int specific_prim_address;
     int render_task_index;
     int clip_task_index;
     int layer_index;
     int z;
     int user_data0;
     int user_data1;
 };
 
 PrimitiveInstance fetch_prim_instance() {
     PrimitiveInstance pi;
 
-    pi.global_prim_index = aData0.x;
-    pi.specific_prim_address = aData0.y;
-    pi.render_task_index = aData0.z;
-    pi.clip_task_index = aData0.w;
-    pi.layer_index = aData1.x;
-    pi.z = aData1.y;
-    pi.user_data0 = aData1.z;
-    pi.user_data1 = aData1.w;
+    pi.prim_address = aData0.x;
+    pi.specific_prim_address = pi.prim_address + VECS_PER_PRIM_HEADER;
+    pi.render_task_index = aData0.y;
+    pi.clip_task_index = aData0.z;
+    pi.layer_index = aData0.w;
+    pi.z = aData1.x;
+    pi.user_data0 = aData1.y;
+    pi.user_data1 = aData1.z;
 
     return pi;
 }
 
 struct CompositeInstance {
     int render_task_index;
     int src_task_index;
     int backdrop_task_index;
@@ -498,48 +496,43 @@ CompositeInstance fetch_composite_instan
 }
 
 struct Primitive {
     Layer layer;
     ClipArea clip_area;
     AlphaBatchTask task;
     RectWithSize local_rect;
     RectWithSize local_clip_rect;
-    int prim_index;
+    int specific_prim_address;
     int user_data0;
     int user_data1;
     float z;
 };
 
-Primitive load_primitive_custom(PrimitiveInstance pi) {
+Primitive load_primitive() {
+    PrimitiveInstance pi = fetch_prim_instance();
+
     Primitive prim;
 
     prim.layer = fetch_layer(pi.layer_index);
     prim.clip_area = fetch_clip_area(pi.clip_task_index);
     prim.task = fetch_alpha_batch_task(pi.render_task_index);
 
-    PrimitiveGeometry pg = fetch_prim_geometry(pi.global_prim_index);
-    prim.local_rect = pg.local_rect;
-    prim.local_clip_rect = pg.local_clip_rect;
+    vec4 geom[2] = fetch_from_resource_cache_2(pi.prim_address);
+    prim.local_rect = RectWithSize(geom[0].xy, geom[0].zw);
+    prim.local_clip_rect = RectWithSize(geom[1].xy, geom[1].zw);
 
-    prim.prim_index = pi.specific_prim_address;
+    prim.specific_prim_address = pi.specific_prim_address;
     prim.user_data0 = pi.user_data0;
     prim.user_data1 = pi.user_data1;
     prim.z = float(pi.z);
 
     return prim;
 }
 
-Primitive load_primitive() {
-    PrimitiveInstance pi = fetch_prim_instance();
-
-    return load_primitive_custom(pi);
-}
-
-
 // Return the intersection of the plane (set up by "normal" and "point")
 // with the ray (set up by "ray_origin" and "ray_dir"),
 // writing the resulting scaler into "t".
 bool ray_plane(vec3 normal, vec3 point, vec3 ray_origin, vec3 ray_dir, out float t)
 {
     float denom = dot(normal, ray_dir);
     if (abs(denom) > 1e-6) {
         vec3 d = point - ray_origin;
@@ -852,43 +845,44 @@ vec4 dither(vec4 color) {
     return color + vec4(noise, noise, noise, 0);
 }
 #else
 vec4 dither(vec4 color) {
     return color;
 }
 #endif //WR_FEATURE_DITHERING
 
-vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index, vec2 gradient_size) {
-    // Modulo the offset if the gradient repeats. We don't need to clamp non-repeating
-    // gradients because the gradient data texture is bound with CLAMP_TO_EDGE, and the
-    // first and last color entries are filled with the first and last stop colors
+vec4 sample_gradient(int address, float offset, float gradient_repeat) {
+    // Modulo the offset if the gradient repeats.
     float x = mix(offset, fract(offset), gradient_repeat);
 
     // Calculate the color entry index to use for this offset:
     //     offsets < 0 use the first color entry, 0
     //     offsets from [0, 1) use the color entries in the range of [1, N-1)
     //     offsets >= 1 use the last color entry, N-1
     //     so transform the range [0, 1) -> [1, N-1)
-    float gradient_entries = 0.5 * gradient_size.x;
-    x = x * (gradient_entries - 2.0) + 1.0;
+
+    // TODO(gw): In the future we might consider making the size of the
+    // LUT vary based on number / distribution of stops in the gradient.
+    const int GRADIENT_ENTRIES = 128;
+    x = 1.0 + x * float(GRADIENT_ENTRIES);
 
     // Calculate the texel to index into the gradient color entries:
     //     floor(x) is the gradient color entry index
     //     fract(x) is the linear filtering factor between start and end
-    //     so, 2 * floor(x) + 0.5 is the center of the start color
-    //     finally, add floor(x) to interpolate to end
-    x = 2.0 * floor(x) + 0.5 + fract(x);
+    int lut_offset = 2 * int(floor(x));     // There is a [start, end] color per entry.
+
+    // Ensure we don't fetch outside the valid range of the LUT.
+    lut_offset = clamp(lut_offset, 0, 2 * (GRADIENT_ENTRIES + 1));
 
-    // Gradient color entries are encoded with high bits in one row and low bits in the next
-    // So use linear filtering to mix (gradient_index + 1) with (gradient_index)
-    float y = gradient_index * 2.0 + 0.5 + 1.0 / 256.0;
+    // Fetch the start and end color.
+    vec4 texels[2] = fetch_from_resource_cache_2(address + lut_offset);
 
-    // Finally sample and apply dithering
-    return dither(texture(sGradients, vec2(x, y) / gradient_size));
+    // Finally interpolate and apply dithering
+    return dither(mix(texels[0], texels[1], fract(x)));
 }
 
 //
 // Signed distance to an ellipse.
 // Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
 // Note that this fails for exact circles.
 //
 float sdEllipse( vec2 p, in vec2 ab ) {
--- a/gfx/webrender/res/ps_angle_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.fs.glsl
@@ -1,19 +1,20 @@
+#line 1
+
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     vec2 pos = mod(vPos, vTileRepeat);
 
     if (pos.x >= vTileSize.x ||
         pos.y >= vTileSize.y) {
         discard;
     }
 
     float offset = dot(pos - vStartPoint, vScaledDir);
 
-    oFragColor = sample_gradient(offset,
-                                 vGradientRepeat,
-                                 vGradientIndex,
-                                 vGradientTextureSize);
+    oFragColor = sample_gradient(vGradientAddress,
+                                 offset,
+                                 vGradientRepeat);
 }
--- a/gfx/webrender/res/ps_angle_gradient.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.glsl
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-flat varying float vGradientIndex;
-flat varying vec2 vGradientTextureSize;
+flat varying int vGradientAddress;
 flat varying float vGradientRepeat;
 
 flat varying vec2 vScaledDir;
 flat varying vec2 vStartPoint;
 
 flat varying vec2 vTileSize;
 flat varying vec2 vTileRepeat;
 
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.vs.glsl
@@ -1,16 +1,16 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
-    Gradient gradient = fetch_gradient(prim.prim_index);
+    Gradient gradient = fetch_gradient(prim.specific_prim_address);
 
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
 
@@ -21,17 +21,13 @@ void main(void) {
     vec2 dir = end_point - start_point;
 
     vStartPoint = start_point;
     vScaledDir = dir / dot(dir, dir);
 
     vTileSize = gradient.tile_size_repeat.xy;
     vTileRepeat = gradient.tile_size_repeat.zw;
 
-    // V coordinate of gradient row in lookup texture.
-    vGradientIndex = float(prim.user_data0);
-
-    // The texture size of the lookup texture
-    vGradientTextureSize = vec2(textureSize(sGradients, 0));
+    vGradientAddress = prim.specific_prim_address + VECS_PER_GRADIENT;
 
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -112,17 +112,17 @@ int select_style(int color_select, vec2 
             return style.x;
         case SIDE_SECOND:
             return style.y;
     }
 }
 
 void main(void) {
     Primitive prim = load_primitive();
-    Border border = fetch_border(prim.prim_index);
+    Border border = fetch_border(prim.specific_prim_address);
     int sub_part = prim.user_data0;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
 
     vec2 p0, p1;
 
     // TODO(gw): We'll need to pass through multiple styles
     //           once we support style transitions per corner.
     int style;
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -99,17 +99,17 @@ void write_clip_params(float style,
             vClipParams = vec4(1.0);
             vClipSelect = 0.0;
             break;
     }
 }
 
 void main(void) {
     Primitive prim = load_primitive();
-    Border border = fetch_border(prim.prim_index);
+    Border border = fetch_border(prim.specific_prim_address);
     int sub_part = prim.user_data0;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
     vec4 color = border.colors[sub_part];
 
     // TODO(gw): Now that all border styles are supported, the switch
     //           statement below can be tidied up quite a bit.
 
     float style;
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -2,18 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #define BS_HEADER_VECS 4
 
 void main(void) {
     Primitive prim = load_primitive();
-    BoxShadow bs = fetch_boxshadow(prim.prim_index);
-    RectWithSize segment_rect = fetch_instance_geometry(prim.prim_index + BS_HEADER_VECS + prim.user_data0);
+    BoxShadow bs = fetch_boxshadow(prim.specific_prim_address);
+    RectWithSize segment_rect = fetch_instance_geometry(prim.specific_prim_address + BS_HEADER_VECS + prim.user_data0);
 
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
 
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -1,21 +1,25 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
-    Gradient gradient = fetch_gradient(prim.prim_index);
+    Gradient gradient = fetch_gradient(prim.specific_prim_address);
 
     vec4 abs_start_end_point = gradient.start_end_point + prim.local_rect.p0.xyxy;
 
-    GradientStop g0 = fetch_gradient_stop(prim.user_data0 + 0);
-    GradientStop g1 = fetch_gradient_stop(prim.user_data0 + 1);
+    int stop_address = prim.specific_prim_address +
+                       VECS_PER_GRADIENT +
+                       VECS_PER_GRADIENT_STOP * prim.user_data0;
+
+    GradientStop g0 = fetch_gradient_stop(stop_address);
+    GradientStop g1 = fetch_gradient_stop(stop_address + VECS_PER_GRADIENT_STOP);
 
     RectWithSize segment_rect;
     vec2 axis;
     vec4 adjusted_color_g0 = g0.color;
     vec4 adjusted_color_g1 = g1.color;
     if (abs_start_end_point.y == abs_start_end_point.w) {
         // Calculate the x coord of the gradient stops
         vec2 g01_x = mix(abs_start_end_point.xx, abs_start_end_point.zz,
--- a/gfx/webrender/res/ps_image.vs.glsl
+++ b/gfx/webrender/res/ps_image.vs.glsl
@@ -1,16 +1,16 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
-    Image image = fetch_image(prim.prim_index);
+    Image image = fetch_image(prim.specific_prim_address);
     ResourceRect res = fetch_resource_rect(prim.user_data0);
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
--- a/gfx/webrender/res/ps_radial_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.fs.glsl
@@ -44,13 +44,12 @@ void main(void) {
             offset = t0;
         } else if (vStartRadius + rd * t1 >= 0.0) {
             offset = t1;
         } else {
             discard;
         }
     }
 
-    oFragColor = sample_gradient(offset,
-                                 vGradientRepeat,
-                                 vGradientIndex,
-                                 vGradientTextureSize);
+    oFragColor = sample_gradient(vGradientAddress,
+                                 offset,
+                                 vGradientRepeat);
 }
--- a/gfx/webrender/res/ps_radial_gradient.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.glsl
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-flat varying float vGradientIndex;
-flat varying vec2 vGradientTextureSize;
+flat varying int vGradientAddress;
 flat varying float vGradientRepeat;
 
 flat varying vec2 vStartCenter;
 flat varying vec2 vEndCenter;
 flat varying float vStartRadius;
 flat varying float vEndRadius;
 
 flat varying vec2 vTileSize;
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -1,16 +1,16 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
-    RadialGradient gradient = fetch_radial_gradient(prim.prim_index);
+    RadialGradient gradient = fetch_radial_gradient(prim.specific_prim_address);
 
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
 
@@ -29,17 +29,13 @@ void main(void) {
     // fragment shader can work with circles
     float ratio_xy = gradient.start_end_radius_ratio_xy_extend_mode.z;
     vPos.y *= ratio_xy;
     vStartCenter.y *= ratio_xy;
     vEndCenter.y *= ratio_xy;
     vTileSize.y *= ratio_xy;
     vTileRepeat.y *= ratio_xy;
 
-    // V coordinate of gradient row in lookup texture.
-    vGradientIndex = float(prim.user_data0);
-
-    // The texture size of the lookup texture
-    vGradientTextureSize = vec2(textureSize(sGradients, 0));
+    vGradientAddress = prim.specific_prim_address + VECS_PER_GRADIENT;
 
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/ps_rectangle.vs.glsl
+++ b/gfx/webrender/res/ps_rectangle.vs.glsl
@@ -1,16 +1,16 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
-    Rectangle rect = fetch_rectangle(prim.prim_index);
+    Rectangle rect = fetch_rectangle(prim.specific_prim_address);
     vColor = rect.color;
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
--- a/gfx/webrender/res/ps_split_composite.vs.glsl
+++ b/gfx/webrender/res/ps_split_composite.vs.glsl
@@ -1,25 +1,23 @@
 #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/. */
 
-uniform sampler2D sSplitGeometry;
-
 struct SplitGeometry {
     vec3 points[4];
 };
 
-SplitGeometry fetch_split_geometry(int index) {
-    ivec2 uv = get_fetch_uv(index, VECS_PER_SPLIT_GEOM);
+SplitGeometry fetch_split_geometry(int address) {
+    ivec2 uv = get_resource_cache_uv(address);
 
-    vec4 data0 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(0, 0));
-    vec4 data1 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(1, 0));
-    vec4 data2 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(2, 0));
+    vec4 data0 = texelFetchOffset(sResourceCache, uv, 0, ivec2(0, 0));
+    vec4 data1 = texelFetchOffset(sResourceCache, uv, 0, ivec2(1, 0));
+    vec4 data2 = texelFetchOffset(sResourceCache, uv, 0, ivec2(2, 0));
 
     SplitGeometry geo;
     geo.points = vec3[4](
         data0.xyz, vec3(data0.w, data1.xy),
         vec3(data1.zw, data2.x), data2.yzw
     );
     return geo;
 }
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -1,20 +1,23 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     Primitive prim = load_primitive();
-    TextRun text = fetch_text_run(prim.prim_index);
-    Glyph glyph = fetch_glyph(prim.user_data0);
-    ResourceRect res = fetch_resource_rect(prim.user_data1);
+    TextRun text = fetch_text_run(prim.specific_prim_address);
 
-    RectWithSize local_rect = RectWithSize(glyph.offset.xy,
+    int glyph_index = prim.user_data0;
+    int resource_address = prim.user_data1;
+    Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
+    ResourceRect res = fetch_resource_rect(resource_address + glyph_index);
+
+    RectWithSize local_rect = RectWithSize(glyph.offset,
                                            (res.uv_rect.zw - res.uv_rect.xy) / uDevicePixelRatio);
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -62,16 +62,16 @@ void main(void) {
 
     vTextureSizeUv = u_st1 - u_st0;
     vTextureOffsetU = u_st0;
 #ifndef WR_FEATURE_NV12
     vTextureOffsetV = v_st0;
 #endif
 #endif
 
-    YuvImage image = fetch_yuv_image(prim.prim_index);
+    YuvImage image = fetch_yuv_image(prim.specific_prim_address);
     vStretchSize = image.size;
 
     vHalfTexelY = vec2(0.5) / y_texture_size_normalization_factor;
 #ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vHalfTexelUv = vec2(0.5) / uv_texture_size_normalization_factor;
 #endif
 }
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use euclid::Point3D;
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::GpuBlock32;
 use renderer::VertexDataStore;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::TransformedRectKind;
 use webrender_traits::{ClipId, ClipRegion, DeviceIntRect, LayerPixel, LayerPoint, LayerRect};
 use webrender_traits::{LayerSize, LayerToScrollTransform, LayerToWorldTransform, PipelineId};
 use webrender_traits::{ScrollClamping, ScrollEventPhase, ScrollLayerRect, ScrollLocation};
-use webrender_traits::{WorldPoint, WorldPoint4D};
+use webrender_traits::{WorldPoint, LayerVector2D};
+use webrender_traits::{as_scroll_parent_vector};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Clone, Debug)]
@@ -67,24 +67,24 @@ impl ClipInfo {
 #[derive(Clone, Debug)]
 pub enum NodeType {
     /// Transform for this layer, relative to parent reference frame. A reference
     /// frame establishes a new coordinate space in the tree.
     ReferenceFrame(LayerToScrollTransform),
 
     /// Other nodes just do clipping, but no transformation.
     Clip(ClipInfo),
+
+    /// Other nodes just do clipping, but no transformation.
+    ScrollFrame(ScrollingState),
 }
 
-/// Contains scrolling and transform information stacking contexts.
+/// Contains information common among all types of ClipScrollTree nodes.
 #[derive(Clone, Debug)]
 pub struct ClipScrollNode {
-    /// Manages scrolling offset, overscroll state etc.
-    pub scrolling: ScrollingState,
-
     /// Size of the content inside the scroll region (in logical pixels)
     pub content_size: LayerSize,
 
     /// Viewing rectangle in the coordinate system of the parent reference frame.
     pub local_viewport_rect: LayerRect,
 
     /// Clip rect of this node - typically the same as viewport rect, except
     /// in overscroll cases.
@@ -100,158 +100,157 @@ pub struct ClipScrollNode {
     pub world_viewport_transform: LayerToWorldTransform,
 
     /// World transform for content transformed by this node.
     pub world_content_transform: LayerToWorldTransform,
 
     /// The scroll offset of all the nodes between us and our parent reference frame.
     /// This is used to calculate intersections between us and content or nodes that
     /// are also direct children of our reference frame.
-    pub reference_frame_relative_scroll_offset: LayerPoint,
+    pub reference_frame_relative_scroll_offset: LayerVector2D,
 
     /// Pipeline that this layer belongs to
     pub pipeline_id: PipelineId,
 
     /// Parent layer. If this is None, we are the root node.
     pub parent: Option<ClipId>,
 
     /// Child layers
     pub children: Vec<ClipId>,
 
     /// Whether or not this node is a reference frame.
     pub node_type: NodeType,
 }
 
 impl ClipScrollNode {
+    pub fn new_scroll_frame(pipeline_id: PipelineId,
+                            parent_id: ClipId,
+                            content_rect: &LayerRect,
+                            frame_rect: &LayerRect)
+                            -> ClipScrollNode {
+        ClipScrollNode {
+            content_size: content_rect.size,
+            local_viewport_rect: *frame_rect,
+            local_clip_rect: *frame_rect,
+            combined_local_viewport_rect: LayerRect::zero(),
+            world_viewport_transform: LayerToWorldTransform::identity(),
+            world_content_transform: LayerToWorldTransform::identity(),
+            reference_frame_relative_scroll_offset: LayerVector2D::zero(),
+            parent: Some(parent_id),
+            children: Vec::new(),
+            pipeline_id: pipeline_id,
+            node_type: NodeType::ScrollFrame(ScrollingState::new()),
+        }
+    }
+
     pub fn new(pipeline_id: PipelineId,
                parent_id: ClipId,
                content_rect: &LayerRect,
                clip_rect: &LayerRect,
                clip_info: ClipInfo)
                -> ClipScrollNode {
         // FIXME(mrobinson): We don't yet handle clipping rectangles that don't start at the origin
         // of the node.
         let local_viewport_rect = LayerRect::new(content_rect.origin, clip_rect.size);
         ClipScrollNode {
-            scrolling: ScrollingState::new(),
             content_size: content_rect.size,
             local_viewport_rect: local_viewport_rect,
             local_clip_rect: local_viewport_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
-            reference_frame_relative_scroll_offset: LayerPoint::zero(),
+            reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id: pipeline_id,
             node_type: NodeType::Clip(clip_info),
         }
     }
 
     pub fn new_reference_frame(parent_id: Option<ClipId>,
                                local_viewport_rect: &LayerRect,
                                content_size: LayerSize,
                                local_transform: &LayerToScrollTransform,
                                pipeline_id: PipelineId)
                                -> ClipScrollNode {
         ClipScrollNode {
-            scrolling: ScrollingState::new(),
             content_size: content_size,
             local_viewport_rect: *local_viewport_rect,
             local_clip_rect: *local_viewport_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
-            reference_frame_relative_scroll_offset: LayerPoint::zero(),
+            reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             parent: parent_id,
             children: Vec::new(),
             pipeline_id: pipeline_id,
             node_type: NodeType::ReferenceFrame(*local_transform),
         }
     }
 
     pub fn add_child(&mut self, child: ClipId) {
         self.children.push(child);
     }
 
-    pub fn finalize(&mut self, scrolling: &ScrollingState) {
-        self.scrolling = *scrolling;
-    }
-
-    pub fn overscroll_amount(&self) -> LayerSize {
-        let scrollable_width = self.scrollable_width();
-        let overscroll_x = if self.scrolling.offset.x > 0.0 {
-            -self.scrolling.offset.x
-        } else if self.scrolling.offset.x < -scrollable_width {
-            -scrollable_width - self.scrolling.offset.x
-        } else {
-            0.0
-        };
-
-        let scrollable_height = self.scrollable_height();
-        let overscroll_y = if self.scrolling.offset.y > 0.0 {
-            -self.scrolling.offset.y
-        } else if self.scrolling.offset.y < -scrollable_height {
-            -scrollable_height - self.scrolling.offset.y
-        } else {
-            0.0
-        };
-
-        LayerSize::new(overscroll_x, overscroll_y)
+    pub fn finalize(&mut self, new_scrolling: &ScrollingState) {
+        match self.node_type {
+            NodeType::ReferenceFrame(_) | NodeType::Clip(_) =>
+                warn!("Tried to scroll a non-scroll node."),
+            NodeType::ScrollFrame(ref mut scrolling) => *scrolling = *new_scrolling,
+        }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
-        match self.node_type {
-            NodeType::ReferenceFrame(_) => {
-                warn!("Tried to scroll a reference frame.");
+        let scrollable_height = self.scrollable_height();
+        let scrollable_width = self.scrollable_width();
+
+        let scrolling = match self.node_type {
+            NodeType::ReferenceFrame(_) | NodeType::Clip(_) => {
+                warn!("Tried to scroll a non-scroll node.");
                 return false;
             }
-            NodeType::Clip(_) => {}
+             NodeType::ScrollFrame(ref mut scrolling) => scrolling,
         };
 
-
-        let scrollable_height = self.scrollable_height();
-        let scrollable_width = self.scrollable_width();
-
         let new_offset = match clamp {
             ScrollClamping::ToContentBounds => {
                 if scrollable_height <= 0. && scrollable_width <= 0. {
                     return false;
                 }
 
                 let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
-                LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
-                                (-origin.y).max(-scrollable_height).min(0.0).round())
+                LayerVector2D::new((-origin.x).max(-scrollable_width).min(0.0).round(),
+                                   (-origin.y).max(-scrollable_height).min(0.0).round())
             }
             ScrollClamping::NoClamping => LayerPoint::zero() - *origin,
         };
 
-        if new_offset == self.scrolling.offset {
+        if new_offset == scrolling.offset {
             return false;
         }
 
-        self.scrolling.offset = new_offset;
-        self.scrolling.bouncing_back = false;
-        self.scrolling.started_bouncing_back = false;
+        scrolling.offset = new_offset;
+        scrolling.bouncing_back = false;
+        scrolling.started_bouncing_back = false;
         true
     }
 
     pub fn update_transform(&mut self,
                             parent_reference_frame_transform: &LayerToWorldTransform,
                             parent_combined_viewport_rect: &ScrollLayerRect,
-                            parent_scroll_offset: LayerPoint,
-                            parent_accumulated_scroll_offset: LayerPoint) {
+                            parent_scroll_offset: LayerVector2D,
+                            parent_accumulated_scroll_offset: LayerVector2D) {
         self.reference_frame_relative_scroll_offset = match self.node_type {
-            NodeType::ReferenceFrame(_) => LayerPoint::zero(),
-            NodeType::Clip(_) => parent_accumulated_scroll_offset,
+            NodeType::ReferenceFrame(_) => LayerVector2D::zero(),
+            NodeType::Clip(_) | NodeType::ScrollFrame(..) => parent_accumulated_scroll_offset,
         };
 
         let local_transform = match self.node_type {
             NodeType::ReferenceFrame(transform) => transform,
-            NodeType::Clip(_) => LayerToScrollTransform::identity(),
+            NodeType::Clip(_) | NodeType::ScrollFrame(..) => LayerToScrollTransform::identity(),
         };
 
         let inv_transform = match local_transform.inverse() {
             Some(transform) => transform,
             None => {
                 // If a transform function causes the current transformation matrix of an object
                 // to be non-invertible, the object and its content do not get displayed.
                 self.combined_local_viewport_rect = LayerRect::zero();
@@ -260,24 +259,24 @@ impl ClipScrollNode {
         };
 
         // We are trying to move the combined viewport rectangle of our parent nodes into the
         // coordinate system of this node, so we must invert our transformation (only for
         // reference frames) and then apply the scroll offset the parent layer. The combined
         // local viewport rect doesn't include scrolling offsets so the only one that matters
         // is the relative offset between us and the parent.
         let parent_combined_viewport_in_local_space =
-            inv_transform.pre_translated(-parent_scroll_offset.x, -parent_scroll_offset.y, 0.0)
+            inv_transform.pre_translate(-as_scroll_parent_vector(&parent_scroll_offset).to_3d())
                          .transform_rect(parent_combined_viewport_rect);
 
         // Now that we have the combined viewport rectangle of the parent nodes in local space,
         // we do the intersection and get our combined viewport rect in the coordinate system
         // starting from our origin.
         self.combined_local_viewport_rect = match self.node_type {
-            NodeType::Clip(_) => {
+            NodeType::Clip(_) | NodeType::ScrollFrame(..) => {
                 parent_combined_viewport_in_local_space.intersection(&self.local_clip_rect)
                                                        .unwrap_or(LayerRect::zero())
             }
             NodeType::ReferenceFrame(_) => parent_combined_viewport_in_local_space,
         };
 
         // HACK: prevent the code above for non-AA transforms, it's incorrect.
         if (local_transform.m13, local_transform.m23) != (0.0, 0.0) {
@@ -286,172 +285,204 @@ impl ClipScrollNode {
 
         // The transformation for this viewport in world coordinates is the transformation for
         // our parent reference frame, plus any accumulated scrolling offsets from nodes
         // between our reference frame and this node. For reference frames, we also include
         // whatever local transformation this reference frame provides. This can be combined
         // with the local_viewport_rect to get its position in world space.
         self.world_viewport_transform =
             parent_reference_frame_transform
-                .pre_translated(parent_accumulated_scroll_offset.x,
-                                parent_accumulated_scroll_offset.y,
-                                0.0)
+                .pre_translate(parent_accumulated_scroll_offset.to_3d())
                 .pre_mul(&local_transform.with_destination::<LayerPixel>());
 
         // The transformation for any content inside of us is the viewport transformation, plus
         // whatever scrolling offset we supply as well.
+        let scroll_offset = self.scroll_offset();
         self.world_content_transform =
-            self.world_viewport_transform.pre_translated(self.scrolling.offset.x,
-                                                         self.scrolling.offset.y,
-                                                         0.0);
+            self.world_viewport_transform.pre_translate(scroll_offset.to_3d());
     }
 
     pub fn scrollable_height(&self) -> f32 {
         self.content_size.height - self.local_viewport_rect.size.height
     }
 
     pub fn scrollable_width(&self) -> f32 {
         self.content_size.width - self.local_viewport_rect.size.width
     }
 
     pub fn scroll(&mut self, scroll_location: ScrollLocation, phase: ScrollEventPhase) -> bool {
-        if self.scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
+        let scrollable_width = self.scrollable_width();
+        let scrollable_height = self.scrollable_height();
+
+        let scrolling = match self.node_type {
+            NodeType::ReferenceFrame(_) | NodeType::Clip(_) => return false,
+            NodeType::ScrollFrame(ref mut scrolling) => scrolling,
+        };
+
+        if scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) {
             return false;
         }
 
         let mut delta = match scroll_location {
             ScrollLocation::Delta(delta) => delta,
             ScrollLocation::Start => {
-                if self.scrolling.offset.y.round() >= 0.0 {
+                if scrolling.offset.y.round() >= 0.0 {
                     // Nothing to do on this layer.
                     return false;
                 }
 
-                self.scrolling.offset.y = 0.0;
+                scrolling.offset.y = 0.0;
                 return true;
             },
             ScrollLocation::End => {
                 let end_pos = self.local_viewport_rect.size.height - self.content_size.height;
 
-                if self.scrolling.offset.y.round() <= end_pos {
+                if scrolling.offset.y.round() <= end_pos {
                     // Nothing to do on this layer.
                     return false;
                 }
 
-                self.scrolling.offset.y = end_pos;
+                scrolling.offset.y = end_pos;
                 return true;
             }
         };
 
-        let overscroll_amount = self.overscroll_amount();
-        let overscrolling = CAN_OVERSCROLL && (overscroll_amount.width != 0.0 ||
-                                               overscroll_amount.height != 0.0);
+        let overscroll_amount = scrolling.overscroll_amount(scrollable_width, scrollable_height);
+        let overscrolling = CAN_OVERSCROLL && (overscroll_amount.x != 0.0 ||
+                                               overscroll_amount.y != 0.0);
         if overscrolling {
-            if overscroll_amount.width != 0.0 {
-                delta.x /= overscroll_amount.width.abs()
+            if overscroll_amount.x != 0.0 {
+                delta.x /= overscroll_amount.x.abs()
             }
-            if overscroll_amount.height != 0.0 {
-                delta.y /= overscroll_amount.height.abs()
+            if overscroll_amount.y != 0.0 {
+                delta.y /= overscroll_amount.y.abs()
             }
         }
 
-        let scrollable_width = self.scrollable_width();
-        let scrollable_height = self.scrollable_height();
         let is_unscrollable = scrollable_width <= 0. && scrollable_height <= 0.;
-        let original_layer_scroll_offset = self.scrolling.offset;
+        let original_layer_scroll_offset = scrolling.offset;
 
         if scrollable_width > 0. {
-            self.scrolling.offset.x = self.scrolling.offset.x + delta.x;
+            scrolling.offset.x = scrolling.offset.x + delta.x;
             if is_unscrollable || !CAN_OVERSCROLL {
-                self.scrolling.offset.x =
-                    self.scrolling.offset.x.min(0.0).max(-scrollable_width).round();
+                scrolling.offset.x = scrolling.offset.x.min(0.0).max(-scrollable_width).round();
             }
         }
 
         if scrollable_height > 0. {
-            self.scrolling.offset.y = self.scrolling.offset.y + delta.y;
+            scrolling.offset.y = scrolling.offset.y + delta.y;
             if is_unscrollable || !CAN_OVERSCROLL {
-                self.scrolling.offset.y =
-                    self.scrolling.offset.y.min(0.0).max(-scrollable_height).round();
+                scrolling.offset.y = scrolling.offset.y.min(0.0).max(-scrollable_height).round();
             }
         }
 
         if phase == ScrollEventPhase::Start || phase == ScrollEventPhase::Move(true) {
-            self.scrolling.started_bouncing_back = false
+            scrolling.started_bouncing_back = false
         } else if overscrolling &&
                 ((delta.x < 1.0 && delta.y < 1.0) || phase == ScrollEventPhase::End) {
-            self.scrolling.started_bouncing_back = true;
-            self.scrolling.bouncing_back = true
+            scrolling.started_bouncing_back = true;
+            scrolling.bouncing_back = true
         }
 
         if CAN_OVERSCROLL {
-            self.stretch_overscroll_spring();
+            scrolling.stretch_overscroll_spring(overscroll_amount);
         }
 
-        self.scrolling.offset != original_layer_scroll_offset ||
-            self.scrolling.started_bouncing_back
-    }
-
-    pub fn stretch_overscroll_spring(&mut self) {
-        let overscroll_amount = self.overscroll_amount();
-        self.scrolling.spring.coords(self.scrolling.offset,
-                                     self.scrolling.offset,
-                                     self.scrolling.offset + overscroll_amount);
+        scrolling.offset != original_layer_scroll_offset || scrolling.started_bouncing_back
     }
 
     pub fn tick_scrolling_bounce_animation(&mut self) {
-        let finished = self.scrolling.spring.animate();
-        self.scrolling.offset = self.scrolling.spring.current();
-        if finished {
-            self.scrolling.bouncing_back = false
+       if let NodeType::ScrollFrame(ref mut scrolling) = self.node_type {
+           scrolling.tick_scrolling_bounce_animation();
         }
     }
 
     pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool {
         let inv = self.world_viewport_transform.inverse().unwrap();
         let z0 = -10000.0;
         let z1 =  10000.0;
 
-        let p0 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z0, 1.0));
-        let p0 = Point3D::new(p0.x / p0.w,
-                              p0.y / p0.w,
-                              p0.z / p0.w);
-        let p1 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z1, 1.0));
-        let p1 = Point3D::new(p1.x / p1.w,
-                              p1.y / p1.w,
-                              p1.z / p1.w);
+        let p0 = inv.transform_point3d(&cursor.extend(z0));
+        let p1 = inv.transform_point3d(&cursor.extend(z1));
 
         if self.scrollable_width() <= 0. && self.scrollable_height() <= 0. {
             return false;
         }
-        ray_intersects_rect(p0, p1, self.local_viewport_rect.to_untyped())
+        ray_intersects_rect(p0.to_untyped(), p1.to_untyped(), self.local_viewport_rect.to_untyped())
     }
 
-    pub fn scroll_offset(&self) -> Option<LayerPoint> {
+    pub fn scroll_offset(&self) -> LayerVector2D {
         match self.node_type {
-            NodeType::Clip(_) if self.scrollable_width() > 0. || self.scrollable_height() > 0. =>
-                Some(self.scrolling.offset),
-            _ => None,
+            NodeType::ScrollFrame(ref scrolling) => scrolling.offset,
+            _ => LayerVector2D::zero(),
+        }
+    }
+
+    pub fn is_overscrolling(&self) -> bool {
+        match self.node_type {
+            NodeType::ScrollFrame(ref scrolling) => {
+                let overscroll_amount = scrolling.overscroll_amount(self.scrollable_width(),
+                                                                    self.scrollable_height());
+                overscroll_amount.x != 0.0 || overscroll_amount.y != 0.0
+            }
+            _ => false,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollingState {
-    pub offset: LayerPoint,
+    pub offset: LayerVector2D,
     pub spring: Spring,
     pub started_bouncing_back: bool,
     pub bouncing_back: bool,
     pub should_handoff_scroll: bool
 }
 
+/// Manages scrolling offset, overscroll state, etc.
 impl ScrollingState {
     pub fn new() -> ScrollingState {
         ScrollingState {
-            offset: LayerPoint::zero(),
+            offset: LayerVector2D::zero(),
             spring: Spring::at(LayerPoint::zero(), STIFFNESS, DAMPING),
             started_bouncing_back: false,
             bouncing_back: false,
             should_handoff_scroll: false
         }
     }
+
+    pub fn stretch_overscroll_spring(&mut self, overscroll_amount: LayerVector2D) {
+        let offset = self.offset.to_point();
+        self.spring.coords(offset, offset, offset + overscroll_amount);
+    }
+
+    pub fn tick_scrolling_bounce_animation(&mut self) {
+        let finished = self.spring.animate();
+        self.offset = self.spring.current().to_vector();
+        if finished {
+            self.bouncing_back = false
+        }
+    }
+
+    pub fn overscroll_amount(&self,
+                             scrollable_width: f32,
+                             scrollable_height: f32)
+                             -> LayerVector2D {
+        let overscroll_x = if self.offset.x > 0.0 {
+            -self.offset.x
+        } else if self.offset.x < -scrollable_width {
+            -scrollable_width - self.offset.x
+        } else {
+            0.0
+        };
+
+        let overscroll_y = if self.offset.y > 0.0 {
+            -self.offset.y
+        } else if self.offset.y < -scrollable_height {
+            -scrollable_height - self.offset.y
+        } else {
+            0.0
+        };
+
+        LayerVector2D::new(overscroll_x, overscroll_y)
+    }
 }
 
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -5,32 +5,32 @@
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use fnv::FnvHasher;
 use print_tree::PrintTree;
 use std::collections::{HashMap, HashSet};
 use std::hash::BuildHasherDefault;
 use webrender_traits::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
 use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollClamping, ScrollEventPhase};
 use webrender_traits::{ScrollLayerRect, ScrollLayerState, ScrollLocation, WorldPoint};
-use webrender_traits::as_scroll_parent_rect;
+use webrender_traits::{as_scroll_parent_rect, LayerVector2D};
 
 pub type ScrollStates = HashMap<ClipId, ScrollingState, BuildHasherDefault<FnvHasher>>;
 
 pub struct ClipScrollTree {
     pub nodes: HashMap<ClipId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
     pub pending_scroll_offsets: HashMap<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 reference frame id, used for giving a unique id to all new
-    /// reference frames. The ClipScrollTree increments this by one every time a
-    /// reference frame is created.
-    current_reference_frame_id: u64,
+    /// The current frame id, used for giving a unique id to all new dynamically
+    /// added frames and clips. The ClipScrollTree increments this by one every
+    /// time a new dynamic frame is created.
+    current_new_node_item: u64,
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```node``` being empty.
     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,
@@ -44,17 +44,17 @@ impl ClipScrollTree {
     pub fn new() -> ClipScrollTree {
         let dummy_pipeline = PipelineId(0, 0);
         ClipScrollTree {
             nodes: HashMap::default(),
             pending_scroll_offsets: HashMap::new(),
             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_reference_frame_id: 0,
+            current_new_node_item: 1,
             pipelines_to_discard: HashSet::new(),
         }
     }
 
     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));
@@ -67,37 +67,40 @@ impl ClipScrollTree {
         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();
         for (clip_id, node) in self.nodes.iter() {
-            if node.scrolling.bouncing_back {
-                nodes_bouncing_back.insert(*clip_id);
+            if let NodeType::ScrollFrame(ref scrolling) = node.node_type {
+                if scrolling.bouncing_back {
+                    nodes_bouncing_back.insert(*clip_id);
+                }
             }
         }
         nodes_bouncing_back
     }
 
     fn find_scrolling_node_at_point_in_node(&self,
                                             cursor: &WorldPoint,
                                             clip_id: ClipId)
                                             -> Option<ClipId> {
         self.nodes.get(&clip_id).and_then(|node| {
             for child_layer_id in node.children.iter().rev() {
-            if let Some(layer_id) =
-                self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id) {
+                if let Some(layer_id) =
+                   self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id) {
                     return Some(layer_id);
                 }
             }
 
-            if clip_id.is_reference_frame() {
-                return None;
+            match node.node_type {
+                NodeType::ScrollFrame(..) => {},
+                _ => return None,
             }
 
             if node.ray_intersects_node(cursor) {
                 Some(clip_id)
             } else {
                 None
             }
         })
@@ -106,44 +109,42 @@ impl ClipScrollTree {
     pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
         self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
             .unwrap_or(self.topmost_scrolling_node_id())
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         let mut result = vec![];
         for (id, node) in self.nodes.iter() {
-            match node.scroll_offset() {
-                Some(offset) => result.push(ScrollLayerState { id: *id, scroll_offset: offset }),
-                None => {}
+            if let NodeType::ScrollFrame(scrolling) = node.node_type {
+                result.push(ScrollLayerState { id: *id, scroll_offset: scrolling.offset })
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
-        self.current_reference_frame_id = 1;
+        self.current_new_node_item = 1;
 
         let mut scroll_states = HashMap::default();
         for (layer_id, old_node) in &mut self.nodes.drain() {
-            if !self.pipelines_to_discard.contains(&layer_id.pipeline_id()) {
-                scroll_states.insert(layer_id, old_node.scrolling);
+            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);
             }
         }
 
         self.pipelines_to_discard.clear();
         scroll_states
     }
 
     pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
-        if id.is_reference_frame() {
-            warn!("Tried to scroll a reference frame.");
-            return false;
-        }
-
         if self.nodes.is_empty() {
             self.pending_scroll_offsets.insert(id, (origin, clamp));
             return false;
         }
 
         if let Some(node) = self.nodes.get_mut(&id) {
             return node.set_scroll_origin(&origin, clamp);
         }
@@ -179,47 +180,43 @@ impl ClipScrollTree {
                 };
                 clip_id
             },
             (_, _, None) => return false,
         };
 
         let topmost_scrolling_node_id = self.topmost_scrolling_node_id();
         let non_root_overscroll = if clip_id != topmost_scrolling_node_id {
-            // true if the current node is overscrolling,
-            // and it is not the root scroll node.
-            let child_node = self.nodes.get(&clip_id).unwrap();
-            let overscroll_amount = child_node.overscroll_amount();
-            overscroll_amount.width != 0.0 || overscroll_amount.height != 0.0
+            self.nodes.get(&clip_id).unwrap().is_overscrolling()
         } else {
             false
         };
 
-        let switch_node = match phase {
-            ScrollEventPhase::Start => {
-                // if this is a new gesture, we do not switch node,
-                // however we do save the state of non_root_overscroll,
-                // for use in the subsequent Move phase.
-                let mut current_node = self.nodes.get_mut(&clip_id).unwrap();
-                current_node.scrolling.should_handoff_scroll = non_root_overscroll;
-                false
-            },
-            ScrollEventPhase::Move(_) => {
-                // Switch node if movement originated in a new gesture,
-                // from a non root node in overscroll.
-                let current_node = self.nodes.get_mut(&clip_id).unwrap();
-                current_node.scrolling.should_handoff_scroll && non_root_overscroll
-            },
-            ScrollEventPhase::End => {
-                // clean-up when gesture ends.
-                let mut current_node = self.nodes.get_mut(&clip_id).unwrap();
-                current_node.scrolling.should_handoff_scroll = false;
-                false
-            },
-        };
+        let mut switch_node = false;
+        if let Some(node) = self.nodes.get_mut(&clip_id) {
+            if let NodeType::ScrollFrame(ref mut scrolling) = node.node_type {
+                match phase {
+                    ScrollEventPhase::Start => {
+                        // if this is a new gesture, we do not switch node,
+                        // however we do save the state of non_root_overscroll,
+                        // for use in the subsequent Move phase.
+                        scrolling.should_handoff_scroll = non_root_overscroll;
+                    },
+                    ScrollEventPhase::Move(_) => {
+                        // Switch node if movement originated in a new gesture,
+                        // from a non root node in overscroll.
+                        switch_node = scrolling.should_handoff_scroll && non_root_overscroll
+                    },
+                    ScrollEventPhase::End => {
+                        // clean-up when gesture ends.
+                        scrolling.should_handoff_scroll = false;
+                    }
+                }
+            }
+        }
 
         let clip_id = if switch_node {
             topmost_scrolling_node_id
         } else {
             clip_id
         };
 
         self.nodes.get_mut(&clip_id).unwrap().scroll(scroll_location, phase)
@@ -230,26 +227,26 @@ impl ClipScrollTree {
             return;
         }
 
         let root_reference_frame_id = self.root_reference_frame_id();
         let root_viewport = self.nodes[&root_reference_frame_id].local_clip_rect;
         self.update_node_transform(root_reference_frame_id,
                                    &LayerToWorldTransform::create_translation(pan.x, pan.y, 0.0),
                                    &as_scroll_parent_rect(&root_viewport),
-                                   LayerPoint::zero(),
-                                   LayerPoint::zero());
+                                   LayerVector2D::zero(),
+                                   LayerVector2D::zero());
     }
 
     fn update_node_transform(&mut self,
                              layer_id: ClipId,
                              parent_reference_frame_transform: &LayerToWorldTransform,
                              parent_viewport_rect: &ScrollLayerRect,
-                             parent_scroll_offset: LayerPoint,
-                             parent_accumulated_scroll_offset: LayerPoint) {
+                             parent_scroll_offset: LayerVector2D,
+                             parent_accumulated_scroll_offset: LayerVector2D) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let (reference_frame_transform,
              viewport_rect,
              scroll_offset,
              accumulated_scroll_offset,
              node_children) = {
             match self.nodes.get_mut(&layer_id) {
@@ -260,21 +257,24 @@ impl ClipScrollTree {
                                           parent_accumulated_scroll_offset);
 
                     // The transformation we are passing is the transformation of the parent
                     // reference frame and the offset is the accumulated offset of all the nodes
                     // between us and the parent reference frame. If we are a reference frame,
                     // we need to reset both these values.
                     let (transform, offset, accumulated_scroll_offset) = match node.node_type {
                         NodeType::ReferenceFrame(..) =>
-                            (node.world_viewport_transform, LayerPoint::zero(), LayerPoint::zero()),
-                        NodeType::Clip(_) => {
+                            (node.world_viewport_transform,
+                             LayerVector2D::zero(),
+                             LayerVector2D::zero()),
+                        _ => {
+                            let scroll_offset = node.scroll_offset();
                             (*parent_reference_frame_transform,
-                             node.scrolling.offset,
-                             node.scrolling.offset + parent_accumulated_scroll_offset)
+                             scroll_offset,
+                             scroll_offset + parent_accumulated_scroll_offset)
                         }
                     };
 
                     (transform,
                      as_scroll_parent_rect(&node.combined_local_viewport_rect),
                      offset,
                      accumulated_scroll_offset,
                      node.children.clone())
@@ -311,27 +311,29 @@ impl ClipScrollTree {
 
             if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
                 node.set_scroll_origin(&pending_offset, clamping);
             }
         }
 
     }
 
+    pub fn generate_new_clip_id(&mut self, pipeline_id: PipelineId) -> ClipId {
+        let new_id = ClipId::DynamicallyAddedNode(self.current_new_node_item, pipeline_id);
+        self.current_new_node_item += 1;
+        new_id
+    }
+
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
                                transform: &LayerToScrollTransform,
                                pipeline_id: PipelineId,
                                parent_id: Option<ClipId>)
                                -> ClipId {
-
-        let reference_frame_id =
-            ClipId::ReferenceFrame(self.current_reference_frame_id, pipeline_id);
-        self.current_reference_frame_id += 1;
-
+        let reference_frame_id = self.generate_new_clip_id(pipeline_id);
         let node = ClipScrollNode::new_reference_frame(parent_id,
                                                        rect,
                                                        rect.size,
                                                        transform,
                                                        pipeline_id);
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
@@ -368,20 +370,23 @@ impl ClipScrollTree {
                 for source in &info.clip_sources {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref transform) => {
                 pt.new_level(format!("ReferenceFrame {:?}", transform));
             }
+            NodeType::ScrollFrame(scrolling_info) => {
+                pt.new_level(format!("ScrollFrame"));
+                pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
+            }
         }
 
         pt.add_item(format!("content_size: {:?}", node.content_size));
-        pt.add_item(format!("scroll.offset: {:?}", node.scrolling.offset));
         pt.add_item(format!("combined_local_viewport_rect: {:?}", node.combined_local_viewport_rect));
         pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
         pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
 
         for child_id in &node.children {
             self.print_node(child_id, pt);
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -1,16 +1,16 @@
 /* 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 euclid::{Matrix4D, Point2D, Size2D, Rect};
+use euclid::{Transform3D, Point2D, Size2D, Rect};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, TextureSampler};
 use internal_types::{DebugFontVertex, DebugColorVertex, RenderTargetMode, PackedColor};
 use std::f32;
 use webrender_traits::{ColorF, ImageFormat, DeviceUintSize};
 
 pub struct DebugRenderer {
     font_vertices: Vec<DebugFontVertex>,
     font_indices: Vec<u32>,
@@ -161,22 +161,22 @@ impl DebugRenderer {
     pub fn render(&mut self,
                   device: &mut Device,
                   viewport_size: &DeviceUintSize) {
         let _gm = GpuMarker::new(device.rc_gl(), "debug");
         device.disable_depth();
         device.set_blend(true);
         device.set_blend_mode_alpha();
 
-        let projection = Matrix4D::ortho(0.0,
-                                         viewport_size.width as f32,
-                                         viewport_size.height as f32,
-                                         0.0,
-                                         ORTHO_NEAR_PLANE,
-                                         ORTHO_FAR_PLANE);
+        let projection = Transform3D::ortho(0.0,
+                                            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_vao(self.tri_vao);
             device.update_vao_indices(self.tri_vao,
                                       &self.tri_indices,
                                       VertexUsageHint::Dynamic);
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use euclid::Matrix4D;
+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 notify::{self, Watcher};
 use super::shader_source;
 use std::collections::HashMap;
@@ -1054,17 +1054,17 @@ impl Device {
 
         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: &Matrix4D<f32>) {
+                        projection: &Transform3D<f32>) {
         debug_assert!(self.inside_frame);
 
         if self.bound_program != program_id {
             self.bound_program = program_id;
             program_id.bind(&*self.gl);
         }
 
         let program = self.programs.get(&program_id).unwrap();
@@ -1549,51 +1549,31 @@ impl Device {
             self.gl.uniform_1i(u_layers, TextureSampler::Layers as i32);
         }
 
         let u_tasks = self.gl.get_uniform_location(program.id, "sRenderTasks");
         if u_tasks != -1 {
             self.gl.uniform_1i(u_tasks, TextureSampler::RenderTasks as i32);
         }
 
-        let u_prim_geom = self.gl.get_uniform_location(program.id, "sPrimGeometry");
-        if u_prim_geom != -1 {
-            self.gl.uniform_1i(u_prim_geom, TextureSampler::Geometry as i32);
-        }
-
-        let u_data16 = self.gl.get_uniform_location(program.id, "sData16");
-        if u_data16 != -1 {
-            self.gl.uniform_1i(u_data16, TextureSampler::Data16 as i32);
-        }
-
         let u_data32 = self.gl.get_uniform_location(program.id, "sData32");
         if u_data32 != -1 {
             self.gl.uniform_1i(u_data32, TextureSampler::Data32 as i32);
         }
 
         let u_resource_cache = self.gl.get_uniform_location(program.id, "sResourceCache");
         if u_resource_cache != -1 {
             self.gl.uniform_1i(u_resource_cache, TextureSampler::ResourceCache as i32);
         }
 
         let u_resource_rects = self.gl.get_uniform_location(program.id, "sResourceRects");
         if u_resource_rects != -1 {
             self.gl.uniform_1i(u_resource_rects, TextureSampler::ResourceRects as i32);
         }
 
-        let u_gradients = self.gl.get_uniform_location(program.id, "sGradients");
-        if u_gradients != -1 {
-            self.gl.uniform_1i(u_gradients, TextureSampler::Gradients as i32);
-        }
-
-        let u_split_geometry = self.gl.get_uniform_location(program.id, "sSplitGeometry");
-        if u_split_geometry != -1 {
-            self.gl.uniform_1i(u_split_geometry, TextureSampler::SplitGeometry as i32);
-        }
-
         Ok(())
     }
 
 /*
     pub fn refresh_shader(&mut self, path: PathBuf) {
         let mut vs_preamble_path = self.resource_path.clone();
         vs_preamble_path.push(VERTEX_SHADER_PREAMBLE);
 
@@ -1637,17 +1617,17 @@ impl Device {
     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: &Matrix4D<f32>,
+                    transform: &Transform3D<f32>,
                     device_pixel_ratio: 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);
     }
 
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,32 +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/. */
 
 use app_units::Au;
-use euclid::rect::rect;
+use euclid::rect;
 use fnv::FnvHasher;
+use gpu_cache::GpuCache;
 use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection};
 use internal_types::{LowLevelFilterOp};
 use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::ResourceCache;
 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};
 use webrender_traits::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipDisplayItem};
 use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintRect, DeviceUintSize, DisplayItemRef};
 use webrender_traits::{Epoch, FilterOp, ImageDisplayItem, ItemRange, LayerPoint, LayerRect};
-use webrender_traits::{LayerSize, LayerToScrollTransform, LayoutSize, LayoutTransform};
+use webrender_traits::{LayerSize, LayerToScrollTransform, LayoutSize, LayoutTransform, LayerVector2D};
 use webrender_traits::{MixBlendMode, PipelineId, ScrollClamping, ScrollEventPhase};
 use webrender_traits::{ScrollLayerState, ScrollLocation, ScrollPolicy, SpecificDisplayItem};
 use webrender_traits::{StackingContext, TileOffset, TransformStyle, WorldPoint};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
@@ -85,58 +86,51 @@ impl StackingContextHelpers for Stacking
     }
 
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
                                   properties: &SceneProperties) -> Vec<LowLevelFilterOp> {
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
+            if filter.is_noop() {
+                continue;
+            }
+
             match filter {
                 FilterOp::Blur(radius) => {
-                    filters.push(LowLevelFilterOp::Blur(
-                        radius,
-                        AxisDirection::Horizontal));
-                    filters.push(LowLevelFilterOp::Blur(
-                        radius,
-                        AxisDirection::Vertical));
+                    filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Horizontal));
+                    filters.push(LowLevelFilterOp::Blur(radius, AxisDirection::Vertical));
                 }
                 FilterOp::Brightness(amount) => {
-                    filters.push(
-                            LowLevelFilterOp::Brightness(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Brightness(Au::from_f32_px(amount)));
                 }
                 FilterOp::Contrast(amount) => {
-                    filters.push(
-                            LowLevelFilterOp::Contrast(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Contrast(Au::from_f32_px(amount)));
                 }
                 FilterOp::Grayscale(amount) => {
-                    filters.push(
-                            LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)));
                 }
                 FilterOp::HueRotate(angle) => {
                     filters.push(
                             LowLevelFilterOp::HueRotate(f32::round(
                                     angle * ANGLE_FLOAT_TO_FIXED) as i32));
                 }
                 FilterOp::Invert(amount) => {
-                    filters.push(
-                            LowLevelFilterOp::Invert(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Invert(Au::from_f32_px(amount)));
                 }
                 FilterOp::Opacity(ref value) => {
                     let amount = properties.resolve_float(value, 1.0);
-                    filters.push(
-                            LowLevelFilterOp::Opacity(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Opacity(Au::from_f32_px(amount)));
                 }
                 FilterOp::Saturate(amount) => {
-                    filters.push(
-                            LowLevelFilterOp::Saturate(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Saturate(Au::from_f32_px(amount)));
                 }
                 FilterOp::Sepia(amount) => {
-                    filters.push(
-                            LowLevelFilterOp::Sepia(Au::from_f32_px(amount)));
+                    filters.push(LowLevelFilterOp::Sepia(Au::from_f32_px(amount)));
                 }
             }
         }
         filters
     }
 }
 
 fn clip_intersection(original_rect: &LayerRect,
@@ -212,18 +206,17 @@ impl Frame {
             None => return,
         };
 
         let root_pipeline = match scene.pipeline_map.get(&root_pipeline_id) {
             Some(root_pipeline) => root_pipeline,
             None => return,
         };
 
-        let display_list = scene.display_lists.get(&root_pipeline_id);
-        let display_list = match display_list {
+        let display_list = match scene.display_lists.get(&root_pipeline_id) {
             Some(display_list) => display_list,
             None => return,
         };
 
         if window_size.width == 0 || window_size.height == 0 {
             error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
         }
 
@@ -269,31 +262,39 @@ impl Frame {
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
                         pipeline_id: PipelineId,
                         parent_id: ClipId,
                         item: &ClipDisplayItem,
                         content_rect: &LayerRect,
                         clip: &ClipRegion) {
-        context.builder.add_clip_scroll_node(item.id,
+        let clip_viewport = LayerRect::new(content_rect.origin, clip.main.size);
+        let new_clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
+        context.builder.add_clip_scroll_node(new_clip_id,
                                              parent_id,
                                              pipeline_id,
-                                             &content_rect,
+                                             &clip_viewport,
                                              clip,
                                              &mut self.clip_scroll_tree);
+        context.builder.add_scroll_frame(item.id,
+                                         new_clip_id,
+                                         pipeline_id,
+                                         &content_rect,
+                                         &clip_viewport,
+                                         &mut self.clip_scroll_tree);
 
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut BuiltDisplayListIter<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_node_id: ClipId,
-                                    mut reference_frame_relative_offset: LayerPoint,
+                                    mut reference_frame_relative_offset: LayerVector2D,
                                     bounds: &LayerRect,
                                     stacking_context: &StackingContext,
                                     filters: ItemRange<FilterOp>) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
@@ -328,30 +329,30 @@ impl Frame {
             let transform = stacking_context.transform.as_ref();
             let transform = context.scene.properties.resolve_layout_transform(transform);
             let perspective =
                 stacking_context.perspective.unwrap_or_else(LayoutTransform::identity);
             let transform =
                 LayerToScrollTransform::create_translation(reference_frame_relative_offset.x,
                                                            reference_frame_relative_offset.y,
                                                            0.0)
-                                        .pre_translated(bounds.origin.x, bounds.origin.y, 0.0)
+                                        .pre_translate(bounds.origin.to_vector().to_3d())
                                         .pre_mul(&transform)
                                         .pre_mul(&perspective);
 
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
             clip_id = context.builder.push_reference_frame(Some(clip_id),
                                                            pipeline_id,
                                                            &reference_frame_bounds,
                                                            &transform,
                                                            &mut self.clip_scroll_tree);
             context.replacements.push((context_scroll_node_id, clip_id));
-            reference_frame_relative_offset = LayerPoint::zero();
+            reference_frame_relative_offset = LayerVector2D::zero();
         } else {
-            reference_frame_relative_offset = LayerPoint::new(
+            reference_frame_relative_offset = LayerVector2D::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
 
         context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
                                               composition_operations,
                                               *bounds,
@@ -373,62 +374,70 @@ impl Frame {
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_iframe<'a>(&mut self,
                           pipeline_id: PipelineId,
                           parent_id: ClipId,
                           bounds: &LayerRect,
+                          clip_region: &ClipRegion,
                           context: &mut FlattenContext,
-                          reference_frame_relative_offset: LayerPoint) {
+                          reference_frame_relative_offset: LayerVector2D) {
         let pipeline = match context.scene.pipeline_map.get(&pipeline_id) {
             Some(pipeline) => pipeline,
             None => return,
         };
 
-        let display_list = context.scene.display_lists.get(&pipeline_id);
-        let display_list = match display_list {
+        let display_list = match context.scene.display_lists.get(&pipeline_id) {
             Some(display_list) => display_list,
             None => return,
         };
 
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let transform = LayerToScrollTransform::create_translation(
             reference_frame_relative_offset.x + bounds.origin.x,
             reference_frame_relative_offset.y + bounds.origin.y,
             0.0);
 
+        let new_clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
+        context.builder.add_clip_scroll_node(new_clip_id,
+                                             parent_id,
+                                             parent_id.pipeline_id(),
+                                             bounds,
+                                             clip_region,
+                                             &mut self.clip_scroll_tree);
+
         let iframe_reference_frame_id =
-            context.builder.push_reference_frame(Some(parent_id),
+            context.builder.push_reference_frame(Some(new_clip_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
-        context.builder.add_clip_scroll_node(
+        context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
             &LayerRect::new(LayerPoint::zero(), pipeline.content_size),
-            &ClipRegion::simple(&iframe_rect),
+            &iframe_rect,
             &mut self.clip_scroll_tree);
 
         self.flatten_root(&mut display_list.iter(), pipeline_id, context, &pipeline.content_size);
 
         context.builder.pop_reference_frame();
     }
 
     fn flatten_item<'a, 'b>(&mut self,
                             item: DisplayItemRef<'a, 'b>,
                             pipeline_id: PipelineId,
                             context: &mut FlattenContext,
-                            reference_frame_relative_offset: LayerPoint)
+                            reference_frame_relative_offset: LayerVector2D)
                             -> Option<BuiltDisplayListIter<'a>> {
         let mut clip_and_scroll = item.clip_and_scroll();
         clip_and_scroll.scroll_node_id =
             context.clip_id_with_replacement(clip_and_scroll.scroll_node_id);
 
         match *item.item() {
             SpecificDisplayItem::WebGL(ref info) => {
                 context.builder.add_webgl_rectangle(clip_and_scroll,
@@ -478,23 +487,20 @@ impl Frame {
                                          text_info.size,
                                          text_info.blur_radius,
                                          &text_info.color,
                                          item.glyphs(),
                                          item.display_list().get(item.glyphs()).count(),
                                          text_info.glyph_options);
             }
             SpecificDisplayItem::Rectangle(ref info) => {
-                let display_list = context.scene.display_lists
-                                          .get(&pipeline_id)
-                                          .expect("No display list?!");
                 // Try to extract the opaque inner rectangle out of the clipped primitive.
                 if let Some(opaque_rect) = clip_intersection(&item.rect(),
                                                              item.clip_region(),
-                                                             display_list) {
+                                                             item.display_list()) {
                     let mut results = Vec::new();
                     subtract_rect(&item.rect(), &opaque_rect, &mut results);
                     // The inner rectangle is considered opaque within this layer.
                     // It may still inherit some masking from the clip stack.
                     context.builder.add_solid_rectangle(clip_and_scroll,
                                                         &opaque_rect,
                                                         &ClipRegion::simple(&item.clip_region().main),
                                                         &info.color,
@@ -572,16 +578,17 @@ impl Frame {
                                               &info.stacking_context,
                                               item.filters());
                 return Some(subtraversal);
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(info.pipeline_id,
                                     clip_and_scroll.scroll_node_id,
                                     &item.rect(),
+                                    &item.clip_region(),
                                     context,
                                     reference_frame_relative_offset);
             }
             SpecificDisplayItem::Clip(ref info) => {
                 let content_rect = &item.rect().translate(&reference_frame_relative_offset);
                 self.flatten_clip(context,
                                   pipeline_id,
                                   clip_and_scroll.scroll_node_id,
@@ -600,45 +607,45 @@ impl Frame {
     }
 
     fn flatten_root<'a>(&mut self,
                         traversal: &mut BuiltDisplayListIter<'a>,
                         pipeline_id: PipelineId,
                         context: &mut FlattenContext,
                         content_size: &LayoutSize) {
         let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size);
-        context.builder.push_stacking_context(&LayerPoint::zero(),
+        context.builder.push_stacking_context(&LayerVector2D::zero(),
                                               pipeline_id,
                                               CompositeOps::default(),
                                               root_bounds,
                                               TransformStyle::Flat);
 
         // We do this here, rather than above because we want any of the top-level
         // stacking contexts in the display list to be treated like root stacking contexts.
         // FIXME(mrobinson): Currently only the first one will, which for the moment is
         // sufficient for all our use cases.
         context.builder.notify_waiting_for_root_stacking_context();
 
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         let clip_id = ClipId::root_scroll_node(pipeline_id);
-        if context.scene.root_pipeline_id.unwrap() != pipeline_id {
+        if context.scene.root_pipeline_id != Some(pipeline_id) {
             if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     context.builder.add_solid_rectangle(ClipAndScrollInfo::simple(clip_id),
                                                         &root_bounds,
                                                         &ClipRegion::simple(&root_bounds),
                                                         &bg_color,
                                                         PrimitiveFlags::None);
                 }
             }
         }
 
 
-        self.flatten_items(traversal, pipeline_id, context, LayerPoint::zero());
+        self.flatten_items(traversal, pipeline_id, context, LayerVector2D::zero());
 
         if self.frame_builder_config.enable_scrollbars {
             let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
             context.builder.add_solid_rectangle(
                 ClipAndScrollInfo::simple(clip_id),
                 &scrollbar_rect,
                 &ClipRegion::simple(&scrollbar_rect),
                 &DEFAULT_SCROLLBAR_COLOR,
@@ -647,17 +654,17 @@ impl Frame {
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_items<'a>(&mut self,
                          traversal: &mut BuiltDisplayListIter<'a>,
                          pipeline_id: PipelineId,
                          context: &mut FlattenContext,
-                         reference_frame_relative_offset: LayerPoint) {
+                         reference_frame_relative_offset: LayerVector2D) {
         loop {
             let subtraversal = {
                 let item = match traversal.next() {
                     Some(item) => item,
                     None => break,
                 };
 
                 if SpecificDisplayItem::PopStackingContext == *item.item() {
@@ -930,17 +937,17 @@ impl Frame {
         // See the repeat_x/y code below.
 
         let stretched_size = LayerSize::new(
             stretched_tile_size.width * tile_ratio_width,
             stretched_tile_size.height * tile_ratio_height,
         );
 
         let mut prim_rect = LayerRect::new(
-            item_rect.origin + LayerPoint::new(
+            item_rect.origin + LayerVector2D::new(
                 tile_offset.x as f32 * stretched_tile_size.width,
                 tile_offset.y as f32 * stretched_tile_size.height,
             ),
             stretched_size,
         );
 
         if repeat_x {
             assert_eq!(tile_offset.x, 0);
@@ -963,45 +970,49 @@ impl Frame {
                                       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,
                  pan: LayerPoint,
                  texture_cache_profile: &mut TextureCacheProfileCounters,
                  gpu_cache_profile: &mut GpuCacheProfileCounters)
                  -> 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,
                    texture_cache_profile: &mut TextureCacheProfileCounters,
                    gpu_cache_profile: &mut GpuCacheProfileCounters)
                    -> RendererFrame {
         let mut frame_builder = self.frame_builder.take();
         let frame = frame_builder.as_mut().map(|builder|
             builder.build(resource_cache,
+                          gpu_cache,
                           self.id,
                           &mut self.clip_scroll_tree,
                           display_lists,
                           device_pixel_ratio,
                           texture_cache_profile,
                           gpu_cache_profile)
         );
         self.frame_builder = frame_builder;
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,45 +1,46 @@
 /* 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 frame::FrameId;
+use gpu_cache::GpuCache;
 use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, SourceTexture};
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo, RegionMode};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu};
-use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
+use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
-use prim_store::{RectanglePrimitive, SplitGeometry, TextRunPrimitiveCpu};
+use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, 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 euclid::{SideOffsets2D, TypedPoint3D};
+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::RectHelpers;
 use webrender_traits::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo};
 use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{DeviceUintRect, DeviceUintSize, ExtendMode, FontKey, FontRenderMode};
 use webrender_traits::{GlyphInstance, GlyphOptions, GradientStop, ImageKey, ImageRendering};
 use webrender_traits::{ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
 use webrender_traits::{PipelineId, RepeatMode, TileOffset, TransformStyle, WebGLContextId};
-use webrender_traits::{WorldPixel, YuvColorSpace, YuvData};
+use webrender_traits::{WorldPixel, YuvColorSpace, YuvData, LayerVector2D};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
     stretch_size: LayerSize,
     tile_spacing: LayerSize,
 }
@@ -90,17 +91,17 @@ impl ImageBorderSegment {
 fn make_polygon(sc: &StackingContext, node: &ClipScrollNode, anchor: usize)
                 -> Polygon<f32, WorldPixel> {
     //TODO: only work with `sc.local_bounds` worth of space
     // This can be achieved by moving the `sc.local_bounds.origin` shift
     // from the primitive local coordinates into the layer transformation.
     // Which in turn needs it to be a render task property obeyed by all primitives
     // upon rendering, possibly not limited to `write_*_vertex` implementations.
     let size = sc.local_bounds.bottom_right();
-    let bounds = LayerRect::new(sc.reference_frame_offset, LayerSize::new(size.x, size.y));
+    let bounds = LayerRect::new(sc.reference_frame_offset.to_point(), LayerSize::new(size.x, size.y));
     Polygon::from_transformed_rect(bounds, node.world_content_transform, anchor)
 }
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
@@ -192,31 +193,28 @@ impl FrameBuilder {
                          clip_region: &ClipRegion,
                          extra_clips: &[ClipSource],
                          container: PrimitiveContainer)
                          -> PrimitiveIndex {
         let stacking_context_index = *self.stacking_context_stack.last().unwrap();
 
         self.create_clip_scroll_group_if_necessary(stacking_context_index, clip_and_scroll);
 
-        let geometry = PrimitiveGeometry {
-            local_rect: *rect,
-            local_clip_rect: clip_region.main,
-        };
         let mut clip_sources = Vec::new();
         if clip_region.is_complex() {
             clip_sources.push(ClipSource::Region(clip_region.clone(), RegionMode::ExcludeRect));
         }
 
         clip_sources.extend(extra_clips.iter().cloned());
 
         let clip_info = MaskCacheInfo::new(&clip_sources,
                                            &mut self.prim_store.gpu_data32);
 
-        let prim_index = self.prim_store.add_primitive(geometry,
+        let prim_index = self.prim_store.add_primitive(rect,
+                                                       &clip_region.main,
                                                        clip_sources,
                                                        clip_info,
                                                        container);
 
         match self.cmds.last_mut().unwrap() {
             &mut PrimitiveRunCmd::PrimitiveRun(_run_prim_index, ref mut count, run_clip_and_scroll)
                 if run_clip_and_scroll == clip_and_scroll => {
                     debug_assert!(_run_prim_index.0 + *count == prim_index.0);
@@ -250,17 +248,17 @@ impl FrameBuilder {
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, info)
     }
 
     pub fn notify_waiting_for_root_stacking_context(&mut self) {
         self.has_root_stacking_context = false;
     }
 
     pub fn push_stacking_context(&mut self,
-                                 reference_frame_offset: &LayerPoint,
+                                 reference_frame_offset: &LayerVector2D,
                                  pipeline_id: PipelineId,
                                  composite_ops: CompositeOps,
                                  local_bounds: LayerRect,
                                  transform_style: TransformStyle) {
         if let Some(parent_index) = self.stacking_context_stack.last() {
             let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
 
             if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
@@ -350,22 +348,24 @@ impl FrameBuilder {
                      clip_scroll_tree: &mut ClipScrollTree)
                      -> ClipId {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
         let identity = &LayerToScrollTransform::identity();
         self.push_reference_frame(None, pipeline_id, &viewport_rect, identity, clip_scroll_tree);
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
-        self.add_clip_scroll_node(topmost_scrolling_node_id,
-                                   clip_scroll_tree.root_reference_frame_id,
-                                   pipeline_id,
-                                   &LayerRect::new(LayerPoint::zero(), *content_size),
-                                   &ClipRegion::simple(&viewport_rect),
-                                   clip_scroll_tree);
+
+        self.add_scroll_frame(topmost_scrolling_node_id,
+                              clip_scroll_tree.root_reference_frame_id,
+                              pipeline_id,
+                              &LayerRect::new(LayerPoint::zero(), *content_size),
+                              &viewport_rect,
+                              clip_scroll_tree);
+
         topmost_scrolling_node_id
     }
 
     pub fn add_clip_scroll_node(&mut self,
                                 new_node_id: ClipId,
                                 parent_id: ClipId,
                                 pipeline_id: PipelineId,
                                 content_rect: &LayerRect,
@@ -379,16 +379,31 @@ impl FrameBuilder {
                                        content_rect,
                                        &clip_region.main,
                                        clip_info);
 
         clip_scroll_tree.add_node(node, new_node_id);
         self.packed_layers.push(PackedLayer::empty());
     }
 
+    pub fn add_scroll_frame(&mut self,
+                            new_node_id: ClipId,
+                            parent_id: ClipId,
+                            pipeline_id: PipelineId,
+                            content_rect: &LayerRect,
+                            frame_rect: &LayerRect,
+                            clip_scroll_tree: &mut ClipScrollTree) {
+        let node = ClipScrollNode::new_scroll_frame(pipeline_id,
+                                                    parent_id,
+                                                    content_rect,
+                                                    frame_rect);
+
+        clip_scroll_tree.add_node(node, new_node_id);
+    }
+
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
     pub fn add_solid_rectangle(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: &LayerRect,
                                clip_region: &ClipRegion,
@@ -431,27 +446,27 @@ impl FrameBuilder {
             // Calculate the modified rect as specific by border-image-outset
             let origin = LayerPoint::new(rect.origin.x - outset.left,
                                          rect.origin.y - outset.top);
             let size = LayerSize::new(rect.size.width + outset.left + outset.right,
                                       rect.size.height + outset.top + outset.bottom);
             let rect = LayerRect::new(origin, size);
 
             let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
-            let tl_inner = tl_outer + LayerPoint::new(border_item.widths.left, border_item.widths.top);
+            let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
 
             let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-            let tr_inner = tr_outer + LayerPoint::new(-border_item.widths.right, border_item.widths.top);
+            let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
 
             let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-            let bl_inner = bl_outer + LayerPoint::new(border_item.widths.left, -border_item.widths.bottom);
+            let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
 
             let br_outer = LayerPoint::new(rect.origin.x + rect.size.width,
                                            rect.origin.y + rect.size.height);
-            let br_inner = br_outer - LayerPoint::new(border_item.widths.right, border_item.widths.bottom);
+            let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
 
             // Build the list of gradient segments
             vec![
                 // Top left
                 LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
                 // Top right
                 LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
                 // Bottom right
@@ -485,27 +500,27 @@ impl FrameBuilder {
                 let px3 = border.patch.width;
 
                 let py0 = 0;
                 let py1 = border.patch.slice.top;
                 let py2 = border.patch.height - border.patch.slice.bottom;
                 let py3 = border.patch.height;
 
                 let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
-                let tl_inner = tl_outer + LayerPoint::new(border_item.widths.left, border_item.widths.top);
+                let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
 
                 let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-                let tr_inner = tr_outer + LayerPoint::new(-border_item.widths.right, border_item.widths.top);
+                let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
 
                 let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-                let bl_inner = bl_outer + LayerPoint::new(border_item.widths.left, -border_item.widths.bottom);
+                let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
 
                 let br_outer = LayerPoint::new(rect.origin.x + rect.size.width,
                                                rect.origin.y + rect.size.height);
-                let br_inner = br_outer - LayerPoint::new(border_item.widths.right, border_item.widths.bottom);
+                let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
 
                 // Build the list of image segments
                 let mut segments = vec![
                     // Top left
                     ImageBorderSegment::new(LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
                                             TexelRect::new(px0, py0, px1, py1),
                                             RepeatMode::Stretch,
                                             RepeatMode::Stretch),
@@ -659,19 +674,16 @@ impl FrameBuilder {
             (start_point, end_point)
         };
 
         let gradient_cpu = GradientPrimitiveCpu {
             stops_range: stops,
             stops_count: stops_count,
             extend_mode: extend_mode,
             reverse_stops: reverse_stops,
-            cache_dirty: true,
-            gpu_data_address: GpuStoreAddress(0),
-            gpu_data_count: 0,
             gpu_blocks: [
                 [sp.x, sp.y, ep.x, ep.y].into(),
                 [tile_size.width, tile_size.height, tile_repeat.width, tile_repeat.height].into(),
                 [pack_as_float(extend_mode as u32), 0.0, 0.0, 0.0].into(),
             ],
         };
 
         let prim = if aligned {
@@ -696,17 +708,16 @@ impl FrameBuilder {
                                extend_mode: ExtendMode,
                                tile_size: LayerSize,
                                tile_spacing: LayerSize) {
         let tile_repeat = tile_size + tile_spacing;
 
         let radial_gradient_cpu = RadialGradientPrimitiveCpu {
             stops_range: stops,
             extend_mode: extend_mode,
-            cache_dirty: true,
             gpu_data_address: GpuStoreAddress(0),
             gpu_data_count: 0,
             gpu_blocks: [
                 [start_center.x, start_center.y, end_center.x, end_center.y].into(),
                 [start_radius, end_radius, ratio_xy, pack_as_float(extend_mode as u32)].into(),
                 [tile_size.width, tile_size.height, tile_repeat.width, tile_repeat.height].into(),
             ],
         };
@@ -767,25 +778,22 @@ impl FrameBuilder {
         }
 
         let prim_cpu = TextRunPrimitiveCpu {
             font_key: font_key,
             logical_font_size: size,
             blur_radius: blur_radius,
             glyph_range: glyph_range,
             glyph_count: glyph_count,
-            cache_dirty: true,
             glyph_instances: Vec::new(),
             color_texture_id: SourceTexture::Invalid,
             color: *color,
             render_mode: render_mode,
             glyph_options: glyph_options,
             resource_address: GpuStoreAddress(0),
-            gpu_data_address: GpuStoreAddress(0),
-            gpu_data_count: 0,
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            clip_region,
                            &[],
                            PrimitiveContainer::TextRun(prim_cpu));
     }
@@ -826,17 +834,17 @@ impl FrameBuilder {
                            &extra_clips,
                            PrimitiveContainer::Rectangle(prim));
     }
 
     pub fn add_box_shadow(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
                           box_bounds: &LayerRect,
                           clip_region: &ClipRegion,
-                          box_offset: &LayerPoint,
+                          box_offset: &LayerVector2D,
                           color: &ColorF,
                           blur_radius: f32,
                           spread_radius: f32,
                           border_radius: f32,
                           clip_mode: BoxShadowClipMode) {
         if color.a == 0.0 {
             return
         }
@@ -1085,74 +1093,80 @@ impl FrameBuilder {
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
                                                 clip_scroll_tree: &mut ClipScrollTree,
                                                 display_lists: &DisplayListMap,
                                                 resource_cache: &mut ResourceCache,
+                                                gpu_cache: &mut GpuCache,
                                                 profile_counters: &mut FrameProfileCounters,
                                                 device_pixel_ratio: f32) {
         profile_scope!("cull");
         LayerRectCalculationAndCullingPass::create_and_run(self,
                                                            screen_rect,
                                                            clip_scroll_tree,
                                                            display_lists,
                                                            resource_cache,
+                                                           gpu_cache,
                                                            profile_counters,
                                                            device_pixel_ratio);
     }
 
-    fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree) {
+    fn update_scroll_bars(&mut self,
+                          clip_scroll_tree: &ClipScrollTree,
+                          gpu_cache: &mut GpuCache) {
         let distance_from_edge = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
-            let mut geom = (*self.prim_store.gpu_geometry.get(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32))).clone();
+            let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
             let clip_scroll_node = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
 
+            // Invalidate what's in the cache so it will get rebuilt.
+            gpu_cache.invalidate(&metadata.gpu_location);
+
             let scrollable_distance = clip_scroll_node.scrollable_height();
 
             if scrollable_distance <= 0.0 {
-                geom.local_clip_rect.size = LayerSize::zero();
-                *self.prim_store.gpu_geometry.get_mut(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32)) = geom;
+                metadata.local_clip_rect.size = LayerSize::zero();
                 continue;
             }
 
-            let f = -clip_scroll_node.scrolling.offset.y / scrollable_distance;
+            let scroll_offset = clip_scroll_node.scroll_offset();
+            let f = -scroll_offset.y / scrollable_distance;
 
             let min_y = clip_scroll_node.local_viewport_rect.origin.y -
-                        clip_scroll_node.scrolling.offset.y +
+                        scroll_offset.y +
                         distance_from_edge;
 
             let max_y = clip_scroll_node.local_viewport_rect.origin.y +
                         clip_scroll_node.local_viewport_rect.size.height -
-                        clip_scroll_node.scrolling.offset.y -
-                        geom.local_rect.size.height -
+                        scroll_offset.y -
+                        metadata.local_rect.size.height -
                         distance_from_edge;
 
-            geom.local_rect.origin.x = clip_scroll_node.local_viewport_rect.origin.x +
-                                       clip_scroll_node.local_viewport_rect.size.width -
-                                       geom.local_rect.size.width -
-                                       distance_from_edge;
-
-            geom.local_rect.origin.y = util::lerp(min_y, max_y, f);
-            geom.local_clip_rect = geom.local_rect;
+            metadata.local_rect.origin.x = clip_scroll_node.local_viewport_rect.origin.x +
+                                           clip_scroll_node.local_viewport_rect.size.width -
+                                           metadata.local_rect.size.width -
+                                           distance_from_edge;
 
-            let clip_source = if scrollbar_prim.border_radius > 0.0 {
-                Some(ClipSource::Complex(geom.local_rect, scrollbar_prim.border_radius, ClipMode::Clip))
-            } else {
-                None
-            };
-            self.prim_store.set_clip_source(scrollbar_prim.prim_index, clip_source);
-            *self.prim_store.gpu_geometry.get_mut(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32)) = geom;
+            metadata.local_rect.origin.y = util::lerp(min_y, max_y, f);
+            metadata.local_clip_rect = metadata.local_rect;
+
+            // TODO(gw): The code to set / update border clips on scroll bars
+            //           has been broken for a long time, so I've removed it
+            //           for now. We can re-add that code once the clips
+            //           data is moved over to the GPU cache!
         }
     }
 
-    fn build_render_task(&mut self, clip_scroll_tree: &ClipScrollTree)
+    fn build_render_task(&mut self,
+                         clip_scroll_tree: &ClipScrollTree,
+                         gpu_cache: &mut GpuCache)
                          -> (RenderTask, usize) {
         profile_scope!("build_render_task");
 
         let mut next_z = 0;
         let mut next_task_index = RenderTaskIndex(0);
 
         let mut sc_stack: Vec<StackingContextIndex> = Vec::new();
         let mut current_task = RenderTask::new_alpha_batch(next_task_index,
@@ -1166,17 +1180,16 @@ impl FrameBuilder {
         // 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();
         // The plane splitter, using a simple BSP tree.
         let mut splitter = BspSplitter::new();
 
-        self.prim_store.gpu_split_geometry.clear();
         debug!("build_render_task()");
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
                     let parent_isolation = sc_stack.last()
                                                    .map(|index| self.stacking_context_store[index.0].isolation);
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
@@ -1284,29 +1297,28 @@ impl FrameBuilder {
                     }
 
                     if !preserve_3d_map.is_empty() && parent_isolation != Some(ContextIsolation::Items) {
                         // Flush the accumulated plane splits onto the task tree.
                         // Notice how this is done before splitting in order to avoid duplicate tasks.
                         current_task.children.extend(preserve_3d_map.values().cloned());
                         debug!("\tplane splitting in {:?}", current_task.id);
                         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
-                        for poly in splitter.sort(TypedPoint3D::new(0.0, 0.0, 1.0)) {
+                        for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
                             let sc_index = StackingContextIndex(poly.anchor);
                             let task_id = preserve_3d_map[&sc_index].id;
                             debug!("\t\tproduce {:?} -> {:?} for {:?}", sc_index, poly, task_id);
                             let pp = &poly.points;
-                            let split_geo = SplitGeometry {
-                                data: [pp[0].x, pp[0].y, pp[0].z,
-                                       pp[1].x, pp[1].y, pp[1].z,
-                                       pp[2].x, pp[2].y, pp[2].z,
-                                       pp[3].x, pp[3].y, pp[3].z],
-                            };
-                            let gpu_index = self.prim_store.gpu_split_geometry.push(split_geo);
-                            let item = AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_index, next_z);
+                            let gpu_blocks = [
+                                [pp[0].x, pp[0].y, pp[0].z, pp[1].x].into(),
+                                [pp[1].y, pp[1].z, pp[2].x, pp[2].y].into(),
+                                [pp[2].z, pp[3].x, pp[3].y, pp[3].z].into(),
+                            ];
+                            let handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
+                            let item = AlphaRenderItem::SplitComposite(sc_index, task_id, handle, next_z);
                             current_task.as_alpha_batch().items.push(item);
                         }
                         splitter.reset();
                         preserve_3d_map.clear();
                         next_z += 1;
                     }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
@@ -1352,52 +1364,55 @@ impl FrameBuilder {
         debug_assert!(alpha_task_stack.is_empty());
         debug_assert!(preserve_3d_map.is_empty());
         debug_assert_eq!(current_task.id, RenderTaskId::Static(RenderTaskIndex(0)));
         (current_task, next_task_index.0)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
+                 gpu_cache: &mut GpuCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &mut ClipScrollTree,
                  display_lists: &DisplayListMap,
                  device_pixel_ratio: f32,
                  texture_cache_profile: &mut TextureCacheProfileCounters,
                  gpu_cache_profile: &mut GpuCacheProfileCounters)
                  -> Frame {
         profile_scope!("build");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters.total_primitives.set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
+        gpu_cache.begin_frame();
 
         let screen_rect = DeviceIntRect::new(
             DeviceIntPoint::zero(),
             DeviceIntSize::new(self.screen_size.width as i32,
                                self.screen_size.height as i32));
 
         // Pick a size for the cache render targets to be. The main requirement is that it
         // has to be at least as large as the framebuffer size. This ensures that it will
         // always be able to allocate the worst case render task (such as a clip mask that
         // covers the entire screen).
         let cache_size = DeviceUintSize::new(cmp::max(1024, screen_rect.size.width as u32),
                                              cmp::max(1024, screen_rect.size.height as u32));
 
-        self.update_scroll_bars(clip_scroll_tree);
+        self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
         self.build_layer_screen_rects_and_cull_layers(&screen_rect,
                                                       clip_scroll_tree,
                                                       display_lists,
                                                       resource_cache,
+                                                      gpu_cache,
                                                       &mut profile_counters,
                                                       device_pixel_ratio);
 
-        let (main_render_task, static_render_task_count) = self.build_render_task(clip_scroll_tree);
+        let (main_render_task, static_render_task_count) = self.build_render_task(clip_scroll_tree, gpu_cache);
         let mut render_tasks = RenderTaskCollection::new(static_render_task_count);
 
         let mut required_pass_count = 0;
         main_render_task.max_depth(0, &mut required_pass_count);
 
         resource_cache.block_until_all_resources_added(texture_cache_profile);
 
         for node in clip_scroll_tree.nodes.values() {
@@ -1406,18 +1421,17 @@ impl FrameBuilder {
                     self.prim_store.resolve_clip_cache(mask_info, resource_cache);
                 }
             }
         }
 
         let deferred_resolves = self.prim_store.resolve_primitives(resource_cache,
                                                                    device_pixel_ratio);
 
-        let gpu_cache_updates = resource_cache.gpu_cache
-                                              .end_frame(gpu_cache_profile);
+        let gpu_cache_updates = gpu_cache.end_frame(gpu_cache_profile);
 
         let mut passes = Vec::new();
 
         // Do the allocations now, assigning each tile's tasks to a render
         // pass and target as required.
         for index in 0..required_pass_count {
             passes.push(RenderPass::new(index as isize,
                                         index == required_pass_count-1,
@@ -1427,16 +1441,17 @@ impl FrameBuilder {
         main_render_task.assign_to_passes(passes.len() - 1, &mut passes);
 
         for pass in &mut passes {
             let ctx = RenderTargetContext {
                 stacking_context_store: &self.stacking_context_store,
                 clip_scroll_group_store: &self.clip_scroll_group_store,
                 prim_store: &self.prim_store,
                 resource_cache: resource_cache,
+                gpu_cache: gpu_cache,
             };
 
             pass.build(&ctx, &mut render_tasks);
 
             profile_counters.passes.inc();
             profile_counters.color_targets.add(pass.color_targets.target_count());
             profile_counters.alpha_targets.add(pass.alpha_targets.target_count());
         }
@@ -1447,35 +1462,32 @@ impl FrameBuilder {
             device_pixel_ratio: device_pixel_ratio,
             background_color: self.background_color,
             window_size: self.screen_size,
             profile_counters: profile_counters,
             passes: passes,
             cache_size: cache_size,
             layer_texture_data: self.packed_layers.clone(),
             render_task_data: render_tasks.render_task_data,
-            gpu_data16: self.prim_store.gpu_data16.build(),
             gpu_data32: self.prim_store.gpu_data32.build(),
-            gpu_geometry: self.prim_store.gpu_geometry.build(),
-            gpu_gradient_data: self.prim_store.gpu_gradient_data.build(),
-            gpu_split_geometry: self.prim_store.gpu_split_geometry.build(),
             gpu_resource_rects: self.prim_store.gpu_resource_rects.build(),
             deferred_resolves: deferred_resolves,
             gpu_cache_updates: Some(gpu_cache_updates),
         }
     }
 
 }
 
 struct LayerRectCalculationAndCullingPass<'a> {
     frame_builder: &'a mut FrameBuilder,
     screen_rect: &'a DeviceIntRect,
     clip_scroll_tree: &'a mut ClipScrollTree,
     display_lists: &'a DisplayListMap,
     resource_cache: &'a mut ResourceCache,
+    gpu_cache: &'a mut GpuCache,
     profile_counters: &'a mut FrameProfileCounters,
     device_pixel_ratio: f32,
     stacking_context_stack: Vec<StackingContextIndex>,
 
     /// A cached clip info stack, which should handle the most common situation,
     /// which is that we are using the same clip info stack that we were using
     /// previously.
     current_clip_stack: Vec<(PackedLayerIndex, MaskCacheInfo)>,
@@ -1486,25 +1498,27 @@ struct LayerRectCalculationAndCullingPas
 }
 
 impl<'a> LayerRectCalculationAndCullingPass<'a> {
     fn create_and_run(frame_builder: &'a mut FrameBuilder,
                       screen_rect: &'a DeviceIntRect,
                       clip_scroll_tree: &'a mut ClipScrollTree,
                       display_lists: &'a DisplayListMap,
                       resource_cache: &'a mut ResourceCache,
+                      gpu_cache: &'a mut GpuCache,
                       profile_counters: &'a mut FrameProfileCounters,
                       device_pixel_ratio: f32) {
 
         let mut pass = LayerRectCalculationAndCullingPass {
             frame_builder: frame_builder,
             screen_rect: screen_rect,
             clip_scroll_tree: clip_scroll_tree,
             display_lists: display_lists,
             resource_cache: resource_cache,
+            gpu_cache: gpu_cache,
             profile_counters: profile_counters,
             device_pixel_ratio: device_pixel_ratio,
             stacking_context_stack: Vec::new(),
             current_clip_stack: Vec::new(),
             current_clip_info: None,
         };
         pass.run();
     }
@@ -1527,35 +1541,33 @@ impl<'a> LayerRectCalculationAndCullingP
 
         mem::replace(&mut self.frame_builder.cmds, commands);
     }
 
     fn recalculate_clip_scroll_nodes(&mut self) {
         for (_, ref mut node) in self.clip_scroll_tree.nodes.iter_mut() {
             let node_clip_info = match node.node_type {
                 NodeType::Clip(ref mut clip_info) => clip_info,
-                NodeType::ReferenceFrame(_) => continue,
+                _ => continue,
             };
 
             let packed_layer_index = node_clip_info.packed_layer_index;
             let packed_layer = &mut self.frame_builder.packed_layers[packed_layer_index.0];
 
             // The coordinates of the mask are relative to the origin of the node itself,
             // so we need to account for that origin in the transformation we assign to
             // the packed layer.
             let transform = node.world_viewport_transform
-                                .pre_translated(node.local_viewport_rect.origin.x,
-                                                node.local_viewport_rect.origin.y,
-                                                0.0);
+                .pre_translate(node.local_viewport_rect.origin.to_vector().to_3d());
             packed_layer.set_transform(transform);
 
             // Meanwhile, the combined viewport rect is relative to the reference frame, so
             // we move it into the local coordinate system of the node.
             let local_viewport_rect =
-                node.combined_local_viewport_rect.translate(&-node.local_viewport_rect.origin);
+                node.combined_local_viewport_rect.translate(&-node.local_viewport_rect.origin.to_vector());
 
             node_clip_info.screen_bounding_rect = packed_layer.set_rect(&local_viewport_rect,
                                                                         self.screen_rect,
                                                                         self.device_pixel_ratio);
 
             let mask_info = match node_clip_info.mask_cache_info {
                 Some(ref mut mask_info) => mask_info,
                 _ => continue,
@@ -1588,33 +1600,31 @@ impl<'a> LayerRectCalculationAndCullingP
 
             let scroll_node = &self.clip_scroll_tree.nodes[&group.scroll_node_id];
             let clip_node = &self.clip_scroll_tree.nodes[&group.clip_node_id];
             let packed_layer = &mut self.frame_builder.packed_layers[group.packed_layer_index.0];
 
             // The world content transform is relative to the containing reference frame,
             // so we translate into the origin of the stacking context itself.
             let transform = scroll_node.world_content_transform
-                                       .pre_translated(stacking_context.reference_frame_offset.x,
-                                                       stacking_context.reference_frame_offset.y,
-                                                       0.0);
+                .pre_translate(stacking_context.reference_frame_offset.to_3d());
             packed_layer.set_transform(transform);
 
             if !stacking_context.can_contribute_to_scene() {
                 return;
             }
 
             // Here we move the viewport rectangle into the coordinate system
             // of the stacking context content.
             let viewport_rect =
                 &clip_node.combined_local_viewport_rect
                      .translate(&clip_node.reference_frame_relative_scroll_offset)
                      .translate(&-scroll_node.reference_frame_relative_scroll_offset)
                      .translate(&-stacking_context.reference_frame_offset)
-                     .translate(&-scroll_node.scrolling.offset);
+                     .translate(&-scroll_node.scroll_offset());
             group.screen_bounding_rect = packed_layer.set_rect(viewport_rect,
                                                                self.screen_rect,
                                                                self.device_pixel_ratio);
         }
     }
 
     fn compute_stacking_context_visibility(&mut self) {
         for context_index in 0..self.frame_builder.stacking_context_store.len() {
@@ -1735,16 +1745,17 @@ impl<'a> LayerRectCalculationAndCullingP
             let prim_index = PrimitiveIndex(prim_index.0 + i);
             if self.frame_builder.prim_store.build_bounding_rect(prim_index,
                                                                  self.screen_rect,
                                                                  &packed_layer.transform,
                                                                  &packed_layer.local_clip_rect,
                                                                  self.device_pixel_ratio) {
                 self.frame_builder.prim_store.prepare_prim_for_render(prim_index,
                                                                       self.resource_cache,
+                                                                      self.gpu_cache,
                                                                       &packed_layer.transform,
                                                                       self.device_pixel_ratio,
                                                                       display_list);
 
                 // If the primitive is visible, consider culling it via clip rect(s).
                 // If it is visible but has clips, create the clip task for it.
                 let prim_bounding_rect =
                     match self.frame_builder.prim_store.cpu_bounding_rects[prim_index.0] {
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -1,14 +1,12 @@
 /* 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::HashSet;
-
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct FreeListItemId(u32);
 
 impl FreeListItemId {
     #[inline]
     pub fn new(value: u32) -> FreeListItemId {
         FreeListItemId(value)
     }
@@ -80,44 +78,23 @@ impl<T: FreeListItem> FreeList<T> {
         }
     }
 
     pub fn get(&self, id: FreeListItemId) -> &T {
         debug_assert_eq!(self.free_iter().find(|&fid| fid==id), None);
         &self.items[id.0 as usize]
     }
 
-    pub fn get_mut(&mut self, id: FreeListItemId) -> &mut T {
-        debug_assert_eq!(self.free_iter().find(|&fid| fid==id), None);
-        &mut self.items[id.0 as usize]
-    }
-
     #[allow(dead_code)]
     pub fn len(&self) -> usize {
         self.alloc_count
     }
 
     pub fn free(&mut self, id: FreeListItemId) -> T {
         self.alloc_count -= 1;
         let FreeListItemId(index) = id;
         let item = &mut self.items[index as usize];
         let data = item.take();
         item.set_next_free_id(self.first_free_index);
         self.first_free_index = Some(id);
         data
     }
-
-    pub fn for_each_item<F>(&mut self, f: F) where F: Fn(&mut T) {
-        //TODO: this could be done much faster. Instead of gathering the free
-        // indices into a set, we could re-order the free list to be ascending.
-        // That is an one-time operation with at most O(nf^2), where
-        //    nf = number of elements in the free list
-        // Then this code would just walk both `items` and the ascending free
-        // list, essentially skipping the free indices for free.
-        let free_ids: HashSet<_> = self.free_iter().collect();
-
-        for (index, mut item) in self.items.iter_mut().enumerate() {
-            if !free_ids.contains(&FreeListItemId(index as u32)) {
-                f(&mut item);
-            }
-        }
-    }
 }
--- a/gfx/webrender/src/geometry.rs
+++ b/gfx/webrender/src/geometry.rs
@@ -36,20 +36,18 @@ pub fn ray_intersects_rect(ray_origin: P
         if inv_direction.z < 0.0 {
             1
         } else {
             0
         },
     ];
 
     let parameters = [
-        Point3D::new(rect.origin.x, rect.origin.y, 0.0),
-        Point3D::new(rect.origin.x + rect.size.width,
-                     rect.origin.y + rect.size.height,
-                     0.0),
+        rect.origin.to_3d(),
+        rect.bottom_right().to_3d(),
     ];
 
     let mut tmin = (parameters[sign[0]].x - ray_origin.x) * inv_direction.x;
     let mut tmax = (parameters[1-sign[0]].x - ray_origin.x) * inv_direction.x;
     let tymin = (parameters[sign[1]].y - ray_origin.y) * inv_direction.y;
     let tymax = (parameters[1-sign[1]].y - ray_origin.y) * inv_direction.y;
     if (tmin > tymax) || (tymin > tmax) {
         return false;
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -10,17 +10,17 @@ use profiler::TextureCacheProfileCounter
 use rayon::ThreadPool;
 use rayon::prelude::*;
 use resource_cache::ResourceClassCache;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::collections::HashSet;
 use std::mem;
 use texture_cache::{TextureCacheItemId, TextureCache};
-use internal_types::FontTemplate;
+use webrender_traits::FontTemplate;
 use webrender_traits::{FontKey, FontRenderMode, ImageData, ImageFormat};
 use webrender_traits::{ImageDescriptor, ColorF, LayoutPoint};
 use webrender_traits::{GlyphKey, GlyphOptions, GlyphInstance, GlyphDimensions};
 
 pub type GlyphCache = ResourceClassCache<GlyphRequest, Option<TextureCacheItemId>>;
 
 pub struct FontContexts {
     // These worker are mostly accessed from their corresponding worker threads.
@@ -243,19 +243,17 @@ impl GlyphRasterizer {
         // differences in rasterizers due to the different coordinates
         // that text runs get associated with by the texture cache allocator.
         rasterized_glyphs.sort_by(|a, b| a.request.cmp(&b.request));
 
         // Update the caches.
         for job in rasterized_glyphs {
             let image_id = job.result.and_then(
                 |glyph| if glyph.width > 0 && glyph.height > 0 {
-                    let image_id = texture_cache.new_item_id();
-                    texture_cache.insert(
-                        image_id,
+                    let image_id = texture_cache.insert(
                         ImageDescriptor {
                             width: glyph.width,
                             height: glyph.height,
                             stride: None,
                             format: ImageFormat::RGBA8,
                             is_opaque: false,
                             offset: 0,
                         },
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -22,25 +22,32 @@
 //! After ```end_frame``` has occurred, callers can
 //! use the ```get_address``` API to get the allocated
 //! address in the GPU cache of a given resource slot
 //! for this frame.
 
 use device::FrameId;
 use profiler::GpuCacheProfileCounters;
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
-use std::mem;
+use std::{mem, u32};
 use webrender_traits::{ColorF, LayerRect};
 
 pub const GPU_CACHE_INITIAL_HEIGHT: u32 = 512;
 const FRAMES_BEFORE_EVICTION: usize = 10;
+const NEW_ROWS_PER_RESIZE: u32 = 512;
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 struct Epoch(u32);
 
+impl Epoch {
+    fn next(&mut self) {
+        *self = Epoch(self.0.wrapping_add(1));
+    }
+}
+
 #[derive(Debug, Copy, Clone)]
 struct CacheLocation {
     block_index: BlockIndex,
     epoch: Epoch,
 }
 
 /// A single texel in RGBAF32 texture - 16 bytes.
 #[derive(Copy, Clone, Debug)]
@@ -188,41 +195,53 @@ pub struct GpuCacheUpdateList {
 
 // Holds the free lists of fixed size blocks. Mostly
 // just serves to work around the borrow checker.
 struct FreeBlockLists {
     free_list_1: Option<BlockIndex>,
     free_list_2: Option<BlockIndex>,
     free_list_4: Option<BlockIndex>,
     free_list_8: Option<BlockIndex>,
+    free_list_16: Option<BlockIndex>,
+    free_list_32: Option<BlockIndex>,
+    free_list_64: Option<BlockIndex>,
+    free_list_128: Option<BlockIndex>,
     free_list_large: Option<BlockIndex>,
 }
 
 impl FreeBlockLists {
     fn new() -> FreeBlockLists {
         FreeBlockLists {
             free_list_1: None,
             free_list_2: None,
             free_list_4: None,
             free_list_8: None,
+            free_list_16: None,
+            free_list_32: None,
+            free_list_64: None,
+            free_list_128: None,
             free_list_large: None,
         }
     }
 
     fn get_actual_block_count_and_free_list(&mut self,
                                             block_count: usize) -> (usize, &mut Option<BlockIndex>) {
         // Find the appropriate free list to use
         // based on the block size.
         match block_count {
             0 => panic!("Can't allocate zero sized blocks!"),
             1 => (1, &mut self.free_list_1),
             2 => (2, &mut self.free_list_2),
             3...4 => (4, &mut self.free_list_4),
             5...8 => (8, &mut self.free_list_8),
-            9...MAX_VERTEX_TEXTURE_WIDTH => (MAX_VERTEX_TEXTURE_WIDTH, &mut self.free_list_large),
+            9...16 => (16, &mut self.free_list_16),
+            17...32 => (32, &mut self.free_list_32),
+            33...64 => (64, &mut self.free_list_64),
+            65...128 => (128, &mut self.free_list_128),
+            129...MAX_VERTEX_TEXTURE_WIDTH => (MAX_VERTEX_TEXTURE_WIDTH, &mut self.free_list_large),
             _ => panic!("Can't allocate > MAX_VERTEX_TEXTURE_WIDTH per resource!"),
         }
     }
 }
 
 // CPU-side representation of the GPU resource cache texture.
 struct Texture {
     // Current texture height
@@ -270,20 +289,18 @@ impl Texture {
                  block_count: usize,
                  frame_id: FrameId) -> CacheLocation {
         // Find the appropriate free list to use based on the block size.
         let (alloc_size, free_list) = self.free_lists
                                           .get_actual_block_count_and_free_list(block_count);
 
         // See if we need a new row (if free-list has nothing available)
         if free_list.is_none() {
-            // TODO(gw): Handle the case where we need to resize
-            //           the cache texture itself!
             if self.rows.len() as u32 == self.height {
-                panic!("need to re-alloc texture!!");
+                self.height += NEW_ROWS_PER_RESIZE;
             }
 
             // Create a new row.
             let items_per_row = MAX_VERTEX_TEXTURE_WIDTH / alloc_size;
             let row_index = self.rows.len();
             self.rows.push(Row::new(alloc_size));
 
             // Create a ```Block``` for each possible allocation address
@@ -353,17 +370,17 @@ impl Texture {
                     // Get the row metadata from the address.
                     let row = &mut self.rows[block.address.v as usize];
 
                     // Use the row metadata to determine which free-list
                     // this block belongs to.
                     let (_, free_list) = self.free_lists
                                              .get_actual_block_count_and_free_list(row.block_count_per_item);
 
-                    block.epoch = Epoch(block.epoch.0 + 1);
+                    block.epoch.next();
                     block.next = *free_list;
                     *free_list = Some(index);
 
                     self.allocated_block_count -= row.block_count_per_item;
                 };
 
                 (next_block, should_unlink)
             };
@@ -439,36 +456,64 @@ impl GpuCache {
 
     /// Begin a new frame.
     pub fn begin_frame(&mut self) {
         debug_assert!(self.texture.pending_blocks.is_empty());
         self.frame_id = self.frame_id + 1;
         self.texture.evict_old_blocks(self.frame_id);
     }
 
+    // Invalidate a (possibly) existing block in the cache.
+    // This means the next call to request() for this location
+    // will rebuild the data and upload it to the GPU.
+    pub fn invalidate(&mut self, handle: &GpuCacheHandle) {
+        if let Some(ref location) = handle.location {
+            let block = &mut self.texture.blocks[location.block_index.0];
+            block.epoch.next();
+        }
+    }
+
     // Request a resource be added to the cache. If the resource
     /// is already in the cache, `None` will be returned.
     pub fn request<'a>(&'a mut self, handle: &'a mut GpuCacheHandle) -> Option<GpuDataRequest<'a>> {
         // Check if the allocation for this handle is still valid.
         if let Some(ref location) = handle.location {
             let block = &mut self.texture.blocks[location.block_index.0];
             if block.epoch == location.epoch {
                 // Mark last access time to avoid evicting this block.
                 block.last_access_time = self.frame_id;
                 return None
             }
         }
+
         Some(GpuDataRequest {
             handle: handle,
             frame_id: self.frame_id,
             start_index: self.texture.pending_blocks.len(),
             texture: &mut self.texture,
         })
     }
 
+    // Push an array of data blocks to be uploaded to the GPU
+    // unconditionally for this frame. The cache handle will
+    // assert if the caller tries to retrieve the address
+    // of this handle on a subsequent frame. This is typically
+    // used for uploading data that changes every frame, and
+    // therefore makes no sense to try and cache.
+    pub fn push_per_frame_blocks(&mut self, blocks: &[GpuBlockData]) -> GpuCacheHandle {
+        let start_index = self.texture.pending_blocks.len();
+        self.texture.pending_blocks.extend_from_slice(blocks);
+        let location = self.texture.push_data(start_index,
+                                              blocks.len(),
+                                              self.frame_id);
+        GpuCacheHandle {
+            location: Some(location),
+        }
+    }
+
     /// End the frame. Return the list of updates to apply to the
     /// device specific cache texture.
     pub fn end_frame(&mut self,
                      profile_counters: &mut GpuCacheProfileCounters) -> GpuCacheUpdateList {
         profile_counters.allocated_rows.set(self.texture.rows.len());
         profile_counters.allocated_blocks.set(self.texture.allocated_block_count);
 
         GpuCacheUpdateList {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -43,44 +43,33 @@ pub enum SourceTexture {
     External(ExternalImageData),
     #[cfg_attr(not(feature = "webgl"), allow(dead_code))]
     /// This is actually a gl::GLuint, with the shared texture id between the
     /// main context and the WebGL context.
     WebGL(u32),
 }
 
 const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
-const COLOR_FLOAT_TO_FIXED_WIDE: f32 = 65535.0;
 pub const ANGLE_FLOAT_TO_FIXED: f32 = 65535.0;
 
 pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
 pub const ORTHO_FAR_PLANE: f32 = 1000000.0;
 
-#[derive(Clone)]
-pub enum FontTemplate {
-    Raw(Arc<Vec<u8>>, u32),
-    Native(NativeFontHandle),
-}
-
 #[derive(Debug, PartialEq, Eq)]
 pub enum TextureSampler {
     Color0,
     Color1,
     Color2,
     CacheA8,
     CacheRGBA8,
-    Data16,
     Data32,
     ResourceCache,
     Layers,
     RenderTasks,
-    Geometry,
     ResourceRects,
-    Gradients,
-    SplitGeometry,
     Dither,
 }
 
 impl TextureSampler {
     pub fn color(n: usize) -> TextureSampler {
         match n {
             0 => TextureSampler::Color0,
             1 => TextureSampler::Color1,
@@ -138,17 +127,16 @@ pub enum ClipAttribute {
     // instance frequency
     RenderTaskIndex,
     LayerIndex,
     DataIndex,
     SegmentIndex,
 }
 
 // A packed RGBA8 color ordered for vertex data or similar.
-// Use PackedTexel instead if intending to upload to a texture.
 
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedColor {
     pub r: u8,
     pub g: u8,
     pub b: u8,
     pub a: u8,
@@ -160,47 +148,16 @@ impl PackedColor {
             r: (0.5 + color.r * COLOR_FLOAT_TO_FIXED).floor() as u8,
             g: (0.5 + color.g * COLOR_FLOAT_TO_FIXED).floor() as u8,
             b: (0.5 + color.b * COLOR_FLOAT_TO_FIXED).floor() as u8,
             a: (0.5 + color.a * COLOR_FLOAT_TO_FIXED).floor() as u8,
         }
     }
 }
 
-// RGBA8 textures currently pack texels in BGRA format for upload.
-// PackedTexel abstracts away this difference from PackedColor.
-
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub struct PackedTexel {
-    pub b: u8,
-    pub g: u8,
-    pub r: u8,
-    pub a: u8,
-}
-
-impl PackedTexel {
-    pub fn high_bytes(color: &ColorF) -> PackedTexel {
-        Self::extract_bytes(color, 8)
-    }
-
-    pub fn low_bytes(color: &ColorF) -> PackedTexel {
-        Self::extract_bytes(color, 0)
-    }
-
-    fn extract_bytes(color: &ColorF, shift_by: i32) -> PackedTexel {
-        PackedTexel {
-            b: ((0.5 + color.b * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
-            g: ((0.5 + color.g * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
-            r: ((0.5 + color.r * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
-            a: ((0.5 + color.a * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
-        }
-    }
-}
-
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
 }
 
 #[derive(Debug)]
 #[repr(C)]
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -3,32 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use border::{BorderCornerClipData, BorderCornerDashClipData, BorderCornerDotClipData};
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_store::GpuStoreAddress;
-use internal_types::{SourceTexture, PackedTexel};
+use internal_types::SourceTexture;
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo};
-use renderer::{VertexDataStore, GradientDataStore, SplitGeometryStore, MAX_VERTEX_TEXTURE_WIDTH};
+use renderer::{VertexDataStore, MAX_VERTEX_TEXTURE_WIDTH};
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{CacheItem, ImageProperties, ResourceCache};
 use std::mem;
 use std::usize;
 use util::{TransformedRect, recycle_vec};
 use webrender_traits::{BuiltDisplayList, ColorF, ImageKey, ImageRendering, YuvColorSpace};
 use webrender_traits::{YuvFormat, ClipRegion, ComplexClipRegion, ItemRange, GlyphKey};
 use webrender_traits::{FontKey, FontRenderMode, WebGLContextId};
 use webrender_traits::{device_length, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{DeviceRect, DevicePoint, DeviceSize};
 use webrender_traits::{LayerRect, LayerSize, LayerPoint, LayoutPoint};
 use webrender_traits::{LayerToWorldTransform, GlyphInstance, GlyphOptions};
-use webrender_traits::{ExtendMode, GradientStop, AuxIter, TileOffset};
+use webrender_traits::{ExtendMode, GradientStop, TileOffset};
 
 pub const CLIP_DATA_GPU_SIZE: usize = 5;
 pub const MASK_DATA_GPU_SIZE: usize = 1;
 
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
 /// may grow. Storing them as texel coords and normalizing
 /// the UVs in the vertex shader means nothing needs to be
@@ -84,32 +84,16 @@ pub enum PrimitiveKind {
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
 }
 
-/// Geometry description for simple rectangular primitives, uploaded to the GPU.
-#[derive(Debug, Clone)]
-pub struct PrimitiveGeometry {
-    pub local_rect: LayerRect,
-    pub local_clip_rect: LayerRect,
-}
-
-impl Default for PrimitiveGeometry {
-    fn default() -> PrimitiveGeometry {
-        PrimitiveGeometry {
-            local_rect: unsafe { mem::uninitialized() },
-            local_clip_rect: unsafe { mem::uninitialized() },
-        }
-    }
-}
-
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
@@ -136,39 +120,31 @@ pub struct PrimitiveMetadata {
     // use this to draw a portion of the box shadow to
     // a render target to reduce the number of pixels
     // that the box-shadow shader needs to run on. For
     // text-shadow, this creates a render task chain
     // that implements a 2-pass separable blur on a
     // text run.
     pub render_task: Option<RenderTask>,
     pub clip_task: Option<RenderTask>,
+
+    // TODO(gw): In the future, we should just pull these
+    //           directly from the DL item, instead of
+    //           storing them here.
+    pub local_rect: LayerRect,
+    pub local_clip_rect: LayerRect,
 }
 
 impl PrimitiveMetadata {
     pub fn needs_clipping(&self) -> bool {
         self.clip_task.is_some()
     }
 }
 
 #[derive(Debug, Clone)]
-pub struct SplitGeometry {
-    pub data: [f32; 12],
-}
-
-impl Default for SplitGeometry {
-    fn default() -> SplitGeometry {
-        SplitGeometry {
-            data: unsafe { mem::uninitialized() },
-        }
-    }
-}
-
-
-#[derive(Debug, Clone)]
 #[repr(C)]
 pub struct RectanglePrimitive {
     pub color: ColorF,
 }
 
 impl ToGpuBlocks for RectanglePrimitive {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.push(self.color.into());
@@ -265,55 +241,46 @@ impl ToGpuBlocks for BoxShadowPrimitiveC
                       self.blur_radius,
                       self.inverted].into());
         for &rect in &self.rects {
             request.push(rect.into());
         }
     }
 }
 
-#[derive(Debug, Clone)]
-#[repr(C)]
-pub struct GradientStopGpu {
-    color: ColorF,
-    offset: f32,
-    padding: [f32; 3],
-}
-
 #[derive(Debug)]
 pub struct GradientPrimitiveCpu {
     pub stops_range: ItemRange<GradientStop>,
     pub stops_count: usize,
     pub extend_mode: ExtendMode,
     pub reverse_stops: bool,
-    pub cache_dirty: bool,
-    pub gpu_data_address: GpuStoreAddress,
-    pub gpu_data_count: i32,
     pub gpu_blocks: [GpuBlockData; 3],
 }
 
-impl ToGpuBlocks for GradientPrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
+impl GradientPrimitiveCpu {
+    fn build_gpu_blocks_for_aligned(&self,
+                                    display_list: &BuiltDisplayList,
+                                    mut request: GpuDataRequest) {
         request.extend_from_slice(&self.gpu_blocks);
-    }
-}
+        let src_stops = display_list.get(self.stops_range);
 
-#[derive(Debug)]
-pub struct RadialGradientPrimitiveCpu {
-    pub stops_range: ItemRange<GradientStop>,
-    pub extend_mode: ExtendMode,
-    pub cache_dirty: bool,
-    pub gpu_data_address: GpuStoreAddress,
-    pub gpu_data_count: i32,
-    pub gpu_blocks: [GpuBlockData; 3],
-}
+        for src in src_stops {
+            request.push(src.color.premultiplied().into());
+            request.push([src.offset, 0.0, 0.0, 0.0].into());
+        }
+    }
 
-impl ToGpuBlocks for RadialGradientPrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
+    fn build_gpu_blocks_for_angle_radial(&self,
+                                         display_list: &BuiltDisplayList,
+                                         mut request: GpuDataRequest) {
         request.extend_from_slice(&self.gpu_blocks);
+
+        let gradient_builder = GradientGpuBlockBuilder::new(self.stops_range,
+                                                            display_list);
+        gradient_builder.build(self.reverse_stops, &mut request);
     }
 }
 
 // The gradient entry index for the first color stop
 pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
 // The gradient entry index for the last color stop
 pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
 
@@ -326,188 +293,207 @@ pub const GRADIENT_DATA_TABLE_SIZE: usiz
 
 // The number of entries in a gradient data: GRADIENT_DATA_TABLE_SIZE + first stop entry + last stop entry
 pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2;
 
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 // An entry in a gradient data table representing a segment of the gradient color space.
 pub struct GradientDataEntry {
-    pub start_color: PackedTexel,
-    pub end_color: PackedTexel,
+    pub start_color: ColorF,
+    pub end_color: ColorF,
 }
 
-#[repr(C)]
-// A table of gradient entries, with two colors per entry, that specify the start and end color
-// within the segment of the gradient space represented by that entry. To lookup a gradient result,
-// first the entry index is calculated to determine which two colors to interpolate between, then
-// the offset within that entry bucket is used to interpolate between the two colors in that entry.
-// This layout preserves hard stops, as the end color for a given entry can differ from the start
-// color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
-// format for texture upload. This table requires the gradient color stops to be normalized to the
-// range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
-// while the entries in between hold the interpolated color stop values for the range [0, 1].
-pub struct GradientData {
-    pub colors_high: [GradientDataEntry; GRADIENT_DATA_SIZE],
-    pub colors_low: [GradientDataEntry; GRADIENT_DATA_SIZE],
+struct GradientGpuBlockBuilder<'a> {
+    stops_range: ItemRange<GradientStop>,
+    display_list: &'a BuiltDisplayList,
 }
 
-impl Default for GradientData {
-    fn default() -> GradientData {
-        GradientData {
-            colors_high: unsafe { mem::uninitialized() },
-            colors_low: unsafe { mem::uninitialized() }
+impl<'a> GradientGpuBlockBuilder<'a> {
+    fn new(stops_range: ItemRange<GradientStop>,
+           display_list: &'a BuiltDisplayList) -> GradientGpuBlockBuilder<'a> {
+        GradientGpuBlockBuilder {
+            stops_range: stops_range,
+            display_list: display_list,
         }
     }
-}
 
-impl Clone for GradientData {
-    fn clone(&self) -> GradientData {
-        GradientData {
-            colors_high: self.colors_high,
-            colors_low: self.colors_low,
-        }
-    }
-}
-
-impl GradientData {
     /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating
     /// from start_color to end_color.
-    fn fill_colors(&mut self, start_idx: usize, end_idx: usize, start_color: &ColorF, end_color: &ColorF) {
+    fn fill_colors(&self,
+                   start_idx: usize,
+                   end_idx: usize,
+                   start_color: &ColorF,
+                   end_color: &ColorF,
+                   entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE]) {
         // Calculate the color difference for individual steps in the ramp.
         let inv_steps = 1.0 / (end_idx - start_idx) as f32;
         let step_r = (end_color.r - start_color.r) * inv_steps;
         let step_g = (end_color.g - start_color.g) * inv_steps;
         let step_b = (end_color.b - start_color.b) * inv_steps;
         let step_a = (end_color.a - start_color.a) * inv_steps;
 
         let mut cur_color = *start_color;
-        let mut cur_color_high = PackedTexel::high_bytes(&cur_color);
-        let mut cur_color_low = PackedTexel::low_bytes(&cur_color);
 
         // Walk the ramp writing start and end colors for each entry.
         for index in start_idx..end_idx {
-            let high_byte_entry = &mut self.colors_high[index];
-            let low_byte_entry = &mut self.colors_low[index];
-
-            high_byte_entry.start_color = cur_color_high;
-            low_byte_entry.start_color = cur_color_low;
+            let entry = &mut entries[index];
+            entry.start_color = cur_color;
             cur_color.r += step_r;
             cur_color.g += step_g;
             cur_color.b += step_b;
             cur_color.a += step_a;
-            cur_color_high = PackedTexel::high_bytes(&cur_color);
-            cur_color_low = PackedTexel::low_bytes(&cur_color);
-            high_byte_entry.end_color = cur_color_high;
-            low_byte_entry.end_color = cur_color_low;
+            entry.end_color = cur_color;
         }
     }
 
     /// Compute an index into the gradient entry table based on a gradient stop offset. This
     /// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END].
     #[inline]
     fn get_index(offset: f32) -> usize {
         (offset.max(0.0).min(1.0)
             * GRADIENT_DATA_TABLE_SIZE as f32
             + GRADIENT_DATA_TABLE_BEGIN as f32).round() as usize
     }
 
     // Build the gradient data from the supplied stops, reversing them if necessary.
-    fn build(&mut self, src_stops: AuxIter<GradientStop>, reverse_stops: bool) {
+    fn build(&self, reverse_stops: bool, request: &mut GpuDataRequest) {
+        let src_stops = self.display_list.get(self.stops_range);
 
         // Preconditions (should be ensured by DisplayListBuilder):
         // * we have at least two stops
         // * first stop has offset 0.0
         // * last stop has offset 1.0
 
         let mut src_stops = src_stops.into_iter();
         let first = src_stops.next().unwrap();
         let mut cur_color = first.color.premultiplied();
         debug_assert_eq!(first.offset, 0.0);
 
+        // A table of gradient entries, with two colors per entry, that specify the start and end color
+        // within the segment of the gradient space represented by that entry. To lookup a gradient result,
+        // first the entry index is calculated to determine which two colors to interpolate between, then
+        // the offset within that entry bucket is used to interpolate between the two colors in that entry.
+        // This layout preserves hard stops, as the end color for a given entry can differ from the start
+        // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
+        // format for texture upload. This table requires the gradient color stops to be normalized to the
+        // range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
+        // while the entries in between hold the interpolated color stop values for the range [0, 1].
+        let mut entries: [GradientDataEntry; GRADIENT_DATA_SIZE] = unsafe { mem::uninitialized() };
+
         if reverse_stops {
             // Fill in the first entry (for reversed stops) with the first color stop
-            self.fill_colors(GRADIENT_DATA_LAST_STOP, GRADIENT_DATA_LAST_STOP + 1, &cur_color, &cur_color);
+            self.fill_colors(GRADIENT_DATA_LAST_STOP, GRADIENT_DATA_LAST_STOP + 1, &cur_color, &cur_color, &mut entries);
 
             // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
             // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The
             // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
             let mut cur_idx = GRADIENT_DATA_TABLE_END;
             for next in src_stops {
                 let next_color = next.color.premultiplied();
                 let next_idx = Self::get_index(1.0 - next.offset);
 
                 if next_idx < cur_idx {
                     self.fill_colors(next_idx, cur_idx,
-                                     &next_color, &cur_color);
+                                     &next_color, &cur_color, &mut entries);
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
             debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_BEGIN);
 
             // Fill in the last entry (for reversed stops) with the last color stop
-            self.fill_colors(GRADIENT_DATA_FIRST_STOP, GRADIENT_DATA_FIRST_STOP + 1, &cur_color, &cur_color);
+            self.fill_colors(GRADIENT_DATA_FIRST_STOP, GRADIENT_DATA_FIRST_STOP + 1, &cur_color, &cur_color, &mut entries);
         } else {
             // Fill in the first entry with the first color stop
-            self.fill_colors(GRADIENT_DATA_FIRST_STOP, GRADIENT_DATA_FIRST_STOP + 1, &cur_color, &cur_color);
+            self.fill_colors(GRADIENT_DATA_FIRST_STOP, GRADIENT_DATA_FIRST_STOP + 1, &cur_color, &cur_color, &mut entries);
 
             // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
             // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The
             // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
             let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
             for next in src_stops {
                 let next_color = next.color.premultiplied();
                 let next_idx = Self::get_index(next.offset);
 
                 if next_idx > cur_idx {
                     self.fill_colors(cur_idx, next_idx,
-                                     &cur_color, &next_color);
+                                     &cur_color, &next_color, &mut entries);
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
             debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_END);
 
             // Fill in the last entry with the last color stop
-            self.fill_colors(GRADIENT_DATA_LAST_STOP, GRADIENT_DATA_LAST_STOP + 1, &cur_color, &cur_color);
+            self.fill_colors(GRADIENT_DATA_LAST_STOP, GRADIENT_DATA_LAST_STOP + 1, &cur_color, &cur_color, &mut entries);
+        }
+
+        for entry in entries.iter() {
+            request.push(entry.start_color.into());
+            request.push(entry.end_color.into());
         }
     }
 }
 
-#[derive(Debug, Clone)]
-#[repr(C)]
-struct InstanceRect {
-    rect: LayerRect,
+#[derive(Debug)]
+pub struct RadialGradientPrimitiveCpu {
+    pub stops_range: ItemRange<GradientStop>,
+    pub extend_mode: ExtendMode,
+    pub gpu_data_address: GpuStoreAddress,
+    pub gpu_data_count: i32,
+    pub gpu_blocks: [GpuBlockData; 3],
+}
+
+impl RadialGradientPrimitiveCpu {
+    fn build_gpu_blocks_for_angle_radial(&self,
+                                         display_list: &BuiltDisplayList,
+                                         mut request: GpuDataRequest) {
+        request.extend_from_slice(&self.gpu_blocks);
+
+        let gradient_builder = GradientGpuBlockBuilder::new(self.stops_range,
+                                                            display_list);
+        gradient_builder.build(false, &mut request);
+    }
 }
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font_key: FontKey,
     pub logical_font_size: Au,
     pub blur_radius: f32,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
-    pub cache_dirty: bool,
     // TODO(gw): Maybe make this an Arc for sharing with resource cache
     pub glyph_instances: Vec<GlyphInstance>,
     pub color_texture_id: SourceTexture,
     pub color: ColorF,
     pub render_mode: FontRenderMode,
     pub resource_address: GpuStoreAddress,
     pub glyph_options: Option<GlyphOptions>,
-    pub gpu_data_address: GpuStoreAddress,
-    pub gpu_data_count: i32,
 }
 
 impl ToGpuBlocks for TextRunPrimitiveCpu {
     fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
         request.push(self.color.into());
+
+        // 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].into());
+        }
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct GlyphPrimitive {
     offset: LayerPoint,
     padding: LayerPoint,
@@ -661,23 +647,17 @@ pub struct PrimitiveStore {
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
     pub cpu_box_shadows: Vec<BoxShadowPrimitiveCpu>,
 
     /// Gets uploaded directly to GPU via vertex texture.
-    pub gpu_geometry: VertexDataStore<PrimitiveGeometry>,
-    pub gpu_data16: VertexDataStore<GpuBlock16>,
     pub gpu_data32: VertexDataStore<GpuBlock32>,
-    pub gpu_gradient_data: GradientDataStore,
-
-    /// Geometry generated by plane splitting.
-    pub gpu_split_geometry: SplitGeometryStore,
 
     /// Resolved resource rects.
     pub gpu_resource_rects: VertexDataStore<TexelRect>,
 
     /// General
     prims_to_resolve: Vec<PrimitiveIndex>,
 }
 
@@ -690,21 +670,17 @@ impl PrimitiveStore {
             cpu_text_runs: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
             cpu_box_shadows: Vec::new(),
             prims_to_resolve: Vec::new(),
-            gpu_geometry: VertexDataStore::new(),
-            gpu_data16: VertexDataStore::new(),
             gpu_data32: VertexDataStore::new(),
-            gpu_gradient_data: GradientDataStore::new(),
-            gpu_split_geometry: SplitGeometryStore::new(),
             gpu_resource_rects: VertexDataStore::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_rectangles: recycle_vec(self.cpu_rectangles),
@@ -712,76 +688,73 @@ impl PrimitiveStore {
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
             cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
             prims_to_resolve: recycle_vec(self.prims_to_resolve),
-            gpu_geometry: self.gpu_geometry.recycle(),
-            gpu_data16: self.gpu_data16.recycle(),
             gpu_data32: self.gpu_data32.recycle(),
-            gpu_gradient_data: self.gpu_gradient_data.recycle(),
-            gpu_split_geometry: self.gpu_split_geometry.recycle(),
             gpu_resource_rects: self.gpu_resource_rects.recycle(),
         }
     }
 
     pub fn populate_clip_data(data: &mut [GpuBlock32], clip: ClipData) {
         data[0] = GpuBlock32::from(clip.rect);
         data[1] = GpuBlock32::from(clip.top_left);
         data[2] = GpuBlock32::from(clip.top_right);
         data[3] = GpuBlock32::from(clip.bottom_left);
         data[4] = GpuBlock32::from(clip.bottom_right);
     }
 
     pub fn add_primitive(&mut self,
-                         geometry: PrimitiveGeometry,
+                         local_rect: &LayerRect,
+                         local_clip_rect: &LayerRect,
                          clips: Vec<ClipSource>,
                          clip_info: Option<MaskCacheInfo>,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.cpu_metadata.len();
         self.cpu_bounding_rects.push(None);
-        self.gpu_geometry.push(geometry);
 
         let metadata = match container {
             PrimitiveContainer::Rectangle(rect) => {
                 let is_opaque = rect.color.a == 1.0;
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: is_opaque,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Rectangle,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_rectangles.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_rectangles.push(rect);
 
                 metadata
             }
             PrimitiveContainer::TextRun(mut text_cpu) => {
-                let gpu_glyphs_address = self.gpu_data16.alloc(text_cpu.glyph_count);
                 text_cpu.resource_address = self.gpu_resource_rects.alloc(text_cpu.glyph_count);
-                text_cpu.gpu_data_address = gpu_glyphs_address;
-                text_cpu.gpu_data_count = text_cpu.glyph_count as i32;
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
             PrimitiveContainer::Image(mut image_cpu) => {
                 image_cpu.resource_address = self.gpu_resource_rects.alloc(1);
 
@@ -789,16 +762,18 @@ impl PrimitiveStore {
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::YuvImage(mut image_cpu) => {
                 image_cpu.yuv_resource_address = self.gpu_resource_rects.alloc(3);
 
@@ -806,94 +781,89 @@ impl PrimitiveStore {
                     is_opaque: true,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::YuvImage,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_yuv_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::Border(border_cpu) => {
                 let metadata = PrimitiveMetadata {
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_borders.push(border_cpu);
                 metadata
             }
-            PrimitiveContainer::AlignedGradient(mut gradient_cpu) => {
-                let gpu_stops_address = self.gpu_data32.alloc(gradient_cpu.stops_count);
-
-                gradient_cpu.gpu_data_address = gpu_stops_address;
-                gradient_cpu.gpu_data_count = gradient_cpu.stops_count as i32;
-
+            PrimitiveContainer::AlignedGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AlignedGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
-            PrimitiveContainer::AngleGradient(mut gradient_cpu) => {
-                let gpu_gradient_address = self.gpu_gradient_data.alloc(1);
-
-                gradient_cpu.gpu_data_address = gpu_gradient_address;
-                gradient_cpu.gpu_data_count = 1;
-
+            PrimitiveContainer::AngleGradient(gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
-            PrimitiveContainer::RadialGradient(mut radial_gradient_cpu) => {
-                let gpu_gradient_address = self.gpu_gradient_data.alloc(1);
-
-                radial_gradient_cpu.gpu_data_address = gpu_gradient_address;
-                radial_gradient_cpu.gpu_data_count = 1;
-
+            PrimitiveContainer::RadialGradient(radial_gradient_cpu) => {
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::RadialGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: None,
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_radial_gradients.push(radial_gradient_cpu);
                 metadata
             }
             PrimitiveContainer::BoxShadow(box_shadow) => {
                 let cache_key = PrimitiveCacheKey::BoxShadow(BoxShadowPrimitiveCacheKey {
                     blur_radius: Au::from_f32_px(box_shadow.blur_radius),
@@ -924,16 +894,18 @@ impl PrimitiveStore {
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::BoxShadow,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_box_shadows.len()),
                     gpu_location: GpuCacheHandle::new(),
                     render_task: Some(render_task),
                     clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_box_shadows.push(box_shadow);
                 metadata
             }
         };
 
         self.cpu_metadata.push(metadata);
@@ -1097,118 +1069,58 @@ impl PrimitiveStore {
                     }
                 }
             }
         }
 
         deferred_resolves
     }
 
-    pub fn set_clip_source(&mut self, index: PrimitiveIndex, source: Option<ClipSource>) {
-        let metadata = &mut self.cpu_metadata[index.0];
-        metadata.clips = match source {
-            Some(source) => {
-                let (rect, is_complex) = match source {
-                    ClipSource::Complex(rect, radius, _) => (rect, radius > 0.0),
-                    ClipSource::Region(ref region, _) => (region.main, region.is_complex()),
-                    ClipSource::BorderCorner{..} => panic!("Not supported!"),
-                };
-                self.gpu_geometry.get_mut(GpuStoreAddress(index.0 as i32))
-                    .local_clip_rect = rect;
-                if is_complex {
-                    metadata.clip_cache_info = None; //CLIP TODO: re-use the existing GPU allocation
-                }
-                vec![source]
-            }
-            None => {
-                vec![]
-            }
-        }
-    }
-
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
         &self.cpu_metadata[index.0]
     }
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
     pub fn build_bounding_rect(&mut self,
                                prim_index: PrimitiveIndex,
                                screen_rect: &DeviceIntRect,
                                layer_transform: &LayerToWorldTransform,
                                layer_combined_local_clip_rect: &LayerRect,
                                device_pixel_ratio: f32) -> bool {
-        let geom = &self.gpu_geometry.get(GpuStoreAddress(prim_index.0 as i32));
+        let metadata = &self.cpu_metadata[prim_index.0];
 
-        let bounding_rect = geom.local_rect
-                                .intersection(&geom.local_clip_rect)
-                                .and_then(|rect| rect.intersection(layer_combined_local_clip_rect))
-                                .and_then(|ref local_rect| {
+        let bounding_rect = metadata.local_rect
+                                    .intersection(&metadata.local_clip_rect)
+                                    .and_then(|rect| rect.intersection(layer_combined_local_clip_rect))
+                                    .and_then(|ref local_rect| {
             let xf_rect = TransformedRect::new(local_rect,
                                                layer_transform,
                                                device_pixel_ratio);
             xf_rect.bounding_rect.intersection(screen_rect)
         });
 
         self.cpu_bounding_rects[prim_index.0] = bounding_rect;
         bounding_rect.is_some()
     }
 
     /// Returns true if the bounding box needs to be updated.
     pub fn prepare_prim_for_render(&mut self,
                                    prim_index: PrimitiveIndex,
                                    resource_cache: &mut ResourceCache,
+                                   gpu_cache: &mut GpuCache,
                                    layer_transform: &LayerToWorldTransform,
                                    device_pixel_ratio: f32,
                                    display_list: &BuiltDisplayList) {
 
         let metadata = &mut self.cpu_metadata[prim_index.0];
         let mut prim_needs_resolve = false;
 
-        // Mark this GPU resource as required for this frame.
-        if let Some(request) = resource_cache.gpu_cache.request(&mut metadata.gpu_location) {
-            match metadata.prim_kind {
-                PrimitiveKind::Rectangle => {
-                    let rect = &self.cpu_rectangles[metadata.cpu_prim_index.0];
-                    rect.write_gpu_blocks(request);
-                }
-                PrimitiveKind::Border => {
-                    let border = &self.cpu_borders[metadata.cpu_prim_index.0];
-                    border.write_gpu_blocks(request);
-                }
-                PrimitiveKind::BoxShadow => {
-                    let box_shadow = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
-                    box_shadow.write_gpu_blocks(request);
-                }
-                PrimitiveKind::Image => {
-                    let image = &self.cpu_images[metadata.cpu_prim_index.0];
-                    image.write_gpu_blocks(request);
-                }
-                PrimitiveKind::YuvImage => {
-                    let yuv_image = &self.cpu_yuv_images[metadata.cpu_prim_index.0];
-                    yuv_image.write_gpu_blocks(request);
-                }
-                PrimitiveKind::AlignedGradient |
-                PrimitiveKind::AngleGradient => {
-                    let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
-                    gradient.write_gpu_blocks(request);
-                }
-                PrimitiveKind::RadialGradient => {
-                    let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
-                    gradient.write_gpu_blocks(request);
-                }
-                PrimitiveKind::TextRun => {
-                    let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                    text.write_gpu_blocks(request);
-                }
-            }
-        }
-
         if let Some(ref mut clip_info) = metadata.clip_cache_info {
             clip_info.update(&metadata.clips,
                              layer_transform,
                              &mut self.gpu_data32,
                              device_pixel_ratio,
                              display_list);
             for clip in &metadata.clips {
                 if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
@@ -1238,33 +1150,24 @@ impl PrimitiveStore {
             }
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
 
                 let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio);
                 let src_glyphs = display_list.get(text.glyph_range);
                 prim_needs_resolve = true;
 
-                if text.cache_dirty {
-                    text.cache_dirty = false;
-
-                    debug_assert!(text.gpu_data_count == src_glyphs.len() as i32);
-                    debug_assert!(text.glyph_instances.is_empty());
-
-                    let dest_glyphs = self.gpu_data16.get_slice_mut(text.gpu_data_address,
-                                                                    src_glyphs.len());
-
+                // Cache the glyph positions, if not in the cache already.
+                if text.glyph_instances.is_empty() {
                     let mut glyph_key = GlyphKey::new(text.font_key,
                                                       font_size_dp,
                                                       text.color,
                                                       0,
                                                       LayoutPoint::new(0.0, 0.0),
                                                       text.render_mode);
-                    let mut actual_glyph_count = 0;
-
                     for src in src_glyphs {
                         glyph_key.index = src.index;
                         glyph_key.subpixel_point.set_offset(src.point, text.render_mode);
 
                         let dimensions = match resource_cache.get_glyph_dimensions(&glyph_key) {
                             None => continue,
                             Some(dimensions) => dimensions,
                         };
@@ -1272,52 +1175,41 @@ impl PrimitiveStore {
                         // TODO(gw): Check for this and ensure platforms return None in this case!!!
                         debug_assert!(dimensions.width > 0 && dimensions.height > 0);
 
                         let x = src.point.x + dimensions.left as f32 / device_pixel_ratio;
                         let y = src.point.y - dimensions.top as f32 / device_pixel_ratio;
 
                         let glyph_pos = LayerPoint::new(x, y);
 
-                        dest_glyphs[actual_glyph_count] = GpuBlock16::from(GlyphPrimitive {
-                            padding: LayerPoint::zero(),
-                            offset: glyph_pos,
-                        });
-
                         text.glyph_instances.push(GlyphInstance {
                             index: src.index,
                             point: glyph_pos,
                         });
-
-                        actual_glyph_count += 1;
                     }
+                }
 
-                    let render_task = if text.blur_radius == 0.0 {
-                        None
-                    } else {
-                        // This is a text-shadow element. Create a render task that will
-                        // render the text run to a target, and then apply a gaussian
-                        // blur to that text run in order to build the actual primitive
-                        // which will be blitted to the framebuffer.
-                        let geom = &self.gpu_geometry.get(GpuStoreAddress(prim_index.0 as i32));
-                        let cache_width = (geom.local_rect.size.width * device_pixel_ratio).ceil() as i32;
-                        let cache_height = (geom.local_rect.size.height * device_pixel_ratio).ceil() as i32;
-                        let cache_size = DeviceIntSize::new(cache_width, cache_height);
-                        let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
-                        let blur_radius = device_length(text.blur_radius,
-                                                        device_pixel_ratio);
-                        Some(RenderTask::new_blur(cache_key,
-                                                  cache_size,
-                                                  blur_radius,
-                                                  prim_index))
-                    };
-
-                    text.gpu_data_count = actual_glyph_count as i32;
-                    metadata.render_task = render_task;
-                }
+                metadata.render_task = if text.blur_radius == 0.0 {
+                    None
+                } else {
+                    // This is a text-shadow element. Create a render task that will
+                    // render the text run to a target, and then apply a gaussian
+                    // blur to that text run in order to build the actual primitive
+                    // which will be blitted to the framebuffer.
+                    let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
+                    let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
+                    let cache_size = DeviceIntSize::new(cache_width, cache_height);
+                    let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
+                    let blur_radius = device_length(text.blur_radius,
+                                                    device_pixel_ratio);
+                    Some(RenderTask::new_blur(cache_key,
+                                              cache_size,
+                                              blur_radius,
+                                              prim_index))
+                };
 
                 resource_cache.request_glyphs(text.font_key,
                                               font_size_dp,
                                               text.color,
                                               &text.glyph_instances,
                                               text.render_mode,
                                               text.glyph_options);
             }
@@ -1349,54 +1241,65 @@ impl PrimitiveStore {
                 debug_assert!(channel_num <= 3);
                 for channel in 0..channel_num {
                     resource_cache.request_image(image_cpu.yuv_key[channel], image_cpu.image_rendering, None);
                 }
 
                 // TODO(nical): Currently assuming no tile_spacing for yuv images.
                 metadata.is_opaque = true;
             }
-            PrimitiveKind::AlignedGradient => {
-                let gradient = &mut self.cpu_gradients[metadata.cpu_prim_index.0];
-                if gradient.cache_dirty {
-                    let src_stops = display_list.get(gradient.stops_range);
+            PrimitiveKind::AlignedGradient |
+            PrimitiveKind::AngleGradient |
+            PrimitiveKind::RadialGradient => {}
+        }
 
-                    debug_assert!(gradient.gpu_data_count == src_stops.len() as i32);
-                    let dest_stops = self.gpu_data32.get_slice_mut(gradient.gpu_data_address,
-                                                                   src_stops.len());
+        // Mark this GPU resource as required for this frame.
+        if let Some(mut request) = gpu_cache.request(&mut metadata.gpu_location) {
+            request.push(metadata.local_rect.into());
+            request.push(metadata.local_clip_rect.into());
 
-                    for (src, dest) in src_stops.zip(dest_stops.iter_mut()) {
-                        *dest = GpuBlock32::from(GradientStopGpu {
-                            offset: src.offset,
-                            color: src.color.premultiplied(),
-                            padding: [0.0; 3],
-                        });
-                    }
-
-                    gradient.cache_dirty = false;
+            match metadata.prim_kind {
+                PrimitiveKind::Rectangle => {
+                    let rect = &self.cpu_rectangles[metadata.cpu_prim_index.0];
+                    rect.write_gpu_blocks(request);
+                }
+                PrimitiveKind::Border => {
+                    let border = &self.cpu_borders[metadata.cpu_prim_index.0];
+                    border.write_gpu_blocks(request);
+                }
+                PrimitiveKind::BoxShadow => {
+                    let box_shadow = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
+                    box_shadow.write_gpu_blocks(request);
                 }
-            }
-            PrimitiveKind::AngleGradient => {
-                let gradient = &mut self.cpu_gradients[metadata.cpu_prim_index.0];
-                if gradient.cache_dirty {
-                    let src_stops = display_list.get(gradient.stops_range);
-
-                    let dest_gradient = self.gpu_gradient_data.get_mut(gradient.gpu_data_address);
-                    dest_gradient.build(src_stops, gradient.reverse_stops);
-                    gradient.cache_dirty = false;
+                PrimitiveKind::Image => {
+                    let image = &self.cpu_images[metadata.cpu_prim_index.0];
+                    image.write_gpu_blocks(request);
+                }
+                PrimitiveKind::YuvImage => {
+                    let yuv_image = &self.cpu_yuv_images[metadata.cpu_prim_index.0];
+                    yuv_image.write_gpu_blocks(request);
+                }
+                PrimitiveKind::AlignedGradient => {
+                    let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
+                    gradient.build_gpu_blocks_for_aligned(display_list,
+                                                          request);
                 }
-            }
-            PrimitiveKind::RadialGradient => {
-                let gradient = &mut self.cpu_radial_gradients[metadata.cpu_prim_index.0];
-                if gradient.cache_dirty {
-                    let src_stops = display_list.get(gradient.stops_range);
-
-                    let dest_gradient = self.gpu_gradient_data.get_mut(gradient.gpu_data_address);
-                    dest_gradient.build(src_stops, false);
-                    gradient.cache_dirty = false;
+                PrimitiveKind::AngleGradient => {
+                    let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0];
+                    gradient.build_gpu_blocks_for_angle_radial(display_list,
+                                                               request);
+                }
+                PrimitiveKind::RadialGradient => {
+                    let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
+                    gradient.build_gpu_blocks_for_angle_radial(display_list,
+                                                               request);
+                }
+                PrimitiveKind::TextRun => {
+                    let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
+                    text.write_gpu_blocks(request);
                 }
             }
         }
 
         if prim_needs_resolve {
             self.prims_to_resolve.push(prim_index);
         }
     }
@@ -1424,21 +1327,18 @@ macro_rules! define_gpu_block {
                 fn from(data: $derive) -> $name {
                     unsafe { mem::transmute(data) }
                 }
             }
         )*
     )
 }
 
-define_gpu_block!(GpuBlock16: [f32; 4] =
-    InstanceRect, GlyphPrimitive
-);
 define_gpu_block!(GpuBlock32: [f32; 8] =
-    GradientStopGpu, ClipCorner, ClipRect, ImageMaskData,
+    ClipCorner, ClipRect, ImageMaskData,
     BorderCornerClipData, BorderCornerDashClipData, BorderCornerDotClipData
 );
 
 //Test for one clip region contains another
 trait InsideTest<T> {
     fn might_contain(&self, clip: &T) -> bool;
 }
 
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -1,15 +1,15 @@
 /* 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_render::DebugRenderer;
 use device::{Device, GpuMarker, GpuSample, NamedTag};
-use euclid::{Point2D, Size2D, Rect};
+use euclid::{Point2D, Size2D, Rect, vec2};
 use std::collections::vec_deque::VecDeque;
 use std::f32;
 use std::mem;
 use webrender_traits::ColorF;
 use time::precise_time_ns;
 
 const GRAPH_WIDTH: f32 = 1024.0;
 const GRAPH_HEIGHT: f32 = 320.0;
@@ -464,17 +464,17 @@ impl ProfileGraph {
                   description: &'static str,
                   debug_renderer: &mut DebugRenderer) -> Rect<f32> {
         let size = Size2D::new(600.0, 120.0);
         let line_height = debug_renderer.line_height();
         let mut rect = Rect::new(Point2D::new(x, y), size);
         let stats = self.stats();
 
         let text_color = ColorF::new(1.0, 1.0, 0.0, 1.0);
-        let text_origin = rect.origin + Point2D::new(rect.size.width, 20.0);
+        let text_origin = rect.origin + vec2(rect.size.width, 20.0);
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y,
                                 description,
                                 &ColorF::new(0.0, 1.0, 0.0, 1.0));
         debug_renderer.add_text(text_origin.x,
                                 text_origin.y + line_height,
                                 &format!("Min: {:.2} ms", stats.min_value),
                                 &text_color);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,15 +1,16 @@
 /* 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 internal_types::{FontTemplate, SourceTexture, ResultMsg, RendererFrame};
+use gpu_cache::GpuCache;
+use internal_types::{SourceTexture, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 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;
@@ -18,16 +19,17 @@ use thread_profiler::register_thread_wit
 use rayon::ThreadPool;
 use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use webrender_traits::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
 use webrender_traits::channel::{PayloadSender, PayloadSenderHelperMethods};
 use webrender_traits::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
 use webrender_traits::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, IdNamespace, ImageData};
 use webrender_traits::{LayerPoint, PipelineId, RenderDispatcher, RenderNotifier};
 use webrender_traits::{VRCompositorCommand, VRCompositorHandler, WebGLCommand, WebGLContextId};
+use webrender_traits::{FontTemplate};
 
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::GLContextDispatcher;
 
 #[cfg(not(feature = "webgl"))]
 use webgl_types::GLContextDispatcher;
 
 /// The render backend is responsible for transforming high level display lists into
@@ -44,16 +46,17 @@ pub struct RenderBackend {
     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,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
     webrender_context_handle: Option<GLContextHandleWrapper>,
     webgl_contexts: HashMap<WebGLContextId, GLContextWrapper>,
@@ -92,16 +95,17 @@ impl RenderBackend {
             payload_rx: payload_rx,
             payload_tx: payload_tx,
             result_tx: result_tx,
             hidpi_factor: hidpi_factor,
             page_zoom_factor: 1.0,
             pinch_zoom_factor: 1.0,
             pan: DeviceIntPoint::zero(),
             resource_cache: resource_cache,
+            gpu_cache: GpuCache::new(),
             scene: Scene::new(),
             frame: Frame::new(config),
             next_namespace_id: IdNamespace(1),
             notifier: notifier,
             webrender_context_handle: webrender_context_handle,
             webgl_contexts: HashMap::new(),
             current_bound_webgl_context_id: None,
             recorder: recorder,
@@ -489,16 +493,17 @@ impl RenderBackend {
     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
     }
 
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use gpu_store::GpuStoreAddress;
+use gpu_cache::GpuCacheHandle;
 use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
 use mask_cache::{MaskBounds, MaskCacheInfo};
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 use webrender_traits::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{MixBlendMode};
@@ -50,17 +50,17 @@ pub enum RenderTaskLocation {
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug, Clone)]
 pub enum AlphaRenderItem {
     Primitive(Option<ClipScrollGroupIndex>, PrimitiveIndex, i32),
     Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
-    SplitComposite(StackingContextIndex, RenderTaskId, GpuStoreAddress, i32),
+    SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
     pub items: Vec<AlphaRenderItem>,
 }
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -9,27 +9,26 @@
 //!
 //! [renderer]: struct.Renderer.html
 
 use debug_colors;
 use debug_render::DebugRenderer;
 use device::{DepthFunction, Device, FrameId, ProgramId, TextureId, VertexFormat, GpuMarker, GpuProfiler};
 use device::{GpuSample, TextureFilter, VAOId, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
 use device::get_gl_format_bgra;
-use euclid::Matrix4D;
+use euclid::Transform3D;
 use fnv::FnvHasher;
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_cache::{GpuCacheUpdate, GpuCacheUpdateList};
 use gpu_store::{GpuStore, GpuStoreLayout};
 use internal_types::{CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
 use internal_types::{TextureUpdateList, PackedVertex, RenderTargetMode};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
 use internal_types::{BatchTextures, TextureSampler};
-use prim_store::{GradientData, SplitGeometry};
 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};
@@ -50,18 +49,17 @@ use tiling::{AlphaBatchKind, BlurCommand
 use tiling::{AlphaRenderTarget, CacheClipInstance, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
 use webgl_types::GLContextHandleWrapper;
 use webrender_traits::{ColorF, Epoch, PipelineId, RenderNotifier, RenderDispatcher};
 use webrender_traits::{ExternalImageId, ExternalImageType, ImageData, ImageFormat, RenderApiSender};
 use webrender_traits::{DeviceIntRect, DeviceUintRect, DevicePoint, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
-use webrender_traits::{ImageDescriptor, BlobImageRenderer};
-use webrender_traits::{channel, FontRenderMode};
+use webrender_traits::{BlobImageRenderer, channel, FontRenderMode};
 use webrender_traits::VRCompositorHandler;
 use webrender_traits::{YuvColorSpace, YuvFormat};
 use webrender_traits::{YUV_COLOR_SPACES, YUV_FORMATS};
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
@@ -190,47 +188,63 @@ pub enum BlendMode {
     PremultipliedAlpha,
 
     // Use the color of the text itself as a constant color blend factor.
     Subpixel(ColorF),
 }
 
 /// The device-specific representation of the cache texture in gpu_cache.rs
 struct CacheTexture {
-    id: TextureId,
+    current_id: TextureId,
+    next_id: TextureId,
 }
 
 impl CacheTexture {
     fn new(device: &mut Device) -> CacheTexture {
-        let id = device.create_texture_ids(1, TextureTarget::Default)[0];
+        let ids = device.create_texture_ids(2, TextureTarget::Default);
 
         CacheTexture {
-            id: id,
+            current_id: ids[0],
+            next_id: ids[1],
         }
     }
 
     fn update(&mut self, device: &mut Device, updates: &GpuCacheUpdateList) {
         // See if we need to create or resize the texture.
-        let current_dimensions = device.get_texture_dimensions(self.id);
-
+        let current_dimensions = device.get_texture_dimensions(self.current_id);
         if updates.height > current_dimensions.height {
-            // TODO(gw): Handle resizing an existing cache texture.
-            if current_dimensions.height > 0 {
-                panic!("TODO: Implement texture copy!!!");
-            }
-
             // Create a f32 texture that can be used for the vertex shader
             // to fetch data from.
-            device.init_texture(self.id,
+            device.init_texture(self.next_id,
                                 MAX_VERTEX_TEXTURE_WIDTH as u32,
                                 updates.height as u32,
                                 ImageFormat::RGBAF32,
                                 TextureFilter::Nearest,
-                                RenderTargetMode::None,
+                                RenderTargetMode::SimpleRenderTarget,
                                 None);
+
+            // Copy the current texture into the newly resized texture.
+            if current_dimensions.height > 0 {
+                device.bind_draw_target(Some((self.next_id, 0)), None);
+
+                let blit_rect = DeviceIntRect::new(DeviceIntPoint::zero(),
+                                                   DeviceIntSize::new(MAX_VERTEX_TEXTURE_WIDTH as i32,
+                                                                      current_dimensions.height as i32));
+
+                // TODO(gw): Should probably switch this to glCopyTexSubImage2D, since we
+                // don't do any stretching here.
+                device.blit_render_target(Some((self.current_id, 0)),
+                                          Some(blit_rect),
+                                          blit_rect);
+
+                // Free the GPU memory for that texture until we need to resize again.
+                device.deinit_texture(self.current_id);
+            }
+
+            mem::swap(&mut self.current_id, &mut self.next_id);
         }
 
         for update in &updates.updates {
             match update {
                 &GpuCacheUpdate::Copy { block_index, block_count, address } => {
                     // Apply an incremental update to the cache texture.
                     // TODO(gw): For the initial implementation, we will just
                     //           use update_texture() since it's simple. If / when
@@ -239,17 +253,17 @@ impl CacheTexture {
                     //           using glMapBuffer() with the unsynchronized bit,
                     //           and managing the synchronization ourselves with fences.
                     let data: &[u8] = unsafe {
                         let ptr = updates.blocks
                                          .as_ptr()
                                          .offset(block_index as isize);
                         slice::from_raw_parts(ptr as *const _, block_count * 16)
                     };
-                    device.update_texture(self.id,
+                    device.update_texture(self.current_id,
                                           address.u as u32,
                                           address.v as u32,
                                           block_count as u32,
                                           1,
                                           None,
                                           data);
                 }
             }
@@ -321,55 +335,16 @@ impl GpuStoreLayout for VertexDataTextur
     fn texture_filter() -> TextureFilter {
         TextureFilter::Nearest
     }
 }
 
 type VertexDataTexture = GpuDataTexture<VertexDataTextureLayout>;
 pub type VertexDataStore<T> = GpuStore<T, VertexDataTextureLayout>;
 
-pub struct GradientDataTextureLayout;
-
-impl GpuStoreLayout for GradientDataTextureLayout {
-    fn image_format() -> ImageFormat {
-        ImageFormat::RGBA8
-    }
-
-    fn texture_width<T>() -> usize {
-        mem::size_of::<GradientData>() / Self::texel_size() / 2
-    }
-
-    fn texture_filter() -> TextureFilter {
-        TextureFilter::Linear
-    }
-}
-
-type GradientDataTexture = GpuDataTexture<GradientDataTextureLayout>;
-pub type GradientDataStore = GpuStore<GradientData, GradientDataTextureLayout>;
-
-pub struct SplitGeometryTextureLayout;
-
-impl GpuStoreLayout for SplitGeometryTextureLayout {
-    fn image_format() -> ImageFormat {
-        //TODO: use normalized integers
-        ImageFormat::RGBAF32
-    }
-
-    fn texture_width<T>() -> usize {
-        MAX_VERTEX_TEXTURE_WIDTH - (MAX_VERTEX_TEXTURE_WIDTH % Self::texels_per_item::<T>())
-    }
-
-    fn texture_filter() -> TextureFilter {
-        TextureFilter::Nearest
-    }
-}
-
-type SplitGeometryTexture = GpuDataTexture<SplitGeometryTextureLayout>;
-pub type SplitGeometryStore = GpuStore<SplitGeometry, SplitGeometryTextureLayout>;
-
 const TRANSFORM_FEATURE: &'static str = "TRANSFORM";
 const SUBPIXEL_AA_FEATURE: &'static str = "SUBPIXEL_AA";
 const CLIP_FEATURE: &'static str = "CLIP";
 
 enum ShaderKind {
     Primitive,
     Cache(VertexFormat),
     ClipCache,
@@ -525,56 +500,40 @@ fn create_clip_shader(name: &'static str
 
     let includes = &["prim_shared", "clip_shared"];
     device.create_program_with_prefix(name, includes, Some(prefix), VertexFormat::Clip)
 }
 
 struct GpuDataTextures {
     layer_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
-    prim_geom_texture: VertexDataTexture,
-    data16_texture: VertexDataTexture,
     data32_texture: VertexDataTexture,
     resource_rects_texture: VertexDataTexture,
-    gradient_data_texture: GradientDataTexture,
-    split_geometry_texture: SplitGeometryTexture,
 }
 
 impl GpuDataTextures {
     fn new(device: &mut Device) -> GpuDataTextures {
         GpuDataTextures {
             layer_texture: VertexDataTexture::new(device),
             render_task_texture: VertexDataTexture::new(device),
-            prim_geom_texture: VertexDataTexture::new(device),
-            data16_texture: VertexDataTexture::new(device),
             data32_texture: VertexDataTexture::new(device),
             resource_rects_texture: VertexDataTexture::new(device),
-            gradient_data_texture: GradientDataTexture::new(device),
-            split_geometry_texture: SplitGeometryTexture::new(device),
         }
     }
 
     fn init_frame(&mut self, device: &mut Device, frame: &mut Frame) {
-        self.data16_texture.init(device, &mut frame.gpu_data16);
         self.data32_texture.init(device, &mut frame.gpu_data32);
-        self.prim_geom_texture.init(device, &mut frame.gpu_geometry);
         self.resource_rects_texture.init(device, &mut frame.gpu_resource_rects);
         self.layer_texture.init(device, &mut frame.layer_texture_data);
         self.render_task_texture.init(device, &mut frame.render_task_data);
-        self.gradient_data_texture.init(device, &mut frame.gpu_gradient_data);
-        self.split_geometry_texture.init(device, &mut frame.gpu_split_geometry);
 
         device.bind_texture(TextureSampler::Layers, self.layer_texture.id);
         device.bind_texture(TextureSampler::RenderTasks, self.render_task_texture.id);
-        device.bind_texture(TextureSampler::Geometry, self.prim_geom_texture.id);
-        device.bind_texture(TextureSampler::Data16, self.data16_texture.id);
         device.bind_texture(TextureSampler::Data32, self.data32_texture.id);
         device.bind_texture(TextureSampler::ResourceRects, self.resource_rects_texture.id);
-        device.bind_texture(TextureSampler::Gradients, self.gradient_data_texture.id);
-        device.bind_texture(TextureSampler::SplitGeometry, self.split_geometry_texture.id);
     }
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum ReadPixelsFormat {
     Rgba8,
     Bgra8,
 }
@@ -987,44 +946,18 @@ impl Renderer {
                                      &[],
                                      &mut device,
                                      options.precache_shaders)
         };
 
         let device_max_size = device.max_texture_size();
         let max_texture_size = cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size));
 
-        let mut texture_cache = TextureCache::new(max_texture_size);
-        let mut backend_profile_counters = BackendProfileCounters::new();
-
-        let white_pixels: Vec<u8> = vec![
-            0xff, 0xff, 0xff, 0xff,
-            0xff, 0xff, 0xff, 0xff,
-            0xff, 0xff, 0xff, 0xff,
-            0xff, 0xff, 0xff, 0xff,
-        ];
-        let mask_pixels: Vec<u8> = vec![
-            0xff, 0xff,
-            0xff, 0xff,
-        ];
-
-        // TODO: Ensure that the white texture can never get evicted when the cache supports LRU eviction!
-        let white_image_id = texture_cache.new_item_id();
-        texture_cache.insert(white_image_id,
-                             ImageDescriptor::new(2, 2, ImageFormat::RGBA8, false),
-                             TextureFilter::Linear,
-                             ImageData::Raw(Arc::new(white_pixels)),
-                             &mut backend_profile_counters.resources.texture_cache);
-
-        let dummy_mask_image_id = texture_cache.new_item_id();
-        texture_cache.insert(dummy_mask_image_id,
-                             ImageDescriptor::new(2, 2, ImageFormat::A8, false),
-                             TextureFilter::Linear,
-                             ImageData::Raw(Arc::new(mask_pixels)),
-                             &mut backend_profile_counters.resources.texture_cache);
+        let texture_cache = TextureCache::new(max_texture_size);
+        let backend_profile_counters = BackendProfileCounters::new();
 
         let dummy_cache_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
         device.init_texture(dummy_cache_texture_id,
                             1,
                             1,
                             ImageFormat::RGBA8,
                             TextureFilter::Linear,
                             RenderTargetMode::LayerRenderTarget(1),
@@ -1379,17 +1312,17 @@ impl Renderer {
 
                         self.device.disable_scissor();
                         self.device.disable_depth();
                         self.device.set_blend(false);
                         //self.update_shaders();
                         self.update_texture_cache();
 
                         self.update_gpu_cache();
-                        self.device.bind_texture(TextureSampler::ResourceCache, self.gpu_cache_texture.id);
+                        self.device.bind_texture(TextureSampler::ResourceCache, self.gpu_cache_texture.current_id);
 
                         frame_id
                     };
 
                     self.draw_tile_frame(frame, &framebuffer_size);
 
                     self.gpu_profile.end_frame();
                     cpu_frame_id
@@ -1581,17 +1514,17 @@ impl Renderer {
         }
     }
 
     fn draw_instanced_batch<T>(&mut self,
                                data: &[T],
                                vao: VAOId,
                                shader: ProgramId,
                                textures: &BatchTextures,
-                               projection: &Matrix4D<f32>) {
+                               projection: &Transform3D<f32>) {
         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);
         }
 
@@ -1612,17 +1545,17 @@ impl Renderer {
             }
         }
 
         self.profile_counters.vertices.add(6 * data.len());
     }
 
     fn submit_batch(&mut self,
                     batch: &PrimitiveBatch,
-                    projection: &Matrix4D<f32>,
+                    projection: &Transform3D<f32>,
                     render_task_data: &[RenderTaskData],
                     cache_texture: TextureId,
                     render_target: Option<(TextureId, i32)>,
                     target_dimensions: DeviceUintSize) {
         let transform_kind = batch.key.flags.transform_kind();
         let needs_clipping = batch.key.flags.needs_clipping();
         debug_assert!(!needs_clipping ||
                       match batch.key.blend_mode {
@@ -1776,17 +1709,17 @@ impl Renderer {
 
     fn draw_color_target(&mut self,
                          render_target: Option<(TextureId, i32)>,
                          target: &ColorRenderTarget,
                          target_size: DeviceUintSize,
                          color_cache_texture: TextureId,
                          clear_color: Option<[f32; 4]>,
                          render_task_data: &[RenderTaskData],
-                         projection: &Matrix4D<f32>) {
+                         projection: &Transform3D<f32>) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
             self.device.disable_depth();
             self.device.enable_depth_write();
             self.device.set_blend(false);
             self.device.set_blend_mode_alpha();
             match render_target {
@@ -1920,17 +1853,17 @@ impl Renderer {
         self.device.disable_depth();
         self.device.set_blend(false);
     }
 
     fn draw_alpha_target(&mut self,
                          render_target: (TextureId, i32),
                          target: &AlphaRenderTarget,
                          target_size: DeviceUintSize,
-                         projection: &Matrix4D<f32>) {
+                         projection: &Transform3D<f32>) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(Some(render_target), Some(target_size));
             self.device.disable_depth();
             self.device.disable_depth_write();
 
             // TODO(gw): Applying a scissor rect and minimal clear here
             // is a very large performance win on the Intel and nVidia
@@ -2158,26 +2091,26 @@ impl Renderer {
                     clear_color = if self.clear_framebuffer || needs_clear {
                         Some(frame.background_color.map_or(self.clear_color.to_array(), |color| {
                             color.to_array()
                         }))
                     } else {
                         None
                     };
                     size = framebuffer_size;
-                    projection = Matrix4D::ortho(0.0,
+                    projection = Transform3D::ortho(0.0,
                                                  size.width as f32,
                                                  size.height as f32,
                                                  0.0,
                                                  ORTHO_NEAR_PLANE,
                                                  ORTHO_FAR_PLANE)
                 } else {
                     size = &frame.cache_size;
                     clear_color = Some([0.0, 0.0, 0.0, 0.0]);
-                    projection = Matrix4D::ortho(0.0,
+                    projection = Transform3D::ortho(0.0,
                                                  size.width as f32,
                                                  0.0,
                                                  size.height as f32,
                                                  ORTHO_NEAR_PLANE,
                                                  ORTHO_FAR_PLANE);
                 }
 
                 self.device.bind_texture(TextureSampler::CacheA8, src_alpha_id);
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,32 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use frame::FrameId;
-use gpu_cache::GpuCache;
-use internal_types::{FontTemplate, SourceTexture, TextureUpdateList};
+use internal_types::{SourceTexture, TextureUpdateList};
 use profiler::TextureCacheProfileCounters;
 use std::collections::{HashMap, HashSet};
 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 webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageRendering};
+use webrender_traits::{Epoch, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
 use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
 use webrender_traits::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
 use webrender_traits::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
-use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData, ImageStore};
+use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
+use webrender_traits::BlobImageResources;
 use webrender_traits::{ExternalImageData, ExternalImageType, LayoutPoint};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
 // These coordinates are always in texels.
 // They are converted to normalized ST
@@ -87,22 +87,16 @@ impl ImageTemplates {
         self.images.get(&key)
     }
 
     fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> {
         self.images.get_mut(&key)
     }
 }
 
-impl ImageStore for ImageTemplates {
-    fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)> {
-        self.images.get(&key).map(|resource|{ (&resource.data, &resource.descriptor) })
-    }
-}
-
 struct CachedImageInfo {
     texture_cache_id: TextureCacheItemId,
     epoch: Epoch,
 }
 
 pub struct ResourceClassCache<K,V> {
     resources: HashMap<K, V, BuildHasherDefault<FnvHasher>>,
     last_access_times: HashMap<K, FrameId, BuildHasherDefault<FnvHasher>>,
@@ -181,30 +175,42 @@ impl Into<BlobImageRequest> for ImageReq
     }
 }
 
 struct WebGLTexture {
     id: SourceTexture,
     size: DeviceIntSize,
 }
 
+struct Resources {
+    font_templates: HashMap<FontKey, FontTemplate, BuildHasherDefault<FnvHasher>>,
+    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)> {
+        self.image_templates.get(key).map(|resource| { (&resource.data, &resource.descriptor) })
+    }
+}
+
 pub struct ResourceCache {
     cached_glyphs: GlyphCache,
     cached_images: ResourceClassCache<ImageRequest, CachedImageInfo>,
 
     // TODO(pcwalton): Figure out the lifecycle of these.
     webgl_textures: HashMap<WebGLContextId, WebGLTexture, BuildHasherDefault<FnvHasher>>,
 
-    font_templates: HashMap<FontKey, FontTemplate, BuildHasherDefault<FnvHasher>>,
-    image_templates: ImageTemplates,
+    resources: Resources,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
-    pub gpu_cache: GpuCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
     cached_glyph_dimensions: HashMap<GlyphKey, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
     pending_image_requests: Vec<ImageRequest>,
     glyph_rasterizer: GlyphRasterizer,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
     blob_image_requests: HashSet<ImageRequest>,
@@ -213,21 +219,22 @@ pub struct ResourceCache {
 impl ResourceCache {
     pub fn new(texture_cache: TextureCache,
                workers: Arc<ThreadPool>,
                blob_image_renderer: Option<Box<BlobImageRenderer>>) -> ResourceCache {
         ResourceCache {
             cached_glyphs: ResourceClassCache::new(),
             cached_images: ResourceClassCache::new(),
             webgl_textures: HashMap::default(),
-            font_templates: HashMap::default(),
-            image_templates: ImageTemplates::new(),
+            resources: Resources {
+                font_templates: HashMap::default(),
+                image_templates: ImageTemplates::new(),
+            },
             cached_glyph_dimensions: HashMap::default(),
             texture_cache: texture_cache,
-            gpu_cache: GpuCache::new(),
             state: State::Idle,
             current_frame_id: FrameId(0),
             pending_image_requests: Vec::new(),
             glyph_rasterizer: GlyphRasterizer::new(workers),
 
             blob_image_renderer: blob_image_renderer,
             blob_image_requests: HashSet::new(),
         }
@@ -249,22 +256,25 @@ impl ResourceCache {
             },
         }
     }
 
     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.font_templates.insert(font_key, template);
+        self.resources.font_templates.insert(font_key, template);
     }
 
     pub fn delete_font_template(&mut self, font_key: FontKey) {
         self.glyph_rasterizer.delete_font(font_key);
-        self.font_templates.remove(&font_key);
+        self.resources.font_templates.remove(&font_key);
+        if let Some(ref mut r) = self.blob_image_renderer {
+            r.delete_font(font_key);
+        }
     }
 
     pub fn add_image_template(&mut self,
                               image_key: ImageKey,
                               descriptor: ImageDescriptor,
                               mut data: ImageData,
                               mut tiling: Option<TileSize>) {
         if tiling.is_none() && self.should_tile(&descriptor, &data) {
@@ -284,25 +294,25 @@ impl ResourceCache {
         let resource = ImageResource {
             descriptor: descriptor,
             data: data,
             epoch: Epoch(0),
             tiling: tiling,
             dirty_rect: None,
         };
 
-        self.image_templates.insert(image_key, resource);
+        self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(&mut self,
                                  image_key: ImageKey,
                                  descriptor: ImageDescriptor,
                                  mut data: ImageData,
                                  dirty_rect: Option<DeviceUintRect>) {
-        let resource = if let Some(image) = self.image_templates.get(image_key) {
+        let resource = if let Some(image) = self.resources.image_templates.get(image_key) {
             assert_eq!(image.descriptor.width, descriptor.width);
             assert_eq!(image.descriptor.height, descriptor.height);
             assert_eq!(image.descriptor.format, descriptor.format);
 
             let next_epoch = Epoch(image.epoch.0 + 1);
 
             let mut tiling = image.tiling;
             if tiling.is_none() && self.should_tile(&descriptor, &data) {
@@ -326,21 +336,21 @@ impl ResourceCache {
                     (Some(rect), None) => Some(rect),
                     _ => None,
                 },
             }
         } else {
             panic!("Attempt to update non-existant image (key {:?}).", image_key);
         };
 
-        self.image_templates.insert(image_key, resource);
+        self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
-        let value = self.image_templates.remove(image_key);
+        let value = self.resources.image_templates.remove(image_key);
 
         match value {
             Some(image) => {
                 if image.data.is_blob() {
                     self.blob_image_renderer.as_mut().unwrap().delete(image_key);
                 }
             }
             None => {
@@ -371,17 +381,17 @@ impl ResourceCache {
 
         debug_assert_eq!(self.state, State::AddResources);
         let request = ImageRequest {
             key: key,
             rendering: rendering,
             tile: tile,
         };
 
-        let template = self.image_templates.get(key).unwrap();
+        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);
         }
         if template.data.is_blob() {
             if let Some(ref mut renderer) = self.blob_image_renderer {
                 let same_epoch = match self.cached_images.resources.get(&request) {
                     Some(entry) => entry.epoch == template.epoch,
                     None => false,
@@ -400,25 +410,25 @@ impl ResourceCache {
                             (offset, w, h)
                         }
                         None => {
                             (DevicePoint::zero(), template.descriptor.width, template.descriptor.height)
                         }
                     };
 
                     renderer.request(
+                        &self.resources,
                         request.into(),
                         &BlobImageDescriptor {
                             width: w,
                             height: h,
                             offset: offset,
                             format: template.descriptor.format,
                         },
                         template.dirty_rect,
-                        &self.image_templates,
                     );
                 }
             }
         } else {
             self.pending_image_requests.push(request);
         }
     }
 
@@ -514,17 +524,17 @@ impl ResourceCache {
             uv0: DevicePoint::new(item.pixel_rect.top_left.x as f32,
                                   item.pixel_rect.top_left.y as f32),
             uv1: DevicePoint::new(item.pixel_rect.bottom_right.x as f32,
                                   item.pixel_rect.bottom_right.y as f32),
         }
     }
 
     pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties {
-        let image_template = &self.image_templates.get(image_key).unwrap();
+        let image_template = &self.resources.image_templates.get(image_key).unwrap();
 
         let external_image = match image_template.data {
             ImageData::External(ext_image) => {
                 match ext_image.image_type {
                     ExternalImageType::Texture2DHandle |
                     ExternalImageType::TextureRectHandle |
                     ExternalImageType::TextureExternalHandle => {
                         Some(ext_image)
@@ -562,17 +572,16 @@ impl ResourceCache {
         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;
-        self.gpu_cache.begin_frame();
     }
 
     pub fn block_until_all_resources_added(&mut self,
                                            texture_cache_profile: &mut TextureCacheProfileCounters) {
         profile_scope!("block_until_all_resources_added");
 
         debug_assert_eq!(self.state, State::AddResources);
         self.state = State::QueryResources;
@@ -618,17 +627,17 @@ impl ResourceCache {
             }
         }
     }
 
     fn update_texture_cache(&mut self,
                             request: &ImageRequest,
                             image_data: Option<ImageData>,
                             texture_cache_profile: &mut TextureCacheProfileCounters) {
-        let image_template = self.image_templates.get_mut(request.key).unwrap();
+        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 descriptor = if let Some(tile) = request.tile {
             let tile_size = image_template.tiling.unwrap();
             let image_descriptor = &image_template.descriptor;
 
@@ -675,41 +684,38 @@ impl ResourceCache {
                     *entry.into_mut() = CachedImageInfo {
                         texture_cache_id: image_id,
                         epoch: image_template.epoch,
                     };
                     image_template.dirty_rect = None;
                 }
             }
             Vacant(entry) => {
-                let image_id = self.texture_cache.new_item_id();
-
                 let filter = match request.rendering {
                     ImageRendering::Pixelated => TextureFilter::Nearest,
                     ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
                 };
 
-                self.texture_cache.insert(image_id,
-                                          descriptor,
-                                          filter,
-                                          image_data,
-                                          texture_cache_profile);
+                let image_id = self.texture_cache.insert(descriptor,
+                                                         filter,
+                                                         image_data,
+                                                         texture_cache_profile);
 
                 entry.insert(CachedImageInfo {
                     texture_cache_id: image_id,
                     epoch: image_template.epoch,
                 });
             }
         }
     }
     fn finalize_image_request(&mut self,
                               request: ImageRequest,
                               image_data: Option<ImageData>,
                               texture_cache_profile: &mut TextureCacheProfileCounters) {
-        match self.image_templates.get(request.key).unwrap().data {
+        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 => {
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -440,19 +440,16 @@ impl FreeListBin {
 #[derive(Debug, Clone)]
 pub struct TextureCacheItem {
     // Identifies the texture and array slice
     pub texture_id: CacheTextureId,
 
     // The texture coordinates for this item
     pub pixel_rect: RectUv<i32, DevicePixel>,
 
-    // The size of the entire texture (not just the allocated rectangle)
-    pub texture_size: DeviceUintSize,
-
     // The size of the allocated rectangle.
     pub allocated_rect: DeviceUintRect,
 }
 
 // Structure squat the width/height fields to maintain the free list information :)
 impl FreeListItem for TextureCacheItem {
     fn take(&mut self) -> Self {
         let data = self.clone();
@@ -481,22 +478,20 @@ impl FreeListItem for TextureCacheItem {
                 self.allocated_rect.size.height = 0;
             }
         }
     }
 }
 
 impl TextureCacheItem {
     fn new(texture_id: CacheTextureId,
-           rect: DeviceUintRect,
-           texture_size: &DeviceUintSize)
+           rect: DeviceUintRect)
            -> TextureCacheItem {
         TextureCacheItem {
             texture_id: texture_id,
-            texture_size: *texture_size,
             pixel_rect: RectUv {
                 top_left: DeviceIntPoint::new(rect.origin.x as i32,
                                               rect.origin.y as i32),
                 top_right: DeviceIntPoint::new((rect.origin.x + rect.size.width) as i32,
                                                 rect.origin.y as i32),
                 bottom_left: DeviceIntPoint::new(rect.origin.x as i32,
                                                 (rect.origin.y + rect.size.height) as i32),
                 bottom_right: DeviceIntPoint::new((rect.origin.x + rect.size.width) as i32,
@@ -578,16 +573,17 @@ pub struct TextureCache {
 #[derive(PartialEq, Eq, Debug)]
 pub enum AllocationKind {
     TexturePage,
     Standalone,
 }
 
 #[derive(Debug)]
 pub struct AllocationResult {
+    image_id: TextureCacheItemId,
     kind: AllocationKind,
     item: TextureCacheItem,
 }
 
 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;
@@ -606,28 +602,17 @@ impl TextureCache {
     pub fn max_texture_size(&self) -> u32 {
         self.max_texture_size
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         mem::replace(&mut self.pending_updates, TextureUpdateList::new())
     }
 
-    // TODO(gw): This API is a bit ugly (having to allocate an ID and
-    //           then use it). But it has to be that way for now due to
-    //           how the raster_jobs code works.
-    pub fn new_item_id(&mut self) -> TextureCacheItemId {
-        let new_item = TextureCacheItem::new(CacheTextureId(0),
-                                             DeviceUintRect::zero(),
-                                             &DeviceUintSize::zero());
-        self.items.insert(new_item)
-    }
-
     pub fn allocate(&mut self,
-                    image_id: TextureCacheItemId,
                     requested_width: u32,
                     requested_height: u32,
                     format: ImageFormat,
                     filter: TextureFilter,
                     profile: &mut TextureCacheProfileCounters)
                     -> AllocationResult {
         let requested_size = DeviceUintSize::new(requested_width, requested_height);
 
@@ -636,23 +621,23 @@ impl TextureCache {
         //           texture. This isn't ideal, as it causes lots of batch breaks,
         //           but is probably rare enough that it can be fixed up later (it's also
         //           fairly trivial to implement, just tedious).
         if filter == TextureFilter::Nearest {
             // Fall back to standalone texture allocation.
             let texture_id = self.cache_id_list.allocate();
             let cache_item = TextureCacheItem::new(
                 texture_id,
-                DeviceUintRect::new(DeviceUintPoint::zero(), requested_size),
-                &requested_size);
-            *self.items.get_mut(image_id) = cache_item;
+                DeviceUintRect::new(DeviceUintPoint::zero(), requested_size));
+            let image_id = self.items.insert(cache_item);
 
             return AllocationResult {
                 item: self.items.get(image_id).clone(),
                 kind: AllocationKind::Standalone,
+                image_id: image_id,
             }
         }
 
         let mode = RenderTargetMode::SimpleRenderTarget;
         let (page_list, page_profile) = match format {
             ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
             ImageFormat::RGBA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
             ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
@@ -686,22 +671,16 @@ impl TextureCache {
                 });
 
                 let extra_texels = new_width * new_height - page.texture_size.width * page.texture_size.height;
                 let extra_bytes = extra_texels * format.bytes_per_pixel().unwrap_or(0);
                 page_profile.inc(extra_bytes as usize);
 
                 page.grow(texture_size);
 
-                self.items.for_each_item(|item| {
-                    if item.texture_id == page.texture_id {
-                        item.texture_size = texture_size;
-                    }
-                });
-
                 if page.can_allocate(&requested_size) {
                     page_id = Some(i);
                     break;
                 }
             }
         }
 
         let mut page = match page_id {
@@ -738,23 +717,23 @@ impl TextureCache {
                 page_list.push(page);
                 page_list.last_mut().unwrap()
             },
         };
 
         let location = page.allocate(&requested_size)
                            .expect("All the checks have passed till now, there is no way back.");
         let cache_item = TextureCacheItem::new(page.texture_id,
-                                               DeviceUintRect::new(location, requested_size),
-                                               &page.texture_size);
-        *self.items.get_mut(image_id) = cache_item.clone();
+                                               DeviceUintRect::new(location, requested_size));
+        let image_id = self.items.insert(cache_item.clone());
 
         AllocationResult {
             item: cache_item,
             kind: AllocationKind::TexturePage,
+            image_id: image_id,
         }
     }
 
     pub fn update(&mut self,
                   image_id: TextureCacheItemId,
                   descriptor: ImageDescriptor,
                   data: ImageData,
                   dirty_rect: Option<DeviceUintRect>) {
@@ -805,39 +784,37 @@ impl TextureCache {
             id: existing_item.texture_id,
             op: op,
         };
 
         self.pending_updates.push(update_op);
     }
 
     pub fn insert(&mut self,
-                  image_id: TextureCacheItemId,
                   descriptor: ImageDescriptor,
                   filter: TextureFilter,
                   data: ImageData,
-                  profile: &mut TextureCacheProfileCounters) {
+                  profile: &mut TextureCacheProfileCounters) -> TextureCacheItemId {
         if let ImageData::Blob(..) = data {
             panic!("must rasterize the vector image before adding to the cache");
         }
 
         let width = descriptor.width;
         let height = descriptor.height;
         let format = descriptor.format;
         let stride = descriptor.stride;
 
         if let ImageData::Raw(ref vec) = data {
             let finish = descriptor.offset +
                          width * format.bytes_per_pixel().unwrap_or(0) +
                          (height-1) * descriptor.compute_stride();
             assert!(vec.len() >= finish as usize);
         }
 
-        let result = self.allocate(image_id,
-                                   width,
+        let result = self.allocate(width,
                                    height,
                                    format,
                                    filter,
                                    profile);
 
         match result.kind {
             AllocationKind::TexturePage => {
                 match data {
@@ -924,16 +901,18 @@ impl TextureCache {
                             },
                         };
 
                         self.pending_updates.push(update_op);
                     }
                 }
             }
         }
+
+        result.image_id
     }
 
     pub fn get(&self, id: TextureCacheItemId) -> &TextureCacheItem {
         self.items.get(id)
     }
 
     pub fn free(&mut self, id: TextureCacheItemId) {
         let item = self.items.free(id);
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,41 +1,41 @@
 /* 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 border::{BorderCornerInstance, BorderCornerSide};
 use device::TextureId;
 use fnv::FnvHasher;
-use gpu_cache::GpuCacheUpdateList;
+use gpu_cache::{GpuCache, GpuCacheUpdateList};
 use gpu_store::GpuStoreAddress;
 use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchTextures, CacheTextureId, LowLevelFilterOp};
 use internal_types::SourceTexture;
 use mask_cache::MaskCacheInfo;
-use prim_store::{CLIP_DATA_GPU_SIZE, DeferredResolve, GpuBlock16, GpuBlock32};
-use prim_store::{GradientData, SplitGeometry, PrimitiveCacheKey, PrimitiveGeometry};
+use prim_store::{CLIP_DATA_GPU_SIZE, DeferredResolve, GpuBlock32};
+use prim_store::PrimitiveCacheKey;
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore, TexelRect};
 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 webrender_traits::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint};
 use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
-use webrender_traits::{ExternalImageType, FontRenderMode, ImageRendering, LayerPoint, LayerRect};
+use webrender_traits::{ExternalImageType, FontRenderMode, ImageRendering, LayerRect};
 use webrender_traits::{LayerToWorldTransform, MixBlendMode, PipelineId, TransformStyle};
-use webrender_traits::{WorldToLayerTransform, YuvColorSpace, YuvFormat};
+use webrender_traits::{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,
@@ -420,20 +420,19 @@ impl AlphaRenderItem {
                     }
                 };
                 let needs_blending = !prim_metadata.is_opaque ||
                                      needs_clipping ||
                                      transform_kind == TransformedRectKind::Complex;
                 let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
 
                 let prim_cache_address = prim_metadata.gpu_location
-                                                      .as_int(&ctx.resource_cache.gpu_cache);
+                                                      .as_int(&ctx.gpu_cache);
 
-                let base_instance = SimplePrimitiveInstance::new(prim_index,
-                                                                 prim_cache_address,
+                let base_instance = SimplePrimitiveInstance::new(prim_cache_address,
                                                                  task_index,
                                                                  clip_task_index,
                                                                  packed_layer_index,
                                                                  z);
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Border => {
                         let border_cpu = &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
@@ -512,48 +511,43 @@ impl AlphaRenderItem {
                             Some(ref task) => {
                                 let cache_task_id = task.id;
                                 render_tasks.get_task_index(&cache_task_id,
                                                             child_pass_index).0 as i32
                             }
                             None => 0,
                         };
 
-                        for glyph_index in 0..text_cpu.gpu_data_count {
-                            let user_data1 = match batch_kind {
-                                AlphaBatchKind::TextRun => text_cpu.resource_address.0 + glyph_index,
-                                AlphaBatchKind::CacheImage => cache_task_index,
-                                _ => unreachable!(),
-                            };
+                        let user_data1 = match batch_kind {
+                            AlphaBatchKind::TextRun => text_cpu.resource_address.0,
+                            AlphaBatchKind::CacheImage => cache_task_index,
+                            _ => unreachable!(),
+                        };
 
-                            batch.add_instance(base_instance.build(text_cpu.gpu_data_address.0 + glyph_index,
-                                                                   user_data1));
+                        for glyph_index in 0..text_cpu.glyph_instances.len() {
+                            batch.add_instance(base_instance.build(glyph_index as i32, user_data1));
                         }
                     }
                     PrimitiveKind::AlignedGradient => {
                         let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        for part_index in 0..(gradient_cpu.gpu_data_count - 1) {
-                            batch.add_instance(base_instance.build(gradient_cpu.gpu_data_address.0 + part_index, 0));
+                        for part_index in 0..(gradient_cpu.stops_count - 1) {
+                            batch.add_instance(base_instance.build(part_index as i32, 0));
                         }
                     }
                     PrimitiveKind::AngleGradient => {
-                        let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::AngleGradient, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(gradient_cpu.gpu_data_address.0,
-                                                               0));
+                        batch.add_instance(base_instance.build(0, 0));
                     }
                     PrimitiveKind::RadialGradient => {
-                        let gradient_cpu = &ctx.prim_store.cpu_radial_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::RadialGradient, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(gradient_cpu.gpu_data_address.0,
-                                                               0));
+                        batch.add_instance(base_instance.build(0, 0));
                     }
                     PrimitiveKind::YuvImage => {
                         let image_yuv_cpu = &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
 
                         let get_buffer_kind = |texture: SourceTexture| {
                             match texture {
                                 SourceTexture::External(ext_image) => {
                                     match ext_image.image_type {
@@ -599,29 +593,30 @@ impl AlphaRenderItem {
 
                         for rect_index in 0..box_shadow.rects.len() {
                             batch.add_instance(base_instance.build(rect_index as i32,
                                                                    cache_task_index.0 as i32));
                         }
                     }
                 }
             }
-            AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_address, z) => {
+            AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_handle, z) => {
                 let key = AlphaBatchKey::new(AlphaBatchKind::SplitComposite,
                                              AlphaBatchKeyFlags::empty(),
                                              BlendMode::PremultipliedAlpha,
                                              BatchTextures::no_texture());
                 let stacking_context = &ctx.stacking_context_store[sc_index.0];
                 let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
                 let source_task = render_tasks.get_task_index(&task_id, child_pass_index);
+                let gpu_address = gpu_handle.as_int(ctx.gpu_cache);
 
                 let instance = CompositePrimitiveInstance::new(task_index,
                                                                source_task,
                                                                RenderTaskIndex(0),
-                                                               gpu_address.0,
+                                                               gpu_address,
                                                                0,
                                                                z);
 
                 batch.add_instance(PrimitiveInstance::from(instance));
             }
         }
     }
 }
@@ -757,16 +752,17 @@ impl ClipBatcher {
     }
 }
 
 pub struct RenderTargetContext<'a> {
     pub stacking_context_store: &'a [StackingContext],
     pub clip_scroll_group_store: &'a [ClipScrollGroup],
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
+    pub gpu_cache: &'a GpuCache,
 }
 
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
     // cache allocator requires.
     page_allocator: TexturePage,
@@ -976,22 +972,21 @@ impl RenderTarget for ColorRenderTarget 
                     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(&ctx.resource_cache.gpu_cache);
+                                                .as_int(&ctx.gpu_cache);
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::BoxShadow => {
-                        let instance = SimplePrimitiveInstance::new(prim_index,
-                                                                    prim_address,
+                        let instance = SimplePrimitiveInstance::new(prim_address,
                                                                     render_tasks.get_task_index(&task.id, pass_index),
                                                                     RenderTaskIndex(0),
                                                                     PackedLayerIndex(0),
                                                                     0);     // z is disabled for rendering cache primitives
                         self.box_shadow_cache_prims.push(instance.build(0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
@@ -1005,26 +1000,25 @@ impl RenderTarget for ColorRenderTarget 
                             colors: ctx.prim_store.get_color_textures(prim_metadata),
                         };
 
                         debug_assert!(textures.colors[0] != SourceTexture::Invalid);
                         debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
                                       self.text_run_textures.colors[0] == textures.colors[0]);
                         self.text_run_textures = textures;
 
-                        let instance = SimplePrimitiveInstance::new(prim_index,
-                                                                    prim_address,
+                        let instance = SimplePrimitiveInstance::new(prim_address,
                                                                     render_tasks.get_task_index(&task.id, pass_index),
                                                                     RenderTaskIndex(0),
                                                                     PackedLayerIndex(0),
                                                                     0);     // z is disabled for rendering cache primitives
 
-                        for glyph_index in 0..text.gpu_data_count {
-                            self.text_run_cache_prims.push(instance.build(text.gpu_data_address.0 + glyph_index,
-                                                                          text.resource_address.0 + glyph_index));
+                        for glyph_index in 0..text.glyph_instances.len() {
+                            self.text_run_cache_prims.push(instance.build(glyph_index as i32,
+                                                                          text.resource_address.0));
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
@@ -1302,58 +1296,55 @@ pub struct CacheClipInstance {
 
 // 32 bytes per instance should be enough for anyone!
 #[derive(Debug, Clone)]
 pub struct PrimitiveInstance {
     data: [i32; 8],
 }
 
 struct SimplePrimitiveInstance {
-    pub global_prim_index: i32,
     // TODO(gw): specific_prim_address is encoded as an i32, since
     //           some primitives use GPU Cache and some still use
     //           GPU Store. Once everything is converted to use the
     //           on-demand GPU cache, then we change change this to
     //           be an ivec2 of u16 - and encode the UV directly
     //           so that the vertex shader can fetch directly.
     pub specific_prim_address: i32,
     pub task_index: i32,
     pub clip_task_index: i32,
     pub layer_index: i32,
     pub z_sort_index: i32,
 }
 
 impl SimplePrimitiveInstance {
-    fn new(prim_index: PrimitiveIndex,
-           specific_prim_address: i32,
+    fn new(specific_prim_address: i32,
            task_index: RenderTaskIndex,
            clip_task_index: RenderTaskIndex,
            layer_index: PackedLayerIndex,
            z_sort_index: i32) -> SimplePrimitiveInstance {
         SimplePrimitiveInstance {
-            global_prim_index: prim_index.0 as i32,
             specific_prim_address: specific_prim_address,
             task_index: task_index.0 as i32,
             clip_task_index: clip_task_index.0 as i32,
             layer_index: layer_index.0 as i32,
             z_sort_index: z_sort_index,
         }
     }
 
     fn build(&self, data0: i32, data1: i32) -> PrimitiveInstance {
         PrimitiveInstance {
             data: [
-                self.global_prim_index,
                 self.specific_prim_address,
                 self.task_index,
                 self.clip_task_index,
                 self.layer_index,
                 self.z_sort_index,
                 data0,
                 data1,
+                0,
             ]
         }
     }
 }
 
 pub struct CompositePrimitiveInstance {
     pub task_index: RenderTaskIndex,
     pub src_task_index: RenderTaskIndex,
@@ -1450,17 +1441,17 @@ pub enum ContextIsolation {
 }
 
 #[derive(Debug)]
 pub struct StackingContext {
     pub pipeline_id: PipelineId,
 
     /// Offset in the parent reference frame to the origin of this stacking
     /// context's coordinate system.
-    pub reference_frame_offset: LayerPoint,
+    pub reference_frame_offset: LayerVector2D,
 
     /// The `ClipId` of the owning reference frame.
     pub reference_frame_id: ClipId,
 
     /// Local bounding rectangle for this stacking context.
     pub local_bounds: LayerRect,
 
     /// Screen space bounding rectangle for this stacking context,
@@ -1479,17 +1470,17 @@ pub struct StackingContext {
 
     /// Whether or not this stacking context has any visible components, calculated
     /// based on the size and position of all children and how they are clipped.
     pub is_visible: bool,
 }
 
 impl StackingContext {
     pub fn new(pipeline_id: PipelineId,
-               reference_frame_offset: LayerPoint,
+               reference_frame_offset: LayerVector2D,
                is_page_root: bool,
                reference_frame_id: ClipId,
                local_bounds: LayerRect,
                transform_style: TransformStyle,
                composite_ops: CompositeOps)
                -> StackingContext {
         let isolation = match transform_style {
             TransformStyle::Flat => ContextIsolation::None,
@@ -1607,19 +1598,18 @@ impl CompositeOps {
     }
 
     pub fn count(&self) -> usize {
         self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
     }
 
     pub fn will_make_invisible(&self) -> bool {
         for op in &self.filters {
-            match op {
-                &LowLevelFilterOp::Opacity(Au(0)) => return true,
-                _ => {}
+            if op == &LowLevelFilterOp::Opacity(Au(0)) {
+                return true;
             }
         }
         false
     }
 }
 
 impl Default for CompositeOps {
     fn default() -> CompositeOps {
@@ -1637,21 +1627,17 @@ pub struct Frame {
     pub background_color: Option<ColorF>,
     pub device_pixel_ratio: f32,
     pub cache_size: DeviceUintSize,
     pub passes: Vec<RenderPass>,
     pub profile_counters: FrameProfileCounters,
 
     pub layer_texture_data: Vec<PackedLayer>,
     pub render_task_data: Vec<RenderTaskData>,
-    pub gpu_data16: Vec<GpuBlock16>,
     pub gpu_data32: Vec<GpuBlock32>,
-    pub gpu_geometry: Vec<PrimitiveGeometry>,
-    pub gpu_gradient_data: Vec<GradientData>,
-    pub gpu_split_geometry: Vec<SplitGeometry>,
     pub gpu_resource_rects: Vec<TexelRect>,
 
     // List of updates that need to be pushed to the
     // gpu resource cache.
     pub gpu_cache_updates: Option<GpuCacheUpdateList>,
 
     // List of textures that we don't know about yet
     // from the backend thread. The render thread
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,41 +1,41 @@
 /* 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::f32::consts::{FRAC_1_SQRT_2};
 use euclid::{Point2D, Rect, Size2D};
-use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedMatrix4D};
+use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedTransform3D};
 use webrender_traits::{DeviceIntRect, DeviceIntPoint, DeviceIntSize};
-use webrender_traits::{LayerRect, WorldPoint4D, LayerPoint4D, LayerToWorldTransform};
+use webrender_traits::{LayerRect, WorldPoint3D, LayerToWorldTransform};
 use webrender_traits::{BorderRadius, ComplexClipRegion, LayoutRect};
 use num_traits::Zero;
 
 // Matches the definition of SK_ScalarNearlyZero in Skia.
 const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst>;
     fn is_identity(&self) -> bool;
     fn preserves_2d_axis_alignment(&self) -> bool;
 }
 
-impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedMatrix4D<f32, Src, Dst> {
+impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst> {
-        let top_left = self.transform_point(&rect.origin);
-        let top_right = self.transform_point(&rect.top_right());
-        let bottom_left = self.transform_point(&rect.bottom_left());
-        let bottom_right = self.transform_point(&rect.bottom_right());
+        let top_left = self.transform_point2d(&rect.origin);
+        let top_right = self.transform_point2d(&rect.top_right());
+        let bottom_left = self.transform_point2d(&rect.bottom_left());
+        let bottom_right = self.transform_point2d(&rect.bottom_right());
         TypedRect::from_points(&[top_left, top_right, bottom_right, bottom_left])
     }
 
     fn is_identity(&self) -> bool {
-        *self == TypedMatrix4D::identity()
+        *self == TypedTransform3D::identity()
     }
 
     // A port of the preserves2dAxisAlignment function in Skia.
     // Defined in the SkMatrix44 class.
     fn preserves_2d_axis_alignment(&self) -> bool {
         if self.m14 != 0.0 || self.m24 != 0.0 {
             return false;
         }
@@ -166,17 +166,17 @@ pub enum TransformedRectKind {
     Complex = 1,
 }
 
 #[derive(Debug, Clone)]
 pub struct TransformedRect {
     pub local_rect: LayerRect,
     pub bounding_rect: DeviceIntRect,
     pub inner_rect: DeviceIntRect,
-    pub vertices: [WorldPoint4D; 4],
+    pub vertices: [WorldPoint3D; 4],
     pub kind: TransformedRectKind,
 }
 
 impl TransformedRect {
     pub fn new(rect: &LayerRect,
                transform: &LayerToWorldTransform,
                device_pixel_ratio: f32) -> TransformedRect {
 
@@ -216,40 +216,27 @@ impl TransformedRect {
                     ],
                     bounding_rect: screen_rect_dp,
                     kind: kind,
                 }
             }
             TransformedRectKind::Complex => {
                 */
                 let vertices = [
-                    transform.transform_point4d(&LayerPoint4D::new(rect.origin.x,
-                                                                   rect.origin.y,
-                                                                   0.0,
-                                                                   1.0)),
-                    transform.transform_point4d(&LayerPoint4D::new(rect.bottom_left().x,
-                                                                   rect.bottom_left().y,
-                                                                   0.0,
-                                                                   1.0)),
-                    transform.transform_point4d(&LayerPoint4D::new(rect.bottom_right().x,
-                                                                   rect.bottom_right().y,
-                                                                   0.0,
-                                                                   1.0)),
-                    transform.transform_point4d(&LayerPoint4D::new(rect.top_right().x,
-                                                                   rect.top_right().y,
-                                                                   0.0,
-                                                                   1.0)),
+                    transform.transform_point3d(&rect.origin.to_3d()),
+                    transform.transform_point3d(&rect.bottom_left().to_3d()),
+                    transform.transform_point3d(&rect.bottom_right().to_3d()),
+                    transform.transform_point3d(&rect.top_right().to_3d()),
                 ];
 
                 let (mut xs, mut ys) = ([0.0; 4], [0.0; 4]);
 
                 for (vertex, (x, y)) in vertices.iter().zip(xs.iter_mut().zip(ys.iter_mut())) {
-                    let inv_w = 1.0 / vertex.w;
-                    *x = get_normal(vertex.x * inv_w).unwrap_or(0.0);
-                    *y = get_normal(vertex.y * inv_w).unwrap_or(0.0);
+                    *x = get_normal(vertex.x).unwrap_or(0.0);
+                    *y = get_normal(vertex.y).unwrap_or(0.0);
                 }
 
                 xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
                 ys.sort_by(|a, b| a.partial_cmp(b).unwrap());
 
                 let outer_min_dp = DeviceIntPoint::new((xs[0] * device_pixel_ratio).floor() as i32,
                                                        (ys[0] * device_pixel_ratio).floor() as i32);
                 let outer_max_dp = DeviceIntPoint::new((xs[3] * device_pixel_ratio).ceil() as i32,
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -3,16 +3,16 @@ name = "webrender_bindings"
 version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
 webrender_traits = {path = "../webrender_traits", version = "0.40.0"}
 rayon = {version = "0.7", features = ["unstable"]}
 thread_profiler = "0.1.1"
-euclid = "0.13"
+euclid = "0.14.4"
 app_units = "0.4"
 gleam = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
 version = "0.40.0"
 default-features = false
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -8,17 +8,18 @@ use gleam::gl;
 
 use webrender_traits::*;
 use webrender::renderer::{ReadPixelsFormat, Renderer, RendererOptions};
 use webrender::renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource};
 use webrender::{ApiRecordingReceiver, BinaryRecorder};
 use thread_profiler::register_thread_with_profiler;
 use moz2d_renderer::Moz2dImageRenderer;
 use app_units::Au;
-use euclid::{TypedPoint2D, TypedSize2D, TypedRect, TypedMatrix4D, SideOffsets2D};
+use euclid::{TypedPoint2D, TypedSize2D, TypedRect, TypedTransform3D, SideOffsets2D};
+use euclid::TypedVector2D;
 use rayon;
 
 extern crate webrender_traits;
 
 // Enables binary recording that can be used with `wrench replay`
 // Outputs a wr-record-*.bin file for each window that is shown
 // Note: wrench will panic if external images are used, they can
 // be disabled in WebRenderBridgeParent::ProcessWebRenderCommands
@@ -172,16 +173,22 @@ pub struct WrPoint {
 }
 
 impl<U> Into<TypedPoint2D<f32, U>> for WrPoint {
     fn into(self) -> TypedPoint2D<f32, U> {
         TypedPoint2D::new(self.x, self.y)
     }
 }
 
+impl<U> Into<TypedVector2D<f32, U>> for WrPoint {
+    fn into(self) -> TypedVector2D<f32, U> {
+        TypedVector2D::new(self.x, self.y)
+    }
+}
+
 #[repr(C)]
 #[derive(Debug, Clone, Copy)]
 pub struct WrSize {
     width: f32,
     height: f32,
 }
 
 impl WrSize {
@@ -227,54 +234,54 @@ impl<U> From<TypedRect<f32, U>> for WrRe
 }
 
 #[repr(C)]
 #[derive(Debug, Clone, Copy)]
 pub struct WrMatrix {
     values: [f32; 16],
 }
 
-impl<'a, U, E> Into<TypedMatrix4D<f32, U, E>> for &'a WrMatrix {
-    fn into(self) -> TypedMatrix4D<f32, U, E> {
-        TypedMatrix4D::row_major(self.values[0],
-                                 self.values[1],
-                                 self.values[2],
-                                 self.values[3],
-                                 self.values[4],
-                                 self.values[5],
-                                 self.values[6],
-                                 self.values[7],
-                                 self.values[8],
-                                 self.values[9],
-                                 self.values[10],
-                                 self.values[11],
-                                 self.values[12],
-                                 self.values[13],
-                                 self.values[14],
-                                 self.values[15])
+impl<'a, U, E> Into<TypedTransform3D<f32, U, E>> for &'a WrMatrix {
+    fn into(self) -> TypedTransform3D<f32, U, E> {
+        TypedTransform3D::row_major(self.values[0],
+                                    self.values[1],
+                                    self.values[2],
+                                    self.values[3],
+                                    self.values[4],
+                                    self.values[5],
+                                    self.values[6],
+                                    self.values[7],
+                                    self.values[8],
+                                    self.values[9],
+                                    self.values[10],
+                                    self.values[11],
+                                    self.values[12],
+                                    self.values[13],
+                                    self.values[14],
+                                    self.values[15])
     }
 }
-impl<U, E> Into<TypedMatrix4D<f32, U, E>> for WrMatrix {
-    fn into(self) -> TypedMatrix4D<f32, U, E> {
-        TypedMatrix4D::row_major(self.values[0],
-                                 self.values[1],
-                                 self.values[2],
-                                 self.values[3],
-                                 self.values[4],
-                                 self.values[5],
-                                 self.values[6],
-                                 self.values[7],
-                                 self.values[8],
-                                 self.values[9],
-                                 self.values[10],
-                                 self.values[11],
-                                 self.values[12],
-                                 self.values[13],
-                                 self.values[14],
-                                 self.values[15])
+impl<U, E> Into<TypedTransform3D<f32, U, E>> for WrMatrix {
+    fn into(self) -> TypedTransform3D<f32, U, E> {
+        TypedTransform3D::row_major(self.values[0],
+                                    self.values[1],
+                                    self.values[2],
+                                    self.values[3],
+                                    self.values[4],
+                                    self.values[5],
+                                    self.values[6],
+                                    self.values[7],
+                                    self.values[8],
+                                    self.values[9],
+                                    self.values[10],
+                                    self.values[11],
+                                    self.values[12],
+                                    self.values[13],
+                                    self.values[14],
+                                    self.values[15])
     }
 }
 
 #[repr(C)]
 #[derive(Debug, Clone, Copy)]
 pub struct WrColor {
     r: f32,
     g: f32,
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -27,20 +27,20 @@ impl BlobImageRenderer for Moz2dImageRen
         self.blob_commands.insert(key, Arc::new(data));
     }
 
     fn delete(&mut self, key: ImageKey) {
         self.blob_commands.remove(&key);
     }
 
     fn request(&mut self,
+               _resources: &BlobImageResources,
                request: BlobImageRequest,
                descriptor: &BlobImageDescriptor,
-               _dirty_rect: Option<DeviceUintRect>,
-               _images: &ImageStore) {
+               _dirty_rect: Option<DeviceUintRect>) {
         debug_assert!(!self.rendered_images.contains_key(&request));
         // TODO: implement tiling.
         assert!(request.tile.is_none());
 
         // Add None in the map of rendered images. This makes it possible to differentiate
         // between commands that aren't finished yet (entry in the map is equal to None) and
         // keys that have never been requested (entry not in the map), which would cause deadlocks
         // if we were to block upon receving their result in resolve!
@@ -100,16 +100,18 @@ impl BlobImageRenderer for Moz2dImageRen
                 return result
             }
             self.rendered_images.insert(req, Some(result));
         }
 
         // If we break out of the loop above it means the channel closed unexpectedly.
         Err(BlobImageError::Other("Channel closed".into()))
     }
+    fn delete_font(&mut self, _font: FontKey) {
+    }
 }
 
 impl Moz2dImageRenderer {
     pub fn new(workers: Arc<ThreadPool>) -> Self {
         let (tx, rx) = channel();
         Moz2dImageRenderer {
             blob_commands: HashMap::new(),
             rendered_images: HashMap::new(),
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -9,21 +9,21 @@ repository = "https://github.com/servo/w
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 webgl = ["offscreen_gl_context"]
 
 [dependencies]
 app_units = "0.4"
 bincode = "1.0.0-alpha2"
 byteorder = "1.0"
-euclid = "0.13"
+euclid = "0.14.4"
 gleam = "0.4.5"
 heapsize = ">= 0.3.6, < 0.5"
 ipc-channel = {version = "0.7.2", optional = true}
-offscreen_gl_context = {version = "0.8", features = ["serde"], optional = true}
+offscreen_gl_context = {version = "0.9", features = ["serde"], optional = true}
 serde = "0.9"
 serde_derive = "0.9"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.3"
 core-graphics = "0.7"
 
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -5,17 +5,17 @@
 use channel::{self, MsgSender, Payload, PayloadSenderHelperMethods, PayloadSender};
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceIntSize};
 use {DeviceUintRect, DeviceUintSize, FontKey, GlyphDimensions, GlyphKey};
-use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize, LayoutTransform};
+use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutVector2D, LayoutSize, LayoutTransform};
 use {NativeFontHandle, WorldPoint};
 #[cfg(feature = "webgl")]
 use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
@@ -456,23 +456,23 @@ pub enum ScrollEventPhase {
     Move(bool),
     /// The user ended scrolling.
     End,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct ScrollLayerState {
     pub id: ClipId,
-    pub scroll_offset: LayoutPoint,
+    pub scroll_offset: LayoutVector2D,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub enum ScrollLocation {
     /// Scroll by a certain amount.
-    Delta(LayoutPoint),
+    Delta(LayoutVector2D),
     /// Scroll to very top of element.
     Start,
     /// Scroll to very bottom of element.
     End
 }
 
 /// Represents a zoom factor.
 #[derive(Clone, Copy, Serialize, Deserialize, Debug)]
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -1,16 +1,16 @@
 /* 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 euclid::SideOffsets2D;
 use {ColorF, FontKey, ImageKey, ItemRange, PipelineId, WebGLContextId};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
+use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use {PropertyBinding};
 
 // NOTE: some of these structs have an "IMPLICIT" comment.
 // This indicates that the BuiltDisplayList will have serialized
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -202,17 +202,17 @@ pub enum BoxShadowClipMode {
     None    = 0,
     Outset  = 1,
     Inset   = 2,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BoxShadowDisplayItem {
     pub box_bounds: LayoutRect,
-    pub offset: LayoutPoint,
+    pub offset: LayoutVector2D,
     pub color: ColorF,
     pub blur_radius: f32,
     pub spread_radius: f32,
     pub border_radius: f32,
     pub clip_mode: BoxShadowClipMode,
 }
 
 #[repr(u32)]
@@ -320,16 +320,33 @@ pub enum FilterOp {
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>),
     Saturate(f32),
     Sepia(f32),
 }
 
+impl FilterOp {
+    pub fn is_noop(&self) -> bool {
+        match *self {
+            FilterOp::Blur(length) if length == Au(0) => true,
+            FilterOp::Brightness(amount) if amount == 1.0 => true,
+            FilterOp::Contrast(amount) if amount == 1.0 => true,
+            FilterOp::Grayscale(amount) if amount == 0.0 => true,
+            FilterOp::HueRotate(amount) if amount == 0.0 => true,
+            FilterOp::Invert(amount) if amount == 0.0 => true,
+            FilterOp::Opacity(amount) if amount == PropertyBinding::Value(1.0) => true,
+            FilterOp::Saturate(amount) if amount == 1.0 => true,
+            FilterOp::Sepia(amount) if amount == 0.0 => true,
+            _ => false,
+        }
+    }
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
     pub pipeline_id: PipelineId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDisplayItem {
     pub image_key: ImageKey,
@@ -561,50 +578,43 @@ impl ComplexClipRegion {
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ClipId {
     Clip(u64, PipelineId),
     ClipExternalId(u64, PipelineId),
-    ReferenceFrame(u64, PipelineId),
+    DynamicallyAddedNode(u64, PipelineId),
 }
 
 impl ClipId {
     pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
         ClipId::Clip(0, pipeline_id)
     }
 
     pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
-        ClipId::ReferenceFrame(0, pipeline_id)
+        ClipId::DynamicallyAddedNode(0, pipeline_id)
     }
 
     pub fn new(id: u64, pipeline_id: PipelineId) -> ClipId {
         // We do this because it is very easy to create accidentally create something that
         // seems like a root scroll node, but isn't one.
         if id == 0 {
             return ClipId::root_scroll_node(pipeline_id);
         }
 
         ClipId::ClipExternalId(id, pipeline_id)
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
             ClipId::Clip(_, pipeline_id) |
             ClipId::ClipExternalId(_, pipeline_id) |
-            ClipId::ReferenceFrame(_, pipeline_id) => pipeline_id,
-        }
-    }
-
-    pub fn is_reference_frame(&self) -> bool {
-        match *self {
-            ClipId::ReferenceFrame(..) => true,
-            _ => false,
+            ClipId::DynamicallyAddedNode(_, pipeline_id) => pipeline_id,
         }
     }
 
     pub fn external_id(&self) -> Option<u64> {
         match *self {
             ClipId::ClipExternalId(id, _) => Some(id),
             _ => None,
         }
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -10,17 +10,17 @@ use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ClipRegion, ColorF, ComplexClipRegion};
 use {DisplayItem, ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
 use {ImageRendering, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, MixBlendMode};
 use {PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
 use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
 use {StackingContext, TextDisplayItem, TransformStyle, WebGLContextId, WebGLDisplayItem};
-use {YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {YuvColorSpace, YuvData, YuvImageDisplayItem, LayoutVector2D};
 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>,
@@ -768,17 +768,17 @@ impl DisplayListBuilder {
 
         self.push_item(item, rect);
     }
 
     pub fn push_box_shadow(&mut self,
                            rect: LayoutRect,
                            _token: ClipRegionToken,
                            box_bounds: LayoutRect,
-                           offset: LayoutPoint,
+                           offset: LayoutVector2D,
                            color: ColorF,
                            blur_radius: f32,
                            spread_radius: f32,
                            border_radius: f32,
                            clip_mode: BoxShadowClipMode) {
         let item = SpecificDisplayItem::BoxShadow(BoxShadowDisplayItem {
             box_bounds: box_bounds,
             offset: offset,
--- a/gfx/webrender_traits/src/font.rs
+++ b/gfx/webrender_traits/src/font.rs
@@ -1,14 +1,15 @@
 /* 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 {ColorU, ColorF, LayoutPoint};
+use std::sync::Arc;
 
 #[cfg(target_os = "macos")] use core_foundation::string::CFString;
 #[cfg(target_os = "macos")] use core_graphics::font::CGFont;
 #[cfg(target_os = "macos")] use serde::de::{self, Deserialize, Deserializer};
 #[cfg(target_os = "macos")] use serde::ser::{Serialize, Serializer};
 #[cfg(target_os = "windows")] use dwrote::FontDescriptor;
 
 
@@ -58,16 +59,23 @@ pub struct GlyphDimensions {
 pub struct FontKey(pub u32, pub u32);
 
 impl FontKey {
     pub fn new(key0: u32, key1: u32) -> FontKey {
         FontKey(key0, key1)
     }
 }
 
+
+#[derive(Clone)]
+pub enum FontTemplate {
+    Raw(Arc<Vec<u8>>, u32),
+    Native(NativeFontHandle),
+}
+
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono,
     Alpha,
     Subpixel,
 }
 
 const FIXED16_SHIFT: i32 = 16;
--- a/gfx/webrender_traits/src/image.rs
+++ b/gfx/webrender_traits/src/image.rs
@@ -1,15 +1,16 @@
 /* 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::sync::Arc;
 use {DeviceUintRect, DevicePoint};
 use {TileOffset, TileSize};
+use font::{FontKey, FontTemplate};
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub u32, pub u32);
 
 impl ImageKey {
     pub fn new(key0: u32, key1: u32) -> ImageKey {
         ImageKey(key0, key1)
@@ -130,30 +131,37 @@ impl ImageData {
                 }
             }
             &ImageData::Blob(_) => true,
             &ImageData::Raw(_) => true,
         }
     }
 }
 
+pub trait BlobImageResources {
+    fn get_font_data(&self, key: FontKey) -> &FontTemplate;
+    fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)>;
+}
+
 pub trait BlobImageRenderer: Send {
     fn add(&mut self, key: ImageKey, data: BlobImageData, tiling: Option<TileSize>);
 
     fn update(&mut self, key: ImageKey, data: BlobImageData);
 
     fn delete(&mut self, key: ImageKey);
 
     fn request(&mut self,
+               services: &BlobImageResources,
                key: BlobImageRequest,
                descriptor: &BlobImageDescriptor,
-               dirty_rect: Option<DeviceUintRect>,
-               images: &ImageStore);
+               dirty_rect: Option<DeviceUintRect>);
 
     fn resolve(&mut self, key: BlobImageRequest) -> BlobImageResult;
+
+    fn delete_font(&mut self, key: FontKey);
 }
 
 pub type BlobImageData = Vec<u8>;
 
 pub type BlobImageResult = Result<RasterizedBlobImage, BlobImageError>;
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug)]
@@ -178,12 +186,8 @@ pub enum BlobImageError {
     Other(String),
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct BlobImageRequest {
     pub key: ImageKey,
     pub tile: Option<TileOffset>,
 }
-
-pub trait ImageStore {
-    fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)>;
-}
--- a/gfx/webrender_traits/src/units.rs
+++ b/gfx/webrender_traits/src/units.rs
@@ -7,18 +7,18 @@
 //! Physical pixels take into account the device pixel ratio and their dimensions tend
 //! to correspond to the allocated size of resources in memory, while logical pixels
 //! don't have the device pixel ratio applied which means they are agnostic to the usage
 //! of hidpi screens and the like.
 //!
 //! The terms "layer" and "stacking context" can be used interchangeably
 //! in the context of coordinate systems.
 
-use euclid::{Length, TypedMatrix4D, TypedRect, TypedSize2D};
-use euclid::{TypedPoint2D, TypedPoint3D, TypedPoint4D};
+use euclid::{Length, TypedTransform3D, TypedRect, TypedSize2D};
+use euclid::{TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D};
 
 /// Geometry in the coordinate system of the render target (screen or intermediate
 /// surface) in physical pixels.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct DevicePixel;
 
 pub type DeviceIntRect = TypedRect<i32, DevicePixel>;
 pub type DeviceIntPoint = TypedPoint2D<i32, DevicePixel>;
@@ -26,74 +26,85 @@ pub type DeviceIntSize = TypedSize2D<i32
 pub type DeviceIntLength = Length<i32, DevicePixel>;
 
 pub type DeviceUintRect = TypedRect<u32, DevicePixel>;
 pub type DeviceUintPoint = TypedPoint2D<u32, DevicePixel>;
 pub type DeviceUintSize = TypedSize2D<u32, DevicePixel>;
 
 pub type DeviceRect = TypedRect<f32, DevicePixel>;
 pub type DevicePoint = TypedPoint2D<f32, DevicePixel>;
+pub type DeviceVector2D = TypedVector2D<f32, DevicePixel>;
 pub type DeviceSize = TypedSize2D<f32, DevicePixel>;
 
 /// Geometry in a stacking context's local coordinate space (logical pixels).
 ///
 /// For now layout pixels are equivalent to layer pixels, but it may change.
 pub type LayoutPixel = LayerPixel;
 
 pub type LayoutRect = LayerRect;
 pub type LayoutPoint = LayerPoint;
+pub type LayoutVector2D = LayerVector2D;
+pub type LayoutVector3D = LayerVector3D;
 pub type LayoutSize = LayerSize;
 
 /// Geometry in a layer's local coordinate space (logical pixels).
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct LayerPixel;
 
 pub type LayerRect = TypedRect<f32, LayerPixel>;
 pub type LayerPoint = TypedPoint2D<f32, LayerPixel>;
+pub type LayerPoint3D = TypedPoint3D<f32, LayerPixel>;
+pub type LayerVector2D = TypedVector2D<f32, LayerPixel>;
+pub type LayerVector3D = TypedVector3D<f32, LayerPixel>;
 pub type LayerSize = TypedSize2D<f32, LayerPixel>;
-pub type LayerPoint4D = TypedPoint4D<f32, LayerPixel>;
 
 /// Geometry in a layer's scrollable parent coordinate space (logical pixels).
 ///
 /// Some layers are scrollable while some are not. There is a distinction between
 /// a layer's parent layer and a layer's scrollable parent layer (its closest parent
 /// that is scrollable, but not necessarily its immediate parent). Most of the internal
 /// transforms are expressed in terms of the scrollable parent and not the immediate
 /// parent.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct ScrollLayerPixel;
 
 pub type ScrollLayerRect = TypedRect<f32, ScrollLayerPixel>;
 pub type ScrollLayerPoint = TypedPoint2D<f32, ScrollLayerPixel>;
+pub type ScrollLayerVector2D = TypedVector2D<f32, ScrollLayerPixel>;
 pub type ScrollLayerSize = TypedSize2D<f32, ScrollLayerPixel>;
 
 /// Geometry in the document's coordinate space (logical pixels).
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct WorldPixel;
 
 pub type WorldRect = TypedRect<f32, WorldPixel>;
 pub type WorldPoint = TypedPoint2D<f32, WorldPixel>;
 pub type WorldSize = TypedSize2D<f32, WorldPixel>;
 pub type WorldPoint3D = TypedPoint3D<f32, WorldPixel>;
-pub type WorldPoint4D = TypedPoint4D<f32, WorldPixel>;
+pub type WorldVector2D = TypedVector2D<f32, WorldPixel>;
+pub type WorldVector3D = TypedVector3D<f32, WorldPixel>;
 
 /// Offset in number of tiles.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct Tiles;
 pub type TileOffset = TypedPoint2D<u16, Tiles>;
 
-pub type LayoutTransform = TypedMatrix4D<f32, LayoutPixel, LayoutPixel>;
-pub type LayerTransform = TypedMatrix4D<f32, LayerPixel, LayerPixel>;
-pub type LayerToScrollTransform = TypedMatrix4D<f32, LayerPixel, ScrollLayerPixel>;
-pub type ScrollToLayerTransform = TypedMatrix4D<f32, ScrollLayerPixel, LayerPixel>;
-pub type LayerToWorldTransform = TypedMatrix4D<f32, LayerPixel, WorldPixel>;
-pub type WorldToLayerTransform = TypedMatrix4D<f32, WorldPixel, LayerPixel>;
-pub type ScrollToWorldTransform = TypedMatrix4D<f32, ScrollLayerPixel, WorldPixel>;
+pub type LayoutTransform = TypedTransform3D<f32, LayoutPixel, LayoutPixel>;
+pub type LayerTransform = TypedTransform3D<f32, LayerPixel, LayerPixel>;
+pub type LayerToScrollTransform = TypedTransform3D<f32, LayerPixel, ScrollLayerPixel>;
+pub type ScrollToLayerTransform = TypedTransform3D<f32, ScrollLayerPixel, LayerPixel>;
+pub type LayerToWorldTransform = TypedTransform3D<f32, LayerPixel, WorldPixel>;
+pub type WorldToLayerTransform = TypedTransform3D<f32, WorldPixel, LayerPixel>;
+pub type ScrollToWorldTransform = TypedTransform3D<f32, ScrollLayerPixel, WorldPixel>;
 
 
 pub fn device_length(value: f32, device_pixel_ratio: f32) -> DeviceIntLength {
     DeviceIntLength::new((value * device_pixel_ratio).round() as i32)
 }
 
 pub fn as_scroll_parent_rect(rect: &LayerRect) -> ScrollLayerRect {
     ScrollLayerRect::from_untyped(&rect.to_untyped())
 }
 
+pub fn as_scroll_parent_vector(vector: &LayerVector2D) -> ScrollLayerVector2D {
+    ScrollLayerVector2D::from_untyped(&vector.to_untyped())
+}
+
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"118514fd9c4958df0d25584cda4917186c46011569f55ef350530c1ad3fbdb48",".travis.yml":"13d3e5a7bf83b04c8e8cfa14f0297bd8366d68391d977dd547f64707dffc275a","COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"c91c98dc9510ef29a7ce0d7c78294f15cb139c9afecca38e5fda56b0a6984954","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"625bec69c76ce5423fdd05cfe46922b2680ec517f97c5854ce34798d1d8a9541","src/approxeq.rs":"6cf810ad389c73a27141a7a67454ed12d4b01c3c16605b9a7414b389bc0615dd","src/length.rs":"d7c6369f2fe2a17c845b57749bd48c471159f0571a7314d3bf90737d53f697d3","src/lib.rs":"e2e621f05304278d020429d0349acf7a4e7c7a9a72bd23fc0e55680267472ee9","src/macros.rs":"b63dabdb52df84ea170dc1dab5fe8d7a78c054562d1566bab416124708d2d7af","src/matrix2d.rs":"2361338f59813adf4eebaab76e4dd82be0fbfb9ff2461da8dd9ac9d43583b322","src/matrix4d.rs":"b8547bed6108b037192021c97169c00ad456120b849e9b7ac7bec40363edaec1","src/num.rs":"749b201289fc6663199160a2f9204e17925fd3053f8ab7779e7bfb377ad06227","src/point.rs":"dbf12a3ad35dc2502b7f2637856d8ee40f5a96e37ed00f3ee3272bf5752c060c","src/rect.rs":"0a255046dd11a6093d9a77e327e1df31802808536b4d87e4e3b80ff6b208de0f","src/scale_factor.rs":"df6dbd1f0f9f63210b92809f84a383dad982a74f09789cf22c7d8f9b62199d39","src/side_offsets.rs":"f85526a421ffda63ff01a3478d4162c8717eef68e942acfa2fd9a1adee02ebb2","src/size.rs":"ae1b647e300600b50a21dba8c1d915801782ebae82baeb5e49017e6d68a49b28","src/trig.rs":"ef290927af252ca90a29ba9f17158b591ed591604e66cb9df045dd47b9cfdca5"},"package":"6083f113c422ff9cd855a1cf6cc8ec0903606c0eb43a0c6a0ced3bdc9731e4c1"}
\ No newline at end of file
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+/target/
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/.travis.yml
@@ -0,0 +1,19 @@
+language: rust
+
+notifications:
+  webhooks: http://build.servo.org:54856/travis
+
+matrix:
+  include:
+    - rust: stable
+      env: FEATURES=""
+    - rust: beta
+      env: FEATURES=""
+    - rust: nightly
+      env: FEATURES=""
+    - rust: nightly
+      env: FEATURES="unstable"
+
+script:
+  - cargo build --verbose --features "$FEATURES"
+  - cargo test --verbose --features "$FEATURES"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/COPYRIGHT
@@ -0,0 +1,5 @@
+Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+option. All files in the project carrying such notice may not be
+copied, modified, or distributed except according to those terms.
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "euclid"
+version = "0.13.0"
+authors = ["The Servo Project Developers"]
+description = "Geometry primitives"
+documentation = "https://docs.rs/euclid/"
+repository = "https://github.com/servo/euclid"
+keywords = ["matrix", "vector", "linear-algebra", "geometry"]
+categories = ["science"]
+license = "MIT / Apache-2.0"
+
+[features]
+unstable = []
+
+[dependencies]
+heapsize = "0.4"
+rustc-serialize = "0.3.2"
+num-traits = {version = "0.1.32", default-features = false}
+log = "0.3.1"
+serde = "0.9"
+
+[dev-dependencies]
+rand = "0.3.7"
+serde_test = "0.9"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2012-2013 Mozilla Foundation
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/README.md
@@ -0,0 +1,8 @@
+# euclid
+
+This is a small library for geometric types with a focus on 2d graphics and
+layout.
+
+* [Documentation](https://docs.rs/euclid/)
+* [Release notes](https://github.com/servo/euclid/releases)
+* [crates.io](https://crates.io/crates/euclid)
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/src/approxeq.rs
@@ -0,0 +1,36 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+
+/// Trait for testing approximate equality
+pub trait ApproxEq<Eps> {
+    fn approx_epsilon() -> Eps;
+    fn approx_eq(&self, other: &Self) -> bool;
+    fn approx_eq_eps(&self, other: &Self, approx_epsilon: &Eps) -> bool;
+}
+
+macro_rules! approx_eq {
+    ($ty:ty, $eps:expr) => (
+        impl ApproxEq<$ty> for $ty {
+            #[inline]
+            fn approx_epsilon() -> $ty { $eps }
+            #[inline]
+            fn approx_eq(&self, other: &$ty) -> bool {
+                self.approx_eq_eps(other, &$eps)
+            }
+            #[inline]
+            fn approx_eq_eps(&self, other: &$ty, approx_epsilon: &$ty) -> bool {
+                (*self - *other).abs() < *approx_epsilon
+            }
+        }
+    )
+}
+
+approx_eq!(f32, 1.0e-6);
+approx_eq!(f64, 1.0e-6);
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/src/length.rs
@@ -0,0 +1,449 @@
+// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//! A one-dimensional length, tagged with its units.
+
+use scale_factor::ScaleFactor;
+use num::Zero;
+
+use heapsize::HeapSizeOf;
+use num_traits::{NumCast, Saturating};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use std::cmp::Ordering;
+use std::ops::{Add, Sub, Mul, Div, Neg};
+use std::ops::{AddAssign, SubAssign};
+use std::marker::PhantomData;
+use std::fmt;
+
+/// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`.
+///
+/// `T` can be any numeric type, for example a primitive type like `u64` or `f32`.
+///
+/// `Unit` is not used in the representation of a `Length` value. It is used only at compile time
+/// to ensure that a `Length` stored with one unit is converted explicitly before being used in an
+/// expression that requires a different unit.  It may be a type without values, such as an empty
+/// enum.
+///
+/// You can multiply a `Length` by a `scale_factor::ScaleFactor` to convert it from one unit to
+/// another. See the `ScaleFactor` docs for an example.
+// Uncomment the derive, and remove the macro call, once heapsize gets
+// PhantomData<T> support.
+#[repr(C)]
+#[derive(RustcDecodable, RustcEncodable)]
+pub struct Length<T, Unit>(pub T, PhantomData<Unit>);
+
+impl<T: Clone, Unit> Clone for Length<T, Unit> {
+    fn clone(&self) -> Self {
+        Length(self.0.clone(), PhantomData)
+    }
+}
+
+impl<T: Copy, Unit> Copy for Length<T, Unit> {}
+
+impl<Unit, T: HeapSizeOf> HeapSizeOf for Length<T, Unit> {
+    fn heap_size_of_children(&self) -> usize {
+        self.0.heap_size_of_children()
+    }
+}
+
+impl<Unit, T> Deserialize for Length<T, Unit> where T: Deserialize {
+    fn deserialize<D>(deserializer: D) -> Result<Length<T, Unit>,D::Error>
+                      where D: Deserializer {
+        Ok(Length(try!(Deserialize::deserialize(deserializer)), PhantomData))
+    }
+}
+
+impl<T, Unit> Serialize for Length<T, Unit> where T: Serialize {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
+        self.0.serialize(serializer)
+    }
+}
+
+impl<T, Unit> Length<T, Unit> {
+    pub fn new(x: T) -> Length<T, Unit> {
+        Length(x, PhantomData)
+    }
+}
+
+impl<Unit, T: Clone> Length<T, Unit> {
+    pub fn get(&self) -> T {
+        self.0.clone()
+    }
+}
+
+impl<T: fmt::Debug + Clone, U> fmt::Debug for Length<T, U> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.get().fmt(f)
+    }
+}
+
+impl<T: fmt::Display + Clone, U> fmt::Display for Length<T, U> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.get().fmt(f)
+    }
+}
+
+// length + length
+impl<U, T: Clone + Add<T, Output=T>> Add for Length<T, U> {
+    type Output = Length<T, U>;
+    fn add(self, other: Length<T, U>) -> Length<T, U> {
+        Length::new(self.get() + other.get())
+    }
+}
+
+// length += length
+impl<U, T: Clone + AddAssign<T>> AddAssign for Length<T, U> {
+    fn add_assign(&mut self, other: Length<T, U>) {
+        self.0 += other.get();
+    }
+}
+
+// length - length
+impl<U, T: Clone + Sub<T, Output=T>> Sub<Length<T, U>> for Length<T, U> {
+    type Output = Length<T, U>;
+    fn sub(self, other: Length<T, U>) -> <Self as Sub>::Output {
+        Length::new(self.get() - other.get())
+    }
+}
+
+// length -= length
+impl<U, T: Clone + SubAssign<T>> SubAssign for Length<T, U> {
+    fn sub_assign(&mut self, other: Length<T, U>) {
+        self.0 -= other.get();
+    }
+}
+
+// Saturating length + length and length - length.
+impl<U, T: Clone + Saturating> Saturating for Length<T, U> {
+    fn saturating_add(self, other: Length<T, U>) -> Length<T, U> {
+        Length::new(self.get().saturating_add(other.get()))
+    }
+
+    fn saturating_sub(self, other: Length<T, U>) -> Length<T, U> {
+        Length::new(self.get().saturating_sub(other.get()))
+    }
+}
+
+// length / length
+impl<Src, Dst, T: Clone + Div<T, Output=T>> Div<Length<T, Src>> for Length<T, Dst> {
+    type Output = ScaleFactor<T, Src, Dst>;
+    #[inline]
+    fn div(self, other: Length<T, Src>) -> ScaleFactor<T, Src, Dst> {
+        ScaleFactor::new(self.get() / other.get())
+    }
+}
+
+// length * scaleFactor
+impl<Src, Dst, T: Clone + Mul<T, Output=T>> Mul<ScaleFactor<T, Src, Dst>> for Length<T, Src> {
+    type Output = Length<T, Dst>;
+    #[inline]
+    fn mul(self, scale: ScaleFactor<T, Src, Dst>) -> Length<T, Dst> {
+        Length::new(self.get() * scale.get())
+    }
+}
+
+// length / scaleFactor
+impl<Src, Dst, T: Clone + Div<T, Output=T>> Div<ScaleFactor<T, Src, Dst>> for Length<T, Dst> {
+    type Output = Length<T, Src>;
+    #[inline]
+    fn div(self, scale: ScaleFactor<T, Src, Dst>) -> Length<T, Src> {
+        Length::new(self.get() / scale.get())
+    }
+}
+
+// -length
+impl <U, T:Clone + Neg<Output=T>> Neg for Length<T, U> {
+    type Output = Length<T, U>;
+    #[inline]
+    fn neg(self) -> Length<T, U> {
+        Length::new(-self.get())
+    }
+}
+
+impl<Unit, T0: NumCast + Clone> Length<T0, Unit> {
+    /// Cast from one numeric representation to another, preserving the units.
+    pub fn cast<T1: NumCast + Clone>(&self) -> Option<Length<T1, Unit>> {
+        NumCast::from(self.get()).map(Length::new)
+    }
+}
+
+impl<Unit, T: Clone + PartialEq> PartialEq for Length<T, Unit> {
+    fn eq(&self, other: &Length<T, Unit>) -> bool { self.get().eq(&other.get()) }
+}
+
+impl<Unit, T: Clone + PartialOrd> PartialOrd for Length<T, Unit> {
+    fn partial_cmp(&self, other: &Length<T, Unit>) -> Option<Ordering> {
+        self.get().partial_cmp(&other.get())
+    }
+}
+
+impl<Unit, T: Clone + Eq> Eq for Length<T, Unit> {}
+
+impl<Unit, T: Clone + Ord> Ord for Length<T, Unit> {
+    fn cmp(&self, other: &Length<T, Unit>) -> Ordering { self.get().cmp(&other.get()) }
+}
+
+impl<Unit, T: Zero> Zero for Length<T, Unit> {
+    fn zero() -> Length<T, Unit> {
+        Length::new(Zero::zero())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Length;
+    use num::Zero;
+
+    use heapsize::HeapSizeOf;
+    use num_traits::Saturating;
+    use scale_factor::ScaleFactor;
+    use std::f32::INFINITY;
+
+    extern crate serde_test;
+    use self::serde_test::Token;
+    use self::serde_test::assert_tokens;
+
+    enum Inch {}
+    enum Mm {}
+    enum Cm {}
+    enum Second {}
+
+    #[test]
+    fn test_clone() {
+        // A cloned Length is a separate length with the state matching the
+        // original Length at the point it was cloned.
+        let mut variable_length: Length<f32, Inch> = Length::new(12.0);
+
+        let one_foot = variable_length.clone();
+        variable_length.0 = 24.0;
+
+        assert_eq!(one_foot.get(), 12.0);
+        assert_eq!(variable_length.get(), 24.0);
+    }
+
+    #[test]
+    fn test_heapsizeof_builtins() {
+        // Heap size of built-ins is zero by default.
+        let one_foot: Length<f32, Inch> = Length::new(12.0);
+
+        let heap_size_length_f32 = one_foot.heap_size_of_children();
+
+        assert_eq!(heap_size_length_f32, 0);
+    }
+
+    #[test]
+    fn test_heapsizeof_length_vector() {
+        // Heap size of any Length is just the heap size of the length value.
+        for n in 0..5 {
+            let length: Length<Vec<f32>, Inch> = Length::new(Vec::with_capacity(n));
+
+            assert_eq!(length.heap_size_of_children(), length.0.heap_size_of_children());
+        }
+    }
+
+    #[test]
+    fn test_length_serde() {
+        let one_cm: Length<f32, Mm> = Length::new(10.0);
+
+        assert_tokens(&one_cm, &[Token::F32(10.0)]);
+    }
+
+    #[test]
+    fn test_get_clones_length_value() {
+        // Calling get returns a clone of the Length's value.
+        // To test this, we need something clone-able - hence a vector.
+        let mut length: Length<Vec<i32>, Inch> = Length::new(vec![1, 2, 3]);
+
+        let value = length.get();
+        length.0.push(4);
+
+        assert_eq!(value, vec![1, 2, 3]);
+        assert_eq!(length.get(), vec![1, 2, 3, 4]);
+    }
+
+    #[test]
+    fn test_fmt_debug() {
+        // Debug and display format the value only.
+        let one_cm: Length<f32, Mm> = Length::new(10.0);
+
+        let result = format!("{:?}", one_cm);
+
+        assert_eq!(result, "10");
+    }
+
+    #[test]
+    fn test_fmt_display() {
+        // Debug and display format the value only.
+        let one_cm: Length<f32, Mm> = Length::new(10.0);
+
+        let result = format!("{}", one_cm);
+
+        assert_eq!(result, "10");
+    }
+
+    #[test]
+    fn test_add() {
+        let length1: Length<u8, Mm> = Length::new(250);
+        let length2: Length<u8, Mm> = Length::new(5);
+
+        let result = length1 + length2;
+
+        assert_eq!(result.get(), 255);
+    }
+
+    #[test]
+    fn test_addassign() {
+        let one_cm: Length<f32, Mm> = Length::new(10.0);
+        let mut measurement: Length<f32, Mm> = Length::new(5.0);
+
+        measurement += one_cm;
+
+        assert_eq!(measurement.get(), 15.0);
+    }
+
+    #[test]
+    fn test_sub() {
+        let length1: Length<u8, Mm> = Length::new(250);
+        let length2: Length<u8, Mm> = Length::new(5);
+
+        let result = length1 - length2;
+
+        assert_eq!(result.get(), 245);
+    }
+
+    #[test]
+    fn test_subassign() {
+        let one_cm: Length<f32, Mm> = Length::new(10.0);
+        let mut measurement: Length<f32, Mm> = Length::new(5.0);
+
+        measurement -= one_cm;
+
+        assert_eq!(measurement.get(), -5.0);
+    }
+
+    #[test]
+    fn test_saturating_add() {
+        let length1: Length<u8, Mm> = Length::new(250);
+        let length2: Length<u8, Mm> = Length::new(6);
+
+        let result = length1.saturating_add(length2);
+
+        assert_eq!(result.get(), 255);
+    }
+
+    #[test]
+    fn test_saturating_sub() {
+        let length1: Length<u8, Mm> = Length::new(5);
+        let length2: Length<u8, Mm> = Length::new(10);
+
+        let result = length1.saturating_sub(length2);
+
+        assert_eq!(result.get(), 0);
+    }
+
+    #[test]
+    fn test_division_by_length() {
+        // Division results in a ScaleFactor from denominator units
+        // to numerator units.
+        let length: Length<f32, Cm> = Length::new(5.0);
+        let duration: Length<f32, Second> = Length::new(10.0);
+
+        let result = length / duration;
+
+        let expected: ScaleFactor<f32, Second, Cm> = ScaleFactor::new(0.5);
+        assert_eq!(result, expected);
+    }
+
+    #[test]
+    fn test_multiplication() {
+        let length_mm: Length<f32, Mm> = Length::new(10.0);
+        let cm_per_mm: ScaleFactor<f32, Mm, Cm> = ScaleFactor::new(0.1);
+
+        let result = length_mm * cm_per_mm;
+
+        let expected: Length<f32, Cm> = Length::new(1.0);
+        assert_eq!(result, expected);
+    }
+
+    #[test]
+    fn test_division_by_scalefactor() {
+        let length: Length<f32, Cm> = Length::new(5.0);
+        let cm_per_second: ScaleFactor<f32, Second, Cm> = ScaleFactor::new(10.0);
+
+        let result = length / cm_per_second;
+
+        let expected: Length<f32, Second> = Length::new(0.5);
+        assert_eq!(result, expected);
+    }
+
+    #[test]
+    fn test_negation() {
+        let length: Length<f32, Cm> = Length::new(5.0);
+
+        let result = -length;
+
+        let expected: Length<f32, Cm> = Length::new(-5.0);
+        assert_eq!(result, expected);
+    }
+
+    #[test]
+    fn test_cast() {
+        let length_as_i32: Length<i32, Cm> = Length::new(5);
+
+        let result: Length<f32, Cm> = length_as_i32.cast().unwrap();
+
+        let length_as_f32: Length<f32, Cm> = Length::new(5.0);
+        assert_eq!(result, length_as_f32);
+    }
+
+    #[test]
+    fn test_equality() {
+        let length_5_point_0: Length<f32, Cm> = Length::new(5.0);
+        let length_5_point_1: Length<f32, Cm> = Length::new(5.1);
+        let length_0_point_1: Length<f32, Cm> = Length::new(0.1);
+
+        assert!(length_5_point_0 == length_5_point_1 - length_0_point_1);
+        assert!(length_5_point_0 != length_5_point_1);
+    }
+
+    #[test]
+    fn test_order() {
+        let length_5_point_0: Length<f32, Cm> = Length::new(5.0);
+        let length_5_point_1: Length<f32, Cm> = Length::new(5.1);
+        let length_0_point_1: Length<f32, Cm> = Length::new(0.1);
+
+        assert!(length_5_point_0 < length_5_point_1);
+        assert!(length_5_point_0 <= length_5_point_1);
+        assert!(length_5_point_0 <= length_5_point_1 - length_0_point_1);
+        assert!(length_5_point_1 > length_5_point_0);
+        assert!(length_5_point_1 >= length_5_point_0);
+        assert!(length_5_point_0 >= length_5_point_1 - length_0_point_1);
+    }
+
+    #[test]
+    fn test_zero_add() {
+        type LengthCm = Length<f32, Cm>;
+        let length: LengthCm = Length::new(5.0);
+
+        let result = length - LengthCm::zero();
+
+        assert_eq!(result, length);
+    }
+
+    #[test]
+    fn test_zero_division() {
+        type LengthCm = Length<f32, Cm>;
+        let length: LengthCm = Length::new(5.0);
+        let length_zero: LengthCm = Length::zero();
+
+        let result = length / length_zero;
+
+        let expected: ScaleFactor<f32, Cm, Cm> = ScaleFactor::new(INFINITY);
+        assert_eq!(result, expected);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/src/lib.rs
@@ -0,0 +1,113 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![cfg_attr(feature = "unstable", feature(asm, repr_simd, test))]
+
+//! A collection of strongly typed math tools for computer graphics with an inclination
+//! towards 2d graphics and layout.
+//!
+//! All types are generic over the scalar type of their component (`f32`, `i32`, etc.),
+//! and tagged with a generic Unit parameter which is useful to prevent mixing
+//! values from different spaces. For example it should not be legal to translate
+//! a screen-space position by a world-space vector and this can be expressed using
+//! the generic Unit parameter.
+//!
+//! This unit system is not mandatory and all Typed* structures have an alias
+//! with the default unit: `UnknownUnit`.
+//! for example ```Point2D<T>``` is equivalent to ```TypedPoint2D<T, UnknownUnit>```.
+//! Client code typically creates a set of aliases for each type and doesn't need
+//! to deal with the specifics of typed units further. For example:
+//!
+//! All euclid types are marked `#[repr(C)]` in order to facilitate exposing them to
+//! foreign function interfaces (provided the underlying scalar type is also `repr(C)`).
+//!
+//! ```rust
+//! use euclid::*;
+//! pub struct ScreenSpace;
+//! pub type ScreenPoint = TypedPoint2D<f32, ScreenSpace>;
+//! pub type ScreenSize = TypedSize2D<f32, ScreenSpace>;
+//! pub struct WorldSpace;
+//! pub type WorldPoint = TypedPoint3D<f32, WorldSpace>;
+//! pub type ProjectionMatrix = TypedMatrix4D<f32, WorldSpace, ScreenSpace>;
+//! // etc...
+//! ```
+//!
+//! Components are accessed in their scalar form by default for convenience, and most
+//! types additionally implement strongly typed accessors which return typed ```Length``` wrappers.
+//! For example:
+//!
+//! ```rust
+//! # use euclid::*;
+//! # pub struct WorldSpace;
+//! # pub type WorldPoint = TypedPoint3D<f32, WorldSpace>;
+//! let p = WorldPoint::new(0.0, 1.0, 1.0);
+//! // p.x is an f32.
+//! println!("p.x = {:?} ", p.x);
+//! // p.x is a Length<f32, WorldSpace>.
+//! println!("p.x_typed() = {:?} ", p.x_typed());
+//! // Length::get returns the scalar value (f32).
+//! assert_eq!(p.x, p.x_typed().get());
+//! ```
+
+extern crate heapsize;
+
+#[cfg_attr(test, macro_use)]
+extern crate log;
+extern crate rustc_serialize;
+extern crate serde;
+
+#[cfg(test)]
+extern crate rand;
+#[cfg(feature = "unstable")]
+extern crate test;
+extern crate num_traits;
+
+pub use length::Length;
+pub use scale_factor::ScaleFactor;
+pub use matrix2d::{Matrix2D, TypedMatrix2D};
+pub use matrix4d::{Matrix4D, TypedMatrix4D};
+pub use point::{
+    Point2D, TypedPoint2D,
+    Point3D, TypedPoint3D,
+    Point4D, TypedPoint4D,
+};
+pub use rect::{Rect, TypedRect};
+pub use side_offsets::{SideOffsets2D, TypedSideOffsets2D};
+#[cfg(feature = "unstable")] pub use side_offsets::SideOffsets2DSimdI32;
+pub use size::{Size2D, TypedSize2D};
+
+pub mod approxeq;
+pub mod length;
+#[macro_use]
+mod macros;
+pub mod matrix2d;
+pub mod matrix4d;
+pub mod num;
+pub mod point;
+pub mod rect;
+pub mod scale_factor;
+pub mod side_offsets;
+pub mod size;
+pub mod trig;
+
+/// The default unit.
+#[derive(Clone, Copy, RustcDecodable, RustcEncodable)]
+pub struct UnknownUnit;
+
+/// Unit for angles in radians.
+pub struct Rad;
+
+/// Unit for angles in degrees.
+pub struct Deg;
+
+/// A value in radians.
+pub type Radians<T> = Length<T, Rad>;
+
+/// A value in Degrees.
+pub type Degrees<T> = Length<T, Deg>;
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/src/macros.rs
@@ -0,0 +1,87 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+macro_rules! define_matrix {
+    (
+        $(#[$attr:meta])*
+        pub struct $name:ident<T, $($phantom:ident),+> {
+            $(pub $field:ident: T,)+
+        }
+    ) => (
+        #[repr(C)]
+        $(#[$attr])*
+        pub struct $name<T, $($phantom),+> {
+            $(pub $field: T,)+
+            _unit: PhantomData<($($phantom),+)>
+        }
+
+        impl<T: Clone, $($phantom),+> Clone for $name<T, $($phantom),+> {
+            fn clone(&self) -> Self {
+                $name {
+                    $($field: self.$field.clone(),)+
+                    _unit: PhantomData,
+                }
+            }
+        }
+
+        impl<T: Copy, $($phantom),+> Copy for $name<T, $($phantom),+> {}
+
+        impl<T, $($phantom),+> ::heapsize::HeapSizeOf for $name<T, $($phantom),+>
+            where T: ::heapsize::HeapSizeOf
+        {
+            fn heap_size_of_children(&self) -> usize {
+                $(self.$field.heap_size_of_children() +)+ 0
+            }
+        }
+
+        impl<T, $($phantom),+> ::serde::Deserialize for $name<T, $($phantom),+>
+            where T: ::serde::Deserialize
+        {
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+                where D: ::serde::Deserializer
+            {
+                let ($($field,)+) =
+                    try!(::serde::Deserialize::deserialize(deserializer));
+                Ok($name {
+                    $($field: $field,)+
+                    _unit: PhantomData,
+                })
+            }
+        }
+
+        impl<T, $($phantom),+> ::serde::Serialize for $name<T, $($phantom),+>
+            where T: ::serde::Serialize
+        {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+                where S: ::serde::Serializer
+            {
+                ($(&self.$field,)+).serialize(serializer)
+            }
+        }
+
+        impl<T, $($phantom),+> ::std::cmp::Eq for $name<T, $($phantom),+>
+            where T: ::std::cmp::Eq {}
+
+        impl<T, $($phantom),+> ::std::cmp::PartialEq for $name<T, $($phantom),+>
+            where T: ::std::cmp::PartialEq
+        {
+            fn eq(&self, other: &Self) -> bool {
+                true $(&& self.$field == other.$field)+
+            }
+        }
+
+        impl<T, $($phantom),+> ::std::hash::Hash for $name<T, $($phantom),+>
+            where T: ::std::hash::Hash
+        {
+            fn hash<H: ::std::hash::Hasher>(&self, h: &mut H) {
+                $(self.$field.hash(h);)+
+            }
+        }
+    )
+}
rename from third_party/rust/euclid/src/matrix2d.rs
rename to third_party/rust/euclid-0.13.0/src/matrix2d.rs
rename from third_party/rust/euclid/src/matrix4d.rs
rename to third_party/rust/euclid-0.13.0/src/matrix4d.rs
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/src/num.rs
@@ -0,0 +1,77 @@
+// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//! A one-dimensional length, tagged with its units.
+
+use num_traits;
+
+
+pub trait Zero {
+    fn zero() -> Self;
+}
+
+impl<T: num_traits::Zero> Zero for T {
+    fn zero() -> T { num_traits::Zero::zero() }
+}
+
+pub trait One {
+    fn one() -> Self;
+}
+
+impl<T: num_traits::One> One for T {
+    fn one() -> T { num_traits::One::one() }
+}
+
+
+pub trait Round : Copy { fn round(self) -> Self; }
+pub trait Floor : Copy { fn floor(self) -> Self; }
+pub trait Ceil : Copy { fn ceil(self) -> Self; }
+
+macro_rules! num_int {
+    ($ty:ty) => (
+        impl Round for $ty {
+            #[inline]
+            fn round(self) -> $ty { self }
+        }
+        impl Floor for $ty {
+            #[inline]
+            fn floor(self) -> $ty { self }
+        }
+        impl Ceil for $ty {
+            #[inline]
+            fn ceil(self) -> $ty { self }
+        }
+    )
+}
+macro_rules! num_float {
+    ($ty:ty) => (
+        impl Round for $ty {
+            #[inline]
+            fn round(self) -> $ty { self.round() }
+        }
+        impl Floor for $ty {
+            #[inline]
+            fn floor(self) -> $ty { self.floor() }
+        }
+        impl Ceil for $ty {
+            #[inline]
+            fn ceil(self) -> $ty { self.ceil() }
+        }
+    )
+}
+
+num_int!(i16);
+num_int!(u16);
+num_int!(i32);
+num_int!(u32);
+num_int!(i64);
+num_int!(u64);
+num_int!(isize);
+num_int!(usize);
+num_float!(f32);
+num_float!(f64);
new file mode 100644
--- /dev/null
+++ b/third_party/rust/euclid-0.13.0/src/point.rs
@@ -0,0 +1,995 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use approxeq::ApproxEq;
+use length::Length;
+use scale_factor::ScaleFactor;
+use size::TypedSize2D;
+use num::*;
+use num_traits::{Float, NumCast};
+use std::fmt;
+use std::ops::{Add, Neg, Mul, Sub, Div};
+use std::marker::PhantomData;
+
+define_matrix! {
+    /// A 2d Point tagged with a unit.
+    #[derive(RustcDecodable, RustcEncodable)]
+    pub struct TypedPoint2D<T, U> {
+        pub x: T,
+        pub y: T,
+    }
+}
+
+/// Default 2d point type with no unit.
+///
+/// `Point2D` provides the same methods as `TypedPoint2D`.
+pub type Point2D<T> = TypedPoint2D<T, UnknownUnit>;
+
+impl<T: Copy + Zero, U> TypedPoint2D<T, U> {
+    /// Constructor, setting all components to zero.
+    #[inline]
+    pub fn zero() -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(Zero::zero(), Zero::zero())
+    }
+
+    /// Convert into a 3d point.
+    #[inline]
+    pub fn to_3d(&self) -> TypedPoint3D<T, U> {
+        TypedPoint3D::new(self.x, self.y, Zero::zero())
+    }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for TypedPoint2D<T, U> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "({:?},{:?})", self.x, self.y)
+    }
+}
+
+impl<T: fmt::Display, U> fmt::Display for TypedPoint2D<T, U> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        write!(formatter, "({},{})", self.x, self.y)
+    }
+}
+
+impl<T: Copy, U> TypedPoint2D<T, U> {
+    /// Constructor taking scalar values directly.
+    #[inline]
+    pub fn new(x: T, y: T) -> TypedPoint2D<T, U> {
+        TypedPoint2D { x: x, y: y, _unit: PhantomData }
+    }
+
+    /// Constructor taking properly typed Lengths instead of scalar values.
+    #[inline]
+    pub fn from_lengths(x: Length<T, U>, y: Length<T, U>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(x.0, y.0)
+    }
+
+    /// Returns self.x as a Length carrying the unit.
+    #[inline]
+    pub fn x_typed(&self) -> Length<T, U> { Length::new(self.x) }
+
+    /// Returns self.y as a Length carrying the unit.
+    #[inline]
+    pub fn y_typed(&self) -> Length<T, U> { Length::new(self.y) }
+
+    /// Drop the units, preserving only the numeric value.
+    #[inline]
+    pub fn to_untyped(&self) -> Point2D<T> {
+        TypedPoint2D::new(self.x, self.y)
+    }
+
+    /// Tag a unitless value with units.
+    #[inline]
+    pub fn from_untyped(p: &Point2D<T>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(p.x, p.y)
+    }
+
+    #[inline]
+    pub fn to_array(&self) -> [T; 2] {
+        [self.x, self.y]
+    }
+}
+
+impl<T, U> TypedPoint2D<T, U>
+where T: Copy + Mul<T, Output=T> + Add<T, Output=T> + Sub<T, Output=T> {
+    /// Dot product.
+    #[inline]
+    pub fn dot(self, other: TypedPoint2D<T, U>) -> T {
+        self.x * other.x + self.y * other.y
+    }
+
+    /// Returns the norm of the cross product [self.x, self.y, 0] x [other.x, other.y, 0]..
+    #[inline]
+    pub fn cross(self, other: TypedPoint2D<T, U>) -> T {
+        self.x * other.y - self.y * other.x
+    }
+
+    #[inline]
+    pub fn normalize(self) -> Self where T: Float + ApproxEq<T> {
+        let dot = self.dot(self);
+        if dot.approx_eq(&T::zero()) {
+            self
+        } else {
+            self / dot.sqrt()
+        }
+    }
+}
+
+impl<T: Copy + Add<T, Output=T>, U> Add for TypedPoint2D<T, U> {
+    type Output = TypedPoint2D<T, U>;
+    fn add(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x + other.x, self.y + other.y)
+    }
+}
+
+impl<T: Copy + Add<T, Output=T>, U> Add<TypedSize2D<T, U>> for TypedPoint2D<T, U> {
+    type Output = TypedPoint2D<T, U>;
+    fn add(self, other: TypedSize2D<T, U>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x + other.width, self.y + other.height)
+    }
+}
+
+impl<T: Copy + Add<T, Output=T>, U> TypedPoint2D<T, U> {
+    pub fn add_size(&self, other: &TypedSize2D<T, U>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x + other.width, self.y + other.height)
+    }
+}
+
+impl<T: Copy + Sub<T, Output=T>, U> Sub for TypedPoint2D<T, U> {
+    type Output = TypedPoint2D<T, U>;
+    fn sub(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x - other.x, self.y - other.y)
+    }
+}
+
+impl <T: Copy + Neg<Output=T>, U> Neg for TypedPoint2D<T, U> {
+    type Output = TypedPoint2D<T, U>;
+    #[inline]
+    fn neg(self) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(-self.x, -self.y)
+    }
+}
+
+impl<T: Float, U> TypedPoint2D<T, U> {
+    pub fn min(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
+         TypedPoint2D::new(self.x.min(other.x), self.y.min(other.y))
+    }
+
+    pub fn max(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x.max(other.x), self.y.max(other.y))
+    }
+}
+
+impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedPoint2D<T, U> {
+    type Output = TypedPoint2D<T, U>;
+    #[inline]
+    fn mul(self, scale: T) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x * scale, self.y * scale)
+    }
+}
+
+impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedPoint2D<T, U> {
+    type Output = TypedPoint2D<T, U>;
+    #[inline]
+    fn div(self, scale: T) -> TypedPoint2D<T, U> {
+        TypedPoint2D::new(self.x / scale, self.y / scale)
+    }
+}
+
+impl<T: Copy + Mul<T, Output=T>, U1, U2> Mul<ScaleFactor<T, U1, U2>> for TypedPoint2D<T, U1> {
+    type Output = TypedPoint2D<T, U2>;
+    #[inline]
+    fn mul(self, scale: ScaleFactor<T, U1, U2>) -> TypedPoint2D<T, U2> {
+        TypedPoint2D::new(self.x * scale.get(), self.y * scale.get())
+    }
+}
+
+impl<T: Copy + Div<T, Output=T>, U1, U2> Div<ScaleFactor<T, U1, U2>> for TypedPoint2D<T, U2> {
+    type Output = TypedPoint2D<T, U1>;
+    #[inline]
+    fn div(self, scale: ScaleFactor<T, U1, U2>) -> TypedPoint2D<T, U1> {
+        TypedPoint2D::new(self.x / scale.get(), self.y / scale.get())
+    }
+}
+
+impl<T: Round, U> TypedPoint2D<T, U> {
+    /// Rounds each component to the nearest integer value.
+    ///
+    /// This behavior is preserved for negative values (unlike the basic cast).
+    /// For example `{ -0.1, -0.8 }.round() == { 0.0, -1.0 }`.
+    pub fn round(&self) -> Self {
+        TypedPoint2D::new(self.x.round(), self.y.round())
+    }
+}
+
+impl<T: Ceil, U> TypedPoint2D<T, U> {
+    /// Rounds each component to the smallest integer equal or greater than the original value.
+    ///
+    /// This behavior is preserved for negative values (unlike the basic cast).
+    /// For example `{ -0.1, -0.8 }.ceil() == { 0.0, 0.0 }`.
+    pub fn ceil(&self) -> Self {
+        TypedPoint2D::new(self.x.ceil(), self.y.ceil())
+    }
+}
+
+impl<T: Floor, U> TypedPoint2D<T, U> {
+    /// Rounds each component to the biggest integer equal or lower than the original value.
+    ///
+    /// This behavior is preserved for negative values (unlike the basic cast).
+    /// For example `{ -0.1, -0.8 }.floor() == { -1.0, -1.0 }`.
+    pub fn floor(&self) -> Self {
+