Bug 1460861 - Update webrender to commit 9d20df4e76e3b19c569fd89965f70a2c278ff0c8. r=Gankro
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 14 May 2018 10:25:05 -0400
changeset 418084 a7aae42d912d50df70daab8184d27897e5844043
parent 418083 6cb1b7aa94a52aa4bcc9c5fa4261eded2aa982a4
child 418085 8f4ced935fb18dd9a3c505b6bccca55f7daa953d
push id63939
push userkgupta@mozilla.com
push dateMon, 14 May 2018 15:16:19 +0000
treeherderautoland@8f4ced935fb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGankro
bugs1460861
milestone62.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 1460861 - Update webrender to commit 9d20df4e76e3b19c569fd89965f70a2c278ff0c8. r=Gankro MozReview-Commit-ID: C9hlaYMrM7W
gfx/webrender/examples/animation.rs
gfx/webrender/examples/iframe.rs
gfx/webrender/res/prim_shared.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/scene.rs
gfx/webrender_api/src/api.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/blob.rs
gfx/wrench/src/scene.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -38,24 +38,24 @@ impl Example for App {
         _framebuffer_size: DeviceUintSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         // Create a 200x200 stacking context with an animated transform property.
         let bounds = (0, 0).to(200, 200);
 
         let filters = vec![
-            FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key), self.opacity),
+            FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key, self.opacity), self.opacity),
         ];
 
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
-            Some(PropertyBinding::Binding(self.property_key)),
+            Some(PropertyBinding::Binding(self.property_key, LayoutTransform::identity())),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             filters,
             GlyphRasterSpace::Screen,
         );
 
         let complex_clip = ComplexClipRegion {
--- a/gfx/webrender/examples/iframe.rs
+++ b/gfx/webrender/examples/iframe.rs
@@ -60,17 +60,17 @@ impl Example for App {
             true,
         );
         api.send_transaction(document_id, txn);
 
         // And this is for the root pipeline
         builder.push_stacking_context(
             &info,
             None,
-            Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
+            Some(PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity())),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
             Vec::new(),
             GlyphRasterSpace::Screen,
         );
         // red rect under the iframe: if this is visible, things have gone wrong
         builder.push_rect(&info, ColorF::new(1.0, 0.0, 0.0, 1.0));
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -15,18 +15,16 @@
 #define RASTER_SCREEN           1
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 // An A8 target for standalone tasks that is available to all passes.
 uniform sampler2DArray sSharedCacheA8;
 
-uniform sampler2D sGradients;
-
 vec2 clamp_rect(vec2 pt, RectWithSize rect) {
     return clamp(pt, rect.p0, rect.p0 + rect.size);
 }
 
 float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
     vec2 dir_to_p0 = p0 - p;
     return dot(normalize(perp_dir), dir_to_p0);
 }
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -16,17 +16,18 @@ use gpu_types::{BrushFlags, BrushInstanc
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, CompositePrimitiveInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, SimplePrimitiveInstance, ZBufferId};
 use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, CachedGradient, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
-use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore};
+use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
+use prim_store::CachedGradientIndex;
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
@@ -610,16 +611,18 @@ impl AlphaBatchBuilder {
         // If the primitive is internally decomposed into multiple sub-primitives we may not
         // use some of the per-primitive data typically stored in PrimitiveMetadata and get
         // it from each sub-primitive instead.
         let is_multiple_primitives = match prim_metadata.prim_kind {
             PrimitiveKind::Brush => {
                 let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
                 match brush.kind {
                     BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                    BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                    BrushKind::RadialGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
                     _ => false,
                 }
             }
             _ => false,
         };
 
         let prim_cache_address = if is_multiple_primitives {
             GpuCacheAddress::invalid()
@@ -1013,16 +1016,50 @@ impl AlphaBatchBuilder {
                                     task_address,
                                     z,
                                     user_data,
                                     tile.edge_flags
                                 );
                             }
                         }
                     }
