Bug 1499494. Update webrender to commit a0a36d9b416ca3295f8def384814ffef60903a60
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Wed, 17 Oct 2018 12:47:25 -0400
changeset 500241 a4a26e661fa517b4304e4a0d8e5f4d4e8bbba684
parent 500240 16ee6006e57cce243c85a5ab7578b43f3a084213
child 500242 a1f350ca24173934b0633bfff039d672d0187bbb
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1499494
milestone64.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 1499494. Update webrender to commit a0a36d9b416ca3295f8def384814ffef60903a60
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/ps_split_composite.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device/gl.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/gpu_glyph_renderer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/spatial_node.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -17,16 +17,17 @@ void brush_vs(
 );
 
 #define VECS_PER_SEGMENT                    2
 
 #define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
 #define BRUSH_FLAG_SEGMENT_RELATIVE             2
 #define BRUSH_FLAG_SEGMENT_REPEAT_X             4
 #define BRUSH_FLAG_SEGMENT_REPEAT_Y             8
+#define BRUSH_FLAG_TEXEL_RECT                  16
 
 void main(void) {
     // Load the brush instance from vertex attributes.
     int prim_header_address = aData.x;
     int clip_address = aData.y;
     int segment_index = aData.z & 0xffff;
     int edge_flags = (aData.z >> 16) & 0xff;
     int brush_flags = (aData.z >> 24) & 0xff;
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -46,17 +46,17 @@ void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize prim_rect,
     RectWithSize segment_rect,
     ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
-    vec4 texel_rect
+    vec4 segment_data
 ) {
     ImageBrushData image_data = fetch_image_data(prim_address);
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 texture_size = vec2(1, 1);
 #else
@@ -71,29 +71,28 @@ void brush_vs(
     vec2 stretch_size = image_data.stretch_size;
 
     // If this segment should interpolate relative to the
     // segment, modify the parameters for that.
     if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
         local_rect = segment_rect;
         stretch_size = local_rect.size;
 
-        // Note: Here we can assume that texels in device
-        //       space map to local space, due to how border-image
-        //       works. That assumption may not hold if this
-        //       is used for other purposes in the future.
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
-            stretch_size.x = (texel_rect.z - texel_rect.x) / pic_task.common_data.device_pixel_scale;
+            stretch_size.x = (segment_data.z - segment_data.x);
         }
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
-            stretch_size.y = (texel_rect.w - texel_rect.y) / pic_task.common_data.device_pixel_scale;
+            stretch_size.y = (segment_data.w - segment_data.y);
         }
 
-        uv0 = res.uv_rect.p0 + texel_rect.xy;
-        uv1 = res.uv_rect.p0 + texel_rect.zw;
+        // If the extra data is a texel rect, modify the UVs.
+        if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
+            uv0 = res.uv_rect.p0 + segment_data.xy;
+            uv1 = res.uv_rect.p0 + segment_data.zw;
+        }
     }
 
     vUv.z = res.layer;
 
     // Handle case where the UV coords are inverted (e.g. from an
     // external image).
     vec2 min_uv = min(uv0, uv1);
     vec2 max_uv = max(uv0, uv1);
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -5,34 +5,31 @@
 #include shared,prim_shared
 
 varying vec3 vUv;
 flat varying vec4 vUvSampleBounds;
 
 #ifdef WR_VERTEX_SHADER
 struct SplitGeometry {
     vec2 local[4];
-    RectWithSize local_rect;
 };
 
 SplitGeometry fetch_split_geometry(int address) {
     ivec2 uv = get_gpu_cache_uv(address);
 
     vec4 data0 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0));
     vec4 data1 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0));
-    vec4 data2 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0));
 
     SplitGeometry geo;
     geo.local = vec2[4](
         data0.xy,
         data0.zw,
         data1.xy,
         data1.zw
     );
-    geo.local_rect = RectWithSize(data2.xy, data2.zw);
 
     return geo;
 }
 
 vec2 bilerp(vec2 a, vec2 b, vec2 c, vec2 d, float s, float t) {
     vec2 x = mix(a, b, t);
     vec2 y = mix(c, d, t);
     return mix(x, y, s);
@@ -93,17 +90,17 @@ void main(void) {
     vec2 min_uv = min(uv0, uv1);
     vec2 max_uv = max(uv0, uv1);
 
     vUvSampleBounds = vec4(
         min_uv + vec2(0.5),
         max_uv - vec2(0.5)
     ) / texture_size.xyxy;
 
-    vec2 f = (local_pos - geometry.local_rect.p0) / geometry.local_rect.size;
+    vec2 f = (local_pos - ph.local_rect.p0) / ph.local_rect.size;
 
     f = bilerp(
         extra_data.st_tl, extra_data.st_tr,
         extra_data.st_bl, extra_data.st_br,
         f.y, f.x
     );
     vec2 uv = mix(uv0, uv1, f);
 
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -15,22 +15,23 @@ use gpu_types::{ClipMaskInstance, SplitC
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex};
 use prim_store::{VisibleGradientTile, PrimitiveInstance};
-use prim_store::{BorderSource, Primitive, PrimitiveDetails};
+use prim_store::{BrushSegment, BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
+use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
@@ -414,16 +415,25 @@ impl AlphaBatchContainer {
                     self.alpha_batches.push(other_batch);
                     min_batch_index = self.alpha_batches.len();
                 }
             }
         }
     }
 }
 
+/// Each segment can optionally specify a per-segment
+/// texture set and one user data field.
+#[derive(Debug, Copy, Clone)]
+struct SegmentInstanceData {
+    textures: BatchTextures,
+    user_data: i32,
+    is_opaque_override: Option<bool>,
+}
+
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatchBuilder {
     pub batch_list: BatchList,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
     target_rect: DeviceIntRect,
     can_merge: bool,
 }
 
