Bug 1614890 - Implement conic-gradient for WebRender graphics backend. r=gw,nical
☠☠ backed out by 7a4bbabfcc67 ☠ ☠
authorTim Nguyen <ntim.bugs@gmail.com>
Wed, 12 Feb 2020 08:50:06 +0000
changeset 513488 53650b8686a6cf9ff55b4f83e33042d97efd7a9d
parent 513487 3e34513993e2bc8b70ce5cdcb035661c6f845b4d
child 513489 bd3c32f19d2c1dbdaa8af0ccaebc6327c78f2934
push id37116
push userrmaries@mozilla.com
push dateWed, 12 Feb 2020 20:57:45 +0000
treeherdermozilla-central@e84a0546e6e2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw, nical
bugs1614890
milestone75.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 1614890 - Implement conic-gradient for WebRender graphics backend. r=gw,nical Differential Revision: https://phabricator.services.mozilla.com/D61599
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/wr/webrender/res/brush.glsl
gfx/wr/webrender/res/brush_conic_gradient.glsl
gfx/wr/webrender/res/brush_multi.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/gpu_types.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/gradient.rs
gfx/wr/webrender/src/prim_store/interned.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/profiler.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/scene_builder_thread.rs
gfx/wr/webrender/src/scene_building.rs
gfx/wr/webrender/src/shade.rs
gfx/wr/webrender_api/src/api.rs
gfx/wr/webrender_api/src/display_item.rs
gfx/wr/webrender_api/src/display_list.rs
gfx/wr/webrender_api/src/gradient_builder.rs
gfx/wr/wrench/reftests/gradient/conic-color-wheel.yaml
gfx/wr/wrench/reftests/gradient/conic-simple.yaml
gfx/wr/wrench/reftests/gradient/conic-start-angle.yaml
gfx/wr/wrench/src/yaml_frame_reader.rs
gfx/wr/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -1190,16 +1190,27 @@ void DisplayListBuilder::PushRadialGradi
     wr::ExtendMode aExtendMode, const wr::LayoutSize aTileSize,
     const wr::LayoutSize aTileSpacing) {
   wr_dp_push_radial_gradient(
       mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
       &mCurrentSpaceAndClipChain, aCenter, aRadius, aStops.Elements(),
       aStops.Length(), aExtendMode, aTileSize, aTileSpacing);
 }
 
+void DisplayListBuilder::PushConicGradient(
+    const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+    bool aIsBackfaceVisible, const wr::LayoutPoint& aCenter, const float aAngle,
+    const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+    const wr::LayoutSize aTileSize, const wr::LayoutSize aTileSpacing) {
+  wr_dp_push_conic_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
+                            aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+                            aCenter, aAngle, aStops.Elements(), aStops.Length(),
+                            aExtendMode, aTileSize, aTileSpacing);
+}
+
 void DisplayListBuilder::PushImage(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, wr::ImageRendering aFilter, wr::ImageKey aImage,
     bool aPremultipliedAlpha, const wr::ColorF& aColor) {
   wr::LayoutRect clip = MergeClipLeaf(aClip);
   WRDL_LOG("PushImage b=%s cl=%s\n", mWrState, Stringify(aBounds).c_str(),
            Stringify(clip).c_str());
   wr_dp_push_image(mWrState, aBounds, clip, aIsBackfaceVisible,
@@ -1312,16 +1323,28 @@ void DisplayListBuilder::PushBorderRadia
     const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
     const wr::LayoutSideOffsets& aOutset) {
   wr_dp_push_border_radial_gradient(
       mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
       &mCurrentSpaceAndClipChain, aWidths, aFill, aCenter, aRadius,
       aStops.Elements(), aStops.Length(), aExtendMode, aOutset);
 }
 
+void DisplayListBuilder::PushBorderConicGradient(
+    const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+    bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
+    const wr::LayoutPoint& aCenter, const float aAngle,
+    const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+    const wr::LayoutSideOffsets& aOutset) {
+  wr_dp_push_border_conic_gradient(
+      mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+      &mCurrentSpaceAndClipChain, aWidths, aFill, aCenter, aAngle,
+      aStops.Elements(), aStops.Length(), aExtendMode, aOutset);
+}
+
 void DisplayListBuilder::PushText(const wr::LayoutRect& aBounds,
                                   const wr::LayoutRect& aClip,
                                   bool aIsBackfaceVisible,
                                   const wr::ColorF& aColor,
                                   wr::FontInstanceKey aFontKey,
                                   Range<const wr::GlyphInstance> aGlyphBuffer,
                                   const wr::GlyphOptions* aGlyphOptions) {
   wr_dp_push_text(mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -488,16 +488,24 @@ class DisplayListBuilder final {
                           const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                           const wr::LayoutPoint& aCenter,
                           const wr::LayoutSize& aRadius,
                           const nsTArray<wr::GradientStop>& aStops,
                           wr::ExtendMode aExtendMode,
                           const wr::LayoutSize aTileSize,
                           const wr::LayoutSize aTileSpacing);
 
+  void PushConicGradient(const wr::LayoutRect& aBounds,
+                         const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+                         const wr::LayoutPoint& aCenter, const float aAngle,
+                         const nsTArray<wr::GradientStop>& aStops,
+                         wr::ExtendMode aExtendMode,
+                         const wr::LayoutSize aTileSize,
+                         const wr::LayoutSize aTileSpacing);
+
   void PushImage(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                  bool aIsBackfaceVisible, wr::ImageRendering aFilter,
                  wr::ImageKey aImage, bool aPremultipliedAlpha = true,
                  const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f});
 
   void PushRepeatingImage(
       const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
       bool aIsBackfaceVisible, const wr::LayoutSize& aStretchSize,
@@ -552,16 +560,23 @@ class DisplayListBuilder final {
 
   void PushBorderRadialGradient(
       const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
       bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
       const wr::LayoutPoint& aCenter, const wr::LayoutSize& aRadius,
       const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
       const wr::LayoutSideOffsets& aOutset);
 
+  void PushBorderConicGradient(
+      const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+      bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
+      const wr::LayoutPoint& aCenter, const float aAngle,
+      const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+      const wr::LayoutSideOffsets& aOutset);
+
   void PushText(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                 bool aIsBackfaceVisible, const wr::ColorF& aColor,
                 wr::FontInstanceKey aFontKey,
                 Range<const wr::GlyphInstance> aGlyphBuffer,
                 const wr::GlyphOptions* aGlyphOptions = nullptr);
 
   void PushLine(const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                 const wr::Line& aLine);
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -3360,16 +3360,79 @@ pub extern "C" fn wr_dp_push_border_radi
         &prim_info,
         rect,
         widths.into(),
         border_details,
     );
 }
 
 #[no_mangle]
+pub extern "C" fn wr_dp_push_border_conic_gradient(state: &mut WrState,
+                                                   rect: LayoutRect,
+                                                   clip: LayoutRect,
+                                                   is_backface_visible: bool,
+                                                   parent: &WrSpaceAndClipChain,
+                                                   widths: LayoutSideOffsets,
+                                                   fill: bool,
+                                                   center: LayoutPoint,
+                                                   angle: f32,
+                                                   stops: *const GradientStop,
+                                                   stops_count: usize,
+                                                   extend_mode: ExtendMode,
+                                                   outset: LayoutSideOffsets) {
+    debug_assert!(unsafe { is_in_main_thread() });
+
+    let stops_slice = unsafe { make_slice(stops, stops_count) };
+    let stops_vector = stops_slice.to_owned();
+
+    let slice = SideOffsets2D::new(
+        widths.top as i32,
+        widths.right as i32,
+        widths.bottom as i32,
+        widths.left as i32,
+    );
+
+    let gradient = state.frame_builder.dl_builder.create_conic_gradient(
+        center.into(),
+        angle,
+        stops_vector,
+        extend_mode.into()
+    );
+
+    let border_details = BorderDetails::NinePatch(NinePatchBorder {
+        source: NinePatchBorderSource::ConicGradient(gradient),
+        width: rect.size.width as i32,
+        height: rect.size.height as i32,
+        slice,
+        fill,
+        outset: outset.into(),
+        repeat_horizontal: RepeatMode::Stretch,
+        repeat_vertical: RepeatMode::Stretch,
+    });
+
+    let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+    let prim_info = CommonItemProperties {
+        clip_rect: clip,
+        clip_id: space_and_clip.clip_id,
+        spatial_id: space_and_clip.spatial_id,
+        flags: prim_flags(is_backface_visible),
+        hit_info: state.current_tag,
+        item_key: state.current_item_key,
+    };
+
+    state.frame_builder.dl_builder.push_border(
+        &prim_info,
+        rect,
+        widths.into(),
+        border_details,
+    );
+}
+
+#[no_mangle]
 pub extern "C" fn wr_dp_push_linear_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
                                              is_backface_visible: bool,
                                              parent: &WrSpaceAndClipChain,
                                              start_point: LayoutPoint,
                                              end_point: LayoutPoint,
                                              stops: *const GradientStop,
@@ -3449,16 +3512,60 @@ pub extern "C" fn wr_dp_push_radial_grad
         &prim_info,
         rect,
         gradient,
         tile_size,
         tile_spacing);
 }
 
 #[no_mangle]
