Bug 1510074 - Update webrender to commit 604c69a40920e34bb1b8fa3f02bca6e5edfe73f4 (WR PR #3352). r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Tue, 27 Nov 2018 12:11:06 +0000
changeset 507463 2f8233496ee8d83688e27d266e5dd61ba29ad5b1
parent 507462 99fb2b79579902eb3365c87202ad3deea9c484af
child 507464 c19baae766e96ffbc7bf20534e1ba62b64871fe3
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1510074
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1510074 - Update webrender to commit 604c69a40920e34bb1b8fa3f02bca6e5edfe73f4 (WR PR #3352). r=kats https://github.com/servo/webrender/pull/3352 Differential Revision: https://phabricator.services.mozilla.com/D13056
gfx/webrender_bindings/revision.txt
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/border.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store.rs
gfx/wr/webrender/src/surface.rs
gfx/wr/wrench/reftests/gradient/premultiplied-aligned-2.png
gfx/wr/wrench/reftests/gradient/premultiplied-angle-2.png
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-235273012e08230c07a214e907175c535206098d
+604c69a40920e34bb1b8fa3f02bca6e5edfe73f4
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -903,16 +903,17 @@ impl AlphaBatchBuilder {
                                 PrimitiveInstanceKind::LineDecoration { .. } |
                                 PrimitiveInstanceKind::TextRun { .. } |
                                 PrimitiveInstanceKind::LegacyPrimitive { .. } |
                                 PrimitiveInstanceKind::NormalBorder { .. } |
                                 PrimitiveInstanceKind::ImageBorder { .. } |
                                 PrimitiveInstanceKind::Rectangle { .. } |
                                 PrimitiveInstanceKind::YuvImage { .. } |
                                 PrimitiveInstanceKind::Image { .. } |
+                                PrimitiveInstanceKind::LinearGradient { .. } |
                                 PrimitiveInstanceKind::Clear => {
                                     unreachable!();
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[pic_index.0];
 
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
@@ -1405,18 +1406,17 @@ impl AlphaBatchBuilder {
             ) => {
                 let prim = &ctx.prim_store.primitives[prim_index.0];
 
                 // If the primitive is internally decomposed into multiple sub-primitives we may not
                 // use some of the per-primitive data and get it from each sub-primitive instead.
                 let is_multiple_primitives = match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         match brush.kind {
-                            BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
-                            BrushKind::RadialGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                            BrushKind::RadialGradient { visible_tiles_range, .. } => !visible_tiles_range.is_empty(),
                         }
                     }
                 };
 
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
@@ -1445,32 +1445,19 @@ impl AlphaBatchBuilder {
                         };
 
                         if prim_instance.is_chased() {
                             println!("\ttask target {:?}", self.target_rect);
                             println!("\t{:?}", prim_header);
                         }
 
                         match brush.kind {
-                            BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
-                                add_gradient_tiles(
-                                    visible_tiles,
-                                    stops_handle,
-                                    BrushBatchKind::LinearGradient,
-                                    specified_blend_mode,
-                                    bounding_rect,
-                                    clip_task_address,
-                                    gpu_cache,
-                                    &mut self.batch_list,
-                                    &prim_header,
-                                    prim_headers,
-                                    z_id,
-                                );
-                            }
-                            BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                            BrushKind::RadialGradient { ref stops_handle, visible_tiles_range, .. } if !visible_tiles_range.is_empty() => {
+                                let visible_tiles = &ctx.scratch.gradient_tiles[visible_tiles_range];
+
                                 add_gradient_tiles(
                                     visible_tiles,
                                     stops_handle,
                                     BrushBatchKind::RadialGradient,
                                     specified_blend_mode,
                                     bounding_rect,
                                     clip_task_address,
                                     gpu_cache,
@@ -1889,16 +1876,99 @@ impl AlphaBatchBuilder {
                                 tile.edge_flags,
                                 uv_rect_address,
                                 z_id,
                             );
                         }
                     }
                 }
             }
+            (
+                PrimitiveInstanceKind::LinearGradient { visible_tiles_range, .. },
+                PrimitiveTemplateKind::LinearGradient { stops_handle, ref brush_segments, .. }
+            ) => {
+                let specified_blend_mode = BlendMode::PremultipliedAlpha;
+
+                let mut prim_header = PrimitiveHeader {
+                    local_rect: prim_data.prim_rect,
+                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    task_address,
+                    specific_prim_address: GpuCacheAddress::invalid(),
+                    clip_task_address,
+                    transform_id,
+                };
+
+                if visible_tiles_range.is_empty() {
+                    let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        transform_kind == TransformedRectKind::Complex
+                    {
+                        specified_blend_mode
+                    } else {
+                        BlendMode::None
+                    };
+
+                    let batch_params = BrushBatchParameters::shared(
+                        BrushBatchKind::LinearGradient,
+                        BatchTextures::no_texture(),
+                        [
+                            stops_handle.as_int(gpu_cache),
+                            0,
+                            0,
+                        ],
+                        0,
+                    );
+
+                    prim_header.specific_prim_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
+
+                    let prim_header_index = prim_headers.push(
+                        &prim_header,
+                        z_id,
+                        batch_params.prim_user_data,
+                    );
+
+                    let segments = if brush_segments.is_empty() {
+                        None
+                    } else {
+                        Some(brush_segments.as_slice())
+                    };
+
+                    self.add_segmented_prim_to_batch(
+                        segments,
+                        prim_data.opacity,
+                        &batch_params,
+                        specified_blend_mode,
+                        non_segmented_blend_mode,
+                        prim_header_index,
+                        clip_task_address,
+                        bounding_rect,
+                        transform_kind,
+                        render_tasks,
+                        z_id,
+                        prim_instance.clip_task_index,
+                        ctx,
+                    );
+                } else {
+                    let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
+
+                    add_gradient_tiles(
+                        visible_tiles,
+                        stops_handle,
+                        BrushBatchKind::LinearGradient,
+                        specified_blend_mode,
+                        bounding_rect,
+                        clip_task_address,
+                        gpu_cache,
+                        &mut self.batch_list,
+                        &prim_header,
+                        prim_headers,
+                        z_id,
+                    );
+                }
+            }
             _ => {
                 unreachable!();
             }
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
@@ -2238,28 +2308,16 @@ impl BrushPrimitive {
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
                     0,
                 ))
             }
-            BrushKind::LinearGradient { ref stops_handle, .. } => {
-                Some(BrushBatchParameters::shared(
-                    BrushBatchKind::LinearGradient,
-                    BatchTextures::no_texture(),
-                    [
-                        stops_handle.as_int(gpu_cache),
-                        0,
-                        0,
-                    ],
-                    0,
-                ))
-            }
         }
     }
 }
 
 impl PrimitiveInstance {
     pub fn is_cacheable(
         &self,
         prim_data_store: &PrimitiveDataStore,
@@ -2281,16 +2339,17 @@ impl PrimitiveInstance {
             }
             PrimitiveInstanceKind::LegacyPrimitive { .. } |
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Rectangle { .. } |
+            PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::Clear => {
                 return true;
             }
         };
         match resource_cache.get_image_properties(image_key) {
             Some(ImageProperties { external_image: Some(_), .. }) => {
                 false
             }
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
@@ -2,20 +2,20 @@
  * 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, LayoutRect, LayoutSize, NormalBorder, DeviceIntSize};
 use api::{AuHelpers, LayoutPoint, RepeatMode, TexelRect};
 use ellipse::Ellipse;
-use euclid::{SideOffsets2D, vec2};
+use euclid::vec2;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
-use prim_store::{BorderSegmentInfo, BrushSegment};
+use prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 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.
@@ -1123,181 +1123,176 @@ pub fn build_border_instances(
         widths,
         radius,
         border.do_aa,
     );
 
     instances
 }
 