+                    BrushKind::LinearGradient { gradient_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                        add_gradient_tiles(
+                            visible_tiles,
+                            gradient_index,
+                            BrushBatchKind::LinearGradient,
+                            specified_blend_mode,
+                            &task_relative_bounding_rect,
+                            clip_chain_rect_index,
+                            scroll_id,
+                            task_address,
+                            clip_task_address,
+                            z,
+                            ctx,
+                            gpu_cache,
+                            &mut self.batch_list,
+                        );
+                    }
+                    BrushKind::RadialGradient { gradient_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                        add_gradient_tiles(
+                            visible_tiles,
+                            gradient_index,
+                            BrushBatchKind::RadialGradient,
+                            specified_blend_mode,
+                            &task_relative_bounding_rect,
+                            clip_chain_rect_index,
+                            scroll_id,
+                            task_address,
+                            clip_task_address,
+                            z,
+                            ctx,
+                            gpu_cache,
+                            &mut self.batch_list,
+                        );
+                    }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                                 ctx.cached_gradients,
                         ) {
                             self.add_brush_to_batch(
@@ -1343,16 +1380,67 @@ impl AlphaBatchBuilder {
                 };
                 let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect);
                 batch.push(PrimitiveInstance::from(base_instance));
             }
         }
     }
 }
 
+fn add_gradient_tiles(
+    visible_tiles: &[VisibleGradientTile],
+    gradient_index: CachedGradientIndex,
+    kind: BrushBatchKind,
+    blend_mode: BlendMode,
+    task_relative_bounding_rect: &DeviceIntRect,
+    clip_chain_rect_index: ClipChainRectIndex,
+    scroll_id: ClipScrollNodeIndex,
+    task_address: RenderTaskAddress,
+    clip_task_address: RenderTaskAddress,
+    z: ZBufferId,
+    ctx: &RenderTargetContext,
+    gpu_cache: &GpuCache,
+    batch_list: &mut BatchList,
+) {
+    batch_list.add_bounding_rect(task_relative_bounding_rect);
+    let batch = batch_list.get_suitable_batch(
+        BatchKey {
+            blend_mode: blend_mode,
+            kind: BatchKind::Brush(kind),
+            textures: BatchTextures::no_texture(),
+        },
+        task_relative_bounding_rect
+    );
+
+    let stops_handle = &ctx.cached_gradients[gradient_index.0].handle;
+    let user_data = [stops_handle.as_int(gpu_cache), 0, 0];
+
+    let base_instance = BrushInstance {
+        picture_address: task_address,
+        prim_address: GpuCacheAddress::invalid(),
+        clip_chain_rect_index,
+        scroll_id,
+        clip_task_address,
+        z,
+        segment_index: 0,
+        edge_flags: EdgeAaSegmentMask::all(),
+        brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+        user_data,
+    };
+
+    for tile in visible_tiles {
+        batch.push(PrimitiveInstance::from(
+            BrushInstance {
+                prim_address: gpu_cache.get_address(&tile.handle),
+                ..base_instance
+            }
+        ));
+    }
+}
+
 fn get_image_tile_params(
     resource_cache: &ResourceCache,
     gpu_cache: &mut GpuCache,
     deferred_resolves: &mut Vec<DeferredResolve>,
     request: ImageRequest,
 ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
 
     let cache_item = resolve_image(
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -1795,28 +1795,38 @@ impl<'a> DisplayListFlattener<'a> {
                         segment.size,
                         LayoutSize::zero(),
                     );
                 }
             }
         }
     }
 
