Bug 1460861 - Update webrender to commit 9d20df4e76e3b19c569fd89965f70a2c278ff0c8. r?Gankro draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 14 May 2018 10:25:05 -0400
changeset 794815 b2305e8b89549dc0074f83005e80403861bb1c5d
parent 794690 45ec8fd380dd2c308e79dbb396ca87f2ce9b3f9c
child 794816 a0c7f39c62dd024c85cb48513cd6b1b0155de75d
push id109777
push userkgupta@mozilla.com
push dateMon, 14 May 2018 14:27:23 +0000
reviewersGankro
bugs1460861
milestone62.0a1
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))))