-pub fn create_nine_patch_segments(
-    rect: &LayoutRect,
-    widths: &LayoutSideOffsets,
-    width: i32,
-    height: i32,
-    slice: SideOffsets2D<i32>,
-    fill: bool,
-    repeat_horizontal: RepeatMode,
-    repeat_vertical: RepeatMode,
-    outset: SideOffsets2D<f32>,
-) -> Vec<BrushSegment> {
-    // Calculate the modified rect as specific by border-image-outset
-    let origin = LayoutPoint::new(
-        rect.origin.x - outset.left,
-        rect.origin.y - outset.top,
-    );
-    let size = LayoutSize::new(
-        rect.size.width + outset.left + outset.right,
-        rect.size.height + outset.top + outset.bottom,
-    );
-    let rect = LayoutRect::new(origin, size);
-
-    // Calculate the local texel coords of the slices.
-    let px0 = 0.0;
-    let px1 = slice.left as f32;
-    let px2 = width as f32 - slice.right as f32;
-    let px3 = width as f32;
+impl NinePatchDescriptor {
+    pub fn create_segments(
+        &self,
+        rect: &LayoutRect,
+    ) -> Vec<BrushSegment> {
+        // Calculate the modified rect as specific by border-image-outset
+        let origin = LayoutPoint::new(
+            rect.origin.x - self.outset.left,
+            rect.origin.y - self.outset.top,
+        );
+        let size = LayoutSize::new(
+            rect.size.width + self.outset.left + self.outset.right,
+            rect.size.height + self.outset.top + self.outset.bottom,
+        );
+        let rect = LayoutRect::new(origin, size);
 
-    let py0 = 0.0;
-    let py1 = slice.top as f32;
-    let py2 = height as f32 - slice.bottom as f32;
-    let py3 = height as f32;
+        // Calculate the local texel coords of the slices.
+        let px0 = 0.0;
+        let px1 = self.slice.left as f32;
+        let px2 = self.width as f32 - self.slice.right as f32;
+        let px3 = self.width as f32;
 
-    let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y);
-    let tl_inner = tl_outer + vec2(widths.left, widths.top);
-
-    let tr_outer = LayoutPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-    let tr_inner = tr_outer + vec2(-widths.right, widths.top);
-
-    let bl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-    let bl_inner = bl_outer + vec2(widths.left, -widths.bottom);
+        let py0 = 0.0;
+        let py1 = self.slice.top as f32;
+        let py2 = self.height as f32 - self.slice.bottom as f32;
+        let py3 = self.height as f32;
 
-    let br_outer = LayoutPoint::new(
-        rect.origin.x + rect.size.width,
-        rect.origin.y + rect.size.height,
-    );
-    let br_inner = br_outer - vec2(widths.right, widths.bottom);
+        let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y);
+        let tl_inner = tl_outer + vec2(self.widths.left, self.widths.top);
+
+        let tr_outer = LayoutPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+        let tr_inner = tr_outer + vec2(-self.widths.right, self.widths.top);
 
-    fn add_segment(
-        segments: &mut Vec<BrushSegment>,
-        rect: LayoutRect,
-        uv_rect: TexelRect,
-        repeat_horizontal: RepeatMode,
-        repeat_vertical: RepeatMode
-    ) {
-        if uv_rect.uv1.x > uv_rect.uv0.x &&
-           uv_rect.uv1.y > uv_rect.uv0.y {
+        let bl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+        let bl_inner = bl_outer + vec2(self.widths.left, -self.widths.bottom);
 
-            // Use segment relative interpolation for all
-            // instances in this primitive.
-            let mut brush_flags =
-                BrushFlags::SEGMENT_RELATIVE |
-                BrushFlags::SEGMENT_TEXEL_RECT;
+        let br_outer = LayoutPoint::new(
+            rect.origin.x + rect.size.width,
+            rect.origin.y + rect.size.height,
+        );
+        let br_inner = br_outer - vec2(self.widths.right, self.widths.bottom);
 
-            // 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;
-            }
+        fn add_segment(
+            segments: &mut Vec<BrushSegment>,
+            rect: LayoutRect,
+            uv_rect: TexelRect,
+            repeat_horizontal: RepeatMode,
+            repeat_vertical: RepeatMode
+        ) {
+            if uv_rect.uv1.x > uv_rect.uv0.x &&
+               uv_rect.uv1.y > uv_rect.uv0.y {
 
-            let segment = BrushSegment::new(
-                rect,
-                true,
-                EdgeAaSegmentMask::empty(),
-                [
-                    uv_rect.uv0.x,
-                    uv_rect.uv0.y,
-                    uv_rect.uv1.x,
-                    uv_rect.uv1.y,
-                ],
-                brush_flags,
-            );
-
-            segments.push(segment);
-        }
-    }
-
-    // Build the list of image segments
-    let mut segments = Vec::new();
+                // Use segment relative interpolation for all
+                // instances in this primitive.
+                let mut brush_flags =
+                    BrushFlags::SEGMENT_RELATIVE |
+                    BrushFlags::SEGMENT_TEXEL_RECT;
 
-    // Top left
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
-        TexelRect::new(px0, py0, px1, py1),
-        RepeatMode::Stretch,
-        RepeatMode::Stretch
-    );
-    // Top right
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
-        TexelRect::new(px2, py0, px3, py1),
-        RepeatMode::Stretch,
-        RepeatMode::Stretch
-    );
-    // Bottom right
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
-        TexelRect::new(px2, py2, px3, py3),
-        RepeatMode::Stretch,
-        RepeatMode::Stretch
-    );
-    // Bottom left
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
-        TexelRect::new(px0, py2, px1, py3),
-        RepeatMode::Stretch,
-        RepeatMode::Stretch
-    );
+                // 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;
+                }
 
-    // Center
-    if fill {
+                let segment = BrushSegment::new(
+                    rect,
+                    true,
+                    EdgeAaSegmentMask::empty(),
+                    [
+                        uv_rect.uv0.x,
+                        uv_rect.uv0.y,
+                        uv_rect.uv1.x,
+                        uv_rect.uv1.y,
+                    ],
+                    brush_flags,
+                );
+
+                segments.push(segment);
+            }
+        }
+
+        // Build the list of image segments
+        let mut segments = Vec::new();
+
+        // Top left
         add_segment(
             &mut segments,
-            LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
-            TexelRect::new(px1, py1, px2, py2),
-            repeat_horizontal,
-            repeat_vertical
+            LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+            TexelRect::new(px0, py0, px1, py1),
+            RepeatMode::Stretch,
+            RepeatMode::Stretch
+        );
+        // Top right
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+            TexelRect::new(px2, py0, px3, py1),
+            RepeatMode::Stretch,
+            RepeatMode::Stretch
         );
-    }
-
-    // Add edge segments.
+        // Bottom right
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+            TexelRect::new(px2, py2, px3, py3),
+            RepeatMode::Stretch,
+            RepeatMode::Stretch
+        );
+        // Bottom left
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+            TexelRect::new(px0, py2, px1, py3),
+            RepeatMode::Stretch,
+            RepeatMode::Stretch
+        );
 
-    // Top
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
-        TexelRect::new(px1, py0, px2, py1),
-        repeat_horizontal,
-        RepeatMode::Stretch,
-    );
-    // Bottom
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
-        TexelRect::new(px1, py2, px2, py3),
-        repeat_horizontal,
-        RepeatMode::Stretch,
-    );
-    // Left
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
-        TexelRect::new(px0, py1, px1, py2),
-        RepeatMode::Stretch,
-        repeat_vertical,
-    );
-    // Right
-    add_segment(
-        &mut segments,
-        LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
-        TexelRect::new(px2, py1, px3, py2),
-        RepeatMode::Stretch,
-        repeat_vertical,
-    );
+        // Center
+        if self.fill {
+            add_segment(
+                &mut segments,
+                LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
+                TexelRect::new(px1, py1, px2, py2),
+                self.repeat_horizontal,
+                self.repeat_vertical
+            );
+        }
+
+        // Add edge segments.
 
-    segments
+        // Top
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+            TexelRect::new(px1, py0, px2, py1),
+            self.repeat_horizontal,
+            RepeatMode::Stretch,
+        );
+        // Bottom
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+            TexelRect::new(px1, py2, px2, py3),
+            self.repeat_horizontal,
+            RepeatMode::Stretch,
+        );
+        // Left
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+            TexelRect::new(px0, py1, px1, py2),
+            RepeatMode::Stretch,
+            self.repeat_vertical,
+        );
+        // Right
+        add_segment(
+            &mut segments,
+            LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+            TexelRect::new(px2, py1, px3, py2),
+            RepeatMode::Stretch,
+            self.repeat_vertical,
+        );
+
+        segments
+    }
 }
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -8,30 +8,29 @@ use api::{ClipId, ColorF, ComplexClipReg
 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, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
-use border::create_nine_patch_segments;
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemSceneData};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveInstance, PrimitiveDataInterner, PrimitiveKeyKind};
-use prim_store::{PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind};
+use prim_store::{PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind, GradientStopKey, NinePatchDescriptor};
 use prim_store::{PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats, BrushSegmentDescriptor};
