Bug 1481570. Update webrender to commit 890f3746b798538d201e54b66d2711a70447b781
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Tue, 14 Aug 2018 09:47:58 -0400
changeset 431390 7846bdd3762cf494ec24efc9cfc3472dc715ce4f
parent 431389 08bf805f6f0ef61f68686ef1ca2cc6f750a2cfa0
child 431391 51d56958d3d0016f38591a340eef901845cd2d7e
push id106443
push userjmuizelaar@mozilla.com
push dateTue, 14 Aug 2018 13:57:35 +0000
treeherdermozilla-inbound@7846bdd3762c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1481570
milestone63.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 1481570. Update webrender to commit 890f3746b798538d201e54b66d2711a70447b781
gfx/webrender/res/brush.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/shared.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device/gl.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/main.rs
gfx/wrench/src/wrench.rs
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -117,26 +117,30 @@ struct Fragment {
 #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
     vec4 blend;
 #endif
 };
 
 Fragment brush_fs();
 
 void main(void) {
+#ifdef WR_FEATURE_DEBUG_OVERDRAW
+    oFragColor = WR_DEBUG_OVERDRAW_COLOR;
+#else
     // Run the specific brush FS code to output the color.
     Fragment frag = brush_fs();
 
 #ifdef WR_FEATURE_ALPHA_PASS
     // Apply the clip mask
     float clip_alpha = do_clip();
 
     frag.color *= clip_alpha;
 
     #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
         oFragBlend = frag.blend * clip_alpha;
     #endif
 #endif
 
     // TODO(gw): Handle pre-multiply common code here as required.
     oFragColor = frag.color;
+#endif
 }
 #endif
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -266,17 +266,19 @@ void main(void) {
     vec4 mask = texture(sColor0, tc);
     mask.rgb = mask.rgb * vMaskSwizzle.x + mask.aaa * vMaskSwizzle.y;
 
     float alpha = do_clip();
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     alpha *= float(all(greaterThanEqual(vUvClip, vec4(0.0))));
 #endif
 
-#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+#if defined(WR_FEATURE_DEBUG_OVERDRAW)
+    oFragColor = WR_DEBUG_OVERDRAW_COLOR;
+#elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING)
     vec4 alpha_mask = mask * alpha;
     oFragColor = vColor * alpha_mask;
     oFragBlend = alpha_mask * vColor.a;
 #else
     oFragColor = vColor * mask * alpha;
 #endif
 }
 #endif
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -52,17 +52,20 @@
     // Fragment shader outputs
     #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
         layout(location = 0, index = 0) out vec4 oFragColor;
         layout(location = 0, index = 1) out vec4 oFragBlend;
     #else
         out vec4 oFragColor;
     #endif
 
-    #define EPSILON     0.0001
+    #define EPSILON                     0.0001
+
+    // "Show Overdraw" color. Premultiplied.
+    #define WR_DEBUG_OVERDRAW_COLOR     vec4(0.110, 0.077, 0.027, 0.125)
 
     float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
         vec2 dir_to_p0 = p0 - p;
         return dot(normalize(perp_dir), dir_to_p0);
     }
 
     /// Find the appropriate half range to apply the AA approximation over.
     /// This range represents a coefficient to go from one CSS pixel to half a device pixel.
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,31 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize, DeviceIntPoint};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering};
 use api::{YuvColorSpace, YuvFormat, WorldPixel};
-use clip::{ClipSource, ClipStore, ClipWorkItem};
-use clip_scroll_tree::{CoordinateSystemId};
+use clip::{ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore};
 use euclid::vec3;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
