Bug 1499494. Update webrender to commit a0a36d9b416ca3295f8def384814ffef60903a60
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Wed, 17 Oct 2018 12:47:25 -0400
changeset 490008 a4a26e661fa517b4304e4a0d8e5f4d4e8bbba684
parent 490007 16ee6006e57cce243c85a5ab7578b43f3a084213
child 490009 a1f350ca24173934b0633bfff039d672d0187bbb
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
bugs1499494
milestone64.0a1
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