Bug 1533236 - Add a fast path to WR for common gradient types. r=kvark
authorGlenn Watson <github@intuitionlibrary.com>
Thu, 07 Mar 2019 21:20:40 +0000
changeset 520912 59d1e67be63c082fcc6c3ccf486f999a38e20e28
parent 520911 4a8244ddd0c470edf74c6ea0927aa154be89651d
child 520913 267bf61505cf01bca8cf84c8d670088c86999278
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1533236
milestone67.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 1533236 - Add a fast path to WR for common gradient types. r=kvark The existing linear gradient shader is quite slow, especially on very large gradients on integrated GPUs. The vast majority of gradients in real content are very simple (typically < 4 stops, no angle, no repeat). For these, we can run a fast path to persist a small gradient in the texture cache and draw the gradient via the image shader. This is _much_ faster than the catch-all gradient shader, and also results in better batching while drawing the main scene. In future, we can expand the fast path to handle more cases, such as angled gradients. For now, it takes a conservative approach, but still seems to hit the fast path on most real content. Differential Revision: https://phabricator.services.mozilla.com/D22445
gfx/wr/webrender/res/cs_gradient.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/prim_store/gradient.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/shade.rs
gfx/wr/webrender/src/texture_cache.rs
gfx/wr/webrender/src/tiling.rs
gfx/wr/webrender/tests/angle_shader_validation.rs
gfx/wr/wrench/reftests/border/reftest.list
gfx/wr/wrench/reftests/gradient/linear-aligned-border-radius.png
gfx/wr/wrench/reftests/gradient/linear-stops-ref.png
gfx/wr/wrench/reftests/gradient/premultiplied-aligned-2.png
gfx/wr/wrench/reftests/gradient/premultiplied-aligned.png
gfx/wr/wrench/reftests/gradient/reftest.list
gfx/wr/wrench/reftests/gradient/repeat-border-radius.png
layout/reftests/css-blending/reftest.list
layout/reftests/css-gradients/reftest.list
layout/reftests/xul/reftest.list
testing/web-platform/meta/css/css-backgrounds/background-gradient-subpixel-fills-area.html.ini
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_gradient.glsl
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+varying float vPos;
+flat varying vec4 vStops;
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+flat varying vec4 vColor2;
+flat varying vec4 vColor3;
+
+#ifdef WR_VERTEX_SHADER
+
+in vec4 aTaskRect;
+in float aAxisSelect;
+in vec4 aStops;
+in vec4 aColor0;
+in vec4 aColor1;
+in vec4 aColor2;
+in vec4 aColor3;
+in vec2 aStartStop;
+
+void main(void) {
+    vPos = mix(aStartStop.x, aStartStop.y, mix(aPosition.x, aPosition.y, aAxisSelect));
+
+    vStops = aStops;
+    vColor0 = aColor0;
+    vColor1 = aColor1;
+    vColor2 = aColor2;
+    vColor3 = aColor3;
+
+    gl_Position = uTransform * vec4(aTaskRect.xy + aTaskRect.zw * aPosition.xy, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+float linear_step(float edge0, float edge1, float x) {
+    if (edge0 >= edge1) {
+        return 0.0;
+    }
+
+    return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
+}
+
+void main(void) {
+    vec4 color = vColor0;
+
+    color = mix(color, vColor1, linear_step(vStops.x, vStops.y, vPos));
+    color = mix(color, vColor2, linear_step(vStops.y, vStops.z, vPos));
+    color = mix(color, vColor3, linear_step(vStops.z, vStops.w, vPos));
+
+    oFragColor = color;
+}
+#endif
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -2072,39 +2072,87 @@ impl AlphaBatchBuilder {
                                     z_id,
                                     base_instance.into(),
                                 );
                             }
                         }
                     }
                 }
             }
-            PrimitiveInstanceKind::LinearGradient { data_handle, ref visible_tiles_range, .. } => {
+            PrimitiveInstanceKind::LinearGradient { data_handle, gradient_index, .. } => {
+                let gradient = &ctx.prim_store.linear_gradients[gradient_index];
                 let prim_data = &ctx.data_stores.linear_grad[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.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_info.clip_task_index != ClipTaskIndex::INVALID ||
-                        transform_kind == TransformedRectKind::Complex
-                    {
-                        specified_blend_mode
-                    } else {
-                        BlendMode::None
+                let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
+                    transform_kind == TransformedRectKind::Complex
+                {
+                    specified_blend_mode
+                } else {
+                    BlendMode::None
+                };
+
+                if let Some(ref cache_handle) = gradient.cache_handle {
+                    let rt_cache_entry = ctx.resource_cache
+                        .get_cached_render_task(cache_handle);
+                    let cache_item = ctx.resource_cache
+                        .get_texture_cache_item(&rt_cache_entry.handle);
+
+                    if cache_item.texture_id == TextureSource::Invalid {
+                        return;
+                    }
+
+                    let textures = BatchTextures::color(cache_item.texture_id);
+                    let batch_kind = BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id));
+                    let prim_user_data = [
+                        ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                        RasterizationSpace::Local as i32,
+                        get_shader_opacity(1.0),
+                    ];
+                    let segment_user_data = cache_item.uv_rect_handle.as_int(gpu_cache);
+                    prim_header.specific_prim_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
+
+                    let prim_header_index = prim_headers.push(
+                        &prim_header,
+                        z_id,
+                        prim_user_data,
+                    );
+
+                    let batch_key = BatchKey {
+                        blend_mode: non_segmented_blend_mode,
+                        kind: BatchKind::Brush(batch_kind),
+                        textures: textures,
                     };
 
+                    let instance = PrimitiveInstanceData::from(BrushInstance {
+                        segment_index: INVALID_SEGMENT_INDEX,
+                        edge_flags: EdgeAaSegmentMask::all(),
+                        clip_task_address,
+                        brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+                        prim_header_index,
+                        user_data: segment_user_data,
+                    });
+
+                    self.current_batch_list().push_single_instance(
+                        batch_key,
+                        bounding_rect,
+                        z_id,
+                        PrimitiveInstanceData::from(instance),
+                    );
+                } else if gradient.visible_tiles_range.is_empty() {
                     let batch_params = BrushBatchParameters::shared(
                         BrushBatchKind::LinearGradient,
                         BatchTextures::no_texture(),
                         [
                             prim_data.stops_handle.as_int(gpu_cache),
                             0,
                             0,
                         ],
@@ -2136,17 +2184,17 @@ impl AlphaBatchBuilder {
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
                         prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
-                    let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
+                    let visible_tiles = &ctx.scratch.gradient_tiles[gradient.visible_tiles_range];
 
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
                         BrushBatchKind::LinearGradient,
                         specified_blend_mode,
                         bounding_rect,
                         clip_task_address,
--- a/gfx/wr/webrender/src/prim_store/gradient.rs
+++ b/gfx/wr/webrender/src/prim_store/gradient.rs
@@ -1,36 +1,59 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{
     ColorF, ColorU,ExtendMode, GradientStop, LayoutPoint, LayoutSize,
-    LayoutPrimitiveInfo, PremultipliedColorF, LayoutVector2D,
+    LayoutPrimitiveInfo, PremultipliedColorF, LayoutVector2D, LineOrientation,
 };
 use display_list_flattener::IsVisible;
+use euclid::approxeq::ApproxEq;
 use frame_builder::FrameBuildingState;
 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
 use intern::{Internable, InternDebug, Handle as InternHandle};
-use prim_store::{BrushSegment, GradientTileRange};
+use prim_store::{BrushSegment, GradientTileRange, VectorKey};
 use prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData};
 use prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
 use prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
+use render_task::RenderTaskCacheEntryHandle;
 use std::{hash, ops::{Deref, DerefMut}, mem};
 use util::pack_as_float;
 
+/// The maximum number of stops a gradient may have to use the fast path.
+pub const GRADIENT_FP_STOPS: usize = 4;
+
 /// 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, MallocSizeOf, PartialEq)]
+#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)]
 pub struct GradientStopKey {
     pub offset: f32,
     pub color: ColorU,
 }
 
+impl GradientStopKey {
+    pub fn empty() -> Self {
+        GradientStopKey {
+            offset: 0.0,
+            color: ColorU::new(0, 0, 0, 0),
+        }
+    }
+}
+
+impl Into<GradientStopKey> for GradientStop {
+    fn into(self) -> GradientStopKey {
+        GradientStopKey {
+            offset: self.offset,
+            color: self.color.into(),
+        }
+    }
+}
+
 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);
     }
 }
@@ -71,31 +94,43 @@ impl LinearGradientKey {
             reverse_stops: linear_grad.reverse_stops,
             nine_patch: linear_grad.nine_patch,
         }
     }
 }
 
 impl InternDebug for LinearGradientKey {}
 