@@ -546,17 +556,16 @@ impl AlphaBatchBuilder {
                 transform.transform_point3d(&poly.points[0].cast()).unwrap(),
                 transform.transform_point3d(&poly.points[1].cast()).unwrap(),
                 transform.transform_point3d(&poly.points[2].cast()).unwrap(),
                 transform.transform_point3d(&poly.points[3].cast()).unwrap(),
             ];
             let gpu_blocks = [
                 [local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
                 [local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
-                pic_metadata.local_rect.into(),
             ];
 
             let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
@@ -1098,47 +1107,39 @@ impl AlphaBatchBuilder {
                             clip_task_address,
                             gpu_cache,
                             &mut self.batch_list,
                             &prim_header,
                             prim_headers,
                         );
                     }
                     _ => {
-                        // TODO(gw): As an interim step, just return one value for the
-                        //           per-segment user data. In the future, this method
-                        //           will be expanded to optionally return a list of
-                        //           (BatchTextures, user_data) per segment, which will
-                        //           allow a different texture / render task to be used
-                        //           per segment.
-                        if let Some((batch_kind, textures, user_data, segment_user_data)) = brush.get_batch_params(
-                                ctx.resource_cache,
-                                gpu_cache,
-                                deferred_resolves,
-                                ctx.prim_store.chase_id == Some(prim_instance.prim_index),
+                        if let Some(params) = brush.get_batch_params(
+                            ctx.resource_cache,
+                            gpu_cache,
+                            deferred_resolves,
+                            ctx.prim_store.chase_id == Some(prim_instance.prim_index),
                         ) {
-                            let prim_header_index = prim_headers.push(&prim_header, user_data);
+                            let prim_header_index = prim_headers.push(&prim_header, params.prim_user_data);
                             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
                                 println!("\t{:?} {:?}, task relative bounds {:?}",
-                                    batch_kind, prim_header_index, bounding_rect);
+                                    params.batch_kind, prim_header_index, bounding_rect);
                             }
 
                             self.add_brush_to_batch(
                                 brush,
+                                &params,
                                 prim_instance,
-                                batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
-                                textures,
                                 prim_header_index,
                                 clip_task_address,
                                 bounding_rect,
                                 transform_kind,
                                 render_tasks,
-                                segment_user_data,
                             );
                         }
                     }
                 }
             }
             PrimitiveDetails::TextRun(ref text_cpu) => {
                 let subpx_dir = text_cpu.used_font.get_subpx_dir();
 
@@ -1265,90 +1266,157 @@ impl AlphaBatchBuilder {
         self.batch_list.push_single_instance(
             batch_key,
             bounding_rect,
             prim_instance.prim_index,
             PrimitiveInstanceData::from(base_instance),
         );
     }
 
+    /// Add a single segment instance to a batch.
+    fn add_segment_to_batch(
+        &mut self,
+        segment: &BrushSegment,
+        segment_data: &SegmentInstanceData,
+        segment_index: i32,
+        batch_kind: BrushBatchKind,
+        prim_instance: &PrimitiveInstance,
+        prim_header_index: PrimitiveHeaderIndex,
+        alpha_blend_mode: BlendMode,
+        bounding_rect: &WorldRect,
+        transform_kind: TransformedRectKind,
+        render_tasks: &RenderTaskTree,
+    ) {
+        let clip_task_address = match segment.clip_task_id {
+            BrushSegmentTaskId::RenderTaskId(id) =>
+                render_tasks.get_task_address(id),
+            BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
+            BrushSegmentTaskId::Empty => return,
+        };
+
+        // If the segment instance data specifies opacity for that
+        // segment, use it. Otherwise, assume opacity for the segment
+        // from the overall primitive opacity.
+        let is_segment_opaque = match segment_data.is_opaque_override {
+            Some(is_opaque) => is_opaque,
+            None => prim_instance.opacity.is_opaque,
+        };
+
+        let is_inner = segment.edge_flags.is_empty();
+        let needs_blending = !is_segment_opaque ||
+                             segment.clip_task_id.needs_blending() ||
+                             (!is_inner && transform_kind == TransformedRectKind::Complex);
+
+        let instance = PrimitiveInstanceData::from(BrushInstance {
+            segment_index,
+            edge_flags: segment.edge_flags,
+            clip_task_address,
+            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags,
+            prim_header_index,
+            user_data: segment_data.user_data,
+        });
+
+        let batch_key = BatchKey {
+            blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
+            kind: BatchKind::Brush(batch_kind),
+            textures: segment_data.textures,
+        };
+
+        self.batch_list.push_single_instance(
+            batch_key,
+            bounding_rect,
+            prim_instance.prim_index,
+            instance,
+        );
+    }
+
+    /// Add any segment(s) from a brush to batches.
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
+        params: &BrushBatchParameters,
         prim_instance: &PrimitiveInstance,
-        batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
-        textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskTree,
-        user_data: i32,
     ) {
-        let base_instance = BrushInstance {
-            prim_header_index,
-            clip_task_address,
-            segment_index: 0,
-            edge_flags: EdgeAaSegmentMask::all(),
-            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-            user_data,
-        };
-
-        match brush.segment_desc {
-            Some(ref segment_desc) => {
-                for (i, segment) in segment_desc.segments.iter().enumerate() {
-                    let is_inner = segment.edge_flags.is_empty();
-                    let needs_blending = !prim_instance.opacity.is_opaque ||
-                                         segment.clip_task_id.needs_blending() ||
-                                         (!is_inner && transform_kind == TransformedRectKind::Complex);
-
-                    let clip_task_address = match segment.clip_task_id {
-                        BrushSegmentTaskId::RenderTaskId(id) =>
-                            render_tasks.get_task_address(id),
-                        BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
-                        BrushSegmentTaskId::Empty => continue,
-                    };
-
-                    let instance = PrimitiveInstanceData::from(BrushInstance {
-                        segment_index: i as i32,
-                        edge_flags: segment.edge_flags,
-                        clip_task_address,
-                        brush_flags: base_instance.brush_flags | segment.brush_flags,
-                        ..base_instance
-                    });
-
-                    let batch_key = BatchKey {
-                        blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
-                        kind: BatchKind::Brush(batch_kind),
-                        textures,
-                    };
-
-                    self.batch_list.push_single_instance(
-                        batch_key,
+        match (&brush.segment_desc, &params.segment_data) {
+            (Some(ref segment_desc), SegmentDataKind::Instanced(ref segment_data)) => {
+                // In this case, we have both a list of segments, and a list of
+                // per-segment instance data. Zip them together to build batches.
+                debug_assert_eq!(segment_desc.segments.len(), segment_data.len());
+                for (segment_index, (segment, segment_data)) in segment_desc.segments
+                    .iter()
+                    .zip(segment_data.iter())
+                    .enumerate() {
+                    self.add_segment_to_batch(
+                        segment,
+                        segment_data,
+                        segment_index as i32,
+                        params.batch_kind,
+                        prim_instance,
+                        prim_header_index,
+                        alpha_blend_mode,
                         bounding_rect,
-                        prim_instance.prim_index,
-                        instance,
+                        transform_kind,
+                        render_tasks,
                     );
                 }
             }
-            None => {
+            (Some(ref segment_desc), SegmentDataKind::Shared(ref segment_data)) => {
+                // A list of segments, but the per-segment data is common
+                // between all segments.
+                for (segment_index, segment) in segment_desc.segments
+                    .iter()
+                    .enumerate() {
+                    self.add_segment_to_batch(
+                        segment,
+                        segment_data,
+                        segment_index as i32,
+                        params.batch_kind,
+                        prim_instance,
+                        prim_header_index,
+                        alpha_blend_mode,
+                        bounding_rect,
+                        transform_kind,
+                        render_tasks,
+                    );
+                }
+            }
+            (None, SegmentDataKind::Shared(ref segment_data)) => {
+                // No segments, and thus no per-segment instance data.
                 let batch_key = BatchKey {
                     blend_mode: non_segmented_blend_mode,
-                    kind: BatchKind::Brush(batch_kind),
-                    textures,
+                    kind: BatchKind::Brush(params.batch_kind),
+                    textures: segment_data.textures,
                 };
+                let instance = PrimitiveInstanceData::from(BrushInstance {
+                    segment_index: 0,
+                    edge_flags: EdgeAaSegmentMask::all(),
+                    clip_task_address,
+                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+                    prim_header_index,
+                    user_data: segment_data.user_data,
+                });
                 self.batch_list.push_single_instance(
                     batch_key,
                     bounding_rect,
                     prim_instance.prim_index,
-                    PrimitiveInstanceData::from(base_instance),
+                    PrimitiveInstanceData::from(instance),
                 );
             }
+            (None, SegmentDataKind::Instanced(..)) => {
+                // We should never hit the case where there are no segments,
+                // but a list of segment instance data.
+                unreachable!();
+            }
         }
     }
 }
 
 fn add_gradient_tiles(
     prim_instance: &PrimitiveInstance,
     visible_tiles: &[VisibleGradientTile],
     stops_handle: &GpuCacheHandle,
@@ -1421,24 +1489,76 @@ fn get_image_tile_params(
                 RasterizationSpace::Local as i32,
                 0,
             ],
             gpu_cache.get_address(&cache_item.uv_rect_handle),
         ))
     }
 }
 
+/// Either a single texture / user data for all segments,
+/// or a list of one per segment.
+enum SegmentDataKind {
+    Shared(SegmentInstanceData),
+    Instanced(SmallVec<[SegmentInstanceData; 8]>),
+}
+
+/// The parameters that are specific to a kind of brush,
+/// used by the common method to add a brush to batches.
+struct BrushBatchParameters {
+    batch_kind: BrushBatchKind,
+    prim_user_data: [i32; 3],
+    segment_data: SegmentDataKind,
+}
+
+impl BrushBatchParameters {
+    /// This brush instance has a list of per-segment
+    /// instance data.
+    fn instanced(
+        batch_kind: BrushBatchKind,
+        prim_user_data: [i32; 3],
+        segment_data: SmallVec<[SegmentInstanceData; 8]>,
+    ) -> Self {
+        BrushBatchParameters {
+            batch_kind,
+            prim_user_data,
+            segment_data: SegmentDataKind::Instanced(segment_data),
+        }
+    }
+
+    /// This brush instance shares the per-segment data
+    /// across all segments.
+    fn shared(
+        batch_kind: BrushBatchKind,
+        textures: BatchTextures,
+        prim_user_data: [i32; 3],
+        segment_user_data: i32,
+    ) -> Self {
+        BrushBatchParameters {
+            batch_kind,
+            prim_user_data,
+            segment_data: SegmentDataKind::Shared(
+                SegmentInstanceData {
+                    textures,
+                    user_data: segment_user_data,
+                    is_opaque_override: None,
+                }
+            ),
+        }
+    }
+}
+
 impl BrushPrimitive {
     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], i32)> {
+    ) -> Option<BrushBatchParameters> {
         match self.kind {
             BrushKind::Image { request, ref source, .. } => {
                 let cache_item = match *source {
                     ImageSource::Default => {
                         resolve_image(
                             request,
                             resource_cache,
                             gpu_cache,
@@ -1458,129 +1578,150 @@ impl BrushPrimitive {
                     println!("\tsource {:?}", cache_item);
                 }
 
                 if cache_item.texture_id == TextureSource::Invalid {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
-                    Some((
+                    Some(BrushBatchParameters::shared(
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                         textures,
                         [
                             ShaderColorMode::Image as i32,
                             RasterizationSpace::Local as i32,
                             0,
                         ],
                         cache_item.uv_rect_handle.as_int(gpu_cache),
                     ))
                 }
             }
             BrushKind::LineDecoration { ref handle, style, .. } => {
                 match style {
                     LineStyle::Solid => {
-                        Some((
+                        Some(BrushBatchParameters::shared(
                             BrushBatchKind::Solid,
                             BatchTextures::no_texture(),
                             [0; 3],
                             0,
                         ))
                     }
                     LineStyle::Dotted |
                     LineStyle::Dashed |
                     LineStyle::Wavy => {
                         let rt_cache_entry = resource_cache
                             .get_cached_render_task(handle.as_ref().unwrap());
                         let cache_item = resource_cache.get_texture_cache_item(&rt_cache_entry.handle);
                         let textures = BatchTextures::color(cache_item.texture_id);
-                        Some((
+                        Some(BrushBatchParameters::shared(
                             BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                             textures,
                             [
                                 ShaderColorMode::Image as i32,
                                 RasterizationSpace::Local as i32,
                                 0,
                             ],
                             cache_item.uv_rect_handle.as_int(gpu_cache),
                         ))
                     }
                 }
             }
             BrushKind::Border { ref source, .. } => {
-                let cache_item = match *source {
+                match *source {
                     BorderSource::Image(request) => {
-                        resolve_image(
+                        let cache_item = resolve_image(
                             request,
                             resource_cache,
                             gpu_cache,
                             deferred_resolves,
-                        )
+                        );
+
+                        if cache_item.texture_id == TextureSource::Invalid {
+                            return None;
+                        }
+
+                        let textures = BatchTextures::color(cache_item.texture_id);
+
+                        Some(BrushBatchParameters::shared(
+                            BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
+                            textures,
+                            [
+                                ShaderColorMode::Image as i32,
+                                RasterizationSpace::Local as i32,
+                                0,
+                            ],
+                            cache_item.uv_rect_handle.as_int(gpu_cache),
+                        ))
                     }
-                    BorderSource::Border { ref handle, .. } => {
-                        let rt_handle = match *handle {
-                            Some(ref handle) => handle,
-                            None => return None,
-                        };
-                        let rt_cache_entry = resource_cache
-                            .get_cached_render_task(rt_handle);
-                        resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
-                    }
-                };
+                    BorderSource::Border { ref segments, .. } => {
+                        let mut segment_data = SmallVec::new();
+
+                        // Collect the segment instance data from each render
+                        // task for each valid edge / corner of the border.
 
-                if cache_item.texture_id == TextureSource::Invalid {
-                    None
-                } else {
-                    let textures = BatchTextures::color(cache_item.texture_id);
+                        for segment in segments {
+                            let rt_cache_entry = resource_cache
+                                .get_cached_render_task(segment.handle.as_ref().unwrap());
+                            let cache_item = resource_cache
+                                .get_texture_cache_item(&rt_cache_entry.handle);
+                            segment_data.push(
+                                SegmentInstanceData {
+                                    textures: BatchTextures::color(cache_item.texture_id),
+                                    user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
+                                    is_opaque_override: Some(segment.is_opaque),
+                                }
+                            );
+                        }
 
-                    Some((
-                        BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
-                        textures,
-                        [
-                            ShaderColorMode::Image as i32,
-                            RasterizationSpace::Local as i32,
-                            0,
-                        ],
-                        cache_item.uv_rect_handle.as_int(gpu_cache),
-                    ))
+                        Some(BrushBatchParameters::instanced(
+                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
+                            [
+                                ShaderColorMode::Image as i32,
+                                RasterizationSpace::Local as i32,
+                                0,
+                            ],
+                            segment_data,
+                        ))
+                    }
                 }
             }
             BrushKind::Picture { .. } => {
                 panic!("bug: get_batch_key is handled at higher level for pictures");
             }
             BrushKind::Solid { .. } => {
-                Some((
+                Some(BrushBatchParameters::shared(
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
                     0,
                 ))
             }
             BrushKind::Clear => {
-                Some((
+                Some(BrushBatchParameters::shared(
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
                     0,
                 ))
             }
             BrushKind::RadialGradient { ref stops_handle, .. } => {
-                Some((
+                Some(BrushBatchParameters::shared(
                     BrushBatchKind::RadialGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
                     0,
                 ))
             }
             BrushKind::LinearGradient { ref stops_handle, .. } => {
-                Some((
+                Some(BrushBatchParameters::shared(
                     BrushBatchKind::LinearGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
                     0,
@@ -1626,17 +1767,17 @@ impl BrushPrimitive {
 
                 let kind = BrushBatchKind::YuvImage(
                     buffer_kind,
                     format,
                     color_depth,
                     color_space,
                 );
 
-                Some((
+                Some(BrushBatchParameters::shared(
                     kind,
                     textures,
                     [
                         uv_rect_addresses[0],
                         uv_rect_addresses[1],
                         uv_rect_addresses[2],
                     ],
                     0,
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,23 +1,23 @@
 /* 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, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize};
 use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
-use api::{DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
+use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, NormalBorder, DeviceIntSize};
 use api::{AuHelpers};
-use app_units::Au;
 use ellipse::Ellipse;
-use euclid::SideOffsets2D;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegment, BrushSegmentVec};
-use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
+use prim_store::{BorderSegmentInfo, BrushKind, BrushPrimitive, BrushSegment, BrushSegmentVec};
+use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain, BrushSegmentDescriptor};
+use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind};
+use smallvec::SmallVec;
 use util::{lerp, RectHelpers};
 
 // Using 2048 as the maximum radius in device space before which we
 // start stretching is up for debate.
 // the value must be chosen so that the corners will not use an
 // unreasonable amount of memory but should allow crisp corners in the
 // common cases.
 
@@ -88,48 +88,40 @@ impl From<BorderSide> for BorderSideAu {
     fn from(side: BorderSide) -> Self {
         BorderSideAu {
             color: side.color.into(),
             style: side.style,
         }
     }
 }
 
+/// Cache key that uniquely identifies a border
+/// edge in the render task cache.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct BorderCacheKey {
-    pub left: BorderSideAu,
-    pub right: BorderSideAu,
-    pub top: BorderSideAu,
-    pub bottom: BorderSideAu,
-    pub radius: BorderRadiusAu,
-    pub widths: SideOffsets2D<Au>,
+pub struct BorderEdgeCacheKey {
+    pub side: BorderSideAu,
+    pub size: LayoutSizeAu,
     pub do_aa: bool,
-    pub scale: Au,
+    pub segment: BorderSegment,
 }
 
-impl BorderCacheKey {
-    pub fn new(border: &NormalBorder, widths: &LayoutSideOffsets) -> Self {
-        BorderCacheKey {
-            left: border.left.into(),
-            top: border.top.into(),
-            right: border.right.into(),
-            bottom: border.bottom.into(),
-            widths: SideOffsets2D::new(
-                Au::from_f32_px(widths.top),
-                Au::from_f32_px(widths.right),
-                Au::from_f32_px(widths.bottom),
-                Au::from_f32_px(widths.left),
-            ),
-            radius: border.radius.into(),
-            do_aa: border.do_aa,
-            scale: Au(0),
-        }
-    }
+/// Cache key that uniquely identifies a border
+/// corner in the render task cache.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderCornerCacheKey {
+    pub widths: LayoutSizeAu,
+    pub radius: LayoutSizeAu,
+    pub side0: BorderSideAu,
+    pub side1: BorderSideAu,
+    pub segment: BorderSegment,
+    pub do_aa: bool,
 }
 
 pub fn ensure_no_corner_overlap(
     radius: &mut BorderRadius,
     rect: &LayoutRect,
 ) {
     let mut ratio = 1.0;
     let top_left_radius = &mut radius.top_left;
@@ -172,38 +164,40 @@ pub fn ensure_no_corner_overlap(
     }
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn add_normal_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         border: &NormalBorder,
-        widths: &LayoutSideOffsets,
+        widths: LayoutSideOffsets,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
         let mut border = *border;
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
-        let prim = BrushPrimitive::new(
-            BrushKind::new_border(border, *widths),
-            None,
+        let prim = create_normal_border_prim(
+            &info.rect,
+            border,
+            widths,
         );
 
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self, is_inner_border: bool) -> ColorF;
+    fn is_opaque(&self) -> bool;
 }
 
 impl BorderSideHelpers for BorderSide {
     fn border_color(&self, is_inner_border: bool) -> ColorF {
         let lighter = match self.style {
             BorderStyle::Inset => is_inner_border,
             BorderStyle::Outset => !is_inner_border,
             _ => return self.color,
@@ -222,16 +216,21 @@ impl BorderSideHelpers for BorderSide {
         if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
             let scale = if lighter { 1.0 } else { 2.0 / 3.0 };
             return self.color.scale_rgb(scale)
         }
 
         let black = if lighter { 0.7 } else { 0.3 };
         ColorF::new(black, black, black, self.color.a)
     }
+
+    /// Returns true if all pixels in this border style are opaque.
+    fn is_opaque(&self) -> bool {
+        self.color.a >= 1.0 && self.style.is_opaque()
+    }
 }
 
 /// The kind of border corner clip.
 #[repr(C)]
 #[derive(Copy, Debug, Clone, PartialEq)]
 pub enum BorderClipKind {
     DashCorner = 1,
     DashEdge = 2,
@@ -512,109 +511,37 @@ struct DotInfo {
 }
 
 impl DotInfo {
     fn new(arc_pos: f32, diameter: f32) -> DotInfo {
         DotInfo { arc_pos, diameter }
     }
 }
 
-#[derive(Debug)]
-pub struct BorderSegmentInfo {
-    task_rect: DeviceRect,
-    segment: BorderSegment,
-    radius: DeviceSize,
-    widths: DeviceSize,
-}
-
-bitflags! {
-    /// Whether we depend on the available size for the border (effectively in
-    /// the local rect of the primitive), and in which direction.
-    ///
-    /// Note that this relies on the corners being only dependent on the border
-    /// widths and radius.
-    ///
-    /// This is not just a single boolean to allow instance caching for border
-    /// boxes where one of the directions differ but not the one on the affected
-    /// border is.
-    ///
-    /// This allows sharing instances for stuff like paragraphs of different
-    /// heights separated by horizontal borders.
-    pub struct AvailableSizeDependence : u8 {
-        /// There's a dependence on the vertical direction, that is, at least
-        /// one of the right or left edges is dashed or dotted.
-        const VERTICAL = 1 << 0;
-        /// Same but for the horizontal direction.
-        const HORIZONTAL = 1 << 1;
-    }
-}
-
-/// This is the data that describes a single border with (up to) four sides and
-/// four corners.
-///
-/// This object gets created for each border primitive at least once. Note,
-/// however, that the instances this produces via `build_instances()` can and
-/// will be shared by multiple borders, as long as they share the same cache
-/// key.
-///
-/// Segments, however, also get build once per primitive.
-///
-/// So the important invariant to preserve when going through this code is that
-/// the result of `build_instances()` would remain invariant for a given cache
-/// key.
-///
-/// That means, then, that `border_segments` can't depend at all on something
-/// that isn't on the key like the available_size, while the brush segments can
-/// (and will, since we skip painting empty segments caused by things like edges
-/// getting constrained by huge border-radius).
-///
-/// Note that the cache key is not only `BorderCacheKey`, but also a
-/// `size` from `RenderTaskCacheKey`, which will always be zero unless
-/// `available_size_dependence` is non-empty, which is effectively just dashed
-/// and dotted borders for now, since the spacing between the dash and dots
-/// changes depending on that size.
-#[derive(Debug)]
-pub struct BorderRenderTaskInfo {
-    pub border_segments: Vec<BorderSegmentInfo>,
-    pub size: DeviceIntSize,
-    pub available_size_dependence: AvailableSizeDependence,
-    do_aa: bool,
-}
-
-#[derive(PartialEq, Eq)]
-enum DependsOnAvailableSize {
-    No,
-    Yes,
-}
-
 /// Information needed to place and draw a border edge.
 #[derive(Debug)]
 struct EdgeInfo {
     /// Offset in local space to place the edge from origin.
     local_offset: f32,
     /// Size of the edge in local space.
     local_size: f32,
-    /// Size in device pixels needed in the render task.
-    device_size: f32,
-    /// Whether this edge depends on the available size.
-    depends_on_available_size: bool,
+    /// Local stretch size for this edge (repeat past this).
+    stretch_size: f32,
 }
 
 impl EdgeInfo {
     fn new(
         local_offset: f32,
         local_size: f32,
-        device_size: f32,
-        depends_on_avail_size: DependsOnAvailableSize,
+        stretch_size: f32,
     ) -> Self {
         Self {
             local_offset,
             local_size,
-            device_size,
-            depends_on_available_size: depends_on_avail_size == DependsOnAvailableSize::Yes,
+            stretch_size,
         }
     }
 }
 
 // Given a side width and the available space, compute the half-dash (half of
 // the 'on' segment) and the count of them for a given segment.
 fn compute_half_dash(side_width: f32, total_size: f32) -> (f32, u32) {
     let half_dash = side_width * 1.5;
@@ -640,458 +567,254 @@ fn compute_half_dash(side_width: f32, to
 
 // Get the needed size in device pixels for an edge,
 // based on the border style of that edge. This is used
 // to determine how big the render task should be.
 fn get_edge_info(
     style: BorderStyle,
     side_width: f32,
     avail_size: f32,
-    scale: f32,
 ) -> EdgeInfo {
     // To avoid division by zero below.
-    if side_width <= 0.0 {
-        return EdgeInfo::new(0.0, 0.0, 0.0, DependsOnAvailableSize::No);
+    if side_width <= 0.0 || avail_size <= 0.0 {
+        return EdgeInfo::new(0.0, 0.0, 0.0);
     }
 
     match style {
         BorderStyle::Dashed => {
             // Basically, two times the dash size.
             let (half_dash, _num_half_dashes) =
                 compute_half_dash(side_width, avail_size);
-            let device_size = (2.0 * 2.0 * half_dash * scale).round();
-            EdgeInfo::new(0., avail_size, device_size, DependsOnAvailableSize::Yes)
+            let stretch_size = 2.0 * 2.0 * half_dash;
+            EdgeInfo::new(0., avail_size, stretch_size)
         }
         BorderStyle::Dotted => {
             let dot_and_space_size = 2.0 * side_width;
             if avail_size < dot_and_space_size * 0.75 {
-                return EdgeInfo::new(0.0, 0.0, 0.0, DependsOnAvailableSize::Yes);
+                return EdgeInfo::new(0.0, 0.0, 0.0);
             }
             let approx_dot_count = avail_size / dot_and_space_size;
             let dot_count = approx_dot_count.floor().max(1.0);
             let used_size = dot_count * dot_and_space_size;
             let extra_space = avail_size - used_size;
-            let device_size = dot_and_space_size * scale;
+            let stretch_size = dot_and_space_size;
             let offset = (extra_space * 0.5).round();
-            EdgeInfo::new(offset, used_size, device_size, DependsOnAvailableSize::Yes)
+            EdgeInfo::new(offset, used_size, stretch_size)
         }
         _ => {
-            EdgeInfo::new(0.0, avail_size, 8.0, DependsOnAvailableSize::No)
+            EdgeInfo::new(0.0, avail_size, 8.0)
         }
     }
 }
 
-impl BorderRenderTaskInfo {
-    pub fn new(
-        rect: &LayoutRect,
-        border: &NormalBorder,
-        widths: &LayoutSideOffsets,
-        scale: LayoutToDeviceScale,
-        brush_segments: &mut BrushSegmentVec,
-    ) -> Option<Self> {
-        let mut border_segments = Vec::new();
-
-        let dp_width_top = (widths.top * scale.0).ceil();
-        let dp_width_bottom = (widths.bottom * scale.0).ceil();
-        let dp_width_left = (widths.left * scale.0).ceil();
-        let dp_width_right = (widths.right * scale.0).ceil();
-
-        let dp_corner_tl = (border.radius.top_left * scale).ceil();
-        let dp_corner_tr = (border.radius.top_right * scale).ceil();
-        let dp_corner_bl = (border.radius.bottom_left * scale).ceil();
-        let dp_corner_br = (border.radius.bottom_right * scale).ceil();
-
-        let dp_size_tl = DeviceSize::new(
-            dp_corner_tl.width.max(dp_width_left),
-            dp_corner_tl.height.max(dp_width_top),
-        );
-        let dp_size_tr = DeviceSize::new(
-            dp_corner_tr.width.max(dp_width_right),
-            dp_corner_tr.height.max(dp_width_top),
-        );
-        let dp_size_br = DeviceSize::new(
-            dp_corner_br.width.max(dp_width_right),
-            dp_corner_br.height.max(dp_width_bottom),
-        );
-        let dp_size_bl = DeviceSize::new(
-            dp_corner_bl.width.max(dp_width_left),
-            dp_corner_bl.height.max(dp_width_bottom),
-        );
-
-        let local_size_tl = LayoutSize::new(
-            border.radius.top_left.width.max(widths.left),
-            border.radius.top_left.height.max(widths.top),
-        );
-        let local_size_tr = LayoutSize::new(
-            border.radius.top_right.width.max(widths.right),
-            border.radius.top_right.height.max(widths.top),
-        );
-        let local_size_br = LayoutSize::new(
-            border.radius.bottom_right.width.max(widths.right),
-            border.radius.bottom_right.height.max(widths.bottom),
-        );
-        let local_size_bl = LayoutSize::new(
-            border.radius.bottom_left.width.max(widths.left),
-            border.radius.bottom_left.height.max(widths.bottom),
-        );
-
-        let top_edge_info = get_edge_info(
-            border.top.style,
-            widths.top,
-            rect.size.width - local_size_tl.width - local_size_tr.width,
-            scale.0,
-        );
-        let bottom_edge_info = get_edge_info(
-            border.bottom.style,
-            widths.bottom,
-            rect.size.width - local_size_bl.width - local_size_br.width,
-            scale.0,
-        );
-        let inner_width = top_edge_info.device_size.max(bottom_edge_info.device_size).ceil();
-
-        let left_edge_info = get_edge_info(
-            border.left.style,
-            widths.left,
-            rect.size.height - local_size_tl.height - local_size_bl.height,
-            scale.0,
-        );
-        let right_edge_info = get_edge_info(
-            border.right.style,
-            widths.right,
-            rect.size.height - local_size_tr.height - local_size_br.height,
-            scale.0,
-        );
-
-        let inner_height = left_edge_info.device_size.max(right_edge_info.device_size).ceil();
-
-        let size = DeviceSize::new(
-            dp_size_tl.width.max(dp_size_bl.width) + inner_width + dp_size_tr.width.max(dp_size_br.width),
-            dp_size_tl.height.max(dp_size_tr.height) + inner_height + dp_size_bl.height.max(dp_size_br.height),
-        );
-
-        if size.width == 0.0 || size.height == 0.0 {
-            return None;
-        }
+/// Create the set of border segments and render task
+/// cache keys for a given CSS border.
+fn create_border_segments(
+    rect: &LayoutRect,
+    border: &NormalBorder,
+    widths: &LayoutSideOffsets,
+    border_segments: &mut SmallVec<[BorderSegmentInfo; 8]>,
+    brush_segments: &mut BrushSegmentVec,
+) {
+    let local_size_tl = LayoutSize::new(
+        border.radius.top_left.width.max(widths.left),
+        border.radius.top_left.height.max(widths.top),
+    );
+    let local_size_tr = LayoutSize::new(
+        border.radius.top_right.width.max(widths.right),
+        border.radius.top_right.height.max(widths.top),
+    );
+    let local_size_br = LayoutSize::new(
+        border.radius.bottom_right.width.max(widths.right),
+        border.radius.bottom_right.height.max(widths.bottom),
+    );
+    let local_size_bl = LayoutSize::new(
+        border.radius.bottom_left.width.max(widths.left),
+        border.radius.bottom_left.height.max(widths.bottom),
+    );
 
-        let mut size_dependence = AvailableSizeDependence::empty();
-        if top_edge_info.depends_on_available_size ||
-            bottom_edge_info.depends_on_available_size
-        {
-            size_dependence.insert(AvailableSizeDependence::HORIZONTAL);
-        }
-
-        if left_edge_info.depends_on_available_size ||
-            right_edge_info.depends_on_available_size
-        {
-            size_dependence.insert(AvailableSizeDependence::VERTICAL);
-        }
-
-        add_edge_segment(
-            LayoutRect::from_floats(
-                rect.origin.x,
-                rect.origin.y + local_size_tl.height + left_edge_info.local_offset,
-                rect.origin.x + widths.left,
-                rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
-            ),
-            DeviceRect::from_floats(
-                0.0,
-                dp_size_tl.height,
-                dp_width_left,
-                dp_size_tl.height + left_edge_info.device_size,
-            ),
-            &border.left,
-            BorderSegment::Left,
-            EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
-            &mut border_segments,
-            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
-            brush_segments,
-        );
+    let top_edge_info = get_edge_info(
+        border.top.style,
+        widths.top,
+        rect.size.width - local_size_tl.width - local_size_tr.width,
+    );
+    let bottom_edge_info = get_edge_info(
+        border.bottom.style,
+        widths.bottom,
+        rect.size.width - local_size_bl.width - local_size_br.width,
+    );
 
-        add_edge_segment(
-            LayoutRect::from_floats(
-                rect.origin.x + local_size_tl.width + top_edge_info.local_offset,
-                rect.origin.y,
-                rect.origin.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
-                rect.origin.y + widths.top,
-            ),
-            DeviceRect::from_floats(
-                dp_size_tl.width,
-                0.0,
-                dp_size_tl.width + top_edge_info.device_size,
-                dp_width_top,
-            ),
-            &border.top,
-            BorderSegment::Top,
-            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
-            &mut border_segments,
-            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
-            brush_segments,
-        );
-
-        add_edge_segment(
-            LayoutRect::from_floats(
-                rect.origin.x + rect.size.width - widths.right,
-                rect.origin.y + local_size_tr.height + right_edge_info.local_offset,
-                rect.origin.x + rect.size.width,
-                rect.origin.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
-            ),
-            DeviceRect::from_floats(
-                size.width - dp_width_right,
-                dp_size_tr.height,
-                size.width,
-                dp_size_tr.height + right_edge_info.device_size,
-            ),
-            &border.right,
-            BorderSegment::Right,
-            EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
-            &mut border_segments,
-            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
-            brush_segments,
-        );
-
-        add_edge_segment(
-            LayoutRect::from_floats(
-                rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset,
-                rect.origin.y + rect.size.height - widths.bottom,
-                rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
-                rect.origin.y + rect.size.height,
-            ),
-            DeviceRect::from_floats(
-                dp_size_bl.width,
-                size.height - dp_width_bottom,
-                dp_size_bl.width + bottom_edge_info.device_size,
-                size.height,
-            ),
-            &border.bottom,
-            BorderSegment::Bottom,
-            EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
-            &mut border_segments,
-            BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
-            brush_segments,
-        );
+    let left_edge_info = get_edge_info(
+        border.left.style,
+        widths.left,
+        rect.size.height - local_size_tl.height - local_size_bl.height,
+    );
+    let right_edge_info = get_edge_info(
+        border.right.style,
+        widths.right,
+        rect.size.height - local_size_tr.height - local_size_br.height,
+    );
 
-        add_corner_segment(
-            LayoutRect::from_floats(
-                rect.origin.x,
-                rect.origin.y,
-                rect.origin.x + local_size_tl.width,
-                rect.origin.y + local_size_tl.height,
-            ),
-            DeviceRect::from_floats(
-                0.0,
-                0.0,
-                dp_size_tl.width,
-                dp_size_tl.height,
-            ),
-            &border.left,
-            &border.top,
-            DeviceSize::new(dp_width_left, dp_width_top),
-            dp_corner_tl,
-            BorderSegment::TopLeft,
-            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
-            &mut border_segments,
-            brush_segments,
-        );
-
-        add_corner_segment(
-            LayoutRect::from_floats(
-                rect.origin.x + rect.size.width - local_size_tr.width,
-                rect.origin.y,
-                rect.origin.x + rect.size.width,
-                rect.origin.y + local_size_tr.height,
-            ),
-            DeviceRect::from_floats(
-                size.width - dp_size_tr.width,
-                0.0,
-                size.width,
-                dp_size_tr.height,
-            ),
-            &border.top,
-            &border.right,
-            DeviceSize::new(dp_width_right, dp_width_top),
-            dp_corner_tr,
-            BorderSegment::TopRight,
-            EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
-            &mut border_segments,
-            brush_segments,
-        );
-
-        add_corner_segment(
-            LayoutRect::from_floats(
-                rect.origin.x + rect.size.width - local_size_br.width,
-                rect.origin.y + rect.size.height - local_size_br.height,
-                rect.origin.x + rect.size.width,
-                rect.origin.y + rect.size.height,
-            ),
-            DeviceRect::from_floats(
-                size.width - dp_size_br.width,
-                size.height - dp_size_br.height,
-                size.width,
-                size.height,
-            ),
-            &border.right,
-            &border.bottom,
-            DeviceSize::new(dp_width_right, dp_width_bottom),
-            dp_corner_br,
-            BorderSegment::BottomRight,
-            EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
-            &mut border_segments,
-            brush_segments,
-        );
-
-        add_corner_segment(
-            LayoutRect::from_floats(
-                rect.origin.x,
-                rect.origin.y + rect.size.height - local_size_bl.height,
-                rect.origin.x + local_size_bl.width,
-                rect.origin.y + rect.size.height,
-            ),
-            DeviceRect::from_floats(
-                0.0,
-                size.height - dp_size_bl.height,
-                dp_size_bl.width,
-                size.height,
-            ),
-            &border.bottom,
-            &border.left,
-            DeviceSize::new(dp_width_left, dp_width_bottom),
-            dp_corner_bl,
-            BorderSegment::BottomLeft,
-            EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
-            &mut border_segments,
-            brush_segments,
-        );
+    add_edge_segment(
+        LayoutRect::from_floats(
+            rect.origin.x,
+            rect.origin.y + local_size_tl.height + left_edge_info.local_offset,
+            rect.origin.x + widths.left,
+            rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
+        ),
+        &left_edge_info,
+        border.left,
+        widths.left,
+        BorderSegment::Left,
+        EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
+    add_edge_segment(
+        LayoutRect::from_floats(
+            rect.origin.x + local_size_tl.width + top_edge_info.local_offset,
+            rect.origin.y,
+            rect.origin.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
+            rect.origin.y + widths.top,
+        ),
+        &top_edge_info,
+        border.top,
+        widths.top,
+        BorderSegment::Top,
+        EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
+    add_edge_segment(
+        LayoutRect::from_floats(
+            rect.origin.x + rect.size.width - widths.right,
+            rect.origin.y + local_size_tr.height + right_edge_info.local_offset,
+            rect.origin.x + rect.size.width,
+            rect.origin.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
+        ),
+        &right_edge_info,
+        border.right,
+        widths.right,
+        BorderSegment::Right,
+        EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
+    add_edge_segment(
+        LayoutRect::from_floats(
+            rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset,
+            rect.origin.y + rect.size.height - widths.bottom,
+            rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
+            rect.origin.y + rect.size.height,
+        ),
+        &bottom_edge_info,
+        border.bottom,
+        widths.bottom,
+        BorderSegment::Bottom,
+        EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
 
-        Some(BorderRenderTaskInfo {
-            border_segments,
-            size: size.to_i32(),
-            available_size_dependence: size_dependence,
-            do_aa: border.do_aa,
-        })
-    }
-
-    /// Returns the cache key size for this task, based on our available size
-    /// dependence computed earlier.
-    #[inline]
-    pub fn cache_key_size(
-        &self,
-        local_size: &LayoutSize,
-        scale: LayoutToDeviceScale,
-    ) -> DeviceIntSize {
-        let mut size = DeviceIntSize::zero();
-        if self.available_size_dependence.is_empty() {
-            return size;
-        }
-
-        let device_size = (*local_size * scale).to_i32();
-        if self.available_size_dependence.contains(AvailableSizeDependence::VERTICAL) {
-            size.height = device_size.height;
-        }
-        if self.available_size_dependence.contains(AvailableSizeDependence::HORIZONTAL) {
-            size.width = device_size.width;
-        }
-        size
-    }
-
-    pub fn build_instances(&self, border: &NormalBorder) -> Vec<BorderInstance> {
-        let mut instances = Vec::new();
-
-        for info in &self.border_segments {
-            let (side0, side1, flip0, flip1) = match info.segment {
-                BorderSegment::Left => (&border.left, &border.left, false, false),
-                BorderSegment::Top => (&border.top, &border.top, false, false),
-                BorderSegment::Right => (&border.right, &border.right, true, true),
-                BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
-                BorderSegment::TopLeft => (&border.left, &border.top, false, false),
-                BorderSegment::TopRight => (&border.top, &border.right, false, true),
-                BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
-                BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
-            };
-
-            let style0 = if side0.style.is_hidden() {
-                side1.style
-            } else {
-                side0.style
-            };
-            let style1 = if side1.style.is_hidden() {
-                side0.style
-            } else {
-                side1.style
-            };
-
-            let color0 = side0.border_color(flip0);
-            let color1 = side1.border_color(flip1);
-
-            add_segment(
-                info.task_rect,
-                style0,
-                style1,
-                color0,
-                color1,
-                info.segment,
-                &mut instances,
-                info.widths,
-                info.radius,
-                self.do_aa,
-            );
-        }
-
-        instances
-    }
-
-    /// 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,
-        widths: &LayoutSideOffsets
-    ) -> 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(widths.top)
-            .max(widths.bottom)
-            .max(widths.left)
-            .max(widths.right);
-
-        LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
-    }
+    add_corner_segment(
+        LayoutRect::from_floats(
+            rect.origin.x,
+            rect.origin.y,
+            rect.origin.x + local_size_tl.width,
+            rect.origin.y + local_size_tl.height,
+        ),
+        border.left,
+        border.top,
+        LayoutSize::new(widths.left, widths.top),
+        border.radius.top_left,
+        BorderSegment::TopLeft,
+        EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
+    add_corner_segment(
+        LayoutRect::from_floats(
+            rect.origin.x + rect.size.width - local_size_tr.width,
+            rect.origin.y,
+            rect.origin.x + rect.size.width,
+            rect.origin.y + local_size_tr.height,
+        ),
+        border.top,
+        border.right,
+        LayoutSize::new(widths.right, widths.top),
+        border.radius.top_right,
+        BorderSegment::TopRight,
+        EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
+    add_corner_segment(
+        LayoutRect::from_floats(
+            rect.origin.x + rect.size.width - local_size_br.width,
+            rect.origin.y + rect.size.height - local_size_br.height,
+            rect.origin.x + rect.size.width,
+            rect.origin.y + rect.size.height,
+        ),
+        border.right,
+        border.bottom,
+        LayoutSize::new(widths.right, widths.bottom),
+        border.radius.bottom_right,
+        BorderSegment::BottomRight,
+        EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
+    add_corner_segment(
+        LayoutRect::from_floats(
+            rect.origin.x,
+            rect.origin.y + rect.size.height - local_size_bl.height,
+            rect.origin.x + local_size_bl.width,
+            rect.origin.y + rect.size.height,
+        ),
+        border.bottom,
+        border.left,
+        LayoutSize::new(widths.left, widths.bottom),
+        border.radius.bottom_left,
+        BorderSegment::BottomLeft,
+        EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
+        brush_segments,
+        border_segments,
+        border.do_aa,
+    );
 }
 
-fn add_brush_segment(
-    image_rect: LayoutRect,
-    task_rect: DeviceRect,
-    brush_flags: BrushFlags,
-    edge_flags: EdgeAaSegmentMask,
-    brush_segments: &mut BrushSegmentVec,
-) {
-    if image_rect.size.width <= 0. || image_rect.size.width <= 0. {
-        return;
-    }
+/// 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_for_border(
+    radii: &BorderRadius,
+    widths: &LayoutSideOffsets
+) -> 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(widths.top)
+        .max(widths.bottom)
+        .max(widths.left)
+        .max(widths.right);
 
-    brush_segments.push(
-        BrushSegment::new(
-            image_rect,
-            /* may_need_clip_mask = */ true,
-            edge_flags,
-            [
-                task_rect.origin.x,
-                task_rect.origin.y,
-                task_rect.origin.x + task_rect.size.width,
-                task_rect.origin.y + task_rect.size.height,
-            ],
-            brush_flags,
-        )
-    );
+    LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
 }
 
 fn add_segment(
     task_rect: DeviceRect,
     style0: BorderStyle,
     style1: BorderStyle,
     color0: ColorF,
     color1: ColorF,
@@ -1204,81 +927,233 @@ fn add_segment(
                 _ => {
                     instances.push(base_instance);
                 }
             }
         }
     }
 }
 
+/// Add a corner segment (if valid) to the list of
+/// border segments for this primitive.
 fn add_corner_segment(
     image_rect: LayoutRect,
-    task_rect: DeviceRect,
-    side0: &BorderSide,
-    side1: &BorderSide,
-    widths: DeviceSize,
-    radius: DeviceSize,
+    side0: BorderSide,
+    side1: BorderSide,
+    widths: LayoutSize,
+    radius: LayoutSize,
     segment: BorderSegment,
     edge_flags: EdgeAaSegmentMask,
-    border_segments: &mut Vec<BorderSegmentInfo>,
     brush_segments: &mut BrushSegmentVec,
+    border_segments: &mut SmallVec<[BorderSegmentInfo; 8]>,
+    do_aa: bool,
 ) {
     if side0.color.a <= 0.0 && side1.color.a <= 0.0 {
         return;
     }
 
     if widths.width <= 0.0 && widths.height <= 0.0 {
         return;
     }
 
     if side0.style.is_hidden() && side1.style.is_hidden() {
         return;
     }
 
-    border_segments.push(BorderSegmentInfo {
-        task_rect,
-        segment,
-        radius,
-        widths,
-    });
+    if image_rect.size.width <= 0. || image_rect.size.height <= 0. {
+        return;
+    }
+
+    let is_opaque =
+        side0.is_opaque() &&
+        side1.is_opaque() &&
+        radius.width <= 0.0 &&
+        radius.height <= 0.0;
 
-    add_brush_segment(
-        image_rect,
-        task_rect,
-        BrushFlags::SEGMENT_RELATIVE,
-        edge_flags,
-        brush_segments,
+    brush_segments.push(
+        BrushSegment::new(
+            image_rect,
+            /* may_need_clip_mask = */ true,
+            edge_flags,
+            [0.0; 4],
+            BrushFlags::SEGMENT_RELATIVE,
+        )
     );
+
+    border_segments.push(BorderSegmentInfo {
+        handle: None,
+        local_task_size: image_rect.size,
+        is_opaque,
+        cache_key: RenderTaskCacheKey {
+            size: DeviceIntSize::zero(),
+            kind: RenderTaskCacheKeyKind::BorderCorner(
+                BorderCornerCacheKey {
+                    do_aa,
+                    side0: side0.into(),
+                    side1: side1.into(),
+                    segment,
+                    radius: radius.to_au(),
+                    widths: widths.to_au(),
+                }
+            ),
+        },
+    });
 }
 
+/// Add an edge segment (if valid) to the list of
+/// border segments for this primitive.
 fn add_edge_segment(
     image_rect: LayoutRect,
-    task_rect: DeviceRect,
-    side: &BorderSide,
+    edge_info: &EdgeInfo,
+    side: BorderSide,
+    width: f32,
     segment: BorderSegment,
     edge_flags: EdgeAaSegmentMask,
-    border_segments: &mut Vec<BorderSegmentInfo>,
-    brush_flags: BrushFlags,
     brush_segments: &mut BrushSegmentVec,
+    border_segments: &mut SmallVec<[BorderSegmentInfo; 8]>,
+    do_aa: bool,
 ) {
     if side.color.a <= 0.0 {
         return;
     }
 
     if side.style.is_hidden() {
         return;
     }
 
+    let (size, brush_flags) = match segment {
+        BorderSegment::Left | BorderSegment::Right => {
+            (LayoutSize::new(width, edge_info.stretch_size), BrushFlags::SEGMENT_REPEAT_Y)
+        }
+        BorderSegment::Top | BorderSegment::Bottom => {
+            (LayoutSize::new(edge_info.stretch_size, width), BrushFlags::SEGMENT_REPEAT_X)
+        }
+        _ => {
+            unreachable!();
+        }
+    };
+
+    if image_rect.size.width <= 0. || image_rect.size.height <= 0. {
+        return;
+    }
+
+    let is_opaque = side.is_opaque();
+
+    brush_segments.push(
+        BrushSegment::new(
+            image_rect,
+            /* may_need_clip_mask = */ true,
+            edge_flags,
+            [0.0, 0.0, size.width, size.height],
+            BrushFlags::SEGMENT_RELATIVE | brush_flags,
+        )
+    );
+
     border_segments.push(BorderSegmentInfo {
-        task_rect,
-        segment,
-        radius: DeviceSize::zero(),
-        widths: task_rect.size,
+        handle: None,
+        local_task_size: size,
+        is_opaque,
+        cache_key: RenderTaskCacheKey {
+            size: DeviceIntSize::zero(),
+            kind: RenderTaskCacheKeyKind::BorderEdge(
+                BorderEdgeCacheKey {
+                    do_aa,
+                    side: side.into(),
+                    size: size.to_au(),
+                    segment,
+                },
+            ),
+        },
     });
+}
+
+/// Build the set of border instances needed to draw a border
+/// segment into the render task cache.
+pub fn build_border_instances(
+    cache_key: &RenderTaskCacheKey,
+    border: &NormalBorder,
+    scale: LayoutToDeviceScale,
+) -> Vec<BorderInstance> {
+    let mut instances = Vec::new();
+
+    let (segment, widths, radius) = match cache_key.kind {
+        RenderTaskCacheKeyKind::BorderEdge(ref key) => {
+            (key.segment, LayoutSize::from_au(key.size), LayoutSize::zero())
+        }
+        RenderTaskCacheKeyKind::BorderCorner(ref key) => {
+            (key.segment, LayoutSize::from_au(key.widths), LayoutSize::from_au(key.radius))
+        }
+        _ => {
+            unreachable!();
+        }
+    };
 
-    add_brush_segment(
-        image_rect,
-        task_rect,
-        brush_flags,
-        edge_flags,
-        brush_segments,
+    let (side0, side1, flip0, flip1) = match segment {
+        BorderSegment::Left => (&border.left, &border.left, false, false),
+        BorderSegment::Top => (&border.top, &border.top, false, false),
+        BorderSegment::Right => (&border.right, &border.right, true, true),
+        BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
+        BorderSegment::TopLeft => (&border.left, &border.top, false, false),
+        BorderSegment::TopRight => (&border.top, &border.right, false, true),
+        BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
+        BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
+    };
+
+    let style0 = if side0.style.is_hidden() {
+        side1.style
+    } else {
+        side0.style
+    };
+    let style1 = if side1.style.is_hidden() {
+        side0.style
+    } else {
+        side1.style
+    };
+
+    let color0 = side0.border_color(flip0);
+    let color1 = side1.border_color(flip1);
+
+    let widths = (widths * scale).ceil();
+    let radius = (radius * scale).ceil();
+
+    add_segment(
+        DeviceRect::new(DevicePoint::zero(), cache_key.size.to_f32()),
+        style0,
+        style1,
+        color0,
+        color1,
+        segment,
+        &mut instances,
+        widths,
+        radius,
+        border.do_aa,
     );
+
+    instances
 }
+
+pub fn create_normal_border_prim(
+    local_rect: &LayoutRect,
+    border: NormalBorder,
+    widths: LayoutSideOffsets,
+) -> BrushPrimitive {
+    let mut brush_segments = BrushSegmentVec::new();
+    let mut border_segments = SmallVec::new();
+
+    create_border_segments(
+        local_rect,
+        &border,
+        &widths,
+        &mut border_segments,
+        &mut brush_segments,
+    );
+
+    BrushPrimitive::new(
+        BrushKind::new_border(
+            border,
+            widths,
+            border_segments,
+        ),
+        Some(BrushSegmentDescriptor {
+            segments: brush_segments,
+        }),
+    )
+}
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -400,18 +400,18 @@ impl ClipScrollTree {
             }
             SpatialNodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
                 pt.add_item(format!("index: {:?}", index));
                 pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
                 pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
                 pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
             }
-            SpatialNodeType::ReferenceFrame(ref info) => {
-                pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
+            SpatialNodeType::ReferenceFrame(ref _info) => {
+                pt.new_level(format!("ReferenceFrame"));
                 pt.add_item(format!("index: {:?}", index));
             }
         }
 
         pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
         pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
         pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
 
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -1,25 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use super::super::shader_source;
-use api::{ColorF, ImageFormat};
+use api::{ColorF, ImageFormat, MemoryReport};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
 use api::TextureTarget;
 #[cfg(any(feature = "debug_renderer", feature="capture"))]
 use api::ImageDescriptor;
 use euclid::Transform3D;
 use gleam::gl;
-use internal_types::{FastHashMap, RenderTargetInfo};
+use internal_types::{FastHashMap, LayerIndex, RenderTargetInfo};
 use log::Level;
 use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::cmp;
+use std::collections::hash_map::Entry;
 use std::fs::File;
 use std::io::Read;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
@@ -437,55 +438,61 @@ impl ExternalTexture {
 pub struct Texture {
     id: gl::GLuint,
     target: gl::GLuint,
     layer_count: i32,
     format: ImageFormat,
     width: u32,
     height: u32,
     filter: TextureFilter,
-    render_target: Option<RenderTargetInfo>,
-    fbo_ids: Vec<FBOId>,
-    depth_rb: Option<RBOId>,
+    /// Framebuffer Objects, one for each layer of the texture, allowing this
+    /// texture to be rendered to. Empty if this texture is not used as a render
+    /// target.
+    fbos: Vec<FBOId>,
+    /// Same as the above, but with a depth buffer attached.
+    ///
+    /// FBOs are cheap to create but expensive to reconfigure (since doing so
+    /// invalidates framebuffer completeness caching). Moreover, rendering with
+    /// a depth buffer attached but the depth write+test disabled relies on the
+    /// driver to optimize it out of the rendering pass, which most drivers
+    /// probably do but, according to jgilbert, is best not to rely on.
+    ///
+    /// So we lazily generate a second list of FBOs with depth. This list is
+    /// empty if this texture is not used as a render target _or_ if it is, but
+    /// the depth buffer has never been requested.
+    ///
+    /// Note that we always fill fbos, and then lazily create fbos_with_depth
+    /// when needed. We could make both lazy (i.e. render targets would have one
+    /// or the other, but not both, unless they were actually used in both
+    /// configurations). But that would complicate a lot of logic in this module,
+    /// and FBOs are cheap enough to create.
+    fbos_with_depth: Vec<FBOId>,
     last_frame_used: FrameId,
 }
 
 impl Texture {
     pub fn get_dimensions(&self) -> DeviceUintSize {
         DeviceUintSize::new(self.width, self.height)
     }
 
-    pub fn get_render_target_layer_count(&self) -> usize {
-        self.fbo_ids.len()
-    }
-
     pub fn get_layer_count(&self) -> i32 {
         self.layer_count
     }
 
     pub fn get_format(&self) -> ImageFormat {
         self.format
     }
 
     #[cfg(any(feature = "debug_renderer", feature = "capture"))]
     pub fn get_filter(&self) -> TextureFilter {
         self.filter
     }
 
-    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
-    pub fn get_render_target(&self) -> Option<RenderTargetInfo> {
-        self.render_target.clone()
-    }
-
-    pub fn has_depth(&self) -> bool {
-        self.depth_rb.is_some()
-    }
-
-    pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
-        self.render_target.as_ref()
+    pub fn supports_depth(&self) -> bool {
+        !self.fbos_with_depth.is_empty()
     }
 
     pub fn used_in_frame(&self, frame_id: FrameId) -> bool {
         self.last_frame_used == frame_id
     }
 
     /// Returns true if this texture was used within `threshold` frames of
     /// the current frame.
@@ -708,16 +715,32 @@ pub struct Capabilities {
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
     Compilation(String, String), // name, error message
     Link(String, String),        // name, error message
 }
 
+/// A refcounted depth target, which may be shared by multiple textures across
+/// the device.
+struct SharedDepthTarget {
+    /// The Render Buffer Object representing the depth target.
+    rbo_id: RBOId,
+    /// Reference count. When this drops to zero, the RBO is deleted.
+    refcount: usize,
+}
+
+#[cfg(feature = "debug")]
+impl Drop for SharedDepthTarget {
+    fn drop(&mut self) {
+        debug_assert!(thread::panicking() || self.refcount == 0);
+    }
+}
+
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [gl::GLuint; 16],
     bound_program: gl::GLuint,
     bound_vao: gl::GLuint,
     bound_read_fbo: FBOId,
     bound_draw_fbo: FBOId,
@@ -730,16 +753,22 @@ pub struct Device {
 
     // HW or API capabilities
     #[cfg(feature = "debug_renderer")]
     capabilities: Capabilities,
 
     bgra_format_internal: gl::GLuint,
     bgra_format_external: gl::GLuint,
 
+    /// Map from texture dimensions to shared depth buffers for render targets.
+    ///
+    /// Render targets often have the same width/height, so we can save memory
+    /// by sharing these across targets.
+    depth_targets: FastHashMap<DeviceUintSize, SharedDepthTarget>,
+
     // debug
     inside_frame: bool,
 
     // resources
     resource_override_path: Option<PathBuf>,
 
     max_texture_size: u32,
     renderer_name: String,
@@ -754,16 +783,45 @@ pub struct Device {
     /// otherwise are on some drivers, particularly ANGLE), If it's not
     /// supported, we fall back to glTexImage*.
     supports_texture_storage: bool,
 
     // GL extensions
     extensions: Vec<String>,
 }
 
+/// Contains the parameters necessary to bind a texture-backed draw target.
+#[derive(Clone, Copy)]
+pub struct TextureDrawTarget<'a> {
+    /// The target texture.
+    pub texture: &'a Texture,
+    /// The slice within the texture array to draw to.
+    pub layer: LayerIndex,
+    /// Whether to draw with the texture's associated depth target.
+    pub with_depth: bool,
+}
+
+/// Contains the parameters necessary to bind a texture-backed read target.
+#[derive(Clone, Copy)]
+pub struct TextureReadTarget<'a> {
+    /// The source texture.
+    pub texture: &'a Texture,
+    /// The slice within the texture array to read from.
+    pub layer: LayerIndex,
+}
+
+impl<'a> From<TextureDrawTarget<'a>> for TextureReadTarget<'a> {
+    fn from(t: TextureDrawTarget<'a>) -> Self {
+        TextureReadTarget {
+            texture: t.texture,
+            layer: t.layer,
+        }
+    }
+}
+
 impl Device {
     pub fn new(
         gl: Rc<gl::Gl>,
         resource_override_path: Option<PathBuf>,
         upload_method: UploadMethod,
         cached_programs: Option<Rc<ProgramCache>>,
     ) -> Device {
         let mut max_texture_size = [0];
@@ -824,16 +882,18 @@ impl Device {
             #[cfg(feature = "debug_renderer")]
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
             },
 
             bgra_format_internal,
             bgra_format_external,
 
+            depth_targets: FastHashMap::default(),
+
             bound_textures: [0; 16],
             bound_program: 0,
             bound_vao: 0,
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             program_mode_id: UniformLocation::INVALID,
             default_read_fbo: 0,
             default_draw_fbo: 0,
@@ -1001,40 +1061,44 @@ impl Device {
         debug_assert!(self.inside_frame);
 
         if self.bound_read_fbo != fbo_id {
             self.bound_read_fbo = fbo_id;
             fbo_id.bind(self.gl(), FBOTarget::Read);
         }
     }
 
-    pub fn bind_read_target(&mut self, texture_and_layer: Option<(&Texture, i32)>) {
-        let fbo_id = texture_and_layer.map_or(FBOId(self.default_read_fbo), |texture_and_layer| {
-            texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
+    pub fn bind_read_target(&mut self, texture_target: Option<TextureReadTarget>) {
+        let fbo_id = texture_target.map_or(FBOId(self.default_read_fbo), |target| {
+            target.texture.fbos[target.layer]
         });
 
         self.bind_read_target_impl(fbo_id)
     }
 
     fn bind_draw_target_impl(&mut self, fbo_id: FBOId) {
         debug_assert!(self.inside_frame);
 
         if self.bound_draw_fbo != fbo_id {
             self.bound_draw_fbo = fbo_id;
             fbo_id.bind(self.gl(), FBOTarget::Draw);
         }
     }
 
     pub fn bind_draw_target(
         &mut self,
-        texture_and_layer: Option<(&Texture, i32)>,
+        texture_target: Option<TextureDrawTarget>,
         dimensions: Option<DeviceUintSize>,
     ) {
-        let fbo_id = texture_and_layer.map_or(FBOId(self.default_draw_fbo), |texture_and_layer| {
-            texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
+        let fbo_id = texture_target.map_or(FBOId(self.default_draw_fbo), |target| {
+            if target.with_depth {
+                target.texture.fbos_with_depth[target.layer]
+            } else {
+                target.texture.fbos[target.layer]
+            }
         });
 
         self.bind_draw_target_impl(fbo_id);
 
         if let Some(dimensions) = dimensions {
             self.gl.viewport(
                 0,
                 0,
@@ -1227,19 +1291,18 @@ impl Device {
         let mut texture = Texture {
             id: self.gl.gen_textures(1)[0],
             target: get_gl_target(target),
             width,
             height,
             layer_count,
             format,
             filter,
-            render_target,
-            fbo_ids: vec![],
-            depth_rb: None,
+            fbos: vec![],
+            fbos_with_depth: vec![],
             last_frame_used: self.frame_id,
         };
         self.bind_texture(DEFAULT_TEXTURE, &texture);
         self.set_texture_parameters(texture.target, filter);
 
         // Allocate storage.
         let desc = self.gl_describe_format(texture.format);
         let is_array = match texture.target {
@@ -1304,17 +1367,20 @@ impl Device {
                     desc.external,
                     desc.pixel_type,
                     None,
                 ),
         }
 
         // Set up FBOs, if required.
         if let Some(rt_info) = render_target {
-            self.init_fbos(&mut texture, rt_info);
+            self.init_fbos(&mut texture, false);
+            if rt_info.has_depth {
+                self.init_fbos(&mut texture, true);
+            }
         }
 
         texture
     }
 
     fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
         let mag_filter = match filter {
             TextureFilter::Nearest => gl::NEAREST,
@@ -1344,35 +1410,39 @@ impl Device {
         dst: &mut Texture,
         src: &Texture,
     ) {
         debug_assert!(self.inside_frame);
         debug_assert!(dst.width >= src.width);
         debug_assert!(dst.height >= src.height);
 
         let rect = DeviceIntRect::new(DeviceIntPoint::zero(), src.get_dimensions().to_i32());
-        for (read_fbo, draw_fbo) in src.fbo_ids.iter().zip(&dst.fbo_ids) {
+        for (read_fbo, draw_fbo) in src.fbos.iter().zip(&dst.fbos) {
             self.bind_read_target_impl(*read_fbo);
             self.bind_draw_target_impl(*draw_fbo);
             self.blit_render_target(rect, rect);
         }
         self.bind_read_target(None);
     }
 
     /// Notifies the device that the contents of a render target are no longer
     /// needed.
+    ///
+    /// FIXME(bholley): We could/should invalidate the depth targets earlier
+    /// than the color targets, i.e. immediately after each pass.
     pub fn invalidate_render_target(&mut self, texture: &Texture) {
-        let attachments: &[gl::GLenum] = if texture.has_depth() {
-            &[gl::COLOR_ATTACHMENT0, gl::DEPTH_ATTACHMENT]
+        let (fbos, attachments) = if texture.supports_depth() {
+            (&texture.fbos_with_depth,
+             &[gl::COLOR_ATTACHMENT0, gl::DEPTH_ATTACHMENT] as &[gl::GLenum])
         } else {
-            &[gl::COLOR_ATTACHMENT0]
+            (&texture.fbos, &[gl::COLOR_ATTACHMENT0] as &[gl::GLenum])
         };
 
         let original_bound_fbo = self.bound_draw_fbo;
-        for fbo_id in texture.fbo_ids.iter() {
+        for fbo_id in fbos.iter() {
             // Note: The invalidate extension may not be supported, in which
             // case this is a no-op. That's ok though, because it's just a
             // hint.
             self.bind_external_draw_target(*fbo_id);
             self.gl.invalidate_framebuffer(gl::FRAMEBUFFER, attachments);
         }
         self.bind_external_draw_target(original_bound_fbo);
     }
@@ -1381,52 +1451,38 @@ impl Device {
     ///
     /// This method adds or removes a depth target as necessary.
     pub fn reuse_render_target<T: Texel>(
         &mut self,
         texture: &mut Texture,
         rt_info: RenderTargetInfo,
     ) {
         texture.last_frame_used = self.frame_id;
-        texture.render_target = Some(rt_info);
 
-        // If the depth target requirements changed, just drop the FBOs and
-        // reinitialize.
-        //
-        // FIXME(bholley): I have a patch to do this better.
-        if rt_info.has_depth != texture.has_depth() {
-            self.deinit_fbos(texture);
-            self.init_fbos(texture, rt_info);
+        // Add depth support if needed.
+        if rt_info.has_depth && !texture.supports_depth() {
+            self.init_fbos(texture, true);
         }
     }
 
-    fn init_fbos(&mut self, texture: &mut Texture, rt_info: RenderTargetInfo) {
-        // Generate the FBOs.
-        assert!(texture.fbo_ids.is_empty());
-        texture.fbo_ids.extend(
-            self.gl.gen_framebuffers(texture.layer_count).into_iter().map(FBOId)
-        );
+    fn init_fbos(&mut self, texture: &mut Texture, with_depth: bool) {
+        let (fbos, depth_rb) = if with_depth {
+            let depth_target = self.acquire_depth_target(texture.get_dimensions());
+            (&mut texture.fbos_with_depth, Some(depth_target))
+        } else {
+            (&mut texture.fbos, None)
+        };
 
-        // Optionally generate a depth target.
-        if rt_info.has_depth {
-            let renderbuffer_ids = self.gl.gen_renderbuffers(1);
-            let depth_rb = renderbuffer_ids[0];
-            texture.depth_rb = Some(RBOId(depth_rb));
-            self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
-            self.gl.renderbuffer_storage(
-                gl::RENDERBUFFER,
-                gl::DEPTH_COMPONENT24,
-                texture.width as _,
-                texture.height as _,
-            );
-        }
+        // Generate the FBOs.
+        assert!(fbos.is_empty());
+        fbos.extend(self.gl.gen_framebuffers(texture.layer_count).into_iter().map(FBOId));
 
         // Bind the FBOs.
         let original_bound_fbo = self.bound_draw_fbo;
-        for (fbo_index, &fbo_id) in texture.fbo_ids.iter().enumerate() {
+        for (fbo_index, &fbo_id) in fbos.iter().enumerate() {
             self.bind_external_draw_target(fbo_id);
             match texture.target {
                 gl::TEXTURE_2D_ARRAY => {
                     self.gl.framebuffer_texture_layer(
                         gl::DRAW_FRAMEBUFFER,
                         gl::COLOR_ATTACHMENT0,
                         texture.id,
                         0,
@@ -1440,44 +1496,72 @@ impl Device {
                         gl::COLOR_ATTACHMENT0,
                         texture.target,
                         texture.id,
                         0,
                     )
                 }
             }
 
-            if let Some(depth_rb) = texture.depth_rb {
+            if let Some(depth_rb) = depth_rb {
                 self.gl.framebuffer_renderbuffer(
                     gl::DRAW_FRAMEBUFFER,
                     gl::DEPTH_ATTACHMENT,
                     gl::RENDERBUFFER,
                     depth_rb.0,
                 );
             }
         }
         self.bind_external_draw_target(original_bound_fbo);
     }
 
-    fn deinit_fbos(&mut self, texture: &mut Texture) {
-        if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
-            self.gl.delete_renderbuffers(&[depth_rb]);
-            texture.depth_rb = None;
-        }
-
-        if !texture.fbo_ids.is_empty() {
-            let fbo_ids: Vec<_> = texture
-                .fbo_ids
+    fn deinit_fbos(&mut self, fbos: &mut Vec<FBOId>) {
+        if !fbos.is_empty() {
+            let fbo_ids: SmallVec<[gl::GLuint; 8]> = fbos
                 .drain(..)
                 .map(|FBOId(fbo_id)| fbo_id)
                 .collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
     }
 
+    fn acquire_depth_target(&mut self, dimensions: DeviceUintSize) -> RBOId {
+        let gl = &self.gl;
+        let target = self.depth_targets.entry(dimensions).or_insert_with(|| {
+            let renderbuffer_ids = gl.gen_renderbuffers(1);
+            let depth_rb = renderbuffer_ids[0];
+            gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
+            gl.renderbuffer_storage(
+                gl::RENDERBUFFER,
+                gl::DEPTH_COMPONENT24,
+                dimensions.width as _,
+                dimensions.height as _,
+            );
+            SharedDepthTarget {
+                rbo_id: RBOId(depth_rb),
+                refcount: 0,
+            }
+        });
+        target.refcount += 1;
+        target.rbo_id
+    }
+
+    fn release_depth_target(&mut self, dimensions: DeviceUintSize) {
+        let mut entry = match self.depth_targets.entry(dimensions) {
+            Entry::Occupied(x) => x,
+            Entry::Vacant(..) => panic!("Releasing unknown depth target"),
+        };
+        debug_assert!(entry.get().refcount != 0);
+        entry.get_mut().refcount -= 1;
+        if entry.get().refcount == 0 {
+            let t = entry.remove();
+            self.gl.delete_renderbuffers(&[t.rbo_id.0]);
+        }
+    }
+
     pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
         debug_assert!(self.inside_frame);
 
         self.gl.blit_framebuffer(
             src_rect.origin.x,
             src_rect.origin.y,
             src_rect.origin.x + src_rect.size.width,
             src_rect.origin.y + src_rect.size.height,
@@ -1487,17 +1571,23 @@ impl Device {
             dest_rect.origin.y + dest_rect.size.height,
             gl::COLOR_BUFFER_BIT,
             gl::LINEAR,
         );
     }
 
     pub fn delete_texture(&mut self, mut texture: Texture) {
         debug_assert!(self.inside_frame);
-        self.deinit_fbos(&mut texture);
+        let had_depth = texture.supports_depth();
+        self.deinit_fbos(&mut texture.fbos);
+        self.deinit_fbos(&mut texture.fbos_with_depth);
+        if had_depth {
+            self.release_depth_target(texture.get_dimensions());
+        }
+
         self.gl.delete_textures(&[texture.id]);
 
         for bound_texture in &mut self.bound_textures {
             if *bound_texture == texture.id {
                 *bound_texture = 0
             }
         }
 
@@ -2312,16 +2402,28 @@ impl Device {
             },
             ImageFormat::RG8 => FormatDesc {
                 internal: gl::RG8,
                 external: gl::RG,
                 pixel_type: gl::UNSIGNED_BYTE,
             },
         }
     }
+
+    /// Generates a memory report for the resources managed by the device layer.
+    pub fn report_memory(&self) -> MemoryReport {
+        let mut report = MemoryReport::default();
+        for dim in self.depth_targets.keys() {
+            // DEPTH24 textures generally reserve 3 bytes for depth and 1 byte
+            // for stencil, so we measure them as 32 bytes.
+            let pixels: u32 = dim.width * dim.height;
+            report.depth_target_textures += (pixels as usize) * 4;
+        }
+        report
+    }
 }
 
 struct FormatDesc {
     internal: gl::GLenum,
     external: gl::GLuint,
     pixel_type: gl::GLuint,
 }
 
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.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::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
+use api::{DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
@@ -27,17 +27,17 @@ use picture::{PictureCompositeMode, Pict
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
 use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, 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::DocumentResources;
-use spatial_node::{SpatialNodeType, StickyFrameInfo};
+use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect, RectHelpers};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
@@ -204,17 +204,16 @@ impl<'a> DisplayListFlattener<'a> {
             root_prim_index: PrimitiveIndex(0),
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
-        flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
         flattener.flatten_root(root_pipeline, &root_pipeline.viewport_size);
 
         debug_assert!(flattener.sc_stack.is_empty());
 
         new_scene.root_pipeline_id = Some(root_pipeline_id);
         new_scene.pipeline_epochs = scene.pipeline_epochs.clone();
         new_scene.pipelines = scene.pipelines.clone();
 
@@ -1252,30 +1251,16 @@ impl<'a> DisplayListFlattener<'a> {
         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, ClipChainId::NONE, 0),
         }
         index
     }
 
-    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];
-        if let SpatialNodeType::ReferenceFrame(ref mut info) = root_node.node_type {
-            info.resolved_transform =
-                LayoutVector2D::new(viewport_offset.x, viewport_offset.y).into();
-        }
-    }
-
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayoutSize,
         content_size: &LayoutSize,
     ) {
         if let ChasePrimitive::Index(prim_index) = self.config.chase_primitive {
             println!("Chasing {:?} by index", prim_index);
@@ -1479,17 +1464,20 @@ impl<'a> DisplayListFlattener<'a> {
                             info.rect = info.rect.translate(&pending_shadow.shadow.offset);
                             info.clip_rect = info.clip_rect.translate(&pending_shadow.shadow.offset);
 
                             // Construct and add a primitive for the given shadow.
                             let shadow_prim_instance = self.create_primitive(
                                 &info,
                                 pending_primitive.clip_and_scroll.clip_chain_id,
                                 pending_primitive.clip_and_scroll.spatial_node_index,
-                                pending_primitive.container.create_shadow(&pending_shadow.shadow),
+                                pending_primitive.container.create_shadow(
+                                    &pending_shadow.shadow,
+                                    &info.rect,
+                                ),
                             );
 
                             // Add the new primitive to the shadow picture.
                             prims.push(shadow_prim_instance);
                         }
                     }
 
                     // No point in adding a shadow here if there were no primitives
@@ -1682,17 +1670,19 @@ impl<'a> DisplayListFlattener<'a> {
                     repeat_horizontal: RepeatMode,
                     repeat_vertical: RepeatMode
                 ) {
                     if uv_rect.uv1.x > uv_rect.uv0.x &&
                        uv_rect.uv1.y > uv_rect.uv0.y {
 
                         // Use segment relative interpolation for all
                         // instances in this primitive.
-                        let mut brush_flags = BrushFlags::SEGMENT_RELATIVE;
+                        let mut brush_flags =
+                            BrushFlags::SEGMENT_RELATIVE |
+                            BrushFlags::SEGMENT_TEXEL_RECT;
 
                         // Enable repeat modes on the segment.
                         if repeat_horizontal == RepeatMode::Repeat {
                             brush_flags |= BrushFlags::SEGMENT_REPEAT_X;
                         }
                         if repeat_vertical == RepeatMode::Repeat {
                             brush_flags |= BrushFlags::SEGMENT_REPEAT_Y;
                         }
@@ -1840,17 +1830,22 @@ impl<'a> DisplayListFlattener<'a> {
                 };
 
                 let prim = PrimitiveContainer::Brush(
                     BrushPrimitive::new(brush_kind, Some(descriptor))
                 );
                 self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
             }
             BorderDetails::Normal(ref border) => {
-                self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
+                self.add_normal_border(
+                    info,
+                    border,
+                    border_item.widths,
+                    clip_and_scroll,
+                );
             }
         }
     }
 
     pub fn create_brush_kind_for_gradient(
         &mut self,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
--- a/gfx/webrender/src/gpu_glyph_renderer.rs
+++ b/gfx/webrender/src/gpu_glyph_renderer.rs
@@ -2,17 +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/. */
 
 //! GPU glyph rasterization using Pathfinder.
 
 use api::{DeviceIntPoint, DeviceIntRect, DeviceUintSize, FontRenderMode};
 use api::{ImageFormat, TextureTarget};
 use debug_colors;
-use device::{Device, Texture, TextureFilter, VAO};
+use device::{Device, Texture, TextureDrawTarget, TextureFilter, VAO};
 use euclid::{Point2D, Size2D, Transform3D, TypedVector2D, Vector2D};
 use internal_types::RenderTargetInfo;
 use pathfinder_gfx_utils::ShelfBinPacker;
 use profiler::GpuProfileTag;
 use renderer::{self, ImageBufferKind, Renderer, RendererError, RendererStats};
 use renderer::{TextureSampler, VertexArrayKind, ShaderPrecacheFlags};
 use shade::{LazilyCompiledShader, ShaderKind};
 use tiling::GlyphJob;
@@ -189,17 +189,21 @@ impl Renderer {
             1,
         );
         self.device.upload_texture_immediate(&path_info_texture, &path_info_texels);
 
         self.gpu_glyph_renderer.vector_stencil.bind(&mut self.device,
                                                     projection,
                                                     &mut self.renderer_errors);
 
-        self.device.bind_draw_target(Some((&current_page.texture, 0)), Some(*target_size));
+        self.device.bind_draw_target(Some(TextureDrawTarget {
+            texture: &current_page.texture,
+            layer: 0,
+            with_depth: false,
+        }), Some(*target_size));
         self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, None);
 
         self.device.set_blend(true);
         self.device.set_blend_mode_subpixel_pass1();
 
         let mut instance_data = vec![];
         for (path_id, &glyph_id) in glyph_indices.iter().enumerate() {
             let glyph = &glyphs[glyph_id];
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -84,17 +84,17 @@ pub struct BlurInstance {
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ScalingInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
 }
 
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BorderSegment {
     TopLeft,
     TopRight,
     BottomRight,
     BottomLeft,
@@ -313,16 +313,18 @@ bitflags! {
         const PERSPECTIVE_INTERPOLATION = 0x1;
         /// Do interpolation relative to segment rect,
         /// rather than primitive rect.
         const SEGMENT_RELATIVE = 0x2;
         /// Repeat UVs horizontally.
         const SEGMENT_REPEAT_X = 0x4;
         /// Repeat UVs vertically.
         const SEGMENT_REPEAT_Y = 0x8;
+        /// The extra segment data is a texel rect.
+        const SEGMENT_TEXEL_RECT = 0x10;
     }
 }
 
 // TODO(gw): Some of these fields can be moved to the primitive
 //           header since they are constant, and some can be
 //           compressed to a smaller size.
 #[repr(C)]
 pub struct BrushInstance {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -33,16 +33,29 @@ pub type FastHashSet<K> = HashSet<K, Bui
 /// thread maintains a map from cache texture ID to native texture.
 ///
 /// We never reuse IDs, so we use a u64 here to be safe.
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheTextureId(pub u64);
 
+/// Canonical type for texture layer indices.
+///
+/// WebRender is currently not very consistent about layer index types. Some
+/// places use i32 (since that's the type used in various OpenGL APIs), some
+/// places use u32 (since having it be signed is non-sensical, but the
+/// underlying graphics APIs generally operate on 32-bit integers) and some
+/// places use usize (since that's most natural in Rust).
+///
+/// Going forward, we aim to us usize throughout the codebase, since that allows
+/// operations like indexing without a cast, and convert to the required type in
+/// the device module when making calls into the platform layer.
+pub type LayerIndex = usize;
+
 /// Identifies a render pass target that is persisted until the end of the frame.
 ///
 /// By default, only the targets of the immediately-preceding pass are bound as
 /// inputs to the next pass. However, tasks can opt into having their target
 /// preserved in a list until the end of the frame, and this type specifies the
 /// index in that list.
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2,35 +2,35 @@
  * 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, BuiltDisplayList, ClipMode, ColorF, PictureRect};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
 use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
-use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
+use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, ColorDepth, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers};
 use app_units::Au;
-use border::{BorderCacheKey, BorderRenderTaskInfo};
+use border::{get_max_scale_for_border, build_border_instances, create_normal_border_prim};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{TypedTransform3D, TypedRect, TypedScale};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 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 intern;
 use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
-use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
+use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, to_cache_size};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use std::{cmp, fmt, mem, usize};
 use util::{ScaleOffset, MatrixHelpers, pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
@@ -340,23 +340,31 @@ pub struct VisibleImageTile {
 
 #[derive(Debug)]
 pub struct VisibleGradientTile {
     pub handle: GpuCacheHandle,
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
 }
 
+/// Information about how to cache a border segment,
+/// along with the current render task cache entry.
+#[derive(Debug)]
+pub struct BorderSegmentInfo {
+    pub handle: Option<RenderTaskCacheEntryHandle>,
+    pub local_task_size: LayoutSize,
+    pub cache_key: RenderTaskCacheKey,
+    pub is_opaque: bool,
+}
+
 #[derive(Debug)]
 pub enum BorderSource {
     Image(ImageRequest),
     Border {
-        handle: Option<RenderTaskCacheEntryHandle>,
-        cache_key: BorderCacheKey,
-        task_info: Option<BorderRenderTaskInfo>,
+        segments: SmallVec<[BorderSegmentInfo; 8]>,
         border: NormalBorder,
         widths: LayoutSideOffsets,
     },
 }
 
 #[derive(Debug)]
 pub enum BrushKind {
     Solid {
@@ -451,28 +459,29 @@ impl BrushKind {
         BrushKind::Solid {
             color,
             opacity_binding: OpacityBinding::new(),
         }
     }
 
     // Construct a brush that is a border with `border` style and `widths`
     // dimensions.
-    pub fn new_border(mut border: NormalBorder, widths: LayoutSideOffsets) -> BrushKind {
+    pub fn new_border(
+        mut border: NormalBorder,
+        widths: LayoutSideOffsets,
+        segments: SmallVec<[BorderSegmentInfo; 8]>,
+    ) -> BrushKind {
         // FIXME(emilio): Is this the best place to do this?
         border.normalize(&widths);
 
-        let cache_key = BorderCacheKey::new(&border, &widths);
         BrushKind::Border {
             source: BorderSource::Border {
                 border,
                 widths,
-                cache_key,
-                task_info: None,
-                handle: None,
+                segments,
             }
         }
     }
 
     // Construct a brush that is an image wisth `stretch_size` dimensions and
     // `color`.
     pub fn new_image(
         request: ImageRequest,
@@ -1407,17 +1416,21 @@ impl PrimitiveContainer {
                 }
             }
         }
     }
 
     // Create a clone of this PrimitiveContainer, applying whatever
     // changes are necessary to the primitive to support rendering
     // it as part of the supplied shadow.
-    pub fn create_shadow(&self, shadow: &Shadow) -> PrimitiveContainer {
+    pub fn create_shadow(
+        &self,
+        shadow: &Shadow,
+        prim_rect: &LayoutRect,
+    ) -> PrimitiveContainer {
         match *self {
             PrimitiveContainer::TextRun(ref info) => {
                 let mut font = FontInstance {
                     color: shadow.color.into(),
                     ..info.specified_font.clone()
                 };
                 if shadow.blur_radius > 0.0 {
                     font.disable_subpixel_aa();
@@ -1435,32 +1448,35 @@ impl PrimitiveContainer {
                 match brush.kind {
                     BrushKind::Solid { .. } => {
                         PrimitiveContainer::Brush(BrushPrimitive::new(
                             BrushKind::new_solid(shadow.color),
                             None,
                         ))
                     }
                     BrushKind::Border { ref source } => {
-                        let source = match *source {
+                        let prim = match *source {
                             BorderSource::Image(request) => {
-                                BrushKind::Border {
-                                    source: BorderSource::Image(request)
-                                }
-                            },
+                                BrushPrimitive::new(
+                                    BrushKind::Border {
+                                        source: BorderSource::Image(request)
+                                    },
+                                    None,
+                                )
+                            }
                             BorderSource::Border { border, widths, .. } => {
                                 let border = border.with_color(shadow.color);
-                                BrushKind::new_border(border, widths)
-
+                                create_normal_border_prim(
+                                    prim_rect,
+                                    border,
+                                    widths,
+                                )
                             }
                         };
-                        PrimitiveContainer::Brush(BrushPrimitive::new(
-                            source,
-                            None,
-                        ))
+                        PrimitiveContainer::Brush(prim)
                     }
                     BrushKind::LineDecoration { style, orientation, wavy_line_thickness, .. } => {
                         PrimitiveContainer::Brush(BrushPrimitive::new_line_decoration(
                             shadow.color,
                             style,
                             orientation,
                             wavy_line_thickness,
                         ))
@@ -1955,23 +1971,16 @@ impl PrimitiveStore {
                 Some(rect) => rect,
                 None => {
                     return false;
                 }
             };
 
             prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
-            prim.build_prim_segments_if_needed(
-                prim_instance,
-                pic_state,
-                frame_state,
-                frame_context,
-            );
-
             prim.update_clip_task(
                 prim_instance,
                 prim_context,
                 clipped_world_rect,
                 pic_state.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
@@ -2834,19 +2843,56 @@ impl Primitive {
                                         image_properties.descriptor.is_opaque;
 
                                     frame_state.resource_cache.request_image(
                                         request,
                                         frame_state.gpu_cache,
                                     );
                                 }
                             }
-                            BorderSource::Border { .. } => {
-                                // Handled earlier since we need to update the segment
-                                // descriptor *before* update_clip_task() is called.
+                            BorderSource::Border { ref border, ref widths, ref mut segments, .. } => {
+                                // 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 = get_max_scale_for_border(&border.radius, widths);
+                                scale.0 = scale.0.min(max_scale.0);
+
+                                // For each edge and corner, request the render task by content key
+                                // from the render task cache. This ensures that the render task for
+                                // this segment will be available for batching later in the frame.
+                                for segment in segments {
+                                    // Update the cache key device size based on requested scale.
+                                    segment.cache_key.size = to_cache_size(segment.local_task_size * scale);
+
+                                    segment.handle = Some(frame_state.resource_cache.request_render_task(
+                                        segment.cache_key.clone(),
+                                        frame_state.gpu_cache,
+                                        frame_state.render_tasks,
+                                        None,
+                                        segment.is_opaque,
+                                        |render_tasks| {
+                                            let task = RenderTask::new_border_segment(
+                                                segment.cache_key.size,
+                                                build_border_instances(
+                                                    &segment.cache_key,
+                                                    border,
+                                                    scale,
+                                                ),
+                                            );
+
+                                            let task_id = render_tasks.add(task);
+
+                                            pic_state.tasks.push(task_id);
+
+                                            task_id
+                                        }
+                                    ));
+                                }
                             }
                         }
                     }
                     BrushKind::RadialGradient {
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
@@ -3087,105 +3133,16 @@ impl Primitive {
                     println!("\tcreated task {:?} with device rect {:?}",
                         clip_task_id, device_rect);
                 }
                 prim_instance.clip_task_id = Some(clip_task_id);
                 pic_state.tasks.push(clip_task_id);
             }
         }
     }
-
-    fn build_prim_segments_if_needed(
-        &mut self,
-        prim_instance: &mut PrimitiveInstance,
-        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);
-
-            // NOTE(emilio): This `needs_update` relies on the local rect for a
-            // given primitive being immutable. If that changes, this code
-            // should probably handle changes to it as well, retaining the old
-            // size in cache_key.
-            let needs_update = scale_au != cache_key.scale;
-
-            let mut new_segments = BrushSegmentVec::new();
-
-            let local_rect = &self.metadata.local_rect;
-            if needs_update {
-                cache_key.scale = scale_au;
-
-                *task_info = BorderRenderTaskInfo::new(
-                    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: task_info.cache_key_size(&local_rect.size, scale),
-                        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,
-                });
-
-                // The segments have changed, so force the GPU cache to
-                // re-upload the primitive information.
-                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
-            }
-        }
-    }
 }
 
 pub fn get_raster_rects(
     pic_rect: PictureRect,
     map_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
     map_to_world: &SpaceMapper<RasterPixel, WorldPixel>,
     prim_bounding_rect: WorldRect,
     device_pixel_scale: DevicePixelScale,
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -272,16 +272,22 @@ impl Document {
                 tx.send(self.get_scroll_node_state()).unwrap();
             }
             FrameMsg::UpdateDynamicProperties(property_bindings) => {
                 self.dynamic_properties.set_properties(property_bindings);
             }
             FrameMsg::AppendDynamicProperties(property_bindings) => {
                 self.dynamic_properties.add_properties(property_bindings);
             }
+            FrameMsg::SetPinchZoom(factor) => {
+                if self.view.pinch_zoom_factor != factor.get() {
+                    self.view.pinch_zoom_factor = factor.get();
+                    self.frame_is_valid = false;
+                }
+            }
         }
 
         DocumentOps::nop()
     }
 
     fn build_frame(
         &mut self,
         resource_cache: &mut ResourceCache,
@@ -503,19 +509,16 @@ impl RenderBackend {
 
         match message {
             SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
                 txn.epoch_updates.push((pipeline_id, epoch));
             }
             SceneMsg::SetPageZoom(factor) => {
                 doc.view.page_zoom_factor = factor.get();
             }
-            SceneMsg::SetPinchZoom(factor) => {
-                doc.view.pinch_zoom_factor = factor.get();
-            }
             SceneMsg::SetWindowParameters {
                 window_size,
                 inner_rect,
                 device_pixel_ratio,
             } => {
                 doc.view.window_size = window_size;
                 doc.view.inner_rect = inner_rect;
                 doc.view.device_pixel_ratio = device_pixel_ratio;
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -2,28 +2,28 @@
  * 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};
 use api::{DevicePixelScale, ImageDescriptor, ImageFormat};
 use api::{LineStyle, LineOrientation, LayoutSize};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
-use border::BorderCacheKey;
+use border::{BorderCornerCacheKey, BorderEdgeCacheKey};
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange};
 use clip_scroll_tree::SpatialNodeIndex;
 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, UvRectKind};
-use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex};
+use internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::PictureCacheKey;
 use prim_store::{PrimitiveIndex, ImageCacheKey, LineDecorationCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
@@ -108,17 +108,17 @@ impl RenderTaskTree {
         }
 
         // Sanity check - can be relaxed if needed
         match task.location {
             RenderTaskLocation::Fixed(..) => {
                 debug_assert!(pass_index == passes.len() - 1);
             }
             RenderTaskLocation::Dynamic(..) |
-            RenderTaskLocation::TextureCache(..) => {
+            RenderTaskLocation::TextureCache { .. } => {
                 debug_assert!(pass_index < passes.len() - 1);
             }
         }
 
         let pass_index = if task.is_global_cached_task() {
             0
         } else {
             pass_index
@@ -187,17 +187,24 @@ pub enum RenderTaskLocation {
     /// The second member specifies the width and height of the task
     /// output, and the first member is initially left as `None`. During the
     /// build phase, we invoke `RenderTargetList::alloc()` and store the
     /// resulting location in the first member. That location identifies the
     /// render target and the offset of the allocated region within that target.
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
     /// The output of the `RenderTask` will be persisted beyond this frame, and
     /// thus should be drawn into the `TextureCache`.
-    TextureCache(CacheTextureId, i32, DeviceIntRect),
+    TextureCache {
+        /// Which texture in the texture cache should be drawn into.
+        texture: CacheTextureId,
+        /// The target layer in the above texture.
+        layer: LayerIndex,
+        /// The target region within the above layer.
+        rect: DeviceIntRect,
+    },
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CacheMaskTask {
     actual_rect: DeviceIntRect,
     pub root_spatial_node_index: SpatialNodeIndex,
@@ -381,17 +388,17 @@ impl RenderTask {
         content_origin: DeviceIntPoint,
         children: Vec<RenderTaskId>,
         uv_rect_kind: UvRectKind,
         root_spatial_node_index: SpatialNodeIndex,
     ) -> Self {
         let size = match location {
             RenderTaskLocation::Dynamic(_, size) => size,
             RenderTaskLocation::Fixed(rect) => rect.size,
-            RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
+            RenderTaskLocation::TextureCache { rect, .. } => rect.size,
         };
 
         render_task_sanity_check(&size);
 
         let can_merge = size.width as f32 >= unclipped_size.width &&
                         size.height as f32 >= unclipped_size.height;
 
         RenderTask {
@@ -650,17 +657,17 @@ impl RenderTask {
                 target_kind,
                 uv_rect_handle: GpuCacheHandle::new(),
                 uv_rect_kind,
             }),
             clear_mode,
         )
     }
 
-    pub fn new_border(
+    pub fn new_border_segment(
         size: DeviceIntSize,
         instances: Vec<BorderInstance>,
     ) -> Self {
         RenderTask::with_dynamic_location(
             size,
             Vec::new(),
             RenderTaskKind::Border(BorderTask {
                 instances,
@@ -834,17 +841,17 @@ impl RenderTask {
             }
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
             RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(),
             RenderTaskLocation::Dynamic(_, size) => size,
-            RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
+            RenderTaskLocation::TextureCache { rect, .. } => rect.size,
         }
     }
 
     pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) {
         match self.location {
             RenderTaskLocation::Fixed(rect) => {
                 (rect, RenderTargetIndex(0))
             }
@@ -863,17 +870,17 @@ impl RenderTask {
             //           to mark a task as unused explicitly. This
             //           would allow us to restore this debug check.
             RenderTaskLocation::Dynamic(Some((origin, target_index)), size) => {
                 (DeviceIntRect::new(origin, size), target_index)
             }
             RenderTaskLocation::Dynamic(None, _) => {
                 (DeviceIntRect::zero(), RenderTargetIndex(0))
             }
-            RenderTaskLocation::TextureCache(_, layer, rect) => {
+            RenderTaskLocation::TextureCache {layer, rect, .. } => {
                 (rect, RenderTargetIndex(layer as usize))
             }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             RenderTaskKind::Readback(..) => RenderTargetKind::Color,
@@ -1039,33 +1046,34 @@ impl RenderTask {
 
     /// Mark this render task for keeping the results alive up until the end of the frame.
     pub fn mark_for_saving(&mut self) {
         match self.location {
             RenderTaskLocation::Fixed(..) |
             RenderTaskLocation::Dynamic(..) => {
                 self.saved_index = Some(SavedTargetIndex::PENDING);
             }
-            RenderTaskLocation::TextureCache(..) => {
+            RenderTaskLocation::TextureCache { .. } => {
                 panic!("Unable to mark a permanently cached task for saving!");
             }
         }
     }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
     #[allow(dead_code)]
     Glyph(GpuGlyphCacheKey),
     Picture(PictureCacheKey),
-    Border(BorderCacheKey),
+    BorderEdge(BorderEdgeCacheKey),
+    BorderCorner(BorderCornerCacheKey),
     LineDecoration(LineDecorationCacheKey),
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
     pub size: DeviceIntSize,
@@ -1156,17 +1164,17 @@ impl RenderTaskCache {
 
             if let Some(pending_render_task_id) = entry.pending_render_task_id.take() {
                 let render_task = &mut render_tasks[pending_render_task_id];
                 let target_kind = render_task.target_kind();
 
                 // Find out what size to alloc in the texture cache.
                 let size = match render_task.location {
                     RenderTaskLocation::Fixed(..) |
-                    RenderTaskLocation::TextureCache(..) => {
+                    RenderTaskLocation::TextureCache { .. } => {
                         panic!("BUG: dynamic task was expected");
                     }
                     RenderTaskLocation::Dynamic(_, size) => size,
                 };
 
                 // Select the right texture page to allocate from.
                 let image_format = match target_kind {
                     RenderTargetKind::Color => ImageFormat::BGRA8,
@@ -1198,21 +1206,21 @@ impl RenderTaskCache {
 
                 // Get the allocation details in the texture cache, and store
                 // this in the render task. The renderer will draw this
                 // task into the appropriate layer and rect of the texture
                 // cache on this frame.
                 let (texture_id, texture_layer, uv_rect) =
                     texture_cache.get_cache_location(&entry.handle);
 
-                render_task.location = RenderTaskLocation::TextureCache(
-                    texture_id,
-                    texture_layer,
-                    uv_rect.to_i32()
-                );
+                render_task.location = RenderTaskLocation::TextureCache {
+                    texture: texture_id,
+                    layer: texture_layer,
+                    rect: uv_rect.to_i32(),
+                };
             }
         }
     }
 
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -31,17 +31,17 @@ use api::{RenderApiSender, RenderNotifie
 use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
-use device::{ExternalTexture, FBOId, TextureSlot};
+use device::{ExternalTexture, FBOId, TextureDrawTarget, TextureReadTarget, TextureSlot};
 use device::{ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 #[cfg(feature = "debug_renderer")]
 use euclid::rect;
 use euclid::Transform3D;
 use frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
@@ -49,17 +49,17 @@ use glyph_rasterizer::{GlyphFormat, Glyp
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 #[cfg(feature = "debug_renderer")]
 use gpu_cache::GpuDebugChunk;
 #[cfg(feature = "pathfinder")]
 use gpu_glyph_renderer::GpuGlyphRenderer;
 use gpu_types::ScalingInstance;
 use internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
-use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
+use internal_types::{LayerIndex, TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
                GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
@@ -1332,17 +1332,21 @@ impl GpuCacheTexture {
                 rows_dirty
             }
             GpuCacheBus::Scatter { ref program, ref vao, count, .. } => {
                 device.disable_depth();
                 device.set_blend(false);
                 device.bind_program(program);
                 device.bind_custom_vao(vao);
                 device.bind_draw_target(
-                    Some((texture, 0)),
+                    Some(TextureDrawTarget {
+                        texture,
+                        layer: 0,
+                        with_depth: false,
+                    }),
                     Some(texture.get_dimensions()),
                 );
                 device.draw_nonindexed_points(0, count as _);
                 0
             }
         }
     }
 }
@@ -2913,17 +2917,17 @@ impl Renderer {
             }
         }
 
         self.profile_counters.vertices.add(6 * data.len());
     }
 
     fn handle_readback_composite(
         &mut self,
-        render_target: Option<(&Texture, i32)>,
+        render_target: Option<TextureDrawTarget>,
         framebuffer_size: DeviceUintSize,
         scissor_rect: Option<DeviceIntRect>,
         source: &RenderTask,
         backdrop: &RenderTask,
         readback: &RenderTask,
     ) {
         if scissor_rect.is_some() {
             self.device.disable_scissor();
@@ -2946,34 +2950,38 @@ impl Renderer {
             RenderTaskKind::Picture(ref task_info) => task_info.content_origin,
             _ => panic!("bug: composite on non-picture?"),
         };
 
         // Bind the FBO to blit the backdrop to.
         // Called per-instance in case the layer (and therefore FBO)
         // changes. The device will skip the GL call if the requested
         // target is already bound.
-        let cache_draw_target = (cache_texture, readback_layer.0 as i32);
+        let cache_draw_target = TextureDrawTarget {
+            texture: cache_texture,
+            layer: readback_layer.0 as usize,
+            with_depth: false,
+        };
         self.device.bind_draw_target(Some(cache_draw_target), None);
 
         let mut src = DeviceIntRect::new(
             source_screen_origin + (backdrop_rect.origin - backdrop_screen_origin),
             readback_rect.size,
         );
         let mut dest = readback_rect.to_i32();
 
         // Need to invert the y coordinates and flip the image vertically when
         // reading back from the framebuffer.
         if render_target.is_none() {
             src.origin.y = framebuffer_size.height as i32 - src.size.height - src.origin.y;
             dest.origin.y += dest.size.height;
             dest.size.height = -dest.size.height;
         }
 
-        self.device.bind_read_target(render_target);
+        self.device.bind_read_target(render_target.map(|r| r.into()));
         self.device.blit_render_target(src, dest);
 
         // Restore draw target to current pass render target + layer.
         // Note: leaving the viewport unchanged, it's not a part of FBO state
         self.device.bind_draw_target(render_target, None);
 
         if scissor_rect.is_some() {
             self.device.enable_scissor();
@@ -2992,32 +3000,32 @@ impl Renderer {
         let _timer = self.gpu_profile.start_timer(GPU_TAG_BLIT);
 
         // TODO(gw): For now, we don't bother batching these by source texture.
         //           If if ever shows up as an issue, we can easily batch them.
         for blit in blits {
             let source_rect = match blit.source {
                 BlitJobSource::Texture(texture_id, layer, source_rect) => {
                     // A blit from a texture into this target.
-                    let src_texture = self.texture_resolver
+                    let texture = self.texture_resolver
                         .resolve(&texture_id)
                         .expect("BUG: invalid source texture");
-                    self.device.bind_read_target(Some((src_texture, layer)));
+                    self.device.bind_read_target(Some(TextureReadTarget { texture, layer: layer as usize }));
                     source_rect
                 }
                 BlitJobSource::RenderTask(task_id) => {
                     // A blit from the child render task into this target.
                     // TODO(gw): Support R8 format here once we start
                     //           creating mips for alpha masks.
-                    let src_texture = self.texture_resolver
+                    let texture = self.texture_resolver
                         .resolve(&TextureSource::PrevPassColor)
                         .expect("BUG: invalid source texture");
                     let source = &render_tasks[task_id];
                     let (source_rect, layer) = source.get_target_rect();
-                    self.device.bind_read_target(Some((src_texture, layer.0 as i32)));
+                    self.device.bind_read_target(Some(TextureReadTarget { texture, layer: layer.0 }));
                     source_rect
                 }
             };
             debug_assert_eq!(source_rect.size, blit.target_rect.size);
             self.device.blit_render_target(
                 source_rect,
                 blit.target_rect,
             );
@@ -3054,33 +3062,33 @@ impl Renderer {
             VertexArrayKind::Scale,
             &BatchTextures::no_texture(),
             stats,
         );
     }
 
     fn draw_color_target(
         &mut self,
-        render_target: Option<(&Texture, i32)>,
+        render_target: Option<TextureDrawTarget>,
         target: &ColorRenderTarget,
         framebuffer_target_rect: DeviceUintRect,
         target_size: DeviceUintSize,
         depth_is_ready: bool,
         clear_color: Option<[f32; 4]>,
         render_tasks: &RenderTaskTree,
         projection: &Transform3D<f32>,
         frame_id: FrameId,
         stats: &mut RendererStats,
     ) {
         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());
+        if let Some(t) = render_target {
+            assert!(t.texture.supports_depth() >= target.needs_depth());
         }
 
         let framebuffer_kind = if render_target.is_none() {
             FramebufferKind::Main
         } else {
             FramebufferKind::Other
         };
 
@@ -3367,27 +3375,27 @@ impl Renderer {
                 };
                 let (src_rect, _) = render_tasks[output.task_id].get_target_rect();
                 let mut dest_rect = DeviceIntRect::new(DeviceIntPoint::zero(), output_size);
 
                 // Invert Y coordinates, to correctly convert between coordinate systems.
                 dest_rect.origin.y += dest_rect.size.height;
                 dest_rect.size.height *= -1;
 
-                self.device.bind_read_target(render_target);
+                self.device.bind_read_target(render_target.map(|r| r.into()));
                 self.device.bind_external_draw_target(fbo_id);
                 self.device.blit_render_target(src_rect, dest_rect);
                 handler.unlock(output.pipeline_id);
             }
         }
     }
 
     fn draw_alpha_target(
         &mut self,
-        render_target: (&Texture, i32),
+        render_target: TextureDrawTarget,
         target: &AlphaRenderTarget,
         target_size: DeviceUintSize,
         projection: &Transform3D<f32>,
         render_tasks: &RenderTaskTree,
         stats: &mut RendererStats,
     ) {
         self.profile_counters.alpha_targets.inc();
         let _gm = self.gpu_profile.start_marker("alpha target");
@@ -3522,17 +3530,17 @@ impl Renderer {
         }
 
         self.gpu_profile.finish_sampler(alpha_sampler);
     }
 
     fn draw_texture_cache_target(
         &mut self,
         texture: &CacheTextureId,
-        layer: i32,
+        layer: LayerIndex,
         target: &TextureCacheRenderTarget,
         render_tasks: &RenderTaskTree,
         stats: &mut RendererStats,
     ) {
         let texture_source = TextureSource::TextureCache(*texture);
         let (target_size, projection) = {
             let texture = self.texture_resolver
                 .resolve(&texture_source)
@@ -3556,18 +3564,21 @@ impl Renderer {
 
         // Handle any Pathfinder glyphs.
         let stencil_page = self.stencil_glyphs(&target.glyphs, &projection, &target_size, stats);
 
         {
             let texture = self.texture_resolver
                 .resolve(&texture_source)
                 .expect("BUG: invalid target texture");
-            self.device
-                .bind_draw_target(Some((texture, layer)), Some(target_size));
+            self.device.bind_draw_target(Some(TextureDrawTarget {
+                texture,
+                layer,
+                with_depth: false,
+            }), Some(target_size));
         }
 
         self.device.disable_depth();
         self.device.disable_depth_write();
         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));
@@ -3796,25 +3807,25 @@ impl Renderer {
         }
 
         counters.targets_used.inc();
 
         // Try finding a match in the existing pool. If there's no match, we'll
         // create a new texture.
         let selector = TargetSelector {
             size: list.max_size,
-            num_layers: list.targets.len() as _,
+            num_layers: list.targets.len(),
             format: list.format,
         };
         let index = self.texture_resolver.render_target_pool
             .iter()
             .position(|texture| {
                 selector == TargetSelector {
                     size: texture.get_dimensions(),
-                    num_layers: texture.get_render_target_layer_count(),
+                    num_layers: texture.get_layer_count() as usize,
                     format: texture.get_format(),
                 }
             });
 
         let rt_info = RenderTargetInfo { has_depth: list.needs_depth() };
         let texture = if let Some(idx) = index {
             let mut t = self.texture_resolver.render_target_pool.swap_remove(idx);
             self.device.reuse_render_target::<u8>(&mut t, rt_info);
@@ -3975,17 +3986,21 @@ impl Renderer {
                             alpha.max_size.width as f32,
                             0.0,
                             alpha.max_size.height as f32,
                             ORTHO_NEAR_PLANE,
                             ORTHO_FAR_PLANE,
                         );
 
                         self.draw_alpha_target(
-                            (&alpha_tex.as_ref().unwrap().texture, target_index as i32),
+                            TextureDrawTarget {
+                                texture: &alpha_tex.as_ref().unwrap().texture,
+                                layer: target_index,
+                                with_depth: false,
+                            },
                             target,
                             alpha.max_size,
                             &projection,
                             &frame.render_tasks,
                             stats,
                         );
                     }
 
@@ -3997,17 +4012,21 @@ impl Renderer {
                             color.max_size.width as f32,
                             0.0,
                             color.max_size.height as f32,
                             ORTHO_NEAR_PLANE,
                             ORTHO_FAR_PLANE,
                         );
 
                         self.draw_color_target(
-                            Some((&color_tex.as_ref().unwrap().texture, target_index as i32)),
+                            Some(TextureDrawTarget {
+                                texture: &color_tex.as_ref().unwrap().texture,
+                                layer: target_index,
+                                with_depth: target.needs_depth(),
+                            }),
                             target,
                             frame.inner_rect,
                             color.max_size,
                             false,
                             Some([0.0, 0.0, 0.0, 0.0]),
                             &frame.render_tasks,
                             &projection,
                             frame_id,
@@ -4101,34 +4120,33 @@ impl Renderer {
             return;
         }
 
         let mut spacing = 16;
         let mut size = 512;
         let fb_width = framebuffer_size.width as i32;
         let num_layers: i32 = self.texture_resolver.render_target_pool
             .iter()
-            .map(|texture| texture.get_render_target_layer_count() as i32)
+            .map(|texture| texture.get_layer_count() as i32)
             .sum();
 
         if num_layers * (size + spacing) > fb_width {
             let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32;
             size = (size as f32 * factor) as i32;
             spacing = (spacing as f32 * factor) as i32;
         }
 
         let mut target_index = 0;
         for texture in &self.texture_resolver.render_target_pool {
             let dimensions = texture.get_dimensions();
             let src_rect = DeviceIntRect::new(DeviceIntPoint::zero(), dimensions.to_i32());
 
-            let layer_count = texture.get_render_target_layer_count();
-            for layer_index in 0 .. layer_count {
-                self.device
-                    .bind_read_target(Some((texture, layer_index as i32)));
+            let layer_count = texture.get_layer_count() as usize;
+            for layer in 0 .. layer_count {
+                self.device.bind_read_target(Some(TextureReadTarget { texture, layer }));
                 let x = fb_width - (spacing + size) * (target_index + 1);
                 let y = spacing;
 
                 let dest_rect = rect(x, y, size, size);
                 self.device.blit_render_target(src_rect, dest_rect);
                 target_index += 1;
             }
         }
@@ -4163,19 +4181,19 @@ impl Renderer {
                 0
             };
             let dimensions = texture.get_dimensions();
             let src_rect = DeviceIntRect::new(
                 DeviceIntPoint::zero(),
                 DeviceIntSize::new(dimensions.width as i32, dimensions.height as i32),
             );
 
-            let layer_count = texture.get_layer_count();
-            for layer_index in 0 .. layer_count {
-                self.device.bind_read_target(Some((texture, layer_index)));
+            let layer_count = texture.get_layer_count() as usize;
+            for layer in 0 .. layer_count {
+                self.device.bind_read_target(Some(TextureReadTarget { texture, layer}));
 
                 let x = fb_width - (spacing + size) * (i as i32 + 1);
 
                 // If we have more targets than fit on one row in screen, just early exit.
                 if x > fb_width {
                     return;
                 }
 
@@ -4272,17 +4290,17 @@ impl Renderer {
         pixels
     }
 
     pub fn read_gpu_cache(&mut self) -> (DeviceUintSize, Vec<u8>) {
         let texture = self.gpu_cache_texture.texture.as_ref().unwrap();
         let size = texture.get_dimensions();
         let mut texels = vec![0; (size.width * size.height * 16) as usize];
         self.device.begin_frame();
-        self.device.bind_read_target(Some((texture, 0)));
+        self.device.bind_read_target(Some(TextureReadTarget { texture, layer: 0 }));
         self.device.read_pixels_into(
             DeviceUintRect::new(DeviceUintPoint::zero(), size),
             ReadPixelsFormat::Standard(ImageFormat::RGBAF32),
             &mut texels,
         );
         self.device.bind_read_target(None);
         self.device.end_frame();
         (size, texels)
@@ -4357,16 +4375,19 @@ impl Renderer {
         report.vertex_data_textures += self.prim_header_f_texture.size_in_bytes();
         report.vertex_data_textures += self.prim_header_i_texture.size_in_bytes();
         report.vertex_data_textures += self.transforms_texture.size_in_bytes();
         report.vertex_data_textures += self.render_task_texture.size_in_bytes();
 
         // Texture cache and render target GPU memory.
         report += self.texture_resolver.report_memory();
 
+        // Textures held internally within the device layer.
+        report += self.device.report_memory();
+
         report
     }
 
     // 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) {
@@ -4626,17 +4647,16 @@ impl RendererStats {
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainTexture {
     data: String,
     size: (u32, u32, i32),
     format: ImageFormat,
     filter: TextureFilter,
-    render_target: Option<RenderTargetInfo>,
 }
 
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainRenderer {
     gpu_cache: PlainTexture,
@@ -4736,24 +4756,24 @@ impl Renderer {
                 .unwrap();
         }
 
         PlainTexture {
             data: short_path,
             size: (rect.size.width, rect.size.height, texture.get_layer_count()),
             format: texture.get_format(),
             filter: texture.get_filter(),
-            render_target: texture.get_render_target(),
         }
     }
 
     #[cfg(feature = "replay")]
     fn load_texture(
         target: TextureTarget,
         plain: &PlainTexture,
+        rt_info: Option<RenderTargetInfo>,
         root: &PathBuf,
         device: &mut Device
     ) -> (Texture, Vec<u8>)
     {
         use std::fs::File;
         use std::io::Read;
 
         let mut texels = Vec::new();
@@ -4763,17 +4783,17 @@ impl Renderer {
             .unwrap();
 
         let texture = device.create_texture(
             target,
             plain.format,
             plain.size.0,
             plain.size.1,
             plain.filter,
-            plain.render_target,
+            rt_info,
             plain.size.2,
         );
         device.upload_texture_immediate(&texture, &texels);
 
         (texture, texels)
     }
 
     #[cfg(feature = "capture")]
@@ -4934,29 +4954,31 @@ impl Renderer {
             for (_id, texture) in self.texture_resolver.texture_cache_map.drain() {
                 self.device.delete_texture(texture);
             }
             for (id, texture) in renderer.textures {
                 info!("\t{}", texture.data);
                 let t = Self::load_texture(
                     TextureTarget::Array,
                     &texture,
+                    Some(RenderTargetInfo { has_depth: false }),
                     &root,
                     &mut self.device
                 );
                 self.texture_resolver.texture_cache_map.insert(id, t.0);
             }
 
             info!("loading gpu cache");
             if let Some(t) = self.gpu_cache_texture.texture.take() {
                 self.device.delete_texture(t);
             }
             let (t, gpu_cache_data) = Self::load_texture(
                 TextureTarget::Default,
                 &renderer.gpu_cache,
+                Some(RenderTargetInfo { has_depth: false }),
                 &root,
                 &mut self.device,
             );
             self.gpu_cache_texture.texture = Some(t);
             match self.gpu_cache_texture.bus {
                 GpuCacheBus::PixelBuffer { ref mut rows, ref mut cpu_blocks, .. } => {
                     let dim = self.gpu_cache_texture.texture.as_ref().unwrap().get_dimensions();
                     let blocks = unsafe {
@@ -4991,21 +5013,21 @@ impl Renderer {
                     Entry::Vacant(e) => {
                         //TODO: provide a way to query both the layer count and the filter from external images
                         let (layer_count, filter) = (1, TextureFilter::Linear);
                         let plain_tex = PlainTexture {
                             data: e.key().clone(),
                             size: (descriptor.size.width, descriptor.size.height, layer_count),
                             format: descriptor.format,
                             filter,
-                            render_target: None,
                         };
                         let t = Self::load_texture(
                             target,
                             &plain_tex,
+                            None,
                             &root,
                             &mut self.device
                         );
                         let extex = t.0.into_external();
                         self.owned_external_images.insert(key, extex.clone());
                         e.insert(extex.internal_id()).clone()
                     }
                 };
--- a/gfx/webrender/src/spatial_node.rs
+++ b/gfx/webrender/src/spatial_node.rs
@@ -117,17 +117,16 @@ impl SpatialNode {
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
         pipeline_id: PipelineId,
     ) -> Self {
         let identity = LayoutTransform::identity();
         let source_perspective = source_perspective.map_or_else(
             LayoutFastTransform::identity, |perspective| perspective.into());
         let info = ReferenceFrameInfo {
-            resolved_transform: LayoutFastTransform::identity(),
             source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
             source_perspective,
             origin_in_parent_reference_frame,
             invertible: true,
         };
         Self::new(pipeline_id, parent_index, SpatialNodeType:: ReferenceFrame(info))
     }
 
@@ -251,26 +250,26 @@ impl SpatialNode {
             SpatialNodeType::ReferenceFrame(ref mut info) => {
                 // Resolve the transform against any property bindings.
                 let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
                 // Do a change-basis operation on the perspective matrix using
                 // the scroll offset.
                 let scrolled_perspective = info.source_perspective
                     .pre_translate(&state.parent_accumulated_scroll_offset)
                     .post_translate(-state.parent_accumulated_scroll_offset);
-                info.resolved_transform =
+                let resolved_transform =
                     LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
                     .pre_mul(&source_transform.into())
                     .pre_mul(&scrolled_perspective);
 
                 // The transformation for this viewport in world coordinates is the transformation for
                 // our parent reference frame, plus any accumulated scrolling offsets from nodes
                 // between our reference frame and this node. Finally, we also include
                 // whatever local transformation this reference frame provides.
-                let relative_transform = info.resolved_transform
+                let relative_transform = resolved_transform
                     .post_translate(state.parent_accumulated_scroll_offset)
                     .to_transform()
                     .with_destination::<LayoutPixel>();
                 self.world_viewport_transform =
                     state.parent_reference_frame_transform.pre_mul(&relative_transform.into());
                 self.world_content_transform = self.world_viewport_transform;
 
                 info.invertible = self.world_viewport_transform.is_invertible();
@@ -617,20 +616,16 @@ impl ScrollFrameInfo {
             external_id: self.external_id,
         }
     }
 }
 
 /// Contains information about reference frames.
 #[derive(Copy, Clone, Debug)]
 pub struct ReferenceFrameInfo {
-    /// The transformation that establishes this reference frame, relative to the parent
-    /// reference frame. The origin of the reference frame is included in the transformation.
-    pub resolved_transform: LayoutFastTransform,
-
     /// The source transform and perspective matrices provided by the stacking context
     /// that forms this reference frame. We maintain the property binding information
     /// here so that we can resolve the animated transform and update the tree each
     /// frame.
     pub source_transform: PropertyBinding<LayoutTransform>,
     pub source_perspective: LayoutFastTransform,
 
     /// The original, not including the transform and relative to the parent reference frame,
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -4,17 +4,17 @@
 
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{ExternalImageType, ImageData, ImageFormat};
 use api::ImageDescriptor;
 use device::TextureFilter;
 use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use gpu_types::{ImageSource, UvRectKind};
-use internal_types::{CacheTextureId, TextureUpdateList, TextureUpdateSource};
+use internal_types::{CacheTextureId, LayerIndex, TextureUpdateList, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, TextureSource, TextureUpdate, TextureUpdateOp};
 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use resource_cache::CacheItem;
 use std::cell::Cell;
 use std::cmp;
 use std::mem;
 use std::rc::Rc;
@@ -536,22 +536,24 @@ impl TextureCache {
                     uv_rect: DeviceUintRect::new(origin, entry.size),
                     texture_layer: layer_index as i32,
                 }
             }
             None => panic!("BUG: handle not requested earlier in frame"),
         }
     }
 
-    // A more detailed version of get(). This allows access to the actual
-    // device rect of the cache allocation.
+    /// A more detailed version of get(). This allows access to the actual
+    /// device rect of the cache allocation.
+    ///
+    /// Returns a tuple identifying the texture, the layer, and the region.
     pub fn get_cache_location(
         &self,
         handle: &TextureCacheHandle,
-    ) -> (CacheTextureId, i32, DeviceUintRect) {
+    ) -> (CacheTextureId, LayerIndex, DeviceUintRect) {
         let handle = handle
             .entry
             .as_ref()
             .expect("BUG: handle not requested earlier in frame");
 
         let entry = self.entries
             .get_opt(handle)
             .expect("BUG: was dropped from cache or not updated!");
@@ -562,17 +564,17 @@ impl TextureCache {
             }
             EntryKind::Cache {
                 layer_index,
                 origin,
                 ..
             } => (layer_index, origin),
         };
         (entry.texture_id,
-         layer_index as i32,
+         layer_index as usize,
          DeviceUintRect::new(origin, entry.size))
     }
 
     pub fn mark_unused(&mut self, handle: &TextureCacheHandle) {
         if let Some(ref handle) = handle.entry {
             if let Some(entry) = self.entries.get_opt_mut(handle) {
                 // Set a very low last accessed frame to make it very likely that this entry
                 // will get cleaned up next time we try to expire entries.
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -280,20 +280,18 @@ impl<T: RenderTarget> RenderTargetList<T
 
     pub fn needs_depth(&self) -> bool {
         self.targets.iter().any(|target| target.needs_depth())
     }
 
     pub fn check_ready(&self, t: &Texture) {
         assert_eq!(t.get_dimensions(), self.max_size);
         assert_eq!(t.get_format(), self.format);
-        assert_eq!(t.get_render_target_layer_count(), self.targets.len());
         assert_eq!(t.get_layer_count() as usize, self.targets.len());
-        assert_eq!(t.has_depth(), t.get_rt_info().unwrap().has_depth);
-        assert_eq!(t.has_depth(), self.needs_depth());
+        assert!(t.supports_depth() >= self.needs_depth());
     }
 }
 
 /// Frame output information for a given pipeline ID.
 /// Storing the task ID allows the renderer to find
 /// the target rect within the render target that this
 /// pipeline exists at.
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -816,17 +814,17 @@ impl TextureCacheRenderTarget {
 pub enum RenderPassKind {
     /// The final pass to the main frame buffer, where we have a single color
     /// target for display to the user.
     MainFramebuffer(ColorRenderTarget),
     /// An intermediate pass, where we may have multiple targets.
     OffScreen {
         alpha: RenderTargetList<AlphaRenderTarget>,
         color: RenderTargetList<ColorRenderTarget>,
-        texture_cache: FastHashMap<(CacheTextureId, i32), TextureCacheRenderTarget>,
+        texture_cache: FastHashMap<(CacheTextureId, usize), TextureCacheRenderTarget>,
     },
 }
 
 /// A render pass represents a set of rendering operations that don't depend on one
 /// another.
 ///
 /// A render pass can have several render targets if there wasn't enough space in one
 /// target to do all of the rendering for that pass. See `RenderTargetList`.
@@ -945,18 +943,18 @@ impl RenderPass {
                 for &task_id in &self.tasks {
                     let (target_kind, texture_target) = {
                         let task = &mut render_tasks[task_id];
                         let target_kind = task.target_kind();
 
                         // Find a target to assign this task to, or create a new
                         // one if required.
                         let texture_target = match task.location {
-                            RenderTaskLocation::TextureCache(texture_id, layer, _) => {
-                                Some((texture_id, layer))
+                            RenderTaskLocation::TextureCache { texture, layer, .. } => {
+                                Some((texture, layer))
                             }
                             RenderTaskLocation::Fixed(..) => {
                                 None
                             }
                             RenderTaskLocation::Dynamic(ref mut origin, size) => {
                                 let alloc_size = DeviceUintSize::new(size.width as u32, size.height as u32);
                                 let (alloc_origin, target_index) =  match target_kind {
                                     RenderTargetKind::Color => color.allocate(alloc_size),
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -227,17 +227,17 @@ impl Transaction {
         self.frame_ops.push(FrameMsg::ScrollNodeWithId(origin, id, clamp));
     }
 
     pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) {
         self.scene_ops.push(SceneMsg::SetPageZoom(page_zoom));
     }
 
     pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) {
-        self.scene_ops.push(SceneMsg::SetPinchZoom(pinch_zoom));
+        self.frame_ops.push(FrameMsg::SetPinchZoom(pinch_zoom));
     }
 
     pub fn set_pan(&mut self, pan: DeviceIntPoint) {
         self.frame_ops.push(FrameMsg::SetPan(pan));
     }
 
     /// Generate a new frame. When it's done and a RenderNotifier has been set
     /// in `webrender::Renderer`, [new_frame_ready()][notifier] gets called.
@@ -518,17 +518,16 @@ pub struct AddFontInstance {
     pub variations: Vec<FontVariation>,
 }
 
 // Frame messages affect building the scene.
 #[derive(Clone, Deserialize, Serialize)]
 pub enum SceneMsg {
     UpdateEpoch(PipelineId, Epoch),
     SetPageZoom(ZoomFactor),
-    SetPinchZoom(ZoomFactor),
     SetRootPipeline(PipelineId),
     RemovePipeline(PipelineId),
     SetDisplayList {
         list_descriptor: BuiltDisplayListDescriptor,
         epoch: Epoch,
         pipeline_id: PipelineId,
         background: Option<ColorF>,
         viewport_size: LayoutSize,
@@ -549,25 +548,25 @@ pub enum FrameMsg {
     HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetPan(DeviceIntPoint),
     EnableFrameOutput(PipelineId, bool),
     Scroll(ScrollLocation, WorldPoint),
     ScrollNodeWithId(LayoutPoint, ExternalScrollId, ScrollClamping),
     GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     UpdateDynamicProperties(DynamicProperties),
     AppendDynamicProperties(DynamicProperties),
+    SetPinchZoom(ZoomFactor),
 }
 
 impl fmt::Debug for SceneMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
             SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
             SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
-            SceneMsg::SetPinchZoom(..) => "SceneMsg::SetPinchZoom",
             SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline",
             SceneMsg::SetWindowParameters { .. } => "SceneMsg::SetWindowParameters",
             SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline",
         })
     }
 }
 
 impl fmt::Debug for FrameMsg {
@@ -577,16 +576,17 @@ impl fmt::Debug for FrameMsg {
             FrameMsg::HitTest(..) => "FrameMsg::HitTest",
             FrameMsg::SetPan(..) => "FrameMsg::SetPan",
             FrameMsg::Scroll(..) => "FrameMsg::Scroll",
             FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId",
             FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState",
             FrameMsg::EnableFrameOutput(..) => "FrameMsg::EnableFrameOutput",
             FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
             FrameMsg::AppendDynamicProperties(..) => "FrameMsg::AppendDynamicProperties",
+            FrameMsg::SetPinchZoom(..) => "FrameMsg::SetPinchZoom",
         })
     }
 }
 
 bitflags!{
     /// Bit flags for WR stages to store in a capture.
     // Note: capturing `FRAME` without `SCENE` is not currently supported.
     #[derive(Deserialize, Serialize)]
@@ -786,16 +786,17 @@ pub struct MemoryReport {
     pub rasterized_blobs: usize,
     //
     // GPU memory.
     //
     pub gpu_cache_textures: usize,
     pub vertex_data_textures: usize,
     pub render_target_textures: usize,
     pub texture_cache_textures: usize,
+    pub depth_target_textures: usize,
 }
 
 impl ::std::ops::AddAssign for MemoryReport {
     fn add_assign(&mut self, other: MemoryReport) {
         self.primitive_stores += other.primitive_stores;
         self.clip_stores += other.clip_stores;
         self.gpu_cache_metadata += other.gpu_cache_metadata;
         self.gpu_cache_cpu_mirror += other.gpu_cache_cpu_mirror;
@@ -803,16 +804,17 @@ impl ::std::ops::AddAssign for MemoryRep
         self.hit_testers += other.hit_testers;
         self.fonts += other.fonts;
         self.images += other.images;
         self.rasterized_blobs += other.rasterized_blobs;
         self.gpu_cache_textures += other.gpu_cache_textures;
         self.vertex_data_textures += other.vertex_data_textures;
         self.render_target_textures += other.render_target_textures;
         self.texture_cache_textures += other.texture_cache_textures;
+        self.depth_target_textures += other.depth_target_textures;
     }
 }
 
 /// A C function that takes a pointer to a heap allocation and returns its size.
 ///
 /// This is borrowed from the malloc_size_of crate, upon which we want to avoid
 /// a dependency from WebRender.
 pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -430,16 +430,39 @@ pub enum BorderStyle {
     Inset = 8,
     Outset = 9,
 }
 
 impl BorderStyle {
     pub fn is_hidden(&self) -> bool {
         *self == BorderStyle::Hidden || *self == BorderStyle::None
     }
+
+    /// Returns true if the border style itself is opaque. Other
+    /// factors (such as color, or border radii) may mean that
+    /// the border segment isn't opaque regardless of this.
+    pub fn is_opaque(&self) -> bool {
+        match *self {
+            BorderStyle::None |
+            BorderStyle::Double |
+            BorderStyle::Dotted |
+            BorderStyle::Dashed |
+            BorderStyle::Hidden => {
+                false
+            }
+
+            BorderStyle::Solid |
+            BorderStyle::Groove |
+            BorderStyle::Ridge |
+            BorderStyle::Inset |
+            BorderStyle::Outset => {
+                true
+            }
+        }
+    }
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum BoxShadowClipMode {
     Outset = 0,
     Inset = 1,
 }
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-98d507003c07c003ef0e0297dc4d29ee896a5868
+a0a36d9b416ca3295f8def384814ffef60903a60