+pub extern "C" fn wr_dp_push_conic_gradient(state: &mut WrState,
+                                            rect: LayoutRect,
+                                            clip: LayoutRect,
+                                            is_backface_visible: bool,
+                                            parent: &WrSpaceAndClipChain,
+                                            center: LayoutPoint,
+                                            angle: f32,
+                                            stops: *const GradientStop,
+                                            stops_count: usize,
+                                            extend_mode: ExtendMode,
+                                            tile_size: LayoutSize,
+                                            tile_spacing: LayoutSize) {
+    debug_assert!(unsafe { is_in_main_thread() });
+
+    let stops_slice = unsafe { make_slice(stops, stops_count) };
+    let stops_vector = stops_slice.to_owned();
+
+    let gradient = state.frame_builder
+                        .dl_builder
+                        .create_conic_gradient(center.into(),
+                                               angle,
+                                               stops_vector,
+                                               extend_mode.into());
+
+    let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+    let prim_info = CommonItemProperties {
+        clip_rect: clip,
+        clip_id: space_and_clip.clip_id,
+        spatial_id: space_and_clip.spatial_id,
+        flags: prim_flags(is_backface_visible),
+        hit_info: state.current_tag,
+        item_key: state.current_item_key,
+    };
+
+    state.frame_builder.dl_builder.push_conic_gradient(
+        &prim_info,
+        rect,
+        gradient,
+        tile_size,
+        tile_spacing);
+}
+
+#[no_mangle]
 pub extern "C" fn wr_dp_push_box_shadow(state: &mut WrState,
                                         _rect: LayoutRect,
                                         clip: LayoutRect,
                                         is_backface_visible: bool,
                                         parent: &WrSpaceAndClipChain,
                                         box_bounds: LayoutRect,
                                         offset: LayoutVector2D,
                                         color: ColorF,
--- a/gfx/wr/webrender/res/brush.glsl
+++ b/gfx/wr/webrender/res/brush.glsl
@@ -84,16 +84,17 @@ void name(                              
 
 // Forward-declare all brush vertex entry points.
 FWD_DECLARE_VS_FUNCTION(image_brush_vs)
 FWD_DECLARE_VS_FUNCTION(solid_brush_vs)
 FWD_DECLARE_VS_FUNCTION(blend_brush_vs)
 FWD_DECLARE_VS_FUNCTION(mix_blend_brush_vs)
 FWD_DECLARE_VS_FUNCTION(linear_gradient_brush_vs)
 FWD_DECLARE_VS_FUNCTION(radial_gradient_brush_vs)
+FWD_DECLARE_VS_FUNCTION(conic_gradient_brush_vs)
 FWD_DECLARE_VS_FUNCTION(yuv_brush_vs)
 FWD_DECLARE_VS_FUNCTION(opacity_brush_vs)
 
 void multi_brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
@@ -253,16 +254,17 @@ struct Fragment {
 
 // Foward-declare all brush entry-points.
 Fragment image_brush_fs();
 Fragment solid_brush_fs();
 Fragment blend_brush_fs();
 Fragment mix_blend_brush_fs();
 Fragment linear_gradient_brush_fs();
 Fragment radial_gradient_brush_fs();
+Fragment conic_gradient_brush_fs();
 Fragment yuv_brush_fs();
 Fragment opacity_brush_fs();
 Fragment multi_brush_fs(int brush_kind);
 
 void main(void) {
 #ifdef WR_FEATURE_DEBUG_OVERDRAW
     oFragColor = WR_DEBUG_OVERDRAW_COLOR;
 #else
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_conic_gradient.glsl
@@ -0,0 +1,140 @@
+/* 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/. */
+
+#define VECS_PER_CONIC_GRADIENT_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH VECS_PER_CONIC_GRADIENT_BRUSH
+
+#define WR_BRUSH_VS_FUNCTION conic_gradient_brush_vs
+#define WR_BRUSH_FS_FUNCTION conic_gradient_brush_fs
+
+#include shared,prim_shared,brush
+
+#define V_GRADIENT_ADDRESS  flat_varying_highp_int_address_0
+
+#define V_CENTER            flat_varying_vec4_0.xy
+#define V_ANGLE             flat_varying_vec4_0.z
+
+// Size of the gradient pattern's rectangle, used to compute horizontal and vertical
+// repetitions. Not to be confused with another kind of repetition of the pattern
+// which happens along the gradient stops.
+#define V_REPEATED_SIZE     flat_varying_vec4_1.xy
+// Repetition along the gradient stops.
+#define V_GRADIENT_REPEAT   flat_varying_vec4_1.z
+
+#define V_POS               varying_vec4_0.zw
+
+#ifdef WR_FEATURE_ALPHA_PASS
+#define V_LOCAL_POS         varying_vec4_0.xy
+#define V_TILE_REPEAT       flat_varying_vec4_2.xy
+#endif
+
+#define PI                  3.1415926538
+
+#ifdef WR_VERTEX_SHADER
+
+struct ConicGradient {
+    vec2 center_point;
+    float angle;
+    int extend_mode;
+    vec2 stretch_size;
+};
+
+ConicGradient fetch_gradient(int address) {
+    vec4 data[2] = fetch_from_gpu_cache_2(address);
+    return ConicGradient(
+        data[0].xy,
+        float(data[0].z),
+        int(data[1].x),
+        data[1].yz
+    );
+}
+
+void conic_gradient_brush_vs(
+    VertexInfo vi,
+    int prim_address,
+    RectWithSize local_rect,
+    RectWithSize segment_rect,
+    ivec4 prim_user_data,
+    int specific_resource_address,
+    mat4 transform,
+    PictureTask pic_task,
+    int brush_flags,
+    vec4 texel_rect
+) {
+    ConicGradient gradient = fetch_gradient(prim_address);
+
+    if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
+        V_POS = (vi.local_pos - segment_rect.p0) / segment_rect.size;
+        V_POS = V_POS * (texel_rect.zw - texel_rect.xy) + texel_rect.xy;
+        V_POS = V_POS * local_rect.size;
+    } else {
+        V_POS = vi.local_pos - local_rect.p0;
+    }
+
+    V_CENTER = gradient.center_point;
+    V_ANGLE = gradient.angle;
+
+    vec2 tile_repeat = local_rect.size / gradient.stretch_size;
+    V_REPEATED_SIZE = gradient.stretch_size;
+
+    V_GRADIENT_ADDRESS = prim_user_data.x;
+
+    // Whether to repeat the gradient along the line instead of clamping.
+    V_GRADIENT_REPEAT = float(gradient.extend_mode != EXTEND_MODE_CLAMP);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    V_TILE_REPEAT = tile_repeat;
+    V_LOCAL_POS = vi.local_pos;
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment conic_gradient_brush_fs() {
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    // Handle top and left inflated edges (see brush_image).
+    vec2 local_pos = max(V_POS, vec2(0.0));
+
+    // Apply potential horizontal and vertical repetitions.
+    vec2 pos = mod(local_pos, V_REPEATED_SIZE);
+
+    vec2 prim_size = V_REPEATED_SIZE * V_TILE_REPEAT;
+    // Handle bottom and right inflated edges (see brush_image).
+    if (local_pos.x >= prim_size.x) {
+        pos.x = V_REPEATED_SIZE.x;
+    }
+    if (local_pos.y >= prim_size.y) {
+        pos.y = V_REPEATED_SIZE.y;
+    }
+#else
+    // Apply potential horizontal and vertical repetitions.
+    vec2 pos = mod(V_POS, V_REPEATED_SIZE);
+#endif
+
+    vec2 current_dir = pos - V_CENTER;
+    float current_angle = atan(current_dir.y, current_dir.x) + (PI / 2 - V_ANGLE);
+    float offset = mod(current_angle / (2 * PI), 1.0);
+
+    vec4 color = sample_gradient(V_GRADIENT_ADDRESS,
+                                 offset,
+                                 V_GRADIENT_REPEAT);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    color *= init_transform_fs(V_LOCAL_POS);
+#endif
+
+    return Fragment(color);
+}
+#endif
+
+// Undef macro names that could be re-defined by other shaders.
+#undef V_GRADIENT_ADDRESS
+#undef V_START_POINT
+#undef V_SCALE_DIR
+#undef V_REPEATED_SIZE
+#undef V_GRADIENT_REPEAT
+#undef V_POS
+#undef V_LOCAL_POS
+#undef V_TILE_REPEAT
--- a/gfx/wr/webrender/res/brush_multi.glsl
+++ b/gfx/wr/webrender/res/brush_multi.glsl
@@ -13,20 +13,21 @@
 #define WR_FEATURE_MULTI_BRUSH
 
 // These constants must match the BrushShaderKind enum in gpu_types.rs.
 #define BRUSH_KIND_SOLID            1
 #define BRUSH_KIND_IMAGE            2
 #define BRUSH_KIND_TEXT             3
 #define BRUSH_KIND_LINEAR_GRADIENT  4
 #define BRUSH_KIND_RADIAL_GRADIENT  5
-#define BRUSH_KIND_BLEND            6
-#define BRUSH_KIND_MIX_BLEND        7
-#define BRUSH_KIND_YV               8
-#define BRUSH_KIND_OPACITY          9
+#define BRUSH_KIND_CONIC_GRADIENT   6
+#define BRUSH_KIND_BLEND            7
+#define BRUSH_KIND_MIX_BLEND        8
+#define BRUSH_KIND_YV               9
+#define BRUSH_KIND_OPACITY          10
 
 int vecs_per_brush(int brush_kind);
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_IMAGE_BRUSH
 #include brush_image
 #endif
@@ -70,16 +71,24 @@ int vecs_per_brush(int brush_kind);
 #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
 #include brush_radial_gradient
 #endif
 
 #undef VECS_PER_SPECIFIC_BRUSH
 #undef WR_BRUSH_VS_FUNCTION
 #undef WR_BRUSH_FS_FUNCTION
 
+#ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH
+#include brush_conic_gradient
+#endif
+
+#undef VECS_PER_SPECIFIC_BRUSH
+#undef WR_BRUSH_VS_FUNCTION
+#undef WR_BRUSH_FS_FUNCTION
+
 #ifdef WR_FEATURE_OPACITY_BRUSH
 #include brush_opacity
 #endif
 
 int vecs_per_brush(int brush_kind) {
     switch (brush_kind) {
         // The default arm should never be taken, we let it point to whichever shader
         // is enabled first to satisfy ANGLE validation.
@@ -104,16 +113,21 @@ int vecs_per_brush(int brush_kind) {
         #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH
         case BRUSH_KIND_LINEAR_GRADIENT: return VECS_PER_LINEAR_GRADIENT_BRUSH;
         #endif
 
         #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
         case BRUSH_KIND_RADIAL_GRADIENT: return VECS_PER_RADIAL_GRADIENT_BRUSH;
         #endif
 
+
+        #ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH
+        case BRUSH_KIND_CONIC_GRADIENT: return VECS_PER_CONIC_GRADIENT_BRUSH;
+        #endif
+
         #ifdef WR_FEATURE_OPACITY_BRUSH
         case BRUSH_KIND_OPACITY: return VECS_PER_OPACITY_BRUSH;
         #endif
     }
 }
 
 #define BRUSH_VS_PARAMS vi, prim_address, local_rect, segment_rect, \
     prim_user_data, specific_resource_address, transform, pic_task, \
@@ -168,16 +182,22 @@ void multi_brush_vs(
         #endif
 
         #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
         case BRUSH_KIND_RADIAL_GRADIENT:
             radial_gradient_brush_vs(BRUSH_VS_PARAMS);
             break;
         #endif
 
+        #ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH
+        case BRUSH_KIND_CONIC_GRADIENT:
+            conic_gradient_brush_vs(BRUSH_VS_PARAMS);
+            break;
+        #endif
+
         #ifdef WR_FEATURE_OPACITY_BRUSH
         case BRUSH_KIND_OPACITY:
             opacity_brush_vs(BRUSH_VS_PARAMS);
             break;
         #endif
     }
 }
 
@@ -208,15 +228,19 @@ Fragment multi_brush_fs(int brush_kind) 
         #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH
         case BRUSH_KIND_LINEAR_GRADIENT: return linear_gradient_brush_fs();
         #endif
 
         #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
         case BRUSH_KIND_RADIAL_GRADIENT: return radial_gradient_brush_fs();
         #endif
 
+        #ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH
+        case BRUSH_KIND_CONIC_GRADIENT: return conic_gradient_brush_fs();
+        #endif
+
         #ifdef WR_FEATURE_OPACITY_BRUSH
         case BRUSH_KIND_OPACITY: return opacity_brush_fs();
         #endif
     }
 }
 
 #endif
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -51,16 +51,17 @@ pub enum BrushBatchKind {
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
     YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange),
+    ConicGradient,
     RadialGradient,
     LinearGradient,
     Opacity,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -72,16 +73,17 @@ pub enum BatchKind {
 
 impl BatchKind {
     fn shader_kind(&self) -> BrushShaderKind {
         match self {
             BatchKind::Brush(BrushBatchKind::Solid) => BrushShaderKind::Solid,
             BatchKind::Brush(BrushBatchKind::Image(..)) => BrushShaderKind::Image,
             BatchKind::Brush(BrushBatchKind::LinearGradient) => BrushShaderKind::LinearGradient,
             BatchKind::Brush(BrushBatchKind::RadialGradient) => BrushShaderKind::RadialGradient,
+            BatchKind::Brush(BrushBatchKind::ConicGradient) => BrushShaderKind::ConicGradient,
             BatchKind::Brush(BrushBatchKind::Blend) => BrushShaderKind::Blend,
             BatchKind::Brush(BrushBatchKind::MixBlend { .. }) => BrushShaderKind::MixBlend,
             BatchKind::Brush(BrushBatchKind::YuvImage(..)) => BrushShaderKind::Yuv,
             BatchKind::Brush(BrushBatchKind::Opacity) => BrushShaderKind::Opacity,
             BatchKind::TextRun(..) => BrushShaderKind::Text,
             _ => BrushShaderKind::None,
         }
     }
@@ -2325,16 +2327,97 @@ impl BatchBuilder {
                         gpu_cache,
                         &prim_header,
                         prim_headers,
                         z_id,
                         prim_vis_mask,
                     );
                 }
             }
+            PrimitiveInstanceKind::ConicGradient { data_handle, ref visible_tiles_range, .. } => {
+                let prim_data = &ctx.data_stores.conic_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,
+                    specific_prim_address: GpuCacheAddress::INVALID,
+                    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 batch_params = BrushBatchParameters::shared(
+                        BrushBatchKind::ConicGradient,
+                        BatchTextures::no_texture(),
+                        [
+                            prim_data.stops_handle.as_int(gpu_cache),
+                            0,
+                            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 prim_data.brush_segments.is_empty() {
+                        None
+                    } else {
+                        Some(prim_data.brush_segments.as_slice())
+                    };
+
+                    self.add_segmented_prim_to_batch(
+                        segments,
+                        prim_data.opacity,
+                        &batch_params,
+                        specified_blend_mode,
+                        non_segmented_blend_mode,
+                        batch_features,
+                        prim_header_index,
+                        bounding_rect,
+                        transform_kind,
+                        render_tasks,
+                        z_id,
+                        prim_info.clip_task_index,
+                        prim_vis_mask,
+                        ctx,
+                    );
+                } else {
+                    let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
+
+                    self.add_gradient_tiles(
+                        visible_tiles,
+                        &prim_data.stops_handle,
+                        BrushBatchKind::ConicGradient,
+                        specified_blend_mode,
+                        bounding_rect,
+                        clip_task_address.unwrap(),
+                        gpu_cache,
+                        &prim_header,
+                        prim_headers,
+                        z_id,
+                        prim_vis_mask,
+                    );
+                }
+            }
             PrimitiveInstanceKind::Backdrop { data_handle } => {
                 let prim_data = &ctx.data_stores.backdrop[data_handle];
                 let backdrop_pic_index = prim_data.kind.pic_index;
                 let backdrop_surface_index = ctx.prim_store.pictures[backdrop_pic_index.0]
                     .raster_config
                     .as_ref()
                     .expect("backdrop surface should be alloc by now")
                     .surface_index;
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -73,20 +73,21 @@ impl ZBufferIdGenerator {
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BrushShaderKind {
     None            = 0,
     Solid           = 1,
     Image           = 2,
     Text            = 3,
     LinearGradient  = 4,
     RadialGradient  = 5,
-    Blend           = 6,
-    MixBlend        = 7,
-    Yuv             = 8,
-    Opacity         = 9,
+    ConicGradient   = 6,
+    Blend           = 7,
+    MixBlend        = 8,
+    Yuv             = 9,
+    Opacity         = 10,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub enum RasterizationSpace {
     Local = 0,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -117,17 +117,17 @@ use crate::intern::{Internable, UpdateLi
 use crate::intern::{UpdateKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::prim_store::backdrop::Backdrop;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
 #[cfg(any(feature = "capture", feature = "replay"))]
-use crate::prim_store::gradient::{LinearGradient, RadialGradient};
+use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::prim_store::image::{Image, YuvImage};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::prim_store::line_dec::LineDecoration;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::prim_store::picture::Picture;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::prim_store::text_run::TextRun;
@@ -2709,16 +2709,17 @@ impl TileCacheInstance {
             }
             PrimitiveInstanceKind::Clear { .. } => {
                 backdrop_candidate = Some(BackdropKind::Clear);
             }
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
+            PrimitiveInstanceKind::ConicGradient { .. } |
             PrimitiveInstanceKind::Backdrop { .. } => {
                 // These don't contribute dependencies
             }
         };
 
         // If this primitive considers itself a backdrop candidate, apply further
         // checks to see if it matches all conditions to be a backdrop.
         if let Some(backdrop_candidate) = backdrop_candidate {
--- a/gfx/wr/webrender/src/prim_store/gradient.rs
+++ b/gfx/wr/webrender/src/prim_store/gradient.rs
@@ -557,16 +557,232 @@ impl InternablePrimitive for RadialGradi
 impl IsVisible for RadialGradient {
     fn is_visible(&self) -> bool {
         true
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
+/// Conic gradients
+
+/// Hashable conic 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 ConicGradientAngle {
+    pub angle: f32,
+}
+
+impl Eq for ConicGradientAngle {}
+
+impl hash::Hash for ConicGradientAngle {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.angle.to_bits().hash(state);
+    }
+}
+
+/// Identifying key for a line decoration.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
+pub struct ConicGradientKey {
+    pub common: PrimKeyCommonData,
+    pub extend_mode: ExtendMode,
+    pub center: PointKey,
+    pub angle: ConicGradientAngle,
+    pub stretch_size: SizeKey,
+    pub stops: Vec<GradientStopKey>,
+    pub tile_spacing: SizeKey,
+    pub nine_patch: Option<Box<NinePatchDescriptor>>,
+}
+
+impl ConicGradientKey {
+    pub fn new(
+        flags: PrimitiveFlags,
+        prim_size: LayoutSize,
+        conic_grad: ConicGradient,
+    ) -> Self {
+        ConicGradientKey {
+            common: PrimKeyCommonData {
+                flags,
+                prim_size: prim_size.into(),
+            },
+            extend_mode: conic_grad.extend_mode,
+            center: conic_grad.center,
+            angle: conic_grad.angle,
+            stretch_size: conic_grad.stretch_size,
+            stops: conic_grad.stops,
+            tile_spacing: conic_grad.tile_spacing,
+            nine_patch: conic_grad.nine_patch,
+        }
+    }
+}
+
+impl InternDebug for ConicGradientKey {}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
+pub struct ConicGradientTemplate {
+    pub common: PrimTemplateCommonData,
+    pub extend_mode: ExtendMode,
+    pub center: LayoutPoint,
+    pub angle: ConicGradientAngle,
+    pub stretch_size: LayoutSize,
+    pub tile_spacing: LayoutSize,
+    pub brush_segments: Vec<BrushSegment>,
+    pub stops: Vec<GradientStop>,
+    pub stops_handle: GpuCacheHandle,
+}
+
+impl Deref for ConicGradientTemplate {
+    type Target = PrimTemplateCommonData;
+    fn deref(&self) -> &Self::Target {
+        &self.common
+    }
+}
+
+impl DerefMut for ConicGradientTemplate {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.common
+    }
+}
+
+impl From<ConicGradientKey> for ConicGradientTemplate {
+    fn from(item: ConicGradientKey) -> Self {
+        let common = PrimTemplateCommonData::with_key_common(item.common);
+        let mut brush_segments = Vec::new();
+
+        if let Some(ref nine_patch) = item.nine_patch {
+            brush_segments = nine_patch.create_segments(common.prim_size);
+        }
+
+        let stops = item.stops.iter().map(|stop| {
+            GradientStop {
+                offset: stop.offset,
+                color: stop.color.into(),
+            }
+        }).collect();
+
+        ConicGradientTemplate {
+            common,
+            center: item.center.into(),
+            extend_mode: item.extend_mode,
+            angle: item.angle,
+            stretch_size: item.stretch_size.into(),
+            tile_spacing: item.tile_spacing.into(),
+            brush_segments,
+            stops,
+            stops_handle: GpuCacheHandle::new(),
+        }
+    }
+}
+
+impl ConicGradientTemplate {
+    /// 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
+    /// done if the cache entry is invalid (due to first use or eviction).
+    pub fn update(
+        &mut self,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        if let Some(mut request) =
+            frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
+            // write_prim_gpu_blocks
+            request.push([
+                self.center.x,
+                self.center.y,
+                self.angle.angle,
+                0.0,
+            ]);
+            request.push([
+                pack_as_float(self.extend_mode as u32),
+                self.stretch_size.width,
+                self.stretch_size.height,
+                0.0,
+            ]);
+
+            // write_segment_gpu_blocks
+            for segment in &self.brush_segments {
+                // has to match VECS_PER_SEGMENT
+                request.write_segment(
+                    segment.local_rect,
+                    segment.extra_data,
+                );
+            }
+        }
+
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) {
+            GradientGpuBlockBuilder::build(
+                false,
+                &mut request,
+                &self.stops,
+            );
+        }
+
+        self.opacity = PrimitiveOpacity::translucent();
+    }
+}
+
+pub type ConicGradientDataHandle = InternHandle<ConicGradient>;
+
+#[derive(Debug, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ConicGradient {
+    pub extend_mode: ExtendMode,
+    pub center: PointKey,
+    pub angle: ConicGradientAngle,
+    pub stretch_size: SizeKey,
+    pub stops: Vec<GradientStopKey>,
+    pub tile_spacing: SizeKey,
+    pub nine_patch: Option<Box<NinePatchDescriptor>>,
+}
+
+impl Internable for ConicGradient {
+    type Key = ConicGradientKey;
+    type StoreData = ConicGradientTemplate;
+    type InternData = PrimitiveSceneData;
+}
+
+impl InternablePrimitive for ConicGradient {
+    fn into_key(
+        self,
+        info: &LayoutPrimitiveInfo,
+    ) -> ConicGradientKey {
+        ConicGradientKey::new(
+            info.flags,
+            info.rect.size,
+            self,
+        )
+    }
+
+    fn make_instance_kind(
+        _key: ConicGradientKey,
+        data_handle: ConicGradientDataHandle,
+        _prim_store: &mut PrimitiveStore,
+        _reference_frame_relative_offset: LayoutVector2D,
+    ) -> PrimitiveInstanceKind {
+        PrimitiveInstanceKind::ConicGradient {
+            data_handle,
+            visible_tiles_range: GradientTileRange::empty(),
+        }
+    }
+}
+
+impl IsVisible for ConicGradient {
+    fn is_visible(&self) -> bool {
+        true
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 // The gradient entry index for the first color stop
 pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
 // The gradient entry index for the last color stop
 pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
 
 // The start of the gradient data table
 pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
 // The exclusive bound of the gradient data table
@@ -778,9 +994,13 @@ fn test_struct_sizes() {
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<LinearGradient>(), 72, "LinearGradient size changed");
     assert_eq!(mem::size_of::<LinearGradientTemplate>(), 112, "LinearGradientTemplate size changed");
     assert_eq!(mem::size_of::<LinearGradientKey>(), 80, "LinearGradientKey size changed");
 
     assert_eq!(mem::size_of::<RadialGradient>(), 72, "RadialGradient size changed");
     assert_eq!(mem::size_of::<RadialGradientTemplate>(), 120, "RadialGradientTemplate size changed");
     assert_eq!(mem::size_of::<RadialGradientKey>(), 88, "RadialGradientKey size changed");
+
+    assert_eq!(mem::size_of::<ConicGradient>(), 72, "ConicGradient size changed");
+    assert_eq!(mem::size_of::<ConicGradientTemplate>(), 112, "ConicGradientTemplate size changed");
+    assert_eq!(mem::size_of::<ConicGradientKey>(), 80, "ConicGradientKey size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/interned.rs
+++ b/gfx/wr/webrender/src/prim_store/interned.rs
@@ -3,12 +3,12 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // list of all interned primitives to match enumerate_interners!
 
 pub use crate::prim_store::backdrop::Backdrop;
 pub use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
 pub use crate::prim_store::image::{Image, YuvImage};
 pub use crate::prim_store::line_dec::{LineDecoration};
-pub use crate::prim_store::gradient::{LinearGradient, RadialGradient};
+pub use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
 pub use crate::prim_store::picture::Picture;
 pub use crate::prim_store::text_run::TextRun;
 
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -27,17 +27,17 @@ use crate::image::{Repetition};
 use crate::intern;
 use crate::internal_types::PlaneSplitAnchor;
 use malloc_size_of::MallocSizeOf;
 use crate::picture::{PictureCompositeMode, PicturePrimitive, ClusterFlags, TileCacheLogger};
 use crate::picture::{PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig};
 use crate::prim_store::backdrop::BackdropDataHandle;
 use crate::prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
 use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey};
-use crate::prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle};
+use crate::prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle, ConicGradientDataHandle};
 use crate::prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle};
 use crate::prim_store::line_dec::LineDecorationDataHandle;
 use crate::prim_store::picture::PictureDataHandle;
 use crate::prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
 #[cfg(debug_assertions)]
 use crate::render_backend::{FrameId};
 use crate::render_backend::DataStores;
 use crate::render_task_graph::RenderTaskId;
@@ -1421,16 +1421,21 @@ pub enum PrimitiveInstanceKind {
         data_handle: LinearGradientDataHandle,
         gradient_index: LinearGradientIndex,
     },
     RadialGradient {
         /// Handle to the common interned data for this primitive.
         data_handle: RadialGradientDataHandle,
         visible_tiles_range: GradientTileRange,
     },
+    ConicGradient {
+        /// Handle to the common interned data for this primitive.
+        data_handle: ConicGradientDataHandle,
+        visible_tiles_range: GradientTileRange,
+    },
     /// Clear out a rect, used for special effects.
     Clear {
         /// Handle to the common interned data for this primitive.
         data_handle: PrimitiveDataHandle,
     },
     /// Render a portion of a specified backdrop.
     Backdrop {
         data_handle: BackdropDataHandle,
@@ -1618,16 +1623,19 @@ impl PrimitiveInstance {
                 data_handle.uid()
             }
             PrimitiveInstanceKind::Picture { data_handle, .. } => {
                 data_handle.uid()
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
                 data_handle.uid()
             }
+            PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
+                data_handle.uid()
+            }
             PrimitiveInstanceKind::TextRun { data_handle, .. } => {
                 data_handle.uid()
             }
             PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
                 data_handle.uid()
             }
             PrimitiveInstanceKind::Backdrop { data_handle, .. } => {
                 data_handle.uid()
@@ -2235,16 +2243,17 @@ impl PrimitiveStore {
                             PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE,
                             PrimitiveInstanceKind::NormalBorder { .. } |
                             PrimitiveInstanceKind::ImageBorder { .. } => debug_colors::ORANGE,
                             PrimitiveInstanceKind::Rectangle { .. } => ColorF { r: 0.8, g: 0.8, b: 0.8, a: 0.5 },
                             PrimitiveInstanceKind::YuvImage { .. } => debug_colors::BLUE,
                             PrimitiveInstanceKind::Image { .. } => debug_colors::BLUE,
                             PrimitiveInstanceKind::LinearGradient { .. } => debug_colors::PINK,
                             PrimitiveInstanceKind::RadialGradient { .. } => debug_colors::PINK,
+                            PrimitiveInstanceKind::ConicGradient { .. } => debug_colors::PINK,
                             PrimitiveInstanceKind::Clear { .. } => debug_colors::CYAN,
                             PrimitiveInstanceKind::Backdrop { .. } => debug_colors::MEDIUMAQUAMARINE,
                         };
                         if debug_color.a != 0.0 {
                             let debug_rect = clipped_world_rect * frame_context.global_device_pixel_scale;
                             frame_state.scratch.push_debug_rect(debug_rect, debug_color, debug_color.scale_alpha(0.5));
                         }
                     } else if frame_context.debug_flags.contains(::api::DebugFlags::OBSCURE_IMAGES) {
@@ -2554,16 +2563,17 @@ impl PrimitiveStore {
             }
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
+            PrimitiveInstanceKind::ConicGradient { .. } |
             PrimitiveInstanceKind::PushClipChain |
             PrimitiveInstanceKind::PopClipChain |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::Backdrop { .. } => {
                 // These prims don't support opacity collapse
             }
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let pic = &self.pictures[pic_index.0];
@@ -2697,16 +2707,17 @@ impl PrimitiveStore {
                 PrimitiveInstanceKind::Rectangle { .. } |
                 PrimitiveInstanceKind::LineDecoration { .. } |
                 PrimitiveInstanceKind::NormalBorder { .. } |
                 PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::YuvImage { .. } |
                 PrimitiveInstanceKind::Image { .. } |
                 PrimitiveInstanceKind::LinearGradient { .. } |
                 PrimitiveInstanceKind::RadialGradient { .. } |
+                PrimitiveInstanceKind::ConicGradient { .. } |
                 PrimitiveInstanceKind::PushClipChain |
                 PrimitiveInstanceKind::PopClipChain |
                 PrimitiveInstanceKind::Clear { .. } |
                 PrimitiveInstanceKind::Backdrop { .. } => {
                     None
                 }
             }
         };
@@ -3322,16 +3333,80 @@ impl PrimitiveStore {
                     if 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::ConicGradient { data_handle, ref mut visible_tiles_range, .. } => {
+                let prim_data = &mut data_stores.conic_grad[*data_handle];
+
+                if prim_data.stretch_size.width >= prim_data.common.prim_size.width &&
+                    prim_data.stretch_size.height >= prim_data.common.prim_size.height {
+
+                    // We are performing the decomposition on the CPU here, no need to
+                    // have it in the shader.
+                    prim_data.common.may_need_repetition = false;
+                }
+
+                // 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.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,
+                    );
+
+                    let map_local_to_world = SpaceMapper::new_with_target(
+                        ROOT_SPATIAL_NODE_INDEX,
+                        prim_spatial_node_index,
+                        frame_context.global_screen_world_rect,
+                        frame_context.spatial_tree,
+                    );
+
+                    prim_data.common.may_need_repetition = false;
+
+                    *visible_tiles_range = decompose_repeated_primitive(
+                        &prim_info.combined_local_clip_rect,
+                        &prim_rect,
+                        prim_info.clipped_world_rect,
+                        &prim_data.stretch_size,
+                        &prim_data.tile_spacing,
+                        frame_state,
+                        &mut scratch.gradient_tiles,
+                        &map_local_to_world,
+                        &mut |_, mut request| {
+                            request.push([
+                                prim_data.center.x,
+                                prim_data.center.y,
+                                prim_data.angle.angle,
+                                0.0,
+                            ]);
+                            request.push([
+                                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() {
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
+                    }
+                }
+
+                // TODO(gw): Consider whether it's worth doing segment building
+                //           for gradient primitives.
+            }
             PrimitiveInstanceKind::Picture { pic_index, segment_instance_index, data_handle, .. } => {
                 let pic = &mut self.pictures[pic_index.0];
                 let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
 
                 data_stores.picture[*data_handle].common.may_need_repetition = false;
 
                 if pic.prepare_for_render(
                     frame_context,
@@ -3713,16 +3788,17 @@ impl PrimitiveInstance {
                 }
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
+            PrimitiveInstanceKind::ConicGradient { .. } |
             PrimitiveInstanceKind::PushClipChain |
             PrimitiveInstanceKind::PopClipChain |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::Backdrop { .. } => {
                 // These primitives don't support / need segments.
                 return;
             }
         };
@@ -3860,16 +3936,27 @@ impl PrimitiveInstance {
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
                 if prim_data.brush_segments.is_empty() {
                     return false;
                 }
 
                 prim_data.brush_segments.as_slice()
             }
+            PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
+                let prim_data = &data_stores.conic_grad[data_handle];
+
+                // TODO: This is quite messy - once we remove legacy primitives we
+                //       can change this to be a tuple match on (instance, template)
+                if prim_data.brush_segments.is_empty() {
+                    return false;
+                }
+
+                prim_data.brush_segments.as_slice()
+            }
         };
 
         // If there are no segments, early out to avoid setting a valid
         // clip task instance location below.
         if segments.is_empty() {
             return true;
         }
 
--- a/gfx/wr/webrender/src/profiler.rs
+++ b/gfx/wr/webrender/src/profiler.rs
@@ -800,16 +800,17 @@ impl BackendProfileCounters {
                 display_lists: ResourceProfileCounter::new(
                     "Display Lists Sent",
                     None, Some(expected::DISPLAY_LIST_MB),
                 ),
             },
             //TODO: generate this by a macro
             intern: InternProfileCounters {
                 prim: ResourceProfileCounter::new("Interned primitives", None, None),
+                conic_grad: ResourceProfileCounter::new("Interned conic gradients", None, None),
                 image: ResourceProfileCounter::new("Interned images", None, None),
                 image_border: ResourceProfileCounter::new("Interned image borders", None, None),
                 line_decoration: ResourceProfileCounter::new("Interned line decorations", None, None),
                 linear_grad: ResourceProfileCounter::new("Interned linear gradients", None, None),
                 normal_border: ResourceProfileCounter::new("Interned normal borders", None, None),
                 picture: ResourceProfileCounter::new("Interned pictures", None, None),
                 radial_grad: ResourceProfileCounter::new("Interned radial gradients", None, None),
                 text_run: ResourceProfileCounter::new("Interned text runs", None, None),
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -304,16 +304,20 @@ impl DataStores {
             PrimitiveInstanceKind::Picture { data_handle, .. } => {
                 let prim_data = &self.picture[data_handle];
                 &prim_data.common
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
                 let prim_data = &self.radial_grad[data_handle];
                 &prim_data.common
             }
+            PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
+                let prim_data = &self.conic_grad[data_handle];
+                &prim_data.common
+            }
             PrimitiveInstanceKind::TextRun { data_handle, .. }  => {
                 let prim_data = &self.text_run[data_handle];
                 &prim_data.common
             }
             PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
                 let prim_data = &self.yuv_image[data_handle];
                 &prim_data.common
             }
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -148,16 +148,20 @@ const GPU_TAG_BRUSH_OPACITY: GpuProfileT
 const GPU_TAG_BRUSH_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag {
     label: "B_LinearGradient",
     color: debug_colors::POWDERBLUE,
 };
 const GPU_TAG_BRUSH_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
     label: "B_RadialGradient",
     color: debug_colors::LIGHTPINK,
 };
+const GPU_TAG_BRUSH_CONIC_GRADIENT: GpuProfileTag = GpuProfileTag {
+    label: "B_ConicGradient",
+    color: debug_colors::GREEN,
+};
 const GPU_TAG_BRUSH_YUV_IMAGE: GpuProfileTag = GpuProfileTag {
     label: "B_YuvImage",
     color: debug_colors::DARKGREEN,
 };
 const GPU_TAG_BRUSH_MIXBLEND: GpuProfileTag = GpuProfileTag {
     label: "B_MixBlend",
     color: debug_colors::MAGENTA,
 };
@@ -250,16 +254,17 @@ impl BatchKind {
             BatchKind::SplitComposite => "SplitComposite",
             BatchKind::Brush(kind) => {
                 match kind {
                     BrushBatchKind::Solid => "Brush (Solid)",
                     BrushBatchKind::Image(..) => "Brush (Image)",
                     BrushBatchKind::Blend => "Brush (Blend)",
                     BrushBatchKind::MixBlend { .. } => "Brush (Composite)",
                     BrushBatchKind::YuvImage(..) => "Brush (YuvImage)",
+                    BrushBatchKind::ConicGradient => "Brush (ConicGradient)",
                     BrushBatchKind::RadialGradient => "Brush (RadialGradient)",
                     BrushBatchKind::LinearGradient => "Brush (LinearGradient)",
                     BrushBatchKind::Opacity => "Brush (Opacity)",
                 }
             }
             BatchKind::TextRun(_) => "TextRun",
         }
     }
@@ -269,16 +274,17 @@ impl BatchKind {
             BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
             BatchKind::Brush(kind) => {
                 match kind {
                     BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
                     BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
                     BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
                     BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
                     BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
+                    BrushBatchKind::ConicGradient => GPU_TAG_BRUSH_CONIC_GRADIENT,
                     BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT,
                     BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT,
                     BrushBatchKind::Opacity => GPU_TAG_BRUSH_OPACITY,
                 }
             }
             BatchKind::TextRun(_) => GPU_TAG_PRIM_TEXT_RUN,
         }
     }
@@ -6860,16 +6866,17 @@ enum FramebufferKind {
     Other,
 }
 
 fn should_skip_batch(kind: &BatchKind, flags: DebugFlags) -> bool {
     match kind {
         BatchKind::TextRun(_) => {
             flags.contains(DebugFlags::DISABLE_TEXT_PRIMS)
         }
+        BatchKind::Brush(BrushBatchKind::ConicGradient) |
         BatchKind::Brush(BrushBatchKind::RadialGradient) |
         BatchKind::Brush(BrushBatchKind::LinearGradient) => {
             flags.contains(DebugFlags::DISABLE_GRADIENT_PRIMS)
         }
         _ => false,
     }
 }
 
--- a/gfx/wr/webrender/src/scene_builder_thread.rs
+++ b/gfx/wr/webrender/src/scene_builder_thread.rs
@@ -12,17 +12,17 @@ use api::units::LayoutSize;
 use crate::capture::CaptureConfig;
 use crate::frame_builder::FrameBuilderConfig;
 use crate::scene_building::SceneBuilder;
 use crate::intern::{Internable, Interner, UpdateList};
 use crate::internal_types::{FastHashMap, FastHashSet};
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use crate::prim_store::backdrop::Backdrop;
 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
-use crate::prim_store::gradient::{LinearGradient, RadialGradient};
+use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
 use crate::prim_store::image::{Image, YuvImage};
 use crate::prim_store::line_dec::LineDecoration;
 use crate::prim_store::picture::Picture;
 use crate::prim_store::text_run::TextRun;
 use crate::resource_cache::{AsyncBlobImageInfo, FontInstanceMap};
 use crate::render_backend::DocumentView;
 use crate::renderer::{PipelineInfo, SceneBuilderHooks};
 use crate::scene::{Scene, BuiltScene, SceneStats};
--- a/gfx/wr/webrender/src/scene_building.rs
+++ b/gfx/wr/webrender/src/scene_building.rs
@@ -26,17 +26,17 @@ use crate::picture::{BlitReason, Ordered
 use crate::prim_store::{PrimitiveInstance, PrimitiveSceneData};
 use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
 use crate::prim_store::{ScrollNodeAndClipChain, PictureIndex};
 use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex};
 use crate::prim_store::{register_prim_chase_id, get_line_decoration_size};
 use crate::prim_store::{SpaceSnapper};
 use crate::prim_store::backdrop::Backdrop;
 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
-use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
+use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientAngle};
 use crate::prim_store::image::{Image, YuvImage};
 use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey};
 use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
 use crate::prim_store::text_run::TextRun;
 use crate::render_backend::{DocumentView};
 use crate::resource_cache::{FontInstanceMap, ImageRequest};
 use crate::scene::{Scene, BuiltScene, SceneStats, StackingContextHelpers};
 use crate::scene_builder_thread::Interners;