+#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GradientCacheKey {
+    pub orientation: LineOrientation,
+    pub start_stop_point: VectorKey,
+    pub stops: [GradientStopKey; GRADIENT_FP_STOPS],
+}
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(MallocSizeOf)]
 pub struct LinearGradientTemplate {
     pub common: PrimTemplateCommonData,
     pub extend_mode: ExtendMode,
     pub start_point: LayoutPoint,
     pub end_point: LayoutPoint,
     pub stretch_size: LayoutSize,
     pub tile_spacing: LayoutSize,
     pub stops_opacity: PrimitiveOpacity,
     pub stops: Vec<GradientStop>,
     pub brush_segments: Vec<BrushSegment>,
     pub reverse_stops: bool,
     pub stops_handle: GpuCacheHandle,
+    /// If true, this gradient can be drawn via the fast path
+    /// (cache gradient, and draw as image).
+    pub supports_caching: bool,
 }
 
 impl Deref for LinearGradientTemplate {
     type Target = PrimTemplateCommonData;
     fn deref(&self) -> &Self::Target {
         &self.common
     }
 }
@@ -106,22 +141,54 @@ impl DerefMut for LinearGradientTemplate
     }
 }
 
 impl From<LinearGradientKey> for LinearGradientTemplate {
     fn from(item: LinearGradientKey) -> Self {
         let common = PrimTemplateCommonData::with_key_common(item.common);
         let mut min_alpha: f32 = 1.0;
 
+        // Check if we can draw this gradient via a fast path by caching the
+        // gradient in a smaller task, and drawing as an image.
+        // TODO(gw): Aim to reduce the constraints on fast path gradients in future,
+        //           although this catches the vast majority of gradients on real pages.
+        let mut supports_caching =
+            // No repeating support in fast path
+            item.extend_mode == ExtendMode::Clamp &&
+            // Gradient must cover entire primitive
+            item.tile_spacing.w + item.stretch_size.w >= common.prim_size.width &&
+            item.tile_spacing.h + item.stretch_size.h >= common.prim_size.height &&
+            // Must be a vertical or horizontal gradient
+            (item.start_point.x.approx_eq(&item.end_point.x) ||
+             item.start_point.y.approx_eq(&item.end_point.y)) &&
+            // Fast path supports a limited number of stops
+            item.stops.len() <= GRADIENT_FP_STOPS &&
+            // Fast path not supported on segmented (border-image) gradients.
+            item.nine_patch.is_none();
+
         // Convert the stops to more convenient representation
         // for the current gradient builder.
-        let stops = item.stops.iter().map(|stop| {
+        let mut prev_color = None;
+
+        let stops: Vec<GradientStop> = item.stops.iter().map(|stop| {
             let color: ColorF = stop.color.into();
             min_alpha = min_alpha.min(color.a);
 
+            if let Some(prev_color) = prev_color {
+                // The fast path doesn't support hard color stops, yet.
+                // Since the length of the gradient is a fixed size (512 device pixels), if there
+                // is a hard stop you will see bilinear interpolation with this method, instead
+                // of an abrupt color change.
+                if prev_color == color {
+                    supports_caching = false;
+                }
+            }
+
+            prev_color = Some(color);
+
             GradientStop {
                 offset: stop.offset,
                 color,
             }
         }).collect();
 
         let mut brush_segments = Vec::new();
 
@@ -141,16 +208,17 @@ impl From<LinearGradientKey> for LinearG
             end_point: item.end_point.into(),
             stretch_size: item.stretch_size.into(),
             tile_spacing: item.tile_spacing.into(),
             stops_opacity,
             stops,
             brush_segments,
             reverse_stops: item.reverse_stops,
             stops_handle: GpuCacheHandle::new(),
+            supports_caching,
         }
     }
 }
 
 impl LinearGradientTemplate {
     /// Update the GPU cache for a given primitive template. This may be called multiple
     /// times per frame, by each primitive reference that refers to this interned
     /// template. The initial request call to the GPU cache ensures that work is only
@@ -242,32 +310,44 @@ impl InternablePrimitive for LinearGradi
             info.rect.size,
             self
         )
     }
 
     fn make_instance_kind(
         _key: LinearGradientKey,
         data_handle: LinearGradientDataHandle,
-        _prim_store: &mut PrimitiveStore,
+        prim_store: &mut PrimitiveStore,
         _reference_frame_relative_offset: LayoutVector2D,
     ) -> PrimitiveInstanceKind {
+        let gradient_index = prim_store.linear_gradients.push(LinearGradientPrimitive {
+            cache_handle: None,
+            visible_tiles_range: GradientTileRange::empty(),
+        });
+
         PrimitiveInstanceKind::LinearGradient {
             data_handle,
-            visible_tiles_range: GradientTileRange::empty(),
+            gradient_index,
         }
     }
 }
 
 impl IsVisible for LinearGradient {
     fn is_visible(&self) -> bool {
         true
     }
 }
 
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub struct LinearGradientPrimitive {
+    pub cache_handle: Option<RenderTaskCacheEntryHandle>,
+    pub visible_tiles_range: GradientTileRange,
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 /// Hashable radial gradient parameters, for use during prim interning.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, MallocSizeOf, PartialEq)]
 pub struct RadialGradientParams {
     pub start_radius: f32,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1,39 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ColorF};
 use api::{FilterOp, ImageRendering, TileOffset, RepeatMode, WorldPoint, WorldSize};
-use api::{PremultipliedColorF, PropertyBinding, Shadow};
+use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop};
 use api::{BoxShadowClipMode, LineStyle, LineOrientation, AuHelpers};
 use api::{LayoutPrimitiveInfo, PrimitiveKeyKind};
 use api::units::*;
 use border::{get_max_scale_for_border, build_border_instances};
 use border::BorderSegmentCacheKey;
 use clip::{ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, VisibleFace};
 use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use debug_colors;
 use debug_render::DebugItem;
 use display_list_flattener::{CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
+use euclid::approxeq::ApproxEq;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::{PrimitiveContext, FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::{BrushFlags, SnapOffsets};
 use image::{Repetition};
 use intern;
 use malloc_size_of::MallocSizeOf;
 use picture::{PictureCompositeMode, PicturePrimitive};
 use picture::{ClusterIndex, PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig};
 use prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
-use prim_store::gradient::{LinearGradientDataHandle, RadialGradientDataHandle};
+use prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey};
+use prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle};
 use prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle};
 use prim_store::line_dec::LineDecorationDataHandle;
 use prim_store::picture::PictureDataHandle;
 use prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
 #[cfg(debug_assertions)]
 use render_backend::{FrameId};
 use render_backend::DataStores;
 use render_task::{RenderTask, RenderTaskCacheKey, to_cache_size};
@@ -41,16 +43,17 @@ use render_task::{RenderTaskCacheKeyKind
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
+use texture_cache::TEXTURE_REGION_DIMENSIONS;
 use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler, TransformedRectKind};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use util::{scale_factors, clamp_to_scale_factor};
 use smallvec::SmallVec;
 
 pub mod borders;
 pub mod gradient;
 pub mod image;