-use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id};
+use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, GradientTileRange};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use smallvec::SmallVec;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
@@ -576,28 +575,33 @@ impl<'a> DisplayListFlattener<'a> {
                     &prim_info,
                     info.wavy_line_thickness,
                     info.orientation,
                     &info.color,
                     info.style,
                 );
             }
             SpecificDisplayItem::Gradient(ref info) => {
-                if let Some(brush_kind) = self.create_brush_kind_for_gradient(
+                if let Some(prim) = self.create_linear_gradient_prim(
                     &prim_info,
                     info.gradient.start_point,
                     info.gradient.end_point,
                     item.gradient_stops(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                     pipeline_id,
+                    None,
                 ) {
-                    let prim = PrimitiveContainer::Brush(BrushPrimitive::new(brush_kind, None));
-                    self.add_primitive(clip_and_scroll, &prim_info, Vec::new(), prim);
+                    self.add_primitive(
+                        clip_and_scroll,
+                        &prim_info,
+                        Vec::new(),
+                        prim,
+                    );
                 }
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 let brush_kind = self.create_brush_kind_for_radial_gradient(
                     &prim_info,
                     info.gradient.center,
                     info.gradient.start_offset * info.gradient.radius.width,
                     info.gradient.end_offset * info.gradient.radius.width,
@@ -1733,93 +1737,68 @@ impl<'a> DisplayListFlattener<'a> {
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         border_item: &BorderDisplayItem,
         gradient_stops: ItemRange<GradientStop>,
         pipeline_id: PipelineId,
     ) {
         match border_item.details {
             BorderDetails::NinePatch(ref border) => {
+                let nine_patch = NinePatchDescriptor {
+                    width: border.width,
+                    height: border.height,
+                    slice: border.slice,
+                    fill: border.fill,
+                    repeat_horizontal: border.repeat_horizontal,
+                    repeat_vertical: border.repeat_vertical,
+                    outset: border.outset.into(),
+                    widths: border_item.widths.into(),
+                };
+
                 let prim = match border.source {
                     NinePatchBorderSource::Image(image_key) => {
                         PrimitiveContainer::ImageBorder {
                             request: ImageRequest {
                                 key: image_key,
                                 rendering: ImageRendering::Auto,
                                 tile: None,
                             },
-                            widths: border_item.widths,
-                            width: border.width,
-                            height: border.height,
-                            slice: border.slice,
-                            fill: border.fill,
-                            repeat_horizontal: border.repeat_horizontal,
-                            repeat_vertical: border.repeat_vertical,
-                            outset: border.outset,
+                            nine_patch,
                         }
                     }
                     NinePatchBorderSource::Gradient(gradient) => {
-                        match self.create_brush_kind_for_gradient(
+                        match self.create_linear_gradient_prim(
                             &info,
                             gradient.start_point,
                             gradient.end_point,
                             gradient_stops,
                             gradient.extend_mode,
                             LayoutSize::new(border.height as f32, border.width as f32),
                             LayoutSize::zero(),
                             pipeline_id,
+                            Some(Box::new(nine_patch)),
                         ) {
-                            Some(brush_kind) => {
-                                let segments = create_nine_patch_segments(
-                                    &info.rect,
-                                    &border_item.widths,
-                                    border.width,
-                                    border.height,
-                                    border.slice,
-                                    border.fill,
-                                    border.repeat_horizontal,
-                                    border.repeat_vertical,
-                                    border.outset,
-                                );
-
-                                let descriptor = BrushSegmentDescriptor {
-                                    segments: SmallVec::from_vec(segments),
-                                };
-
-                                PrimitiveContainer::Brush(
-                                    BrushPrimitive::new(brush_kind, Some(descriptor))
-                                )
-                            }
+                            Some(prim) => prim,
                             None => return,
                         }
                     }
                     NinePatchBorderSource::RadialGradient(gradient) => {
                         let brush_kind = self.create_brush_kind_for_radial_gradient(
                             &info,
                             gradient.center,
                             gradient.start_offset * gradient.radius.width,
                             gradient.end_offset * gradient.radius.width,
                             gradient.radius.width / gradient.radius.height,
                             gradient_stops,
                             gradient.extend_mode,
                             LayoutSize::new(border.height as f32, border.width as f32),
                             LayoutSize::zero(),
                         );
 
-                        let segments = create_nine_patch_segments(
-                            &info.rect,
-                            &border_item.widths,
-                            border.width,
-                            border.height,
-                            border.slice,
-                            border.fill,
-                            border.repeat_horizontal,
-                            border.repeat_vertical,
-                            border.outset,
-                        );
+                        let segments = nine_patch.create_segments(&info.rect);
 
                         let descriptor = BrushSegmentDescriptor {
                             segments: SmallVec::from_vec(segments),
                         };
 
                         PrimitiveContainer::Brush(
                             BrushPrimitive::new(brush_kind, Some(descriptor))
                         )
@@ -1839,53 +1818,51 @@ impl<'a> DisplayListFlattener<'a> {
                     border,
                     border_item.widths,
                     clip_and_scroll,
                 );
             }
         }
     }
 
-    pub fn create_brush_kind_for_gradient(
+    pub fn create_linear_gradient_prim(
         &mut self,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
         pipeline_id: PipelineId,
-    ) -> Option<BrushKind> {
+        nine_patch: Option<Box<NinePatchDescriptor>>,
+    ) -> Option<PrimitiveContainer> {
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
 
         // TODO(gw): It seems like we should be able to look this up once in
         //           flatten_root() and pass to all children here to avoid
         //           some hash lookups?
         let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
+        let mut max_alpha: f32 = 0.0;
 
-        let mut max_alpha: f32 = 0.0;
-        let mut min_alpha: f32 = 1.0;
-        for stop in display_list.get(stops) {
+        let stops = display_list.get(stops).map(|stop| {
             max_alpha = max_alpha.max(stop.color.a);
-            min_alpha = min_alpha.min(stop.color.a);
-        }
+            GradientStopKey {
+                offset: stop.offset,
+                color: stop.color.into(),
+            }
+        }).collect();
 
         // If all the stops have no alpha, then this
         // gradient can't contribute to the scene.
         if max_alpha <= 0.0 {
             return None;
         }
 
-        // Save opacity of the stops for use in
-        // selecting which pass this gradient
-        // should be drawn in.
-        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
-
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
         // are also supplied in reverse that the rendered result will be equivalent. To do this,
         // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
         // just designate the reference orientation as start < end. Aligned gradient rendering
         // manages to produce the same result regardless of orientation, so don't worry about
         // reversing in that case.
         let reverse_stops = start_point.x > end_point.x ||
             (start_point.x == end_point.x && start_point.y > end_point.y);
@@ -1894,27 +1871,25 @@ impl<'a> DisplayListFlattener<'a> {
         // points, it's necessary to reverse the gradient
         // line in some cases.
         let (sp, ep) = if reverse_stops {
             (end_point, start_point)
         } else {
             (start_point, end_point)
         };
 
-        Some(BrushKind::LinearGradient {
-            stops_range: stops,
+        Some(PrimitiveContainer::LinearGradient {
             extend_mode,
-            reverse_stops,
             start_point: sp,
             end_point: ep,
-            stops_handle: GpuCacheHandle::new(),
             stretch_size,
             tile_spacing,
-            visible_tiles: Vec::new(),
-            stops_opacity,
+            stops,
+            reverse_stops,
+            nine_patch,
         })
     }
 
     pub fn create_brush_kind_for_radial_gradient(
         &mut self,
         info: &LayoutPrimitiveInfo,
         center: LayoutPoint,
         start_radius: f32,
@@ -1933,17 +1908,17 @@ impl<'a> DisplayListFlattener<'a> {
             extend_mode,
             center,
             start_radius,
             end_radius,
             ratio_xy,
             stops_handle: GpuCacheHandle::new(),
             stretch_size,
             tile_spacing,
-            visible_tiles: Vec::new(),
+            visible_tiles_range: GradientTileRange::empty(),
         }
     }
 
     pub fn add_text(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         offset: LayoutVector2D,
         prim_info: &LayoutPrimitiveInfo,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -441,16 +441,17 @@ impl TileCache {
                     }
                 }
             }
             PrimitiveInstanceKind::LegacyPrimitive { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::NormalBorder { .. } |
+            PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } => {
                 // These don't contribute dependencies
             }
         }
 
         for (key, current) in &mut opacity_bindings {
             if let Some(value) = scene_properties.get_float_value(*key) {
                 *current = value;
--- a/gfx/wr/webrender/src/prim_store.rs
+++ b/gfx/wr/webrender/src/prim_store.rs
@@ -6,17 +6,17 @@ use api::{AlphaType, BorderRadius, Built
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, LayoutSideOffsetsAu};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset, RepeatMode};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
 use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
 use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, ColorDepth, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers, LayoutVector2DAu};
 use app_units::Au;
 use border::{get_max_scale_for_border, build_border_instances, create_border_segments};
-use border::{create_nine_patch_segments, BorderSegmentCacheKey, NormalBorderAu};
+use border::{BorderSegmentCacheKey, NormalBorderAu};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{SideOffsets2D, 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::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
@@ -370,24 +370,17 @@ pub enum PrimitiveKeyKind {
     /// Clear an existing rect, used for special effects on some platforms.
     Clear,
     NormalBorder {
         border: NormalBorderAu,
         widths: LayoutSideOffsetsAu,
     },
     ImageBorder {
         request: ImageRequest,
-        widths: LayoutSideOffsetsAu,
-        width: i32,
-        height: i32,
-        slice: SideOffsets2D<i32>,
-        fill: bool,
-        repeat_horizontal: RepeatMode,
-        repeat_vertical: RepeatMode,
-        outset: SideOffsets2D<Au>,
+        nine_patch: NinePatchDescriptor,
     },
     Rectangle {
         color: ColorU,
     },
     YuvImage {
         color_depth: ColorDepth,
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
@@ -398,16 +391,44 @@ pub enum PrimitiveKeyKind {
         key: ImageKey,
         stretch_size: SizeKey,
         tile_spacing: SizeKey,
         color: ColorU,
         sub_rect: Option<DeviceIntRect>,
         image_rendering: ImageRendering,
         alpha_type: AlphaType,
     },
+    LinearGradient {
+        extend_mode: ExtendMode,
+        start_point: PointKey,
+        end_point: PointKey,
+        stretch_size: SizeKey,
+        tile_spacing: SizeKey,
+        stops: Vec<GradientStopKey>,
+        reverse_stops: bool,
+        nine_patch: Option<Box<NinePatchDescriptor>>,
+    },
+}
+
+/// A hashable gradient stop that can be used in primitive keys.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct GradientStopKey {
+    pub offset: f32,
+    pub color: ColorU,
+}
+
+impl Eq for GradientStopKey {}
+
+impl hash::Hash for GradientStopKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.offset.to_bits().hash(state);
+        self.color.hash(state);
+    }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
 pub struct RectangleKey {
     x: f32,
     y: f32,
@@ -441,16 +462,71 @@ impl From<LayoutRect> for RectangleKey {
             x: rect.origin.x,
             y: rect.origin.y,
             w: rect.size.width,
             h: rect.size.height,
         }
     }
 }
 
+/// A hashable SideOffset2D that can be used in primitive keys.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct SideOffsetsKey {
+    pub top: f32,
+    pub right: f32,
+    pub bottom: f32,
+    pub left: f32,
+}
+
+impl Eq for SideOffsetsKey {}
+
+impl hash::Hash for SideOffsetsKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.top.to_bits().hash(state);
+        self.right.to_bits().hash(state);
+        self.bottom.to_bits().hash(state);
+        self.left.to_bits().hash(state);
+    }
+}
+
+impl From<SideOffsetsKey> for LayoutSideOffsets {
+    fn from(key: SideOffsetsKey) -> LayoutSideOffsets {
+        LayoutSideOffsets::new(
+            key.top,
+            key.right,
+            key.bottom,
+            key.left,
+        )
+    }
+}
+
+impl From<LayoutSideOffsets> for SideOffsetsKey {
+    fn from(offsets: LayoutSideOffsets) -> SideOffsetsKey {
+        SideOffsetsKey {
+            top: offsets.top,
+            right: offsets.right,
+            bottom: offsets.bottom,
+            left: offsets.left,
+        }
+    }
+}
+
+impl From<SideOffsets2D<f32>> for SideOffsetsKey {
+    fn from(offsets: SideOffsets2D<f32>) -> SideOffsetsKey {
+        SideOffsetsKey {
+            top: offsets.top,
+            right: offsets.right,
+            bottom: offsets.bottom,
+            left: offsets.left,
+        }
+    }
+}
+
 /// A hashable size for using as a key during primitive interning.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
 pub struct SizeKey {
     w: f32,
     h: f32,
 }
@@ -474,16 +550,49 @@ impl From<LayoutSize> for SizeKey {
     fn from(size: LayoutSize) -> SizeKey {
         SizeKey {
             w: size.width,
             h: size.height,
         }
     }
 }
 
+/// A hashable point for using as a key during primitive interning.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct PointKey {
+    x: f32,
+    y: f32,
+}
+
+impl Eq for PointKey {}
+
+impl hash::Hash for PointKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.x.to_bits().hash(state);
+        self.y.to_bits().hash(state);
+    }
+}
+
+impl From<PointKey> for LayoutPoint {
+    fn from(key: PointKey) -> LayoutPoint {
+        LayoutPoint::new(key.x, key.y)
+    }
+}
+
+impl From<LayoutPoint> for PointKey {
+    fn from(p: LayoutPoint) -> PointKey {
+        PointKey {
+            x: p.x,
+            y: p.y,
+        }
+    }
+}
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct PrimitiveKey {
     pub is_backface_visible: bool,
     pub prim_rect: RectangleKey,
     pub clip_rect: RectangleKey,
     pub kind: PrimitiveKeyKind,
@@ -558,16 +667,21 @@ impl PrimitiveKey {
                     segment_instance_index: SegmentInstanceIndex::INVALID,
                     visible_tiles: Vec::new(),
                 });
 
                 PrimitiveInstanceKind::Image {
                     image_instance_index,
                 }
             }
+            PrimitiveKeyKind::LinearGradient { .. } => {
+                PrimitiveInstanceKind::LinearGradient {
+                    visible_tiles_range: GradientTileRange::empty(),
+                }
+            }
             PrimitiveKeyKind::Unused => {
                 // Should never be hit as this method should not be
                 // called for old style primitives.
                 unreachable!();
             }
         }
     }
 }