-use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
-use prim_store::{BorderSource};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex};
+use prim_store::{PrimitiveMetadata, PrimitiveRun, VisibleGradientTile};
+use prim_store::{BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{f32, i32};
 use tiling::{RenderTargetContext};
 use util::{TransformedRectKind};
@@ -471,17 +470,17 @@ impl AlphaBatchBuilder {
         // Even though most of the time a splitter isn't used or needed,
         // they are cheap to construct so we will always pass one down.
         let mut splitter = BspSplitter::new();
 
         // Add each run in this picture to the batch.
         for run in &pic.runs {
             let transform_id = ctx
                 .transforms
-                .get_id(run.clip_and_scroll.spatial_node_index);
+                .get_id(run.spatial_node_index);
             self.add_run_to_batch(
                 run,
                 transform_id,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
@@ -507,19 +506,18 @@ impl AlphaBatchBuilder {
                 [pp[2].z as f32, pp[3].x as f32, pp[3].y as f32, pp[3].z as f32].into(),
             ];
             let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
-            let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
-            let brush = &ctx.prim_store.cpu_brushes[pic_metadata.cpu_prim_index.0];
-            let pic = &ctx.prim_store.pictures[brush.get_picture_index().0];
+            let pic_metadata = &ctx.prim_store.primitives[prim_index.0].metadata;
+            let pic = ctx.prim_store.get_pic(prim_index);
             let batch = self.batch_list.get_suitable_batch(key, &pic_metadata.screen_rect.as_ref().expect("bug").clipped);
 
             let source_task_id = pic
                 .surface
                 .as_ref()
                 .expect("BUG: unexpected surface in splitting")
                 .resolve_render_task_id();
             let source_task_address = render_tasks.get_task_address(source_task_id);
@@ -550,17 +548,17 @@ impl AlphaBatchBuilder {
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
         content_origin: DeviceIntPoint,
         prim_headers: &mut PrimitiveHeaders,
     ) {
         for i in 0 .. run.count {
             let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-            let metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
+            let metadata = &ctx.prim_store.primitives[prim_index.0].metadata;
 
             if metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
                     transform_id,
                     prim_index,
                     ctx,
                     gpu_cache,
                     render_tasks,
@@ -588,17 +586,18 @@ impl AlphaBatchBuilder {
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
         content_origin: DeviceIntPoint,
         prim_headers: &mut PrimitiveHeaders,
     ) {
-        let prim_metadata = ctx.prim_store.get_metadata(prim_index);
+        let prim = &ctx.prim_store.primitives[prim_index.0];
+        let prim_metadata = &prim.metadata;
         #[cfg(debug_assertions)] //TODO: why is this needed?
         debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id());
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
 
@@ -609,19 +608,18 @@ impl AlphaBatchBuilder {
                 screen_rect.unclipped.origin.y - content_origin.y,
             ),
             screen_rect.unclipped.size,
         );
 
         // If the primitive is internally decomposed into multiple sub-primitives we may not
         // use some of the per-primitive data typically stored in PrimitiveMetadata and get
         // it from each sub-primitive instead.
-        let is_multiple_primitives = match prim_metadata.prim_kind {
-            PrimitiveKind::Brush => {
-                let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
+        let is_multiple_primitives = match prim.details {
+            PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
                     BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(),
                     BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
                     BrushKind::RadialGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
                     _ => false,
                 }
             }
             _ => false,
@@ -632,17 +630,17 @@ impl AlphaBatchBuilder {
         } else {
             gpu_cache.get_address(&prim_metadata.gpu_location)
         };
 
         let clip_task_address = prim_metadata
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
-        let specified_blend_mode = ctx.prim_store.get_blend_mode(prim_metadata);
+        let specified_blend_mode = prim.get_blend_mode();
 
         let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque ||
             prim_metadata.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex {
             specified_blend_mode
         } else {
             BlendMode::None
         };
@@ -656,32 +654,28 @@ impl AlphaBatchBuilder {
             transform_id,
         };
 
         if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
             println!("\ttask target {:?}", self.target_rect);
             println!("\t{:?}", prim_header);
         }
 
-        match prim_metadata.prim_kind {
-            PrimitiveKind::Brush => {
-                let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
-
+        match prim.details {
+            PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Picture { pic_index, .. } => {
-                        let picture = &ctx.prim_store.pictures[pic_index.0];
-
+                    BrushKind::Picture(ref picture) => {
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
                             debug_assert!(picture.surface.is_some());
                             let transform = &ctx.transforms
-                                .get_transform(picture.reference_frame_index);
+                                .get_transform(picture.original_spatial_node_index);
 
                             match transform.transform_kind {
                                 TransformedRectKind::AxisAligned => {
                                     let polygon = Polygon::from_transformed_rect(
                                         picture.real_local_rect.cast(),
                                         transform.m.cast(),
                                         prim_index.0,
                                     ).unwrap();
@@ -1093,20 +1087,17 @@ impl AlphaBatchBuilder {
                                 &task_relative_bounding_rect,
                                 transform_kind,
                                 render_tasks,
                             );
                         }
                     }
                 }
             }
-            PrimitiveKind::TextRun => {
-                let text_cpu =
-                    &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-
+            PrimitiveDetails::TextRun(ref text_cpu) => {
                 let subpx_dir = text_cpu.used_font.get_subpx_dir();
 
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let batch_list = &mut self.batch_list;
 
                 ctx.resource_cache.fetch_glyphs(
                     text_cpu.used_font.clone(),
                     &text_cpu.glyph_keys,
@@ -1386,27 +1377,16 @@ fn get_image_tile_params(
                      RasterizationSpace::Local as i32,
                 0,
             ],
         ))
     }
 }
 
 impl BrushPrimitive {
-    pub fn get_picture_index(&self) -> PictureIndex {
-        match self.kind {
-            BrushKind::Picture { pic_index, .. } => {
-                pic_index
-            }
-            _ => {
-                panic!("bug: not a picture brush!!");
-            }
-        }
-    }
-
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
         is_chased: bool,
     ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
         match self.kind {
@@ -1580,33 +1560,25 @@ impl BrushPrimitive {
                         uv_rect_addresses[2],
                     ],
                 ))
             }
         }
     }
 }
 
-trait AlphaBatchHelpers {
-    fn get_blend_mode(
-        &self,
-        metadata: &PrimitiveMetadata,
-    ) -> BlendMode;
-}
-
-impl AlphaBatchHelpers for PrimitiveStore {
-    fn get_blend_mode(&self, metadata: &PrimitiveMetadata) -> BlendMode {
-        match metadata.prim_kind {
+impl Primitive {
+    fn get_blend_mode(&self) -> BlendMode {
+        match self.details {
             // Can only resolve the TextRun's blend mode once glyphs are fetched.
-            PrimitiveKind::TextRun => {
+            PrimitiveDetails::TextRun(..) => {
                 BlendMode::PremultipliedAlpha
             }
 
-            PrimitiveKind::Brush => {
-                let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
+            PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
                     BrushKind::Clear => {
                         BlendMode::PremultipliedDestOut
                     }
                     BrushKind::Image { alpha_type, .. } => {
                         match alpha_type {
                             AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                             AlphaType::Alpha => BlendMode::Alpha,
@@ -1757,103 +1729,99 @@ impl ClipBatcher {
         };
 
         self.rectangles.push(instance);
     }
 
     pub fn add(
         &mut self,
         task_address: RenderTaskAddress,
-        clips: &[ClipWorkItem],
-        coordinate_system_id: CoordinateSystemId,
+        clip_node_range: ClipNodeRange,
         resource_cache: &ResourceCache,
         gpu_cache: &GpuCache,
         clip_store: &ClipStore,
         transforms: &TransformPalette,
     ) {
-        let mut coordinate_system_id = coordinate_system_id;
-        for work_item in clips.iter() {
-            let info = &clip_store[work_item.clip_sources_index];
+        for i in 0 .. clip_node_range.count {
+            let (clip_node, flags) = clip_store.get_node_from_range(&clip_node_range, i);
+
             let instance = ClipMaskInstance {
                 render_task_address: task_address,
-                transform_id: transforms.get_id(info.spatial_node_index),
+                transform_id: transforms.get_id(clip_node.spatial_node_index),
                 segment: 0,
                 clip_data_address: GpuCacheAddress::invalid(),
                 resource_address: GpuCacheAddress::invalid(),
             };
 
-            for &(ref source, ref handle) in &info.clips {
-                let gpu_address = gpu_cache.get_address(handle);
+            let gpu_address = gpu_cache.get_address(&clip_node.gpu_cache_handle);
 
-                match *source {
-                    ClipSource::Image(ref mask) => {
-                        if let Ok(cache_item) = resource_cache.get_cached_image(
-                            ImageRequest {
-                                key: mask.image,
-                                rendering: ImageRendering::Auto,
-                                tile: None,
-                            }
-                        ) {
-                            self.images
-                                .entry(cache_item.texture_id)
-                                .or_insert(Vec::new())
-                                .push(ClipMaskInstance {
-                                    clip_data_address: gpu_address,
-                                    resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
-                                    ..instance
-                                });
-                        } else {
-                            warn!("Warnings: skip a image mask");
-                            debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
-                            continue;
+            match clip_node.item {
+                ClipItem::Image(ref mask) => {
+                    if let Ok(cache_item) = resource_cache.get_cached_image(
+                        ImageRequest {
+                            key: mask.image,
+                            rendering: ImageRendering::Auto,
+                            tile: None,
                         }
-                    }
-                    ClipSource::LineDecoration(..) => {
-                        self.line_decorations.push(ClipMaskInstance {
-                            clip_data_address: gpu_address,
-                            ..instance
-                        });
-                    }
-                    ClipSource::BoxShadow(ref info) => {
-                        let rt_handle = info
-                            .cache_handle
-                            .as_ref()
-                            .expect("bug: render task handle not allocated");
-                        let rt_cache_entry = resource_cache
-                            .get_cached_render_task(rt_handle);
-                        let cache_item = resource_cache
-                            .get_texture_cache_item(&rt_cache_entry.handle);
-                        debug_assert_ne!(cache_item.texture_id, SourceTexture::Invalid);
-
-                        self.box_shadows
+                    ) {
+                        self.images
                             .entry(cache_item.texture_id)
                             .or_insert(Vec::new())
                             .push(ClipMaskInstance {
                                 clip_data_address: gpu_address,
                                 resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
                                 ..instance
                             });
+                    } else {
+                        warn!("Warnings: skip a image mask");
+                        debug!("Key:{:?} Rect::{:?}", mask.image, mask.rect);
+                        continue;
                     }
-                    ClipSource::Rectangle(_, mode) => {
-                        if work_item.coordinate_system_id != coordinate_system_id ||
-                           mode == ClipMode::ClipOut {
-                            self.rectangles.push(ClipMaskInstance {
-                                clip_data_address: gpu_address,
-                                ..instance
-                            });
-                            coordinate_system_id = work_item.coordinate_system_id;
-                        }
-                    }
-                    ClipSource::RoundedRectangle(..) => {
+                }
+                ClipItem::LineDecoration(..) => {
+                    self.line_decorations.push(ClipMaskInstance {
+                        clip_data_address: gpu_address,
+                        ..instance
+                    });
+                }
+                ClipItem::BoxShadow(ref info) => {
+                    let rt_handle = info
+                        .cache_handle
+                        .as_ref()
+                        .expect("bug: render task handle not allocated");
+                    let rt_cache_entry = resource_cache
+                        .get_cached_render_task(rt_handle);
+                    let cache_item = resource_cache
+                        .get_texture_cache_item(&rt_cache_entry.handle);
+                    debug_assert_ne!(cache_item.texture_id, SourceTexture::Invalid);
+
+                    self.box_shadows
+                        .entry(cache_item.texture_id)
+                        .or_insert(Vec::new())
+                        .push(ClipMaskInstance {
+                            clip_data_address: gpu_address,
+                            resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
+                            ..instance
+                        });
+                }
+                ClipItem::Rectangle(_, mode) => {
+                    if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
+                        mode == ClipMode::ClipOut {
                         self.rectangles.push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             ..instance
                         });
                     }
                 }
+                ClipItem::RoundedRectangle(..) => {
+                    self.rectangles.push(ClipMaskInstance {
+                        clip_data_address: gpu_address,
+                        ..instance
+                    });
+                }
             }
         }
     }
 }
 
 fn get_buffer_kind(texture: SourceTexture) -> ImageBufferKind {
     match texture {
         SourceTexture::External(ext_image) => {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -941,29 +941,36 @@ impl BorderRenderTaskInfo {
                 info.widths,
                 info.radius,
             );
         }
 
         instances
     }
 
-    /// Computes the maximum scale that we allow for this set of border radii.
+    /// Computes the maximum scale that we allow for this set of border parameters.
     /// capping the scale will result in rendering very large corners at a lower
     /// resolution and stretching them, so they will have the right shape, but
     /// blurrier.
-    pub fn get_max_scale(radii: &BorderRadius) -> LayoutToDeviceScale {
+    pub fn get_max_scale(
+        radii: &BorderRadius,
+        widths: &BorderWidths
+    ) -> LayoutToDeviceScale {
         let r = radii.top_left.width
             .max(radii.top_left.height)
             .max(radii.top_right.width)
             .max(radii.top_right.height)
             .max(radii.bottom_left.width)
             .max(radii.bottom_left.height)
             .max(radii.bottom_right.width)
-            .max(radii.bottom_right.height);
+            .max(radii.bottom_right.height)
+            .max(widths.top)
+            .max(widths.bottom)
+            .max(widths.left)
+            .max(widths.right);
 
         LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
     }
 }
 
 fn add_brush_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.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 api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, DeviceIntSize, LayoutPrimitiveInfo};
 use api::{LayoutRect, LayoutSize, LayoutVector2D};
-use clip::ClipSource;
+use clip::ClipItem;
 use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BoxShadowStretchMode;
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
 use render_task::RenderTaskCacheEntryHandle;
 use util::RectHelpers;
 
@@ -117,38 +117,38 @@ impl<'a> DisplayListFlattener<'a> {
             let mut clips = Vec::with_capacity(2);
             let (final_prim_rect, clip_radius) = match clip_mode {
                 BoxShadowClipMode::Outset => {
                     if !shadow_rect.is_well_formed_and_nonempty() {
                         return;
                     }
 
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
-                    clips.push(ClipSource::new_rounded_rect(
+                    clips.push(ClipItem::new_rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut
                     ));
 
                     (shadow_rect, shadow_radius)
                 }
                 BoxShadowClipMode::Inset => {
                     if shadow_rect.is_well_formed_and_nonempty() {
-                        clips.push(ClipSource::new_rounded_rect(
+                        clips.push(ClipItem::new_rounded_rect(
                             shadow_rect,
                             shadow_radius,
                             ClipMode::ClipOut
                         ));
                     }
 
                     (prim_info.rect, border_radius)
                 }
             };
 
-            clips.push(ClipSource::new_rounded_rect(final_prim_rect, clip_radius, ClipMode::Clip));
+            clips.push(ClipItem::new_rounded_rect(final_prim_rect, clip_radius, ClipMode::Clip));
 
             self.add_primitive(
                 clip_and_scroll,
                 &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
                 clips,
                 PrimitiveContainer::Brush(
                     BrushPrimitive::new(
                         BrushKind::new_solid(*color),
@@ -158,35 +158,35 @@ impl<'a> DisplayListFlattener<'a> {
             );
         } else {
             // Normal path for box-shadows with a valid blur radius.
             let blur_offset = BLUR_SAMPLE_SCALE * blur_radius;
             let mut extra_clips = vec![];
 
             // Add a normal clip mask to clip out the contents
             // of the surrounding primitive.
-            extra_clips.push(ClipSource::new_rounded_rect(
+            extra_clips.push(ClipItem::new_rounded_rect(
                 prim_info.rect,
                 border_radius,
                 prim_clip_mode,
             ));
 
             // Get the local rect of where the shadow will be drawn,
             // expanded to include room for the blurred region.
             let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
 
             // Draw the box-shadow as a solid rect, using a box-shadow
-            // clip mask source.
+            // clip mask item.
             let prim = BrushPrimitive::new(
                 BrushKind::new_solid(*color),
                 None,
             );
 
-            // Create the box-shadow clip source.
-            let shadow_clip_source = ClipSource::new_box_shadow(
+            // Create the box-shadow clip item.
+            let shadow_clip_source = ClipItem::new_box_shadow(
                 shadow_rect,
                 shadow_radius,
                 dest_rect,
                 blur_radius,
                 clip_mode,
             );
 
             let prim_info = match clip_mode {
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,68 +1,641 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
-use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle};
+use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, LayoutTransform};
 use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, SpatialNodeIndex};
+use clip_scroll_tree::{CoordinateSystemId, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::BoxShadowStretchMode;
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
-use util::{LayoutToWorldFastTransform, MaxRect, TransformedRectKind};
-use util::{calculate_screen_bounding_rect, extract_inner_rect_safe, pack_as_float, recycle_vec};
-use std::{iter, ops};
-use std::sync::Arc;
+use spatial_node::SpatialNode;
+use std::u32;
+use util::{extract_inner_rect_safe, pack_as_float, recycle_vec, MatrixHelpers};
+
+/*
+
+ Module Overview
+
+ There are a number of data structures involved in the clip module:
+
+ ClipStore - Main interface used by other modules.
+
+ ClipItem - A single clip item (e.g. a rounded rect, or a box shadow).
+            These are an exposed API type, stored inline in a ClipNode.
+
+ ClipNode - A ClipItem with attached positioning information (a spatial node index).
+            Stored as a contiguous array of nodes within the ClipStore.
+
+    +-----------------------+-----------------------+-----------------------+
+    | ClipItem              | ClipItem              | ClipItem              |
+    | Spatial Node Index    | Spatial Node Index    | Spatial Node Index    |
+    | GPU cache handle      | GPU cache handle      | GPU cache handle      |
+    +-----------------------+-----------------------+-----------------------+
+               0                        1                       2
+
+       +----------------+    |                                              |
+       | ClipItemRange  |____|                                              |
+       |    index: 1    |                                                   |
+       |    count: 2    |___________________________________________________|
+       +----------------+
+
+ ClipItemRange - A clip item range identifies a range of clip nodes. It is stored
+                 as an (index, count).
+
+ ClipChain - A clip chain node contains a range of ClipNodes (a ClipItemRange)
+             and a parent link to an optional ClipChain. Both legacy hierchical clip
+             chains and user defined API clip chains use the same data structure.
+             ClipChainId is an index into an array, or ClipChainId::NONE for no parent.
+
+    +----------------+    ____+----------------+    ____+----------------+    ____+----------------+
+    | ClipChain      |   |    | ClipChain      |   |    | ClipChain      |   |    | ClipChain      |
+    +----------------+   |    +----------------+   |    +----------------+   |    +----------------+
+    | ClipItemRange  |   |    | ClipItemRange  |   |    | ClipItemRange  |   |    | ClipItemRange  |
+    | Parent Id      |___|    | Parent Id      |___|    | Parent Id      |___|    | Parent Id      |
+    +----------------+        +----------------+        +----------------+        +----------------+
+
+ ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node.
+
+    When given a clip chain ID, and a local primitive rect + spatial node, the clip module
+    creates a clip chain instance. This is a struct with various pieces of useful information
+    (such as a local clip rect and affected local bounding rect). It also contains a (index, count)
+    range specifier into an index buffer of the ClipNode structures that are actually relevant
+    for this clip chain instance. The index buffer structure allows a single array to be used for
+    all of the clip-chain instances built in a single frame. Each entry in the index buffer
+    also stores some flags relevant to the clip node in this positioning context.
+
+    +----------------------+
+    | ClipChainInstance    |
+    +----------------------+
+    | local_clip_rect      |
+    | local_bounding_rect  |________________________________________________________________________
+    | clips_range          |_______________                                                        |
+    +----------------------+              |                                                        |
+                                          |                                                        |
+    +------------------+------------------+------------------+------------------+------------------+
+    | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
+    +------------------+------------------+------------------+------------------+------------------+
+    | flags            | flags            | flags            | flags            | flags            |
+    | ClipNodeIndex    | ClipNodeIndex    | ClipNodeIndex    | ClipNodeIndex    | ClipNodeIndex    |
+    +------------------+------------------+------------------+------------------+------------------+
+
+ */
+
+// Result of comparing a clip node instance against a local rect.
+#[derive(Debug)]
+enum ClipResult {
+    // The clip does not affect the region at all.
+    Accept,
+    // The clip prevents the region from being drawn.
+    Reject,
+    // The clip affects part of the region. This may
+    // require a clip mask, depending on other factors.
+    Partial,
+}
 
+// A clip item range represents one or more ClipItem structs.
+// They are stored in a contiguous array inside the ClipStore,
+// and identified by an (offset, count).
+#[derive(Debug, Copy, Clone)]
+pub struct ClipItemRange {
+    pub index: ClipNodeIndex,
+    pub count: u32,
+}
+
+// A clip node is a single clip source, along with some
+// positioning information and implementation details
+// that control where the GPU data for this clip source
+// can be found.
+pub struct ClipNode {
+    pub spatial_node_index: SpatialNodeIndex,
+    pub item: ClipItem,
+    pub gpu_cache_handle: GpuCacheHandle,
+}
+
+// Flags that are attached to instances of clip nodes.
+bitflags! {
+    pub struct ClipNodeFlags: u8 {
+        const SAME_SPATIAL_NODE = 0x1;
+        const SAME_COORD_SYSTEM = 0x2;
+    }
+}
+
+// Identifier for a clip chain. Clip chains are stored
+// in a contiguous array in the clip store. They are
+// identified by a simple index into that array.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct ClipChainId(pub u32);
+
+// The root of each clip chain is the NONE id. The
+// value is specifically set to u32::MAX so that if
+// any code accidentally tries to access the root
+// node, a bounds error will occur.
+impl ClipChainId {
+    pub const NONE: Self = ClipChainId(u32::MAX);
+}
+
+// A clip chain node is an id for a range of clip sources,
+// and a link to a parent clip chain node, or ClipChainId::NONE.
+#[derive(Clone)]
+pub struct ClipChainNode {
+    pub clip_item_range: ClipItemRange,
+    pub parent_clip_chain_id: ClipChainId,
+}
+
+// An index into the clip_nodes array.
+#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipNodeIndex(pub u32);
+
+// When a clip node is found to be valid for a
+// clip chain instance, it's stored in an index
+// buffer style structure. This struct contains
+// an index to the node data itself, as well as
+// some flags describing how this clip node instance
+// is positioned.
+#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipNodeInstance(pub u32);
+
+impl ClipNodeInstance {
+    fn new(index: ClipNodeIndex, flags: ClipNodeFlags) -> ClipNodeInstance {
+        ClipNodeInstance(
+            (index.0 & 0x00ffffff) | ((flags.bits() as u32) << 24)
+        )
+    }
+
+    fn flags(&self) -> ClipNodeFlags {
+        ClipNodeFlags::from_bits_truncate((self.0 >> 24) as u8)
+    }
+
+    fn index(&self) -> usize {
+        (self.0 & 0x00ffffff) as usize
+    }
+}
+
+// A range of clip node instances that were found by
+// building a clip chain instance.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ClipSourcesIndex(usize);
+pub struct ClipNodeRange {
+    pub first: u32,
+    pub count: u32,
+}
+
+// A helper struct for converting between coordinate systems
+// of clip sources and primitives.
+// todo(gw): optimize:
+//  separate arrays for matrices
+//  cache and only build as needed.
+#[derive(Debug)]
+enum ClipSpaceConversion {
+    Local,
+    Offset(LayoutVector2D),
+    Transform(LayoutTransform, LayoutTransform),
+}
+
+impl ClipSpaceConversion {
+    fn transform_to_prim_space(&self, rect: &LayoutRect) -> Option<LayoutRect> {
+        match *self {
+            ClipSpaceConversion::Local => {
+                Some(*rect)
+            }
+            ClipSpaceConversion::Offset(ref offset) => {
+                Some(rect.translate(offset))
+            }
+            ClipSpaceConversion::Transform(ref transform, _) => {
+                if transform.has_perspective_component() {
+                    None
+                } else {
+                    transform.transform_rect(rect)
+                }
+            }
+        }
+    }
+
+    fn transform_from_prim_space(&self, rect: &LayoutRect) -> Option<LayoutRect> {
+        match *self {
+            ClipSpaceConversion::Local => {
+                Some(*rect)
+            }
+            ClipSpaceConversion::Offset(offset) => {
+                Some(rect.translate(&-offset))
+            }
+            ClipSpaceConversion::Transform(_, ref inv_transform) => {
+                inv_transform.transform_rect(rect)
+            }
+        }
+    }
+}
+
+// Temporary information that is cached and reused
+// during building of a clip chain instance.
+struct ClipNodeInfo {
+    conversion: ClipSpaceConversion,
+    node_index: ClipNodeIndex,
+    has_non_root_coord_system: bool,
+}
 
+impl ClipNode {
+    pub fn update(
+        &mut self,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        device_pixel_scale: DevicePixelScale,
+    ) {
+        if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
+            match self.item {
+                ClipItem::Image(ref mask) => {
+                    let data = ImageMaskData { local_rect: mask.rect };
+                    data.write_gpu_blocks(request);
+                }
+                ClipItem::BoxShadow(ref info) => {
+                    request.push([
+                        info.shadow_rect_alloc_size.width,
+                        info.shadow_rect_alloc_size.height,
+                        info.clip_mode as i32 as f32,
+                        0.0,
+                    ]);
+                    request.push([
+                        info.stretch_mode_x as i32 as f32,
+                        info.stretch_mode_y as i32 as f32,
+                        0.0,
+                        0.0,
+                    ]);
+                    request.push(info.prim_shadow_rect);
+                }
+                ClipItem::Rectangle(rect, mode) => {
+                    let data = ClipData::uniform(rect, 0.0, mode);
+                    data.write(&mut request);
+                }
+                ClipItem::RoundedRectangle(ref rect, ref radius, mode) => {
+                    let data = ClipData::rounded_rect(rect, radius, mode);
+                    data.write(&mut request);
+                }
+                ClipItem::LineDecoration(ref info) => {
+                    request.push(info.rect);
+                    request.push([
+                        info.wavy_line_thickness,
+                        pack_as_float(info.style as u32),
+                        pack_as_float(info.orientation as u32),
+                        0.0,
+                    ]);
+                }
+            }
+        }
+
+        match self.item {
+            ClipItem::Image(ref mask) => {
+                resource_cache.request_image(
+                    ImageRequest {
+                        key: mask.image,
+                        rendering: ImageRendering::Auto,
+                        tile: None,
+                    },
+                    gpu_cache,
+                );
+            }
+            ClipItem::BoxShadow(ref mut info) => {
+                // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                // "the image that would be generated by applying to the shadow a
+                // Gaussian blur with a standard deviation equal to half the blur radius."
+                let blur_radius_dp = (info.blur_radius * 0.5 * device_pixel_scale.0).round();
+
+                // Create the cache key for this box-shadow render task.
+                let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
+                let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
+                let bs_cache_key = BoxShadowCacheKey {
+                    blur_radius_dp: blur_radius_dp as i32,
+                    clip_mode: info.clip_mode,
+                    rect_size: (info.shadow_rect_alloc_size * content_scale).round().to_i32(),
+                    br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
+                    br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
+                    br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
+                    br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
+                };
+
+                info.cache_key = Some((cache_size, bs_cache_key));
+
+                if let Some(mut request) = gpu_cache.request(&mut info.clip_data_handle) {
+                    let data = ClipData::rounded_rect(
+                        &info.minimal_shadow_rect,
+                        &info.shadow_radius,
+                        ClipMode::Clip,
+                    );
+
+                    data.write(&mut request);
+                }
+            }
+            ClipItem::Rectangle(..) |
+            ClipItem::RoundedRectangle(..) |
+            ClipItem::LineDecoration(..) => {}
+        }
+    }
+}
+
+// The main clipping public interface that other modules access.
 pub struct ClipStore {
-    clip_sources: Vec<ClipSources>,
+    pub clip_nodes: Vec<ClipNode>,
+    pub clip_chain_nodes: Vec<ClipChainNode>,
+    clip_node_indices: Vec<ClipNodeInstance>,
+    clip_node_info: Vec<ClipNodeInfo>,
+}
+
+// A clip chain instance is what gets built for a given clip
+// chain id + local primitive region + positioning node.
+pub struct ClipChainInstance {
+    pub clips_range: ClipNodeRange,
+    pub local_clip_rect: LayoutRect,
+    pub has_clips_from_other_coordinate_systems: bool,
+    pub has_non_root_coord_system: bool,
+    pub local_bounding_rect: LayoutRect,
 }
 
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
-            clip_sources: Vec::new(),
+            clip_nodes: Vec::new(),
+            clip_chain_nodes: Vec::new(),
+            clip_node_indices: Vec::new(),
+            clip_node_info: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         ClipStore {
-            clip_sources: recycle_vec(self.clip_sources),
+            clip_nodes: recycle_vec(self.clip_nodes),
+            clip_chain_nodes: recycle_vec(self.clip_chain_nodes),
+            clip_node_indices: recycle_vec(self.clip_node_indices),
+            clip_node_info: recycle_vec(self.clip_node_info),
         }
     }
 
-    pub fn insert(&mut self, clip_sources: ClipSources) -> ClipSourcesIndex {
-        let index = ClipSourcesIndex(self.clip_sources.len());
-        self.clip_sources.push(clip_sources);
-        index
+    pub fn add_clip_items(
+        &mut self,
+        clip_items: Vec<ClipItem>,
+        spatial_node_index: SpatialNodeIndex,
+    ) -> ClipItemRange {
+        debug_assert!(!clip_items.is_empty());
+
+        let range = ClipItemRange {
+            index: ClipNodeIndex(self.clip_nodes.len() as u32),
+            count: clip_items.len() as u32,
+        };
+
+        let nodes = clip_items
+            .into_iter()
+            .map(|item| {
+                ClipNode {
+                    item,
+                    spatial_node_index,
+                    gpu_cache_handle: GpuCacheHandle::new(),
+                }
+            });
+
+        self.clip_nodes.extend(nodes);
+        range
+    }
+
+    pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
+        &self.clip_chain_nodes[clip_chain_id.0 as usize]
+    }
+
+    pub fn add_clip_chain(
+        &mut self,
+        clip_item_range: ClipItemRange,
+        parent_clip_chain_id: ClipChainId,
+    ) -> ClipChainId {
+        let id = ClipChainId(self.clip_chain_nodes.len() as u32);
+        self.clip_chain_nodes.push(ClipChainNode {
+            clip_item_range,
+            parent_clip_chain_id,
+        });
+        id
     }
-}
+
+    pub fn get_node_from_range(
+        &self,
+        node_range: &ClipNodeRange,
+        index: u32,
+    ) -> (&ClipNode, ClipNodeFlags) {
+        let instance = self.clip_node_indices[(node_range.first + index) as usize];
+        (&self.clip_nodes[instance.index()], instance.flags())
+    }
+
+    pub fn get_node_from_range_mut(
+        &mut self,
+        node_range: &ClipNodeRange,
+        index: u32,
+    ) -> (&mut ClipNode, ClipNodeFlags) {
+        let instance = self.clip_node_indices[(node_range.first + index) as usize];
+        (&mut self.clip_nodes[instance.index()], instance.flags())
+    }
+
+    // The main interface other code uses. Given a local primitive, positioning
+    // information, and a clip chain id, build an optimized clip chain instance.
+    pub fn build_clip_chain_instance(
+        &mut self,
+        clip_chain_id: ClipChainId,
+        local_prim_rect: LayoutRect,
+        local_prim_clip_rect: LayoutRect,
+        spatial_node_index: SpatialNodeIndex,
+        spatial_nodes: &[SpatialNode],
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        device_pixel_scale: DevicePixelScale,
+    ) -> Option<ClipChainInstance> {
+        // Trivial check to see if the primitive is clipped out by the
+        // local clip rect of the primitive itself.
+        let mut local_bounding_rect = match local_prim_rect.intersection(&local_prim_clip_rect) {
+            Some(rect) => rect,
+            None => return None,
+        };
+        let mut current_local_clip_rect = local_prim_clip_rect;
+
+        // Walk the clip chain to build local rects, and collect the
+        // smallest possible local clip area.
+
+        self.clip_node_info.clear();
+        let ref_spatial_node = &spatial_nodes[spatial_node_index.0];
+        let mut current_clip_chain_id = clip_chain_id;
+
+        // for each clip chain node
+        while current_clip_chain_id != ClipChainId::NONE {
+            let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
+            let node_count = clip_chain_node.clip_item_range.count;
+
+            // for each clip node (clip source) in this clip chain node
+            for i in 0 .. node_count {
+                let clip_node_index = ClipNodeIndex(clip_chain_node.clip_item_range.index.0 + i);
+                let clip_node = &self.clip_nodes[clip_node_index.0 as usize];
+                let clip_spatial_node = &spatial_nodes[clip_node.spatial_node_index.0 as usize];
 
-impl ops::Index<ClipSourcesIndex> for ClipStore {
-    type Output = ClipSources;
-    fn index(&self, index: ClipSourcesIndex) -> &Self::Output {
-        &self.clip_sources[index.0]
-    }
-}
+                // Determine the most efficient way to convert between coordinate
+                // systems of the primitive and clip node.
+                let conversion = if spatial_node_index == clip_node.spatial_node_index {
+                    Some(ClipSpaceConversion::Local)
+                } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
+                    let offset = clip_spatial_node.coordinate_system_relative_offset -
+                                 ref_spatial_node.coordinate_system_relative_offset;
+                    Some(ClipSpaceConversion::Offset(offset))
+                } else {
+                    // TODO(gw): We still have issues with clip nodes and primitives where
+                    //           there is a perspective transform. We intend to fix these
+                    //           cases as a follow up.
+                    let relative_transform = ref_spatial_node
+                        .world_content_transform
+                        .to_transform()
+                        .inverse()
+                        .map(|inv| {
+                            inv.pre_mul(&clip_spatial_node.world_content_transform.to_transform())
+                        });
+                    let inv_relative_transform = relative_transform
+                        .and_then(|rt| rt.inverse());
+                    match (relative_transform, inv_relative_transform) {
+                        (Some(relative_transform), Some(inv_relative_transform)) => {
+                            Some(ClipSpaceConversion::Transform(relative_transform, inv_relative_transform))
+                        }
+                        _ => {
+                            None
+                        }
+                    }
+                };
+
+                // If we can convert spaces, try to reduce the size of the region
+                // requested, and cache the conversion information for the next step.
+                if let Some(conversion) = conversion {
+                    if let Some(clip_rect) = clip_node.item.get_local_clip_rect() {
+                        let clip_rect = conversion.transform_to_prim_space(&clip_rect);
+                        if let Some(clip_rect) = clip_rect {
+                            local_bounding_rect = match local_bounding_rect.intersection(&clip_rect) {
+                                Some(new_local_bounding_rect) => new_local_bounding_rect,
+                                None => return None,
+                            };
+
+                            if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
+                                current_local_clip_rect = match current_local_clip_rect.intersection(&clip_rect) {
+                                    Some(new_local_clip_rect) => new_local_clip_rect,
+                                    None => {
+                                        return None
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    self.clip_node_info.push(ClipNodeInfo {
+                        conversion,
+                        node_index: clip_node_index,
+                        has_non_root_coord_system: clip_spatial_node.coordinate_system_id != CoordinateSystemId::root(),
+                    })
+                }
+            }
+
+            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
+        }
+
+        // Now, we've collected all the clip nodes that *potentially* affect this
+        // primitive region, and reduced the size of the prim region as much as possible.
+
+        // Run through the clip nodes, and see which ones affect this prim region.
+
+        let first_clip_node_index = self.clip_node_indices.len() as u32;
+        let mut has_non_root_coord_system = false;
+        let mut has_clips_from_other_coordinate_systems = false;
+
+        // For each potential clip node
+        for node_info in self.clip_node_info.drain(..) {
+            let node = &mut self.clip_nodes[node_info.node_index.0 as usize];
 
-impl ops::IndexMut<ClipSourcesIndex> for ClipStore {
-    fn index_mut(&mut self, index: ClipSourcesIndex) -> &mut Self::Output {
-        &mut self.clip_sources[index.0]
+            // Convert the prim rect into the clip nodes local space
+            let prim_rect = node_info
+                .conversion
+                .transform_from_prim_space(&current_local_clip_rect);
+
+            // See how this clip affects the prim region.
+            let clip_result = match prim_rect {
+                Some(prim_rect) => {
+                    node.item.get_clip_result(&prim_rect)
+                }
+                None => {
+                    // If we can't get a local rect due to perspective
+                    // weirdness, just assume that we need a clip mask
+                    // for this case.
+                    // TODO(gw): We can probably improve on this once
+                    //           we support local space picture raster.
+                    ClipResult::Partial
+                }
+            };
+
+            match clip_result {
+                ClipResult::Accept => {
+                    // Doesn't affect the primitive at all, so skip adding to list
+                }
+                ClipResult::Reject => {
+                    // Completely clips the supplied prim rect
+                    return None;
+                }
+                ClipResult::Partial => {
+                    // Needs a mask -> add to clip node indices
+
+                    // TODO(gw): Ensure this only runs once on each node per frame?
+                    node.update(
+                        gpu_cache,
+                        resource_cache,
+                        device_pixel_scale,
+                    );
+
+                    // Calculate some flags that are required for the segment
+                    // building logic.
+                    let flags = match node_info.conversion {
+                        ClipSpaceConversion::Local => {
+                            ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
+                        }
+                        ClipSpaceConversion::Offset(..) => {
+                            ClipNodeFlags::SAME_COORD_SYSTEM
+                        }
+                        ClipSpaceConversion::Transform(..) => {
+                            has_clips_from_other_coordinate_systems = true;
+                            ClipNodeFlags::empty()
+                        }
+                    };
+
+                    // Store this in the index buffer for this clip chain instance.
+                    self.clip_node_indices
+                        .push(ClipNodeInstance::new(node_info.node_index, flags));
+
+                    has_non_root_coord_system |= node_info.has_non_root_coord_system;
+                }
+            }
+        }
+
+        // Get the range identifying the clip nodes in the index buffer.
+        let clips_range = ClipNodeRange {
+            first: first_clip_node_index,
+            count: self.clip_node_indices.len() as u32 - first_clip_node_index,
+        };
+
+        // Return a valid clip chain instance
+        Some(ClipChainInstance {
+            clips_range,
+            has_clips_from_other_coordinate_systems,
+            has_non_root_coord_system,
+            local_clip_rect: current_local_clip_rect,
+            local_bounding_rect,
+        })
     }
 }
 
 #[derive(Debug)]
 pub struct LineDecorationClipSource {
     rect: LayoutRect,
     style: LineStyle,
     orientation: LineOrientation,
@@ -139,65 +712,65 @@ impl ClipRegion<Option<ComplexClipRegion
                     })
                 },
             }
         }
     }
 }
 
 #[derive(Debug)]
-pub enum ClipSource {
+pub enum ClipItem {
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
     Image(ImageMask),
     BoxShadow(BoxShadowClipSource),
     LineDecoration(LineDecorationClipSource),
 }
 
-impl ClipSource {
+impl ClipItem {
     pub fn new_rounded_rect(
         rect: LayoutRect,
         mut radii: BorderRadius,
         clip_mode: ClipMode
-    ) -> ClipSource {
+    ) -> Self {
         if radii.is_zero() {
-            ClipSource::Rectangle(rect, clip_mode)
+            ClipItem::Rectangle(rect, clip_mode)
         } else {
             ensure_no_corner_overlap(&mut radii, &rect);
-            ClipSource::RoundedRectangle(
+            ClipItem::RoundedRectangle(
                 rect,
                 radii,
                 clip_mode,
             )
         }
     }
 
     pub fn new_line_decoration(
         rect: LayoutRect,
         style: LineStyle,
         orientation: LineOrientation,
         wavy_line_thickness: f32,
-    ) -> ClipSource {
-        ClipSource::LineDecoration(
+    ) -> Self {
+        ClipItem::LineDecoration(
             LineDecorationClipSource {
                 rect,
                 style,
                 orientation,
                 wavy_line_thickness,
             }
         )
     }
 
     pub fn new_box_shadow(
         shadow_rect: LayoutRect,
         shadow_radius: BorderRadius,
         prim_shadow_rect: LayoutRect,
         blur_radius: f32,
         clip_mode: BoxShadowClipMode,
-    ) -> ClipSource {
+    ) -> Self {
         // Get the fractional offsets required to match the
         // source rect with a minimal rect.
         let fract_offset = LayoutPoint::new(
             shadow_rect.origin.x.fract().abs(),
             shadow_rect.origin.y.fract().abs(),
         );
         let fract_size = LayoutSize::new(
             shadow_rect.size.width.fract().abs(),
@@ -261,337 +834,174 @@ impl ClipSource {
         }
 
         // Expand the shadow rect by enough room for the blur to take effect.
         let shadow_rect_alloc_size = LayoutSize::new(
             2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
             2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
         );
 
-        ClipSource::BoxShadow(BoxShadowClipSource {
+        ClipItem::BoxShadow(BoxShadowClipSource {
             shadow_rect_alloc_size,
             shadow_radius,
             prim_shadow_rect,
             blur_radius,
             clip_mode,
             stretch_mode_x,
             stretch_mode_y,
             cache_handle: None,
             cache_key: None,
             clip_data_handle: GpuCacheHandle::new(),
             minimal_shadow_rect,
         })
     }
 
     // Return a modified clip source that is the same as self
     // but offset in local-space by a specified amount.
-    pub fn offset(&self, offset: &LayoutVector2D) -> ClipSource {
+    pub fn offset(&self, offset: &LayoutVector2D) -> Self {
         match *self {
-            ClipSource::LineDecoration(ref info) => {
-                ClipSource::LineDecoration(LineDecorationClipSource {
+            ClipItem::LineDecoration(ref info) => {
+                ClipItem::LineDecoration(LineDecorationClipSource {
                     rect: info.rect.translate(offset),
                     ..*info
                 })
             }
             _ => {
                 panic!("bug: other clip sources not expected here yet");
             }
         }
     }
 
     pub fn is_rect(&self) -> bool {
         match *self {
-            ClipSource::Rectangle(..) => true,
+            ClipItem::Rectangle(..) => true,
             _ => false,
         }
     }
 
     pub fn is_image_or_line_decoration_clip(&self) -> bool {
         match *self {
-            ClipSource::Image(..) | ClipSource::LineDecoration(..) => true,
+            ClipItem::Image(..) | ClipItem::LineDecoration(..) => true,
             _ => false,
         }
     }
-}
-
-
-struct BoundsAccumulator {
-    local_outer: Option<LayoutRect>,
-    local_inner: Option<LayoutRect>,
-    can_calculate_inner_rect: bool,
-    can_calculate_outer_rect: bool,
-}
-
-impl BoundsAccumulator {
-    fn new() -> Self {
-        BoundsAccumulator {
-            local_outer: Some(LayoutRect::max_rect()),
-            local_inner: Some(LayoutRect::max_rect()),
-            can_calculate_inner_rect: true,
-            can_calculate_outer_rect: false,
-        }
-    }
 
-    fn add(&mut self, source: &ClipSource) {
-        // Depending on the complexity of the clip, we may either know the outer and/or inner
-        // rect, or neither or these.  In the case of a clip-out, we currently set the mask bounds
-        // to be unknown. This is conservative, but ensures correctness. In the future we can make
-        // this a lot more clever with some proper region handling.
-        if !self.can_calculate_inner_rect {
-            return
-        }
-
-        match *source {
-            ClipSource::Image(ref mask) => {
-                if !mask.repeat {
-                    self.can_calculate_outer_rect = true;
-                    self.local_outer = self.local_outer.and_then(|r| r.intersection(&mask.rect));
-                }
-                self.local_inner = None;
-            }
-            ClipSource::Rectangle(rect, mode) => {
-                // Once we encounter a clip-out, we just assume the worst
-                // case clip mask size, for now.
-                if mode == ClipMode::ClipOut {
-                    self.can_calculate_inner_rect = false;
-                    return
-                }
-
-                self.can_calculate_outer_rect = true;
-                self.local_outer = self.local_outer.and_then(|r| r.intersection(&rect));
-                self.local_inner = self.local_inner.and_then(|r| r.intersection(&rect));
-            }
-            ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
-                // Once we encounter a clip-out, we just assume the worst
-                // case clip mask size, for now.
-                if mode == ClipMode::ClipOut {
-                    self.can_calculate_inner_rect = false;
-                    return
-                }
-
-                self.can_calculate_outer_rect = true;
-                self.local_outer = self.local_outer.and_then(|r| r.intersection(rect));
-
-                let inner_rect = extract_inner_rect_safe(rect, radius);
-                self.local_inner = self.local_inner
-                    .and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
-            }
-            ClipSource::BoxShadow(..) |
-            ClipSource::LineDecoration(..) => {
-                self.can_calculate_inner_rect = false;
-            }
-        }
-    }
-
-    fn finish(self) -> (LayoutRect, Option<LayoutRect>) {
-        (
-            if self.can_calculate_inner_rect {
-                self.local_inner.unwrap_or_else(LayoutRect::zero)
-            } else {
-                LayoutRect::zero()
-            },
-            if self.can_calculate_outer_rect {
-                Some(self.local_outer.unwrap_or_else(LayoutRect::zero))
-            } else {
-                None
-            },
-        )
-    }
-}
-
-
-#[derive(Debug)]
-pub struct ClipSources {
-    pub clips: Vec<(ClipSource, GpuCacheHandle)>,
-    pub local_inner_rect: LayoutRect,
-    pub local_outer_rect: Option<LayoutRect>,
-    pub only_rectangular_clips: bool,
-    pub has_image_or_line_decoration_clip: bool,
-    pub spatial_node_index: SpatialNodeIndex,
-}
-
-impl ClipSources {
-    pub fn new<I>(clip_iter: I, spatial_node_index: SpatialNodeIndex) -> Self
-    where
-        I: IntoIterator<Item = ClipSource>,
-    {
-        let mut clips = Vec::new();
-        let mut bounds_accum = BoundsAccumulator::new();
-        let mut has_image_or_line_decoration_clip = false;
-        let mut only_rectangular_clips = true;
-
-        for clip in clip_iter {
-            bounds_accum.add(&clip);
-            has_image_or_line_decoration_clip |= clip.is_image_or_line_decoration_clip();
-            only_rectangular_clips &= clip.is_rect();
-            clips.push((clip, GpuCacheHandle::new()));
-        }
-
-        only_rectangular_clips &= !has_image_or_line_decoration_clip;
-        let (local_inner_rect, local_outer_rect) = bounds_accum.finish();
-
-        ClipSources {
-            clips,
-            local_inner_rect,
-            local_outer_rect,
-            only_rectangular_clips,
-            has_image_or_line_decoration_clip,
-            spatial_node_index,
+    // Get an optional clip rect that a clip source can provide to
+    // reduce the size of a primitive region. This is typically
+    // used to eliminate redundant clips, and reduce the size of
+    // any clip mask that eventually gets drawn.
+    fn get_local_clip_rect(&self) -> Option<LayoutRect> {
+        match *self {
+            ClipItem::Rectangle(clip_rect, ClipMode::Clip) => Some(clip_rect),
+            ClipItem::Rectangle(_, ClipMode::ClipOut) => None,
+            ClipItem::RoundedRectangle(clip_rect, _, ClipMode::Clip) => Some(clip_rect),
+            ClipItem::RoundedRectangle(_, _, ClipMode::ClipOut) => None,
+            ClipItem::Image(ref mask) if mask.repeat => None,
+            ClipItem::Image(ref mask) => Some(mask.rect),
+            ClipItem::BoxShadow(..) => None,
+            ClipItem::LineDecoration(..) => None,
         }
     }
 
-    pub fn from_region<I>(
-        region: ClipRegion<I>,
-        spatial_node_index: SpatialNodeIndex,
-    ) -> ClipSources
-    where
-        I: IntoIterator<Item = ComplexClipRegion>
-    {
-        let clip_rect = iter::once(ClipSource::Rectangle(region.main, ClipMode::Clip));
-        let clip_image = region.image_mask.map(ClipSource::Image);
-        let clips_complex = region.complex_clips
-            .into_iter()
-            .map(|complex| ClipSource::new_rounded_rect(
-                complex.rect,
-                complex.radii,
-                complex.mode,
-            ));
-
-        let clips_all = clip_rect.chain(clip_image).chain(clips_complex);
-        ClipSources::new(clips_all, spatial_node_index)
-    }
-
-    pub fn clips(&self) -> &[(ClipSource, GpuCacheHandle)] {
-        &self.clips
-    }
+    // Check how a given clip source affects a local primitive region.
+    fn get_clip_result(
+        &self,
+        prim_rect: &LayoutRect,
+    ) -> ClipResult {
+        match *self {
+            ClipItem::Rectangle(ref clip_rect, ClipMode::Clip) => {
+                if clip_rect.contains_rect(prim_rect) {
+                    return ClipResult::Accept;
+                }
 
-    pub fn update(
-        &mut self,
-        gpu_cache: &mut GpuCache,
-        resource_cache: &mut ResourceCache,
-        device_pixel_scale: DevicePixelScale,
-    ) {
-        for &mut (ref mut source, ref mut handle) in &mut self.clips {
-            if let Some(mut request) = gpu_cache.request(handle) {
-                match *source {
-                    ClipSource::Image(ref mask) => {
-                        let data = ImageMaskData { local_rect: mask.rect };
-                        data.write_gpu_blocks(request);
+                match clip_rect.intersection(prim_rect) {
+                    Some(..) => {
+                        ClipResult::Partial
+                    }
+                    None => {
+                        ClipResult::Reject
                     }
-                    ClipSource::BoxShadow(ref info) => {
-                        request.push([
-                            info.shadow_rect_alloc_size.width,
-                            info.shadow_rect_alloc_size.height,
-                            info.clip_mode as i32 as f32,
-                            0.0,
-                        ]);
-                        request.push([
-                            info.stretch_mode_x as i32 as f32,
-                            info.stretch_mode_y as i32 as f32,
-                            0.0,
-                            0.0,
-                        ]);
-                        request.push(info.prim_shadow_rect);
+                }
+            }
+            ClipItem::Rectangle(ref clip_rect, ClipMode::ClipOut) => {
+                if clip_rect.contains_rect(prim_rect) {
+                    return ClipResult::Reject;
+                }
+
+                match clip_rect.intersection(prim_rect) {
+                    Some(_) => {
+                        ClipResult::Partial
                     }
-                    ClipSource::Rectangle(rect, mode) => {
-                        let data = ClipData::uniform(rect, 0.0, mode);
-                        data.write(&mut request);
-                    }
-                    ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
-                        let data = ClipData::rounded_rect(rect, radius, mode);
-                        data.write(&mut request);
-                    }
-                    ClipSource::LineDecoration(ref info) => {
-                        request.push(info.rect);
-                        request.push([
-                            info.wavy_line_thickness,
-                            pack_as_float(info.style as u32),
-                            pack_as_float(info.orientation as u32),
-                            0.0,
-                        ]);
+                    None => {
+                        ClipResult::Accept
                     }
                 }
             }
-
-            match *source {
-                ClipSource::Image(ref mask) => {
-                    resource_cache.request_image(
-                        ImageRequest {
-                            key: mask.image,
-                            rendering: ImageRendering::Auto,
-                            tile: None,
-                        },
-                        gpu_cache,
-                    );
+            ClipItem::RoundedRectangle(ref clip_rect, ref radius, ClipMode::Clip) => {
+                // TODO(gw): Consider caching this in the ClipNode
+                //           if it ever shows in profiles.
+                // TODO(gw): extract_inner_rect_safe is overly
+                //           conservative for this code!
+                let inner_clip_rect = extract_inner_rect_safe(clip_rect, radius);
+                if let Some(inner_clip_rect) = inner_clip_rect {
+                    if inner_clip_rect.contains_rect(prim_rect) {
+                        return ClipResult::Accept;
+                    }
                 }
-                ClipSource::BoxShadow(ref mut info) => {
-                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-                    // "the image that would be generated by applying to the shadow a
-                    // Gaussian blur with a standard deviation equal to half the blur radius."
-                    let blur_radius_dp = (info.blur_radius * 0.5 * device_pixel_scale.0).round();
 
-                    // Create the cache key for this box-shadow render task.
-                    let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
-                    let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
-                    let bs_cache_key = BoxShadowCacheKey {
-                        blur_radius_dp: blur_radius_dp as i32,
-                        clip_mode: info.clip_mode,
-                        rect_size: (info.shadow_rect_alloc_size * content_scale).round().to_i32(),
-                        br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
-                        br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
-                        br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
-                        br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
-                    };
-
-                    info.cache_key = Some((cache_size, bs_cache_key));
-
-                    if let Some(mut request) = gpu_cache.request(&mut info.clip_data_handle) {
-                        let data = ClipData::rounded_rect(
-                            &info.minimal_shadow_rect,
-                            &info.shadow_radius,
-                            ClipMode::Clip,
-                        );
-
-                        data.write(&mut request);
+                match clip_rect.intersection(prim_rect) {
+                    Some(..) => {
+                        ClipResult::Partial
+                    }
+                    None => {
+                        ClipResult::Reject
                     }
                 }
-                _ => {}
+            }
+            ClipItem::RoundedRectangle(ref clip_rect, ref radius, ClipMode::ClipOut) => {
+                // TODO(gw): Consider caching this in the ClipNode
+                //           if it ever shows in profiles.
+                // TODO(gw): extract_inner_rect_safe is overly
+                //           conservative for this code!
+                let inner_clip_rect = extract_inner_rect_safe(clip_rect, radius);
+                if let Some(inner_clip_rect) = inner_clip_rect {
+                    if inner_clip_rect.contains_rect(prim_rect) {
+                        return ClipResult::Reject;
+                    }
+                }
+
+                match clip_rect.intersection(prim_rect) {
+                    Some(_) => {
+                        ClipResult::Partial
+                    }
+                    None => {
+                        ClipResult::Accept
+                    }
+                }
+            }
+            ClipItem::Image(ref mask) => {
+                if mask.repeat {
+                    ClipResult::Partial
+                } else {
+                    match mask.rect.intersection(prim_rect) {
+                        Some(..) => {
+                            ClipResult::Partial
+                        }
+                        None => {
+                            ClipResult::Reject
+                        }
+                    }
+                }
+            }
+            ClipItem::BoxShadow(..) |
+            ClipItem::LineDecoration(..) => {
+                ClipResult::Partial
             }
         }
     }
-
-    pub fn get_screen_bounds(
-        &self,
-        transform: &LayoutToWorldFastTransform,
-        device_pixel_scale: DevicePixelScale,
-        screen_rect: Option<&DeviceIntRect>,
-    ) -> (DeviceIntRect, Option<DeviceIntRect>) {
-        // If this translation isn't axis aligned or has a perspective component, don't try to
-        // calculate the inner rectangle. The rectangle that we produce would include potentially
-        // clipped screen area.
-        // TODO(mrobinson): We should eventually try to calculate an inner region or some inner
-        // rectangle so that we can do screen inner rectangle optimizations for these kind of
-        // cilps.
-        let can_calculate_inner_rect =
-            transform.kind() == TransformedRectKind::AxisAligned &&
-            !transform.has_perspective_component();
-        let screen_inner_rect = if can_calculate_inner_rect {
-            calculate_screen_bounding_rect(transform, &self.local_inner_rect, device_pixel_scale, screen_rect)
-                .unwrap_or(DeviceIntRect::zero())
-        } else {
-            DeviceIntRect::zero()
-        };
-
-        let screen_outer_rect = self.local_outer_rect.map(|outer_rect|
-            calculate_screen_bounding_rect(transform, &outer_rect, device_pixel_scale, screen_rect)
-                .unwrap_or(DeviceIntRect::zero())
-        );
-
-        (screen_inner_rect, screen_outer_rect)
-    }
 }
 
 /// Represents a local rect and a device space
 /// rectangles that are either outside or inside bounds.
 #[derive(Clone, Debug, PartialEq)]
 pub struct Geometry {
     pub local_rect: LayoutRect,
     pub device_rect: DeviceIntRect,
@@ -638,100 +1048,8 @@ pub fn rounded_rectangle_contains_point(
                              LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
     if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
        !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
         return false;
     }
 
     true
 }
-
-pub type ClipChainNodeRef = Option<Arc<ClipChainNode>>;
-
-#[derive(Debug, Clone)]
-pub struct ClipChainNode {
-    pub work_item: ClipWorkItem,
-    pub local_clip_rect: LayoutRect,
-    pub screen_outer_rect: DeviceIntRect,
-    pub screen_inner_rect: DeviceIntRect,
-    pub prev: ClipChainNodeRef,
-}
-
-#[derive(Debug, Clone)]
-pub struct ClipChain {
-    pub parent_index: Option<ClipChainIndex>,
-    pub combined_outer_screen_rect: DeviceIntRect,
-    pub combined_inner_screen_rect: DeviceIntRect,
-    pub nodes: ClipChainNodeRef,
-    pub has_non_root_coord_system: bool,
-}
-
-impl ClipChain {
-    pub fn empty(screen_rect: &DeviceIntRect) -> Self {
-        ClipChain {
-            parent_index: None,
-            combined_inner_screen_rect: *screen_rect,
-            combined_outer_screen_rect: *screen_rect,
-            nodes: None,
-            has_non_root_coord_system: false,
-        }
-    }
-
-    pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> Self {
-        // If the new node's inner rectangle completely surrounds our outer rectangle,
-        // we can discard the new node entirely since it isn't going to affect anything.
-        if new_node.screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
-            return self.clone();
-        }
-
-        let mut new_chain = self.clone();
-        new_chain.add_node(new_node.clone());
-        new_chain
-    }
-
-    pub fn add_node(&mut self, mut new_node: ClipChainNode) {
-        new_node.prev = self.nodes.clone();
-
-        // If this clip's outer rectangle is completely enclosed by the clip
-        // chain's inner rectangle, then the only clip that matters from this point
-        // on is this clip. We can disconnect this clip from the parent clip chain.
-        if self.combined_inner_screen_rect.contains_rect(&new_node.screen_outer_rect) {
-            new_node.prev = None;
-        }
-
-        self.combined_outer_screen_rect =
-            self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
-            .unwrap_or_else(DeviceIntRect::zero);
-        self.combined_inner_screen_rect =
-            self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
-            .unwrap_or_else(DeviceIntRect::zero);
-
-        self.has_non_root_coord_system |= new_node.work_item.coordinate_system_id != CoordinateSystemId::root();
-
-        self.nodes = Some(Arc::new(new_node));
-    }
-}
-
-pub struct ClipChainNodeIter {
-    pub current: ClipChainNodeRef,
-}
-
-impl Iterator for ClipChainNodeIter {
-    type Item = Arc<ClipChainNode>;
-
-    fn next(&mut self) -> ClipChainNodeRef {
-        let previous = self.current.clone();
-        self.current = match self.current {
-            Some(ref item) => item.prev.clone(),
-            None => return None,
-        };
-        previous
-    }
-}
-
-#[derive(Debug, Clone)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ClipWorkItem {
-    pub clip_sources_index: ClipSourcesIndex,
-    pub coordinate_system_id: CoordinateSystemId,
-}
-
deleted file mode 100644
--- a/gfx/webrender/src/clip_node.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use api::DevicePixelScale;
-use clip::{ClipChain, ClipChainNode, ClipSourcesIndex, ClipStore, ClipWorkItem};
-use clip_scroll_tree::{ClipChainIndex};
-use gpu_cache::GpuCache;
-use resource_cache::ResourceCache;
-use spatial_node::SpatialNode;
-
-#[derive(Debug)]
-pub struct ClipNode {
-    /// A handle to this clip nodes clips in the ClipStore.
-    pub clip_sources_index: ClipSourcesIndex,
-
-    /// An index to a ClipChain defined by this ClipNode's hiearchy in the display
-    /// list.
-    pub clip_chain_index: ClipChainIndex,
-
-    /// The index of the parent ClipChain of this node's hiearchical ClipChain.
-    pub parent_clip_chain_index: ClipChainIndex,
-
-    /// A copy of the ClipChainNode this node would produce. We need to keep a copy,
-    /// because the ClipChain may not contain our node if is optimized out, but API
-    /// defined ClipChains will still need to access it.
-    pub clip_chain_node: Option<ClipChainNode>,
-}
-
-impl ClipNode {
-    pub fn update(
-        &mut self,
-        device_pixel_scale: DevicePixelScale,
-        clip_store: &mut ClipStore,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        clip_chains: &mut [ClipChain],
-        spatial_nodes: &[SpatialNode],
-    ) {
-        let clip_sources = &mut clip_store[self.clip_sources_index];
-        clip_sources.update(gpu_cache, resource_cache, device_pixel_scale);
-        let spatial_node = &spatial_nodes[clip_sources.spatial_node_index.0];
-
-        let (screen_inner_rect, screen_outer_rect) = clip_sources.get_screen_bounds(
-            &spatial_node.world_content_transform,
-            device_pixel_scale,
-            None,
-        );
-
-        // All clipping SpatialNodes should have outer rectangles, because they never
-        // use the BorderCorner clip type and they always have at last one non-ClipOut
-        // Rectangle ClipSource.
-        let screen_outer_rect = screen_outer_rect
-            .expect("Clipping node didn't have outer rect.");
-        let local_outer_rect = clip_sources.local_outer_rect
-            .expect("Clipping node didn't have outer rect.");
-
-        let new_node = ClipChainNode {
-            work_item: ClipWorkItem {
-                clip_sources_index: self.clip_sources_index,
-                coordinate_system_id: spatial_node.coordinate_system_id,
-            },
-            local_clip_rect: local_outer_rect
-                .translate(&spatial_node.coordinate_system_relative_offset),
-            screen_outer_rect,
-            screen_inner_rect,
-            prev: None,
-        };
-
-        let mut clip_chain =
-            clip_chains[self.parent_clip_chain_index.0]
-            .new_with_added_node(&new_node);
-
-        self.clip_chain_node = Some(new_node);
-        clip_chain.parent_index = Some(self.parent_clip_chain_index);
-        clip_chains[self.clip_chain_index.0] = clip_chain;
-    }
-}
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,22 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
-use api::{PipelineId, ScrollClamping, ScrollLocation, ScrollNodeState};
+use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
+use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation};
 use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
-use clip::{ClipChain, ClipSourcesIndex, ClipStore};
-use clip_node::ClipNode;
-use gpu_cache::GpuCache;
+use clip::{ClipStore};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
-use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
 use util::LayoutToWorldFastTransform;
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
@@ -27,19 +24,16 @@ pub type ScrollStates = FastHashMap<Exte
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SpatialNodeIndex(pub usize);
 
-#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
-pub struct ClipNodeIndex(pub usize);
-
 const ROOT_REFERENCE_FRAME_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
 const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
 
 impl CoordinateSystemId {
     pub fn root() -> Self {
         CoordinateSystemId(0)
     }
 
@@ -48,46 +42,21 @@ impl CoordinateSystemId {
         CoordinateSystemId(id + 1)
     }
 
     pub fn advance(&mut self) {
         self.0 += 1;
     }
 }
 
-pub struct ClipChainDescriptor {
-    pub index: ClipChainIndex,
-    pub parent: Option<ClipChainIndex>,
-    pub clips: Vec<ClipNodeIndex>,
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct ClipChainIndex(pub usize);
-
-impl ClipChainIndex {
-    pub const NO_CLIP: Self = ClipChainIndex(0);
-}
-
 pub struct ClipScrollTree {
     /// Nodes which determine the positions (offsets and transforms) for primitives
     /// and clips.
     pub spatial_nodes: Vec<SpatialNode>,
 
-    /// Nodes which clip primitives.
-    pub clip_nodes: Vec<ClipNode>,
-
-    /// A Vec of all descriptors that describe ClipChains in the order in which they are
-    /// encountered during display list flattening. ClipChains are expected to never be
-    /// the children of ClipChains later in the list.
-    pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
-
-    /// A vector of all ClipChains in this ClipScrollTree including those from
-    /// ClipChainDescriptors and also those defined by the clipping node hierarchy.
-    pub clip_chains: Vec<ClipChain>,
-
     pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayoutPoint, ScrollClamping)>,
 
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
 }
 
 #[derive(Clone)]
@@ -111,19 +80,16 @@ pub struct TransformUpdateState {
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
             spatial_nodes: Vec::new(),
-            clip_nodes: Vec::new(),
-            clip_chains_descriptors: Vec::new(),
-            clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
             pending_scroll_offsets: FastHashMap::default(),
             pipelines_to_discard: FastHashSet::default(),
         }
     }
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
     pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
@@ -162,20 +128,17 @@ impl ClipScrollTree {
             match old_node.node_type {
                 SpatialNodeType::ScrollFrame(info) if info.external_id.is_some() => {
                     scroll_states.insert(info.external_id.unwrap(), info);
                 }
                 _ => {}
             }
         }
 
-        self.clip_nodes.clear();
         self.pipelines_to_discard.clear();
-        self.clip_chains = vec![ClipChain::empty(&DeviceIntRect::zero())];
-        self.clip_chains_descriptors.clear();
         scroll_states
     }
 
     pub fn scroll_node(
         &mut self,
         origin: LayoutPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping
@@ -215,31 +178,24 @@ impl ClipScrollTree {
             return false;
         }
         let node_index = self.find_nearest_scrolling_ancestor(node_index);
         self.spatial_nodes[node_index.0].scroll(scroll_location)
     }
 
     pub fn update_tree(
         &mut self,
-        screen_rect: &DeviceIntRect,
-        device_pixel_scale: DevicePixelScale,
-        clip_store: &mut ClipStore,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
         pan: WorldPoint,
         scene_properties: &SceneProperties,
     ) -> TransformPalette {
         let mut transform_palette = TransformPalette::new(self.spatial_nodes.len());
         if self.spatial_nodes.is_empty() {
             return transform_palette;
         }
 
-        self.clip_chains[0] = ClipChain::empty(screen_rect);
-
         let root_reference_frame_index = self.root_reference_frame_index();
         let mut state = TransformUpdateState {
             parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
             parent_accumulated_scroll_offset: LayoutVector2D::zero(),
             nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
             nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
             current_coordinate_system_id: CoordinateSystemId::root(),
             coordinate_system_relative_offset: LayoutVector2D::zero(),
@@ -250,28 +206,16 @@ impl ClipScrollTree {
         self.update_node(
             root_reference_frame_index,
             &mut state,
             &mut next_coordinate_system_id,
             &mut transform_palette,
             scene_properties,
         );
 
-        for clip_node in &mut self.clip_nodes {
-            clip_node.update(
-                device_pixel_scale,
-                clip_store,
-                resource_cache,
-                gpu_cache,
-                &mut self.clip_chains,
-                &self.spatial_nodes,
-            );
-        }
-        self.build_clip_chains(screen_rect);
-
         transform_palette
     }
 
     fn update_node(
         &mut self,
         node_index: SpatialNodeIndex,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
@@ -304,42 +248,16 @@ impl ClipScrollTree {
                 &mut state,
                 next_coordinate_system_id,
                 transform_palette,
                 scene_properties,
             );
         }
     }
 
-    pub fn build_clip_chains(&mut self, screen_rect: &DeviceIntRect) {
-        for descriptor in &self.clip_chains_descriptors {
-            // A ClipChain is an optional parent (which is another ClipChain) and a list of
-            // SpatialNode clipping nodes. Here we start the ClipChain with a clone of the
-            // parent's node, if necessary.
-            let mut chain = match descriptor.parent {
-                Some(index) => self.clip_chains[index.0].clone(),
-                None => ClipChain::empty(screen_rect),
-            };
-
-            // Now we walk through each ClipNode in the vector and extract their ClipChain nodes to
-            // construct the final list.
-            for clip_index in &descriptor.clips {
-                match self.clip_nodes[clip_index.0] {
-                    ClipNode { clip_chain_node: Some(ref node), .. } => {
-                        chain.add_node(node.clone());
-                    }
-                    ClipNode { .. } => warn!("Found uninitialized clipping ClipNode."),
-                };
-            }
-
-            chain.parent_index = descriptor.parent;
-            self.clip_chains[descriptor.index.0] = chain;
-        }
-    }
-
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         for node in &mut self.spatial_nodes {
             let external_id = match node.node_type {
                 SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
                 _ => continue,
             };
 
             if let Some(scrolling_state) = old_states.get(&external_id) {
@@ -347,32 +265,16 @@ impl ClipScrollTree {
             }
 
             if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
                 node.set_scroll_origin(&offset, clamping);
             }
         }
     }
 
-    pub fn add_clip_node(
-        &mut self,
-        parent_clip_chain_index: ClipChainIndex,
-        clip_sources_index: ClipSourcesIndex,
-    ) -> (ClipNodeIndex, ClipChainIndex) {
-        let clip_chain_index = self.allocate_clip_chain();
-        let node = ClipNode {
-            parent_clip_chain_index,
-            clip_sources_index,
-            clip_chain_index,
-            clip_chain_node: None,
-        };
-        let node_index = self.push_clip_node(node);
-        (node_index, clip_chain_index)
-    }
-
     pub fn add_scroll_frame(
         &mut self,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
@@ -415,32 +317,16 @@ impl ClipScrollTree {
         let node = SpatialNode::new_sticky_frame(
             parent_index,
             sticky_frame_info,
             pipeline_id,
         );
         self.add_spatial_node(node)
     }
 
-    pub fn add_clip_chain_descriptor(
-        &mut self,
-        parent: Option<ClipChainIndex>,
-        clips: Vec<ClipNodeIndex>
-    ) -> ClipChainIndex {
-        let index = self.allocate_clip_chain();
-        self.clip_chains_descriptors.push(ClipChainDescriptor { index, parent, clips });
-        index
-    }
-
-    pub fn push_clip_node(&mut self, node: ClipNode) -> ClipNodeIndex {
-        let index = ClipNodeIndex(self.clip_nodes.len());
-        self.clip_nodes.push(node);
-        index
-    }
-
     pub fn add_spatial_node(&mut self, node: SpatialNode) -> SpatialNodeIndex {
         let index = SpatialNodeIndex(self.spatial_nodes.len());
 
         // When the parent node is None this means we are adding the root.
         if let Some(parent_index) = node.parent {
             self.spatial_nodes[parent_index.0].add_child(index);
         }
 
@@ -497,20 +383,9 @@ impl ClipScrollTree {
         }
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
         if !self.spatial_nodes.is_empty() {
             self.print_node(self.root_reference_frame_index(), pt, clip_store);
         }
     }
-
-    pub fn allocate_clip_chain(&mut self) -> ClipChainIndex {
-        debug_assert!(!self.clip_chains.is_empty());
-        let new_clip_chain =self.clip_chains[0].clone();
-        self.clip_chains.push(new_clip_chain);
-        ClipChainIndex(self.clip_chains.len() - 1)
-    }
-
-    pub fn get_clip_chain(&self, index: ClipChainIndex) -> &ClipChain {
-        &self.clip_chains[index.0]
-    }
 }
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -2176,16 +2176,20 @@ impl Device {
         self.gl
             .blend_func(gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
     pub fn set_blend_mode_subpixel_dual_source(&self) {
         self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC1_COLOR);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
+    pub fn set_blend_mode_show_overdraw(&self) {
+        self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
+        self.gl.blend_equation(gl::FUNC_ADD);
+    }
 
     pub fn supports_extension(&self, extension: &str) -> bool {
         supports_extension(&self.extensions, extension)
     }
 
     pub fn echo_driver_messages(&self) {
         for msg in self.gl.get_debug_messages() {
             let level = match msg.severity {
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -7,98 +7,77 @@ use api::{AlphaType, BorderDetails, Bord
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
-use api::{TransformStyle, YuvColorSpace, YuvData};
-use clip::{ClipRegion, ClipSource, ClipSources, ClipSourcesIndex, ClipStore};
-use clip_scroll_tree::{ClipChainIndex, ClipNodeIndex, ClipScrollTree, SpatialNodeIndex};
+use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
+use clip::{ClipChainId, ClipRegion, ClipItem, ClipStore};
+use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
-use picture::PictureCompositeMode;
+use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
 use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
-use prim_store::{BorderSource, BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
-use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
+use prim_store::{BorderSource, BrushSegment, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
+use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
-use std::{f32, mem};
+use std::{f32, iter, mem};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers, recycle_vec};
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
 
 /// A data structure that keeps track of mapping between API ClipIds and the indices used
 /// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
 /// responsible for mapping both ClipId to ClipChainIndex and ClipId to SpatialNodeIndex.
 #[derive(Default)]
 pub struct ClipIdToIndexMapper {
-    clip_chain_map: FastHashMap<ClipId, ClipChainIndex>,
-    clip_node_map: FastHashMap<ClipId, ClipNodeIndex>,
+    clip_chain_map: FastHashMap<ClipId, ClipChainId>,
     spatial_node_map: FastHashMap<ClipId, SpatialNodeIndex>,
 }
 
 impl ClipIdToIndexMapper {
-    pub fn add_clip_chain(&mut self, id: ClipId, index: ClipChainIndex) {
+    pub fn add_clip_chain(&mut self, id: ClipId, index: ClipChainId) {
         let _old_value = self.clip_chain_map.insert(id, index);
         debug_assert!(_old_value.is_none());
     }
 
     pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
-        let parent_chain_index = self.get_clip_chain_index(parent_id);
-        self.add_clip_chain(id, parent_chain_index);
+        let parent_chain_id = self.get_clip_chain_id(parent_id);
+        self.add_clip_chain(id, parent_chain_id);
     }
 
     pub fn map_spatial_node(&mut self, id: ClipId, index: SpatialNodeIndex) {
         let _old_value = self.spatial_node_map.insert(id, index);
         debug_assert!(_old_value.is_none());
     }
 
-    pub fn map_clip_node(&mut self, id: ClipId, index: ClipNodeIndex) {
-        let _old_value = self.clip_node_map.insert(id, index);
-        debug_assert!(_old_value.is_none());
-    }
-
-    pub fn get_clip_chain_index(&self, id: &ClipId) -> ClipChainIndex {
+    pub fn get_clip_chain_id(&self, id: &ClipId) -> ClipChainId {
         self.clip_chain_map[id]
     }
 
-    pub fn get_clip_node_index(&self, id: ClipId) -> ClipNodeIndex {
-        match id {
-            ClipId::Clip(..) => {
-                self.clip_node_map[&id]
-            }
-            ClipId::Spatial(..) => {
-                // We could theoretically map back to the containing clip node with the current
-                // design, but we will eventually fully separate out clipping from spatial nodes
-                // in the display list. We don't ever need to do this anyway.
-                panic!("Tried to use positioning node as clip node.");
-            }
-            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
-        }
-    }
-
     pub fn get_spatial_node_index(&self, id: ClipId) -> SpatialNodeIndex {
         match id {
             ClipId::Clip(..) |
             ClipId::Spatial(..) => {
                 self.spatial_node_map[&id]
             }
             ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
         }
@@ -121,47 +100,45 @@ pub struct DisplayListFlattener<'a> {
     /// A set of pipelines that the caller has requested be made available as
     /// output textures.
     output_pipelines: &'a FastHashSet<PipelineId>,
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
-    /// A stack of scroll nodes used during display list processing to properly
-    /// parent new scroll nodes.
-    reference_frame_stack: Vec<(ClipId, SpatialNodeIndex)>,
-
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
     /// A stack of the current pictures.
-    picture_stack: Vec<PictureIndex>,
+    picture_stack: Vec<PrimitiveIndex>,
 
     /// A stack of the currently active shadows
-    shadow_stack: Vec<(Shadow, PictureIndex)>,
+    shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
-    pipeline_clip_chain_stack: Vec<ClipChainIndex>,
+    pipeline_clip_chain_stack: Vec<ClipChainId>,
 
     /// A list of scrollbar primitives.
     pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// The store of primitives.
     pub prim_store: PrimitiveStore,
 
     /// Information about all primitives involved in hit testing.
     pub hit_testing_runs: Vec<HitTestingRun>,
 
     /// The store which holds all complex clipping information.
     pub clip_store: ClipStore,
 
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
+
+    pub next_picture_id: u64,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         old_builder: FrameBuilder,
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
@@ -183,21 +160,21 @@ impl<'a> DisplayListFlattener<'a> {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
             scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
-            reference_frame_stack: Vec::new(),
             picture_stack: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
-            pipeline_clip_chain_stack: Vec::new(),
+            next_picture_id: old_builder.next_picture_id,
+            pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: old_builder.prim_store.recycle(),
             clip_store: old_builder.clip_store.recycle(),
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
@@ -281,17 +258,17 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayoutVector2D::zero());
 
         if self.config.enable_scrollbars {
             let scrollbar_rect = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(10.0, 70.0));
             let container_rect = LayoutRect::new(LayoutPoint::zero(), *frame_size);
             self.add_scroll_bar(
-                reference_frame_info,
+                reference_frame_info.spatial_node_index,
                 &LayoutPrimitiveInfo::new(scrollbar_rect),
                 DEFAULT_SCROLLBAR_COLOR,
                 ScrollbarInfo(scroll_frame_info.spatial_node_index, container_rect),
             );
         }
 
         self.pop_stacking_context();
     }
@@ -409,18 +386,16 @@ impl<'a> DisplayListFlattener<'a> {
             Some(scroll_node_id),
             pipeline_id,
             reference_frame.transform,
             reference_frame.perspective,
             reference_frame_relative_offset + item.rect().origin.to_vector(),
         );
 
         self.flatten_items(traversal, pipeline_id, LayoutVector2D::zero());
-
-        self.pop_reference_frame();
     }
 
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         item: &DisplayItemRef,
         stacking_context: &StackingContext,
@@ -509,17 +484,16 @@ impl<'a> DisplayListFlattener<'a> {
             iframe_pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
         );
 
         self.flatten_root(pipeline, &iframe_rect.size);
 
-        self.pop_reference_frame();
         self.pipeline_clip_chain_stack.pop();
     }
 
     fn flatten_item<'b>(
         &'b mut self,
         item: DisplayItemRef<'a, 'b>,
         pipeline_id: PipelineId,
         reference_frame_relative_offset: LayoutVector2D,
@@ -679,28 +653,62 @@ impl<'a> DisplayListFlattener<'a> {
                     *item.clip_rect(),
                     complex_clips,
                     info.image_mask,
                     &reference_frame_relative_offset,
                 );
                 self.add_clip_node(info.id, clip_and_scroll_ids.scroll_node_id, clip_region);
             }
             SpecificDisplayItem::ClipChain(ref info) => {
-                let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items())
-                    .map(|id| self.id_to_index_mapper.get_clip_node_index(id))
-                    .collect();
-                let parent = match info.parent {
-                    Some(id) => Some(
-                        self.id_to_index_mapper.get_clip_chain_index(&ClipId::ClipChain(id))
-                    ),
-                    None => self.pipeline_clip_chain_stack.last().cloned(),
+                // For a user defined clip-chain the parent (if specified) must
+                // refer to another user defined clip-chain. If none is specified,
+                // the parent is the root clip-chain for the given pipeline. This
+                // is used to provide a root clip chain for iframes.
+                let mut parent_clip_chain_id = match info.parent {
+                    Some(id) => {
+                        self.id_to_index_mapper.get_clip_chain_id(&ClipId::ClipChain(id))
+                    }
+                    None => {
+                        self.pipeline_clip_chain_stack.last().cloned().unwrap()
+                    }
                 };
-                let clip_chain_index =
-                    self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
-                self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_index);
+
+                // Create a linked list of clip chain nodes. To do this, we will
+                // create a clip chain node + clip source for each listed clip id,
+                // and link these together, with the root of this list parented to
+                // the parent clip chain node found above. For this API, the clip
+                // id that is specified for an existing clip chain node is used to
+                // get the index of the clip sources that define that clip node.
+
+                let mut clip_chain_id = parent_clip_chain_id;
+
+                // For each specified clip id
+                for item in self.get_clip_chain_items(pipeline_id, item.clip_chain_items()) {
+                    // Map the ClipId to an existing clip chain node.
+                    let item_clip_chain_id = self
+                        .id_to_index_mapper
+                        .get_clip_chain_id(&item);
+                    // Get the id of the clip sources entry for that clip chain node.
+                    let clip_item_range = self
+                        .clip_store
+                        .get_clip_chain(item_clip_chain_id)
+                        .clip_item_range;
+                    // Add a new clip chain node, which references the same clip sources, and
+                    // parent it to the current parent.
+                    clip_chain_id = self
+                        .clip_store
+                        .add_clip_chain(clip_item_range, parent_clip_chain_id);
+                    // For the next clip node, use this new clip chain node as the parent,
+                    // to form a linked list.
+                    parent_clip_chain_id = clip_chain_id;
+                }
+
+                // Map the last entry in the clip chain to the supplied ClipId. This makes
+                // this ClipId available as a source to other user defined clip chains.
+                self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_id);
             },
             SpecificDisplayItem::ScrollFrame(ref info) => {
                 self.flatten_scroll_frame(
                     &item,
                     info,
                     pipeline_id,
                     &clip_and_scroll_ids,
                     &reference_frame_relative_offset
@@ -730,44 +738,58 @@ impl<'a> DisplayListFlattener<'a> {
             }
             SpecificDisplayItem::PopAllShadows => {
                 self.pop_all_shadows();
             }
         }
         None
     }
 
-    fn add_clip_sources(
+    // Given a list of clip sources, a positioning node and
+    // a parent clip chain, return a new clip chain entry.
+    // If the supplied list of clip sources is empty, then
+    // just return the parent clip chain id directly.
+    fn build_clip_chain(
         &mut self,
-        clip_sources: Vec<ClipSource>,
+        clip_items: Vec<ClipItem>,
         spatial_node_index: SpatialNodeIndex,
-    ) -> Option<ClipSourcesIndex> {
-        if clip_sources.is_empty() {
-            None
+        parent_clip_chain_id: ClipChainId,
+    ) -> ClipChainId {
+        if clip_items.is_empty() {
+            parent_clip_chain_id
         } else {
-            Some(self.clip_store.insert(ClipSources::new(clip_sources, spatial_node_index)))
+            // Add a range of clip sources.
+            let clip_item_range = self
+                .clip_store
+                .add_clip_items(clip_items, spatial_node_index);
+
+            // Add clip chain node that references the clip source range.
+            self.clip_store.add_clip_chain(
+                clip_item_range,
+                parent_clip_chain_id,
+            )
         }
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
-        clip_sources: Option<ClipSourcesIndex>,
+        clip_chain_id: ClipChainId,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
 
         self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
             info.is_backface_visible && stacking_context.is_backface_visible,
-            clip_sources,
+            clip_chain_id,
             info.tag,
             container,
         )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
         info: &LayoutPrimitiveInfo,
@@ -790,141 +812,158 @@ impl<'a> DisplayListFlattener<'a> {
 
         self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_index: PrimitiveIndex,
-        clip_and_scroll: ScrollNodeAndClipChain,
+        spatial_node_index: SpatialNodeIndex,
     ) {
         // Add primitive to the top-most Picture on the stack.
-        let pic_index = self.picture_stack.last().unwrap();
-        let pic = &mut self.prim_store.pictures[pic_index.0];
-        pic.add_primitive(prim_index, clip_and_scroll);
+        let pic_prim_index = *self.picture_stack.last().unwrap();
+        let pic = self.prim_store.get_pic_mut(pic_prim_index);
+        pic.add_primitive(prim_index, spatial_node_index);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
-        clip_sources: Vec<ClipSource>,
+        clip_items: Vec<ClipItem>,
         container: PrimitiveContainer,
     ) {
         if !self.shadow_stack.is_empty() {
             // TODO(gw): Restructure this so we don't need to move the shadow
             //           stack out (borrowck due to create_primitive below).
             let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
-            for &(ref shadow, shadow_pic_index) in &shadow_stack {
+            for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
                 // Offset the local rect and clip rect by the shadow offset.
                 let mut info = info.clone();
                 info.rect = info.rect.translate(&shadow.offset);
                 info.clip_rect = info.clip_rect.translate(&shadow.offset);
 
                 // Offset any local clip sources by the shadow offset.
-                let clip_sources: Vec<ClipSource> = clip_sources
+                let clip_items: Vec<ClipItem> = clip_items
                     .iter()
                     .map(|cs| cs.offset(&shadow.offset))
                     .collect();
-                let clip_sources = self.add_clip_sources(
-                    clip_sources,
+                let clip_chain_id = self.build_clip_chain(
+                    clip_items,
                     clip_and_scroll.spatial_node_index,
+                    clip_and_scroll.clip_chain_id,
                 );
 
                 // Construct and add a primitive for the given shadow.
                 let shadow_prim_index = self.create_primitive(
                     &info,
-                    clip_sources,
+                    clip_chain_id,
                     container.create_shadow(shadow),
                 );
 
                 // Add the new primitive to the shadow picture.
-                let shadow_pic = &mut self.prim_store.pictures[shadow_pic_index.0];
-                shadow_pic.add_primitive(shadow_prim_index, clip_and_scroll);
+                let shadow_pic = self.prim_store.get_pic_mut(shadow_pic_prim_index);
+                shadow_pic.add_primitive(
+                    shadow_prim_index,
+                    clip_and_scroll.spatial_node_index,
+                );
             }
             self.shadow_stack = shadow_stack;
         }
 
         if container.is_visible() {
-            let clip_sources = self.add_clip_sources(
-                clip_sources,
+            let clip_chain_id = self.build_clip_chain(
+                clip_items,
                 clip_and_scroll.spatial_node_index,
+                clip_and_scroll.clip_chain_id,
             );
-            let prim_index = self.create_primitive(info, clip_sources, container);
+            let prim_index = self.create_primitive(info, clip_chain_id, container);
             if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
                 println!("Chasing {:?}", prim_index);
                 self.prim_store.chase_id = Some(prim_index);
             }
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+            self.add_primitive_to_draw_list(
+                prim_index,
+                clip_and_scroll.spatial_node_index,
+            );
         }
     }
 
+    fn get_next_picture_id(&mut self) -> PictureId {
+        let id = PictureId(self.next_picture_id);
+        self.next_picture_id += 1;
+        id
+    }
+
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
         spatial_node: ClipId,
         clipping_node: Option<ClipId>,
         glyph_raster_space: GlyphRasterSpace,
     ) {
+        let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(spatial_node);
         let clip_chain_id = match clipping_node {
-            Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_index(clipping_node),
-            None => ClipChainIndex::NO_CLIP,
+            Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_id(clipping_node),
+            None => ClipChainId::NONE,
         };
-        let clip_and_scroll = ScrollNodeAndClipChain::new(
-            self.id_to_index_mapper.get_spatial_node_index(spatial_node),
-            clip_chain_id
-        );
-
-        // Construct the necessary set of Picture primitives
-        // to draw this stacking context.
-        let current_reference_frame_index = self.current_reference_frame_index();
 
         // An arbitrary large clip rect. For now, we don't
         // specify a clip specific to the stacking context.
         // However, now that they are represented as Picture
         // primitives, we can apply any kind of clip mask
         // to them, as for a normal primitive. This is needed
         // to correctly handle some CSS cases (see #1957).
         let max_clip = LayoutRect::max_rect();
 
         // If there is no root picture, create one for the main framebuffer.
         if self.sc_stack.is_empty() {
             // Should be no pictures at all if the stack is empty...
-            debug_assert!(self.prim_store.pictures.is_empty());
+            debug_assert!(self.prim_store.primitives.is_empty());
             debug_assert_eq!(transform_style, TransformStyle::Flat);
 
             // This picture stores primitive runs for items on the
             // main framebuffer.
-            let pic_index = self.prim_store.add_image_picture(
+            let picture = PicturePrimitive::new_image(
+                self.get_next_picture_id(),
                 None,
                 false,
                 pipeline_id,
-                current_reference_frame_index,
+                spatial_node_index,
                 None,
                 true,
             );
 
-            self.picture_stack.push(pic_index);
+            let prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                true,
+                ClipChainId::NONE,
+                None,
+                PrimitiveContainer::Brush(BrushPrimitive::new_picture(picture)),
+            );
+
+            self.picture_stack.push(prim_index);
         } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
             // If we have a mix-blend-mode, and we aren't the primary framebuffer,
             // the stacking context needs to be isolated to blend correctly as per
             // the CSS spec.
             // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
             //           is hacky and depends on how we create a root stacking context
             //           during flattening.
-            let parent_pic_index = self.picture_stack.last().unwrap();
-            let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
+            let parent_prim_index = *self.picture_stack.last().unwrap();
+            let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
 
             // If not already isolated for some other reason,
             // make this picture as isolated.
             if parent_pic.composite_mode.is_none() {
                 parent_pic.composite_mode = Some(PictureCompositeMode::Blit);
             }
         }
 
@@ -948,124 +987,127 @@ impl<'a> DisplayListFlattener<'a> {
 
         // If this is participating in a 3d context *and* the
         // parent was not a 3d context, then this must be the
         // element that establishes a new 3d context.
         let establishes_3d_context =
             participating_in_3d_context &&
             parent_transform_style == TransformStyle::Flat;
 
-        let rendering_context_3d_pic_index = if establishes_3d_context {
+        let rendering_context_3d_prim_index = if establishes_3d_context {
             // If establishing a 3d context, we need to add a picture
             // that will be the container for all the planes and any
             // un-transformed content.
-            let container_index = self.prim_store.add_image_picture(
+            let picture = PicturePrimitive::new_image(
+                self.get_next_picture_id(),
                 None,
                 false,
                 pipeline_id,
-                current_reference_frame_index,
+                spatial_node_index,
                 None,
                 true,
             );
 
-            let prim = BrushPrimitive::new_picture(container_index);
+            let prim = BrushPrimitive::new_picture(picture);
 
             let prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 is_backface_visible,
-                None,
+                clip_chain_id,
                 None,
                 PrimitiveContainer::Brush(prim),
             );
 
-            let parent_pic_index = *self.picture_stack.last().unwrap();
+            let parent_prim_index = *self.picture_stack.last().unwrap();
 
-            let pic = &mut self.prim_store.pictures[parent_pic_index.0];
-            pic.add_primitive(prim_index, clip_and_scroll);
+            let pic = self.prim_store.get_pic_mut(parent_prim_index);
+            pic.add_primitive(prim_index, spatial_node_index);
 
-            self.picture_stack.push(container_index);
+            self.picture_stack.push(prim_index);
 
-            Some(container_index)
+            Some(prim_index)
         } else {
             None
         };
 
-        let mut parent_pic_index = if !establishes_3d_context && participating_in_3d_context {
+        let mut parent_prim_index = if !establishes_3d_context && participating_in_3d_context {
             // If we're in a 3D context, we will parent the picture
             // to the first stacking context we find that is a
             // 3D rendering context container. This follows the spec
             // by hoisting these items out into the same 3D context
             // for plane splitting.
             self.sc_stack
                 .iter()
                 .rev()
-                .find(|sc| sc.rendering_context_3d_pic_index.is_some())
-                .map(|sc| sc.rendering_context_3d_pic_index.unwrap())
+                .find(|sc| sc.rendering_context_3d_prim_index.is_some())
+                .map(|sc| sc.rendering_context_3d_prim_index.unwrap())
                 .unwrap()
         } else {
             *self.picture_stack.last().unwrap()
         };
 
         // For each filter, create a new image with that composite mode.
         for filter in composite_ops.filters.iter().rev() {
-            let src_pic_index = self.prim_store.add_image_picture(
+            let picture = PicturePrimitive::new_image(
+                self.get_next_picture_id(),
                 Some(PictureCompositeMode::Filter(*filter)),
                 false,
                 pipeline_id,
-                current_reference_frame_index,
+                spatial_node_index,
                 None,
                 true,
             );
 
-            let src_prim = BrushPrimitive::new_picture(src_pic_index);
+            let src_prim = BrushPrimitive::new_picture(picture);
             let src_prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 is_backface_visible,
-                None,
+                clip_chain_id,
                 None,
                 PrimitiveContainer::Brush(src_prim),
             );
 
-            let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
-            parent_pic_index = src_pic_index;
+            let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
+            parent_prim_index = src_prim_index;
 
-            parent_pic.add_primitive(src_prim_index, clip_and_scroll);
+            parent_pic.add_primitive(src_prim_index, spatial_node_index);
 
-            self.picture_stack.push(src_pic_index);
+            self.picture_stack.push(src_prim_index);
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
-            let src_pic_index = self.prim_store.add_image_picture(
+            let picture = PicturePrimitive::new_image(
+                self.get_next_picture_id(),
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
                 false,
                 pipeline_id,
-                current_reference_frame_index,
+                spatial_node_index,
                 None,
                 true,
             );
 
-            let src_prim = BrushPrimitive::new_picture(src_pic_index);
+            let src_prim = BrushPrimitive::new_picture(picture);
 
             let src_prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 is_backface_visible,
-                None,
+                clip_chain_id,
                 None,
                 PrimitiveContainer::Brush(src_prim),
             );
 
-            let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
-            parent_pic_index = src_pic_index;
-            parent_pic.add_primitive(src_prim_index, clip_and_scroll);
+            let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
+            parent_prim_index = src_prim_index;
+            parent_pic.add_primitive(src_prim_index, spatial_node_index);
 
-            self.picture_stack.push(src_pic_index);
+            self.picture_stack.push(src_prim_index);
         }
 
         // By default, this picture will be collapsed into
         // the owning target.
         let mut composite_mode = None;
         let mut frame_output_pipeline_id = None;
 
         // If this stacking context if the root of a pipeline, and the caller
@@ -1086,78 +1128,79 @@ impl<'a> DisplayListFlattener<'a> {
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
             composite_mode = Some(PictureCompositeMode::Blit);
         }
 
         // Add picture for this actual stacking context contents to render into.
-        let pic_index = self.prim_store.add_image_picture(
+        let picture = PicturePrimitive::new_image(
+            self.get_next_picture_id(),
             composite_mode,
             participating_in_3d_context,
             pipeline_id,
-            current_reference_frame_index,
+            spatial_node_index,
             frame_output_pipeline_id,
             true,
         );
 
         // Create a brush primitive that draws this picture.
-        let sc_prim = BrushPrimitive::new_picture(pic_index);
+        let sc_prim = BrushPrimitive::new_picture(picture);
 
         // Add the brush to the parent picture.
         let sc_prim_index = self.prim_store.add_primitive(
             &LayoutRect::zero(),
             &max_clip,
             is_backface_visible,
-            None,
+            clip_chain_id,
             None,
             PrimitiveContainer::Brush(sc_prim),
         );
 
-        let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
-        parent_pic.add_primitive(sc_prim_index, clip_and_scroll);
+        let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
+        parent_pic.add_primitive(sc_prim_index, spatial_node_index);
 
         // Add this as the top-most picture for primitives to be added to.
-        self.picture_stack.push(pic_index);
+        self.picture_stack.push(sc_prim_index);
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         let sc = FlattenedStackingContext {
             composite_ops,
             is_backface_visible,
             pipeline_id,
             transform_style,
-            rendering_context_3d_pic_index,
+            rendering_context_3d_prim_index,
             glyph_raster_space,
         };
 
         self.sc_stack.push(sc);
     }
 
     pub fn pop_stacking_context(&mut self) {
         let sc = self.sc_stack.pop().unwrap();
 
         // Always pop at least the main picture for this stacking context.
         let mut pop_count = 1;
 
         // Remove the picture for any filter/mix-blend-mode effects.
         pop_count += sc.composite_ops.count();
 
         // Remove the 3d context container if created
-        if sc.rendering_context_3d_pic_index.is_some() {
+        if sc.rendering_context_3d_prim_index.is_some() {
             pop_count += 1;
         }
 
         for _ in 0 .. pop_count {
-            let pic_index = self
+            let prim_index = self
                 .picture_stack
                 .pop()
                 .expect("bug: mismatched picture stack");
-            self.prim_store.optimize_picture_if_possible(pic_index);
+            self.prim_store.optimize_picture_if_possible(prim_index);
         }
 
         // By the time the stacking context stack is empty, we should
         // also have cleared the picture stack.
         if self.sc_stack.is_empty() {
             self.picture_stack.pop().expect("bug: picture stack invalid");
             debug_assert!(self.picture_stack.is_empty());
         }
@@ -1180,31 +1223,26 @@ impl<'a> DisplayListFlattener<'a> {
         let parent_index = parent_id.map(|id| self.id_to_index_mapper.get_spatial_node_index(id));
         let index = self.clip_scroll_tree.add_reference_frame(
             parent_index,
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
-        self.reference_frame_stack.push((reference_frame_id, index));
         self.id_to_index_mapper.map_spatial_node(reference_frame_id, index);
 
         match parent_id {
             Some(ref parent_id) =>
                 self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
-            _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainIndex::NO_CLIP),
+            _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainId::NONE),
         }
         index
     }
 
-    pub fn current_reference_frame_index(&self) -> SpatialNodeIndex {
-        self.reference_frame_stack.last().unwrap().1
-    }
-
     pub fn setup_viewport_offset(
         &mut self,
         inner_rect: DeviceUintRect,
         device_pixel_scale: DevicePixelScale,
     ) {
         let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
         let root_id = self.clip_scroll_tree.root_reference_frame_index();
         let root_node = &mut self.clip_scroll_tree.spatial_nodes[root_id.0];
@@ -1240,34 +1278,62 @@ impl<'a> DisplayListFlattener<'a> {
         );
     }
 
     pub fn add_clip_node<I>(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         clip_region: ClipRegion<I>,
-    ) -> ClipChainIndex
+    ) -> ClipChainId
     where
         I: IntoIterator<Item = ComplexClipRegion>
     {
-        let parent_clip_chain_index = self.id_to_index_mapper.get_clip_chain_index(&parent_id);
+        // Add a new ClipNode - this is a ClipId that identifies a list of clip items,
+        // and the positioning node associated with those clip sources.
+
+        // Map from parent ClipId to existing clip-chain.
+        let parent_clip_chain_index = self
+            .id_to_index_mapper
+            .get_clip_chain_id(&parent_id);
+        // Map the ClipId for the positioning node to a spatial node index.
         let spatial_node = self.id_to_index_mapper.get_spatial_node_index(parent_id);
 
-        let clip_sources = ClipSources::from_region(clip_region, spatial_node);
-        let handle = self.clip_store.insert(clip_sources);
+        // Build the clip sources from the supplied region.
+        // TODO(gw): We should fix this up to take advantage of the recent
+        //           work to avoid heap allocations where possible!
+        let clip_rect = iter::once(ClipItem::Rectangle(clip_region.main, ClipMode::Clip));
+        let clip_image = clip_region.image_mask.map(ClipItem::Image);
+        let clips_complex = clip_region.complex_clips
+            .into_iter()
+            .map(|complex| ClipItem::new_rounded_rect(
+                complex.rect,
+                complex.radii,
+                complex.mode,
+            ));
+        let clips = clip_rect.chain(clip_image).chain(clips_complex).collect();
 
-        let (node_index, clip_chain_index) = self.clip_scroll_tree.add_clip_node(
-            parent_clip_chain_index,
-            handle,
-        );
-        self.id_to_index_mapper.map_spatial_node(new_node_id, spatial_node);
-        self.id_to_index_mapper.map_clip_node(new_node_id, node_index);
-        self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_index);
-        clip_chain_index
+        // Add those clip sources to the clip store.
+        let clip_item_range = self
+            .clip_store
+            .add_clip_items(clips, spatial_node);
+
+        // Add a mapping for this ClipId in case it's referenced as a positioning node.
+        self.id_to_index_mapper
+            .map_spatial_node(new_node_id, spatial_node);
+
+        // Add the new clip chain entry
+        let clip_chain_id = self
+            .clip_store
+            .add_clip_chain(clip_item_range, parent_clip_chain_index);
+
+        // Map the supplied ClipId -> clip chain id.
+        self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_id);
+
+        clip_chain_id
     }
 
     pub fn add_scroll_frame(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
@@ -1284,28 +1350,23 @@ impl<'a> DisplayListFlattener<'a> {
             content_size,
             scroll_sensitivity,
         );
         self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
         self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
         node_index
     }
 
-    pub fn pop_reference_frame(&mut self) {
-        self.reference_frame_stack.pop();
-    }
-
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
     ) {
         let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let current_reference_frame_index = self.current_reference_frame_index();
         let max_clip = LayoutRect::max_rect();
 
         // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
         // "the image that would be generated by applying to the shadow a
         // Gaussian blur with a standard deviation equal to half the blur radius."
         let std_deviation = shadow.blur_radius * 0.5;
 
         // If the shadow has no blur, any elements will get directly rendered
@@ -1313,54 +1374,58 @@ impl<'a> DisplayListFlattener<'a> {
         // into an intermediate surface. In this case, we will need to apply
         // the local clip rect to primitives.
         let apply_local_clip_rect = shadow.blur_radius == 0.0;
 
         // Create a picture that the shadow primitives will be added to. If the
         // blur radius is 0, the code in Picture::prepare_for_render will
         // detect this and mark the picture to be drawn directly into the
         // parent picture, which avoids an intermediate surface and blur.
-        let shadow_pic_index = self.prim_store.add_image_picture(
+        let shadow_pic = PicturePrimitive::new_image(
+            self.get_next_picture_id(),
             Some(PictureCompositeMode::Filter(FilterOp::Blur(std_deviation))),
             false,
             pipeline_id,
-            current_reference_frame_index,
+            clip_and_scroll.spatial_node_index,
             None,
             apply_local_clip_rect,
         );
 
         // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(shadow_pic_index);
+        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
         let shadow_prim_index = self.prim_store.add_primitive(
             &LayoutRect::zero(),
             &max_clip,
             info.is_backface_visible,
-            None,
+            clip_and_scroll.clip_chain_id,
             None,
             PrimitiveContainer::Brush(shadow_prim),
         );
 
         // Add the shadow primitive. This must be done before pushing this
         // picture on to the shadow stack, to avoid infinite recursion!
-        self.add_primitive_to_draw_list(shadow_prim_index, clip_and_scroll);
-        self.shadow_stack.push((shadow, shadow_pic_index));
+        self.add_primitive_to_draw_list(
+            shadow_prim_index,
+            clip_and_scroll.spatial_node_index,
+        );
+        self.shadow_stack.push((shadow, shadow_prim_index));
     }
 
     pub fn pop_all_shadows(&mut self) {
         assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
         self.shadow_stack.clear();
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
-        extra_clips: Vec<ClipSource>,
+        extra_clips: Vec<ClipItem>,
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
             // testing. This allows specifying invisible hit testing areas.
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             return;
         }
 
@@ -1392,39 +1457,39 @@ impl<'a> DisplayListFlattener<'a> {
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_scroll_bar(
         &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
+        spatial_node_index: SpatialNodeIndex,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
         scrollbar_info: ScrollbarInfo,
     ) {
         if color.a == 0.0 {
             return;
         }
 
         let prim = BrushPrimitive::new(
             BrushKind::new_solid(color),
             None,
         );
 
         let prim_index = self.create_primitive(
             info,
-            None,
+            ClipChainId::NONE,
             PrimitiveContainer::Brush(prim),
         );
 
         self.add_primitive_to_draw_list(
             prim_index,
-            clip_and_scroll,
+            spatial_node_index,
         );
 
         self.scrollbar_prims.push(ScrollbarPrimitive {
             prim_index,
             scroll_frame_index: scrollbar_info.0,
             frame_rect: scrollbar_info.1,
         });
     }
@@ -1446,17 +1511,17 @@ impl<'a> DisplayListFlattener<'a> {
         let extra_clips = match style {
             LineStyle::Solid => {
                 Vec::new()
             }
             LineStyle::Wavy |
             LineStyle::Dotted |
             LineStyle::Dashed => {
                 vec![
-                    ClipSource::new_line_decoration(
+                    ClipItem::new_line_decoration(
                         info.rect,
                         style,
                         orientation,
                         wavy_line_thickness,
                     ),
                 ]
             }
         };
@@ -1810,17 +1875,17 @@ impl<'a> DisplayListFlattener<'a> {
                 *text_color,
                 font_instance.bg_color,
                 render_mode,
                 flags,
                 font_instance.synthetic_italics,
                 font_instance.platform_options,
                 font_instance.variations.clone(),
             );
-            TextRunPrimitiveCpu::new(
+            TextRunPrimitive::new(
                 prim_font,
                 run_offset,
                 glyph_range,
                 Vec::new(),
                 false,
                 glyph_raster_space,
             )
         };
@@ -1921,17 +1986,17 @@ impl<'a> DisplayListFlattener<'a> {
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
         ScrollNodeAndClipChain::new(
             self.id_to_index_mapper.get_spatial_node_index(info.scroll_node_id),
-            self.id_to_index_mapper.get_clip_chain_index(&info.clip_node_id())
+            self.id_to_index_mapper.get_clip_chain_id(&info.clip_node_id())
         )
     }
 
     pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
 }
 
@@ -1978,13 +2043,13 @@ struct FlattenedStackingContext {
     glyph_raster_space: GlyphRasterSpace,
 
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
     /// If Some(..), this stacking context establishes a new
     /// 3d rendering context, and the value is the picture
     // index of the 3d context container.
-    rendering_context_3d_pic_index: Option<PictureIndex>,
+    rendering_context_3d_prim_index: Option<PrimitiveIndex>,
 }
 
 #[derive(Debug)]
 pub struct ScrollbarInfo(pub SpatialNodeIndex, pub LayoutRect);
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.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 api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
+use api::{ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, WorldPoint};
-use clip::{ClipChain, ClipStore};
+use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::PictureSurface;
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveStore, Transform};
@@ -19,17 +19,17 @@ use render_backend::FrameId;
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use spatial_node::SpatialNode;
 use std::{mem, f32};
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
 use tiling::{ScrollbarPrimitive, SpecialRenderPasses};
-use util::{self, WorldToLayoutFastTransform};
+use util;
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ChasePrimitive {
     Nothing,
     LocalRect(LayoutRect),
@@ -53,50 +53,49 @@ pub struct FrameBuilderConfig {
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
+    pub next_picture_id: u64,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
     pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub scene_id: u64,
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_rect: DeviceIntRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
-    pub clip_chains: &'a [ClipChain],
     pub transforms: &'a TransformPalette,
     pub max_local_clip: LayoutRect,
 }
 
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
 }
 
-pub struct PictureContext<'a> {
+pub struct PictureContext {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
-    pub original_reference_frame_index: Option<SpatialNodeIndex>,
-    pub display_list: &'a BuiltDisplayList,
-    pub inv_world_transform: Option<WorldToLayoutFastTransform>,
+    pub spatial_node_index: SpatialNodeIndex,
+    pub original_spatial_node_index: SpatialNodeIndex,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
@@ -109,35 +108,29 @@ impl PictureState {
             tasks: Vec::new(),
             has_non_root_coord_system: false,
             local_rect_changed: false,
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
-    pub clip_chain: &'a ClipChain,
     pub scroll_node: &'a SpatialNode,
     pub spatial_node_index: SpatialNodeIndex,
     pub transform: Transform<'a>,
-    pub local_clip_rect: LayoutRect,
 }
 
 impl<'a> PrimitiveRunContext<'a> {
     pub fn new(
-        clip_chain: &'a ClipChain,
         scroll_node: &'a SpatialNode,
         spatial_node_index: SpatialNodeIndex,
-        local_clip_rect: LayoutRect,
         transform: Transform<'a>,
     ) -> Self {
         PrimitiveRunContext {
-            clip_chain,
             scroll_node,
-            local_clip_rect,
             spatial_node_index,
             transform,
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
@@ -145,16 +138,17 @@ impl FrameBuilder {
             hit_testing_runs: Vec::new(),
             scrollbar_prims: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
+            next_picture_id: 0,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
             },
         }
@@ -172,16 +166,17 @@ impl FrameBuilder {
             scrollbar_prims: flattener.scrollbar_prims,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             screen_rect,
             background_color,
             window_size,
             scene_id,
             config: flattener.config,
+            next_picture_id: flattener.next_picture_id,
         }
     }
 
     /// 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,
         clip_scroll_tree: &ClipScrollTree,
@@ -192,39 +187,34 @@ impl FrameBuilder {
         special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         transform_palette: &TransformPalette,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
-        if self.prim_store.pictures.is_empty() {
+        if self.prim_store.primitives.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
-        let root_spatial_node =
-            &clip_scroll_tree.spatial_nodes[clip_scroll_tree.root_reference_frame_index().0];
-
-        let display_list = &pipelines
-            .get(&root_spatial_node.pipeline_id)
-            .expect("No display list?")
-            .display_list;
+        let root_prim_index = PrimitiveIndex(0);
+        let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
+        let root_spatial_node = &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0];
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let frame_context = FrameBuildingContext {
             scene_id: self.scene_id,
             device_pixel_scale,
             scene_properties,
             pipelines,
             screen_rect: self.screen_rect.to_i32(),
             clip_scroll_tree,
-            clip_chains: &clip_scroll_tree.clip_chains,
             transforms: transform_palette,
             max_local_clip: LayoutRect::new(
                 LayoutPoint::new(-MAX_CLIP_COORD, -MAX_CLIP_COORD),
                 LayoutSize::new(2.0 * MAX_CLIP_COORD, 2.0 * MAX_CLIP_COORD),
             ),
         };
 
         let mut frame_state = FrameBuildingState {
@@ -233,56 +223,58 @@ impl FrameBuilder {
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             special_render_passes,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_spatial_node.pipeline_id,
-            prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
-            original_reference_frame_index: None,
-            display_list,
-            inv_world_transform: None,
+            prim_runs: mem::replace(
+                &mut self.prim_store.get_pic_mut(root_prim_index).runs,
+                Vec::new(),
+            ),
+            spatial_node_index: root_spatial_node_index,
+            original_spatial_node_index: root_spatial_node_index,
             apply_local_clip_rect: true,
             inflation_factor: 0.0,
             allow_subpixel_aa: true,
         };
 
         let mut pic_state = PictureState::new();
 
         self.prim_store.reset_prim_visibility();
         self.prim_store.prepare_prim_runs(
             &pic_context,
             &mut pic_state,
             &frame_context,
             &mut frame_state,
         );
 
-        let pic = &mut self.prim_store.pictures[0];
+        let pic = self.prim_store.get_pic_mut(root_prim_index);
         pic.runs = pic_context.prim_runs;
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(frame_context.screen_rect),
-            PrimitiveIndex(0),
+            root_prim_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.surface = Some(PictureSurface::RenderTask(render_task_id));
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
-            let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
+            let metadata = &mut self.prim_store.primitives[scrollbar_prim.prim_index.0].metadata;
             let scroll_frame = &clip_scroll_tree.spatial_nodes[scrollbar_prim.scroll_frame_index.0];
 
             // Invalidate what's in the cache so it will get rebuilt.
             gpu_cache.invalidate(&metadata.gpu_location);
 
             let scrollable_distance = scroll_frame.scrollable_size().height;
             if scrollable_distance <= 0.0 {
                 metadata.local_clip_rect.size = LayoutSize::zero();
@@ -326,21 +318,16 @@ impl FrameBuilder {
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
         gpu_cache.begin_frame();
 
         let transform_palette = clip_scroll_tree.update_tree(
-            &self.screen_rect.to_i32(),
-            device_pixel_scale,
-            &mut self.clip_store,
-            resource_cache,
-            gpu_cache,
             pan,
             scene_properties,
         );
 
         self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
         let mut render_tasks = RenderTaskTree::new(frame_id);
 
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, WorldPoint};
-use clip::{ClipSource, ClipStore, rounded_rectangle_contains_point};
-use clip_node::ClipNode;
-use clip_scroll_tree::{ClipChainIndex, ClipNodeIndex, SpatialNodeIndex, ClipScrollTree};
+use clip::{ClipNodeIndex, ClipChainNode, ClipNode, ClipItem, ClipStore};
+use clip::{ClipChainId, rounded_rectangle_contains_point};
+use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 use internal_types::FastHashMap;
 use prim_store::ScrollNodeAndClipChain;
 use util::LayoutToWorldFastTransform;
 
 /// A copy of important clip scroll node data to use during hit testing. This a copy of
 /// data from the ClipScrollTree that will persist as a new frame is under construction,
 /// allowing hit tests consistent with the currently rendered frame.
 pub struct HitTestSpatialNode {
@@ -26,58 +26,33 @@ pub struct HitTestSpatialNode {
 }
 
 pub struct HitTestClipNode {
     /// The positioning node for this clip node.
     spatial_node: SpatialNodeIndex,
 
     /// A particular point must be inside all of these regions to be considered clipped in
     /// for the purposes of a hit test.
-    regions: Vec<HitTestRegion>,
+    region: HitTestRegion,
 }
 
 impl HitTestClipNode {
-    fn new(node: &ClipNode, clip_store: &ClipStore) -> Self {
-        let clips = &clip_store[node.clip_sources_index];
-        let regions = clips.clips().iter().map(|source| {
-            match source.0 {
-                ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
-                ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
-                    HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
-                ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
-                ClipSource::LineDecoration(_) |
-                ClipSource::BoxShadow(_) => {
-                    unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
-                }
-            }
-        }).collect();
+    fn new(node: &ClipNode) -> Self {
+        let region = match node.item {
+            ClipItem::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
+            ClipItem::RoundedRectangle(ref rect, ref radii, ref mode) =>
+                HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
+            ClipItem::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
+            ClipItem::LineDecoration(_) |
+            ClipItem::BoxShadow(_) => HitTestRegion::Invalid,
+        };
 
         HitTestClipNode {
-            spatial_node: clips.spatial_node_index,
-            regions,
-        }
-    }
-}
-
-/// A description of a clip chain in the HitTester. This is used to describe
-/// hierarchical clip scroll nodes as well as ClipChains, so that they can be
-/// handled the same way during hit testing. Once we represent all ClipChains
-/// using ClipChainDescriptors, we can get rid of this and just use the
-/// ClipChainDescriptor here.
-#[derive(Clone)]
-struct HitTestClipChainDescriptor {
-    parent: Option<ClipChainIndex>,
-    clips: Vec<ClipNodeIndex>,
-}
-
-impl HitTestClipChainDescriptor {
-    fn empty() -> HitTestClipChainDescriptor {
-        HitTestClipChainDescriptor {
-            parent: None,
-            clips: Vec::new(),
+            spatial_node: node.spatial_node_index,
+            region,
         }
     }
 }
 
 #[derive(Clone)]
 pub struct HitTestingItem {
     rect: LayoutRect,
     clip_rect: LayoutRect,
@@ -95,40 +70,42 @@ impl HitTestingItem {
         }
     }
 }
 
 #[derive(Clone)]
 pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ScrollNodeAndClipChain);
 
 enum HitTestRegion {
+    Invalid,
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
 }
 
 impl HitTestRegion {
     pub fn contains(&self, point: &LayoutPoint) -> bool {
         match *self {
             HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) =>
                 rectangle.contains(point),
             HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) =>
                 !rectangle.contains(point),
             HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
                 rounded_rectangle_contains_point(point, &rect, &radii),
             HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                 !rounded_rectangle_contains_point(point, &rect, &radii),
+            HitTestRegion::Invalid => true,
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
     spatial_nodes: Vec<HitTestSpatialNode>,
     clip_nodes: Vec<HitTestClipNode>,
-    clip_chains: Vec<HitTestClipChainDescriptor>,
+    clip_chains: Vec<ClipChainNode>,
     pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
 }
 
 impl HitTester {
     pub fn new(
         runs: &Vec<HitTestingRun>,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
@@ -146,110 +123,106 @@ impl HitTester {
 
     fn read_clip_scroll_tree(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
     ) {
         self.spatial_nodes.clear();
         self.clip_chains.clear();
-        self.clip_chains.resize(
-            clip_scroll_tree.clip_chains.len(),
-            HitTestClipChainDescriptor::empty()
-        );
+        self.clip_nodes.clear();
 
         for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
             let index = SpatialNodeIndex(index);
 
             // If we haven't already seen a node for this pipeline, record this one as the root
             // node.
             self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
 
             self.spatial_nodes.push(HitTestSpatialNode {
                 pipeline_id: node.pipeline_id,
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
             });
         }
 
-        for (index, node) in clip_scroll_tree.clip_nodes.iter().enumerate() {
-            self.clip_nodes.push(HitTestClipNode::new(node, clip_store));
-            let clip_chain = self.clip_chains.get_mut(node.clip_chain_index.0).unwrap();
-            clip_chain.parent =
-                clip_scroll_tree.get_clip_chain(node.clip_chain_index).parent_index;
-            clip_chain.clips = vec![ClipNodeIndex(index)];
+        for node in &clip_store.clip_nodes {
+            self.clip_nodes.push(HitTestClipNode::new(node));
         }
 
-        for descriptor in &clip_scroll_tree.clip_chains_descriptors {
-            let clip_chain = self.clip_chains.get_mut(descriptor.index.0).unwrap();
-            clip_chain.parent = clip_scroll_tree.get_clip_chain(descriptor.index).parent_index;
-            clip_chain.clips = descriptor.clips.clone();
-        }
+        self.clip_chains
+            .extend_from_slice(&clip_store.clip_chain_nodes);
     }
 
     fn is_point_clipped_in_for_clip_chain(
         &self,
         point: WorldPoint,
-        clip_chain_index: ClipChainIndex,
+        clip_chain_id: ClipChainId,
         test: &mut HitTest
     ) -> bool {
-        if let Some(result) = test.get_from_clip_chain_cache(clip_chain_index) {
+        if clip_chain_id == ClipChainId::NONE {
+            return true;
+        }
+
+        if let Some(result) = test.get_from_clip_chain_cache(clip_chain_id) {
             return result == ClippedIn::ClippedIn;
         }
 
-        let descriptor = &self.clip_chains[clip_chain_index.0];
-        let parent_clipped_in = match descriptor.parent {
-            None => true,
-            Some(parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
-        };
+        let descriptor = &self.clip_chains[clip_chain_id.0 as usize];
+        let parent_clipped_in = self.is_point_clipped_in_for_clip_chain(
+            point,
+            descriptor.parent_clip_chain_id,
+            test,
+        );
 
         if !parent_clipped_in {
-            test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
+            test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
             return false;
         }
 
-        for clip_node_index in &descriptor.clips {
-            if !self.is_point_clipped_in_for_clip_node(point, *clip_node_index, test) {
-                test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::NotClippedIn);
+        for i in 0 .. descriptor.clip_item_range.count {
+            let clip_node_index = ClipNodeIndex(descriptor.clip_item_range.index.0 + i);
+            if !self.is_point_clipped_in_for_clip_node(point, clip_node_index, test) {
+                test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
                 return false;
             }
         }
 
-        test.set_in_clip_chain_cache(clip_chain_index, ClippedIn::ClippedIn);
+        test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::ClippedIn);
         true
     }
 
     fn is_point_clipped_in_for_clip_node(
         &self,
         point: WorldPoint,
         node_index: ClipNodeIndex,
         test: &mut HitTest
     ) -> bool {
         if let Some(clipped_in) = test.node_cache.get(&node_index) {
             return *clipped_in == ClippedIn::ClippedIn;
         }
 
-        let node = &self.clip_nodes[node_index.0];
-        let transform = self.spatial_nodes[node.spatial_node.0].world_viewport_transform;
+        let node = &self.clip_nodes[node_index.0 as usize];
+        let transform = self
+            .spatial_nodes[node.spatial_node.0 as usize]
+            .world_viewport_transform;
         let transformed_point = match transform
             .inverse()
             .and_then(|inverted| inverted.transform_point2d(&point))
         {
             Some(point) => point,
             None => {
                 test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
                 return false;
             }
         };
 
-        for region in &node.regions {
-            if !region.contains(&transformed_point) {
-                test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
-                return false;
-            }
+        if !node.region.contains(&transformed_point) {
+            test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
+            return false;
         }
 
         test.node_cache.insert(node_index, ClippedIn::ClippedIn);
         true
     }
 
     pub fn find_node_under_point(&self, mut test: HitTest) -> Option<SpatialNodeIndex> {
         let point = test.get_absolute_point(self);
@@ -268,19 +241,19 @@ impl HitTester {
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) ||
                     !item.clip_rect.contains(&point_in_layer) {
                     continue;
                 }
 
-                let clip_chain_index = clip_and_scroll.clip_chain_index;
+                let clip_chain_id = clip_and_scroll.clip_chain_id;
                 clipped_in |=
-                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test);
+                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                 if !clipped_in {
                     break;
                 }
 
                 return Some(spatial_node_index);
             }
         }
 
@@ -312,19 +285,19 @@ impl HitTester {
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) ||
                     !item.clip_rect.contains(&point_in_layer) {
                     continue;
                 }
 
-                let clip_chain_index = clip_and_scroll.clip_chain_index;
+                let clip_chain_id = clip_and_scroll.clip_chain_id;
                 clipped_in = clipped_in ||
-                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test);
+                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                 if !clipped_in {
                     break;
                 }
 
                 // Don't hit items with backface-visibility:hidden if they are facing the back.
                 if !item.is_backface_visible {
                     if *facing_backwards.get_or_insert_with(|| transform.is_backface_visible()) {
                         continue;
@@ -389,29 +362,31 @@ impl HitTest {
             pipeline_id,
             point,
             flags,
             node_cache: FastHashMap::default(),
             clip_chain_cache: Vec::new(),
         }
     }
 
-    fn get_from_clip_chain_cache(&mut self, index: ClipChainIndex) -> Option<ClippedIn> {
-        if index.0 >= self.clip_chain_cache.len() {
+    fn get_from_clip_chain_cache(&mut self, index: ClipChainId) -> Option<ClippedIn> {
+        let index = index.0 as usize;
+        if index >= self.clip_chain_cache.len() {
             None
         } else {
-            self.clip_chain_cache[index.0]
+            self.clip_chain_cache[index]
         }
     }
 
-    fn set_in_clip_chain_cache(&mut self, index: ClipChainIndex, value: ClippedIn) {
-        if index.0 >= self.clip_chain_cache.len() {
-            self.clip_chain_cache.resize(index.0 + 1, None);
+    fn set_in_clip_chain_cache(&mut self, index: ClipChainId, value: ClippedIn) {
+        let index = index.0 as usize;
+        if index >= self.clip_chain_cache.len() {
+            self.clip_chain_cache.resize(index + 1, None);
         }
-        self.clip_chain_cache[index.0] = Some(value);
+        self.clip_chain_cache[index] = Some(value);
     }
 
     fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
         if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
             return self.point;
         }
 
         let point =  &LayoutPoint::new(self.point.x, self.point.y);
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -55,17 +55,16 @@ extern crate serde;
 extern crate thread_profiler;
 
 mod batch;
 mod border;
 mod box_shadow;
 #[cfg(any(feature = "capture", feature = "replay"))]
 mod capture;
 mod clip;
-mod clip_node;
 mod clip_scroll_tree;
 mod debug_colors;
 #[cfg(feature = "debug_renderer")]
 mod debug_font_data;
 #[cfg(feature = "debug_renderer")]
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -6,17 +6,17 @@ use api::{DeviceRect, FilterOp, MixBlend
 use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutPoint, LayoutRect};
 use api::{DevicePixelScale, PictureIntPoint, PictureIntRect, PictureIntSize};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip_scroll_tree::SpatialNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PrimitiveRunContext};
 use gpu_cache::{GpuCacheHandle};
 use gpu_types::UvRectKind;
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
-use prim_store::{PrimitiveMetadata, ScrollNodeAndClipChain, Transform};
+use prim_store::{PrimitiveMetadata, Transform};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use std::mem;
 use tiling::RenderTargetKind;
 use util::TransformedRectKind;
 
 /*
@@ -141,20 +141,20 @@ pub struct PicturePrimitive {
     /// If None, don't composite - just draw directly on parent surface.
     pub composite_mode: Option<PictureCompositeMode>,
     // If true, this picture is part of a 3D context.
     pub is_in_3d_context: bool,
     // If requested as a frame output (for rendering
     // pages to a texture), this is the pipeline this
     // picture is the root of.
     pub frame_output_pipeline_id: Option<PipelineId>,
-    // The original reference frame ID for this picture.
+    // The original reference spatial node for this picture.
     // It is only different if this is part of a 3D
     // rendering context.
-    pub reference_frame_index: SpatialNodeIndex,
+    pub original_spatial_node_index: SpatialNodeIndex,
     pub real_local_rect: LayoutRect,
     // An optional cache handle for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
 
     // Unique identifier for this picture.
     pub id: PictureId,
@@ -177,63 +177,69 @@ impl PicturePrimitive {
         }
     }
 
     pub fn new_image(
         id: PictureId,
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
-        reference_frame_index: SpatialNodeIndex,
+        original_spatial_node_index: SpatialNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
     ) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
             surface: None,
             secondary_render_task_id: None,
             composite_mode,
             is_in_3d_context,
             frame_output_pipeline_id,
-            reference_frame_index,
+            original_spatial_node_index,
             real_local_rect: LayoutRect::zero(),
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             pipeline_id,
             id,
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
-        clip_and_scroll: ScrollNodeAndClipChain
+        spatial_node_index: SpatialNodeIndex,
     ) {
         if let Some(ref mut run) = self.runs.last_mut() {
-            if run.clip_and_scroll == clip_and_scroll &&
+            if run.spatial_node_index == spatial_node_index &&
                run.base_prim_index.0 + run.count == prim_index.0 {
                 run.count += 1;
                 return;
             }
         }
 
         self.runs.push(PrimitiveRun {
             base_prim_index: prim_index,
             count: 1,
-            clip_and_scroll,
+            spatial_node_index,
         });
     }
 
-    pub fn update_local_rect(
+    pub fn update_local_rect_and_set_runs(
         &mut self,
         prim_run_rect: PrimitiveRunLocalRect,
+        prim_runs: Vec<PrimitiveRun>,
     ) -> LayoutRect {
-        let local_content_rect = prim_run_rect.local_rect_in_actual_parent_space;
+        self.runs = prim_runs;
+
+        let local_content_rect = prim_run_rect.mapping.local_rect;
 
-        self.real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
+        self.real_local_rect = match prim_run_rect.original_mapping {
+            Some(mapping) => mapping.local_rect,
+            None => local_content_rect,
+        };
 
         match self.composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                 let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                 local_content_rect.inflate(inflate_size, inflate_size)
             }
             Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _))) => {
                 let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,64 +1,62 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF};
-use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode};
+use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF};
+use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, LayoutTransform};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
-use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
-use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
+use api::{PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
+use api::{BorderWidths, BoxShadowClipMode, LayoutToWorldScale, NormalBorder};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use box_shadow::BLUR_SAMPLE_SCALE;
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, SpatialNodeIndex};
-use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
-use clip::{ClipSourcesIndex, ClipWorkItem};
+use clip_scroll_tree::{CoordinateSystemId, SpatialNodeIndex};
+use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{for_each_tile, for_each_repetition};
-use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
+use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use spatial_node::SpatialNode;
 use std::{mem, usize};
-use std::sync::Arc;
 use util::{MatrixHelpers, calculate_screen_bounding_rect};
 use util::{pack_as_float, recycle_vec, TransformedRectKind};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
     pub spatial_node_index: SpatialNodeIndex,
-    pub clip_chain_index: ClipChainIndex,
+    pub clip_chain_id: ClipChainId,
 }
 
 impl ScrollNodeAndClipChain {
     pub fn new(
         spatial_node_index: SpatialNodeIndex,
-        clip_chain_index: ClipChainIndex
+        clip_chain_id: ClipChainId
     ) -> Self {
         ScrollNodeAndClipChain {
             spatial_node_index,
-            clip_chain_index,
+            clip_chain_id,
         }
     }
 }
 
 // This is CPU-side information about a transform, that is relevant
 // during culling and primitive prep pass. Often it is the same as
 // the information in the clip-scroll tree. However, if we decide
 // to rasterize a picture in local space, then this will be the
@@ -68,17 +66,17 @@ pub struct Transform<'a> {
     pub backface_is_visible: bool,
     pub transform_kind: TransformedRectKind,
 }
 
 #[derive(Debug)]
 pub struct PrimitiveRun {
     pub base_prim_index: PrimitiveIndex,
     pub count: usize,
-    pub clip_and_scroll: ScrollNodeAndClipChain,
+    pub spatial_node_index: SpatialNodeIndex,
 }
 
 impl PrimitiveRun {
     pub fn is_chasing(&self, index: Option<PrimitiveIndex>) -> bool {
         match index {
             Some(id) if cfg!(debug_assertions) => {
                 self.base_prim_index <= id && id.0 < self.base_prim_index.0 + self.count
             }
@@ -103,16 +101,94 @@ impl PrimitiveOpacity {
 
     pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
         PrimitiveOpacity {
             is_opaque: alpha == 1.0,
         }
     }
 }
 
+#[derive(Debug)]
+pub enum CoordinateSpaceMappingKind {
+    Local,
+    Offset(LayoutVector2D),
+    Transform(Option<LayoutTransform>),
+}
+
+#[derive(Debug)]
+pub struct CoordinateSpaceMapping {
+    kind: CoordinateSpaceMappingKind,
+    pub local_rect: LayoutRect,
+    ref_spatial_node_index: SpatialNodeIndex,
+}
+
+impl CoordinateSpaceMapping {
+    fn new(
+        ref_spatial_node_index: SpatialNodeIndex,
+    ) -> Self {
+        CoordinateSpaceMapping {
+            kind: CoordinateSpaceMappingKind::Local,
+            local_rect: LayoutRect::zero(),
+            ref_spatial_node_index,
+        }
+    }
+
+    pub fn set_target_spatial_node(
+        &mut self,
+        target_node_index: SpatialNodeIndex,
+        spatial_nodes: &[SpatialNode],
+    ) {
+        let ref_spatial_node = &spatial_nodes[self.ref_spatial_node_index.0];
+        let target_spatial_node = &spatial_nodes[target_node_index.0];
+
+        self.kind = if self.ref_spatial_node_index == target_node_index {
+            CoordinateSpaceMappingKind::Local
+        } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id {
+            let offset = target_spatial_node.coordinate_system_relative_offset -
+                         ref_spatial_node.coordinate_system_relative_offset;
+            CoordinateSpaceMappingKind::Offset(offset)
+        } else {
+            let relative_transform = ref_spatial_node
+                .world_content_transform
+                .inverse()
+                .map(|inv_parent| {
+                    inv_parent.pre_mul(&target_spatial_node.world_content_transform)
+                })
+                .map(|transform| {
+                    *transform.to_transform()
+                });
+            CoordinateSpaceMappingKind::Transform(relative_transform)
+        }
+    }
+
+    pub fn accumulate(&mut self, rect: &LayoutRect) {
+        match self.kind {
+            CoordinateSpaceMappingKind::Local => {
+                self.local_rect = self.local_rect.union(rect);
+            }
+            CoordinateSpaceMappingKind::Offset(ref offset) => {
+                let rect = rect.translate(offset);
+                self.local_rect = self.local_rect.union(&rect);
+            }
+            CoordinateSpaceMappingKind::Transform(ref transform) => {
+                if let Some(ref matrix) = transform {
+                    match matrix.transform_rect(rect) {
+                        Some(bounds) => {
+                            self.local_rect = self.local_rect.union(&bounds);
+                        }
+                        None => {
+                            warn!("parent relative transform can't transform the primitive rect for {:?}", rect);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
 // Represents the local space rect of a list of
 // primitive runs. For most primitive runs, the
 // primitive runs are attached to the parent they
 // are declared in. However, when a primitive run
 // is part of a 3d rendering context, it may get
 // hoisted to a higher level in the picture tree.
 // When this happens, we need to also calculate the
 // local space rects in the original space. This
@@ -120,18 +196,59 @@ impl PrimitiveOpacity {
 // for the primitive, to enable the plane splitting
 // logic to work correctly.
 // TODO(gw) In the future, we can probably simplify
 //          this - perhaps calculate the world space
 //          polygons directly and store internally
 //          in the picture structure.
 #[derive(Debug)]
 pub struct PrimitiveRunLocalRect {
-    pub local_rect_in_actual_parent_space: LayoutRect,
-    pub local_rect_in_original_parent_space: LayoutRect,
+    pub mapping: CoordinateSpaceMapping,
+    pub original_mapping: Option<CoordinateSpaceMapping>,
+}
+
+impl PrimitiveRunLocalRect {
+    pub fn new(
+        spatial_node_index: SpatialNodeIndex,
+        original_spatial_node_index: SpatialNodeIndex,
+    ) -> Self {
+        let mapping = CoordinateSpaceMapping::new(spatial_node_index);
+
+        let original_mapping = if spatial_node_index == original_spatial_node_index {
+            None
+        } else {
+            Some(CoordinateSpaceMapping::new(original_spatial_node_index))
+        };
+
+        PrimitiveRunLocalRect {
+            mapping,
+            original_mapping,
+        }
+    }
+
+    pub fn set_target_spatial_node(
+        &mut self,
+        target_node_index: SpatialNodeIndex,
+        spatial_nodes: &[SpatialNode],
+    ) {
+        self.mapping
+            .set_target_spatial_node(target_node_index, spatial_nodes);
+
+        if let Some(ref mut mapping) = self.original_mapping {
+            mapping.set_target_spatial_node(target_node_index, spatial_nodes);
+        }
+    }
+
+    pub fn accumulate(&mut self, rect: &LayoutRect) {
+        self.mapping.accumulate(rect);
+
+        if let Some(ref mut mapping) = self.original_mapping {
+            mapping.accumulate(rect);
+        }
+    }
 }
 
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
@@ -140,34 +257,20 @@ pub struct PrimitiveRunLocalRect {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct DeferredResolve {
     pub address: GpuCacheAddress,
     pub image_properties: ImageProperties,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub struct SpecificPrimitiveIndex(pub usize);
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveIndex(pub usize);
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct PictureIndex(pub usize);
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum PrimitiveKind {
-    TextRun,
-    Brush,
-}
-
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
         gpu_cache.get_address(self).as_int()
     }
 }
 
 impl GpuCacheAddress {
     pub fn as_int(&self) -> i32 {
@@ -183,19 +286,17 @@ pub struct ScreenRect {
     pub clipped: DeviceIntRect,
     pub unclipped: DeviceIntRect,
 }
 
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
     pub opacity: PrimitiveOpacity,
-    pub clip_sources_index: Option<ClipSourcesIndex>,
-    pub prim_kind: PrimitiveKind,
-    pub cpu_prim_index: SpecificPrimitiveIndex,
+    pub clip_chain_id: ClipChainId,
     pub gpu_location: GpuCacheHandle,
     pub clip_task_id: Option<RenderTaskId>,
 
     // TODO(gw): In the future, we should just pull these
     //           directly from the DL item, instead of
     //           storing them here.
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
@@ -288,19 +389,17 @@ pub enum BorderSource {
 
 #[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
-    Picture {
-        pic_index: PictureIndex,
-    },
+    Picture(PicturePrimitive),
     Image {
         request: ImageRequest,
         alpha_type: AlphaType,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         source: ImageSource,
         sub_rect: Option<DeviceIntRect>,
         opacity_binding: OpacityBinding,
@@ -459,21 +558,19 @@ impl BrushPrimitive {
         segment_desc: Option<BrushSegmentDescriptor>,
     ) -> Self {
         BrushPrimitive {
             kind,
             segment_desc,
         }
     }
 
-    pub fn new_picture(pic_index: PictureIndex) -> Self {
+    pub fn new_picture(prim: PicturePrimitive) -> Self {
         BrushPrimitive {
-            kind: BrushKind::Picture {
-                pic_index,
-            },
+            kind: BrushKind::Picture(prim),
             segment_desc: None,
         }
     }
 
     fn write_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
         local_rect: LayoutRect,
@@ -771,37 +868,37 @@ impl<'a> GradientGpuBlockBuilder<'a> {
         for entry in entries.iter() {
             request.push(entry.start_color);
             request.push(entry.end_color);
         }
     }
 }
 
 #[derive(Debug, Clone)]
-pub struct TextRunPrimitiveCpu {
+pub struct TextRunPrimitive {
     pub specified_font: FontInstance,
     pub used_font: FontInstance,
     pub offset: LayoutVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
     pub shadow: bool,
     pub glyph_raster_space: GlyphRasterSpace,
 }
 
-impl TextRunPrimitiveCpu {
+impl TextRunPrimitive {
     pub fn new(
         font: FontInstance,
         offset: LayoutVector2D,
         glyph_range: ItemRange<GlyphInstance>,
         glyph_keys: Vec<GlyphKey>,
         shadow: bool,
         glyph_raster_space: GlyphRasterSpace,
     ) -> Self {
-        TextRunPrimitiveCpu {
+        TextRunPrimitive {
             specified_font: font.clone(),
             used_font: font,
             offset,
             glyph_range,
             glyph_keys,
             glyph_gpu_blocks: Vec::new(),
             shadow,
             glyph_raster_space,
@@ -1124,17 +1221,17 @@ impl ClipData {
         ] {
             corner.write(request);
         }
     }
 }
 
 #[derive(Debug)]
 pub enum PrimitiveContainer {
-    TextRun(TextRunPrimitiveCpu),
+    TextRun(TextRunPrimitive),
     Brush(BrushPrimitive),
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
@@ -1174,17 +1271,17 @@ impl PrimitiveContainer {
                 let mut font = FontInstance {
                     color: shadow.color.into(),
                     ..info.specified_font.clone()
                 };
                 if shadow.blur_radius > 0.0 {
                     font.disable_subpixel_aa();
                 }
 
-                PrimitiveContainer::TextRun(TextRunPrimitiveCpu::new(
+                PrimitiveContainer::TextRun(TextRunPrimitive::new(
                     font,
                     info.offset + shadow.offset,
                     info.glyph_range,
                     info.glyph_keys.clone(),
                     true,
                     info.glyph_raster_space,
                 ))
             }
@@ -1206,391 +1303,1006 @@ impl PrimitiveContainer {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
         }
     }
 }
 
+pub enum PrimitiveDetails {
+    Brush(BrushPrimitive),
+    TextRun(TextRunPrimitive),
+}
+
+pub struct Primitive {
+    pub metadata: PrimitiveMetadata,
+    pub details: PrimitiveDetails,
+}
+
+impl Primitive {
+    pub fn as_pic(&self) -> &PicturePrimitive {
+        match self.details {
+            PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture(ref pic), .. }) => pic,
+            _ => {
+                panic!("bug: not a picture!");
+            }
+        }
+    }
+
+    pub fn as_pic_mut(&mut self) -> &mut PicturePrimitive {
+        match self.details {
+            PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture(ref mut pic), .. }) => pic,
+            _ => {
+                panic!("bug: not a picture!");
+            }
+        }
+    }
+}
+
 pub struct PrimitiveStore {
-    /// CPU side information only.
-    pub cpu_brushes: Vec<BrushPrimitive>,
-    pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
-    pub cpu_metadata: Vec<PrimitiveMetadata>,
-
-    pub pictures: Vec<PicturePrimitive>,
-    next_picture_id: u64,
+    pub primitives: Vec<Primitive>,
 
     /// A primitive index to chase through debugging.
     pub chase_id: Option<PrimitiveIndex>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
-            cpu_metadata: Vec::new(),
-            cpu_brushes: Vec::new(),
-            cpu_text_runs: Vec::new(),
-
-            pictures: Vec::new(),
-            next_picture_id: 0,
-
+            primitives: Vec::new(),
             chase_id: None,
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
-            cpu_metadata: recycle_vec(self.cpu_metadata),
-            cpu_brushes: recycle_vec(self.cpu_brushes),
-            cpu_text_runs: recycle_vec(self.cpu_text_runs),
-
-            pictures: recycle_vec(self.pictures),
-            next_picture_id: self.next_picture_id,
-
+            primitives: recycle_vec(self.primitives),
             chase_id: self.chase_id,
         }
     }
 
-    pub fn add_image_picture(
-        &mut self,
-        composite_mode: Option<PictureCompositeMode>,
-        is_in_3d_context: bool,
-        pipeline_id: PipelineId,
-        reference_frame_index: SpatialNodeIndex,
-        frame_output_pipeline_id: Option<PipelineId>,
-        apply_local_clip_rect: bool,
-    ) -> PictureIndex {
-        let picture = PicturePrimitive::new_image(
-            PictureId(self.next_picture_id),
-            composite_mode,
-            is_in_3d_context,
-            pipeline_id,
-            reference_frame_index,
-            frame_output_pipeline_id,
-            apply_local_clip_rect,
-        );
+    pub fn get_pic(&self, index: PrimitiveIndex) -> &PicturePrimitive {
+        self.primitives[index.0].as_pic()
+    }
 
-        let picture_index = PictureIndex(self.pictures.len());
-        self.pictures.push(picture);
-        self.next_picture_id += 1;
-        picture_index
+    pub fn get_pic_mut(&mut self, index: PrimitiveIndex) -> &mut PicturePrimitive {
+        self.primitives[index.0].as_pic_mut()
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayoutRect,
         local_clip_rect: &LayoutRect,
         is_backface_visible: bool,
-        clip_sources_index: Option<ClipSourcesIndex>,
+        clip_chain_id: ClipChainId,
         tag: Option<ItemTag>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
-        let prim_index = self.cpu_metadata.len();
+        let prim_index = self.primitives.len();
 
         let base_metadata = PrimitiveMetadata {
-            clip_sources_index,
+            clip_chain_id,
             gpu_location: GpuCacheHandle::new(),
             clip_task_id: None,
             local_rect: *local_rect,
             local_clip_rect: *local_clip_rect,
             combined_local_clip_rect: *local_clip_rect,
             is_backface_visible,
             screen_rect: None,
             tag,
             opacity: PrimitiveOpacity::translucent(),
-            prim_kind: PrimitiveKind::Brush,
-            cpu_prim_index: SpecificPrimitiveIndex(0),
             #[cfg(debug_assertions)]
             prepared_frame_id: FrameId(0),
         };
 
-        let metadata = match container {
+        let prim = match container {
             PrimitiveContainer::Brush(brush) => {
                 let opacity = match brush.kind {
                     BrushKind::Clear => PrimitiveOpacity::translucent(),
                     BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
                     BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
                     BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::LinearGradient { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
                     BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
                 };
 
                 let metadata = PrimitiveMetadata {
                     opacity,
-                    prim_kind: PrimitiveKind::Brush,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()),
                     ..base_metadata
                 };
 
-                self.cpu_brushes.push(brush);
-
-                metadata
+                Primitive {
+                    metadata,
+                    details: PrimitiveDetails::Brush(brush),
+                }
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
-                    prim_kind: PrimitiveKind::TextRun,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     ..base_metadata
                 };
 
-                self.cpu_text_runs.push(text_cpu);
-                metadata
+                Primitive {
+                    metadata,
+                    details: PrimitiveDetails::TextRun(text_cpu),
+                }
             }
         };
 
-        self.cpu_metadata.push(metadata);
+        self.primitives.push(prim);
 
         PrimitiveIndex(prim_index)
     }
 
     // Internal method that retrieves the primitive index of a primitive
     // that can be the target for collapsing parent opacity filters into.
     fn get_opacity_collapse_prim(
         &self,
-        pic_index: PictureIndex,
+        prim_index: PrimitiveIndex,
     ) -> Option<PrimitiveIndex> {
-        let pic = &self.pictures[pic_index.0];
+        let pic = self.get_pic(prim_index);
 
         // We can only collapse opacity if there is a single primitive, otherwise
         // the opacity needs to be applied to the primitives as a group.
         if pic.runs.len() != 1 {
             return None;
         }
 
         let run = &pic.runs[0];
         if run.count != 1 {
             return None;
         }
 
-        let prim_metadata = &self.cpu_metadata[run.base_prim_index.0];
+        let prim = &self.primitives[run.base_prim_index.0];
 
         // For now, we only support opacity collapse on solid rects and images.
         // This covers the most common types of opacity filters that can be
         // handled by this optimization. In the future, we can easily extend
         // this to other primitives, such as text runs and gradients.
-        match prim_metadata.prim_kind {
-            PrimitiveKind::Brush => {
-                let brush = &self.cpu_brushes[prim_metadata.cpu_prim_index.0];
+        match prim.details {
+            PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Picture { pic_index, .. } => {
-                        let pic = &self.pictures[pic_index.0];
+                    BrushKind::Picture(ref pic) => {
                         // If we encounter a picture that is a pass-through
                         // (i.e. no composite mode), then we can recurse into
                         // that to try and find a primitive to collapse to.
                         if pic.composite_mode.is_none() {
-                            return self.get_opacity_collapse_prim(pic_index);
+                            return self.get_opacity_collapse_prim(run.base_prim_index);
                         }
                     }
                     // If we find a single rect or image, we can use that
                     // as the primitive to collapse the opacity into.
                     BrushKind::Solid { .. } | BrushKind::Image { .. } => {
                         return Some(run.base_prim_index)
                     }
                     BrushKind::Border { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Clear => {}
                 }
             }
-            PrimitiveKind::TextRun => {}
+            PrimitiveDetails::TextRun(..) => {}
         }
 
         None
     }
 
     // Apply any optimizations to drawing this picture. Currently,
     // we just support collapsing pictures with an opacity filter
     // by pushing that opacity value into the color of a primitive
     // if that picture contains one compatible primitive.
     pub fn optimize_picture_if_possible(
         &mut self,
-        pic_index: PictureIndex,
+        pic_prim_index: PrimitiveIndex,
     ) {
         // Only handle opacity filters for now.
-        let binding = match self.pictures[pic_index.0].composite_mode {
+        let binding = match self.get_pic(pic_prim_index).composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) => {
                 binding
             }
             _ => {
                 return;
             }
         };
 
         // See if this picture contains a single primitive that supports
         // opacity collapse.
-        if let Some(prim_index) = self.get_opacity_collapse_prim(pic_index) {
-            let prim_metadata = &self.cpu_metadata[prim_index.0];
-            match prim_metadata.prim_kind {
-                PrimitiveKind::Brush => {
-                    let brush = &mut self.cpu_brushes[prim_metadata.cpu_prim_index.0];
-
-                    // By this point, we know we should only have found a primitive
-                    // that supports opacity collapse.
-                    match brush.kind {
-                        BrushKind::Solid { ref mut opacity_binding, .. } |
-                        BrushKind::Image { ref mut opacity_binding, .. } => {
-                            opacity_binding.push(binding);
+        match self.get_opacity_collapse_prim(pic_prim_index) {
+            Some(prim_index) => {
+                let prim = &mut self.primitives[prim_index.0];
+                match prim.details {
+                    PrimitiveDetails::Brush(ref mut brush) => {
+                        // By this point, we know we should only have found a primitive
+                        // that supports opacity collapse.
+                        match brush.kind {
+                            BrushKind::Solid { ref mut opacity_binding, .. } |
+                            BrushKind::Image { ref mut opacity_binding, .. } => {
+                                opacity_binding.push(binding);
+                            }
+                            BrushKind::Clear { .. } |
+                            BrushKind::Picture { .. } |
+                            BrushKind::YuvImage { .. } |
+                            BrushKind::Border { .. } |
+                            BrushKind::LinearGradient { .. } |
+                            BrushKind::RadialGradient { .. } => {
+                                unreachable!("bug: invalid prim type for opacity collapse");
+                            }
                         }
-                        BrushKind::Clear { .. } |
-                        BrushKind::Picture { .. } |
-                        BrushKind::YuvImage { .. } |
-                        BrushKind::Border { .. } |
-                        BrushKind::LinearGradient { .. } |
-                        BrushKind::RadialGradient { .. } => {
-                            unreachable!("bug: invalid prim type for opacity collapse");
-                        }
-                    };
-                }
-                PrimitiveKind::TextRun => {
-                    unreachable!("bug: invalid prim type for opacity collapse");
+                    }
+                    PrimitiveDetails::TextRun(..) => {
+                        unreachable!("bug: invalid prim type for opacity collapse");
+                    }
                 }
             }
+            None => {
+                return;
+            }
+        }
 
-            // The opacity filter has been collapsed, so mark this picture
-            // as a pass though. This means it will no longer allocate an
-            // intermediate surface or incur an extra blend / blit. Instead,
-            // the collapsed primitive will be drawn directly into the
-            // parent picture.
-            self.pictures[pic_index.0].composite_mode = None;
-        }
-    }
-
-    pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
-        &self.cpu_metadata[index.0]
+        // The opacity filter has been collapsed, so mark this picture
+        // as a pass though. This means it will no longer allocate an
+        // intermediate surface or incur an extra blend / blit. Instead,
+        // the collapsed primitive will be drawn directly into the
+        // parent picture.
+        self.get_pic_mut(pic_prim_index).composite_mode = None;
     }
 
     pub fn prim_count(&self) -> usize {
-        self.cpu_metadata.len()
+        self.primitives.len()
     }
 
-    fn build_prim_segments_if_needed(
+    pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
+        prim_run_context: &PrimitiveRunContext,
+        pic_context: &PictureContext,
         pic_state: &mut PictureState,
-        frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
-    ) {
-        let metadata = &mut self.cpu_metadata[prim_index.0];
+        frame_state: &mut FrameBuildingState,
+        display_list: &BuiltDisplayList,
+    ) -> Option<LayoutRect> {
+        let mut may_need_clip_mask = true;
+        let mut pic_state_for_children = PictureState::new();
+        let is_chased = Some(prim_index) == self.chase_id;
+
+        // If we have dependencies, we need to prepare them first, in order
+        // to know the actual rect of this primitive.
+        // For example, scrolling may affect the location of an item in
+        // local space, which may force us to render this item on a larger
+        // picture target, if being composited.
+        let pic_context_for_children = {
+            let prim = &mut self.primitives[prim_index.0];
+
+            // Do some basic checks first, that can early out
+            // without even knowing the local rect.
+            if !prim.metadata.is_backface_visible && prim_run_context.transform.backface_is_visible {
+                if cfg!(debug_assertions) && is_chased {
+                    println!("\tculled for not having visible back faces");
+                }
+                return None;
+            }
+
+            match prim.details {
+                PrimitiveDetails::Brush(ref mut brush) => {
+                    match brush.kind {
+                        BrushKind::Picture(ref mut pic) => {
+                            if !pic.resolve_scene_properties(frame_context.scene_properties) {
+                                if cfg!(debug_assertions) && is_chased {
+                                    println!("\tculled for carrying an invisible composite filter");
+                                }
+                                return None;
+                            }
+
+                            may_need_clip_mask = pic.composite_mode.is_some();
+
+                            let inflation_factor = match pic.composite_mode {
+                                Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                                    // The amount of extra space needed for primitives inside
+                                    // this picture to ensure the visibility check is correct.
+                                    BLUR_SAMPLE_SCALE * blur_radius
+                                }
+                                _ => {
+                                    0.0
+                                }
+                            };
+
+                            // Mark whether this picture has a complex coordinate system.
+                            pic_state_for_children.has_non_root_coord_system |=
+                                prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root();
+
+                            Some(PictureContext {
+                                pipeline_id: pic.pipeline_id,
+                                prim_runs: mem::replace(&mut pic.runs, Vec::new()),
+                                spatial_node_index: prim_run_context.spatial_node_index,
+                                original_spatial_node_index: pic.original_spatial_node_index,
+                                apply_local_clip_rect: pic.apply_local_clip_rect,
+                                inflation_factor,
+                                // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
+                                allow_subpixel_aa: pic_context.allow_subpixel_aa && pic.allow_subpixel_aa(),
+                            })
+                        }
+                        _ => {
+                            None
+                        }
+                    }
+                }
+                PrimitiveDetails::TextRun(..) => {
+                    None
+                }
+            }
+        };
+
+        if let Some(pic_context_for_children) = pic_context_for_children {
+            let result = self.prepare_prim_runs(
+                &pic_context_for_children,
+                &mut pic_state_for_children,
+                frame_context,
+                frame_state,
+            );
+
+            // Restore the dependencies (borrow check dance)
+            let prim = &mut self.primitives[prim_index.0];
+            let new_local_rect = prim
+                .as_pic_mut()
+                .update_local_rect_and_set_runs(
+                    result,
+                    pic_context_for_children.prim_runs
+                );
+
+            if new_local_rect != prim.metadata.local_rect {
+                prim.metadata.local_rect = new_local_rect;
+                frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location);
+                pic_state.local_rect_changed = true;
+            }
+        }
+
+        let prim = &mut self.primitives[prim_index.0];
+        prim.metadata.screen_rect = None;
+
+        if prim.metadata.local_rect.size.width <= 0.0 ||
+           prim.metadata.local_rect.size.height <= 0.0 {
+            if cfg!(debug_assertions) && is_chased {
+                println!("\tculled for zero local rectangle");
+            }
+            return None;
+        }
+
+        // Inflate the local rect for this primitive by the inflation factor of
+        // the picture context. This ensures that even if the primitive itself
+        // is not visible, any effects from the blur radius will be correctly
+        // taken into account.
+        let local_rect = prim
+            .metadata
+            .local_rect
+            .inflate(pic_context.inflation_factor, pic_context.inflation_factor)
+            .intersection(&prim.metadata.local_clip_rect);
+        let local_rect = match local_rect {
+            Some(local_rect) => local_rect,
+            None => {
+                if cfg!(debug_assertions) && is_chased {
+                    println!("\tculled for being out of the local clip rectangle: {:?}",
+                        prim.metadata.local_clip_rect);
+                }
+                return None
+            }
+        };
+
+        let clip_chain = frame_state
+            .clip_store
+            .build_clip_chain_instance(
+                prim.metadata.clip_chain_id,
+                local_rect,
+                prim.metadata.local_clip_rect,
+                prim_run_context.spatial_node_index,
+                &frame_context.clip_scroll_tree.spatial_nodes,
+                frame_state.gpu_cache,
+                frame_state.resource_cache,
+                frame_context.device_pixel_scale,
+            );
+
+        let clip_chain = match clip_chain {
+            Some(clip_chain) => clip_chain,
+            None => {
+                prim.metadata.screen_rect = None;
+                return None;
+            }
+        };
+
+        if cfg!(debug_assertions) && is_chased {
+            println!("\teffective clip chain from {:?} {}",
+                clip_chain.clips_range,
+                if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
+            );
+        }
+
+        pic_state.has_non_root_coord_system |= clip_chain.has_non_root_coord_system;
 
-        if metadata.prim_kind != PrimitiveKind::Brush {
-            return;
+        let unclipped_device_rect = match calculate_screen_bounding_rect(
+            &prim_run_context.scroll_node.world_content_transform,
+            &local_rect,
+            frame_context.device_pixel_scale,
+            None, //TODO: inflate `frame_context.screen_rect` appropriately
+        ) {
+            Some(rect) => rect,
+            None => {
+                if cfg!(debug_assertions) && is_chased {
+                    println!("\tculled for being behind the near plane of transform: {:?}",
+                        prim_run_context.scroll_node.world_content_transform);
+                }
+                return None
+            }
+        };
+
+        let clipped_device_rect = match calculate_screen_bounding_rect(
+            &prim_run_context.scroll_node.world_content_transform,
+            &clip_chain.local_bounding_rect,
+            frame_context.device_pixel_scale,
+            None,
+        ) {
+            Some(rect) => rect,
+            None => {
+                if cfg!(debug_assertions) && is_chased {
+                    println!("\tculled for being behind the near plane of transform: {:?}",
+                        prim_run_context.scroll_node.world_content_transform);
+                }
+                return None
+            }
+        };
+
+        let clipped_device_rect = match clipped_device_rect.intersection(&frame_context.screen_rect) {
+            Some(clipped_device_rect) => clipped_device_rect,
+            None => return None,
+        };
+
+        prim.metadata.screen_rect = Some(ScreenRect {
+            clipped: clipped_device_rect,
+            unclipped: unclipped_device_rect,
+        });
+
+        prim.metadata.combined_local_clip_rect = if pic_context.apply_local_clip_rect {
+            clip_chain.local_clip_rect
+        } else {
+            prim.metadata.local_clip_rect
+        };
+
+        let ccr = match prim.metadata.combined_local_clip_rect.intersection(&local_rect) {
+            Some(ccr) => ccr,
+            None => return None,
+        };
+
+        prim.build_prim_segments_if_needed(
+            pic_state,
+            frame_state,
+            frame_context,
+        );
+
+        if may_need_clip_mask && !prim.update_clip_task(
+            prim_run_context,
+            &clipped_device_rect,
+            &clip_chain,
+            pic_state,
+            frame_context,
+            frame_state,
+            is_chased,
+        ) {
+            return None;
+        }
+
+        if cfg!(debug_assertions) && is_chased {
+            println!("\tconsidered visible and ready with local rect {:?}", local_rect);
+        }
+
+        prim.prepare_prim_for_render_inner(
+            prim_index,
+            prim_run_context,
+            pic_state_for_children,
+            pic_context,
+            pic_state,
+            frame_context,
+            frame_state,
+            display_list,
+            is_chased,
+        );
+
+        Some(ccr)
+    }
+
+    // TODO(gw): Make this simpler / more efficient by tidying
+    //           up the logic that early outs from prepare_prim_for_render.
+    pub fn reset_prim_visibility(&mut self) {
+        for prim in &mut self.primitives {
+            prim.metadata.screen_rect = None;
+        }
+    }
+
+    pub fn prepare_prim_runs(
+        &mut self,
+        pic_context: &PictureContext,
+        pic_state: &mut PictureState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
+    ) -> PrimitiveRunLocalRect {
+        let mut result = PrimitiveRunLocalRect::new(
+            pic_context.spatial_node_index,
+            pic_context.original_spatial_node_index,
+        );
+
+        let display_list = &frame_context
+            .pipelines
+            .get(&pic_context.pipeline_id)
+            .expect("No display list?")
+            .display_list;
+
+        for run in &pic_context.prim_runs {
+            // TODO(gw): Perhaps we can restructure this to not need to create
+            //           a new primitive context for every run (if the hash
+            //           lookups ever show up in a profile).
+            let scroll_node = &frame_context
+                .clip_scroll_tree
+                .spatial_nodes[run.spatial_node_index.0];
+
+            if run.is_chasing(self.chase_id) {
+                println!("\tpreparing a run of length {} in pipeline {:?}",
+                    run.count, pic_context.pipeline_id);
+                println!("\trun {:?}", run.spatial_node_index);
+                println!("\ttransform {:?}", scroll_node.world_content_transform.to_transform());
+            }
+
+            // Mark whether this picture contains any complex coordinate
+            // systems, due to either the scroll node or the clip-chain.
+            pic_state.has_non_root_coord_system |=
+                scroll_node.coordinate_system_id != CoordinateSystemId::root();
+
+            if !scroll_node.invertible {
+                if run.is_chasing(self.chase_id) {
+                    println!("\tculled for the scroll node transform being invertible");
+                }
+                continue;
+            }
+
+            result.set_target_spatial_node(
+                run.spatial_node_index,
+                &frame_context.clip_scroll_tree.spatial_nodes,
+            );
+
+            let transform = frame_context
+                .transforms
+                .get_transform(run.spatial_node_index);
+
+            let child_prim_run_context = PrimitiveRunContext::new(
+                scroll_node,
+                run.spatial_node_index,
+                transform,
+            );
+
+            for i in 0 .. run.count {
+                let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
+
+                if let Some(prim_local_rect) = self.prepare_prim_for_render(
+                    prim_index,
+                    &child_prim_run_context,
+                    pic_context,
+                    pic_state,
+                    frame_context,
+                    frame_state,
+                    display_list,
+                ) {
+                    frame_state.profile_counters.visible_primitives.inc();
+                    result.accumulate(&prim_local_rect);
+                }
+            }
         }
 
-        let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
+        result
+    }
+}
+
+fn build_gradient_stops_request(
+    stops_handle: &mut GpuCacheHandle,
+    stops_range: ItemRange<GradientStop>,
+    reverse_stops: bool,
+    frame_state: &mut FrameBuildingState,
+    display_list: &BuiltDisplayList,
+) {
+    if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
+        let gradient_builder = GradientGpuBlockBuilder::new(
+            stops_range,
+            display_list,
+        );
+        gradient_builder.build(
+            reverse_stops,
+            &mut request,
+        );
+    }
+}
+
+fn decompose_repeated_primitive(
+    visible_tiles: &mut Vec<VisibleGradientTile>,
+    metadata: &mut PrimitiveMetadata,
+    stretch_size: &LayoutSize,
+    tile_spacing: &LayoutSize,
+    prim_run_context: &PrimitiveRunContext,
+    frame_context: &FrameBuildingContext,
+    frame_state: &mut FrameBuildingState,
+    callback: &mut FnMut(&LayoutRect, GpuDataRequest),
+) {
+    visible_tiles.clear();
+
+    // Tighten the clip rect because decomposing the repeated image can
+    // produce primitives that are partially covering the original image
+    // rect and we want to clip these extra parts out.
+    let tight_clip_rect = metadata
+        .combined_local_clip_rect
+        .intersection(&metadata.local_rect).unwrap();
+
+    let unclipped_device_rect = &metadata
+        .screen_rect
+        .unwrap()
+        .unclipped;
+
+    let visible_rect = compute_conservative_visible_rect(
+        prim_run_context,
+        frame_context,
+        unclipped_device_rect,
+        &tight_clip_rect
+    );
+    let stride = *stretch_size + *tile_spacing;
+
+    for_each_repetition(
+        &metadata.local_rect,
+        &visible_rect,
+        &stride,
+        &mut |origin, _| {
+
+            let mut handle = GpuCacheHandle::new();
+            let rect = LayoutRect {
+                origin: *origin,
+                size: *stretch_size,
+            };
+            if let Some(request) = frame_state.gpu_cache.request(&mut handle) {
+                callback(&rect, request);
+            }
+
+            visible_tiles.push(VisibleGradientTile {
+                local_rect: rect,
+                local_clip_rect: tight_clip_rect,
+                handle
+            });
+        }
+    );
+
+    if visible_tiles.is_empty() {
+        // At this point if we don't have tiles to show it means we could probably
+        // have done a better a job at culling during an earlier stage.
+        // Clearing the screen rect has the effect of "culling out" the primitive
+        // from the point of view of the batch builder, and ensures we don't hit
+        // assertions later on because we didn't request any image.
+        metadata.screen_rect = None;
+    }
+}
 
-        if let BrushKind::Border { ref mut source, .. } = brush.kind {
-            if let BorderSource::Border {
-                ref border,
-                ref mut cache_key,
-                ref widths,
-                ref mut handle,
-                ref mut task_info,
-                ..
-            } = *source {
-                // TODO(gw): When drawing in screen raster mode, we should also incorporate a
-                //           scale factor from the world transform to get an appropriately
-                //           sized border task.
-                let world_scale = LayoutToWorldScale::new(1.0);
-                let mut scale = world_scale * frame_context.device_pixel_scale;
-                let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius);
-                scale.0 = scale.0.min(max_scale.0);
-                let scale_au = Au::from_f32_px(scale.0);
-                let needs_update = scale_au != cache_key.scale;
-                let mut new_segments = Vec::new();
+fn compute_conservative_visible_rect(
+    prim_run_context: &PrimitiveRunContext,
+    frame_context: &FrameBuildingContext,
+    clipped_device_rect: &DeviceIntRect,
+    local_clip_rect: &LayoutRect,
+) -> LayoutRect {
+    let world_screen_rect = clipped_device_rect
+        .to_f32() / frame_context.device_pixel_scale;
+
+    if let Some(layer_screen_rect) = prim_run_context
+        .scroll_node
+        .world_content_transform
+        .unapply(&world_screen_rect) {
+
+        return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero());
+    }
+
+    *local_clip_rect
+}
+
+fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask {
+    let mut flags = EdgeAaSegmentMask::empty();
+
+    if tile_spacing.width > 0.0 {
+        flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT;
+    }
+    if tile_spacing.height > 0.0 {
+        flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM;
+    }
+
+    flags
+}
+
+impl<'a> GpuDataRequest<'a> {
+    // Write the GPU cache data for an individual segment.
+    fn write_segment(
+        &mut self,
+        local_rect: LayoutRect,
+        extra_data: [f32; 4],
+    ) {
+        let _ = VECS_PER_SEGMENT;
+        self.push(local_rect);
+        self.push(extra_data);
+    }
+}
 
-                if needs_update {
-                    cache_key.scale = scale_au;
+fn write_brush_segment_description(
+    brush: &mut BrushPrimitive,
+    metadata: &PrimitiveMetadata,
+    clip_chain: &ClipChainInstance,
+    frame_state: &mut FrameBuildingState,
+) {
+    match brush.segment_desc {
+        Some(ref segment_desc) => {
+            // If we already have a segment descriptor, only run through the
+            // clips list if we haven't already determined the mask kind.
+            if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
+                return;
+            }
+        }
+        None => {
+            // If no segment descriptor built yet, see if it is a brush
+            // type that wants to be segmented.
+            if !brush.kind.supports_segments(frame_state.resource_cache) {
+                return;
+            }
+        }
+    }
 
-                    *task_info = BorderRenderTaskInfo::new(
-                        &metadata.local_rect,
-                        border,
-                        widths,
-                        scale,
-                        &mut new_segments,
-                    );
-                }
+    // If the brush is small, we generally want to skip building segments
+    // and just draw it as a single primitive with clip mask. However,
+    // if the clips are purely rectangles that have no per-fragment
+    // clip masks, we will segment anyway. This allows us to completely
+    // skip allocating a clip mask in these cases.
+    let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA;
+
+    // TODO(gw): We should probably detect and store this on each
+    //           ClipSources instance, to avoid having to iterate
+    //           the clip sources here.
+    let mut rect_clips_only = true;
+
+    let mut segment_builder = SegmentBuilder::new(
+        metadata.local_rect,
+        None,
+        metadata.local_clip_rect
+    );
+
+    // If this primitive is clipped by clips from a different coordinate system, then we
+    // need to apply a clip mask for the entire primitive.
+    let mut clip_mask_kind = if clip_chain.has_clips_from_other_coordinate_systems {
+        BrushClipMaskKind::Global
+    } else {
+        BrushClipMaskKind::Individual
+    };
 
-                *handle = task_info.as_ref().map(|task_info| {
-                    frame_state.resource_cache.request_render_task(
-	                    RenderTaskCacheKey {
-	                        size: DeviceIntSize::zero(),
-	                        kind: RenderTaskCacheKeyKind::Border(cache_key.clone()),
-	                    },
-	                    frame_state.gpu_cache,
-	                    frame_state.render_tasks,
-	                    None,
-	                    false,          // todo
-	                    |render_tasks| {
-	                        let task = RenderTask::new_border(
-	                            task_info.size,
-	                            task_info.build_instances(border),
-	                        );
+    // Segment the primitive on all the local-space clip sources that we can.
+    for i in 0 .. clip_chain.clips_range.count {
+        let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i);
+
+        if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
+            continue;
+        }
+
+        // TODO(gw): We can easily extend the segment builder to support these clip sources in
+        // the future, but they are rarely used.
+        // We must do this check here in case we continue early below.
+        if clip_node.item.is_image_or_line_decoration_clip() {
+            clip_mask_kind = BrushClipMaskKind::Global;
+        }
+
+        // If this clip item is positioned by another positioning node, its relative position
+        // could change during scrolling. This means that we would need to resegment. Instead
+        // of doing that, only segment with clips that have the same positioning node.
+        // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
+        // when necessary while scrolling.
+        if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
+            // We don't need to generate a global clip mask for rectangle clips because we are
+            // in the same coordinate system and rectangular clips are handled by the local
+            // clip chain rectangle.
+            if !clip_node.item.is_rect() {
+                clip_mask_kind = BrushClipMaskKind::Global;
+            }
+            continue;
+        }
+
+        let (local_clip_rect, radius, mode) = match clip_node.item {
+            ClipItem::RoundedRectangle(rect, radii, clip_mode) => {
+                rect_clips_only = false;
+                (rect, Some(radii), clip_mode)
+            }
+            ClipItem::Rectangle(rect, mode) => {
+                (rect, None, mode)
+            }
+            ClipItem::BoxShadow(ref info) => {
+                rect_clips_only = false;
+
+                // For inset box shadows, we can clip out any
+                // pixels that are inside the shadow region
+                // and are beyond the inner rect, as they can't
+                // be affected by the blur radius.
+                let inner_clip_mode = match info.clip_mode {
+                    BoxShadowClipMode::Outset => None,
+                    BoxShadowClipMode::Inset => Some(ClipMode::ClipOut),
+                };
+
+                // Push a region into the segment builder where the
+                // box-shadow can have an effect on the result. This
+                // ensures clip-mask tasks get allocated for these
+                // pixel regions, even if no other clips affect them.
+                segment_builder.push_mask_region(
+                    info.prim_shadow_rect,
+                    info.prim_shadow_rect.inflate(
+                        -0.5 * info.shadow_rect_alloc_size.width,
+                        -0.5 * info.shadow_rect_alloc_size.height,
+                    ),
+                    inner_clip_mode,
+                );
+
+                continue;
+            }
+            ClipItem::LineDecoration(..) | ClipItem::Image(..) => {
+                rect_clips_only = false;
+                continue;
+            }
+        };
+
+        segment_builder.push_clip_rect(local_clip_rect, radius, mode);
+    }
+
+    if is_large || rect_clips_only {
+        match brush.segment_desc {
+            Some(ref mut segment_desc) => {
+                segment_desc.clip_mask_kind = clip_mask_kind;
+            }
+            None => {
+                // TODO(gw): We can probably make the allocation
+                //           patterns of this and the segment
+                //           builder significantly better, by
+                //           retaining it across primitives.
+                let mut segments = Vec::new();
 
-	                        let task_id = render_tasks.add(task);
+                segment_builder.build(|segment| {
+                    segments.push(
+                        BrushSegment::new(
+                            segment.rect,
+                            segment.has_mask,
+                            segment.edge_flags,
+                            [0.0; 4],
+                            BrushFlags::empty(),
+                        ),
+                    );
+                });
 
-	                        pic_state.tasks.push(task_id);
+                brush.segment_desc = Some(BrushSegmentDescriptor {
+                    segments,
+                    clip_mask_kind,
+                });
+            }
+        }
+    }
+}
 
-	                        task_id
-	                    }
-	                )
-	            });
+impl Primitive {
+    fn update_clip_task_for_brush(
+        &mut self,
+        prim_run_context: &PrimitiveRunContext,
+        clip_chain: &ClipChainInstance,
+        combined_outer_rect: &DeviceIntRect,
+        pic_state: &mut PictureState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
+    ) -> bool {
+        debug_assert!(frame_context.screen_rect.contains_rect(combined_outer_rect));
+
+        let brush = match self.details {
+            PrimitiveDetails::Brush(ref mut brush) => brush,
+            PrimitiveDetails::TextRun(..) => return false,
+        };
+
+        write_brush_segment_description(
+            brush,
+            &self.metadata,
+            clip_chain,
+            frame_state,
+        );
 
-                if needs_update {
-                    brush.segment_desc = Some(BrushSegmentDescriptor {
-                        segments: new_segments,
-                        clip_mask_kind: BrushClipMaskKind::Unknown,
-                    });
+        let segment_desc = match brush.segment_desc {
+            Some(ref mut description) => description,
+            None => return false,
+        };
+        let clip_mask_kind = segment_desc.clip_mask_kind;
+
+        for segment in &mut segment_desc.segments {
+            if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
+                segment.clip_task_id = BrushSegmentTaskId::Opaque;
+                continue;
+            }
+
+            let intersected_rect = calculate_screen_bounding_rect(
+                &prim_run_context.scroll_node.world_content_transform,
+                &segment.local_rect,
+                frame_context.device_pixel_scale,
+                Some(&combined_outer_rect),
+            );
 
-                    // The segments have changed, so force the GPU cache to
-                    // re-upload the primitive information.
-                    frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+            let bounds = match intersected_rect {
+                Some(bounds) => bounds,
+                None => {
+                    segment.clip_task_id = BrushSegmentTaskId::Empty;
+                    continue;
+                }
+            };
+
+            if clip_chain.clips_range.count > 0 {
+                let clip_task = RenderTask::new_mask(
+                    bounds,
+                    clip_chain.clips_range,
+                    frame_state.clip_store,
+                    frame_state.gpu_cache,
+                    frame_state.resource_cache,
+                    frame_state.render_tasks,
+                );
+
+                let clip_task_id = frame_state.render_tasks.add(clip_task);
+                pic_state.tasks.push(clip_task_id);
+                segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
+            }
+        }
+
+        true
+    }
+
+    fn reset_clip_task(&mut self) {
+        self.metadata.clip_task_id = None;
+        if let PrimitiveDetails::Brush(ref mut brush) = self.details {
+            if let Some(ref mut desc) = brush.segment_desc {
+                for segment in &mut desc.segments {
+                    segment.clip_task_id = BrushSegmentTaskId::Opaque;
                 }
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         pic_state_for_children: PictureState,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
+        display_list: &BuiltDisplayList,
+        is_chased: bool,
     ) {
         let mut is_tiled = false;
-        let metadata = &mut self.cpu_metadata[prim_index.0];
+        let metadata = &mut self.metadata;
         #[cfg(debug_assertions)]
         {
             metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
-        match metadata.prim_kind {
-            PrimitiveKind::TextRun => {
-                let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
+        match self.details {
+            PrimitiveDetails::TextRun(ref mut text) => {
                 // The transform only makes sense for screen space rasterization
                 let transform = prim_run_context.scroll_node.world_content_transform.to_transform();
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
                     &transform,
                     pic_context.allow_subpixel_aa,
-                    pic_context.display_list,
+                    display_list,
                     frame_state,
                 );
             }
-            PrimitiveKind::Brush => {
-                let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
-
+            PrimitiveDetails::Brush(ref mut brush) => {
                 match brush.kind {
                     BrushKind::Image {
                         request,
                         sub_rect,
                         stretch_size,
                         ref mut tile_spacing,
                         ref mut source,
                         ref mut opacity_binding,
@@ -1726,16 +2438,17 @@ impl PrimitiveStore {
                                 // rect and we want to clip these extra parts out.
                                 let tight_clip_rect = metadata
                                     .combined_local_clip_rect
                                     .intersection(&metadata.local_rect).unwrap();
 
                                 let visible_rect = compute_conservative_visible_rect(
                                     prim_run_context,
                                     frame_context,
+                                    &metadata.screen_rect.unwrap().clipped,
                                     &tight_clip_rect
                                 );
 
                                 let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
 
                                 let stride = stretch_size + *tile_spacing;
 
                                 visible_tiles.clear();
@@ -1852,17 +2565,17 @@ impl PrimitiveStore {
                         ref mut visible_tiles,
                         ..
                     } => {
                         build_gradient_stops_request(
                             stops_handle,
                             stops_range,
                             false,
                             frame_state,
-                            pic_context,
+                            display_list,
                         );
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
 
                             decompose_repeated_primitive(
                                 visible_tiles,
                                 metadata,
@@ -1902,17 +2615,17 @@ impl PrimitiveStore {
                         ..
                     } => {
 
                         build_gradient_stops_request(
                             stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
-                            pic_context,
+                            display_list,
                         );
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
 
                             decompose_repeated_primitive(
                                 visible_tiles,
                                 metadata,
@@ -1934,18 +2647,17 @@ impl PrimitiveStore {
                                         stretch_size.height,
                                         0.0,
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
                     }
-                    BrushKind::Picture { pic_index, .. } => {
-                        let pic = &mut self.pictures[pic_index.0];
+                    BrushKind::Picture(ref mut pic) => {
                         pic.prepare_for_render(
                             prim_index,
                             metadata,
                             prim_run_context,
                             pic_state_for_children,
                             pic_state,
                             frame_context,
                             frame_state,
@@ -1968,29 +2680,27 @@ impl PrimitiveStore {
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
-            match metadata.prim_kind {
-                PrimitiveKind::TextRun => {
-                    let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
+            match self.details {
+                PrimitiveDetails::TextRun(ref mut text) => {
                     text.write_gpu_blocks(&mut request);
                 }
-                PrimitiveKind::Brush => {
-                    let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
+                PrimitiveDetails::Brush(ref mut brush) => {
                     brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
-                                if cfg!(debug_assertions) && self.chase_id == Some(prim_index) {
+                                if cfg!(debug_assertions) && is_chased {
                                     println!("\t\t{:?}", segment);
                                 }
                                 // has to match VECS_PER_SEGMENT
                                 request.write_segment(
                                     segment.local_rect,
                                     segment.extra_data,
                                 );
                             }
@@ -2002,984 +2712,143 @@ impl PrimitiveStore {
                             );
                         }
                     }
                 }
             }
         }
     }
 
-    fn write_brush_segment_description(
-        brush: &mut BrushPrimitive,
-        metadata: &PrimitiveMetadata,
-        prim_run_context: &PrimitiveRunContext,
-        clips: &Vec<ClipWorkItem>,
-        has_clips_from_other_coordinate_systems: bool,
-        frame_state: &mut FrameBuildingState,
-    ) {
-        match brush.segment_desc {
-            Some(ref segment_desc) => {
-                // If we already have a segment descriptor, only run through the
-                // clips list if we haven't already determined the mask kind.
-                if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
-                    return;
-                }
-            }
-            None => {
-                // If no segment descriptor built yet, see if it is a brush
-                // type that wants to be segmented.
-                if !brush.kind.supports_segments(frame_state.resource_cache) {
-                    return;
-                }
-            }
-        }
-
-        // If the brush is small, we generally want to skip building segments
-        // and just draw it as a single primitive with clip mask. However,
-        // if the clips are purely rectangles that have no per-fragment
-        // clip masks, we will segment anyway. This allows us to completely
-        // skip allocating a clip mask in these cases.
-        let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA;
-
-        // TODO(gw): We should probably detect and store this on each
-        //           ClipSources instance, to avoid having to iterate
-        //           the clip sources here.
-        let mut rect_clips_only = true;
-
-        let mut segment_builder = SegmentBuilder::new(
-            metadata.local_rect,
-            None,
-            metadata.local_clip_rect
-        );
-
-        // If this primitive is clipped by clips from a different coordinate system, then we
-        // need to apply a clip mask for the entire primitive.
-        let mut clip_mask_kind = if has_clips_from_other_coordinate_systems {
-            BrushClipMaskKind::Global
-        } else {
-            BrushClipMaskKind::Individual
-        };
-
-        // Segment the primitive on all the local-space clip sources that we can.
-        for clip_item in clips {
-            if clip_item.coordinate_system_id != prim_run_context.scroll_node.coordinate_system_id {
-                continue;
-            }
-
-            let local_clips = &frame_state.clip_store[clip_item.clip_sources_index];
-            rect_clips_only = rect_clips_only && local_clips.only_rectangular_clips;
-
-            // TODO(gw): We can easily extend the segment builder to support these clip sources in
-            // the future, but they are rarely used.
-            // We must do this check here in case we continue early below.
-            if local_clips.has_image_or_line_decoration_clip {
-                clip_mask_kind = BrushClipMaskKind::Global;
-            }
-
-            // If this clip item is positioned by another positioning node, its relative position
-            // could change during scrolling. This means that we would need to resegment. Instead
-            // of doing that, only segment with clips that have the same positioning node.
-            // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
-            // when necessary while scrolling.
-            if local_clips.spatial_node_index != prim_run_context.spatial_node_index {
-                // We don't need to generate a global clip mask for rectangle clips because we are
-                // in the same coordinate system and rectangular clips are handled by the local
-                // clip chain rectangle.
-                if !local_clips.only_rectangular_clips {
-                    clip_mask_kind = BrushClipMaskKind::Global;
-                }
-                continue;
-            }
-
-            for &(ref clip, _) in &local_clips.clips {
-                let (local_clip_rect, radius, mode) = match *clip {
-                    ClipSource::RoundedRectangle(rect, radii, clip_mode) => {
-                        (rect, Some(radii), clip_mode)
-                    }
-                    ClipSource::Rectangle(rect, mode) => {
-                        (rect, None, mode)
-                    }
-                    ClipSource::BoxShadow(ref info) => {
-                        // For inset box shadows, we can clip out any
-                        // pixels that are inside the shadow region
-                        // and are beyond the inner rect, as they can't
-                        // be affected by the blur radius.
-                        let inner_clip_mode = match info.clip_mode {
-                            BoxShadowClipMode::Outset => None,
-                            BoxShadowClipMode::Inset => Some(ClipMode::ClipOut),
-                        };
-
-                        // Push a region into the segment builder where the
-                        // box-shadow can have an effect on the result. This
-                        // ensures clip-mask tasks get allocated for these
-                        // pixel regions, even if no other clips affect them.
-                        segment_builder.push_mask_region(
-                            info.prim_shadow_rect,
-                            info.prim_shadow_rect.inflate(
-                                -0.5 * info.shadow_rect_alloc_size.width,
-                                -0.5 * info.shadow_rect_alloc_size.height,
-                            ),
-                            inner_clip_mode,
-                        );
-
-                        continue;
-                    }
-                    ClipSource::LineDecoration(..) | ClipSource::Image(..) => continue,
-                };
-
-                segment_builder.push_clip_rect(local_clip_rect, radius, mode);
-            }
-        }
-
-        if is_large || rect_clips_only {
-            match brush.segment_desc {
-                Some(ref mut segment_desc) => {
-                    segment_desc.clip_mask_kind = clip_mask_kind;
-                }
-                None => {
-                    // TODO(gw): We can probably make the allocation
-                    //           patterns of this and the segment
-                    //           builder significantly better, by
-                    //           retaining it across primitives.
-                    let mut segments = Vec::new();
-
-                    segment_builder.build(|segment| {
-                        segments.push(
-                            BrushSegment::new(
-                                segment.rect,
-                                segment.has_mask,
-                                segment.edge_flags,
-                                [0.0; 4],
-                                BrushFlags::empty(),
-                            ),
-                        );
-                    });
-
-                    brush.segment_desc = Some(BrushSegmentDescriptor {
-                        segments,
-                        clip_mask_kind,
-                    });
-                }
-            }
-        }
-    }
-
-    fn update_clip_task_for_brush(
+    fn update_clip_task(
         &mut self,
         prim_run_context: &PrimitiveRunContext,
-        prim_index: PrimitiveIndex,
-        clips: &Vec<ClipWorkItem>,
-        combined_outer_rect: &DeviceIntRect,
-        has_clips_from_other_coordinate_systems: bool,
+        prim_screen_rect: &DeviceIntRect,
+        clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
+        is_chased: bool,
     ) -> bool {
-        assert!(frame_context.screen_rect.contains_rect(combined_outer_rect));
-
-        let metadata = &self.cpu_metadata[prim_index.0];
-        let brush = match metadata.prim_kind {
-            PrimitiveKind::Brush => {
-                &mut self.cpu_brushes[metadata.cpu_prim_index.0]
-            }
-            _ => {
-                return false;
-            }
-        };
-
-        PrimitiveStore::write_brush_segment_description(
-            brush,
-            metadata,
-            prim_run_context,
-            clips,
-            has_clips_from_other_coordinate_systems,
-            frame_state,
-        );
+        if cfg!(debug_assertions) && is_chased {
+            println!("\tupdating clip task with screen rect {:?}", prim_screen_rect);
+        }
+        // Reset clips from previous frames since we may clip differently each frame.
+        self.reset_clip_task();
 
-        let segment_desc = match brush.segment_desc {
-            Some(ref mut description) => description,
-            None => return false,
-        };
-        let clip_mask_kind = segment_desc.clip_mask_kind;
-
-        for segment in &mut segment_desc.segments {
-            if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global {
-                segment.clip_task_id = BrushSegmentTaskId::Opaque;
-                continue;
+        // First try to  render this primitive's mask using optimized brush rendering.
+        if self.update_clip_task_for_brush(
+            prim_run_context,
+            &clip_chain,
+            prim_screen_rect,
+            pic_state,
+            frame_context,
+            frame_state,
+        ) {
+            if cfg!(debug_assertions) && is_chased {
+                println!("\tsegment tasks have been created for clipping");
             }
+            return true;
+        }
 
-            let intersected_rect = calculate_screen_bounding_rect(
-                &prim_run_context.scroll_node.world_content_transform,
-                &segment.local_rect,
-                frame_context.device_pixel_scale,
-                Some(&combined_outer_rect),
-            );
-
-            let bounds = match intersected_rect {
-                Some(bounds) => bounds,
-                None => {
-                    segment.clip_task_id = BrushSegmentTaskId::Empty;
-                    continue;
-                }
-            };
-
+        if clip_chain.clips_range.count > 0 {
             let clip_task = RenderTask::new_mask(
-                bounds,
-                clips.clone(),
-                prim_run_context.scroll_node.coordinate_system_id,
+                *prim_screen_rect,
+                clip_chain.clips_range,
                 frame_state.clip_store,
                 frame_state.gpu_cache,
                 frame_state.resource_cache,
                 frame_state.render_tasks,
             );
 
             let clip_task_id = frame_state.render_tasks.add(clip_task);
+            if cfg!(debug_assertions) && is_chased {
+                println!("\tcreated task {:?} with combined outer rect {:?}",
+                    clip_task_id, prim_screen_rect);
+            }
+            self.metadata.clip_task_id = Some(clip_task_id);
             pic_state.tasks.push(clip_task_id);
-            segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
         }
 
         true
     }
 
-    fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) {
-        let metadata = &mut self.cpu_metadata[prim_index.0];
-        metadata.clip_task_id = None;
-        if metadata.prim_kind == PrimitiveKind::Brush {
-            if let Some(ref mut desc) = self.cpu_brushes[metadata.cpu_prim_index.0].segment_desc {
-                for segment in &mut desc.segments {
-                    segment.clip_task_id = BrushSegmentTaskId::Opaque;
-                }
+    fn build_prim_segments_if_needed(
+        &mut self,
+        pic_state: &mut PictureState,
+        frame_state: &mut FrameBuildingState,
+        frame_context: &FrameBuildingContext,
+    ) {
+        let brush = match self.details {
+            PrimitiveDetails::Brush(ref mut brush) => brush,
+            PrimitiveDetails::TextRun(..) => return,
+        };
+
+        if let BrushKind::Border {
+            source: BorderSource::Border {
+                ref border,
+                ref mut cache_key,
+                ref widths,
+                ref mut handle,
+                ref mut task_info,
+                ..
+            }
+        } = brush.kind {
+            // TODO(gw): When drawing in screen raster mode, we should also incorporate a
+            //           scale factor from the world transform to get an appropriately
+            //           sized border task.
+            let world_scale = LayoutToWorldScale::new(1.0);
+            let mut scale = world_scale * frame_context.device_pixel_scale;
+            let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths);
+            scale.0 = scale.0.min(max_scale.0);
+            let scale_au = Au::from_f32_px(scale.0);
+            let needs_update = scale_au != cache_key.scale;
+            let mut new_segments = Vec::new();
+
+            if needs_update {
+                cache_key.scale = scale_au;
+
+                *task_info = BorderRenderTaskInfo::new(
+                    &self.metadata.local_rect,
+                    border,
+                    widths,
+                    scale,
+                    &mut new_segments,
+                );
+            }
+
+            *handle = task_info.as_ref().map(|task_info| {
+                frame_state.resource_cache.request_render_task(
+                    RenderTaskCacheKey {
+                        size: DeviceIntSize::zero(),
+                        kind: RenderTaskCacheKeyKind::Border(cache_key.clone()),
+                    },
+                    frame_state.gpu_cache,
+                    frame_state.render_tasks,
+                    None,
+                    false,          // todo
+                    |render_tasks| {
+                        let task = RenderTask::new_border(
+                            task_info.size,
+                            task_info.build_instances(border),
+                        );
+
+                        let task_id = render_tasks.add(task);
+
+                        pic_state.tasks.push(task_id);
+
+                        task_id
+                    }
+                )
+            });
+
+            if needs_update {
+                brush.segment_desc = Some(BrushSegmentDescriptor {
+                    segments: new_segments,
+                    clip_mask_kind: BrushClipMaskKind::Unknown,
+                });
+
+                // The segments have changed, so force the GPU cache to
+                // re-upload the primitive information.
+                frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location);
             }
         }
     }
-
-    fn update_clip_task(
-        &mut self,
-        prim_index: PrimitiveIndex,
-        prim_run_context: &PrimitiveRunContext,
-        prim_screen_rect: &DeviceIntRect,
-        pic_state: &mut PictureState,
-        frame_context: &FrameBuildingContext,
-        frame_state: &mut FrameBuildingState,
-    ) -> bool {
-        if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-            println!("\tupdating clip task with screen rect {:?}", prim_screen_rect);
-        }
-        // Reset clips from previous frames since we may clip differently each frame.
-        self.reset_clip_task(prim_index);
-
-        let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
-            Some(rect) => rect,
-            None => {
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tculled by the intersection with frame rect {:?}",
-                        frame_context.screen_rect);
-                }
-                self.cpu_metadata[prim_index.0].screen_rect = None;
-                return false;
-            }
-        };
-
-        let mut combined_outer_rect =
-            prim_screen_rect.intersection(&prim_run_context.clip_chain.combined_outer_screen_rect);
-        let clip_chain = prim_run_context.clip_chain.nodes.clone();
-        if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-            println!("\tbase screen {:?}, combined clip chain {:?}",
-                prim_screen_rect, prim_run_context.clip_chain.combined_outer_screen_rect);
-        }
-
-        let prim_coordinate_system_id = prim_run_context.scroll_node.coordinate_system_id;
-        let transform = &prim_run_context.scroll_node.world_content_transform;
-        let extra_clip =  {
-            let metadata = &self.cpu_metadata[prim_index.0];
-            metadata.clip_sources_index.map(|clip_sources_index| {
-                let prim_clips = &mut frame_state.clip_store[clip_sources_index];
-                prim_clips.update(
-                    frame_state.gpu_cache,
-                    frame_state.resource_cache,
-                    frame_context.device_pixel_scale,
-                );
-                let (screen_inner_rect, screen_outer_rect) = prim_clips.get_screen_bounds(
-                    transform,
-                    frame_context.device_pixel_scale,
-                    Some(&prim_screen_rect),
-                );
-
-                if let Some(outer) = screen_outer_rect {
-                    combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
-                }
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tfound extra clip with screen bounds {:?}", screen_outer_rect);
-                }
-
-                Arc::new(ClipChainNode {
-                    work_item: ClipWorkItem {
-                        clip_sources_index,
-                        coordinate_system_id: prim_coordinate_system_id,
-                    },
-                    // The local_clip_rect a property of ClipChain nodes that are ClipNodes.
-                    // It's used to calculate a local clipping rectangle before we reach this
-                    // point, so we can set it to zero here. It should be unused from this point
-                    // on.
-                    local_clip_rect: LayoutRect::zero(),
-                    screen_inner_rect,
-                    screen_outer_rect: screen_outer_rect.unwrap_or(prim_screen_rect),
-                    prev: None,
-                })
-            })
-        };
-
-        // If everything is clipped out, then we don't need to render this primitive.
-        let combined_outer_rect = match combined_outer_rect {
-            Some(rect) if !rect.is_empty() => rect,
-            _ => {
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tculled by the empty combined screen rect");
-                }
-                self.cpu_metadata[prim_index.0].screen_rect = None;
-                return false;
-            }
-        };
-
-        let mut has_clips_from_other_coordinate_systems = false;
-        let mut combined_inner_rect = frame_context.screen_rect;
-        let clips = convert_clip_chain_to_clip_vector(
-            clip_chain,
-            extra_clip,
-            &combined_outer_rect,
-            &mut combined_inner_rect,
-            prim_run_context.scroll_node.coordinate_system_id,
-            &mut has_clips_from_other_coordinate_systems
-        );
-
-        // This can happen if we had no clips or if all the clips were optimized away. In
-        // some cases we still need to create a clip mask in order to create a rectangular
-        // clip in screen space coordinates.
-        if clips.is_empty() {
-            // If we don't have any clips from other coordinate systems, the local clip
-            // calculated from the clip chain should be sufficient to ensure proper clipping.
-            if !has_clips_from_other_coordinate_systems {
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tneed no task: all clips are within the coordinate system");
-                }
-                return true;
-            }
-
-            // If we have filtered all clips and the screen rect isn't any smaller, we can just
-            // skip masking entirely.
-            if combined_outer_rect == prim_screen_rect {
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tneed no task: combined rect is not smaller");
-                }
-                return true;
-            }
-            // Otherwise we create an empty mask, but with an empty inner rect to avoid further
-            // optimization of the empty mask.
-            combined_inner_rect = DeviceIntRect::zero();
-        }
-
-        if combined_inner_rect.contains_rect(&prim_screen_rect) {
-            if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                println!("\tneed no task: contained within the clip inner rect");
-            }
-            return true;
-        }
-
-        // First try to  render this primitive's mask using optimized brush rendering.
-        if self.update_clip_task_for_brush(
-            prim_run_context,
-            prim_index,
-            &clips,
-            &combined_outer_rect,
-            has_clips_from_other_coordinate_systems,
-            pic_state,
-            frame_context,
-            frame_state,
-        ) {
-            if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                println!("\tsegment tasks have been created for clipping");
-            }
-            return true;
-        }
-
-        let clip_task = RenderTask::new_mask(
-            combined_outer_rect,
-            clips,
-            prim_coordinate_system_id,
-            frame_state.clip_store,
-            frame_state.gpu_cache,
-            frame_state.resource_cache,
-            frame_state.render_tasks,
-        );
-
-        let clip_task_id = frame_state.render_tasks.add(clip_task);
-        if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-            println!("\tcreated task {:?} with combined outer rect {:?}",
-                clip_task_id, combined_outer_rect);
-        }
-        self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id);
-        pic_state.tasks.push(clip_task_id);
-
-        true
-    }
-
-    pub fn prepare_prim_for_render(
-        &mut self,
-        prim_index: PrimitiveIndex,
-        prim_run_context: &PrimitiveRunContext,
-        pic_context: &PictureContext,
-        pic_state: &mut PictureState,
-        frame_context: &FrameBuildingContext,
-        frame_state: &mut FrameBuildingState,
-    ) -> Option<LayoutRect> {
-        let mut may_need_clip_mask = true;
-        let mut pic_state_for_children = PictureState::new();
-
-        // Do some basic checks first, that can early out
-        // without even knowing the local rect.
-        let (prim_kind, cpu_prim_index) = {
-            let metadata = &self.cpu_metadata[prim_index.0];
-
-            if !metadata.is_backface_visible && prim_run_context.transform.backface_is_visible {
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tculled for not having visible back faces");
-                }
-                return None;
-            }
-
-            (metadata.prim_kind, metadata.cpu_prim_index)
-        };
-
-        // If we have dependencies, we need to prepare them first, in order
-        // to know the actual rect of this primitive.
-        // For example, scrolling may affect the location of an item in
-        // local space, which may force us to render this item on a larger
-        // picture target, if being composited.
-        if let PrimitiveKind::Brush = prim_kind {
-            if let BrushKind::Picture { pic_index, .. } = self.cpu_brushes[cpu_prim_index.0].kind {
-                let pic_context_for_children = {
-                    let pic = &mut self.pictures[pic_index.0];
-
-                    if !pic.resolve_scene_properties(frame_context.scene_properties) {
-                        if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                            println!("\tculled for carrying an invisible composite filter");
-                        }
-                        return None;
-                    }
-
-                    may_need_clip_mask = pic.composite_mode.is_some();
-
-                    let inflation_factor = match pic.composite_mode {
-                        Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
-                            // The amount of extra space needed for primitives inside
-                            // this picture to ensure the visibility check is correct.
-                            BLUR_SAMPLE_SCALE * blur_radius
-                        }
-                        _ => {
-                            0.0
-                        }
-                    };
-
-                    let display_list = &frame_context
-                        .pipelines
-                        .get(&pic.pipeline_id)
-                        .expect("No display list?")
-                        .display_list;
-
-                    let inv_world_transform = prim_run_context
-                        .scroll_node
-                        .world_content_transform
-                        .inverse();
-
-                    // Mark whether this picture has a complex coordinate system.
-                    pic_state_for_children.has_non_root_coord_system |=
-                        prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root();
-
-                    PictureContext {
-                        pipeline_id: pic.pipeline_id,
-                        prim_runs: mem::replace(&mut pic.runs, Vec::new()),
-                        original_reference_frame_index: Some(pic.reference_frame_index),
-                        display_list,
-                        inv_world_transform,
-                        apply_local_clip_rect: pic.apply_local_clip_rect,
-                        inflation_factor,
-                        // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
-                        allow_subpixel_aa: pic_context.allow_subpixel_aa && pic.allow_subpixel_aa(),
-                    }
-                };
-
-                let result = self.prepare_prim_runs(
-                    &pic_context_for_children,
-                    &mut pic_state_for_children,
-                    frame_context,
-                    frame_state,
-                );
-
-                // Restore the dependencies (borrow check dance)
-                let pic = &mut self.pictures[pic_index.0];
-                pic.runs = pic_context_for_children.prim_runs;
-
-                let metadata = &mut self.cpu_metadata[prim_index.0];
-
-                let new_local_rect = pic.update_local_rect(result);
-
-                if new_local_rect != metadata.local_rect {
-                    metadata.local_rect = new_local_rect;
-                    frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
-                    pic_state.local_rect_changed = true;
-                }
-            }
-        }
-
-        let (local_rect, unclipped_device_rect) = {
-            let metadata = &mut self.cpu_metadata[prim_index.0];
-            if metadata.local_rect.size.width <= 0.0 ||
-               metadata.local_rect.size.height <= 0.0 {
-                if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                    println!("\tculled for zero local rectangle");
-                }
-                return None;
-            }
-
-            metadata.screen_rect = None;
-
-            // Inflate the local rect for this primitive by the inflation factor of
-            // the picture context. This ensures that even if the primitive itself
-            // is not visible, any effects from the blur radius will be correctly
-            // taken into account.
-            let local_rect = metadata.local_rect
-                .inflate(pic_context.inflation_factor, pic_context.inflation_factor)
-                .intersection(&metadata.local_clip_rect);
-            let local_rect = match local_rect {
-                Some(local_rect) => local_rect,
-                None => {
-                    if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                        println!("\tculled for being out of the local clip rectangle: {:?}",
-                            metadata.local_clip_rect);
-                    }
-                    return None
-                }
-            };
-
-            let unclipped = match calculate_screen_bounding_rect(
-                &prim_run_context.scroll_node.world_content_transform,
-                &local_rect,
-                frame_context.device_pixel_scale,
-                None, //TODO: inflate `frame_context.screen_rect` appropriately
-            ) {
-                Some(rect) => rect,
-                None => {
-                    if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-                        println!("\tculled for being behind the near plane of transform: {:?}",
-                            prim_run_context.scroll_node.world_content_transform);
-                    }
-                    return None
-                }
-            };
-
-            let clipped = unclipped
-                .intersection(&prim_run_context.clip_chain.combined_outer_screen_rect)?;
-
-            metadata.screen_rect = Some(ScreenRect {
-                clipped,
-                unclipped,
-            });
-
-            metadata.combined_local_clip_rect = prim_run_context
-                .local_clip_rect
-                .intersection(&metadata.local_clip_rect)
-                .unwrap_or(LayoutRect::zero());
-
-            (local_rect, unclipped)
-        };
-
-        self.build_prim_segments_if_needed(
-            prim_index,
-            pic_state,
-            frame_state,
-            frame_context,
-        );
-
-        if may_need_clip_mask && !self.update_clip_task(
-            prim_index,
-            prim_run_context,
-            &unclipped_device_rect,
-            pic_state,
-            frame_context,
-            frame_state,
-        ) {
-            return None;
-        }
-
-        if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-            println!("\tconsidered visible and ready with local rect {:?}", local_rect);
-        }
-
-        self.prepare_prim_for_render_inner(
-            prim_index,
-            prim_run_context,
-            pic_state_for_children,
-            pic_context,
-            pic_state,
-            frame_context,
-            frame_state,
-        );
-
-        Some(local_rect)
-    }
-
-    // TODO(gw): Make this simpler / more efficient by tidying
-    //           up the logic that early outs from prepare_prim_for_render.
-    pub fn reset_prim_visibility(&mut self) {
-        for md in &mut self.cpu_metadata {
-            md.screen_rect = None;
-        }
-    }
-
-    pub fn prepare_prim_runs(
-        &mut self,
-        pic_context: &PictureContext,
-        pic_state: &mut PictureState,
-        frame_context: &FrameBuildingContext,
-        frame_state: &mut FrameBuildingState,
-    ) -> PrimitiveRunLocalRect {
-        let mut result = PrimitiveRunLocalRect {
-            local_rect_in_actual_parent_space: LayoutRect::zero(),
-            local_rect_in_original_parent_space: LayoutRect::zero(),
-        };
-
-        for run in &pic_context.prim_runs {
-            // TODO(gw): Perhaps we can restructure this to not need to create
-            //           a new primitive context for every run (if the hash
-            //           lookups ever show up in a profile).
-            let scroll_node = &frame_context
-                .clip_scroll_tree
-                .spatial_nodes[run.clip_and_scroll.spatial_node_index.0];
-            let clip_chain = &frame_context
-                .clip_chains[run.clip_and_scroll.clip_chain_index.0];
-
-            if run.is_chasing(self.chase_id) {
-                println!("\tpreparing a run of length {} in pipeline {:?}",
-                    run.count, pic_context.pipeline_id);
-                println!("\trun {:?}", run.clip_and_scroll);
-                println!("\ttransform {:?}", scroll_node.world_content_transform.to_transform());
-            }
-
-            // Mark whether this picture contains any complex coordinate
-            // systems, due to either the scroll node or the clip-chain.
-            pic_state.has_non_root_coord_system |=
-                scroll_node.coordinate_system_id != CoordinateSystemId::root();
-            pic_state.has_non_root_coord_system |= clip_chain.has_non_root_coord_system;
-
-            if !scroll_node.invertible {
-                if run.is_chasing(self.chase_id) {
-                    println!("\tculled for the scroll node transform being invertible");
-                }
-                continue;
-            }
-
-            if clip_chain.combined_outer_screen_rect.is_empty() {
-                if run.is_chasing(self.chase_id) {
-                    println!("\tculled for out of screen bounds");
-                }
-                continue;
-            }
-
-            let parent_relative_transform = pic_context
-                .inv_world_transform
-                .map(|inv_parent| {
-                    inv_parent.pre_mul(&scroll_node.world_content_transform)
-                });
-
-            let original_relative_transform = pic_context
-                .original_reference_frame_index
-                .and_then(|original_reference_frame_index| {
-                    frame_context
-                        .clip_scroll_tree
-                        .spatial_nodes[original_reference_frame_index.0]
-                        .world_content_transform
-                        .inverse()
-                })
-                .map(|inv_parent| {
-                    inv_parent.pre_mul(&scroll_node.world_content_transform)
-                });
-
-            if run.is_chasing(self.chase_id) {
-                println!("\teffective clip chain from {:?} {}",
-                    scroll_node.coordinate_system_id,
-                    if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
-                );
-                let iter = ClipChainNodeIter { current: clip_chain.nodes.clone() };
-                for node in iter {
-                    println!("\t\t{:?} {:?}",
-                        node.work_item.coordinate_system_id,
-                        node.local_clip_rect,
-                    );
-                }
-            }
-
-            let clip_chain_rect = if pic_context.apply_local_clip_rect {
-                get_local_clip_rect_for_nodes(scroll_node, clip_chain)
-            } else {
-                None
-            };
-
-            let local_clip_chain_rect = match clip_chain_rect {
-                Some(rect) if rect.is_empty() => {
-                    if run.is_chasing(self.chase_id) {
-                        println!("\tculled by empty chain rect");
-                    }
-                    continue
-                },
-                Some(rect) => rect,
-                None => frame_context.max_local_clip,
-            };
-
-            let transform = frame_context
-                .transforms
-                .get_transform(run.clip_and_scroll.spatial_node_index);
-
-            let child_prim_run_context = PrimitiveRunContext::new(
-                clip_chain,
-                scroll_node,
-                run.clip_and_scroll.spatial_node_index,
-                local_clip_chain_rect,
-                transform,
-            );
-
-            for i in 0 .. run.count {
-                let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-
-                if let Some(prim_local_rect) = self.prepare_prim_for_render(
-                    prim_index,
-                    &child_prim_run_context,
-                    pic_context,
-                    pic_state,
-                    frame_context,
-                    frame_state,
-                ) {
-                    frame_state.profile_counters.visible_primitives.inc();
-
-                    let clipped_rect = match clip_chain_rect {
-                        Some(ref chain_rect) => match prim_local_rect.intersection(chain_rect) {
-                            Some(rect) => rect,
-                            None => {
-                                if cfg!(debug_assertions) && self.chase_id == Some(prim_index) {
-                                    println!("\tculled by chain rect {:?}", chain_rect);
-                                }
-                                continue
-                            },
-                        },
-                        None => prim_local_rect,
-                    };
-
-                    if let Some(ref matrix) = parent_relative_transform {
-                        match matrix.transform_rect(&clipped_rect) {
-                            Some(bounds) => {
-                                result.local_rect_in_actual_parent_space =
-                                    result.local_rect_in_actual_parent_space.union(&bounds);
-                            }
-                            None => {
-                                warn!("parent relative transform can't transform the primitive rect for {:?}", prim_index);
-                            }
-                        }
-
-                    }
-                    if let Some(ref matrix) = original_relative_transform {
-                        match matrix.transform_rect(&clipped_rect) {
-                            Some(bounds) => {
-                                result.local_rect_in_original_parent_space =
-                                    result.local_rect_in_original_parent_space.union(&bounds);
-                            }
-                            None => {
-                                warn!("original relative transform can't transform the primitive rect for {:?}", prim_index);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        result
-    }
 }
-
-fn build_gradient_stops_request(
-    stops_handle: &mut GpuCacheHandle,
-    stops_range: ItemRange<GradientStop>,
-    reverse_stops: bool,
-    frame_state: &mut FrameBuildingState,
-    pic_context: &PictureContext
-) {
-    if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
-        let gradient_builder = GradientGpuBlockBuilder::new(
-            stops_range,
-            pic_context.display_list,
-        );
-        gradient_builder.build(
-            reverse_stops,
-            &mut request,
-        );
-    }
-}
-
-fn decompose_repeated_primitive(
-    visible_tiles: &mut Vec<VisibleGradientTile>,
-    metadata: &mut PrimitiveMetadata,
-    stretch_size: &LayoutSize,
-    tile_spacing: &LayoutSize,
-    prim_run_context: &PrimitiveRunContext,
-    frame_context: &FrameBuildingContext,
-    frame_state: &mut FrameBuildingState,
-    callback: &mut FnMut(&LayoutRect, GpuDataRequest),
-) {
-    visible_tiles.clear();
-
-    // Tighten the clip rect because decomposing the repeated image can
-    // produce primitives that are partially covering the original image
-    // rect and we want to clip these extra parts out.
-    let tight_clip_rect = metadata
-        .combined_local_clip_rect
-        .intersection(&metadata.local_rect).unwrap();
-
-    let visible_rect = compute_conservative_visible_rect(
-        prim_run_context,
-        frame_context,
-        &tight_clip_rect
-    );
-    let stride = *stretch_size + *tile_spacing;
-
-    for_each_repetition(
-        &metadata.local_rect,
-        &visible_rect,
-        &stride,
-        &mut |origin, _| {
-
-            let mut handle = GpuCacheHandle::new();
-            let rect = LayoutRect {
-                origin: *origin,
-                size: *stretch_size,
-            };
-            if let Some(request) = frame_state.gpu_cache.request(&mut handle) {
-                callback(&rect, request);
-            }
-
-            visible_tiles.push(VisibleGradientTile {
-                local_rect: rect,
-                local_clip_rect: tight_clip_rect,
-                handle
-            });
-        }
-    );
-
-    if visible_tiles.is_empty() {
-        // At this point if we don't have tiles to show it means we could probably
-        // have done a better a job at culling during an earlier stage.
-        // Clearing the screen rect has the effect of "culling out" the primitive
-        // from the point of view of the batch builder, and ensures we don't hit
-        // assertions later on because we didn't request any image.
-        metadata.screen_rect = None;
-    }
-}
-
-fn compute_conservative_visible_rect(
-    prim_run_context: &PrimitiveRunContext,
-    frame_context: &FrameBuildingContext,
-    local_clip_rect: &LayoutRect,
-) -> LayoutRect {
-    let world_screen_rect = prim_run_context
-        .clip_chain.combined_outer_screen_rect
-        .to_f32() / frame_context.device_pixel_scale;
-
-    if let Some(layer_screen_rect) = prim_run_context
-        .scroll_node
-        .world_content_transform
-        .unapply(&world_screen_rect) {
-
-        return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero());
-    }
-
-    *local_clip_rect
-}
-
-fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask {
-    let mut flags = EdgeAaSegmentMask::empty();
-
-    if tile_spacing.width > 0.0 {
-        flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT;
-    }
-    if tile_spacing.height > 0.0 {
-        flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM;
-    }
-
-    flags
-}
-
-fn convert_clip_chain_to_clip_vector(
-    clip_chain_nodes: ClipChainNodeRef,
-    extra_clip: ClipChainNodeRef,
-    combined_outer_rect: &DeviceIntRect,
-    combined_inner_rect: &mut DeviceIntRect,
-    prim_coordinate_system: CoordinateSystemId,
-    has_clips_from_other_coordinate_systems: &mut bool,
-) -> Vec<ClipWorkItem> {
-    // Filter out all the clip instances that don't contribute to the result.
-    ClipChainNodeIter { current: extra_clip }
-        .chain(ClipChainNodeIter { current: clip_chain_nodes })
-        .filter_map(|node| {
-            *has_clips_from_other_coordinate_systems |=
-                prim_coordinate_system != node.work_item.coordinate_system_id;
-
-            *combined_inner_rect = if !node.screen_inner_rect.is_empty() {
-                // If this clip's inner area contains the area of the primitive clipped
-                // by previous clips, then it's not going to affect rendering in any way.
-                if node.screen_inner_rect.contains_rect(combined_outer_rect) {
-                    return None;
-                }
-                combined_inner_rect.intersection(&node.screen_inner_rect)
-                    .unwrap_or_else(DeviceIntRect::zero)
-            } else {
-                DeviceIntRect::zero()
-            };
-
-            Some(node.work_item.clone())
-        })
-        .collect()
-}
-
-fn get_local_clip_rect_for_nodes(
-    scroll_node: &SpatialNode,
-    clip_chain: &ClipChain,
-) -> Option<LayoutRect> {
-    ClipChainNodeIter { current: clip_chain.nodes.clone() }
-        .fold(
-            None,
-            |combined_local_clip_rect: Option<LayoutRect>, node| {
-                if node.work_item.coordinate_system_id != scroll_node.coordinate_system_id {
-                    return combined_local_clip_rect;
-                }
-
-                Some(match combined_local_clip_rect {
-                    Some(combined_rect) =>
-                        combined_rect
-                            .intersection(&node.local_clip_rect)
-                            .unwrap_or_else(LayoutRect::zero),
-                    None => node.local_clip_rect,
-                })
-            }
-        )
-        .map(|local_rect| {
-            local_rect.translate(&-scroll_node.coordinate_system_relative_offset)
-        })
-}
-
-impl<'a> GpuDataRequest<'a> {
-    // Write the GPU cache data for an individual segment.
-    fn write_segment(
-        &mut self,
-        local_rect: LayoutRect,
-        extra_data: [f32; 4],
-    ) {
-        let _ = VECS_PER_SEGMENT;
-        self.push(local_rect);
-        self.push(extra_data);
-    }
-}
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -93,29 +93,29 @@ impl ProfileCounter for IntProfileCounte
     }
 
     fn value(&self) -> String {
         format!("{}", self.value)
     }
 }
 
 #[cfg(feature = "debug_renderer")]
-pub struct FloatProfileCounter {
+pub struct PercentageProfileCounter {
     description: &'static str,
     value: f32,
 }
 
 #[cfg(feature = "debug_renderer")]
-impl ProfileCounter for FloatProfileCounter {
+impl ProfileCounter for PercentageProfileCounter {
     fn description(&self) -> &'static str {
         self.description
     }
 
     fn value(&self) -> String {
-        format!("{:.2}", self.value)
+        format!("{:.2}%", self.value * 100.0)
     }
 }
 
 #[derive(Clone)]
 pub struct ResourceProfileCounter {
     description: &'static str,
     value: usize,
     size: usize,
@@ -1114,31 +1114,37 @@ impl Profiler {
                 &renderer_timers.gpu_time,
             ],
             debug_renderer,
             false,
             &mut self.draw_state
         );
 
         if !gpu_samplers.is_empty() {
-            let mut samplers = Vec::<FloatProfileCounter>::new();
+            let mut samplers = Vec::<PercentageProfileCounter>::new();
             // Gathering unique GPU samplers. This has O(N^2) complexity,
             // but we only have a few samplers per target.
+            let mut total = 0.0;
             for sampler in gpu_samplers {
                 let value = sampler.count as f32 * screen_fraction;
+                total += value;
                 match samplers.iter().position(|s| {
                     s.description as *const _ == sampler.tag.label as *const _
                 }) {
                     Some(pos) => samplers[pos].value += value,
-                    None => samplers.push(FloatProfileCounter {
+                    None => samplers.push(PercentageProfileCounter {
                         description: sampler.tag.label,
                         value,
                     }),
                 }
             }
+            samplers.push(PercentageProfileCounter {
+                description: "Total",
+                value: total,
+            });
             let samplers: Vec<&ProfileCounter> = samplers.iter().map(|sampler| {
                 sampler as &ProfileCounter
             }).collect();
             Profiler::draw_counters(
                 &samplers,
                 debug_renderer,
                 false,
                 &mut self.draw_state,
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -2,18 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets, ImageDescriptor, ImageFormat};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use border::BorderCacheKey;
 use box_shadow::{BoxShadowCacheKey};
-use clip::{ClipSource, ClipStore, ClipWorkItem};
-use clip_scroll_tree::CoordinateSystemId;
+use clip::{ClipItem, ClipStore, ClipNodeRange};
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{BorderInstance, ImageSource, RasterizationSpace, UvRectKind};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
@@ -27,20 +26,29 @@ use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 #[cfg(feature = "pathfinder")]
 use webrender_api::DevicePixel;
 
+const RENDER_TASK_SIZE_SANITY_CHECK: i32 = 16000;
 const FLOATS_PER_RENDER_TASK_INFO: usize = 8;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
+fn render_task_sanity_check(size: &DeviceIntSize) {
+    if size.width > RENDER_TASK_SIZE_SANITY_CHECK ||
+        size.height > RENDER_TASK_SIZE_SANITY_CHECK {
+        error!("Attempting to create a render task of size {}x{}", size.width, size.height);
+        panic!();
+    }
+}
+
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskId(pub u32, FrameId); // TODO(gw): Make private when using GPU cache!
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -174,18 +182,17 @@ pub enum RenderTaskLocation {
     TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
-    pub clips: Vec<ClipWorkItem>,
-    pub coordinate_system_id: CoordinateSystemId,
+    pub clip_node_range: ClipNodeRange,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipRegionTask {
     pub clip_data_address: GpuCacheAddress,
 }
@@ -308,45 +315,73 @@ pub struct RenderTask {
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
     pub saved_index: Option<SavedTargetIndex>,
 }
 
 impl RenderTask {
+    #[inline]
+    pub fn with_dynamic_location(
+        size: DeviceIntSize,
+        children: Vec<RenderTaskId>,
+        kind: RenderTaskKind,
+        clear_mode: ClearMode,
+    ) -> Self {
+        render_task_sanity_check(&size);
+
+        RenderTask {
+            location: RenderTaskLocation::Dynamic(None, Some(size)),
+            children,
+            kind,
+            clear_mode,
+            saved_index: None,
+        }
+    }
+
     pub fn new_picture(
         location: RenderTaskLocation,
         prim_index: PrimitiveIndex,
         content_origin: DeviceIntPoint,
         children: Vec<RenderTaskId>,
         uv_rect_kind: UvRectKind,
     ) -> Self {
+        let size = match location {
+            RenderTaskLocation::Dynamic(_, Some(size)) => Some(size),
+            RenderTaskLocation::Fixed(rect) => Some(rect.size),
+            RenderTaskLocation::TextureCache(_, _, rect) => Some(rect.size),
+            _ => None,
+        };
+
+        if let Some(size) = size {
+            render_task_sanity_check(&size);
+        }
+
         RenderTask {
+            location,
             children,
-            location,
             kind: RenderTaskKind::Picture(PictureTask {
                 prim_index,
                 content_origin,
                 uv_rect_handle: GpuCacheHandle::new(),
                 uv_rect_kind,
             }),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
-        RenderTask {
-            children: Vec::new(),
-            location: RenderTaskLocation::Dynamic(None, Some(screen_rect.size)),
-            kind: RenderTaskKind::Readback(screen_rect),
-            clear_mode: ClearMode::Transparent,
-            saved_index: None,
-        }
+        RenderTask::with_dynamic_location(
+            screen_rect.size,
+            Vec::new(),
+            RenderTaskKind::Readback(screen_rect),
+            ClearMode::Transparent,
+        )
     }
 
     pub fn new_blit(
         size: DeviceIntSize,
         source: BlitSource,
     ) -> Self {
         RenderTask::new_blit_with_padding(size, &DeviceIntSideOffsets::zero(), source)
     }
@@ -365,130 +400,123 @@ impl RenderTask {
         // executes.
         if let BlitSource::RenderTask { task_id } = source {
             children.push(task_id);
         }
 
         size.width += padding.horizontal();
         size.height += padding.vertical();
 
-        RenderTask {
+        RenderTask::with_dynamic_location(
+            size,
             children,
-            location: RenderTaskLocation::Dynamic(None, Some(size)),
-            kind: RenderTaskKind::Blit(BlitTask {
+            RenderTaskKind::Blit(BlitTask {
                 source,
                 padding: *padding,
             }),
-            clear_mode: ClearMode::Transparent,
-            saved_index: None,
-        }
+            ClearMode::Transparent,
+        )
     }
 
     pub fn new_mask(
         outer_rect: DeviceIntRect,
-        clips: Vec<ClipWorkItem>,
-        prim_coordinate_system_id: CoordinateSystemId,
+        clip_node_range: ClipNodeRange,
         clip_store: &mut ClipStore,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         render_tasks: &mut RenderTaskTree,
     ) -> Self {
         let mut children = Vec::new();
 
         // Step through the clip sources that make up this mask. If we find
         // any box-shadow clip sources, request that image from the render
         // task cache. This allows the blurred box-shadow rect to be cached
         // in the texture cache across frames.
         // TODO(gw): Consider moving this logic outside this function, especially
         //           as we add more clip sources that depend on render tasks.
         // TODO(gw): If this ever shows up in a profile, we could pre-calculate
         //           whether a ClipSources contains any box-shadows and skip
         //           this iteration for the majority of cases.
-        for clip_item in &clips {
-            let clip_sources = &mut clip_store[clip_item.clip_sources_index];
-            for &mut (ref mut clip, _) in &mut clip_sources.clips {
-                match *clip {
-                    ClipSource::BoxShadow(ref mut info) => {
-                        let (cache_size, cache_key) = info.cache_key
-                            .as_ref()
-                            .expect("bug: no cache key set")
-                            .clone();
-                        let blur_radius_dp = cache_key.blur_radius_dp as f32;
-                        let clip_data_address = gpu_cache.get_address(&info.clip_data_handle);
+        for i in 0 .. clip_node_range.count {
+            let (clip_node, _) = clip_store.get_node_from_range_mut(&clip_node_range, i);
+            match clip_node.item {
+                ClipItem::BoxShadow(ref mut info) => {
+                    let (cache_size, cache_key) = info.cache_key
+                        .as_ref()
+                        .expect("bug: no cache key set")
+                        .clone();
+                    let blur_radius_dp = cache_key.blur_radius_dp as f32;
+                    let clip_data_address = gpu_cache.get_address(&info.clip_data_handle);
 
-                        // Request a cacheable render task with a blurred, minimal
-                        // sized box-shadow rect.
-                        info.cache_handle = Some(resource_cache.request_render_task(
-                            RenderTaskCacheKey {
-                                size: cache_size,
-                                kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
-                            },
-                            gpu_cache,
-                            render_tasks,
-                            None,
-                            false,
-                            |render_tasks| {
-                                // Draw the rounded rect.
-                                let mask_task = RenderTask::new_rounded_rect_mask(
-                                    cache_size,
-                                    clip_data_address,
-                                );
+                    // Request a cacheable render task with a blurred, minimal
+                    // sized box-shadow rect.
+                    info.cache_handle = Some(resource_cache.request_render_task(
+                        RenderTaskCacheKey {
+                            size: cache_size,
+                            kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
+                        },
+                        gpu_cache,
+                        render_tasks,
+                        None,
+                        false,
+                        |render_tasks| {
+                            // Draw the rounded rect.
+                            let mask_task = RenderTask::new_rounded_rect_mask(
+                                cache_size,
+                                clip_data_address,
+                            );
 
-                                let mask_task_id = render_tasks.add(mask_task);
+                            let mask_task_id = render_tasks.add(mask_task);
 
-                                // Blur it
-                                let blur_render_task = RenderTask::new_blur(
-                                    blur_radius_dp,
-                                    mask_task_id,
-                                    render_tasks,
-                                    RenderTargetKind::Alpha,
-                                    ClearMode::Zero,
-                                );
+                            // Blur it
+                            let blur_render_task = RenderTask::new_blur(
+                                blur_radius_dp,
+                                mask_task_id,
+                                render_tasks,
+                                RenderTargetKind::Alpha,
+                                ClearMode::Zero,
+                            );
 
-                                let root_task_id = render_tasks.add(blur_render_task);
-                                children.push(root_task_id);
+                            let root_task_id = render_tasks.add(blur_render_task);
+                            children.push(root_task_id);
 
-                                root_task_id
-                            }
-                        ));
-                    }
-                    ClipSource::Rectangle(..) |
-                    ClipSource::RoundedRectangle(..) |
-                    ClipSource::Image(..) |
-                    ClipSource::LineDecoration(..) => {}
+                            root_task_id
+                        }
+                    ));
                 }
+                ClipItem::Rectangle(..) |
+                ClipItem::RoundedRectangle(..) |
+                ClipItem::Image(..) |
+                ClipItem::LineDecoration(..) => {}
             }
         }
 
-        RenderTask {
+        RenderTask::with_dynamic_location(
+            outer_rect.size,
             children,
-            location: RenderTaskLocation::Dynamic(None, Some(outer_rect.size)),
-            kind: RenderTaskKind::CacheMask(CacheMaskTask {
+            RenderTaskKind::CacheMask(CacheMaskTask {
                 actual_rect: outer_rect,
-                clips,
-                coordinate_system_id: prim_coordinate_system_id,
+                clip_node_range,
             }),
-            clear_mode: ClearMode::One,
-            saved_index: None,
-        }
+            ClearMode::One,
+        )
     }
 
     pub fn new_rounded_rect_mask(
         size: DeviceIntSize,
         clip_data_address: GpuCacheAddress,
     ) -> Self {
-        RenderTask {
-            children: Vec::new(),
-            location: RenderTaskLocation::Dynamic(None, Some(size)),
-            kind: RenderTaskKind::ClipRegion(ClipRegionTask {
+        RenderTask::with_dynamic_location(
+            size,
+            Vec::new(),
+            RenderTaskKind::ClipRegion(ClipRegionTask {
                 clip_data_address,
             }),
-            clear_mode: ClearMode::One,
-            saved_index: None,
-        }
+            ClearMode::One,
+        )
     }
 
     // Construct a render task to apply a blur to a primitive.
     // The render task chain that is constructed looks like:
     //
     //    PrimitiveCacheTask: Draw the primitives.
     //           ^
     //           |
@@ -531,75 +559,71 @@ impl RenderTask {
             let downscaling_task = RenderTask::new_scaling(
                 target_kind,
                 downscaling_src_task_id,
                 adjusted_blur_target_size,
             );
             downscaling_src_task_id = render_tasks.add(downscaling_task);
         }
 
-        let blur_task_v = RenderTask {
-            children: vec![downscaling_src_task_id],
-            location: RenderTaskLocation::Dynamic(None, Some(adjusted_blur_target_size)),
-            kind: RenderTaskKind::VerticalBlur(BlurTask {
+        let blur_task_v = RenderTask::with_dynamic_location(
+            adjusted_blur_target_size,
+            vec![downscaling_src_task_id],
+            RenderTaskKind::VerticalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 uv_rect_handle: GpuCacheHandle::new(),
                 uv_rect_kind,
             }),
             clear_mode,
-            saved_index: None,
-        };
+        );
 
         let blur_task_v_id = render_tasks.add(blur_task_v);
 
-        RenderTask {
-            children: vec![blur_task_v_id],
-            location: RenderTaskLocation::Dynamic(None, Some(adjusted_blur_target_size)),
-            kind: RenderTaskKind::HorizontalBlur(BlurTask {
+        RenderTask::with_dynamic_location(
+            adjusted_blur_target_size,
+            vec![blur_task_v_id],
+            RenderTaskKind::HorizontalBlur(BlurTask {
                 blur_std_deviation: adjusted_blur_std_deviation,
                 target_kind,
                 uv_rect_handle: GpuCacheHandle::new(),
                 uv_rect_kind,
             }),
             clear_mode,
-            saved_index: None,
-        }
+        )
     }
 
     pub fn new_border(
         size: DeviceIntSize,
         instances: Vec<BorderInstance>,
     ) -> Self {
-        RenderTask {
-            children: Vec::new(),
-            location: RenderTaskLocation::Dynamic(None, Some(size)),
-            kind: RenderTaskKind::Border(BorderTask {
+        RenderTask::with_dynamic_location(
+            size,
+            Vec::new(),
+            RenderTaskKind::Border(BorderTask {
                 instances,
             }),
-            clear_mode: ClearMode::Transparent,
-            saved_index: None,
-        }
+            ClearMode::Transparent,
+        )
     }
 
     pub fn new_scaling(
         target_kind: RenderTargetKind,
         src_task_id: RenderTaskId,
         target_size: DeviceIntSize,
     ) -> Self {
-        RenderTask {
-            children: vec![src_task_id],
-            location: RenderTaskLocation::Dynamic(None, Some(target_size)),
-            kind: RenderTaskKind::Scaling(target_kind),
-            clear_mode: match target_kind {
+        RenderTask::with_dynamic_location(
+            target_size,
+            vec![src_task_id],
+            RenderTaskKind::Scaling(target_kind),
+            match target_kind {
                 RenderTargetKind::Color => ClearMode::Transparent,
                 RenderTargetKind::Alpha => ClearMode::One,
             },
-            saved_index: None,
-        }
+        )
     }
 
     #[cfg(feature = "pathfinder")]
     pub fn new_glyph(location: RenderTaskLocation,
                      mesh: Mesh,
                      origin: &DeviceIntPoint,
                      subpixel_offset: &TypedPoint2D<f32, DevicePixel>,
                      render_mode: FontRenderMode,
@@ -895,17 +919,17 @@ impl RenderTask {
 
     #[cfg(feature = "debugger")]
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskTree) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.prim_index));
             }
             RenderTaskKind::CacheMask(ref task) => {
-                pt.new_level(format!("CacheMask with {} clips", task.clips.len()));
+                pt.new_level(format!("CacheMask with {} clips", task.clip_node_range.count));
                 pt.add_item(format!("rect: {:?}", task.actual_rect));
             }
             RenderTaskKind::ClipRegion(..) => {
                 pt.new_level("ClipRegion".to_owned());
             }
             RenderTaskKind::VerticalBlur(ref task) => {
                 pt.new_level("VerticalBlur".to_owned());
                 task.print_with(pt);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -207,27 +207,28 @@ impl BatchKind {
             BatchKind::TextRun(_) => GPU_TAG_PRIM_TEXT_RUN,
         }
     }
 }
 
 bitflags! {
     #[derive(Default)]
     pub struct DebugFlags: u32 {
-        const PROFILER_DBG      = 1 << 0;
-        const RENDER_TARGET_DBG = 1 << 1;
-        const TEXTURE_CACHE_DBG = 1 << 2;
-        const GPU_TIME_QUERIES  = 1 << 3;
-        const GPU_SAMPLE_QUERIES= 1 << 4;
-        const DISABLE_BATCHING  = 1 << 5;
-        const EPOCHS            = 1 << 6;
-        const COMPACT_PROFILER  = 1 << 7;
-        const ECHO_DRIVER_MESSAGES = 1 << 8;
-        const NEW_FRAME_INDICATOR = 1 << 9;
-        const NEW_SCENE_INDICATOR = 1 << 10;
+        const PROFILER_DBG          = 1 << 0;
+        const RENDER_TARGET_DBG     = 1 << 1;
+        const TEXTURE_CACHE_DBG     = 1 << 2;
+        const GPU_TIME_QUERIES      = 1 << 3;
+        const GPU_SAMPLE_QUERIES    = 1 << 4;
+        const DISABLE_BATCHING      = 1 << 5;
+        const EPOCHS                = 1 << 6;
+        const COMPACT_PROFILER      = 1 << 7;
+        const ECHO_DRIVER_MESSAGES  = 1 << 8;
+        const NEW_FRAME_INDICATOR   = 1 << 9;
+        const NEW_SCENE_INDICATOR   = 1 << 10;
+        const SHOW_OVERDRAW         = 1 << 11;
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
     if before & select != after & select {
         Some(after.contains(select))
     } else {
         None
@@ -2163,16 +2164,19 @@ impl Renderer {
                 self.set_debug_flag(DebugFlags::GPU_SAMPLE_QUERIES, enable);
             }
             DebugCommand::EnableNewFrameIndicator(enable) => {
                 self.set_debug_flag(DebugFlags::NEW_FRAME_INDICATOR, enable);
             }
             DebugCommand::EnableNewSceneIndicator(enable) => {
                 self.set_debug_flag(DebugFlags::NEW_SCENE_INDICATOR, enable);
             }
+            DebugCommand::EnableShowOverdraw(enable) => {
+                self.set_debug_flag(DebugFlags::SHOW_OVERDRAW, enable);
+            }
             DebugCommand::EnableDualSourceBlending(_) => {
                 panic!("Should be handled by render backend");
             }
             DebugCommand::FetchDocuments |
             DebugCommand::FetchClipScrollTree => {}
             DebugCommand::FetchRenderTasks => {
                 let json = self.get_render_tasks_for_debugger();
                 self.debug_server.send(json);
@@ -2299,17 +2303,17 @@ impl Renderer {
 
         let cpu_frame_id = profile_timers.cpu_time.profile(|| {
             let _gm = self.gpu_profile.start_marker("begin frame");
             let frame_id = self.device.begin_frame();
             self.gpu_profile.begin_frame(frame_id);
 
             self.device.disable_scissor();
             self.device.disable_depth();
-            self.device.set_blend(false);
+            self.set_blend(false, FramebufferKind::Main);
             //self.update_shaders();
 
             self.update_texture_cache();
 
             frame_id
         });
 
         profile_timers.cpu_time.profile(|| {
@@ -2842,22 +2846,28 @@ impl Renderer {
         self.profile_counters.color_targets.inc();
         let _gm = self.gpu_profile.start_marker("color target");
 
         // sanity check for the depth buffer
         if let Some((texture, _)) = render_target {
             assert!(texture.has_depth() >= target.needs_depth());
         }
 
+        let framebuffer_kind = if render_target.is_none() {
+            FramebufferKind::Main
+        } else {
+            FramebufferKind::Other
+        };
+
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_TARGET);
             self.device
                 .bind_draw_target(render_target, Some(target_size));
             self.device.disable_depth();
-            self.device.set_blend(false);
+            self.set_blend(false, framebuffer_kind);
 
             let depth_clear = if !depth_is_ready && target.needs_depth() {
                 self.device.enable_depth_write();
                 Some(1.0)
             } else {
                 None
             };
 
@@ -2898,17 +2908,17 @@ impl Renderer {
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
-            self.device.set_blend(false);
+            self.set_blend(false, framebuffer_kind);
             self.shaders.cs_blur_rgba8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
@@ -2928,17 +2938,17 @@ impl Renderer {
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheRGBA8);
 
         //TODO: record the pixel count for cached primitives
 
         if target.needs_depth() {
             let _gl = self.gpu_profile.start_marker("opaque batches");
             let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
-            self.device.set_blend(false);
+            self.set_blend(false, framebuffer_kind);
             //Note: depth equality is needed for split planes
             self.device.set_depth_func(DepthFunction::LessEqual);
             self.device.enable_depth();
             self.device.enable_depth_write();
 
             for alpha_batch_container in &target.alpha_batch_containers {
                 if let Some(target_rect) = alpha_batch_container.target_rect {
                     // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
@@ -2958,17 +2968,17 @@ impl Renderer {
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
                 {
                     self.shaders
-                        .get(&batch.key)
+                        .get(&batch.key, self.debug_flags)
                         .bind(
                             &mut self.device, projection,
                             &mut self.renderer_errors,
                         );
 
                     let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
                     self.draw_instanced_batch(
                         &batch.instances,
@@ -2984,17 +2994,17 @@ impl Renderer {
             }
 
             self.device.disable_depth_write();
             self.gpu_profile.finish_sampler(opaque_sampler);
         }
 
         let _gl = self.gpu_profile.start_marker("alpha batches");
         let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
-        self.device.set_blend(true);
+        self.set_blend(true, framebuffer_kind);
         let mut prev_blend_mode = BlendMode::None;
 
         for alpha_batch_container in &target.alpha_batch_containers {
             if let Some(target_rect) = alpha_batch_container.target_rect {
                 // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
                 let rect = if render_target.is_none() {
                     let mut rect = target_rect
                         .intersection(&framebuffer_target_rect.to_i32())
@@ -3005,24 +3015,28 @@ impl Renderer {
                     target_rect
                 };
                 self.device.enable_scissor();
                 self.device.set_scissor_rect(rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
                 self.shaders
-                    .get(&batch.key)
+                    .get(&batch.key, self.debug_flags)
                     .bind(
                         &mut self.device, projection,
                         &mut self.renderer_errors,
                     );
 
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
+                        _ if self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) &&
+                                framebuffer_kind == FramebufferKind::Main => {
+                            self.device.set_blend_mode_show_overdraw();
+                        }
                         BlendMode::None => {
                             unreachable!("bug: opaque blend in alpha pass");
                         }
                         BlendMode::Alpha => {
                             self.device.set_blend_mode_alpha();
                         }
                         BlendMode::PremultipliedAlpha => {
                             self.device.set_blend_mode_premultiplied_alpha();
@@ -3068,43 +3082,43 @@ impl Renderer {
                 self.draw_instanced_batch(
                     &batch.instances,
                     VertexArrayKind::Primitive,
                     &batch.key.textures,
                     stats
                 );
 
                 if batch.key.blend_mode == BlendMode::SubpixelWithBgColor {
-                    self.device.set_blend_mode_subpixel_with_bg_color_pass1();
+                    self.set_blend_mode_subpixel_with_bg_color_pass1(framebuffer_kind);
                     self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass1 as _);
 
                     // When drawing the 2nd and 3rd passes, we know that the VAO, textures etc
                     // are all set up from the previous draw_instanced_batch call,
                     // so just issue a draw call here to avoid re-uploading the
                     // instances and re-binding textures etc.
                     self.device
                         .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
 
-                    self.device.set_blend_mode_subpixel_with_bg_color_pass2();
+                    self.set_blend_mode_subpixel_with_bg_color_pass2(framebuffer_kind);
                     self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass2 as _);
 
                     self.device
                         .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
 
                     prev_blend_mode = BlendMode::None;
                 }
             }
 
             if alpha_batch_container.target_rect.is_some() {
                 self.device.disable_scissor();
             }
         }
 
         self.device.disable_depth();
-        self.device.set_blend(false);
+        self.set_blend(false, framebuffer_kind);
         self.gpu_profile.finish_sampler(transparent_sampler);
 
         // For any registered image outputs on this render target,
         // get the texture from caller and blit it.
         for output in &target.outputs {
             let handler = self.output_image_handler
                 .as_mut()
                 .expect("Found output image, but no handler set!");
@@ -3186,17 +3200,17 @@ impl Renderer {
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
-            self.device.set_blend(false);
+            self.set_blend(false, FramebufferKind::Other);
             self.shaders.cs_blur_a8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
@@ -3216,18 +3230,18 @@ impl Renderer {
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_CLIP);
 
             // switch to multiplicative blending
-            self.device.set_blend(true);
-            self.device.set_blend_mode_multiply();
+            self.set_blend(true, FramebufferKind::Other);
+            self.set_blend_mode_multiply(FramebufferKind::Other);
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip rectangles");
                 self.shaders.cs_clip_rectangle.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
@@ -3321,61 +3335,61 @@ impl Renderer {
                 ORTHO_FAR_PLANE,
             );
             (target_size, projection)
         };
 
         self.device.disable_depth();
         self.device.disable_depth_write();
 
-        self.device.set_blend(false);
+        self.set_blend(false, FramebufferKind::Other);
 
         // Handle any Pathfinder glyphs.
         let stencil_page = self.stencil_glyphs(&target.glyphs, &projection, &target_size, stats);
 
         {
             let texture = self.texture_resolver
                 .resolve(texture)
                 .expect("BUG: invalid target texture");
             self.device
                 .bind_draw_target(Some((texture, layer)), Some(target_size));
         }
 
         self.device.disable_depth();
         self.device.disable_depth_write();
-        self.device.set_blend(false);
+        self.set_blend(false, FramebufferKind::Other);
 
         for rect in &target.clears {
             self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, Some(*rect));
         }
 
         // Handle any blits to this texture from child tasks.
         self.handle_blits(&target.blits, render_tasks);
 
         // Draw any borders for this target.
         if !target.border_segments.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
 
-            self.device.set_blend(true);
-            self.device.set_blend_mode_premultiplied_alpha();
+            self.set_blend(true, FramebufferKind::Other);
+            self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
 
             self.shaders.cs_border_segment.bind(
                 &mut self.device,
                 &projection,
                 &mut self.renderer_errors,
             );
 
             self.draw_instanced_batch(
                 &target.border_segments,
                 VertexArrayKind::Border,
                 &BatchTextures::no_texture(),
                 stats,
             );
 
-            self.device.set_blend(false);
+            self.set_blend(false, FramebufferKind::Other);
         }
 
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             match target.target_kind {
                 RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
@@ -3619,18 +3633,18 @@ impl Renderer {
         let _gm = self.gpu_profile.start_marker("tile frame draw");
 
         if frame.passes.is_empty() {
             frame.has_been_rendered = true;
             return;
         }
 
         self.device.disable_depth_write();
+        self.set_blend(false, FramebufferKind::Other);
         self.device.disable_stencil();
-        self.device.set_blend(false);
 
         self.bind_frame_data(frame);
         self.texture_resolver.begin_frame();
 
         for (pass_index, pass) in frame.passes.iter_mut().enumerate() {
             self.gpu_profile.place_marker(&format!("pass {}", pass_index));
 
             self.texture_resolver.bind(
@@ -4008,16 +4022,62 @@ impl Renderer {
         #[cfg(feature = "capture")]
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
         self.device.end_frame();
     }
+
+    // Sets the blend mode. Blend is unconditionally set if the "show overdraw" debugging mode is
+    // enabled.
+    fn set_blend(&self, mut blend: bool, framebuffer_kind: FramebufferKind) {
+        if framebuffer_kind == FramebufferKind::Main &&
+                self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) {
+            blend = true
+        }
+        self.device.set_blend(blend)
+    }
+
+    fn set_blend_mode_multiply(&self, framebuffer_kind: FramebufferKind) {
+        if framebuffer_kind == FramebufferKind::Main &&
+                self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) {
+            self.device.set_blend_mode_show_overdraw();
+        } else {
+            self.device.set_blend_mode_multiply();
+        }
+    }
+
+    fn set_blend_mode_premultiplied_alpha(&self, framebuffer_kind: FramebufferKind) {
+        if framebuffer_kind == FramebufferKind::Main &&
+                self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) {
+            self.device.set_blend_mode_show_overdraw();
+        } else {
+            self.device.set_blend_mode_premultiplied_alpha();
+        }
+    }
+
+    fn set_blend_mode_subpixel_with_bg_color_pass1(&self, framebuffer_kind: FramebufferKind) {
+        if framebuffer_kind == FramebufferKind::Main &&
+                self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) {
+            self.device.set_blend_mode_show_overdraw();
+        } else {
+            self.device.set_blend_mode_subpixel_with_bg_color_pass1();
+        }
+    }
+
+    fn set_blend_mode_subpixel_with_bg_color_pass2(&self, framebuffer_kind: FramebufferKind) {
+        if framebuffer_kind == FramebufferKind::Main &&
+                self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) {
+            self.device.set_blend_mode_show_overdraw();
+        } else {
+            self.device.set_blend_mode_subpixel_with_bg_color_pass2();
+        }
+    }
 }
 
 pub enum ExternalImageSource<'a> {
     RawData(&'a [u8]),  // raw buffers.
     NativeTexture(u32), // It's a gl::GLuint texture handle
     Invalid,
 }
 
@@ -4607,8 +4667,14 @@ fn get_vao<'a>(vertex_array_kind: Vertex
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
         VertexArrayKind::Border => &vaos.border_vao,
     }
 }
+
+#[derive(Clone, Copy, PartialEq)]
+enum FramebufferKind {
+    Main,
+    Other,
+}
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -8,17 +8,17 @@ use api::{
 };
 use batch::{BatchKey, BatchKind, BrushBatchKind};
 use device::{Device, Program, ShaderError};
 use euclid::{Transform3D};
 use glyph_rasterizer::GlyphFormat;
 use renderer::{
     desc,
     MAX_VERTEX_TEXTURE_WIDTH,
-    BlendMode, ImageBufferKind, RendererError, RendererOptions,
+    BlendMode, DebugFlags, ImageBufferKind, RendererError, RendererOptions,
     TextureSampler, VertexArrayKind,
 };
 
 use gleam::gl::GlType;
 use time::precise_time_ns;
 
 
 impl ImageBufferKind {
@@ -45,16 +45,17 @@ impl ImageBufferKind {
 pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [
     ImageBufferKind::Texture2D,
     ImageBufferKind::TextureRect,
     ImageBufferKind::TextureExternal,
     ImageBufferKind::Texture2DArray,
 ];
 
 const ALPHA_FEATURE: &str = "ALPHA_PASS";
+const DEBUG_OVERDRAW_FEATURE: &str = "DEBUG_OVERDRAW";
 const DITHERING_FEATURE: &str = "DITHERING";
 const DUAL_SOURCE_FEATURE: &str = "DUAL_SOURCE_BLENDING";
 
 pub(crate) enum ShaderKind {
     Primitive,
     Cache(VertexArrayKind),
     ClipCache,
     Brush,
@@ -176,16 +177,17 @@ impl LazilyCompiledShader {
 //   Used for brush primitives in the alpha
 //   pass. Assumes that AA should be applied
 //   along the primitive edge, and also that
 //   clip mask is present.
 struct BrushShader {
     opaque: LazilyCompiledShader,
     alpha: LazilyCompiledShader,
     dual_source: Option<LazilyCompiledShader>,
+    debug_overdraw: LazilyCompiledShader,
 }
 
 impl BrushShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
         precache: bool,
@@ -222,25 +224,39 @@ impl BrushShader {
                 precache,
             )?;
 
             Some(shader)
         } else {
             None
         };
 
+        let mut debug_overdraw_features = features.to_vec();
+        debug_overdraw_features.push(DEBUG_OVERDRAW_FEATURE);
+
+        let debug_overdraw = LazilyCompiledShader::new(
+            ShaderKind::Brush,
+            name,
+            &debug_overdraw_features,
+            device,
+            precache,
+        )?;
+
         Ok(BrushShader {
             opaque,
             alpha,
             dual_source,
+            debug_overdraw,
         })
     }
 
-    fn get(&mut self, blend_mode: BlendMode) -> &mut LazilyCompiledShader {
+    fn get(&mut self, blend_mode: BlendMode, debug_flags: DebugFlags)
+           -> &mut LazilyCompiledShader {
         match blend_mode {
+            _ if debug_flags.contains(DebugFlags::SHOW_OVERDRAW) => &mut self.debug_overdraw,
             BlendMode::None => &mut self.opaque,
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelWithBgColor => &mut self.alpha,
             BlendMode::SubpixelDualSource => {
                 self.dual_source
@@ -251,22 +267,24 @@ impl BrushShader {
     }
 
     fn deinit(self, device: &mut Device) {
         self.opaque.deinit(device);
         self.alpha.deinit(device);
         if let Some(dual_source) = self.dual_source {
             dual_source.deinit(device);
         }
+        self.debug_overdraw.deinit(device);
     }
 }
 
 pub struct TextShader {
     simple: LazilyCompiledShader,
     glyph_transform: LazilyCompiledShader,
+    debug_overdraw: LazilyCompiledShader,
 }
 
 impl TextShader {
     fn new(
         name: &'static str,
         device: &mut Device,
         features: &[&'static str],
         precache: bool,
@@ -285,36 +303,50 @@ impl TextShader {
         let glyph_transform = LazilyCompiledShader::new(
             ShaderKind::Text,
             name,
             &glyph_transform_features,
             device,
             precache,
         )?;
 
-        Ok(TextShader { simple, glyph_transform })
+        let mut debug_overdraw_features = features.to_vec();
+        debug_overdraw_features.push("DEBUG_OVERDRAW");
+
+        let debug_overdraw = LazilyCompiledShader::new(
+            ShaderKind::Text,
+            name,
+            &debug_overdraw_features,
+            device,
+            precache,
+        )?;
+
+        Ok(TextShader { simple, glyph_transform, debug_overdraw })
     }
 
     pub fn get(
         &mut self,
         glyph_format: GlyphFormat,
+        debug_flags: DebugFlags,
     ) -> &mut LazilyCompiledShader {
         match glyph_format {
+            _ if debug_flags.contains(DebugFlags::SHOW_OVERDRAW) => &mut self.debug_overdraw,
             GlyphFormat::Alpha |
             GlyphFormat::Subpixel |
             GlyphFormat::Bitmap |
             GlyphFormat::ColorBitmap => &mut self.simple,
             GlyphFormat::TransformedAlpha |
             GlyphFormat::TransformedSubpixel => &mut self.glyph_transform,
         }
     }
 
     fn deinit(self, device: &mut Device) {
         self.simple.deinit(device);
         self.glyph_transform.deinit(device);
+        self.debug_overdraw.deinit(device);
     }
 }
 
 fn create_prim_shader(
     name: &'static str,
     device: &mut Device,
     features: &[&'static str],
     vertex_format: VertexArrayKind,
@@ -668,17 +700,17 @@ impl Shaders {
         buffer_kind: ImageBufferKind,
         format: YuvFormat,
         color_space: YuvColorSpace,
     ) -> usize {
         ((buffer_kind as usize) * YUV_FORMATS.len() + (format as usize)) * YUV_COLOR_SPACES.len() +
             (color_space as usize)
     }
 
-    pub fn get(&mut self, key: &BatchKey) -> &mut LazilyCompiledShader {
+    pub fn get(&mut self, key: &BatchKey, debug_flags: DebugFlags) -> &mut LazilyCompiledShader {
         match key.kind {
             BatchKind::SplitComposite => {
                 &mut self.ps_split_composite
             }
             BatchKind::Brush(brush_kind) => {
                 let brush_shader = match brush_kind {
                     BrushBatchKind::Solid => {
                         &mut self.brush_solid
@@ -703,24 +735,24 @@ impl Shaders {
                     BrushBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
                         let shader_index =
                             Self::get_yuv_shader_index(image_buffer_kind, format, color_space);
                         self.brush_yuv_image[shader_index]
                             .as_mut()
                             .expect("Unsupported YUV shader kind")
                     }
                 };
-                brush_shader.get(key.blend_mode)
+                brush_shader.get(key.blend_mode, debug_flags)
             }
             BatchKind::TextRun(glyph_format) => {
                 let text_shader = match key.blend_mode {
                     BlendMode::SubpixelDualSource => &mut self.ps_text_run_dual_source,
                     _ => &mut self.ps_text_run,
                 };
-                text_shader.get(glyph_format)
+                text_shader.get(glyph_format, debug_flags)
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.brush_solid.deinit(device);
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -11,18 +11,17 @@ use clip_scroll_tree::SpatialNodeIndex;
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, TransformData, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
-use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveStore};
-use prim_store::{BrushKind, DeferredResolve};
+use prim_store::{PrimitiveIndex, PrimitiveStore, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
 use webrender_api::{DevicePixel, FontRenderMode};
@@ -343,42 +342,34 @@ impl RenderTarget for ColorRenderTarget 
     ) {
         let mut merged_batches = AlphaBatchContainer::new(None);
 
         for task_id in &self.alpha_tasks {
             let task = &render_tasks[*task_id];
 
             match task.kind {
                 RenderTaskKind::Picture(ref pic_task) => {
-                    let brush_index = ctx.prim_store.cpu_metadata[pic_task.prim_index.0].cpu_prim_index;
-                    let brush = &ctx.prim_store.cpu_brushes[brush_index.0];
-                    match brush.kind {
-                        BrushKind::Picture { pic_index, .. } => {
-                            let pic = &ctx.prim_store.pictures[pic_index.0];
-                            let (target_rect, _) = task.get_target_rect();
+                    let pic = ctx.prim_store.get_pic(pic_task.prim_index);
 
-                            let mut batch_builder = AlphaBatchBuilder::new(self.screen_size, target_rect);
+                    let (target_rect, _) = task.get_target_rect();
+
+                    let mut batch_builder = AlphaBatchBuilder::new(self.screen_size, target_rect);
 
-                            batch_builder.add_pic_to_batch(
-                                pic,
-                                *task_id,
-                                ctx,
-                                gpu_cache,
-                                render_tasks,
-                                deferred_resolves,
-                                prim_headers,
-                            );
+                    batch_builder.add_pic_to_batch(
+                        pic,
+                        *task_id,
+                        ctx,
+                        gpu_cache,
+                        render_tasks,
+                        deferred_resolves,
+                        prim_headers,
+                    );
 
-                            if let Some(batch_container) = batch_builder.build(&mut merged_batches) {
-                                self.alpha_batch_containers.push(batch_container);
-                            }
-                        }
-                        _ => {
-                            unreachable!();
-                        }
+                    if let Some(batch_container) = batch_builder.build(&mut merged_batches) {
+                        self.alpha_batch_containers.push(batch_container);
                     }
                 }
                 _ => {
                     unreachable!();
                 }
             }
         }
 
@@ -409,37 +400,26 @@ impl RenderTarget for ColorRenderTarget 
                 info.add_instances(
                     &mut self.horizontal_blurs,
                     BlurDirection::Horizontal,
                     render_tasks.get_task_address(task_id),
                     render_tasks.get_task_address(task.children[0]),
                 );
             }
             RenderTaskKind::Picture(ref task_info) => {
-                let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
-                match prim_metadata.prim_kind {
-                    PrimitiveKind::Brush => {
-                        let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
-                        let pic = &ctx.prim_store.pictures[brush.get_picture_index().0];
-
-                        self.alpha_tasks.push(task_id);
+                let pic = ctx.prim_store.get_pic(task_info.prim_index);
+                self.alpha_tasks.push(task_id);
 
-                        // If this pipeline is registered as a frame output
-                        // store the information necessary to do the copy.
-                        if let Some(pipeline_id) = pic.frame_output_pipeline_id {
-                            self.outputs.push(FrameOutput {
-                                pipeline_id,
-                                task_id,
-                            });
-                        }
-                    }
-                    _ => {
-                        // No other primitives make use of primitive caching yet!
-                        unreachable!()
-                    }
+                // If this pipeline is registered as a frame output
+                // store the information necessary to do the copy.
+                if let Some(pipeline_id) = pic.frame_output_pipeline_id {
+                    self.outputs.push(FrameOutput {
+                        pipeline_id,
+                        task_id,
+                    });
                 }
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Glyph(..) => {
@@ -590,18 +570,17 @@ impl RenderTarget for AlphaRenderTarget 
                     render_tasks.get_task_address(task_id),
                     render_tasks.get_task_address(task.children[0]),
                 );
             }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add(
                     task_address,
-                    &task_info.clips,
-                    task_info.coordinate_system_id,
+                    task_info.clip_node_range,
                     ctx.resource_cache,
                     gpu_cache,
                     clip_store,
                     ctx.transforms,
                 );
             }
             RenderTaskKind::ClipRegion(ref task) => {
                 let task_address = render_tasks.get_task_address(task_id);
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -447,24 +447,16 @@ impl<Src, Dst> FastTransform<Src, Dst> {
             FastTransform::Offset(ref offset) =>
                 FastTransform::Offset(*offset + *other_offset),
             FastTransform::Transform { transform, .. } =>
                 FastTransform::with_transform(transform.pre_translate(other_offset.to_3d()))
         }
     }
 
     #[inline(always)]
-    pub fn has_perspective_component(&self) -> bool {
-        match *self {
-            FastTransform::Offset(..) => false,
-            FastTransform::Transform { ref transform, .. } => transform.has_perspective_component(),
-        }
-    }
-
-    #[inline(always)]
     pub fn is_backface_visible(&self) -> bool {
         match *self {
             FastTransform::Offset(..) => false,
             FastTransform::Transform { ref transform, .. } => transform.is_backface_visible(),
         }
     }
 
     #[inline(always)]
@@ -484,25 +476,16 @@ impl<Src, Dst> FastTransform<Src, Dst> {
             FastTransform::Offset(offset) => {
                 let new_point = *point + offset;
                 HomogeneousVector::new(new_point.x, new_point.y, 0.0, 1.0)
             }
             FastTransform::Transform { ref transform, .. } => transform.transform_point2d_homogeneous(point),
         }
     }
 
-    #[inline(always)]
-    pub fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> Option<TypedRect<f32, Dst>> {
-        match *self {
-            FastTransform::Offset(offset) =>
-                Some(TypedRect::from_untyped(&rect.to_untyped().translate(&offset.to_untyped()))),
-            FastTransform::Transform { ref transform, .. } => transform.transform_rect(rect),
-        }
-    }
-
     pub fn unapply(&self, rect: &TypedRect<f32, Dst>) -> Option<TypedRect<f32, Src>> {
         match *self {
             FastTransform::Offset(offset) =>
                 Some(TypedRect::from_untyped(&rect.to_untyped().translate(&-offset.to_untyped()))),
             FastTransform::Transform { inverse: Some(ref inverse), is_2d: true, .. }  =>
                 inverse.transform_rect(rect),
             FastTransform::Transform { ref transform, is_2d: false, .. } =>
                 Some(transform.inverse_rect_footprint(rect)),
@@ -549,9 +532,8 @@ impl<Src, Dst> From<TypedTransform3D<f32
 impl<Src, Dst> From<TypedVector2D<f32, Src>> for FastTransform<Src, Dst> {
     fn from(vector: TypedVector2D<f32, Src>) -> Self {
         FastTransform::with_vector(vector)
     }
 }
 
 pub type LayoutFastTransform = FastTransform<LayoutPixel, LayoutPixel>;
 pub type LayoutToWorldFastTransform = FastTransform<LayoutPixel, WorldPixel>;
-pub type WorldToLayoutFastTransform = FastTransform<WorldPixel, LayoutPixel>;
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -11,17 +11,17 @@ ipc = ["ipc-channel"]
 serialize = []
 deserialize = []
 
 [dependencies]
 app_units = "0.7"
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.2.1"
-ipc-channel = {version = "0.10.0", optional = true}
+ipc-channel = {version = "0.11.0", optional = true}
 euclid = { version = "0.19", features = ["serde"] }
 serde = { version = "=1.0.66", features = ["rc"] }
 serde_derive = { version = "=1.0.66", features = ["deserialize_in_place"] }
 serde_bytes = "0.10"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.6"
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -566,16 +566,18 @@ pub enum DebugCommand {
     /// Display GPU overdraw results
     EnableGpuSampleQueries(bool),
     /// Configure if dual-source blending is used, if available.
     EnableDualSourceBlending(bool),
     /// Show an indicator that moves every time a frame is rendered.
     EnableNewFrameIndicator(bool),
     /// Show an indicator that moves every time a scene is built.
     EnableNewSceneIndicator(bool),
+    /// Show an overlay displaying overdraw amount.
+    EnableShowOverdraw(bool),
     /// Fetch current documents and display lists.
     FetchDocuments,
     /// Fetch current passes and batches.
     FetchPasses,
     /// Fetch clip-scroll tree.
     FetchClipScrollTree,
     /// Fetch render tasks.
     FetchRenderTasks,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-c939a61b83bcc9dc10742977704793e9a85b3858
+890f3746b798538d201e54b66d2711a70447b781
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -479,16 +479,22 @@ fn main() {
         args.is_present("no_batch"),
         args.is_present("precache"),
         args.is_present("slow_subpixel"),
         zoom_factor.unwrap_or(1.0),
         chase_primitive,
         notifier,
     );
 
+    if let Some(window_title) = wrench.take_title() {
+        if !cfg!(windows) {
+            window.set_title(&window_title);
+        }
+    }
+
     if let Some(subargs) = args.subcommand_matches("show") {
         render(&mut wrench, &mut window, size, &mut events_loop, subargs);
     } else if let Some(subargs) = args.subcommand_matches("png") {
         let surface = match subargs.value_of("surface") {
             Some("screen") | None => png::ReadSurface::Screen,
             Some("gpu-cache") => png::ReadSurface::GpuCache,
             _ => panic!("Unknown surface argument value")
         };
@@ -557,22 +563,16 @@ fn render<'a>(
     let mut cpu_profile_index = 0;
     let mut cursor_position = WorldPoint::zero();
 
     let dim = window.get_inner_size();
     wrench.update(dim);
     thing.do_frame(wrench);
 
     let mut body = |wrench: &mut Wrench, global_event: winit::Event| {
-        if let Some(window_title) = wrench.take_title() {
-            if !cfg!(windows) { //TODO: calling `set_title` from inside the `run_forever` loop is illegal...
-                window.set_title(&window_title);
-            }
-        }
-
         let mut do_frame = false;
         let mut do_render = false;
 
         match global_event {
             winit::Event::Awakened => {
                 do_render = true;
             }
             winit::Event::WindowEvent { event, .. } => match event {
@@ -615,16 +615,20 @@ fn render<'a>(
                         do_render = true;
                     }
                     VirtualKeyCode::Q => {
                         wrench.renderer.toggle_debug_flags(
                             DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES
                         );
                         do_render = true;
                     }
+                    VirtualKeyCode::V => {
+                        wrench.renderer.toggle_debug_flags(DebugFlags::SHOW_OVERDRAW);
+                        do_render = true;
+                    }
                     VirtualKeyCode::R => {
                         wrench.set_page_zoom(ZoomFactor::new(1.0));
                         do_frame = true;
                     }
                     VirtualKeyCode::M => {
                         wrench.api.notify_memory_pressure();
                         do_render = true;
                     }
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -547,16 +547,17 @@ impl Wrench {
         let help_lines = [
             "Esc - Quit",
             "H - Toggle help",
             "R - Toggle recreating display items each frame",
             "P - Toggle profiler",
             "O - Toggle showing intermediate targets",
             "I - Toggle showing texture caches",
             "B - Toggle showing alpha primitive rects",
+            "V - Toggle showing overdraw",
             "S - Toggle compact profiler",
             "Q - Toggle GPU queries for time and samples",
             "M - Trigger memory pressure event",
             "T - Save CPU profile to a file",
             "C - Save a capture to captures/wrench/",
             "X - Do a hit test at the current cursor position",
         ];