@@ -1316,17 +1319,17 @@ pub enum PrimitiveInstanceKind {
     Image {
         /// Handle to the common interned data for this primitive.
         data_handle: ImageDataHandle,
         image_instance_index: ImageInstanceIndex,
     },
     LinearGradient {
         /// Handle to the common interned data for this primitive.
         data_handle: LinearGradientDataHandle,
-        visible_tiles_range: GradientTileRange,
+        gradient_index: LinearGradientIndex,
     },
     RadialGradient {
         /// Handle to the common interned data for this primitive.
         data_handle: RadialGradientDataHandle,
         visible_tiles_range: GradientTileRange,
     },
     /// Clear out a rect, used for special effects.
     Clear {
@@ -1497,16 +1500,18 @@ pub type BorderHandleStorage = storage::
 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>;
+pub type LinearGradientIndex = storage::Index<LinearGradientPrimitive>;
+pub type LinearGradientStorage = storage::Storage<LinearGradientPrimitive>;
 
 /// 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.
@@ -1621,33 +1626,36 @@ impl PrimitiveScratchBuffer {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Debug)]
 pub struct PrimitiveStoreStats {
     picture_count: usize,
     text_run_count: usize,
     opacity_binding_count: usize,
     image_count: usize,
+    linear_gradient_count: usize,
 }
 
 impl PrimitiveStoreStats {
     pub fn empty() -> Self {
         PrimitiveStoreStats {
             picture_count: 0,
             text_run_count: 0,
             opacity_binding_count: 0,
             image_count: 0,
+            linear_gradient_count: 0,
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct PrimitiveStore {
     pub pictures: Vec<PicturePrimitive>,
     pub text_runs: TextRunStorage,
+    pub linear_gradients: LinearGradientStorage,
 
     /// A list of image instances. These are stored separately as
     /// storing them inline in the instance makes the structure bigger
     /// for other types.
     pub images: ImageInstanceStorage,
 
     /// List of animated opacity bindings for a primitive.
     pub opacity_bindings: OpacityBindingStorage,
@@ -1655,25 +1663,27 @@ pub struct PrimitiveStore {
 
 impl PrimitiveStore {
     pub fn new(stats: &PrimitiveStoreStats) -> PrimitiveStore {
         PrimitiveStore {
             pictures: Vec::with_capacity(stats.picture_count),
             text_runs: TextRunStorage::new(stats.text_run_count),
             images: ImageInstanceStorage::new(stats.image_count),
             opacity_bindings: OpacityBindingStorage::new(stats.opacity_binding_count),
+            linear_gradients: LinearGradientStorage::new(stats.linear_gradient_count),
         }
     }
 
     pub fn get_stats(&self) -> PrimitiveStoreStats {
         PrimitiveStoreStats {
             picture_count: self.pictures.len(),
             text_run_count: self.text_runs.len(),
             image_count: self.images.len(),
             opacity_binding_count: self.opacity_bindings.len(),
+            linear_gradient_count: self.linear_gradients.len(),
         }
     }
 
     #[allow(unused)]
     pub fn print_picture_tree(&self, root: PictureIndex) {
         use print_tree::PrintTree;
         let mut pt = PrintTree::new("picture tree");
         self.pictures[root.0].print(&self.pictures, root, &mut pt);
@@ -2725,31 +2735,104 @@ impl PrimitiveStore {
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                     }
                 }
 
                 write_segment(image_instance.segment_instance_index, frame_state, scratch, |request| {
                     image_data.write_prim_gpu_blocks(request);
                 });
             }
-            PrimitiveInstanceKind::LinearGradient { data_handle, ref mut visible_tiles_range, .. } => {
+            PrimitiveInstanceKind::LinearGradient { data_handle, gradient_index, .. } => {
                 let prim_data = &mut data_stores.linear_grad[*data_handle];
+                let gradient = &mut self.linear_gradients[*gradient_index];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
+                if prim_data.supports_caching {
+                    let gradient_size = (prim_data.end_point - prim_data.start_point).to_size();
+
+                    // Calculate what the range of the gradient is that covers this
+                    // primitive. These values are included in the cache key. The
+                    // size of the gradient task is the length of a texture cache
+                    // region, for maximum accuracy, and a minimal size on the
+                    // axis that doesn't matter.
+                    let (size, orientation, start_point, end_point) = if prim_data.start_point.x.approx_eq(&prim_data.end_point.x) {
+                        let start_point = -prim_data.start_point.y / gradient_size.height;
+                        let end_point = (prim_data.common.prim_size.height - prim_data.start_point.y) / gradient_size.height;
+                        let size = DeviceIntSize::new(16, TEXTURE_REGION_DIMENSIONS);
+                        (size, LineOrientation::Vertical, start_point, end_point)
+                    } else {
+                        let start_point = -prim_data.start_point.x / gradient_size.width;
+                        let end_point = (prim_data.common.prim_size.width - prim_data.start_point.x) / gradient_size.width;
+                        let size = DeviceIntSize::new(TEXTURE_REGION_DIMENSIONS, 16);
+                        (size, LineOrientation::Horizontal, start_point, end_point)
+                    };
+
+                    // Build the cache key, including information about the stops.
+                    let mut stops = [GradientStopKey::empty(); GRADIENT_FP_STOPS];
+
+                    // Reverse the stops as required, same as the gradient builder does
+                    // for the slow path.
+                    if prim_data.reverse_stops {
+                        for (src, dest) in prim_data.stops.iter().rev().zip(stops.iter_mut()) {
+                            let stop = GradientStop {
+                                offset: 1.0 - src.offset,
+                                color: src.color,
+                            };
+                            *dest = stop.into();
+                        }
+                    } else {
+                        for (src, dest) in prim_data.stops.iter().zip(stops.iter_mut()) {
+                            *dest = (*src).into();
+                        }
+                    }
+
+                    let cache_key = GradientCacheKey {
+                        orientation,
+                        start_stop_point: VectorKey {
+                            x: start_point,
+                            y: end_point,
+                        },
+                        stops,
+                    };
+
+                    // Request the render task each frame.
+                    gradient.cache_handle = Some(frame_state.resource_cache.request_render_task(
+                        RenderTaskCacheKey {
+                            size: size,
+                            kind: RenderTaskCacheKeyKind::Gradient(cache_key),
+                        },
+                        frame_state.gpu_cache,
+                        frame_state.render_tasks,
+                        None,
+                        prim_data.stops_opacity.is_opaque,
+                        |render_tasks| {
+                            let task = RenderTask::new_gradient(
+                                size,
+                                stops,
+                                orientation,
+                                start_point,
+                                end_point,
+                            );
+
+                            render_tasks.add(task)
+                        }
+                    ));
+                }
+
                 if prim_data.tile_spacing != LayoutSize::zero() {
                     let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
                     let prim_rect = LayoutRect::new(
                         prim_instance.prim_origin,
                         prim_data.common.prim_size,
                     );
 
-                    *visible_tiles_range = decompose_repeated_primitive(
+                    gradient.visible_tiles_range = decompose_repeated_primitive(
                         &prim_info.combined_local_clip_rect,
                         &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
                         prim_context,
                         frame_state,
                         &mut scratch.gradient_tiles,
                         &mut |_, mut request| {
@@ -2763,17 +2846,17 @@ impl PrimitiveStore {
                                 pack_as_float(prim_data.extend_mode as u32),
                                 prim_data.stretch_size.width,
                                 prim_data.stretch_size.height,
                                 0.0,
                             ]);
                         }
                     );
 
-                    if visible_tiles_range.is_empty() {
+                    if gradient.visible_tiles_range.is_empty() {
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                     }
                 }
 
                 // TODO(gw): Consider whether it's worth doing segment building
                 //           for gradient primitives.
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, .. } => {
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -19,16 +19,17 @@ use freelist::{FreeList, FreeListHandle,
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{BorderInstance, ImageSource, UvRectKind, SnapOffsets};
 use internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use prim_store::PictureIndex;
 use prim_store::image::ImageCacheKey;
+use prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey};
 use prim_store::line_dec::LineDecorationCacheKey;
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{ops, mem, usize, f32, i32, u32};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::{RenderPass, RenderTargetIndex};
@@ -377,16 +378,26 @@ pub struct BorderTask {
 pub struct BlitTask {
     pub source: BlitSource,
     pub padding: DeviceIntSideOffsets,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GradientTask {
+    pub stops: [GradientStopKey; GRADIENT_FP_STOPS],
+    pub orientation: LineOrientation,
+    pub start_point: f32,
+    pub end_point: f32,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct LineDecorationTask {
     pub wavy_line_thickness: f32,
     pub style: LineStyle,
     pub orientation: LineOrientation,
     pub local_size: LayoutSize,
 }
 
 #[derive(Debug)]
@@ -406,16 +417,17 @@ pub enum RenderTaskKind {
     VerticalBlur(BlurTask),
     HorizontalBlur(BlurTask),
     #[allow(dead_code)]
     Glyph(GlyphTask),
     Scaling(ScalingTask),
     Blit(BlitTask),
     Border(BorderTask),
     LineDecoration(LineDecorationTask),
+    Gradient(GradientTask),
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClearMode {
     // Applicable to color and alpha targets.
     Zero,
@@ -490,16 +502,36 @@ impl RenderTask {
                 root_spatial_node_index,
                 device_pixel_scale,
             }),
             clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
+    pub fn new_gradient(
+        size: DeviceIntSize,
+        stops: [GradientStopKey; GRADIENT_FP_STOPS],
+        orientation: LineOrientation,
+        start_point: f32,
+        end_point: f32,
+    ) -> Self {
+        RenderTask::with_dynamic_location(
+            size,
+            Vec::new(),
+            RenderTaskKind::Gradient(GradientTask {
+                stops,
+                orientation,
+                start_point,
+                end_point,
+            }),
+            ClearMode::DontCare,
+        )
+    }
+
     pub fn new_blit(
         size: DeviceIntSize,
         source: BlitSource,
     ) -> Self {
         RenderTask::new_blit_with_padding(size, &DeviceIntSideOffsets::zero(), source)
     }
 
     pub fn new_blit_with_padding(
@@ -860,16 +892,17 @@ impl RenderTask {
 
             RenderTaskKind::Scaling(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Glyph(_) |
             RenderTaskKind::Border(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Blit(..) => {
                 UvRectKind::Rect
             }
         }
     }
 
     // Write (up to) 8 floats of data specific to the type
@@ -916,16 +949,17 @@ impl RenderTask {
                 ]
             }
             RenderTaskKind::Glyph(_) => {
                 [0.0, 1.0, 0.0]
             }
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
         };
 
         let (mut target_rect, target_index) = self.get_target_rect();
         // The primitives inside a fixed-location render task
         // are already placed to their corresponding positions,
@@ -957,16 +991,17 @@ impl RenderTask {
             RenderTaskKind::HorizontalBlur(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("texture handle not supported for this task kind");
             }
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
@@ -1026,16 +1061,17 @@ impl RenderTask {
                 RenderTargetKind::Color
             }
 
             RenderTaskKind::Scaling(ref task_info) => {
                 task_info.target_kind
             }
 
             RenderTaskKind::Border(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::Picture(..) => {
                 RenderTargetKind::Color
             }
 
             RenderTaskKind::Blit(..) => {
                 RenderTargetKind::Color
             }
         }
@@ -1062,16 +1098,17 @@ impl RenderTask {
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Glyph(..) => {
                 return;
             }
         };
 
         if let Some(mut request) = gpu_cache.request(cache_handle) {
             let p0 = target_rect.origin.to_f32();
@@ -1121,16 +1158,19 @@ impl RenderTask {
             }
             RenderTaskKind::Blit(ref task) => {
                 pt.new_level("Blit".to_owned());
                 pt.add_item(format!("source: {:?}", task.source));
             }
             RenderTaskKind::Glyph(..) => {
                 pt.new_level("Glyph".to_owned());
             }
+            RenderTaskKind::Gradient(..) => {
+                pt.new_level("Gradient".to_owned());
+            }
         }
 
         pt.add_item(format!("clear to: {:?}", self.clear_mode));
 
         for &child_id in &self.children {
             if tree[child_id].print_with(pt, tree) {
                 pt.add_item(format!("self: {:?}", child_id))
             }
@@ -1159,16 +1199,17 @@ impl RenderTask {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
     #[allow(dead_code)]
     Glyph(GpuGlyphCacheKey),
     BorderSegment(BorderSegmentCacheKey),
     LineDecoration(LineDecorationCacheKey),
+    Gradient(GradientCacheKey),
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
     pub size: DeviceIntSize,
     pub kind: RenderTaskCacheKeyKind,
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -171,16 +171,20 @@ const GPU_TAG_CACHE_CLIP: GpuProfileTag 
 const GPU_TAG_CACHE_BORDER: GpuProfileTag = GpuProfileTag {
     label: "C_Border",
     color: debug_colors::CORNSILK,
 };
 const GPU_TAG_CACHE_LINE_DECORATION: GpuProfileTag = GpuProfileTag {
     label: "C_LineDecoration",
     color: debug_colors::YELLOWGREEN,
 };
+const GPU_TAG_CACHE_GRADIENT: GpuProfileTag = GpuProfileTag {
+    label: "C_Gradient",
+    color: debug_colors::BROWN,
+};
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
     label: "target init",
     color: debug_colors::SLATEGREY,
 };
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
     label: "data init",
     color: debug_colors::LIGHTGREY,
 };
@@ -434,16 +438,72 @@ pub(crate) mod desc {
             VertexAttribute {
                 name: "aOrientation",
                 count: 1,
                 kind: VertexAttributeKind::I32,
             },
         ],
     };
 
+    pub const GRADIENT: VertexDescriptor = VertexDescriptor {
+        vertex_attributes: &[
+            VertexAttribute {
+                name: "aPosition",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+        ],
+        instance_attributes: &[
+            VertexAttribute {
+                name: "aTaskRect",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aStops",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            // TODO(gw): We should probably pack these as u32 colors instead
+            //           of passing as full float vec4 here. It won't make much
+            //           difference in real world, since these are only invoked
+            //           rarely, when creating the cache.
+            VertexAttribute {
+                name: "aColor0",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aColor1",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aColor2",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aColor3",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aAxisSelect",
+                count: 1,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aStartStop",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+        ],
+    };
+
     pub const BORDER: VertexDescriptor = VertexDescriptor {
         vertex_attributes: &[
             VertexAttribute {
                 name: "aPosition",
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
         ],
@@ -676,16 +736,17 @@ pub(crate) enum VertexArrayKind {
     Primitive,
     Blur,
     Clip,
     VectorStencil,
     VectorCover,
     Border,
     Scale,
     LineDecoration,
+    Gradient,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
@@ -1501,16 +1562,17 @@ impl LazyInitializedDebugRenderer {
 // `Renderer::deinit()` below.
 pub struct RendererVAOs {
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
     border_vao: VAO,
     line_vao: VAO,
     scale_vao: VAO,
+    gradient_vao: VAO,
 }
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 ///
 /// We have a separate `Renderer` instance for each instance of WebRender (generally
 /// one per OS window), and all instances share the same thread.
 pub struct Renderer {
@@ -1817,16 +1879,17 @@ impl Renderer {
                                                             &prim_vao,
                                                             options.precache_flags));
 
         let blur_vao = device.create_vao_with_new_instances(&desc::BLUR, &prim_vao);
         let clip_vao = device.create_vao_with_new_instances(&desc::CLIP, &prim_vao);
         let border_vao = device.create_vao_with_new_instances(&desc::BORDER, &prim_vao);
         let scale_vao = device.create_vao_with_new_instances(&desc::SCALE, &prim_vao);
         let line_vao = device.create_vao_with_new_instances(&desc::LINE, &prim_vao);
+        let gradient_vao = device.create_vao_with_new_instances(&desc::GRADIENT, &prim_vao);
         let texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = TextureResolver::new(&mut device);
 
         let prim_header_f_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
         let prim_header_i_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAI32);
         let transforms_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
         let render_task_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
@@ -2020,16 +2083,17 @@ impl Renderer {
             gpu_profile,
             gpu_glyph_renderer,
             vaos: RendererVAOs {
                 prim_vao,
                 blur_vao,
                 clip_vao,
                 border_vao,
                 scale_vao,
+                gradient_vao,
                 line_vao,
             },
             transforms_texture,
             prim_header_i_texture,
             prim_header_f_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
@@ -3827,34 +3891,52 @@ impl Renderer {
 
         // Draw any line decorations for this target.
         if !target.line_decorations.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_LINE_DECORATION);
 
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
 
-            if !target.line_decorations.is_empty() {
-                self.shaders.borrow_mut().cs_line_decoration.bind(
-                    &mut self.device,
-                    &projection,
-                    &mut self.renderer_errors,
-                );
-
-                self.draw_instanced_batch(
-                    &target.line_decorations,
-                    VertexArrayKind::LineDecoration,
-                    &BatchTextures::no_texture(),
-                    stats,
-                );
-            }
+            self.shaders.borrow_mut().cs_line_decoration.bind(
+                &mut self.device,
+                &projection,
+                &mut self.renderer_errors,
+            );
+
+            self.draw_instanced_batch(
+                &target.line_decorations,
+                VertexArrayKind::LineDecoration,
+                &BatchTextures::no_texture(),
+                stats,
+            );
 
             self.set_blend(false, FramebufferKind::Other);
         }
 
+        // Draw any gradients for this target.
+        if !target.gradients.is_empty() {
+            let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_GRADIENT);
+
+            self.set_blend(false, FramebufferKind::Other);
+
+            self.shaders.borrow_mut().cs_gradient.bind(
+                &mut self.device,
+                &projection,
+                &mut self.renderer_errors,
+            );
+
+            self.draw_instanced_batch(
+                &target.gradients,
+                VertexArrayKind::Gradient,
+                &BatchTextures::no_texture(),
+                stats,
+            );
+        }
+
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             {
                 let mut shaders = self.shaders.borrow_mut();
                 match target.target_kind {
                     RenderTargetKind::Alpha => &mut shaders.cs_blur_a8,
@@ -4708,16 +4790,17 @@ impl Renderer {
         self.transforms_texture.deinit(&mut self.device);
         self.prim_header_f_texture.deinit(&mut self.device);
         self.prim_header_i_texture.deinit(&mut self.device);
         self.render_task_texture.deinit(&mut self.device);
         self.device.delete_pbo(self.texture_cache_upload_pbo);
         self.texture_resolver.deinit(&mut self.device);
         self.device.delete_vao(self.vaos.prim_vao);
         self.device.delete_vao(self.vaos.clip_vao);
+        self.device.delete_vao(self.vaos.gradient_vao);
         self.device.delete_vao(self.vaos.blur_vao);
         self.device.delete_vao(self.vaos.line_vao);
         self.device.delete_vao(self.vaos.border_vao);
         self.device.delete_vao(self.vaos.scale_vao);
 
         self.debug.deinit(&mut self.device);
 
         for (_, target) in self.output_targets {
@@ -5485,32 +5568,34 @@ fn get_vao<'a>(vertex_array_kind: Vertex
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
         VertexArrayKind::Border => &vaos.border_vao,
         VertexArrayKind::Scale => &vaos.scale_vao,
         VertexArrayKind::LineDecoration => &vaos.line_vao,
+        VertexArrayKind::Gradient => &vaos.gradient_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
         VertexArrayKind::Border => &vaos.border_vao,
         VertexArrayKind::Scale => &vaos.scale_vao,
         VertexArrayKind::LineDecoration => &vaos.line_vao,
+        VertexArrayKind::Gradient => &vaos.gradient_vao,
     }
 }
 
 #[derive(Clone, Copy, PartialEq)]
 enum FramebufferKind {
     Main,
     Other,
 }
--- a/gfx/wr/webrender/src/shade.rs
+++ b/gfx/wr/webrender/src/shade.rs
@@ -162,16 +162,17 @@ impl LazilyCompiledShader {
                 ShaderKind::VectorStencil => VertexArrayKind::VectorStencil,
                 ShaderKind::VectorCover => VertexArrayKind::VectorCover,
                 ShaderKind::ClipCache => VertexArrayKind::Clip,
             };
 
             let vertex_descriptor = match vertex_format {
                 VertexArrayKind::Primitive => &desc::PRIM_INSTANCES,
                 VertexArrayKind::LineDecoration => &desc::LINE,
+                VertexArrayKind::Gradient => &desc::GRADIENT,
                 VertexArrayKind::Blur => &desc::BLUR,
                 VertexArrayKind::Clip => &desc::CLIP,
                 VertexArrayKind::VectorStencil => &desc::VECTOR_STENCIL,
                 VertexArrayKind::VectorCover => &desc::VECTOR_COVER,
                 VertexArrayKind::Border => &desc::BORDER,
                 VertexArrayKind::Scale => &desc::SCALE,
             };
 
@@ -442,16 +443,17 @@ pub struct Shaders {
     // of these shaders are then used by the primitive shaders.
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
     pub cs_border_segment: LazilyCompiledShader,
     pub cs_border_solid: LazilyCompiledShader,
     pub cs_scale_a8: LazilyCompiledShader,
     pub cs_scale_rgba8: LazilyCompiledShader,
     pub cs_line_decoration: LazilyCompiledShader,
+    pub cs_gradient: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
     brush_radial_gradient: BrushShader,
@@ -662,16 +664,24 @@ impl Shaders {
         let cs_line_decoration = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::LineDecoration),
             "cs_line_decoration",
             &[],
             device,
             options.precache_flags,
         )?;
 
+        let cs_gradient = LazilyCompiledShader::new(
+            ShaderKind::Cache(VertexArrayKind::Gradient),
+            "cs_gradient",
+            &[],
+            device,
+            options.precache_flags,
+        )?;
+
         let cs_border_segment = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_segment",
              &[],
              device,
              options.precache_flags,
         )?;
 
@@ -691,16 +701,17 @@ impl Shaders {
             options.precache_flags,
         )?;
 
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             cs_border_segment,
             cs_line_decoration,
+            cs_gradient,
             cs_border_solid,
             cs_scale_a8,
             cs_scale_rgba8,
             brush_solid,
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
@@ -787,16 +798,17 @@ impl Shaders {
             }
         }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         self.cs_border_solid.deinit(device);
+        self.cs_gradient.deinit(device);
         self.cs_line_decoration.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
 
 // A wrapper around a strong reference to a Shaders
 // object. We have this so that external (ffi)
--- a/gfx/wr/webrender/src/texture_cache.rs
+++ b/gfx/wr/webrender/src/texture_cache.rs
@@ -18,17 +18,17 @@ use render_backend::{FrameId, FrameStamp
 use resource_cache::{CacheItem, CachedImageData};
 use std::cell::Cell;
 use std::cmp;
 use std::mem;
 use std::time::{Duration, SystemTime};
 use std::rc::Rc;
 
 /// The size of each region/layer in shared cache texture arrays.
-const TEXTURE_REGION_DIMENSIONS: i32 = 512;
+pub const TEXTURE_REGION_DIMENSIONS: i32 = 512;
 
 /// The number of slices for picture caching to allocate at start.
 const BASE_PICTURE_TEXTURE_SLICES: usize = 16;
 /// The number of slices to add when we grow out of the current range.
 const ADD_PICTURE_TEXTURE_SLICES: usize = 8;
 /// The chosen image format for picture tiles.
 const PICTURE_TILE_FORMAT: ImageFormat = ImageFormat::BGRA8;
 
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,30 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, BorderStyle, MixBlendMode, PipelineId};
-use api::{DocumentLayer, FilterData, FilterOp, ImageFormat};
+use api::{ColorF, BorderStyle, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{DocumentLayer, FilterData, FilterOp, ImageFormat, LineOrientation};
 use api::units::*;
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::ClipStore;
 use clip_scroll_tree::{ClipScrollTree};
 use debug_render::DebugItem;
 use device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use frame_builder::FrameGlobalResources;
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::{RecordedDirtyRegion, SurfaceInfo};
+use prim_store::gradient::GRADIENT_FP_STOPS;
 use prim_store::{PictureIndex, PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
 use profiler::FrameProfileCounters;
 use render_backend::{DataStores, FrameId};
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::{ArrayAllocationTracker, FreeRectSlice};
@@ -327,16 +328,27 @@ pub struct BlitJob {
 pub struct LineDecorationJob {
     pub task_rect: DeviceRect,
     pub local_size: LayoutSize,
     pub wavy_line_thickness: f32,
     pub style: i32,
     pub orientation: i32,
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+pub struct GradientJob {
+    pub task_rect: DeviceRect,
+    pub stops: [f32; GRADIENT_FP_STOPS],
+    pub colors: [PremultipliedColorF; GRADIENT_FP_STOPS],
+    pub axis_select: f32,
+    pub start_stop: [f32; 2],
+}
+
 #[cfg(feature = "pathfinder")]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphJob {
     pub mesh: Mesh,
     pub target_rect: DeviceIntRect,
     pub origin: DeviceIntPoint,
     pub subpixel_offset: TypedPoint2D<f32, DevicePixel>,
@@ -500,16 +512,17 @@ impl RenderTarget for ColorRenderTarget 
                         pipeline_id,
                         task_id,
                     });
                 }
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Glyph(..) => {
                 // FIXME(pcwalton): Support color glyphs.
                 panic!("Glyphs should not be added to color target!");
             }
             RenderTaskKind::Scaling(..) => {
@@ -643,16 +656,17 @@ impl RenderTarget for AlphaRenderTarget 
             }
         }
 
         match task.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
+            RenderTaskKind::Gradient(..) |
             RenderTaskKind::Glyph(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     BlurDirection::Vertical,
                     render_tasks.get_task_address(task_id),
@@ -731,29 +745,31 @@ pub struct TextureCacheRenderTarget {
     pub target_kind: RenderTargetKind,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub blits: Vec<BlitJob>,
     pub glyphs: Vec<GlyphJob>,
     pub border_segments_complex: Vec<BorderInstance>,
     pub border_segments_solid: Vec<BorderInstance>,
     pub clears: Vec<DeviceIntRect>,
     pub line_decorations: Vec<LineDecorationJob>,
+    pub gradients: Vec<GradientJob>,
 }
 
 impl TextureCacheRenderTarget {
     fn new(target_kind: RenderTargetKind) -> Self {
         TextureCacheRenderTarget {
             target_kind,
             horizontal_blurs: vec![],
             blits: vec![],
             glyphs: vec![],
             border_segments_complex: vec![],
             border_segments_solid: vec![],
             clears: vec![],
             line_decorations: vec![],
+            gradients: vec![],
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         render_tasks: &mut RenderTaskTree,
     ) {
@@ -816,16 +832,38 @@ impl TextureCacheRenderTarget {
                     } else {
                         self.border_segments_complex.push(instance);
                     }
                 }
             }
             RenderTaskKind::Glyph(ref mut task_info) => {
                 self.add_glyph_task(task_info, target_rect.0)
             }
+            RenderTaskKind::Gradient(ref task_info) => {
+                let mut stops = [0.0; 4];
+                let mut colors = [PremultipliedColorF::BLACK; 4];
+
+                let axis_select = match task_info.orientation {
+                    LineOrientation::Horizontal => 0.0,
+                    LineOrientation::Vertical => 1.0,
+                };
+
+                for (stop, (offset, color)) in task_info.stops.iter().zip(stops.iter_mut().zip(colors.iter_mut())) {
+                    *offset = stop.offset;
+                    *color = ColorF::from(stop.color).premultiplied();
+                }
+
+                self.gradients.push(GradientJob {
+                    task_rect: target_rect.0.to_f32(),
+                    axis_select,
+                    stops,
+                    colors,
+                    start_stop: [task_info.start_point, task_info.end_point],
+                });
+            }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
--- a/gfx/wr/webrender/tests/angle_shader_validation.rs
+++ b/gfx/wr/webrender/tests/angle_shader_validation.rs
@@ -47,16 +47,20 @@ const SHADERS: &[Shader] = &[
         name: "cs_border_segment",
         features: CACHE_FEATURES,
     },
     Shader {
         name: "cs_line_decoration",
         features: CACHE_FEATURES,
     },
     Shader {
+        name: "cs_gradient",
+        features: CACHE_FEATURES,
+    },
+    Shader {
         name: "cs_border_solid",
         features: CACHE_FEATURES,
     },
     // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
--- a/gfx/wr/wrench/reftests/border/reftest.list
+++ b/gfx/wr/wrench/reftests/border/reftest.list
@@ -1,10 +1,10 @@
 platform(linux,mac) == border-clamp-corner-radius.yaml border-clamp-corner-radius.png
-== border-gradient-simple.yaml border-gradient-simple-ref.yaml
+fuzzy(1,790) == border-gradient-simple.yaml border-gradient-simple-ref.yaml
 platform(linux,mac) == border-gradient-nine-patch.yaml border-gradient-nine-patch.png
 == border-radial-gradient-simple.yaml border-radial-gradient-simple-ref.yaml
 platform(linux,mac) == border-radial-gradient-nine-patch.yaml border-radial-gradient-nine-patch.png
 == border-radii.yaml border-radii.png
 == border-none.yaml border-none-ref.yaml
 fuzzy(1,68) == border-overlapping.yaml border-overlapping-ref.yaml
 == border-invisible.yaml border-invisible-ref.yaml
 platform(linux,mac) == border-suite.yaml border-suite.png
index 55518031cddf5a0bf2e8f56c475372bc7a84bdd4..95ee7491e82570e65c12d0411492c5055c60b34a
GIT binary patch
literal 6652
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
index 1d06f241bc72ad97c13159e86f26e292b1ff5745..ed80ec9988bc2a403208590727337e5c2ca23215
GIT binary patch
literal 912
zc%17D@N?(olHy`uVBq!ia0y~yU}OMc4mKbaQ?w!tNU;<<d4_NRK@CSr2Ll7MyQhm|
zNX4AD*9`fX6h#~yBi`4i96WWzjazzBt?^m)g$%RApUp~MpWX93x##<znEKiO-&sEX
zeoyYR9RJRflGSGm)33keW_ZrQ(84*2MiKSU*}dQGYB?Y9*fVel4|-J1DE#iicTMTJ
RmcT5=;OXk;vd$@?2>@ZosV@Kk
index 71436ebf36a6b13f1911e900bac0bb1e967b8c16..b27b9df586035770229313f74b28222318a890d6
GIT binary patch
literal 10340
zc%17D@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|7#K9PJzX3_D(1YsdXS4bT9ozT
za{G1vcAQ_=azaYhK!SafdB}aO7e9ZVo`3$|jcvKNxBV}$R=5phgx_Yo38ePj;yVGP
zzFjSI0LiZ3#=Hqg)ozuY0Hofoe%p`(R2-kno&uzPXN_tlMeFbVmJ0R%9>1^u^K-iW
zzq9uLf7LnjNApMbul~9L2zNvwam4GtFxFo<eC_bH$62;HK)CK2g7f#``}*p8xxe-E
z*;#OCw^n%ny?O2Nt5r9uZu~mTcj8<3+lB{;uM4-zZ3Dva*NLxToPSB|PyW`y^w-~D
zyjizVR=@_;Mkpu#O~ao1{4o9eK!53PV{U4P+EW#?wtX#(^Q&8S!uQ_?VJ=Q&Px*bc
z%;5u?JE4|tWd2lV57Te|rXgql4L%MucS1S(We)e=v%~ba%T9PJew*<i_Fy{7cjEWo
z1eiZJFmL*sWUWwv>P{$U{|&~<{qivV^1x_~&lwHe(ZC%I+|j@t4cyVd9Sz*kz#R?T
z(ZC%I+|j@t4cyVd9Sz*kz#R?T(ZC%I+|j@t4cyVd9Sz*kz#R?T(ZC%I+|j@t4cyVd
z9Sz*kz@=v3{x^rLCAptB7*?{3Y8}-&s&%Mq{igqTg2>w#A%lesK;Y@>=d#Wzp$P!7
C-nXs*
index c6525703f690609bbe95b3334305515bb2147a4c..6fc18cd8c0eb2931605546f016a87a0b0363dd2a
GIT binary patch
literal 12560
zc%17D@N?(olHy`uVBq!ia0y~yV2S`?4mP03zO)&47#Iw$d%8G=RLprhjgd>2d*ac3
zyZ)=5w~%csSopw3V3wPSCf`Y+SNZ>IerPKJLEK_?5dA2%Y6F<NY?Mn|?!RKWHT(bn
z{r|nc{?C~=scG@X)iZCNKC>_Fo6+X=Gxx%U`v3pjzW?8^`_KP9w>*3E^qg7{vG>f)
z>mUM|+q<))<42#I4sMgGH`I+PA5~74axgKfd{p_U@=@iZ%14!tDj!uosvNzoE-SBd
zF7DZzR1lF3BXZ8Tx*RG0l3@6I7Eo7Q+Beh9(`VM61?ti_1`E~QY5w|pRQag#QRSn`
zX{r3rdb<eJaRVr8P?Vmd%14!tDj!uos(e)WsPa+eqsm8>k18KkKB^p{{5q&#H>!M8
z`Ka<y<pZz$|2_6@#8?whZFZVIT>E3WHSzJq-;C11M6#Ze(!Ry)ul>=@g_eNw-xL1+
znzebl<!=xnT`Va0QPwu1-Uw|B3u@Kh9qg~KUxzP*nbF<R@&3iZUz5?@0JZA4ZN&b4
zpmBLHaV*BgrN*8A+I)03K&`TWcktI&({%mfZy>_#nUc~Y>#7a^QqbH0wd!{v|F!k&
zEvsj41`~$jf`a!iHh)zoCDhm0qq_m`2;bk6Lo&#Z*Pm+^6$ILn@5CSG;^K1T^p^xz
yAic+Dlsn2D<&JVkxue`s?kIPZJIck*{Ud+FH*2%@Kjy0pK;Y@>=d#Wzp$P!^m+X81
--- a/gfx/wr/wrench/reftests/gradient/reftest.list
+++ b/gfx/wr/wrench/reftests/gradient/reftest.list
@@ -13,17 +13,17 @@ platform(linux,mac) fuzzy(1,35000) == li
 == linear-clamp-1a.yaml linear-clamp-1-ref.yaml
 == linear-clamp-1b.yaml linear-clamp-1-ref.yaml
 == linear-clamp-2.yaml linear-clamp-2-ref.yaml
 
 # dithering requires us to fuzz here
 fuzzy(1,20000) == linear.yaml linear-ref.yaml
 fuzzy(1,20000) == linear-reverse.yaml linear-ref.yaml
 
-== linear-aligned-clip.yaml linear-aligned-clip-ref.yaml
+fuzzy(1,15200) == linear-aligned-clip.yaml linear-aligned-clip-ref.yaml
 
 platform(linux,mac) fuzzy(1,80000) == radial-circle.yaml radial-circle-ref.png
 platform(linux,mac) fuzzy(1,80000) == radial-ellipse.yaml radial-ellipse-ref.png
 
 != radial-circle.yaml radial-ellipse.yaml
 
 == norm-linear-1.yaml norm-linear-1-ref.yaml
 == norm-linear-2.yaml norm-linear-2-ref.yaml
@@ -39,19 +39,19 @@ platform(linux,mac) fuzzy(1,80000) == ra
 # fuzzy because of differences from normalization
 # this might be able to be improved
 fuzzy(255,1200) == repeat-linear.yaml repeat-linear-ref.yaml
 fuzzy(255,1200) == repeat-linear-reverse.yaml repeat-linear-ref.yaml
 fuzzy(255,2664) == repeat-radial.yaml repeat-radial-ref.yaml
 fuzzy(255,2664) == repeat-radial-negative.yaml repeat-radial-ref.yaml
 
 # fuzzy because of thin spaced out column of pixels that are 1 off
-fuzzy(1,50) == tiling-linear-1.yaml tiling-linear-1-ref.yaml
-fuzzy(1,38) == tiling-linear-2.yaml tiling-linear-2-ref.yaml
-== tiling-linear-3.yaml tiling-linear-3-ref.yaml
+fuzzy(1,83164) == tiling-linear-1.yaml tiling-linear-1-ref.yaml
+fuzzy(1,46279) == tiling-linear-2.yaml tiling-linear-2-ref.yaml
+fuzzy(1,62154) == tiling-linear-3.yaml tiling-linear-3-ref.yaml
 
 fuzzy(1,17) == tiling-radial-1.yaml tiling-radial-1-ref.yaml
 fuzzy(1,1) == tiling-radial-2.yaml tiling-radial-2-ref.yaml
 == tiling-radial-3.yaml tiling-radial-3-ref.yaml
 fuzzy(1,17) == tiling-radial-4.yaml tiling-radial-4-ref.yaml
 
 == radial-zero-size-1.yaml radial-zero-size-ref.yaml
 == radial-zero-size-2.yaml radial-zero-size-ref.yaml
index 10dd14b48af77b2ad3127c3da4f04da27a65156b..49fb1195df215bd5cddf2247d756dea62f87d7f3
GIT binary patch
literal 19136
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
M_C7rC&SRhd2e>LLL;wH)
--- a/layout/reftests/css-blending/reftest.list
+++ b/layout/reftests/css-blending/reftest.list
@@ -1,22 +1,22 @@
 == blend-canvas.html blend-canvas-ref.html
 == blend-constant-background-color.html blend-constant-background-color-ref.html
-fuzzy-if(webrender,1-3,1291-7888) == blend-gradient-background-color.html blend-gradient-background-color-ref.html
+== blend-gradient-background-color.html blend-gradient-background-color-ref.html
 == blend-image.html blend-image-ref.html
 == blend-difference-stacking.html blend-difference-stacking-ref.html
 
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-1,0-10000) fuzzy-if(skiaContent,0-1,0-30000) == background-blending-alpha.html background-blending-alpha-ref.html
-fuzzy-if(webrender,1-3,1291-7888) == background-blending-gradient-color.html background-blending-gradient-color-ref.html
-fuzzy-if(azureSkiaGL,0-3,0-7597) fuzzy-if(cocoaWidget,0-3,0-7597) fuzzy-if(d2d,0-1,0-3800) fuzzy-if(d3d11,0-1,0-4200) fuzzy-if(skiaContent,0-2,0-9450) fuzzy-if(webrender,1-5,3938-23925) == background-blending-gradient-gradient.html background-blending-gradient-gradient-ref.html
-fuzzy-if(azureSkiaGL,0-2,0-7174) fuzzy-if(webrender,1-3,1288-7888) == background-blending-gradient-image.html background-blending-gradient-color-ref.html
+== background-blending-gradient-color.html background-blending-gradient-color-ref.html
+fuzzy-if(azureSkiaGL,0-3,0-7597) fuzzy-if(cocoaWidget,0-3,0-7597) fuzzy-if(d2d,0-1,0-3800) fuzzy-if(d3d11,0-1,0-4200) fuzzy-if(skiaContent,0-2,0-9450) fuzzy-if(webrender,1-1,2400-6200) == background-blending-gradient-gradient.html background-blending-gradient-gradient-ref.html
+fuzzy-if(azureSkiaGL,0-2,0-7174) == background-blending-gradient-image.html background-blending-gradient-color-ref.html
 fuzzy-if(azureSkia||d2d||gtkWidget,0-1,0-10000) == background-blending-image-color-jpg.html background-blending-image-color-ref.html
 == background-blending-image-color-png.html background-blending-image-color-ref.html
 == background-blending-image-color-svg.html background-blending-image-color-ref.html
-fuzzy-if(azureSkiaGL,0-2,0-7174) fuzzy-if(webrender,1-3,1288-7888) == background-blending-image-gradient.html background-blending-gradient-color-ref.html
+fuzzy-if(azureSkiaGL,0-2,0-7174) == background-blending-image-gradient.html background-blending-gradient-color-ref.html
 == background-blending-image-image.html background-blending-image-color-ref.html
 == background-blending-isolation.html background-blending-isolation-ref.html
 == background-blending-list-repeat.html background-blending-list-repeat-ref.html
 == background-blending-multiple-images.html background-blending-multiple-images-ref.html
 
 == background-blending-color-burn.html background-blending-color-burn-ref.svg
 == background-blending-color-dodge.html background-blending-color-dodge-ref.svg
 # need to investigate why these tests are fuzzy - first suspect is a possible color space conversion on some platforms; same for mix-blend-mode tests
--- a/layout/reftests/css-gradients/reftest.list
+++ b/layout/reftests/css-gradients/reftest.list
@@ -68,27 +68,27 @@ fuzzy-if(d2d,0-127,0-2612) == repeating-
 fuzzy-if(skiaContent,0-18,0-600) == twostops-1a.html twostops-1-ref.html
 fuzzy-if(skiaContent,0-18,0-600) == twostops-1b.html twostops-1-ref.html
 fuzzy-if(skiaContent,0-226,0-600) == twostops-1c.html twostops-1-ref.html
 fuzzy-if(skiaContent,0-141,0-300) == twostops-1d.html twostops-1-ref.html
 fuzzy-if(skiaContent,0-73,0-900) == twostops-1e.html twostops-1-ref.html
 
 # from http://www.xanthir.com/:4bhipd by way of http://a-ja.net/newgrad.html
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-3,0-20000) fuzzy-if(azureSkiaGL||skiaContent&&layersGPUAccelerated,0-8,0-20000) == aja-linear-1a.html aja-linear-1-ref.html
-fails-if(!d2d&&!skiaContent) fuzzy-if(skiaContent,0-1,0-20000) fuzzy-if(webrender&&winWidget,1-2,11550-11789) == aja-linear-1b.html aja-linear-1-ref.html # bug 526694
+fails-if(!d2d&&!skiaContent) fuzzy-if(skiaContent,0-1,0-20000) fuzzy-if(webrender&&winWidget,1-1,5300-5500) == aja-linear-1b.html aja-linear-1-ref.html # bug 526694
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-3,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-1c.html aja-linear-1-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-3,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-1d.html aja-linear-1-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-3,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-1e.html aja-linear-1-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-3,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-1f.html aja-linear-1-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-2,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-2.html aja-linear-2-ref.html
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-2,0-19999) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-3a.html aja-linear-3-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-2,0-19999) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-3b.html aja-linear-3-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-4,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-4a.html aja-linear-4-ref.html 
 fuzzy-if(!contentSameGfxBackendAsCanvas,0-4,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) == aja-linear-4b.html aja-linear-4-ref.html 
-fuzzy-if(!contentSameGfxBackendAsCanvas,0-4,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) fuzzy-if(webrender&&winWidget,4-9,15926-16125) == aja-linear-5a.html aja-linear-5-ref.html 
+fuzzy-if(!contentSameGfxBackendAsCanvas,0-4,0-20000) fuzzy-if(azureSkiaGL||skiaContent,0-8,0-20000) fuzzy-if(webrender&&winWidget,1-1,5900-6100) == aja-linear-5a.html aja-linear-5-ref.html
 fuzzy-if(Android,0-6,0-10576) == height-dependence-1.html height-dependence-1-ref.html
 fuzzy-if(cocoaWidget,0-1,0-40000) fuzzy-if(Android,0-6,0-10576) == height-dependence-2.html height-dependence-2-ref.html
 fuzzy-if(Android,0-6,0-10576) == height-dependence-3.html height-dependence-3-ref.html
 
 == linear-onestopposition-1.html linear-onestopposition-1-ref.html
 fuzzy-if(d2d,0-47,0-400) fuzzy-if(webrender&&winWidget,0-1,0-1375) == linear-onestopposition-1.html linear-onestopposition-1-ref2.html # d2d interpolates the hard stop
 == radial-onestopposition-1a.html radial-onestopposition-1-ref.html
 == radial-onestopposition-1b.html radial-onestopposition-1-ref.html
--- a/layout/reftests/xul/reftest.list
+++ b/layout/reftests/xul/reftest.list
@@ -7,18 +7,18 @@ random-if(Android) == menulist-shrinkwra
 == textbox-overflow-1.xul textbox-overflow-1-ref.xul # for bug 749658
 # accesskeys are not normally displayed on Mac, so skip this test
 skip-if(cocoaWidget) == accesskey.xul accesskey-ref.xul
 pref(layout.css.xul-tree-pseudos.content.enabled,true) fuzzy-if(xulRuntime.widgetToolkit=="gtk3",0-1,0-11) == tree-row-outline-1.xul tree-row-outline-1-ref.xul # win8: bug 1254832
 skip-if(!cocoaWidget) fails-if(webrender&&cocoaWidget) == mac-tab-toolbar.xul mac-tab-toolbar-ref.xul
 pref(layout.css.xul-tree-pseudos.content.enabled,true) != tree-row-outline-1.xul tree-row-outline-1-notref.xul
 == text-crop.xul text-crop-ref.xul
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == text-small-caps-1.xul text-small-caps-1-ref.xul
-fuzzy-if(skiaContent,0-1,0-60) fuzzy-if(cocoaWidget&&browserIsRemote&&!skiaContent,0-1,0-31) fuzzy-if(winWidget&&browserIsRemote&&layersGPUAccelerated,0-1,0-50) == inactive-fixed-bg-bug1205630.xul inactive-fixed-bg-bug1205630-ref.html
-fuzzy-if(skiaContent,0-1,0-60) fuzzy-if(cocoaWidget&&browserIsRemote&&!skiaContent,0-1,0-31) fuzzy-if(winWidget&&browserIsRemote&&layersGPUAccelerated,0-1,0-50) == inactive-fixed-bg-bug1272525.xul inactive-fixed-bg-bug1272525-ref.html
+fuzzy-if(skiaContent,0-1,0-60) fuzzy-if(cocoaWidget&&browserIsRemote&&!skiaContent,0-1,0-31) fuzzy-if(winWidget&&browserIsRemote&&layersGPUAccelerated,0-1,0-50) fuzzy-if(webrender,0-1,350-1050) == inactive-fixed-bg-bug1205630.xul inactive-fixed-bg-bug1205630-ref.html
+fuzzy-if(skiaContent,0-1,0-60) fuzzy-if(cocoaWidget&&browserIsRemote&&!skiaContent,0-1,0-31) fuzzy-if(winWidget&&browserIsRemote&&layersGPUAccelerated,0-1,0-50) fuzzy-if(webrender,0-1,450-1100) == inactive-fixed-bg-bug1272525.xul inactive-fixed-bg-bug1272525-ref.html
 
 # Tests for XUL <image> with 'object-fit' & 'object-position':
 # These tests should be very similar to tests in our w3c-css/submitted/images3
 # reftest directory. They live here because they use XUL, and it
 # wouldn't be fair of us to make a W3C testsuite implicitly depend on XUL.
 == object-fit-contain-png-001.xul object-fit-contain-png-001-ref.html
 == object-fit-contain-png-002.xul object-fit-contain-png-002-ref.html
 == object-fit-contain-svg-001.xul object-fit-contain-svg-001-ref.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-backgrounds/background-gradient-subpixel-fills-area.html.ini
@@ -0,0 +1,3 @@
+[background-gradient-subpixel-fills-area.html]
+  expected:
+    if webrender: FAIL