@@ -617,16 +731,28 @@ pub enum PrimitiveTemplateKind {
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         color: ColorF,
         source: ImageSource,
         image_rendering: ImageRendering,
         sub_rect: Option<DeviceIntRect>,
         alpha_type: AlphaType,
     },
+    LinearGradient {
+        extend_mode: ExtendMode,
+        start_point: LayoutPoint,
+        end_point: LayoutPoint,
+        stretch_size: LayoutSize,
+        tile_spacing: LayoutSize,
+        stops_opacity: PrimitiveOpacity,
+        stops: Vec<GradientStop>,
+        brush_segments: Vec<BrushSegment>,
+        reverse_stops: bool,
+        stops_handle: GpuCacheHandle,
+    },
     Clear,
     Unused,
 }
 
 /// Construct the primitive template data from a primitive key. This
 /// is invoked when a primitive key is created and the interner
 /// doesn't currently contain a primitive with this key.
 impl PrimitiveKeyKind {
@@ -669,45 +795,21 @@ impl PrimitiveKeyKind {
                         border,
                         widths,
                         border_segments,
                         brush_segments,
                     })
                 }
             }
             PrimitiveKeyKind::ImageBorder {
-                widths,
                 request,
-                width,
-                height,
-                slice,
-                fill,
-                repeat_horizontal,
-                repeat_vertical,
-                outset,
+                ref nine_patch,
                 ..
             } => {
-                let widths = LayoutSideOffsets::from_au(widths);
-
-                let brush_segments = create_nine_patch_segments(
-                    rect,
-                    &widths,
-                    width,
-                    height,
-                    slice,
-                    fill,
-                    repeat_horizontal,
-                    repeat_vertical,
-                    SideOffsets2D::new(
-                        outset.top.to_f32_px(),
-                        outset.right.to_f32_px(),
-                        outset.bottom.to_f32_px(),
-                        outset.left.to_f32_px(),
-                    ),
-                );
+                let brush_segments = nine_patch.create_segments(rect);
 
                 PrimitiveTemplateKind::ImageBorder {
                     request,
                     brush_segments,
                 }
             }
             PrimitiveKeyKind::Rectangle { color, .. } => {
                 PrimitiveTemplateKind::Rectangle {
@@ -736,16 +838,65 @@ impl PrimitiveKeyKind {
                 }
             }
             PrimitiveKeyKind::LineDecoration { cache_key, color } => {
                 PrimitiveTemplateKind::LineDecoration {
                     cache_key,
                     color: color.into(),
                 }
             }
+            PrimitiveKeyKind::LinearGradient {
+                extend_mode,
+                tile_spacing,
+                start_point,
+                end_point,
+                stretch_size,
+                stops,
+                reverse_stops,
+                nine_patch,
+                ..
+            } => {
+                let mut min_alpha: f32 = 1.0;
+
+                // Convert the stops to more convenient representation
+                // for the current gradient builder.
+                let stops = stops.iter().map(|stop| {
+                    let color: ColorF = stop.color.into();
+                    min_alpha = min_alpha.min(color.a);
+
+                    GradientStop {
+                        offset: stop.offset,
+                        color,
+                    }
+                }).collect();
+
+                let mut brush_segments = Vec::new();
+
+                if let Some(ref nine_patch) = nine_patch {
+                    brush_segments = nine_patch.create_segments(rect);
+                }
+
+                // Save opacity of the stops for use in
+                // selecting which pass this gradient
+                // should be drawn in.
+                let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
+
+                PrimitiveTemplateKind::LinearGradient {
+                    extend_mode,
+                    start_point: start_point.into(),
+                    end_point: end_point.into(),
+                    stretch_size: stretch_size.into(),
+                    tile_spacing: tile_spacing.into(),
+                    stops_opacity,
+                    stops,
+                    brush_segments,
+                    reverse_stops,
+                    stops_handle: GpuCacheHandle::new(),
+                }
+            }
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveTemplate {
     pub is_backface_visible: bool,
@@ -884,16 +1035,30 @@ impl PrimitiveTemplateKind {
                 request.push(PremultipliedColorF::WHITE);
                 request.push([
                     stretch_size.width + tile_spacing.width,
                     stretch_size.height + tile_spacing.height,
                     0.0,
                     0.0,
                 ]);
             }
+            PrimitiveTemplateKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
+                request.push([
+                    start_point.x,
+                    start_point.y,
+                    end_point.x,
+                    end_point.y,
+                ]);
+                request.push([
+                    pack_as_float(extend_mode as u32),
+                    stretch_size.width,
+                    stretch_size.height,
+                    0.0,
+                ]);
+            }
             PrimitiveTemplateKind::Unused => {}
         }
     }
 
     fn write_segment_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
         prim_rect: LayoutRect,