@@ -1306,16 +1306,47 @@ impl<'a> SceneBuilder<'a> {
 
                 self.add_nonshadowable_primitive(
                     clip_and_scroll,
                     &layout,
                     Vec::new(),
                     prim_key_kind,
                 );
             }
+            DisplayItem::ConicGradient(ref info) => {
+                let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
+                    &info.common,
+                    &info.bounds,
+                    apply_pipeline_clip,
+                );
+
+                let tile_size = process_repeat_size(
+                    &layout.rect,
+                    &unsnapped_rect,
+                    info.tile_size,
+                );
+
+                let prim_key_kind = self.create_conic_gradient_prim(
+                    &layout,
+                    info.gradient.center,
+                    info.gradient.angle,
+                    item.gradient_stops(),
+                    info.gradient.extend_mode,
+                    tile_size,
+                    info.tile_spacing,
+                    None,
+                );
+
+                self.add_nonshadowable_primitive(
+                    clip_and_scroll,
+                    &layout,
+                    Vec::new(),
+                    prim_key_kind,
+                );
+            }
             DisplayItem::BoxShadow(ref info) => {
                 let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
                     &info.common,
                     &info.box_bounds,
                     apply_pipeline_clip,
                 );
 
                 self.add_box_shadow(
@@ -2895,16 +2926,35 @@ impl<'a> SceneBuilder<'a> {
 
                         self.add_nonshadowable_primitive(
                             clip_and_scroll,
                             info,
                             Vec::new(),
                             prim,
                         );
                     }
+                    NinePatchBorderSource::ConicGradient(gradient) => {
+                        let prim = self.create_conic_gradient_prim(
+                            &info,
+                            gradient.center,
+                            gradient.angle,
+                            gradient_stops,
+                            gradient.extend_mode,
+                            LayoutSize::new(border.height as f32, border.width as f32),
+                            LayoutSize::zero(),
+                            Some(Box::new(nine_patch)),
+                        );
+
+                        self.add_nonshadowable_primitive(
+                            clip_and_scroll,
+                            info,
+                            Vec::new(),
+                            prim,
+                        );
+                    }
                 };
             }
             BorderDetails::Normal(ref border) => {
                 self.add_normal_border(
                     info,
                     border,
                     border_item.widths,
                     clip_and_scroll,
@@ -3008,16 +3058,48 @@ impl<'a> SceneBuilder<'a> {
             params,
             stretch_size: stretch_size.into(),
             tile_spacing: tile_spacing.into(),
             nine_patch,
             stops,
         }
     }
 
+    pub fn create_conic_gradient_prim(
+        &mut self,
+        info: &LayoutPrimitiveInfo,
+        center: LayoutPoint,
+        angle: f32,
+        stops: ItemRange<GradientStop>,
+        extend_mode: ExtendMode,
+        stretch_size: LayoutSize,
+        mut tile_spacing: LayoutSize,
+        nine_patch: Option<Box<NinePatchDescriptor>>,
+    ) -> ConicGradient {
+        let mut prim_rect = info.rect;
+        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+
+        let stops = stops.iter().map(|stop| {
+            GradientStopKey {
+                offset: stop.offset,
+                color: stop.color.into(),
+            }
+        }).collect();
+
+        ConicGradient {
+            extend_mode,
+            center: center.into(),
+            angle: ConicGradientAngle { angle },
+            stretch_size: stretch_size.into(),
+            tile_spacing: tile_spacing.into(),
+            nine_patch,
+            stops,
+        }
+    }
+
     pub fn add_text(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         prim_info: &LayoutPrimitiveInfo,
         font_instance_key: &FontInstanceKey,
         text_color: &ColorF,
         glyph_range: ItemRange<GlyphInstance>,
         glyph_options: Option<GlyphOptions>,
--- a/gfx/wr/webrender/src/shade.rs
+++ b/gfx/wr/webrender/src/shade.rs
@@ -531,16 +531,17 @@ pub struct Shaders {
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_fast_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
+    brush_conic_gradient: BrushShader,
     brush_radial_gradient: BrushShader,
     brush_linear_gradient: BrushShader,
     brush_opacity: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     pub cs_clip_rectangle_slow: LazilyCompiledShader,
@@ -609,16 +610,30 @@ impl Shaders {
             device,
             &[],
             options.precache_flags,
             false /* advanced blend */,
             false /* dual source */,
             use_pixel_local_storage,
         )?;
 
+        let brush_conic_gradient = BrushShader::new(
+            "brush_conic_gradient",
+            device,
+            if options.enable_dithering {
+               &[DITHERING_FEATURE]
+            } else {
+               &[]
+            },
+            options.precache_flags,
+            false /* advanced blend */,
+            false /* dual source */,
+            use_pixel_local_storage,
+        )?;
+
         let brush_radial_gradient = BrushShader::new(
             "brush_radial_gradient",
             device,
             if options.enable_dithering {
                &[DITHERING_FEATURE]
             } else {
                &[]
             },
@@ -899,16 +914,17 @@ impl Shaders {
             cs_scale,
             cs_svg_filter,
             brush_solid,
             brush_image,
             brush_fast_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
+            brush_conic_gradient,
             brush_radial_gradient,
             brush_linear_gradient,
             brush_opacity,
             cs_clip_rectangle_slow,
             cs_clip_rectangle_fast,
             cs_clip_box_shadow,
             cs_clip_image,
             pls_init,
@@ -948,16 +964,19 @@ impl Shaders {
                         }
                     }
                     BrushBatchKind::Blend => {
                         &mut self.brush_blend
                     }
                     BrushBatchKind::MixBlend { .. } => {
                         &mut self.brush_mix_blend
                     }
+                    BrushBatchKind::ConicGradient => {
+                        &mut self.brush_conic_gradient
+                    }
                     BrushBatchKind::RadialGradient => {
                         &mut self.brush_radial_gradient
                     }
                     BrushBatchKind::LinearGradient => {
                         &mut self.brush_linear_gradient
                     }
                     BrushBatchKind::YuvImage(image_buffer_kind, ..) => {
                         let shader_index =
@@ -985,16 +1004,17 @@ impl Shaders {
     pub fn deinit(self, device: &mut Device) {
         self.cs_scale.deinit(device);
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.cs_svg_filter.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
+        self.brush_conic_gradient.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
         self.brush_opacity.deinit(device);
         self.cs_clip_rectangle_slow.deinit(device);
         self.cs_clip_rectangle_fast.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
         self.cs_clip_image.deinit(device);
         self.pls_init.deinit(device);
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -1170,16 +1170,17 @@ macro_rules! enumerate_interners {
             prim: PrimitiveKeyKind,
             normal_border: NormalBorderPrim,
             image_border: ImageBorder,
             image: Image,
             yuv_image: YuvImage,
             line_decoration: LineDecoration,
             linear_grad: LinearGradient,
             radial_grad: RadialGradient,
+            conic_grad: ConicGradient,
             picture: Picture,
             text_run: TextRun,
             filter_data: FilterDataIntern,
             backdrop: Backdrop,
         }
     }
 }
 
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -131,16 +131,17 @@ pub enum DisplayItem {
     HitTest(HitTestDisplayItem),
     Text(TextDisplayItem),
     Line(LineDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     PushShadow(PushShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
+    ConicGradient(ConicGradientDisplayItem),
     Image(ImageDisplayItem),
     RepeatingImage(RepeatingImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     BackdropFilter(BackdropFilterDisplayItem),
 
     // Clips
     Clip(ClipDisplayItem),
     ClipChain(ClipChainItem),
@@ -178,16 +179,17 @@ pub enum DebugDisplayItem {
     HitTest(HitTestDisplayItem),
     Text(TextDisplayItem, Vec<font::GlyphInstance>),
     Line(LineDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     PushShadow(PushShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
+    ConicGradient(ConicGradientDisplayItem),
     Image(ImageDisplayItem),
     RepeatingImage(RepeatingImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     BackdropFilter(BackdropFilterDisplayItem),
 
     Clip(ClipDisplayItem, Vec<ComplexClipRegion>),
     ClipChain(ClipChainItem, Vec<ClipId>),
 
@@ -439,16 +441,17 @@ pub enum RepeatMode {
     Space,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub enum NinePatchBorderSource {
     Image(ImageKey),
     Gradient(Gradient),
     RadialGradient(RadialGradient),
+    ConicGradient(ConicGradient),
 }
 
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub struct NinePatchBorder {
     /// Describes what to use as the 9-patch source image. If this is an image,
     /// it will be stretched to fill the size given by width x height.
     pub source: NinePatchBorderSource,
 
@@ -628,16 +631,25 @@ pub struct GradientStop {
 pub struct RadialGradient {
     pub center: LayoutPoint,
     pub radius: LayoutSize,
     pub start_offset: f32,
     pub end_offset: f32,
     pub extend_mode: ExtendMode,
 } // IMPLICIT stops: Vec<GradientStop>
 
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ConicGradient {
+    pub center: LayoutPoint,
+    pub angle: f32,
+    pub start_offset: f32,
+    pub end_offset: f32,
+    pub extend_mode: ExtendMode,
+} // IMPLICIT stops: Vec<GradientStop>
+
 /// Just an abstraction for bundling up a bunch of clips into a "super clip".
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub struct ClipChainItem {
     pub id: ClipChainId,
     pub parent: Option<ClipChainId>,
 } // IMPLICIT clip_ids: Vec<ClipId>
 
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
@@ -647,16 +659,28 @@ pub struct RadialGradientDisplayItem {
     // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
     // defining the bounds of the item. Needs non-trivial backend changes.
     pub bounds: LayoutRect,
     pub gradient: RadialGradient,
     pub tile_size: LayoutSize,
     pub tile_spacing: LayoutSize,
 }
 
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub struct ConicGradientDisplayItem {
+    pub common: CommonItemProperties,
+    /// The area to tile the gradient over (first tile starts at origin of this rect)
+    // FIXME: this should ideally just be `tile_origin` here, with the clip_rect
+    // defining the bounds of the item. Needs non-trivial backend changes.
+    pub bounds: LayoutRect,
+    pub gradient: ConicGradient,
+    pub tile_size: LayoutSize,
+    pub tile_spacing: LayoutSize,
+}
+
 /// Renders a filtered region of its backdrop
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub struct BackdropFilterDisplayItem {
     pub common: CommonItemProperties,
 }
 // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
 
 #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)]
@@ -1457,16 +1481,17 @@ impl DisplayItem {
     pub fn debug_name(&self) -> &'static str {
         match *self {
             DisplayItem::Border(..) => "border",
             DisplayItem::BoxShadow(..) => "box_shadow",
             DisplayItem::ClearRectangle(..) => "clear_rectangle",
             DisplayItem::HitTest(..) => "hit_test",
             DisplayItem::Clip(..) => "clip",
             DisplayItem::ClipChain(..) => "clip_chain",
+            DisplayItem::ConicGradient(..) => "conic_gradient",
             DisplayItem::Gradient(..) => "gradient",
             DisplayItem::Iframe(..) => "iframe",
             DisplayItem::Image(..) => "image",
             DisplayItem::RepeatingImage(..) => "repeating_image",
             DisplayItem::Line(..) => "line",
             DisplayItem::PopAllShadows => "pop_all_shadows",
             DisplayItem::PopReferenceFrame => "pop_reference_frame",
             DisplayItem::PopStackingContext => "pop_stacking_context",
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -679,16 +679,17 @@ impl Serialize for BuiltDisplayList {
                 Real::Line(v) => Debug::Line(v),
                 Real::Image(v) => Debug::Image(v),
                 Real::RepeatingImage(v) => Debug::RepeatingImage(v),
                 Real::YuvImage(v) => Debug::YuvImage(v),
                 Real::Border(v) => Debug::Border(v),
                 Real::BoxShadow(v) => Debug::BoxShadow(v),
                 Real::Gradient(v) => Debug::Gradient(v),
                 Real::RadialGradient(v) => Debug::RadialGradient(v),
+                Real::ConicGradient(v) => Debug::ConicGradient(v),
                 Real::Iframe(v) => Debug::Iframe(v),
                 Real::PushReferenceFrame(v) => Debug::PushReferenceFrame(v),
                 Real::PushStackingContext(v) => Debug::PushStackingContext(v),
                 Real::PushShadow(v) => Debug::PushShadow(v),
                 Real::BackdropFilter(v) => Debug::BackdropFilter(v),
 
                 Real::PopReferenceFrame => Debug::PopReferenceFrame,
                 Real::PopStackingContext => Debug::PopStackingContext,
@@ -785,16 +786,17 @@ impl<'de> Deserialize<'de> for BuiltDisp
                 Debug::Line(v) => Real::Line(v),
                 Debug::Image(v) => Real::Image(v),
                 Debug::RepeatingImage(v) => Real::RepeatingImage(v),
                 Debug::YuvImage(v) => Real::YuvImage(v),
                 Debug::Border(v) => Real::Border(v),
                 Debug::BoxShadow(v) => Real::BoxShadow(v),
                 Debug::Gradient(v) => Real::Gradient(v),
                 Debug::RadialGradient(v) => Real::RadialGradient(v),
+                Debug::ConicGradient(v) => Real::ConicGradient(v),
                 Debug::PushStackingContext(v) => Real::PushStackingContext(v),
                 Debug::PushShadow(v) => Real::PushShadow(v),
                 Debug::BackdropFilter(v) => Real::BackdropFilter(v),
 
                 Debug::PopStackingContext => Real::PopStackingContext,
                 Debug::PopReferenceFrame => Real::PopReferenceFrame,
                 Debug::PopAllShadows => Real::PopAllShadows,
                 Debug::ReuseItem(k) => Real::ReuseItem(k),
@@ -1200,16 +1202,31 @@ impl DisplayListBuilder {
         extend_mode: di::ExtendMode,
     ) -> di::RadialGradient {
         let mut builder = GradientBuilder::with_stops(stops);
         let gradient = builder.radial_gradient(center, radius, extend_mode);
         self.push_stops(builder.stops());
         gradient
     }
 
+    /// NOTE: gradients must be pushed in the order they're created
+    /// because create_gradient stores the stops in anticipation.
+    pub fn create_conic_gradient(
+        &mut self,
+        center: LayoutPoint,
+        angle: f32,
+        stops: Vec<di::GradientStop>,
+        extend_mode: di::ExtendMode,
+    ) -> di::ConicGradient {
+        let mut builder = GradientBuilder::with_stops(stops);
+        let gradient = builder.conic_gradient(center, angle, extend_mode);
+        self.push_stops(builder.stops());
+        gradient
+    }
+
     pub fn push_border(
         &mut self,
         common: &di::CommonItemProperties,
         bounds: LayoutRect,
         widths: LayoutSideOffsets,
         details: di::BorderDetails,
     ) {
         let item = di::DisplayItem::Border(di::BorderDisplayItem {
@@ -1297,16 +1314,38 @@ impl DisplayListBuilder {
             gradient,
             tile_size,
             tile_spacing,
         });
 
         self.push_item(&item);
     }
 
+    /// Pushes a conic gradient to be displayed.
+    ///
+    /// See [`push_gradient`](#method.push_gradient) for explanation.
+    pub fn push_conic_gradient(
+        &mut self,
+        common: &di::CommonItemProperties,
+        bounds: LayoutRect,
+        gradient: di::ConicGradient,
+        tile_size: LayoutSize,
+        tile_spacing: LayoutSize,
+    ) {
+        let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem {
+            common: *common,
+            bounds,
+            gradient,
+            tile_size,
+            tile_spacing,
+        });
+
+        self.push_item(&item);
+    }
+
     pub fn push_reference_frame(
         &mut self,
         origin: LayoutPoint,
         parent_spatial_id: di::SpatialId,
         transform_style: di::TransformStyle,
         transform: PropertyBinding<LayoutTransform>,
         kind: di::ReferenceFrameKind,
     ) -> di::SpatialId {
--- a/gfx/wr/webrender_api/src/gradient_builder.rs
+++ b/gfx/wr/webrender_api/src/gradient_builder.rs
@@ -94,16 +94,37 @@ impl GradientBuilder {
             center,
             radius,
             start_offset,
             end_offset,
             extend_mode,
         }
     }
 
+    /// Produce a conic gradient, normalize the stops.
+    pub fn conic_gradient(
+        &mut self,
+        center: LayoutPoint,
+        angle: f32,
+        extend_mode: di::ExtendMode,
+    ) -> di::ConicGradient {
+        // XXX(ntim): Possibly handle negative angles ?
+
+        let (start_offset, end_offset) =
+            self.normalize(extend_mode);
+
+        di::ConicGradient {
+            center,
+            angle,
+            start_offset,
+            end_offset,
+            extend_mode,
+        }
+    }
+
     /// Gradients can be defined with stops outside the range of [0, 1]
     /// when this happens the gradient needs to be normalized by adjusting
     /// the gradient stops and gradient line into an equivalent gradient
     /// with stops in the range [0, 1]. this is done by moving the beginning
     /// of the gradient line to where stop[0] and the end of the gradient line
     /// to stop[n-1]. this function adjusts the stops in place, and returns
     /// the amount to adjust the gradient line start and stop.
     fn normalize(&mut self, extend_mode: di::ExtendMode) -> (f32, f32) {
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/gradient/conic-color-wheel.yaml
@@ -0,0 +1,14 @@
+---
+root:
+  items:
+    - type: clip
+      bounds: [50, 50, 300, 300]
+      complex:
+        - rect: [50, 50, 300, 300]
+          radius: 300
+      items:
+      - type: conic-gradient
+        bounds: 50 50 300 300
+        center: 150 150
+        angle: 0.0
+        stops: [0.0, red, 0.16666, yellow, 0.33333, green, 0.5, [0,255,255,1], 0.66666, blue, 0.83333, [255,0,255,1], 1.0, red]
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/gradient/conic-simple.yaml
@@ -0,0 +1,14 @@
+---
+root:
+  items:
+    - type: conic-gradient
+      bounds: 50 50 300 300
+      center: 150 150
+      # angle: 6.283185307179586
+      # angle: 5.497787143782138
+      # angle: 4.71238898038469
+      # angle: 3.141592653589793
+      # angle: 1.5707963267948966
+      # angle: 0.7853981633974483
+      angle: 0.0
+      stops: [0.0, red, 1.0, yellow]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/gradient/conic-start-angle.yaml
@@ -0,0 +1,8 @@
+---
+root:
+  items:
+    - type: conic-gradient
+      bounds: 50 50 300 300
+      center: 150 150
+      angle: 0.7853981633974483
+      stops: [0.0, red, 1.0, yellow]
\ No newline at end of file
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -920,16 +920,43 @@ impl YamlFrameReader {
             ExtendMode::Repeat
         } else {
             ExtendMode::Clamp
         };
 
         dl.create_radial_gradient(center, radius, stops, extend_mode)
     }
 
+    fn to_conic_gradient(&mut self, dl: &mut DisplayListBuilder, item: &Yaml) -> ConicGradient {
+        let center = item["center"].as_point().expect("conic gradient must have center");
+        let angle = item["angle"].as_force_f32().expect("conic gradient must have an angle");
+        let stops = item["stops"]
+            .as_vec()
+            .expect("conic gradient must have stops")
+            .chunks(2)
+            .map(|chunk| {
+                GradientStop {
+                    offset: chunk[0]
+                        .as_force_f32()
+                        .expect("gradient stop offset is not f32"),
+                    color: chunk[1]
+                        .as_colorf()
+                        .expect("gradient stop color is not color"),
+                }
+            })
+            .collect::<Vec<_>>();
+        let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
+            ExtendMode::Repeat
+        } else {
+            ExtendMode::Clamp
+        };
+
+        dl.create_conic_gradient(center, angle, stops, extend_mode)
+    }
+
     fn handle_rect(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut CommonItemProperties,
     ) {
         let bounds_key = if item["type"].is_badvalue() {
             "rect"
@@ -1085,16 +1112,43 @@ impl YamlFrameReader {
             &info,
             bounds,
             gradient,
             tile_size,
             tile_spacing,
         );
     }
 
+    fn handle_conic_gradient(
+        &mut self,
+        dl: &mut DisplayListBuilder,
+        item: &Yaml,
+        info: &mut CommonItemProperties,
+    ) {
+        let bounds_key = if item["type"].is_badvalue() {
+            "conic-gradient"
+        } else {
+            "bounds"
+        };
+        let bounds = item[bounds_key]
+            .as_rect()
+            .expect("conic gradient must have bounds");
+        let gradient = self.to_conic_gradient(dl, item);
+        let tile_size = item["tile-size"].as_size().unwrap_or(bounds.size);
+        let tile_spacing = item["tile-spacing"].as_size().unwrap_or(LayoutSize::zero());
+
+        dl.push_conic_gradient(
+            &info,
+            bounds,
+            gradient,
+            tile_size,
+            tile_spacing,
+        );
+    }
+
     fn handle_border(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         item: &Yaml,
         info: &mut CommonItemProperties,
     ) {
         let bounds_key = if item["type"].is_badvalue() {
@@ -1163,17 +1217,17 @@ impl YamlFrameReader {
                         top,
                         left,
                         bottom,
                         right,
                         radius,
                         do_aa,
                     }))
                 }
-                "image" | "gradient" | "radial-gradient" => {
+                "image" | "gradient" | "radial-gradient" | "conic-gradient" => {
                     let image_width = item["image-width"]
                         .as_i64()
                         .unwrap_or(bounds.size.width as i64);
                     let image_height = item["image-height"]
                         .as_i64()
                         .unwrap_or(bounds.size.height as i64);
                     let fill = item["fill"].as_bool().unwrap_or(false);
 
@@ -1216,17 +1270,20 @@ impl YamlFrameReader {
                         }
                         "gradient" => {
                             let gradient = self.to_gradient(dl, item);
                             NinePatchBorderSource::Gradient(gradient)
                         }
                         "radial-gradient" => {
                             let gradient = self.to_radial_gradient(dl, item);
                             NinePatchBorderSource::RadialGradient(gradient)
-
+                        }
+                        "conic-gradient" => {
+                            let gradient = self.to_conic_gradient(dl, item);
+                            NinePatchBorderSource::ConicGradient(gradient)
                         }
                         _ => unreachable!("Unexpected border type"),
                     };
 
                     Some(BorderDetails::NinePatch(NinePatchBorder {
                         source,
                         width: image_width as i32,
                         height: image_height as i32,
@@ -1588,16 +1645,17 @@ impl YamlFrameReader {
             "rect",
             "image",
             "text",
             "glyphs",
             "box-shadow", // Note: box_shadow shorthand check has to come before border.
             "border",
             "gradient",
             "radial-gradient",
+            "conic-gradient"
         ];
 
         for shorthand in shorthands.iter() {
             if !item[*shorthand].is_badvalue() {
                 return shorthand;
             }
         }
         item["type"].as_str().unwrap_or("unknown")
@@ -1694,16 +1752,17 @@ impl YamlFrameReader {
                 "text" | "glyphs" => self.handle_text(dl, wrench, item, &mut info),
                 "scroll-frame" => self.handle_scroll_frame(dl, wrench, item),
                 "sticky-frame" => self.handle_sticky_frame(dl, wrench, item),
                 "clip" => self.handle_clip(dl, wrench, item),
                 "clip-chain" => self.handle_clip_chain(dl, item),
                 "border" => self.handle_border(dl, wrench, item, &mut info),
                 "gradient" => self.handle_gradient(dl, item, &mut info),
                 "radial-gradient" => self.handle_radial_gradient(dl, item, &mut info),
+                "conic-gradient" => self.handle_conic_gradient(dl, item, &mut info),
                 "box-shadow" => self.handle_box_shadow(dl, item, &mut info),
                 "iframe" => self.handle_iframe(dl, item, &mut info),
                 "stacking-context" => {
                     self.add_stacking_context_from_yaml(dl, wrench, item, false, &mut info)
                 }
                 "reference-frame" => self.handle_reference_frame(dl, wrench, item),
                 "shadow" => self.handle_push_shadow(dl, item, &mut info),
                 "pop-all-shadows" => self.handle_pop_all_shadows(dl),
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -521,16 +521,39 @@ fn radial_gradient_to_yaml(
         let denormalized_stop = (stop.offset * stops_delta) + first_offset;
         denormalized_stops.push(Yaml::Real(denormalized_stop.to_string()));
         denormalized_stops.push(Yaml::String(color_to_string(stop.color)));
     }
     yaml_node(table, "stops", Yaml::Array(denormalized_stops));
     bool_node(table, "repeat", gradient.extend_mode == ExtendMode::Repeat);
 }
 
+fn conic_gradient_to_yaml(
+    table: &mut Table,
+    gradient: &webrender::api::ConicGradient,
+    stops_range: ItemRange<GradientStop>,
+) {
+    point_node(table, "center", &gradient.center);
+    f32_node(table, "angle", gradient.angle);
+
+    let first_offset = gradient.start_offset;
+    let last_offset = gradient.end_offset;
+    let stops_delta = last_offset - first_offset;
+    assert!(first_offset <= last_offset);
+
+    let mut denormalized_stops = vec![];
+    for stop in stops_range {
+        let denormalized_stop = (stop.offset * stops_delta) + first_offset;
+        denormalized_stops.push(Yaml::Real(denormalized_stop.to_string()));
+        denormalized_stops.push(Yaml::String(color_to_string(stop.color)));
+    }
+    yaml_node(table, "stops", Yaml::Array(denormalized_stops));
+    bool_node(table, "repeat", gradient.extend_mode == ExtendMode::Repeat);
+}
+
 enum CachedFont {
     Native(NativeFontHandle, Option<PathBuf>),
     Raw(Option<Vec<u8>>, u32, Option<PathBuf>),
 }
 
 struct CachedFontInstance {
     font_key: FontKey,
     glyph_size: Au,
@@ -1186,16 +1209,24 @@ impl YamlFrameWriter {
                                 NinePatchBorderSource::RadialGradient(gradient) => {
                                     str_node(&mut v, "border-type", "radial-gradient");
                                     radial_gradient_to_yaml(
                                         &mut v,
                                         &gradient,
                                         base.gradient_stops(),
                                     );
                                 }
+                                NinePatchBorderSource::ConicGradient(gradient) => {
+                                    str_node(&mut v, "border-type", "conic-gradient");
+                                    conic_gradient_to_yaml(
+                                        &mut v,
+                                        &gradient,
+                                        base.gradient_stops(),
+                                    );
+                                }
                             }
 
                             i32_node(&mut v, "image-width", details.width);
                             i32_node(&mut v, "image-height", details.height);
                             let slice = [
                                 details.slice.top,
                                 details.slice.right,
                                 details.slice.bottom,
@@ -1268,16 +1299,28 @@ impl YamlFrameWriter {
                     size_node(&mut v, "tile-size", &item.tile_size);
                     size_node(&mut v, "tile-spacing", &item.tile_spacing);
                     radial_gradient_to_yaml(
                         &mut v,
                         &item.gradient,
                         base.gradient_stops(),
                     );
                 }
+                DisplayItem::ConicGradient(item) => {
+                    str_node(&mut v, "type", "conic-gradient");
+                    rect_node(&mut v, "bounds", &item.bounds);
+                    common_node(&mut v, clip_id_mapper, &item.common);
+                    size_node(&mut v, "tile-size", &item.tile_size);
+                    size_node(&mut v, "tile-spacing", &item.tile_spacing);
+                    conic_gradient_to_yaml(
+                        &mut v,
+                        &item.gradient,
+                        base.gradient_stops(),
+                    );
+                }
                 DisplayItem::Iframe(item) => {
                     str_node(&mut v, "type", "iframe");
                     rect_node(&mut v, "bounds", &item.bounds);
                     rect_node(&mut v, "clip_rect", &item.clip_rect);
                     clip_and_scroll_node(
                         &mut v,
                         clip_id_mapper,
                         item.space_and_clip.clip_id,