Bug 1486405. Update webrender to commit 5fa5c46e167ca834d8fec3bf662bf420418698f4
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Thu, 30 Aug 2018 11:00:17 -0400
changeset 491882 0023a1c1c41bab21806ab185c74cc2fdbd0a76aa
parent 491881 1289f17ab75d08071891f71d34480000e2bb52e1
child 491883 a13d10011ddc0d9e6f1f891fb58c0a85ea33e6a1
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1486405
milestone63.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 1486405. Update webrender to commit 5fa5c46e167ca834d8fec3bf662bf420418698f4
gfx/webrender/res/brush_blend.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/scene.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/gradient_builder.rs
gfx/webrender_api/src/lib.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/yaml_frame_writer.rs
gfx/wrench/src/yaml_helper.rs
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -27,17 +27,20 @@ void brush_vs(
     int brush_flags,
     vec4 unused
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
     vec2 uv = snap_device_pos(vi) +
         src_task.common_data.task_rect.p0 -
         src_task.content_origin;
-    vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
+    vUv = vec3(
+        uv * gl_Position.w / texture_size, // multiply by W to compensate for perspective interpolation
+        src_task.common_data.texture_layer_index
+    );
 
     vec2 uv0 = src_task.common_data.task_rect.p0;
     vec2 uv1 = uv0 + src_task.common_data.task_rect.size;
     vUvClipBounds = vec4(uv0, uv1) / texture_size.xyxy;
 
     float lumR = 0.2126;
     float lumG = 0.7152;
     float lumB = 0.0722;
@@ -119,17 +122,18 @@ vec3 Invert(vec3 Cs, float amount) {
 vec3 Brightness(vec3 Cs, float amount) {
     // Apply the brightness factor.
     // Resulting color needs to be clamped to output range
     // since we are pre-multiplying alpha in the shader.
     return clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
 }
 
 Fragment brush_fs() {
-    vec4 Cs = texture(sColor0, vUv);
+    vec2 base_uv = vUv.xy * gl_FragCoord.w;
+    vec4 Cs = texture(sColor0, vec3(base_uv, vUv.z));
 
     if (Cs.a == 0.0) {
         return Fragment(vec4(0.0)); // could also `discard`
     }
 
     // Un-premultiply the input.
     float alpha = Cs.a;
     vec3 color = Cs.rgb / Cs.a;
@@ -150,14 +154,14 @@ Fragment brush_fs() {
             alpha *= vAmount;
             break;
         default:
             color = vColorMat * color + vColorOffset;
     }
 
     // Fail-safe to ensure that we don't sample outside the rendered
     // portion of a blend source.
-    alpha *= point_inside_rect(vUv.xy, vUvClipBounds.xy, vUvClipBounds.zw);
+    alpha *= point_inside_rect(base_uv, vUvClipBounds.xy, vUvClipBounds.zw);
 
     // Pre-multiply the alpha into the output value.
     return Fragment(alpha * vec4(color, 1.0));
 }
 #endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -824,30 +824,32 @@ impl AlphaBatchBuilder {
                                             Some(ref surface) => {
                                                 let key = BatchKey::new(
                                                     BatchKind::Brush(BrushBatchKind::Blend),
                                                     BlendMode::PremultipliedAlpha,
                                                     BatchTextures::render_target_cache(),
                                                 );
 
                                                 let filter_mode = match filter {
+                                                    FilterOp::Identity => 1, // matches `Contrast(1)`
                                                     FilterOp::Blur(..) => 0,
                                                     FilterOp::Contrast(..) => 1,
                                                     FilterOp::Grayscale(..) => 2,
                                                     FilterOp::HueRotate(..) => 3,
                                                     FilterOp::Invert(..) => 4,
                                                     FilterOp::Saturate(..) => 5,
                                                     FilterOp::Sepia(..) => 6,
                                                     FilterOp::Brightness(..) => 7,
                                                     FilterOp::Opacity(..) => 8,
                                                     FilterOp::DropShadow(..) => 9,
                                                     FilterOp::ColorMatrix(..) => 10,
                                                 };
 
                                                 let user_data = match filter {
+                                                    FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
                                                     FilterOp::Contrast(amount) |
                                                     FilterOp::Grayscale(amount) |
                                                     FilterOp::Invert(amount) |
                                                     FilterOp::Saturate(amount) |
                                                     FilterOp::Sepia(amount) |
                                                     FilterOp::Brightness(amount) |
                                                     FilterOp::Opacity(_, amount) => {
                                                         (amount * 65536.0) as i32
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -319,16 +319,17 @@ pub struct ClipStore {
 
 // A clip chain instance is what gets built for a given clip
 // chain id + local primitive region + positioning node.
 #[derive(Debug)]
 pub struct ClipChainInstance {
     pub clips_range: ClipNodeRange,
     pub local_clip_rect: LayoutRect,
     pub has_non_root_coord_system: bool,
+    pub has_non_local_clips: bool,
     pub world_clip_rect: WorldRect,
 }
 
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
             clip_nodes: Vec::new(),
             clip_chain_nodes: Vec::new(),
@@ -526,30 +527,33 @@ impl ClipStore {
 
         // Now, we've collected all the clip nodes that *potentially* affect this
         // primitive region, and reduced the size of the prim region as much as possible.
 
         // Run through the clip nodes, and see which ones affect this prim region.
 
         let first_clip_node_index = self.clip_node_indices.len() as u32;
         let mut has_non_root_coord_system = false;
+        let mut has_non_local_clips = false;
 
         // For each potential clip node
         for node_info in self.clip_node_info.drain(..) {
             let node = &mut self.clip_nodes[node_info.node_index.0 as usize];
 
             // See how this clip affects the prim region.
             let clip_result = match node_info.conversion {
                 ClipSpaceConversion::Local => {
                     node.item.get_clip_result(&local_bounding_rect)
                 }
                 ClipSpaceConversion::Offset(offset) => {
+                    has_non_local_clips = true;
                     node.item.get_clip_result(&local_bounding_rect.translate(&-offset))
                 }
                 ClipSpaceConversion::Transform(ref transform) => {
+                    has_non_local_clips = true;
                     node.item.get_clip_result_complex(
                         transform,
                         &world_bounding_rect,
                     )
                 }
             };
 
             match clip_result {
@@ -598,16 +602,17 @@ impl ClipStore {
             first: first_clip_node_index,
             count: self.clip_node_indices.len() as u32 - first_clip_node_index,
         };
 
         // Return a valid clip chain instance
         Some(ClipChainInstance {
             clips_range,
             has_non_root_coord_system,
+            has_non_local_clips,
             local_clip_rect,
             world_clip_rect,
         })
     }
 }
 
 #[derive(Debug)]
 pub struct LineDecorationClipSource {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2206,17 +2206,18 @@ impl Primitive {
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                 );
 
             match segment_clip_chain {
                 Some(segment_clip_chain) => {
-                    if segment_clip_chain.clips_range.count == 0 {
+                    if segment_clip_chain.clips_range.count == 0 ||
+                       (!segment.may_need_clip_mask && !segment_clip_chain.has_non_local_clips) {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                         continue;
                     }
 
                     let bounds = (segment_clip_chain.world_clip_rect * frame_context.device_pixel_scale)
                         .round_out()
                         .to_i32();
 
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -196,16 +196,17 @@ pub const OPACITY_EPSILON: f32 = 0.001;
 pub trait FilterOpHelpers {
     fn is_visible(&self) -> bool;
     fn is_noop(&self) -> bool;
 }
 
 impl FilterOpHelpers for FilterOp {
     fn is_visible(&self) -> bool {
         match *self {
+            FilterOp::Identity |
             FilterOp::Blur(..) |
             FilterOp::Brightness(..) |
             FilterOp::Contrast(..) |
             FilterOp::Grayscale(..) |
             FilterOp::HueRotate(..) |
             FilterOp::Invert(..) |
             FilterOp::Saturate(..) |
             FilterOp::Sepia(..) |
@@ -214,16 +215,17 @@ impl FilterOpHelpers for FilterOp {
             FilterOp::Opacity(_, amount) => {
                 amount > OPACITY_EPSILON
             }
         }
     }
 
     fn is_noop(&self) -> bool {
         match *self {
+            FilterOp::Identity => false, // this is intentional
             FilterOp::Blur(length) => length == 0.0,
             FilterOp::Brightness(amount) => amount == 1.0,
             FilterOp::Contrast(amount) => amount == 1.0,
             FilterOp::Grayscale(amount) => amount == 0.0,
             FilterOp::HueRotate(amount) => amount == 0.0,
             FilterOp::Invert(amount) => amount == 0.0,
             FilterOp::Opacity(_, amount) => amount >= 1.0,
             FilterOp::Saturate(amount) => amount == 1.0,
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -538,16 +538,19 @@ pub enum MixBlendMode {
     Hue = 12,
     Saturation = 13,
     Color = 14,
     Luminosity = 15,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
+    /// Filter that does no transformation of the colors, needed for
+    /// debug purposes only.
+    Identity,
     Blur(f32),
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>, f32),
     Saturate(f32),
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -11,17 +11,17 @@ use serde::ser::{Serializer, SerializeSe
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::marker::PhantomData;
 use std::{io, mem, ptr, slice};
 use time::precise_time_ns;
 use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
 use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
 use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
-use {FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, Gradient};
+use {FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, Gradient, GradientBuilder};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
 use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform};
 use {LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode, PipelineId};
 use {PropertyBinding, PushReferenceFrameDisplayListItem, PushStackingContextDisplayItem};
 use {RadialGradient, RadialGradientDisplayItem, RectangleDisplayItem, ReferenceFrame};
 use {ScrollFrameDisplayItem, ScrollSensitivity, Shadow, SpecificDisplayItem, StackingContext};
 use {StickyFrameDisplayItem, StickyOffsetBounds, TextDisplayItem, TransformStyle, YuvColorSpace};
 use {YuvData, YuvImageDisplayItem};
@@ -942,17 +942,22 @@ impl DisplayListBuilder {
                 index += 1;
             }
         }
 
         self.data = temp.data;
         index
     }
 
-    fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
+    /// Add an item to the display list.
+    ///
+    /// NOTE: It is usually preferable to use the specialized methods to push
+    /// display items. Pushing unexpected or invalid items here may
+    /// result in WebRender panicking or behaving in unexpected ways.
+    pub fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
         serialize_fast(
             &mut self.data,
             &DisplayItem {
                 item,
                 clip_and_scroll: *self.clip_stack.last().unwrap(),
                 info: *info,
             },
         )
@@ -1013,17 +1018,21 @@ impl DisplayListBuilder {
         bincode::serialize_into(
             &mut &mut data[byte_size_offset..],
             &byte_size,
         ).unwrap();
 
         debug_assert_eq!(len, count);
     }
 
-    fn push_iter<I>(&mut self, iter: I)
+    /// Push items from an iterator to the display list.
+    ///
+    /// NOTE: Pushing unexpected or invalid items to the display list
+    /// may result in panic and confusion.
+    pub fn push_iter<I>(&mut self, iter: I)
     where
         I: IntoIterator,
         I::IntoIter: ExactSizeIterator + Clone,
         I::Item: Serialize,
     {
         Self::push_iter_impl(&mut self.data, iter);
     }
 
@@ -1107,139 +1116,44 @@ impl DisplayListBuilder {
         });
 
         for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
             self.push_item(item, info);
             self.push_iter(split_glyphs);
         }
     }
 
-    // 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_stops(stops: &mut Vec<GradientStop>, extend_mode: ExtendMode) -> (f32, f32) {
-        assert!(stops.len() >= 2);
-
-        let first = *stops.first().unwrap();
-        let last = *stops.last().unwrap();
-
-        assert!(first.offset <= last.offset);
-
-        let stops_delta = last.offset - first.offset;
-
-        if stops_delta > 0.000001 {
-            for stop in stops {
-                stop.offset = (stop.offset - first.offset) / stops_delta;
-            }
-
-            (first.offset, last.offset)
-        } else {
-            // We have a degenerate gradient and can't accurately transform the stops
-            // what happens here depends on the repeat behavior, but in any case
-            // we reconstruct the gradient stops to something simpler and equivalent
-            stops.clear();
-
-            match extend_mode {
-                ExtendMode::Clamp => {
-                    // This gradient is two colors split at the offset of the stops,
-                    // so create a gradient with two colors split at 0.5 and adjust
-                    // the gradient line so 0.5 is at the offset of the stops
-                    stops.push(GradientStop { color: first.color, offset: 0.0, });
-                    stops.push(GradientStop { color: first.color, offset: 0.5, });
-                    stops.push(GradientStop { color: last.color, offset: 0.5, });
-                    stops.push(GradientStop { color: last.color, offset: 1.0, });
-
-                    let offset = last.offset;
-
-                    (offset - 0.5, offset + 0.5)
-                }
-                ExtendMode::Repeat => {
-                    // A repeating gradient with stops that are all in the same
-                    // position should just display the last color. I believe the
-                    // spec says that it should be the average color of the gradient,
-                    // but this matches what Gecko and Blink does
-                    stops.push(GradientStop { color: last.color, offset: 0.0, });
-                    stops.push(GradientStop { color: last.color, offset: 1.0, });
-
-                    (0.0, 1.0)
-                }
-            }
-        }
-    }
-
-    // NOTE: gradients must be pushed in the order they're created
-    // because create_gradient stores the stops in anticipation
+    /// NOTE: gradients must be pushed in the order they're created
+    /// because create_gradient stores the stops in anticipation.
     pub fn create_gradient(
         &mut self,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
-        mut stops: Vec<GradientStop>,
+        stops: Vec<GradientStop>,
         extend_mode: ExtendMode,
     ) -> Gradient {
-        let (start_offset, end_offset) =
-            DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
-
-        let start_to_end = end_point - start_point;
-
-        self.push_stops(&stops);
-
-        Gradient {
-            start_point: start_point + start_to_end * start_offset,
-            end_point: start_point + start_to_end * end_offset,
-            extend_mode,
-        }
+        let mut builder = GradientBuilder::with_stops(stops);
+        let gradient = builder.gradient(start_point, end_point, 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
+    /// NOTE: gradients must be pushed in the order they're created
+    /// because create_gradient stores the stops in anticipation.
     pub fn create_radial_gradient(
         &mut self,
         center: LayoutPoint,
         radius: LayoutSize,
-        mut stops: Vec<GradientStop>,
+        stops: Vec<GradientStop>,
         extend_mode: ExtendMode,
     ) -> RadialGradient {
-        if radius.width <= 0.0 || radius.height <= 0.0 {
-            // The shader cannot handle a non positive radius. So
-            // reuse the stops vector and construct an equivalent
-            // gradient.
-            let last_color = stops.last().unwrap().color;
-
-            let stops = [
-                GradientStop { offset: 0.0, color: last_color, },
-                GradientStop { offset: 1.0, color: last_color, },
-            ];
-
-            self.push_stops(&stops);
-
-            return RadialGradient {
-                center,
-                radius: LayoutSize::new(1.0, 1.0),
-                start_offset: 0.0,
-                end_offset: 1.0,
-                extend_mode,
-            };
-        }
-
-        let (start_offset, end_offset) =
-            DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
-
-        self.push_stops(&stops);
-
-        RadialGradient {
-            center,
-            radius,
-            start_offset,
-            end_offset,
-            extend_mode,
-        }
+        let mut builder = GradientBuilder::with_stops(stops);
+        let gradient = builder.radial_gradient(center, radius, extend_mode);
+        self.push_stops(builder.stops());
+        gradient
     }
 
     pub fn push_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         widths: BorderWidths,
         details: BorderDetails,
     ) {
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_api/src/gradient_builder.rs
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use {ExtendMode, Gradient, GradientStop, LayoutPoint, LayoutSize, RadialGradient};
+
+
+/// Construct a gradient to be used in display lists.
+///
+/// Each gradient needs at least two stops.
+pub struct GradientBuilder {
+    stops: Vec<GradientStop>,
+}
+
+impl GradientBuilder {
+    /// Create a new gradient builder.
+    pub fn new() -> GradientBuilder {
+        GradientBuilder {
+            stops: Vec::new(),
+        }
+    }
+
+    /// Create a gradient builder with a list of stops.
+    pub fn with_stops(stops: Vec<GradientStop>) -> GradientBuilder {
+        GradientBuilder { stops }
+    }
+
+    /// Push an additional stop for the gradient.
+    pub fn push(&mut self, stop: GradientStop) {
+        self.stops.push(stop);
+    }
+
+    /// Get a reference to the list of stops.
+    pub fn stops(&self) -> &[GradientStop] {
+        self.stops.as_ref()
+    }
+
+    /// Produce a linear gradient, normalize the stops.
+    pub fn gradient(
+        &mut self,
+        start_point: LayoutPoint,
+        end_point: LayoutPoint,
+        extend_mode: ExtendMode,
+    ) -> Gradient {
+        let (start_offset, end_offset) = self.normalize(extend_mode);
+        let start_to_end = end_point - start_point;
+
+        Gradient {
+            start_point: start_point + start_to_end * start_offset,
+            end_point: start_point + start_to_end * end_offset,
+            extend_mode,
+        }
+    }
+
+    /// Produce a radial gradient, normalize the stops.
+    ///
+    /// Will replace the gradient with a single color
+    /// if the radius negative.
+    pub fn radial_gradient(
+        &mut self,
+        center: LayoutPoint,
+        radius: LayoutSize,
+        extend_mode: ExtendMode,
+    ) -> RadialGradient {
+        if radius.width <= 0.0 || radius.height <= 0.0 {
+            // The shader cannot handle a non positive radius. So
+            // reuse the stops vector and construct an equivalent
+            // gradient.
+            let last_color = self.stops.last().unwrap().color;
+
+            self.stops.clear();
+            self.stops.push(GradientStop { offset: 0.0, color: last_color, });
+            self.stops.push(GradientStop { offset: 1.0, color: last_color, });
+
+            return RadialGradient {
+                center,
+                radius: LayoutSize::new(1.0, 1.0),
+                start_offset: 0.0,
+                end_offset: 1.0,
+                extend_mode,
+            };
+        }
+
+        let (start_offset, end_offset) =
+            self.normalize(extend_mode);
+
+        RadialGradient {
+            center,
+            radius,
+            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: ExtendMode) -> (f32, f32) {
+        let stops = &mut self.stops;
+        assert!(stops.len() >= 2);
+
+        let first = *stops.first().unwrap();
+        let last = *stops.last().unwrap();
+
+        assert!(first.offset <= last.offset);
+
+        let stops_delta = last.offset - first.offset;
+
+        if stops_delta > 0.000001 {
+            for stop in stops {
+                stop.offset = (stop.offset - first.offset) / stops_delta;
+            }
+
+            (first.offset, last.offset)
+        } else {
+            // We have a degenerate gradient and can't accurately transform the stops
+            // what happens here depends on the repeat behavior, but in any case
+            // we reconstruct the gradient stops to something simpler and equivalent
+            stops.clear();
+
+            match extend_mode {
+                ExtendMode::Clamp => {
+                    // This gradient is two colors split at the offset of the stops,
+                    // so create a gradient with two colors split at 0.5 and adjust
+                    // the gradient line so 0.5 is at the offset of the stops
+                    stops.push(GradientStop { color: first.color, offset: 0.0, });
+                    stops.push(GradientStop { color: first.color, offset: 0.5, });
+                    stops.push(GradientStop { color: last.color, offset: 0.5, });
+                    stops.push(GradientStop { color: last.color, offset: 1.0, });
+
+                    let offset = last.offset;
+
+                    (offset - 0.5, offset + 0.5)
+                }
+                ExtendMode::Repeat => {
+                    // A repeating gradient with stops that are all in the same
+                    // position should just display the last color. I believe the
+                    // spec says that it should be the average color of the gradient,
+                    // but this matches what Gecko and Blink does
+                    stops.push(GradientStop { color: last.color, offset: 0.0, });
+                    stops.push(GradientStop { color: last.color, offset: 1.0, });
+
+                    (0.0, 1.0)
+                }
+            }
+        }
+    }
+}
--- a/gfx/webrender_api/src/lib.rs
+++ b/gfx/webrender_api/src/lib.rs
@@ -28,18 +28,20 @@ extern crate time;
 
 
 mod api;
 pub mod channel;
 mod color;
 mod display_item;
 mod display_list;
 mod font;
+mod gradient_builder;
 mod image;
 mod units;
 
 pub use api::*;
 pub use color::*;
 pub use display_item::*;
 pub use display_list::*;
 pub use font::*;
+pub use gradient_builder::*;
 pub use image::*;
 pub use units::*;
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-816ff14c1805c145ccd60d0227d82b1541fc24eb
+5fa5c46e167ca834d8fec3bf662bf420418698f4
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -223,16 +223,17 @@ fn write_stacking_context(
     // mix_blend_mode
     if sc.mix_blend_mode != MixBlendMode::Normal {
         enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
     }
     // filters
     let mut filters = vec![];
     for filter in filter_iter {
         match filter {
+            FilterOp::Identity => { filters.push(Yaml::String("identity".into())) }
             FilterOp::Blur(x) => { filters.push(Yaml::String(format!("blur({})", x))) }
             FilterOp::Brightness(x) => { filters.push(Yaml::String(format!("brightness({})", x))) }
             FilterOp::Contrast(x) => { filters.push(Yaml::String(format!("contrast({})", x))) }
             FilterOp::Grayscale(x) => { filters.push(Yaml::String(format!("grayscale({})", x))) }
             FilterOp::HueRotate(x) => { filters.push(Yaml::String(format!("hue-rotate({})", x))) }
             FilterOp::Invert(x) => { filters.push(Yaml::String(format!("invert({})", x))) }
             FilterOp::Opacity(x, _) => {
                 filters.push(Yaml::String(format!("opacity({})",
--- a/gfx/wrench/src/yaml_helper.rs
+++ b/gfx/wrench/src/yaml_helper.rs
@@ -539,16 +539,19 @@ impl YamlHelper for Yaml {
 
     fn as_clip_mode(&self) -> Option<ClipMode> {
         self.as_str().and_then(|x| StringEnum::from_str(x))
     }
 
     fn as_filter_op(&self) -> Option<FilterOp> {
         if let Some(s) = self.as_str() {
             match parse_function(s) {
+                ("identity", _, _) => {
+                    Some(FilterOp::Identity)
+                }
                 ("blur", ref args, _) if args.len() == 1 => {
                     Some(FilterOp::Blur(args[0].parse().unwrap()))
                 }
                 ("brightness", ref args, _) if args.len() == 1 => {
                     Some(FilterOp::Brightness(args[0].parse().unwrap()))
                 }
                 ("contrast", ref args, _) if args.len() == 1 => {
                     Some(FilterOp::Contrast(args[0].parse().unwrap()))