@@ -924,16 +1089,25 @@ impl PrimitiveTemplateKind {
                 }
             }
             PrimitiveTemplateKind::LineDecoration { .. } => {
                 request.write_segment(
                     prim_rect,
                     [0.0; 4],
                 );
             }
+            PrimitiveTemplateKind::LinearGradient { ref brush_segments, .. } => {
+                for segment in brush_segments {
+                    // has to match VECS_PER_SEGMENT
+                    request.write_segment(
+                        segment.local_rect,
+                        segment.extra_data,
+                    );
+                }
+            }
             PrimitiveTemplateKind::Image { .. } |
             PrimitiveTemplateKind::Rectangle { .. } |
             PrimitiveTemplateKind::TextRun { .. } |
             PrimitiveTemplateKind::YuvImage { .. } |
             PrimitiveTemplateKind::Unused => {}
         }
     }
 }
@@ -964,16 +1138,46 @@ impl PrimitiveTemplate {
             }
             PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                 PrimitiveOpacity::from_alpha(color.a)
             }
             PrimitiveTemplateKind::NormalBorder { .. } => {
                 // Shouldn't matter, since the segment opacity is used instead
                 PrimitiveOpacity::translucent()
             }
+            PrimitiveTemplateKind::LinearGradient {
+                stretch_size,
+                tile_spacing,
+                stops_opacity,
+                ref mut stops_handle,
+                reverse_stops,
+                ref stops,
+                ..
+            } => {
+                if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
+                    GradientGpuBlockBuilder::build(
+                        reverse_stops,
+                        &mut request,
+                        stops.iter().cloned(),
+                    );
+                }
+
+                // If the coverage of the gradient extends to or beyond
+                // the primitive rect, then the opacity can be determined
+                // by the colors of the stops. If we have tiling / spacing
+                // then we just assume the gradient is translucent for now.
+                // (In the future we could consider segmenting in some cases).
+                let stride = stretch_size + tile_spacing;
+                if stride.width >= self.prim_rect.size.width &&
+                   stride.height >= self.prim_rect.size.height {
+                    stops_opacity
+                } else {
+                    PrimitiveOpacity::translucent()
+                }
+            }
             PrimitiveTemplateKind::ImageBorder { request, .. } => {
                 let image_properties = frame_state
                     .resource_cache
                     .get_image_properties(request.key);
 
                 if let Some(image_properties) = image_properties {
                     frame_state.resource_cache.request_image(
                         request,
@@ -1241,29 +1445,17 @@ pub enum BrushKind {
         stops_range: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
-        visible_tiles: Vec<VisibleGradientTile>,
-    },
-    LinearGradient {
-        stops_handle: GpuCacheHandle,
-        stops_range: ItemRange<GradientStop>,
-        extend_mode: ExtendMode,
-        reverse_stops: bool,
-        start_point: LayoutPoint,
-        end_point: LayoutPoint,
-        stretch_size: LayoutSize,
-        tile_spacing: LayoutSize,
-        visible_tiles: Vec<VisibleGradientTile>,
-        stops_opacity: PrimitiveOpacity,
+        visible_tiles_range: GradientTileRange,
     },
 }
 
 bitflags! {
     /// Each bit of the edge AA mask is:
     /// 0, when the edge of the primitive needs to be considered for AA
     /// 1, when the edge of the segment needs to be considered for AA
     ///
@@ -1403,30 +1595,16 @@ impl BrushPrimitive {
     fn write_gpu_blocks_if_required(
         &mut self,
         local_rect: LayoutRect,
         gpu_cache: &mut GpuCache,
     ) {
         if let Some(mut request) = gpu_cache.request(&mut self.gpu_location) {
             // has to match VECS_PER_SPECIFIC_BRUSH
             match self.kind {
-                BrushKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
-                    request.push([
-                        start_point.x,
-                        start_point.y,
-                        end_point.x,
-                        end_point.y,
-                    ]);
-                    request.push([
-                        pack_as_float(extend_mode as u32),
-                        stretch_size.width,
-                        stretch_size.height,
-                        0.0,
-                    ]);
-                }
                 BrushKind::RadialGradient { stretch_size, center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
                     request.push([
                         center.x,
                         center.y,
                         start_radius,
                         end_radius,
                     ]);
                     request.push([
@@ -1512,36 +1690,23 @@ pub const GRADIENT_DATA_SIZE: usize = GR
 #[derive(Debug)]
 #[repr(C)]
 // An entry in a gradient data table representing a segment of the gradient color space.
 pub struct GradientDataEntry {
     pub start_color: PremultipliedColorF,
     pub end_color: PremultipliedColorF,
 }
 
-struct GradientGpuBlockBuilder<'a> {
-    stops_range: ItemRange<GradientStop>,
-    display_list: &'a BuiltDisplayList,
-}
-
-impl<'a> GradientGpuBlockBuilder<'a> {
-    fn new(
-        stops_range: ItemRange<GradientStop>,
-        display_list: &'a BuiltDisplayList,
-    ) -> Self {
-        GradientGpuBlockBuilder {
-            stops_range,
-            display_list,
-        }
-    }
-
+// TODO(gw): Tidy this up to be a free function / module?
+struct GradientGpuBlockBuilder {}
+
+impl GradientGpuBlockBuilder {
     /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating
     /// from start_color to end_color.
     fn fill_colors(
-        &self,
         start_idx: usize,
         end_idx: usize,
         start_color: &PremultipliedColorF,
         end_color: &PremultipliedColorF,
         entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE],
     ) {
         // Calculate the color difference for individual steps in the ramp.
         let inv_steps = 1.0 / (end_idx - start_idx) as f32;
@@ -1569,24 +1734,25 @@ impl<'a> GradientGpuBlockBuilder<'a> {
     #[inline]
     fn get_index(offset: f32) -> usize {
         (offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 +
             GRADIENT_DATA_TABLE_BEGIN as f32)
             .round() as usize
     }
 
     // Build the gradient data from the supplied stops, reversing them if necessary.
-    fn build(&self, reverse_stops: bool, request: &mut GpuDataRequest) {
-        let src_stops = self.display_list.get(self.stops_range);
-
+    fn build<I>(
+        reverse_stops: bool,
+        request: &mut GpuDataRequest,
+        src_stops: I,
+    ) where I: IntoIterator<Item = GradientStop> {
         // Preconditions (should be ensured by DisplayListBuilder):
         // * we have at least two stops
         // * first stop has offset 0.0
         // * last stop has offset 1.0
-
         let mut src_stops = src_stops.into_iter();
         let mut cur_color = match src_stops.next() {
             Some(stop) => {
                 debug_assert_eq!(stop.offset, 0.0);
                 stop.color.premultiplied()
             }
             None => {
                 error!("Zero gradient stops found!");
@@ -1602,84 +1768,108 @@ impl<'a> GradientGpuBlockBuilder<'a> {
         // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
         // format for texture upload. This table requires the gradient color stops to be normalized to the
         // range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
         // while the entries in between hold the interpolated color stop values for the range [0, 1].
         let mut entries: [GradientDataEntry; GRADIENT_DATA_SIZE] = unsafe { mem::uninitialized() };
 
         if reverse_stops {
             // Fill in the first entry (for reversed stops) with the first color stop
-            self.fill_colors(
+            GradientGpuBlockBuilder::fill_colors(
                 GRADIENT_DATA_LAST_STOP,
                 GRADIENT_DATA_LAST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
             );
 
             // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
             // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The
             // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
             let mut cur_idx = GRADIENT_DATA_TABLE_END;
             for next in src_stops {
                 let next_color = next.color.premultiplied();
                 let next_idx = Self::get_index(1.0 - next.offset);
 
                 if next_idx < cur_idx {
-                    self.fill_colors(next_idx, cur_idx, &next_color, &cur_color, &mut entries);
+                    GradientGpuBlockBuilder::fill_colors(
+                        next_idx,
+                        cur_idx,
+                        &next_color,
+                        &cur_color,
+                        &mut entries,
+                    );
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
             if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
                 error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
-                self.fill_colors(GRADIENT_DATA_TABLE_BEGIN, cur_idx, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
+                GradientGpuBlockBuilder::fill_colors(
+                    GRADIENT_DATA_TABLE_BEGIN,
+                    cur_idx,
+                    &PremultipliedColorF::WHITE,
+                    &cur_color,
+                    &mut entries,
+                );
             }
 
             // Fill in the last entry (for reversed stops) with the last color stop
-            self.fill_colors(
+            GradientGpuBlockBuilder::fill_colors(
                 GRADIENT_DATA_FIRST_STOP,
                 GRADIENT_DATA_FIRST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
             );
         } else {
             // Fill in the first entry with the first color stop
-            self.fill_colors(
+            GradientGpuBlockBuilder::fill_colors(
                 GRADIENT_DATA_FIRST_STOP,
                 GRADIENT_DATA_FIRST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
             );
 
             // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
             // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The
             // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
             let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
             for next in src_stops {
                 let next_color = next.color.premultiplied();
                 let next_idx = Self::get_index(next.offset);
 
                 if next_idx > cur_idx {
-                    self.fill_colors(cur_idx, next_idx, &cur_color, &next_color, &mut entries);
+                    GradientGpuBlockBuilder::fill_colors(
+                        cur_idx,
+                        next_idx,
+                        &cur_color,
+                        &next_color,
+                        &mut entries,
+                    );
                     cur_idx = next_idx;
                 }
 
                 cur_color = next_color;
             }
             if cur_idx != GRADIENT_DATA_TABLE_END {
                 error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
-                self.fill_colors(cur_idx, GRADIENT_DATA_TABLE_END, &PremultipliedColorF::WHITE, &cur_color, &mut entries);
+                GradientGpuBlockBuilder::fill_colors(
+                    cur_idx,
+                    GRADIENT_DATA_TABLE_END,
+                    &PremultipliedColorF::WHITE,
+                    &cur_color,
+                    &mut entries,
+                );
             }
 
             // Fill in the last entry with the last color stop
-            self.fill_colors(
+            GradientGpuBlockBuilder::fill_colors(
                 GRADIENT_DATA_LAST_STOP,
                 GRADIENT_DATA_LAST_STOP + 1,
                 &cur_color,
                 &cur_color,
                 &mut entries,
             );
         }
 
@@ -1979,16 +2169,32 @@ impl ClipData {
             &self.bottom_left,
             &self.bottom_right,
         ] {
             corner.write(request);
         }
     }
 }
 
+/// A hashable descriptor for nine-patches, used by image and
+/// gradient borders.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct NinePatchDescriptor {
+    pub width: i32,
+    pub height: i32,
+    pub slice: SideOffsets2D<i32>,
+    pub fill: bool,
+    pub repeat_horizontal: RepeatMode,
+    pub repeat_vertical: RepeatMode,
+    pub outset: SideOffsetsKey,
+    pub widths: SideOffsetsKey,
+}
+
 pub enum PrimitiveContainer {
     TextRun {
         font: FontInstance,
         offset: LayoutVector2D,
         glyphs: Vec<GlyphInstance>,
         shadow: bool,
     },
     Clear,
@@ -2000,24 +2206,17 @@ pub enum PrimitiveContainer {
         wavy_line_thickness: f32,
     },
     NormalBorder {
         border: NormalBorder,
         widths: LayoutSideOffsets,
     },
     ImageBorder {
         request: ImageRequest,
-        widths: LayoutSideOffsets,
-        width: i32,
-        height: i32,
-        slice: SideOffsets2D<i32>,
-        fill: bool,
-        repeat_horizontal: RepeatMode,
-        repeat_vertical: RepeatMode,
-        outset: SideOffsets2D<f32>,
+        nine_patch: NinePatchDescriptor,
     },
     Rectangle {
         color: ColorF,
     },
     YuvImage {
         color_depth: ColorDepth,
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
@@ -2028,16 +2227,26 @@ pub enum PrimitiveContainer {
         key: ImageKey,
         color: ColorF,
         tile_spacing: LayoutSize,
         stretch_size: LayoutSize,
         sub_rect: Option<DeviceIntRect>,
         image_rendering: ImageRendering,
         alpha_type: AlphaType,
     },
+    LinearGradient {
+        extend_mode: ExtendMode,
+        start_point: LayoutPoint,
+        end_point: LayoutPoint,
+        stretch_size: LayoutSize,
+        tile_spacing: LayoutSize,
+        stops: Vec<GradientStopKey>,
+        reverse_stops: bool,
+        nine_patch: Option<Box<NinePatchDescriptor>>,
+    },
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
     //           add_primitive() call. In the future
@@ -2045,26 +2254,26 @@ impl PrimitiveContainer {
     //           primitive types to use this.
     pub fn is_visible(&self) -> bool {
         match *self {
             PrimitiveContainer::TextRun { ref font, .. } => {
                 font.color.a > 0
             }
             PrimitiveContainer::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::RadialGradient { .. } |
-                    BrushKind::LinearGradient { .. } => {
+                    BrushKind::RadialGradient { .. } => {
                         true
                     }
                 }
             }
             PrimitiveContainer::NormalBorder { .. } |
             PrimitiveContainer::ImageBorder { .. } |
             PrimitiveContainer::YuvImage { .. } |
             PrimitiveContainer::Image { .. } |
+            PrimitiveContainer::LinearGradient { .. } |
             PrimitiveContainer::Clear => {
                 true
             }
             PrimitiveContainer::Rectangle { ref color, .. } |
             PrimitiveContainer::LineDecoration { ref color, .. } => {
                 color.a > 0.0
             }
         }
@@ -2082,16 +2291,40 @@ impl PrimitiveContainer {
                     font,
                     offset: offset.to_au(),
                     glyphs,
                     shadow,
                 };
 
                 (key, None)
             }
+            PrimitiveContainer::LinearGradient {
+                extend_mode,
+                start_point,
+                end_point,
+                stretch_size,
+                tile_spacing,
+                stops,
+                reverse_stops,
+                nine_patch,
+                ..
+            } => {
+                let key = PrimitiveKeyKind::LinearGradient {
+                    extend_mode,
+                    start_point: start_point.into(),
+                    end_point: end_point.into(),
+                    stretch_size: stretch_size.into(),
+                    tile_spacing: tile_spacing.into(),
+                    stops,
+                    reverse_stops,
+                    nine_patch,
+                };
+
+                (key, None)
+            }
             PrimitiveContainer::Clear => {
                 (PrimitiveKeyKind::Clear, None)
             }
             PrimitiveContainer::Rectangle { color, .. } => {
                 let key = PrimitiveKeyKind::Rectangle {
                     color: color.into(),
                 };
 
@@ -2116,43 +2349,20 @@ impl PrimitiveContainer {
                     yuv_key,
                     format,
                     color_space,
                     image_rendering,
                 };
 
                 (key, None)
             }
-            PrimitiveContainer::ImageBorder {
-                request,
-                widths,
-                width,
-                height,
-                slice,
-                fill,
-                repeat_vertical,
-                repeat_horizontal,
-                outset,
-                ..
-            } => {
+            PrimitiveContainer::ImageBorder { request, nine_patch, .. } => {
                 let key = PrimitiveKeyKind::ImageBorder {
                     request,
-                    widths: widths.to_au(),
-                    width,
-                    height,
-                    slice,
-                    fill,
-                    repeat_horizontal,
-                    repeat_vertical,
-                    outset: SideOffsets2D::new(
-                        Au::from_f32_px(outset.top),
-                        Au::from_f32_px(outset.right),
-                        Au::from_f32_px(outset.bottom),
-                        Au::from_f32_px(outset.left),
-                    ),
+                    nine_patch,
                 };
 
                 (key, None)
             }
             PrimitiveContainer::NormalBorder { border, widths, .. } => {
                 let key = PrimitiveKeyKind::NormalBorder {
                     border: border.into(),
                     widths: widths.to_au(),
@@ -2278,16 +2488,17 @@ impl PrimitiveContainer {
                     image_rendering,
                     alpha_type,
                     color: shadow.color,
                 }
             }
             PrimitiveContainer::Brush(..) |
             PrimitiveContainer::ImageBorder { .. } |
             PrimitiveContainer::YuvImage { .. } |
+            PrimitiveContainer::LinearGradient { .. } |
             PrimitiveContainer::Clear => {
                 panic!("bug: this prim is not supported in shadow contexts");
             }
         }
     }
 }
 
 pub enum PrimitiveDetails {
@@ -2362,16 +2573,19 @@ pub enum PrimitiveInstanceKind {
         segment_instance_index: SegmentInstanceIndex,
     },
     YuvImage {
         segment_instance_index: SegmentInstanceIndex,
     },
     Image {
         image_instance_index: ImageInstanceIndex,
     },
+    LinearGradient {
+        visible_tiles_range: GradientTileRange,
+    },
     /// Clear out a rect, used for special effects.
     Clear,
 }
 
 #[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
@@ -2463,16 +2677,18 @@ pub type OpacityBindingIndex = storage::
 pub type OpacityBindingStorage = storage::Storage<OpacityBinding>;
 pub type BorderHandleStorage = storage::Storage<RenderTaskCacheEntryHandle>;
 pub type SegmentStorage = storage::Storage<BrushSegment>;
 pub type SegmentsRange = storage::Range<BrushSegment>;
 pub type SegmentInstanceStorage = storage::Storage<SegmentedInstance>;
 pub type SegmentInstanceIndex = storage::Index<SegmentedInstance>;
 pub type ImageInstanceStorage = storage::Storage<ImageInstance>;
 pub type ImageInstanceIndex = storage::Index<ImageInstance>;
+pub type GradientTileStorage = storage::Storage<VisibleGradientTile>;
+pub type GradientTileRange = storage::Range<VisibleGradientTile>;
 
 /// Contains various vecs of data that is used only during frame building,
 /// where we want to recycle the memory each new display list, to avoid constantly
 /// re-allocating and moving memory around. Written during primitive preparation,
 /// and read during batching.
 pub struct PrimitiveScratchBuffer {
     /// Contains a list of clip mask instance parameters
     /// per segment generated.
@@ -2488,45 +2704,57 @@ pub struct PrimitiveScratchBuffer {
 
     /// A list of brush segments that have been built for this scene.
     pub segments: SegmentStorage,
 
     /// A list of segment ranges and GPU cache handles for prim instances
     /// that have opted into segment building. In future, this should be
     /// removed in favor of segment building during primitive interning.
     pub segment_instances: SegmentInstanceStorage,
+
+    /// A list of visible tiles that tiled gradients use to store
+    /// per-tile information.
+    pub gradient_tiles: GradientTileStorage,
 }
 
 impl PrimitiveScratchBuffer {
     pub fn new() -> Self {
         PrimitiveScratchBuffer {
             clip_mask_instances: Vec::new(),
             glyph_keys: GlyphKeyStorage::new(0),
             border_cache_handles: BorderHandleStorage::new(0),
             segments: SegmentStorage::new(0),
             segment_instances: SegmentInstanceStorage::new(0),
+            gradient_tiles: GradientTileStorage::new(0),
         }
     }
 
     pub fn recycle(&mut self) {
         recycle_vec(&mut self.clip_mask_instances);
         self.glyph_keys.recycle();
         self.border_cache_handles.recycle();
         self.segments.recycle();
         self.segment_instances.recycle();
+        self.gradient_tiles.recycle();
     }
 
     pub fn begin_frame(&mut self) {
         // Clear the clip mask tasks for the beginning of the frame. Append
         // a single kind representing no clip mask, at the ClipTaskIndex::INVALID
         // location.
         self.clip_mask_instances.clear();
         self.clip_mask_instances.push(ClipMaskKind::None);
 
         self.border_cache_handles.clear();
+
+        // TODO(gw): As in the previous code, the gradient tiles store GPU cache
+        //           handles that are cleared (and thus invalidated + re-uploaded)
+        //           every frame. This maintains the existing behavior, but we
+        //           should fix this in the future to retain handles.
+        self.gradient_tiles.clear();
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Debug)]
 pub struct PrimitiveStoreStats {
     primitive_count: usize,
@@ -2697,16 +2925,17 @@ impl PrimitiveStore {
                 return Some(pic_index);
             }
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::LegacyPrimitive { .. } |
+            PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 // These prims don't support opacity collapse
             }
             PrimitiveInstanceKind::Picture { pic_index } => {
                 let pic = &self.pictures[pic_index.0];
 
                 // If we encounter a picture that is a pass-through
                 // (i.e. no composite mode), then we can recurse into
@@ -2831,16 +3060,17 @@ impl PrimitiveStore {
                 PrimitiveInstanceKind::TextRun { .. } |
                 PrimitiveInstanceKind::Rectangle { .. } |
                 PrimitiveInstanceKind::LineDecoration { .. } |
                 PrimitiveInstanceKind::LegacyPrimitive { .. } |
                 PrimitiveInstanceKind::NormalBorder { .. } |
                 PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::YuvImage { .. } |
                 PrimitiveInstanceKind::Image { .. } |
+                PrimitiveInstanceKind::LinearGradient { .. } |
                 PrimitiveInstanceKind::Clear => {
                     None
                 }
             }
         };
 
         let (is_passthrough, clip_node_collector) = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
@@ -2885,16 +3115,17 @@ impl PrimitiveStore {
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::Image { .. } |
+            PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 let prim_data = &resources
                     .prim_data_store[prim_instance.prim_data_handle];
                 (prim_data.prim_rect, prim_data.clip_rect)
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim = &self.primitives[prim_index.0];
                 (prim.local_rect, prim.local_clip_rect)
@@ -3086,16 +3317,17 @@ impl PrimitiveStore {
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::Image { .. } |
+            PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 self.prepare_interned_prim_for_render(
                     prim_instance,
                     prim_context,
                     pic_context,
                     frame_context,
                     frame_state,
                     resources,
@@ -3107,16 +3339,17 @@ impl PrimitiveStore {
 
                 prim_instance.prepare_prim_for_render_inner(
                     prim_local_rect,
                     prim_details,
                     prim_context,
                     pic_context,
                     frame_state,
                     display_list,
+                    scratch,
                 );
             }
         }
 
         true
     }
 
     pub fn prepare_primitives(
@@ -3505,16 +3738,56 @@ impl PrimitiveStore {
                             // assertions later on because we didn't request any image.
                             prim_instance.bounding_rect = None;
                         }
                     }
                 }
 
                 image_instance.segment_instance_index
             }
+            (
+                PrimitiveInstanceKind::LinearGradient { ref mut visible_tiles_range, .. },
+                PrimitiveTemplateKind::LinearGradient { extend_mode, stretch_size, start_point, end_point, tile_spacing, .. }
+            ) => {
+                if *tile_spacing != LayoutSize::zero() {
+                    *visible_tiles_range = decompose_repeated_primitive(
+                        &prim_instance.combined_local_clip_rect,
+                        &prim_data.prim_rect,
+                        &stretch_size,
+                        &tile_spacing,
+                        prim_context,
+                        frame_state,
+                        &pic_context.dirty_world_rect,
+                        &mut scratch.gradient_tiles,
+                        &mut |rect, mut request| {
+                            request.push([
+                                start_point.x,
+                                start_point.y,
+                                end_point.x,
+                                end_point.y,
+                            ]);
+                            request.push([
+                                pack_as_float(*extend_mode as u32),
+                                stretch_size.width,
+                                stretch_size.height,
+                                0.0,
+                            ]);
+                            request.write_segment(*rect, [0.0; 4]);
+                        }
+                    );
+
+                    if visible_tiles_range.is_empty() {
+                        prim_instance.bounding_rect = None;
+                    }
+                }
+
+                // TODO(gw): Consider whether it's worth doing segment building
+                //           for gradient primitives.
+                SegmentInstanceIndex::UNUSED
+            }
             _ => {
                 unreachable!();
             }
         };
 
         debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
         if segment_instance_index != SegmentInstanceIndex::UNUSED {
             let segment_instance = &mut scratch.segment_instances[segment_instance_index];
@@ -3530,53 +3803,33 @@ impl PrimitiveStore {
                         [0.0; 4],
                     );
                 }
             }
         }
     }
 }
 
-fn build_gradient_stops_request(
-    stops_handle: &mut GpuCacheHandle,
-    stops_range: ItemRange<GradientStop>,
-    reverse_stops: bool,
-    frame_state: &mut FrameBuildingState,
-    display_list: &BuiltDisplayList,
-) {
-    if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
-        let gradient_builder = GradientGpuBlockBuilder::new(
-            stops_range,
-            display_list,
-        );
-        gradient_builder.build(
-            reverse_stops,
-            &mut request,
-        );
-    }
-}
-
 fn decompose_repeated_primitive(
-    visible_tiles: &mut Vec<VisibleGradientTile>,
-    instance: &mut PrimitiveInstance,
+    combined_local_clip_rect: &LayoutRect,
     prim_local_rect: &LayoutRect,
     stretch_size: &LayoutSize,
     tile_spacing: &LayoutSize,
     prim_context: &PrimitiveContext,
     frame_state: &mut FrameBuildingState,
     world_rect: &WorldRect,
+    gradient_tiles: &mut GradientTileStorage,
     callback: &mut FnMut(&LayoutRect, GpuDataRequest),
-) {
-    visible_tiles.clear();
+) -> GradientTileRange {
+    let mut visible_tiles = Vec::new();
 
     // Tighten the clip rect because decomposing the repeated image can
     // produce primitives that are partially covering the original image
     // rect and we want to clip these extra parts out.
-    let tight_clip_rect = instance
-        .combined_local_clip_rect
+    let tight_clip_rect = combined_local_clip_rect
         .intersection(prim_local_rect).unwrap();
 
     let visible_rect = compute_conservative_visible_rect(
         prim_context,
         world_rect,
         &tight_clip_rect
     );
     let stride = *stretch_size + *tile_spacing;
@@ -3595,23 +3848,25 @@ fn decompose_repeated_primitive(
 
         visible_tiles.push(VisibleGradientTile {
             local_rect: rect,
             local_clip_rect: tight_clip_rect,
             handle
         });
     }
 
+    // At this point if we don't have tiles to show it means we could probably
+    // have done a better a job at culling during an earlier stage.
+    // Clearing the screen rect has the effect of "culling out" the primitive
+    // from the point of view of the batch builder, and ensures we don't hit
+    // assertions later on because we didn't request any image.
     if visible_tiles.is_empty() {
-        // At this point if we don't have tiles to show it means we could probably
-        // have done a better a job at culling during an earlier stage.
-        // Clearing the screen rect has the effect of "culling out" the primitive
-        // from the point of view of the batch builder, and ensures we don't hit
-        // assertions later on because we didn't request any image.
-        instance.bounding_rect = None;
+        GradientTileRange::empty()
+    } else {
+        gradient_tiles.extend(visible_tiles)
     }
 }
 
 fn compute_conservative_visible_rect(
     prim_context: &PrimitiveContext,
     world_rect: &WorldRect,
     local_clip_rect: &LayoutRect,
 ) -> LayoutRect {
@@ -3814,16 +4069,17 @@ impl PrimitiveInstance {
                 }
                 &mut image_instance.segment_instance_index
             }
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Clear |
+            PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 // These primitives don't support / need segments.
                 return;
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim = &mut prim_store.primitives[prim_index.0];
                 match prim.details {
                     PrimitiveDetails::Brush(ref mut brush) => {
@@ -3986,16 +4242,34 @@ impl PrimitiveInstance {
                     PrimitiveTemplateKind::NormalBorder { ref template, .. } => {
                         template.brush_segments.as_slice()
                     }
                     _ => {
                         unreachable!();
                     }
                 }
             }
+            PrimitiveInstanceKind::LinearGradient { .. } => {
+                let prim_data = &resources.prim_data_store[self.prim_data_handle];
+
+                // TODO: This is quite messy - once we remove legacy primitives we
+                //       can change this to be a tuple match on (instance, template)
+                match prim_data.kind {
+                    PrimitiveTemplateKind::LinearGradient { ref brush_segments, .. } => {
+                        if brush_segments.is_empty() {
+                            return false;
+                        }
+
+                        brush_segments.as_slice()
+                    }
+                    _ => {
+                        unreachable!();
+                    }
+                }
+            }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim = &prim_store.primitives[prim_index.0];
                 match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         match brush.segment_desc {
                             Some(ref description) => {
                                 &description.segments
                             }
@@ -4078,140 +4352,83 @@ impl PrimitiveInstance {
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_local_rect: LayoutRect,
         prim_details: &mut PrimitiveDetails,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
+        scratch: &mut PrimitiveScratchBuffer,
     ) {
         let mut is_tiled = false;
 
         match *prim_details {
             PrimitiveDetails::Brush(ref mut brush) => {
                 brush.opacity = match brush.kind {
                     BrushKind::RadialGradient {
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
                         ratio_xy,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
                         ref mut stops_handle,
-                        ref mut visible_tiles,
+                        ref mut visible_tiles_range,
                         ..
                     } => {
-                        build_gradient_stops_request(
-                            stops_handle,
-                            stops_range,
-                            false,
-                            frame_state,
-                            display_list,
-                        );
+                        if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
+                            let src_stops = display_list.get(stops_range);
+
+                            GradientGpuBlockBuilder::build(
+                                false,
+                                &mut request,
+                                src_stops,
+                            );
+                        }
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
 
-                            decompose_repeated_primitive(
-                                visible_tiles,
-                                self,
+                            *visible_tiles_range = decompose_repeated_primitive(
+                                &self.combined_local_clip_rect,
                                 &prim_local_rect,
                                 &stretch_size,
                                 &tile_spacing,
                                 prim_context,
                                 frame_state,
                                 &pic_context.dirty_world_rect,
+                                &mut scratch.gradient_tiles,
                                 &mut |rect, mut request| {
                                     request.push([
                                         center.x,
                                         center.y,
                                         start_radius,
                                         end_radius,
                                     ]);
                                     request.push([
                                         ratio_xy,
                                         pack_as_float(extend_mode as u32),
                                         stretch_size.width,
                                         stretch_size.height,
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 },
                             );
+
+                            if visible_tiles_range.is_empty() {
+                                self.bounding_rect = None;
+                            }
                         }
 
                         //TODO: can we make it opaque in some cases?
                         PrimitiveOpacity::translucent()
                     }
-                    BrushKind::LinearGradient {
-                        stops_range,
-                        reverse_stops,
-                        start_point,
-                        end_point,
-                        extend_mode,
-                        stretch_size,
-                        tile_spacing,
-                        stops_opacity,
-                        ref mut stops_handle,
-                        ref mut visible_tiles,
-                        ..
-                    } => {
-                        build_gradient_stops_request(
-                            stops_handle,
-                            stops_range,
-                            reverse_stops,
-                            frame_state,
-                            display_list,
-                        );
-
-                        if tile_spacing != LayoutSize::zero() {
-                            is_tiled = true;
-
-                            decompose_repeated_primitive(
-                                visible_tiles,
-                                self,
-                                &prim_local_rect,
-                                &stretch_size,
-                                &tile_spacing,
-                                prim_context,
-                                frame_state,
-                                &pic_context.dirty_world_rect,
-                                &mut |rect, mut request| {
-                                    request.push([
-                                        start_point.x,
-                                        start_point.y,
-                                        end_point.x,
-                                        end_point.y,
-                                    ]);
-                                    request.push([
-                                        pack_as_float(extend_mode as u32),
-                                        stretch_size.width,
-                                        stretch_size.height,
-                                        0.0,
-                                    ]);
-                                    request.write_segment(*rect, [0.0; 4]);
-                                }
-                            );
-                        }
-
-                        // If the coverage of the gradient extends to or beyond
-                        // the primitive rect, then the opacity can be determined
-                        // by the colors of the stops. If we have tiling / spacing
-                        // then we just assume the gradient is translucent for now.
-                        // (In the future we could consider segmenting in some cases).
-                        let stride = stretch_size + tile_spacing;
-                        if stride.width >= prim_local_rect.size.width &&
-                           stride.height >= prim_local_rect.size.height {
-                            stops_opacity
-                        } else {
-                            PrimitiveOpacity::translucent()
-                        }
-                    }
                 };
             }
         }
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
@@ -4410,17 +4627,17 @@ fn update_opacity_binding(
 #[cfg(target_os = "linux")]
 fn test_struct_sizes() {
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<PrimitiveContainer>(), 200, "PrimitiveContainer size changed");
+    assert_eq!(mem::size_of::<PrimitiveContainer>(), 176, "PrimitiveContainer size changed");
     assert_eq!(mem::size_of::<PrimitiveInstance>(), 120, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 16, "PrimitiveInstanceKind size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplate>(), 176, "PrimitiveTemplate size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 112, "PrimitiveTemplateKind size changed");
     assert_eq!(mem::size_of::<PrimitiveKey>(), 152, "PrimitiveKey size changed");
     assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 112, "PrimitiveKeyKind size changed");
-    assert_eq!(mem::size_of::<Primitive>(), 224, "Primitive size changed");
+    assert_eq!(mem::size_of::<Primitive>(), 200, "Primitive size changed");
 }
--- a/gfx/wr/webrender/src/surface.rs
+++ b/gfx/wr/webrender/src/surface.rs
@@ -238,16 +238,17 @@ impl SurfaceDescriptor {
             match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { .. } |
                 PrimitiveInstanceKind::LegacyPrimitive { .. } => {
                     return None;
                 }
                 PrimitiveInstanceKind::Image { .. } |
                 PrimitiveInstanceKind::YuvImage { .. } |
                 PrimitiveInstanceKind::LineDecoration { .. } |
+                PrimitiveInstanceKind::LinearGradient { .. } |
                 PrimitiveInstanceKind::TextRun { .. } |
                 PrimitiveInstanceKind::NormalBorder { .. } |
                 PrimitiveInstanceKind::Rectangle { .. } |
                 PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::Clear => {}
             }
 
             // Record the unique identifier for the content represented
index d47a69dfb1658c2f1994677fcfb3f39da229fdf3..71436ebf36a6b13f1911e900bac0bb1e967b8c16
GIT binary patch
literal 23726
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
B>(BrI
index b28ce02c0fe11fc8b17b7bce74f860283540f783..f5f0335a8123a2235753adebcb53404fac44208e
GIT binary patch
literal 30742
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
E|2-?zGynhq