-    fn add_gradient_impl(
+    pub fn add_gradient(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stops: ItemRange<GradientStop>,
         stops_count: usize,
         extend_mode: ExtendMode,
-        gradient_index: CachedGradientIndex,
         stretch_size: LayoutSize,
+        mut tile_spacing: LayoutSize,
     ) {
+        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
+        self.cached_gradients.push(CachedGradient::new());
+
+        let mut prim_rect = info.rect;
+        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
+        let info = LayoutPrimitiveInfo {
+            rect: prim_rect,
+            .. *info
+        };
+
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
         // are also supplied in reverse that the rendered result will be equivalent. To do this,
         // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
         // just designate the reference orientation as start < end. Aligned gradient rendering
         // manages to produce the same result regardless of orientation, so don't worry about
         // reversing in that case.
         let reverse_stops = start_point.x > end_point.x ||
             (start_point.x == end_point.x && start_point.y > end_point.y);
@@ -1835,119 +1845,25 @@ impl<'a> DisplayListFlattener<'a> {
                 stops_range: stops,
                 stops_count,
                 extend_mode,
                 reverse_stops,
                 start_point: sp,
                 end_point: ep,
                 gradient_index,
                 stretch_size,
+                tile_spacing,
+                visible_tiles: Vec::new(),
             },
             None,
         );
 
         let prim = PrimitiveContainer::Brush(prim);
 
-        self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
-    }
-
-    pub fn add_gradient(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayoutPrimitiveInfo,
-        start_point: LayoutPoint,
-        end_point: LayoutPoint,
-        stops: ItemRange<GradientStop>,
-        stops_count: usize,
-        extend_mode: ExtendMode,
-        stretch_size: LayoutSize,
-        mut tile_spacing: LayoutSize,
-    ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
-        let mut prim_rect = info.rect;
-        simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
-        let info = LayoutPrimitiveInfo {
-            rect: prim_rect,
-            .. *info
-        };
-
-        if tile_spacing != LayoutSize::zero() {
-            let prim_infos = info.decompose(
-                stretch_size,
-                tile_spacing,
-                64 * 64,
-            );
-
-            if !prim_infos.is_empty() {
-                for prim_info in prim_infos {
-                    self.add_gradient_impl(
-                        clip_and_scroll,
-                        &prim_info,
-                        start_point,
-                        end_point,
-                        stops,
-                        stops_count,
-                        extend_mode,
-                        gradient_index,
-                        prim_info.rect.size,
-                    );
-                }
-
-                return;
-            }
-        }
-
-        self.add_gradient_impl(
-            clip_and_scroll,
-            &info,
-            start_point,
-            end_point,
-            stops,
-            stops_count,
-            extend_mode,
-            gradient_index,
-            stretch_size,
-        );
-    }
-
-    fn add_radial_gradient_impl(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayoutPrimitiveInfo,
-        center: LayoutPoint,
-        start_radius: f32,
-        end_radius: f32,
-        ratio_xy: f32,
-        stops: ItemRange<GradientStop>,
-        extend_mode: ExtendMode,
-        gradient_index: CachedGradientIndex,
-        stretch_size: LayoutSize,
-    ) {
-        let prim = BrushPrimitive::new(
-            BrushKind::RadialGradient {
-                stops_range: stops,
-                extend_mode,
-                center,
-                start_radius,
-                end_radius,
-                ratio_xy,
-                gradient_index,
-                stretch_size,
-            },
-            None,
-        );
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
-        );
+        self.add_primitive(clip_and_scroll, &info, Vec::new(), prim);
     }
 
     pub fn add_radial_gradient(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         center: LayoutPoint,
         start_radius: f32,
@@ -1963,54 +1879,37 @@ impl<'a> DisplayListFlattener<'a> {
 
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
         let info = LayoutPrimitiveInfo {
             rect: prim_rect,
             .. *info
         };
 
-        if tile_spacing != LayoutSize::zero() {
-            let prim_infos = info.decompose(
+        let prim = BrushPrimitive::new(
+            BrushKind::RadialGradient {
+                stops_range: stops,
+                extend_mode,
+                center,
+                start_radius,
+                end_radius,
+                ratio_xy,
+                gradient_index,
                 stretch_size,
                 tile_spacing,
-                64 * 64,
-            );
+                visible_tiles: Vec::new(),
+            },
+            None,
+        );
 
-            if !prim_infos.is_empty() {
-                for prim_info in prim_infos {
-                    self.add_radial_gradient_impl(
-                        clip_and_scroll,
-                        &prim_info,
-                        center,
-                        start_radius,
-                        end_radius,
-                        ratio_xy,
-                        stops,
-                        extend_mode,
-                        gradient_index,
-                        stretch_size,
-                    );
-                }
-
-                return;
-            }
-        }
-
-        self.add_radial_gradient_impl(
+        self.add_primitive(
             clip_and_scroll,
             &info,
-            center,
-            start_radius,
-            end_radius,
-            ratio_xy,
-            stops,
-            extend_mode,
-            gradient_index,
-            stretch_size,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_text(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         run_offset: LayoutVector2D,
         prim_info: &LayoutPrimitiveInfo,
@@ -2203,82 +2102,16 @@ pub fn build_scene(config: &FrameBuilder
     BuiltScene {
         scene: new_scene,
         frame_builder,
         clip_scroll_tree,
         removed_pipelines: request.removed_pipelines,
     }
 }
 
-trait PrimitiveInfoTiler {
-    fn decompose(
-        &self,
-        tile_size: LayoutSize,
-        tile_spacing: LayoutSize,
-        max_prims: usize,
-    ) -> Vec<LayoutPrimitiveInfo>;
-}
-
-impl PrimitiveInfoTiler for LayoutPrimitiveInfo {
-    fn decompose(
-        &self,
-        tile_size: LayoutSize,
-        tile_spacing: LayoutSize,
-        max_prims: usize,
-    ) -> Vec<LayoutPrimitiveInfo> {
-        let mut prims = Vec::new();
-        let tile_repeat = tile_size + tile_spacing;
-
-        if tile_repeat.width <= 0.0 ||
-           tile_repeat.height <= 0.0 {
-            return prims;
-        }
-
-        if tile_repeat.width < self.rect.size.width ||
-           tile_repeat.height < self.rect.size.height {
-            let clip_rect = self.clip_rect
-                .intersection(&self.rect)
-                .unwrap_or_else(LayoutRect::zero);
-            let rect_p0 = self.rect.origin;
-            let rect_p1 = self.rect.bottom_right();
-
-            let mut y0 = rect_p0.y;
-            while y0 < rect_p1.y {
-                let mut x0 = rect_p0.x;
-
-                while x0 < rect_p1.x {
-                    prims.push(LayoutPrimitiveInfo {
-                        rect: LayoutRect::new(
-                            LayoutPoint::new(x0, y0),
-                            tile_size,
-                        ),
-                        clip_rect,
-                        is_backface_visible: self.is_backface_visible,
-                        tag: self.tag,
-                    });
-
-                    // Mostly a safety against a crazy number of primitives
-                    // being generated. If we exceed that amount, just bail
-                    // out and only draw the maximum amount.
-                    if prims.len() > max_prims {
-                        warn!("too many prims found due to repeat/tile. dropping extra prims!");
-                        return prims;
-                    }
-
-                    x0 += tile_repeat.width;
-                }
-
-                y0 += tile_repeat.height;
-            }
-        }
-
-        prims
-    }
-}
-
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// Filters / mix-blend-mode effects
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -158,17 +158,17 @@ pub struct PicturePrimitive {
 }
 
 impl PicturePrimitive {
     pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.composite_mode {
             Some(PictureCompositeMode::Filter(ref mut filter)) => {
                 match *filter {
                     FilterOp::Opacity(ref binding, ref mut value) => {
-                        *value = properties.resolve_float(binding, *value);
+                        *value = properties.resolve_float(binding);
                     }
                     _ => {}
                 }
 
                 filter.is_visible()
             }
             _ => true,
         }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -226,17 +226,17 @@ impl OpacityBinding {
 
     // Resolve the current value of each opacity binding, and
     // store that as a single combined opacity. Returns true
     // if the opacity value changed from last time.
     pub fn update(&mut self, scene_properties: &SceneProperties) -> bool {
         let mut new_opacity = 1.0;
 
         for binding in &self.bindings {
-            let opacity = scene_properties.resolve_float(binding, 1.0);
+            let opacity = scene_properties.resolve_float(binding);
             new_opacity = new_opacity * opacity;
         }
 
         let changed = new_opacity != self.current;
         self.current = new_opacity;
 
         changed
     }
@@ -245,16 +245,21 @@ impl OpacityBinding {
 #[derive(Debug)]
 pub struct VisibleImageTile {
     pub tile_offset: TileOffset,
     pub handle: GpuCacheHandle,
     pub edge_flags: EdgeAaSegmentMask,
 }
 
 #[derive(Debug)]
+pub struct VisibleGradientTile {
+    pub handle: GpuCacheHandle,
+}
+
+#[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
@@ -280,26 +285,30 @@ pub enum BrushKind {
         gradient_index: CachedGradientIndex,
         stops_range: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stretch_size: LayoutSize,
+        tile_spacing: LayoutSize,
+        visible_tiles: Vec<VisibleGradientTile>,
     },
     LinearGradient {
         gradient_index: CachedGradientIndex,
         stops_range: ItemRange<GradientStop>,
         stops_count: usize,
         extend_mode: ExtendMode,
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
+        tile_spacing: LayoutSize,
+        visible_tiles: Vec<VisibleGradientTile>,
     },
     Border {
         request: ImageRequest,
     },
 }
 
 impl BrushKind {
     fn supports_segments(&self) -> bool {
@@ -1418,17 +1427,17 @@ impl PrimitiveStore {
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         pic_state_for_children: PictureState,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
-        let mut is_tiled_image = false;
+        let mut is_tiled = false;
         let metadata = &mut self.cpu_metadata[prim_index.0];
         #[cfg(debug_assertions)]
         {
             metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
@@ -1462,46 +1471,46 @@ impl PrimitiveStore {
                         let image_properties = frame_state
                             .resource_cache
                             .get_image_properties(request.key);
 
 
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             *current_epoch = image_properties.epoch;
-                            is_tiled_image = image_properties.tiling.is_some();
+                            is_tiled = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
                                 frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                             }
 
                             // Update opacity for this primitive to ensure the correct
                             // batching parameters are used.
                             metadata.opacity.is_opaque =
                                 image_properties.descriptor.is_opaque &&
                                 opacity_binding.current == 1.0;
 
-                            if *tile_spacing != LayoutSize::zero() && !is_tiled_image {
+                            if *tile_spacing != LayoutSize::zero() && !is_tiled {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: DeviceIntSize::new(
                                         image_properties.descriptor.width as i32,
                                         image_properties.descriptor.height as i32
                                     ),
                                     handle: None,
                                 };
                             }
 
                             // Work out whether this image is a normal / simple type, or if
                             // we need to pre-render it to the render task cache.
                             if let Some(rect) = sub_rect {
                                 // We don't properly support this right now.
-                                debug_assert!(!is_tiled_image);
+                                debug_assert!(!is_tiled);
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: rect.size,
                                     handle: None,
                                 };
                             }
 
                             let mut request_source_image = false;
@@ -1693,39 +1702,117 @@ impl PrimitiveStore {
                                 image_properties.descriptor.is_opaque;
 
                             frame_state.resource_cache.request_image(
                                 request,
                                 frame_state.gpu_cache,
                             );
                         }
                     }
-                    BrushKind::RadialGradient { gradient_index, stops_range, .. } => {
-                        let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
-                        if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
-                            let gradient_builder = GradientGpuBlockBuilder::new(
-                                stops_range,
-                                pic_context.display_list,
-                            );
-                            gradient_builder.build(
-                                false,
-                                &mut request,
+                    BrushKind::RadialGradient {
+                        gradient_index,
+                        stops_range,
+                        center,
+                        start_radius,
+                        end_radius,
+                        ratio_xy,
+                        extend_mode,
+                        stretch_size,
+                        tile_spacing,
+                        ref mut visible_tiles,
+                        ..
+                    } => {
+                        build_gradient_stops_request(
+                            gradient_index,
+                            stops_range,
+                            false,
+                            frame_state,
+                            pic_context,
+                        );
+
+                        if tile_spacing != LayoutSize::zero() {
+                            is_tiled = true;
+
+                            decompose_repeated_primitive(
+                                visible_tiles,
+                                metadata,
+                                &stretch_size,
+                                &tile_spacing,
+                                prim_run_context,
+                                frame_context,
+                                frame_state,
+                                &mut |rect, clip_rect, mut request| {
+                                    request.push(*rect);
+                                    request.push(*clip_rect);
+                                    request.push([
+                                        center.x,
+                                        center.y,
+                                        start_radius,
+                                        end_radius,
+                                    ]);
+                                    request.push([
+                                        ratio_xy,
+                                        pack_as_float(extend_mode as u32),
+                                        stretch_size.width,
+                                        stretch_size.height,
+                                    ]);
+                                    request.write_segment(*rect, [0.0; 4]);
+                                },
                             );
                         }
                     }
-                    BrushKind::LinearGradient { gradient_index, stops_range, reverse_stops, .. } => {
-                        let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
-                        if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
-                            let gradient_builder = GradientGpuBlockBuilder::new(
-                                stops_range,
-                                pic_context.display_list,
-                            );
-                            gradient_builder.build(
-                                reverse_stops,
-                                &mut request,
+                    BrushKind::LinearGradient {
+                        gradient_index,
+                        stops_range,
+                        reverse_stops,
+                        start_point,
+                        end_point,
+                        extend_mode,
+                        stretch_size,
+                        tile_spacing,
+                        ref mut visible_tiles,
+                        ..
+                    } => {
+
+                        build_gradient_stops_request(
+                            gradient_index,
+                            stops_range,
+                            reverse_stops,
+                            frame_state,
+                            pic_context,
+                        );
+
+                        if tile_spacing != LayoutSize::zero() {
+                            is_tiled = true;
+
+                            decompose_repeated_primitive(
+                                visible_tiles,
+                                metadata,
+                                &stretch_size,
+                                &tile_spacing,
+                                prim_run_context,
+                                frame_context,
+                                frame_state,
+                                &mut |rect, clip_rect, mut request| {
+                                    request.push(*rect);
+                                    request.push(*clip_rect);
+                                    request.push([
+                                        start_point.x,
+                                        start_point.y,
+                                        end_point.x,
+                                        end_point.y,
+                                    ]);
+                                    request.push([
+                                        pack_as_float(extend_mode as u32),
+                                        stretch_size.width,
+                                        stretch_size.height,
+                                        0.0,
+                                    ]);
+                                    request.write_segment(*rect, [0.0; 4]);
+                                }
                             );
                         }
                     }
                     BrushKind::Picture { pic_index, .. } => {
                         let pic = &mut self.pictures[pic_index.0];
                         pic.prepare_for_render(
                             prim_index,
                             metadata,
@@ -1746,17 +1833,17 @@ impl PrimitiveStore {
                             frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                         }
                     }
                     BrushKind::Clear => {}
                 }
             }
         }
 
-        if is_tiled_image {
+        if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
@@ -2475,16 +2562,90 @@ impl PrimitiveStore {
                 }
             }
         }
 
         result
     }
 }
 
+fn build_gradient_stops_request(
+    gradient_index: CachedGradientIndex,
+    stops_range: ItemRange<GradientStop>,
+    reverse_stops: bool,
+    frame_state: &mut FrameBuildingState,
+    pic_context: &PictureContext
+) {
+    let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
+    if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
+        let gradient_builder = GradientGpuBlockBuilder::new(
+            stops_range,
+            pic_context.display_list,
+        );
+        gradient_builder.build(
+            reverse_stops,
+            &mut request,
+        );
+    }
+}
+
+fn decompose_repeated_primitive(
+    visible_tiles: &mut Vec<VisibleGradientTile>,
+    metadata: &mut PrimitiveMetadata,
+    stretch_size: &LayoutSize,
+    tile_spacing: &LayoutSize,
+    prim_run_context: &PrimitiveRunContext,
+    frame_context: &FrameBuildingContext,
+    frame_state: &mut FrameBuildingState,
+    callback: &mut FnMut(&LayoutRect, &LayoutRect, GpuDataRequest),
+) {
+    visible_tiles.clear();
+
+    // Tighten the clip rect because decomposing the repeated image can
+    // produce primitives that are partially covering the original image
+    // rect and we want to clip these extra parts out.
+    let tight_clip_rect = metadata.local_clip_rect.intersection(&metadata.local_rect).unwrap();
+
+    let visible_rect = compute_conservative_visible_rect(
+        prim_run_context,
+        frame_context,
+        &tight_clip_rect
+    );
+    let stride = *stretch_size + *tile_spacing;
+
+    for_each_repetition(
+        &metadata.local_rect,
+        &visible_rect,
+        &stride,
+        &mut |origin, _| {
+
+            let mut handle = GpuCacheHandle::new();
+            if let Some(request) = frame_state.gpu_cache.request(&mut handle) {
+                let rect = LayoutRect {
+                    origin: *origin,
+                    size: *stretch_size,
+                };
+
+                callback(&rect, &tight_clip_rect, request);
+            }
+
+            visible_tiles.push(VisibleGradientTile { handle });
+        }
+    );
+
+    if visible_tiles.is_empty() {
+        // At this point if we don't have tiles to show it means we could probably
+        // have done a better a job at culling during an earlier stage.
+        // Clearing the screen rect has the effect of "culling out" the primitive
+        // from the point of view of the batch builder, and ensures we don't hit
+        // assertions later on because we didn't request any image.
+        metadata.screen_rect = None;
+    }
+}
+
 fn compute_conservative_visible_rect(
     prim_run_context: &PrimitiveRunContext,
     frame_context: &FrameBuildingContext,
     local_clip_rect: &LayoutRect,
 ) -> LayoutRect {
     let world_screen_rect = prim_run_context
         .clip_chain.combined_outer_screen_rect
         .to_f32() / frame_context.device_pixel_scale;
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -49,46 +49,37 @@ impl SceneProperties {
 
     /// Get the current value for a transform property.
     pub fn resolve_layout_transform(
         &self,
         property: &PropertyBinding<LayoutTransform>,
     ) -> LayoutTransform {
         match *property {
             PropertyBinding::Value(value) => value,
-            PropertyBinding::Binding(ref key) => {
+            PropertyBinding::Binding(ref key, v) => {
                 self.transform_properties
                     .get(&key.id)
                     .cloned()
-                    .unwrap_or_else(|| {
-                        warn!("Property binding has an invalid value.");
-                        debug!("key={:?}", key);
-                        LayoutTransform::identity()
-                    })
+                    .unwrap_or(v)
             }
         }
     }
 
     /// Get the current value for a float property.
     pub fn resolve_float(
         &self,
-        property: &PropertyBinding<f32>,
-        default_value: f32
+        property: &PropertyBinding<f32>
     ) -> f32 {
         match *property {
             PropertyBinding::Value(value) => value,
-            PropertyBinding::Binding(ref key) => {
+            PropertyBinding::Binding(ref key, v) => {
                 self.float_properties
                     .get(&key.id)
                     .cloned()
-                    .unwrap_or_else(|| {
-                        warn!("Property binding has an invalid value.");
-                        debug!("key={:?}", key);
-                        default_value
-                    })
+                    .unwrap_or(v)
             }
         }
     }
 }
 
 /// A representation of the layout within the display port for a given document or iframe.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -1073,34 +1073,31 @@ impl<T> PropertyBindingKey<T> {
             _phantom: PhantomData,
         }
     }
 }
 
 /// A binding property can either be a specific value
 /// (the normal, non-animated case) or point to a binding location
 /// to fetch the current value from.
+/// Note that Binding has also a non-animated value, the value is
+/// used for the case where the animation is still in-delay phase
+/// (i.e. the animation doesn't produce any animation values).
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum PropertyBinding<T> {
     Value(T),
-    Binding(PropertyBindingKey<T>),
+    Binding(PropertyBindingKey<T>, T),
 }
 
 impl<T> From<T> for PropertyBinding<T> {
     fn from(value: T) -> PropertyBinding<T> {
         PropertyBinding::Value(value)
     }
 }
 
-impl<T> From<PropertyBindingKey<T>> for PropertyBinding<T> {
-    fn from(key: PropertyBindingKey<T>) -> PropertyBinding<T> {
-        PropertyBinding::Binding(key)
-    }
-}
-
 /// The current value of an animated property. This is
 /// supplied by the calling code.
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct PropertyValue<T> {
     pub key: PropertyBindingKey<T>,
     pub value: T,
 }
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-4811a6e7c06f9dd1b4056e5f5e66983842983ba0
+9d20df4e76e3b19c569fd89965f70a2c278ff0c8
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -20,16 +20,22 @@ fn deserialize_blob(blob: &[u8]) -> Resu
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
         (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
         (Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
         _ => Err(()),
     };
 }
 
+// perform floor((x * a) / 255. + 0.5) see "Three wrongs make a right" for deriviation
+fn premul(x: u8, a: u8) -> u8 {
+    let t = (x as u32) * (a as u32) + 128;
+    ((t + (t >> 8)) >> 8) as u8
+}
+
 // This is the function that applies the deserialized drawing commands and generates
 // actual image data.
 fn render_blob(
     color: ColorU,
     descriptor: &BlobImageDescriptor,
     tile: Option<(TileSize, TileOffset)>,
     dirty_rect: Option<DeviceUintRect>,
 ) -> BlobImageResult {
@@ -68,20 +74,21 @@ fn render_blob(
             } else {
                 0
             };
             // ..nested in the per-tile checkerboard pattern
             let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
 
             match descriptor.format {
                 ImageFormat::BGRA8 => {
-                    texels[((y * descriptor.width + x) * 4 + 0) as usize] = color.b * checker + tc;
-                    texels[((y * descriptor.width + x) * 4 + 1) as usize] = color.g * checker + tc;
-                    texels[((y * descriptor.width + x) * 4 + 2) as usize] = color.r * checker + tc;
-                    texels[((y * descriptor.width + x) * 4 + 3) as usize] = color.a * checker + tc;
+                    let a = color.a * checker + tc;
+                    texels[((y * descriptor.width + x) * 4 + 0) as usize] = premul(color.b * checker + tc, a);
+                    texels[((y * descriptor.width + x) * 4 + 1) as usize] = premul(color.g * checker + tc, a);
+                    texels[((y * descriptor.width + x) * 4 + 2) as usize] = premul(color.r * checker + tc, a);
+                    texels[((y * descriptor.width + x) * 4 + 3) as usize] = a;
                 }
                 ImageFormat::R8 => {
                     texels[(y * descriptor.width + x) as usize] = color.a * checker + tc;
                 }
                 _ => {
                     return Err(BlobImageError::Other(
                         format!("Unsupported image format {:?}", descriptor.format),
                     ));
--- a/gfx/wrench/src/scene.rs
+++ b/gfx/wrench/src/scene.rs
@@ -40,37 +40,31 @@ impl SceneProperties {
     ) -> LayoutTransform {
         let property = match *property {
             Some(property) => property,
             None => return LayoutTransform::identity(),
         };
 
         match property {
             PropertyBinding::Value(matrix) => matrix,
-            PropertyBinding::Binding(ref key) => self.transform_properties
+            PropertyBinding::Binding(ref key, v) => self.transform_properties
                 .get(&key.id)
                 .cloned()
-                .unwrap_or_else(|| {
-                    println!("Property binding {:?} has an invalid value.", key);
-                    LayoutTransform::identity()
-                }),
+                .unwrap_or(v),
         }
     }
 
     /// Get the current value for a float property.
-    pub fn resolve_float(&self, property: &PropertyBinding<f32>, default_value: f32) -> f32 {
+    pub fn resolve_float(&self, property: &PropertyBinding<f32>) -> f32 {
         match *property {
             PropertyBinding::Value(value) => value,
-            PropertyBinding::Binding(ref key) => self.float_properties
+            PropertyBinding::Binding(ref key, v) => self.float_properties
                 .get(&key.id)
                 .cloned()
-                .unwrap_or_else(|| {
-                    println!("Property binding {:?} has an invalid value.", key);
-                    default_value
-                }),
+                .unwrap_or(v),
         }
     }
 }
 
 /// A representation of the layout within the display port for a given document or iframe.
 #[derive(Debug)]
 pub struct ScenePipeline {
     pub epoch: Epoch,
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -218,17 +218,17 @@ fn write_stacking_context(
             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({})",
-                                                  properties.resolve_float(&x, 1.0))))
+                                                  properties.resolve_float(&x))))
             }
             FilterOp::Saturate(x) => { filters.push(Yaml::String(format!("saturate({})", x))) }
             FilterOp::Sepia(x) => { filters.push(Yaml::String(format!("sepia({})", x))) }
             FilterOp::DropShadow(offset, blur, color) => {
                 filters.push(Yaml::String(format!("drop-shadow([{},{}],{},[{}])",
                                                   offset.x, offset.y,
                                                   blur,
                                                   color_to_string(color))))