Bug 1498897. Update webrender to commit 98d507003c07c003ef0e0297dc4d29ee896a5868
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Tue, 16 Oct 2018 13:55:02 -0400
changeset 489812 bd4c433b0e5000a35f1b8f1694038d6f48f5abfe
parent 489811 c8d29a3d6554c10d9250196700f7a1acd5ea6a73
child 489813 86f5694c6c50995d0e3cd19be96d9db27240ab10
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
bugs1498897
milestone64.0a1
Bug 1498897. Update webrender to commit 98d507003c07c003ef0e0297dc4d29ee896a5868
gfx/webrender/Cargo.toml
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_blend.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_linear_gradient.glsl
gfx/webrender/res/brush_mix_blend.glsl
gfx/webrender/res/brush_radial_gradient.glsl
gfx/webrender/res/brush_solid.glsl
gfx/webrender/res/brush_yuv_image.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/renderer.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -15,17 +15,17 @@ debugger = ["ws", "serde_json", "serde",
 capture = ["webrender_api/serialize", "ron", "serde", "debug_renderer"]
 replay = ["webrender_api/deserialize", "ron", "serde"]
 debug_renderer = []
 pathfinder = ["pathfinder_font_renderer", "pathfinder_gfx_utils", "pathfinder_partitioner", "pathfinder_path_utils"]
 serialize_program = ["serde"]
 
 [dependencies]
 app_units = "0.7"
-base64 = { optional = true, version = "0.6" }
+base64 = { optional = true, version = "0.9" }
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.0"
 cfg-if = "0.1.2"
 euclid = "0.19"
 fxhash = "0.2.1"
 gleam = "0.6.3"
 image = { optional = true, version = "0.19" }
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -4,17 +4,17 @@
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 segment_data
 );
 
 #define VECS_PER_SEGMENT                    2
 
@@ -25,16 +25,17 @@ void brush_vs(
 
 void main(void) {
     // Load the brush instance from vertex attributes.
     int prim_header_address = aData.x;
     int clip_address = aData.y;
     int segment_index = aData.z & 0xffff;
     int edge_flags = (aData.z >> 16) & 0xff;
     int brush_flags = (aData.z >> 24) & 0xff;
+    int segment_user_data = aData.w;
     PrimitiveHeader ph = fetch_prim_header(prim_header_address);
 
     // Fetch the segment of this brush primitive we are drawing.
     int segment_address = ph.specific_prim_address +
                           VECS_PER_SPECIFIC_BRUSH +
                           segment_index * VECS_PER_SEGMENT;
 
     vec4[2] segment_data = fetch_from_gpu_cache_2(segment_address);
@@ -97,17 +98,17 @@ void main(void) {
 #endif
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
         ph.specific_prim_address,
         ph.local_rect,
         local_segment_rect,
-        ph.user_data,
+        ivec4(ph.user_data, segment_user_data),
         transform.m,
         pic_task,
         brush_flags,
         segment_data[1]
     );
 }
 #endif
 
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -16,17 +16,17 @@ flat varying vec4 vUvClipBounds;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
     ImageResource res = fetch_image_resource(user_data.x);
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -42,33 +42,33 @@ ImageBrushData fetch_image_data(int addr
     return data;
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize prim_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 texel_rect
 ) {
     ImageBrushData image_data = fetch_image_data(prim_address);
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 texture_size = vec2(1, 1);
 #else
     vec2 texture_size = vec2(textureSize(sColor0, 0));
 #endif
 
-    ImageResource res = fetch_image_resource(user_data.x);
+    ImageResource res = fetch_image_resource(user_data.w);
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
 
     RectWithSize local_rect = prim_rect;
     vec2 stretch_size = image_data.stretch_size;
 
     // If this segment should interpolate relative to the
     // segment, modify the parameters for that.
@@ -101,32 +101,32 @@ void brush_vs(
     vUvSampleBounds = vec4(
         min_uv + vec2(0.5),
         max_uv - vec2(0.5)
     ) / texture_size.xyxy;
 
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    int color_mode = user_data.y >> 16;
-    int raster_space = user_data.y & 0xffff;
+    int color_mode = user_data.x;
+    int raster_space = user_data.y;
 
     if (color_mode == COLOR_MODE_FROM_PASS) {
         color_mode = uMode;
     }
 
     // Derive the texture coordinates for this image, based on
     // whether the source image is a local-space or screen-space
     // image.
     switch (raster_space) {
         case RASTER_SCREEN: {
             // Since the screen space UVs specify an arbitrary quad, do
             // a bilinear interpolation to get the correct UV for this
             // local position.
-            ImageResourceExtra extra_data = fetch_image_resource_extra(user_data.x);
+            ImageResourceExtra extra_data = fetch_image_resource_extra(user_data.w);
             vec2 x = mix(extra_data.st_tl, extra_data.st_tr, f.x);
             vec2 y = mix(extra_data.st_bl, extra_data.st_br, f.x);
             f = mix(x, y, f.y);
             break;
         }
         default:
             break;
     }
--- a/gfx/webrender/res/brush_linear_gradient.glsl
+++ b/gfx/webrender/res/brush_linear_gradient.glsl
@@ -40,17 +40,17 @@ Gradient fetch_gradient(int address) {
     );
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 texel_rect
 ) {
     Gradient gradient = fetch_gradient(prim_address);
 
     if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
--- a/gfx/webrender/res/brush_mix_blend.glsl
+++ b/gfx/webrender/res/brush_mix_blend.glsl
@@ -17,17 +17,17 @@ vec2 snap_device_pos(VertexInfo vi, floa
     return vi.world_pos.xy * device_pixel_scale / max(0.0, vi.world_pos.w) + vi.snap_offset;
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
     vec2 snapped_device_pos = snap_device_pos(vi, pic_task.common_data.device_pixel_scale);
     vec2 texture_size = vec2(textureSize(sPrevPassColor, 0));
     vOp = user_data.x;
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -40,17 +40,17 @@ RadialGradient fetch_radial_gradient(int
     );
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 texel_rect
 ) {
     RadialGradient gradient = fetch_radial_gradient(prim_address);
 
     if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
--- a/gfx/webrender/res/brush_solid.glsl
+++ b/gfx/webrender/res/brush_solid.glsl
@@ -23,17 +23,17 @@ SolidBrush fetch_solid_primitive(int add
     return SolidBrush(data);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
     SolidBrush prim = fetch_solid_primitive(prim_address);
     vColor = prim.color;
 
--- a/gfx/webrender/res/brush_yuv_image.glsl
+++ b/gfx/webrender/res/brush_yuv_image.glsl
@@ -81,17 +81,17 @@ YuvPrimitive fetch_yuv_primitive(int add
     return YuvPrimitive(data.x);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
-    ivec3 user_data,
+    ivec4 user_data,
     mat4 transform,
     PictureTask pic_task,
     int brush_flags,
     vec4 unused
 ) {
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
     YuvPrimitive prim = fetch_yuv_primitive(prim_address);
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -13,25 +13,25 @@ use gpu_cache::{GpuCache, GpuCacheHandle
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
-use prim_store::{EdgeAaSegmentMask, ImageSource};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex};
 use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
-use std::{f32, i32};
+use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -122,135 +122,151 @@ impl BatchKey {
 #[inline]
 fn textures_compatible(t1: TextureSource, t2: TextureSource) -> bool {
     t1 == TextureSource::Invalid || t2 == TextureSource::Invalid || t1 == t2
 }
 
 pub struct AlphaBatchList {
     pub batches: Vec<PrimitiveBatch>,
     pub item_rects: Vec<Vec<WorldRect>>,
+    current_batch_index: usize,
+    current_prim_index: PrimitiveIndex,
 }
 
 impl AlphaBatchList {
     fn new() -> Self {
         AlphaBatchList {
             batches: Vec::new(),
             item_rects: Vec::new(),
+            current_prim_index: PrimitiveIndex(usize::MAX),
+            current_batch_index: usize::MAX,
         }
     }
 
-    pub fn get_suitable_batch(
+    pub fn set_params_and_get_batch(
         &mut self,
         key: BatchKey,
         bounding_rect: &WorldRect,
+        prim_index: PrimitiveIndex,
     ) -> &mut Vec<PrimitiveInstanceData> {
-        let mut selected_batch_index = None;
+        if prim_index != self.current_prim_index ||
+           self.current_batch_index == usize::MAX ||
+           !self.batches[self.current_batch_index].key.is_compatible_with(&key) {
+            let mut selected_batch_index = None;
 
-        match key.blend_mode {
-            BlendMode::SubpixelWithBgColor => {
-                'outer_multipass: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
-                    // Some subpixel batches are drawn in two passes. Because of this, we need
-                    // to check for overlaps with every batch (which is a bit different
-                    // than the normal batching below).
-                    for item_rect in &self.item_rects[batch_index] {
-                        if item_rect.intersects(bounding_rect) {
-                            break 'outer_multipass;
+            match key.blend_mode {
+                BlendMode::SubpixelWithBgColor => {
+                    'outer_multipass: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
+                        // Some subpixel batches are drawn in two passes. Because of this, we need
+                        // to check for overlaps with every batch (which is a bit different
+                        // than the normal batching below).
+                        for item_rect in &self.item_rects[batch_index] {
+                            if item_rect.intersects(bounding_rect) {
+                                break 'outer_multipass;
+                            }
+                        }
+
+                        if batch.key.is_compatible_with(&key) {
+                            selected_batch_index = Some(batch_index);
+                            break;
                         }
                     }
+                }
+                _ => {
+                    'outer_default: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
+                        // For normal batches, we only need to check for overlaps for batches
+                        // other than the first batch we consider. If the first batch
+                        // is compatible, then we know there isn't any potential overlap
+                        // issues to worry about.
+                        if batch.key.is_compatible_with(&key) {
+                            selected_batch_index = Some(batch_index);
+                            break;
+                        }
 
-                    if batch.key.is_compatible_with(&key) {
-                        selected_batch_index = Some(batch_index);
-                        break;
-                    }
-                }
-            }
-            _ => {
-                'outer_default: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
-                    // For normal batches, we only need to check for overlaps for batches
-                    // other than the first batch we consider. If the first batch
-                    // is compatible, then we know there isn't any potential overlap
-                    // issues to worry about.
-                    if batch.key.is_compatible_with(&key) {
-                        selected_batch_index = Some(batch_index);
-                        break;
-                    }
-
-                    // check for intersections
-                    for item_rect in &self.item_rects[batch_index] {
-                        if item_rect.intersects(bounding_rect) {
-                            break 'outer_default;
+                        // check for intersections
+                        for item_rect in &self.item_rects[batch_index] {
+                            if item_rect.intersects(bounding_rect) {
+                                break 'outer_default;
+                            }
                         }
                     }
                 }
             }
+
+            if selected_batch_index.is_none() {
+                let new_batch = PrimitiveBatch::new(key);
+                selected_batch_index = Some(self.batches.len());
+                self.batches.push(new_batch);
+                self.item_rects.push(Vec::new());
+            }
+
+            self.current_batch_index = selected_batch_index.unwrap();
+            self.item_rects[self.current_batch_index].push(*bounding_rect);
+            self.current_prim_index = prim_index;
         }
 
-        if selected_batch_index.is_none() {
-            let new_batch = PrimitiveBatch::new(key);
-            selected_batch_index = Some(self.batches.len());
-            self.batches.push(new_batch);
-            self.item_rects.push(Vec::new());
-        }
-
-        let selected_batch_index = selected_batch_index.unwrap();
-        self.item_rects[selected_batch_index].push(*bounding_rect);
-        &mut self.batches[selected_batch_index].instances
+        &mut self.batches[self.current_batch_index].instances
     }
 }
 
 pub struct OpaqueBatchList {
     pub pixel_area_threshold_for_new_batch: f32,
     pub batches: Vec<PrimitiveBatch>,
+    pub current_batch_index: usize,
 }
 
 impl OpaqueBatchList {
     fn new(pixel_area_threshold_for_new_batch: f32) -> Self {
         OpaqueBatchList {
             batches: Vec::new(),
             pixel_area_threshold_for_new_batch,
+            current_batch_index: usize::MAX,
         }
     }
 
-    pub fn get_suitable_batch(
+    pub fn set_params_and_get_batch(
         &mut self,
         key: BatchKey,
         bounding_rect: &WorldRect,
     ) -> &mut Vec<PrimitiveInstanceData> {
-        let mut selected_batch_index = None;
-        let item_area = bounding_rect.size.area();
+        if self.current_batch_index == usize::MAX ||
+           !self.batches[self.current_batch_index].key.is_compatible_with(&key) {
+            let mut selected_batch_index = None;
+            let item_area = bounding_rect.size.area();
 
-        // If the area of this primitive is larger than the given threshold,
-        // then it is large enough to warrant breaking a batch for. In this
-        // case we just see if it can be added to the existing batch or
-        // create a new one.
-        if item_area > self.pixel_area_threshold_for_new_batch {
-            if let Some(batch) = self.batches.last() {
-                if batch.key.is_compatible_with(&key) {
-                    selected_batch_index = Some(self.batches.len() - 1);
+            // If the area of this primitive is larger than the given threshold,
+            // then it is large enough to warrant breaking a batch for. In this
+            // case we just see if it can be added to the existing batch or
+            // create a new one.
+            if item_area > self.pixel_area_threshold_for_new_batch {
+                if let Some(batch) = self.batches.last() {
+                    if batch.key.is_compatible_with(&key) {
+                        selected_batch_index = Some(self.batches.len() - 1);
+                    }
+                }
+            } else {
+                // Otherwise, look back through a reasonable number of batches.
+                for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
+                    if batch.key.is_compatible_with(&key) {
+                        selected_batch_index = Some(batch_index);
+                        break;
+                    }
                 }
             }
-        } else {
-            // Otherwise, look back through a reasonable number of batches.
-            for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
-                if batch.key.is_compatible_with(&key) {
-                    selected_batch_index = Some(batch_index);
-                    break;
-                }
+
+            if selected_batch_index.is_none() {
+                let new_batch = PrimitiveBatch::new(key);
+                selected_batch_index = Some(self.batches.len());
+                self.batches.push(new_batch);
             }
+
+            self.current_batch_index = selected_batch_index.unwrap();
         }
 
-        if selected_batch_index.is_none() {
-            let new_batch = PrimitiveBatch::new(key);
-            selected_batch_index = Some(self.batches.len());
-            self.batches.push(new_batch);
-        }
-
-        let batch = &mut self.batches[selected_batch_index.unwrap()];
-
-        &mut batch.instances
+        &mut self.batches[self.current_batch_index].instances
     }
 
     fn finalize(&mut self) {
         // Reverse the instance arrays in the opaque batches
         // to get maximum z-buffer efficiency by drawing
         // front-to-back.
         // TODO(gw): Maybe we can change the batch code to
         //           build these in reverse and avoid having
@@ -273,53 +289,62 @@ impl BatchList {
         let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0;
 
         BatchList {
             alpha_batch_list: AlphaBatchList::new(),
             opaque_batch_list: OpaqueBatchList::new(batch_area_threshold),
         }
     }
 
-    pub fn get_suitable_batch(
+    pub fn push_single_instance(
         &mut self,
         key: BatchKey,
         bounding_rect: &WorldRect,
-    ) -> &mut Vec<PrimitiveInstanceData> {
+        prim_index: PrimitiveIndex,
+        instance: PrimitiveInstanceData,
+    ) {
         match key.blend_mode {
             BlendMode::None => {
                 self.opaque_batch_list
-                    .get_suitable_batch(key, bounding_rect)
+                    .set_params_and_get_batch(key, bounding_rect)
+                    .push(instance);
             }
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
-                    .get_suitable_batch(key, bounding_rect)
+                    .set_params_and_get_batch(key, bounding_rect, prim_index)
+                    .push(instance);
             }
         }
     }
 
-    // Remove any batches that were added but didn't get any instances
-    // added to them.
-    fn remove_unused_batches(&mut self) {
-        if self.opaque_batch_list
-               .batches
-               .last()
-               .map_or(false, |batch| batch.instances.is_empty()) {
-            self.opaque_batch_list.batches.pop().unwrap();
-        }
-
-        if self.alpha_batch_list
-               .batches
-               .last()
-               .map_or(false, |batch| batch.instances.is_empty()) {
-            self.alpha_batch_list.batches.pop().unwrap();
+    pub fn set_params_and_get_batch(
+        &mut self,
+        key: BatchKey,
+        bounding_rect: &WorldRect,
+        prim_index: PrimitiveIndex,
+    ) -> &mut Vec<PrimitiveInstanceData> {
+        match key.blend_mode {
+            BlendMode::None => {
+                self.opaque_batch_list
+                    .set_params_and_get_batch(key, bounding_rect)
+            }
+            BlendMode::Alpha |
+            BlendMode::PremultipliedAlpha |
+            BlendMode::PremultipliedDestOut |
+            BlendMode::SubpixelConstantTextColor(..) |
+            BlendMode::SubpixelWithBgColor |
+            BlendMode::SubpixelDualSource => {
+                self.alpha_batch_list
+                    .set_params_and_get_batch(key, bounding_rect, prim_index)
+            }
         }
     }
 
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
 
@@ -530,31 +555,31 @@ impl AlphaBatchBuilder {
             ];
 
             let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
-            let batch = self.batch_list
-                            .get_suitable_batch(
-                                key,
-                                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
-                            );
 
             let gpu_address = gpu_cache.get_address(&gpu_handle);
 
             let instance = SplitCompositeInstance::new(
                 prim_header_index,
                 gpu_address,
                 prim_headers.z_generator.next(),
             );
 
-            batch.push(PrimitiveInstanceData::from(instance));
+            self.batch_list.push_single_instance(
+                key,
+                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
+                prim_instance.prim_index,
+                PrimitiveInstanceData::from(instance),
+            );
         }
     }
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
     // in that picture are being drawn into the same target.
     fn add_prim_to_batch(
@@ -721,32 +746,37 @@ impl AlphaBatchBuilder {
                                                         ctx.resource_cache,
                                                         gpu_cache,
                                                     );
                                                 let key = BatchKey::new(
                                                     kind,
                                                     non_segmented_blend_mode,
                                                     textures,
                                                 );
-                                                let batch = self.batch_list.get_suitable_batch(key, bounding_rect);
                                                 let prim_header_index = prim_headers.push(&prim_header, [
-                                                    uv_rect_address.as_int(),
-                                                    (ShaderColorMode::Image as i32) << 16 |
+                                                    ShaderColorMode::Image as i32,
                                                     RasterizationSpace::Screen as i32,
                                                     0,
                                                 ]);
 
                                                 let instance = BrushInstance {
                                                     prim_header_index,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
                                                     clip_task_address,
+                                                    user_data: uv_rect_address.as_int(),
                                                 };
-                                                batch.push(PrimitiveInstanceData::from(instance));
+
+                                                self.batch_list.push_single_instance(
+                                                    key,
+                                                    bounding_rect,
+                                                    prim_instance.prim_index,
+                                                    PrimitiveInstanceData::from(instance),
+                                                );
                                             }
                                             FilterOp::DropShadow(offset, ..) => {
                                                 // Draw an instance of the shadow first, following by the content.
 
                                                 // Both the shadow and the content get drawn as a brush image.
                                                 let kind = BatchKind::Brush(
                                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                                 );
@@ -779,62 +809,68 @@ impl AlphaBatchBuilder {
                                                 let content_uv_rect_address = render_tasks[secondary_id]
                                                     .get_texture_address(gpu_cache)
                                                     .as_int();
 
                                                 // Get the GPU cache address of the extra data handle.
                                                 let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
 
                                                 let content_prim_header_index = prim_headers.push(&prim_header, [
-                                                    content_uv_rect_address,
-                                                    (ShaderColorMode::Image as i32) << 16 |
+                                                    ShaderColorMode::Image as i32,
                                                     RasterizationSpace::Screen as i32,
                                                     0,
                                                 ]);
 
                                                 let shadow_rect = prim_metadata.local_rect.translate(&offset);
                                                 let shadow_clip_rect = prim_metadata.local_clip_rect.translate(&offset);
 
                                                 let shadow_prim_header = PrimitiveHeader {
                                                     local_rect: shadow_rect,
                                                     local_clip_rect: shadow_clip_rect,
                                                     specific_prim_address: shadow_prim_address,
                                                     ..prim_header
                                                 };
 
                                                 let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, [
-                                                    shadow_uv_rect_address,
-                                                    (ShaderColorMode::Alpha as i32) << 16 |
+                                                    ShaderColorMode::Alpha as i32,
                                                     RasterizationSpace::Screen as i32,
                                                     0,
                                                 ]);
 
                                                 let shadow_instance = BrushInstance {
                                                     prim_header_index: shadow_prim_header_index,
                                                     clip_task_address,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
+                                                    user_data: shadow_uv_rect_address,
                                                 };
 
                                                 let content_instance = BrushInstance {
                                                     prim_header_index: content_prim_header_index,
                                                     clip_task_address,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
+                                                    user_data: content_uv_rect_address,
                                                 };
 
-                                                self.batch_list
-                                                    .get_suitable_batch(shadow_key, bounding_rect)
-                                                    .push(PrimitiveInstanceData::from(shadow_instance));
+                                                self.batch_list.push_single_instance(
+                                                    shadow_key,
+                                                    bounding_rect,
+                                                    prim_instance.prim_index,
+                                                    PrimitiveInstanceData::from(shadow_instance),
+                                                );
 
-                                                self.batch_list
-                                                    .get_suitable_batch(content_key, bounding_rect)
-                                                    .push(PrimitiveInstanceData::from(content_instance));
+                                                self.batch_list.push_single_instance(
+                                                    content_key,
+                                                    bounding_rect,
+                                                    prim_instance.prim_index,
+                                                    PrimitiveInstanceData::from(content_instance),
+                                                );
                                             }
                                             _ => {
                                                 let filter_mode = match filter {
                                                     FilterOp::Identity => 1, // matches `Contrast(1)`
                                                     FilterOp::Blur(..) => 0,
                                                     FilterOp::Contrast(..) => 1,
                                                     FilterOp::Grayscale(..) => 2,
                                                     FilterOp::HueRotate(..) => 3,
@@ -894,20 +930,25 @@ impl AlphaBatchBuilder {
                                                 ]);
 
                                                 let instance = BrushInstance {
                                                     prim_header_index,
                                                     clip_task_address,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
+                                                    user_data: 0,
                                                 };
 
-                                                let batch = self.batch_list.get_suitable_batch(key, bounding_rect);
-                                                batch.push(PrimitiveInstanceData::from(instance));
+                                                self.batch_list.push_single_instance(
+                                                    key,
+                                                    bounding_rect,
+                                                    prim_instance.prim_index,
+                                                    PrimitiveInstanceData::from(instance),
+                                                );
                                             }
                                         }
                                     }
                                     PictureCompositeMode::MixBlend(mode) => {
                                         let cache_task_id = surface.resolve_render_task_id();
                                         let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
 
                                         let key = BatchKey::new(
@@ -916,68 +957,75 @@ impl AlphaBatchBuilder {
                                                     task_id,
                                                     source_id: cache_task_id,
                                                     backdrop_id,
                                                 },
                                             ),
                                             BlendMode::PremultipliedAlpha,
                                             BatchTextures::no_texture(),
                                         );
-                                        let batch = self.batch_list.get_suitable_batch(key, bounding_rect);
                                         let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
                                         let source_task_address = render_tasks.get_task_address(cache_task_id);
                                         let prim_header_index = prim_headers.push(&prim_header, [
                                             mode as u32 as i32,
                                             backdrop_task_address.0 as i32,
                                             source_task_address.0 as i32,
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
                                             clip_task_address,
                                             segment_index: 0,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
+                                            user_data: 0,
                                         };
 
-                                        batch.push(PrimitiveInstanceData::from(instance));
+                                        self.batch_list.push_single_instance(
+                                            key,
+                                            bounding_rect,
+                                            prim_instance.prim_index,
+                                            PrimitiveInstanceData::from(instance),
+                                        );
                                     }
                                     PictureCompositeMode::Blit => {
                                         let cache_task_id = surface.resolve_render_task_id();
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                         );
                                         let key = BatchKey::new(
                                             kind,
                                             non_segmented_blend_mode,
                                             BatchTextures::render_target_cache(),
                                         );
-                                        let batch = self.batch_list.get_suitable_batch(
-                                            key,
-                                            bounding_rect,
-                                        );
 
                                         let uv_rect_address = render_tasks[cache_task_id]
                                             .get_texture_address(gpu_cache)
                                             .as_int();
                                         let prim_header_index = prim_headers.push(&prim_header, [
-                                            uv_rect_address,
-                                            (ShaderColorMode::Image as i32) << 16 |
+                                            ShaderColorMode::Image as i32,
                                             RasterizationSpace::Screen as i32,
                                             0,
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
                                             clip_task_address,
                                             segment_index: 0,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
+                                            user_data: uv_rect_address,
                                         };
-                                        batch.push(PrimitiveInstanceData::from(instance));
+
+                                        self.batch_list.push_single_instance(
+                                            key,
+                                            bounding_rect,
+                                            prim_instance.prim_index,
+                                            PrimitiveInstanceData::from(instance),
+                                        );
                                     }
                                 }
                             }
                             None => {
                                 // If this picture is being drawn into an existing target (i.e. with
                                 // no composition operation), recurse and add to the current batch list.
                                 self.add_pic_to_batch(
                                     picture,
@@ -990,73 +1038,83 @@ impl AlphaBatchBuilder {
                                     transforms,
                                     root_spatial_node_index,
                                 );
                             }
                         }
                     }
                     BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         for tile in visible_tiles {
-                            if let Some((batch_kind, textures, user_data)) = get_image_tile_params(
+                            if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
                                     ctx.resource_cache,
                                     gpu_cache,
                                     deferred_resolves,
                                     request.with_tile(tile.tile_offset),
                             ) {
                                 let prim_cache_address = gpu_cache.get_address(&tile.handle);
                                 let prim_header = PrimitiveHeader {
                                     specific_prim_address: prim_cache_address,
                                     local_rect: tile.local_rect,
                                     local_clip_rect: tile.local_clip_rect,
                                     ..prim_header
                                 };
                                 let prim_header_index = prim_headers.push(&prim_header, user_data);
 
                                 self.add_image_tile_to_batch(
+                                    prim_instance,
                                     batch_kind,
                                     specified_blend_mode,
                                     textures,
                                     prim_header_index,
                                     clip_task_address,
                                     bounding_rect,
-                                    tile.edge_flags
+                                    tile.edge_flags,
+                                    uv_rect_address,
                                 );
                             }
                         }
                     }
                     BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
+                            prim_instance,
                             visible_tiles,
                             stops_handle,
                             BrushBatchKind::LinearGradient,
                             specified_blend_mode,
                             bounding_rect,
                             clip_task_address,
                             gpu_cache,
                             &mut self.batch_list,
                             &prim_header,
                             prim_headers,
                         );
                     }
                     BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
+                            prim_instance,
                             visible_tiles,
                             stops_handle,
                             BrushBatchKind::RadialGradient,
                             specified_blend_mode,
                             bounding_rect,
                             clip_task_address,
                             gpu_cache,
                             &mut self.batch_list,
                             &prim_header,
                             prim_headers,
                         );
                     }
                     _ => {
-                        if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
+                        // TODO(gw): As an interim step, just return one value for the
+                        //           per-segment user data. In the future, this method
+                        //           will be expanded to optionally return a list of
+                        //           (BatchTextures, user_data) per segment, which will
+                        //           allow a different texture / render task to be used
+                        //           per segment.
+                        if let Some((batch_kind, textures, user_data, segment_user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                                 ctx.prim_store.chase_id == Some(prim_instance.prim_index),
                         ) {
                             let prim_header_index = prim_headers.push(&prim_header, user_data);
                             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
                                 println!("\t{:?} {:?}, task relative bounds {:?}",
@@ -1070,26 +1128,27 @@ impl AlphaBatchBuilder {
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
                                 prim_header_index,
                                 clip_task_address,
                                 bounding_rect,
                                 transform_kind,
                                 render_tasks,
+                                segment_user_data,
                             );
                         }
                     }
                 }
             }
             PrimitiveDetails::TextRun(ref text_cpu) => {
                 let subpx_dir = text_cpu.used_font.get_subpx_dir();
 
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
-                let batch_list = &mut self.batch_list;
+                let alpha_batch_list = &mut self.batch_list.alpha_batch_list;
 
                 ctx.resource_cache.fetch_glyphs(
                     text_cpu.used_font.clone(),
                     &text_cpu.glyph_keys,
                     glyph_fetch_buffer,
                     gpu_cache,
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, TextureSource::Invalid);
@@ -1149,20 +1208,24 @@ impl AlphaBatchBuilder {
                                     BlendMode::PremultipliedAlpha,
                                     ShaderColorMode::ColorBitmap,
                                 )
                             }
                         };
 
                         let prim_header_index = prim_headers.push(&prim_header, [0; 3]);
                         let key = BatchKey::new(kind, blend_mode, textures);
-                        let batch = batch_list.get_suitable_batch(key, bounding_rect);
                         let base_instance = GlyphInstance::new(
                             prim_header_index,
                         );
+                        let batch = alpha_batch_list.set_params_and_get_batch(
+                            key,
+                            bounding_rect,
+                            prim_instance.prim_index,
+                        );
 
                         for glyph in glyphs {
                             batch.push(base_instance.build(
                                 glyph.index_in_text_run,
                                 glyph.uv_rect_address.as_int(),
                                 (subpx_dir as u32 as i32) << 16 |
                                 (color_mode as u32 as i32),
                             ));
@@ -1170,87 +1233,74 @@ impl AlphaBatchBuilder {
                     },
                 );
             }
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
+        prim_instance: &PrimitiveInstance,
         batch_kind: BrushBatchKind,
         blend_mode: BlendMode,
         textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         edge_flags: EdgeAaSegmentMask,
+        uv_rect_address: GpuCacheAddress,
     ) {
         let base_instance = BrushInstance {
             prim_header_index,
             clip_task_address,
             segment_index: 0,
             edge_flags,
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+            user_data: uv_rect_address.as_int(),
         };
 
         let batch_key = BatchKey {
             blend_mode,
             kind: BatchKind::Brush(batch_kind),
             textures,
         };
-        let batch = self.batch_list.get_suitable_batch(batch_key, bounding_rect);
-        batch.push(PrimitiveInstanceData::from(base_instance));
+        self.batch_list.push_single_instance(
+            batch_key,
+            bounding_rect,
+            prim_instance.prim_index,
+            PrimitiveInstanceData::from(base_instance),
+        );
     }
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
         prim_instance: &PrimitiveInstance,
         batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskTree,
+        user_data: i32,
     ) {
         let base_instance = BrushInstance {
             prim_header_index,
             clip_task_address,
             segment_index: 0,
             edge_flags: EdgeAaSegmentMask::all(),
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+            user_data,
         };
 
         match brush.segment_desc {
             Some(ref segment_desc) => {
-                let alpha_batch_key = BatchKey {
-                    blend_mode: alpha_blend_mode,
-                    kind: BatchKind::Brush(batch_kind),
-                    textures,
-                };
-
-                let alpha_batch = self.batch_list.alpha_batch_list.get_suitable_batch(
-                    alpha_batch_key,
-                    bounding_rect,
-                );
-
-                let opaque_batch_key = BatchKey {
-                    blend_mode: BlendMode::None,
-                    kind: BatchKind::Brush(batch_kind),
-                    textures,
-                };
-
-                let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
-                    opaque_batch_key,
-                    bounding_rect,
-                );
-
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
                     let needs_blending = !prim_instance.opacity.is_opaque ||
                                          segment.clip_task_id.needs_blending() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
                     let clip_task_address = match segment.clip_task_id {
                         BrushSegmentTaskId::RenderTaskId(id) =>
@@ -1262,57 +1312,68 @@ impl AlphaBatchBuilder {
                     let instance = PrimitiveInstanceData::from(BrushInstance {
                         segment_index: i as i32,
                         edge_flags: segment.edge_flags,
                         clip_task_address,
                         brush_flags: base_instance.brush_flags | segment.brush_flags,
                         ..base_instance
                     });
 
-                    if needs_blending {
-                        alpha_batch.push(instance);
-                    } else {
-                        opaque_batch.push(instance);
-                    }
+                    let batch_key = BatchKey {
+                        blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
+                        kind: BatchKind::Brush(batch_kind),
+                        textures,
+                    };
+
+                    self.batch_list.push_single_instance(
+                        batch_key,
+                        bounding_rect,
+                        prim_instance.prim_index,
+                        instance,
+                    );
                 }
             }
             None => {
                 let batch_key = BatchKey {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(batch_kind),
                     textures,
                 };
-                let batch = self.batch_list.get_suitable_batch(batch_key, bounding_rect);
-                batch.push(PrimitiveInstanceData::from(base_instance));
+                self.batch_list.push_single_instance(
+                    batch_key,
+                    bounding_rect,
+                    prim_instance.prim_index,
+                    PrimitiveInstanceData::from(base_instance),
+                );
             }
         }
-
-        self.batch_list.remove_unused_batches();
     }
 }
 
 fn add_gradient_tiles(
+    prim_instance: &PrimitiveInstance,
     visible_tiles: &[VisibleGradientTile],
     stops_handle: &GpuCacheHandle,
     kind: BrushBatchKind,
     blend_mode: BlendMode,
     bounding_rect: &WorldRect,
     clip_task_address: RenderTaskAddress,
     gpu_cache: &GpuCache,
     batch_list: &mut BatchList,
     base_prim_header: &PrimitiveHeader,
     prim_headers: &mut PrimitiveHeaders,
 ) {
-    let batch = batch_list.get_suitable_batch(
+    let batch = batch_list.set_params_and_get_batch(
         BatchKey {
             blend_mode: blend_mode,
             kind: BatchKind::Brush(kind),
             textures: BatchTextures::no_texture(),
         },
         bounding_rect,
+        prim_instance.prim_index,
     );
 
     let user_data = [stops_handle.as_int(gpu_cache), 0, 0];
 
     for tile in visible_tiles {
         let prim_header = PrimitiveHeader {
             specific_prim_address: gpu_cache.get_address(&tile.handle),
             local_rect: tile.local_rect,
@@ -1323,60 +1384,61 @@ fn add_gradient_tiles(
 
         batch.push(PrimitiveInstanceData::from(
             BrushInstance {
                 prim_header_index,
                 clip_task_address,
                 segment_index: 0,
                 edge_flags: EdgeAaSegmentMask::all(),
                 brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
+                user_data: 0,
             }
         ));
     }
 }
 
 fn get_image_tile_params(
     resource_cache: &ResourceCache,
     gpu_cache: &mut GpuCache,
     deferred_resolves: &mut Vec<DeferredResolve>,
     request: ImageRequest,
-) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
+) -> Option<(BrushBatchKind, BatchTextures, [i32; 3], GpuCacheAddress)> {
 
     let cache_item = resolve_image(
         request,
         resource_cache,
         gpu_cache,
         deferred_resolves,
     );
 
     if cache_item.texture_id == TextureSource::Invalid {
         None
     } else {
         let textures = BatchTextures::color(cache_item.texture_id);
         Some((
             BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
             textures,
             [
-                cache_item.uv_rect_handle.as_int(gpu_cache),
-                (ShaderColorMode::Image as i32) << 16 |
-                     RasterizationSpace::Local as i32,
+                ShaderColorMode::Image as i32,
+                RasterizationSpace::Local as i32,
                 0,
             ],
+            gpu_cache.get_address(&cache_item.uv_rect_handle),
         ))
     }
 }
 
 impl BrushPrimitive {
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
         is_chased: bool,
-    ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
+    ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3], i32)> {
         match self.kind {
             BrushKind::Image { request, ref source, .. } => {
                 let cache_item = match *source {
                     ImageSource::Default => {
                         resolve_image(
                             request,
                             resource_cache,
                             gpu_cache,
@@ -1400,49 +1462,50 @@ impl BrushPrimitive {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     Some((
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                         textures,
                         [
-                            cache_item.uv_rect_handle.as_int(gpu_cache),
-                            (ShaderColorMode::Image as i32) << 16|
-                             RasterizationSpace::Local as i32,
+                            ShaderColorMode::Image as i32,
+                            RasterizationSpace::Local as i32,
                             0,
                         ],
+                        cache_item.uv_rect_handle.as_int(gpu_cache),
                     ))
                 }
             }
             BrushKind::LineDecoration { ref handle, style, .. } => {
                 match style {
                     LineStyle::Solid => {
                         Some((
                             BrushBatchKind::Solid,
                             BatchTextures::no_texture(),
                             [0; 3],
+                            0,
                         ))
                     }
                     LineStyle::Dotted |
                     LineStyle::Dashed |
                     LineStyle::Wavy => {
                         let rt_cache_entry = resource_cache
                             .get_cached_render_task(handle.as_ref().unwrap());
                         let cache_item = resource_cache.get_texture_cache_item(&rt_cache_entry.handle);
                         let textures = BatchTextures::color(cache_item.texture_id);
                         Some((
                             BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                             textures,
                             [
-                                cache_item.uv_rect_handle.as_int(gpu_cache),
-                                (ShaderColorMode::Image as i32) << 16|
-                                 RasterizationSpace::Local as i32,
+                                ShaderColorMode::Image as i32,
+                                RasterizationSpace::Local as i32,
                                 0,
                             ],
+                            cache_item.uv_rect_handle.as_int(gpu_cache),
                         ))
                     }
                 }
             }
             BrushKind::Border { ref source, .. } => {
                 let cache_item = match *source {
                     BorderSource::Image(request) => {
                         resolve_image(
@@ -1467,61 +1530,65 @@ impl BrushPrimitive {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     Some((
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                         textures,
                         [
-                            cache_item.uv_rect_handle.as_int(gpu_cache),
-                            (ShaderColorMode::Image as i32) << 16|
-                             RasterizationSpace::Local as i32,
+                            ShaderColorMode::Image as i32,
+                            RasterizationSpace::Local as i32,
                             0,
                         ],
+                        cache_item.uv_rect_handle.as_int(gpu_cache),
                     ))
                 }
             }
             BrushKind::Picture { .. } => {
                 panic!("bug: get_batch_key is handled at higher level for pictures");
             }
             BrushKind::Solid { .. } => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
+                    0,
                 ))
             }
             BrushKind::Clear => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
+                    0,
                 ))
             }
             BrushKind::RadialGradient { ref stops_handle, .. } => {
                 Some((
                     BrushBatchKind::RadialGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
+                    0,
                 ))
             }
             BrushKind::LinearGradient { ref stops_handle, .. } => {
                 Some((
                     BrushBatchKind::LinearGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
+                    0,
                 ))
             }
             BrushKind::YuvImage { format, yuv_key, image_rendering, color_depth, color_space } => {
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
 
                 //yuv channel
                 let channel_count = format.get_plane_num();
@@ -1567,16 +1634,17 @@ impl BrushPrimitive {
                 Some((
                     kind,
                     textures,
                     [
                         uv_rect_addresses[0],
                         uv_rect_addresses[1],
                         uv_rect_addresses[2],
                     ],
+                    0,
                 ))
             }
         }
     }
 }
 
 impl Primitive {
     fn get_blend_mode(&self) -> BlendMode {
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -326,28 +326,29 @@ bitflags! {
 //           compressed to a smaller size.
 #[repr(C)]
 pub struct BrushInstance {
     pub prim_header_index: PrimitiveHeaderIndex,
     pub clip_task_address: RenderTaskAddress,
     pub segment_index: i32,
     pub edge_flags: EdgeAaSegmentMask,
     pub brush_flags: BrushFlags,
+    pub user_data: i32,
 }
 
 impl From<BrushInstance> for PrimitiveInstanceData {
     fn from(instance: BrushInstance) -> Self {
         PrimitiveInstanceData {
             data: [
                 instance.prim_header_index.0,
                 instance.clip_task_address.0 as i32,
                 instance.segment_index |
                 ((instance.edge_flags.bits() as i32) << 16) |
                 ((instance.brush_flags.bits() as i32) << 24),
-                0,
+                instance.user_data,
             ]
         }
     }
 }
 
 // Represents the information about a transform palette
 // entry that is passed to shaders. It includes an index
 // into the transform palette, and a set of flags. The
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -2663,16 +2663,17 @@ impl Renderer {
                 }
             }
         }
 
         if self.debug_flags.contains(DebugFlags::ECHO_DRIVER_MESSAGES) {
             self.device.echo_driver_messages();
         }
 
+        stats.texture_upload_kb = self.profile_counters.texture_data_uploaded.get();
         self.backend_profile_counters.reset();
         self.profile_counters.reset();
         self.profile_counters.frame_counter.inc();
 
         profile_timers.cpu_time.profile(|| {
             let _gm = self.gpu_profile.start_marker("end frame");
             self.gpu_profile.end_frame();
             #[cfg(feature = "debug_renderer")]
@@ -4601,24 +4602,26 @@ impl DebugServer {
 // Some basic statistics about the rendered scene
 // that we can use in wrench reftests to ensure that
 // tests are batching and/or allocating on render
 // targets as we expect them to.
 pub struct RendererStats {
     pub total_draw_calls: usize,
     pub alpha_target_count: usize,
     pub color_target_count: usize,
+    pub texture_upload_kb: usize,
 }
 
 impl RendererStats {
     pub fn empty() -> Self {
         RendererStats {
             total_draw_calls: 0,
             alpha_target_count: 0,
             color_target_count: 0,
+            texture_upload_kb: 0,
         }
     }
 }
 
 
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-5adc86c19cbef6697975ea078fa0d10635e5d660
+98d507003c07c003ef0e0297dc4d29ee896a5868
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -1,17 +1,17 @@
 [package]
 name = "wrench"
 version = "0.3.0"
 authors = ["Vladimir Vukicevic <vladimir@pobox.com>"]
 build = "build.rs"
 license = "MPL-2.0"
 
 [dependencies]
-base64 = "0.6"
+base64 = "0.9"
 bincode = "1.0"
 byteorder = "1.0"
 env_logger = { version = "0.5", optional = true }
 euclid = "0.19"
 gleam = "0.6.2"
 glutin = "0.17"
 app_units = "0.7"
 image = "0.19"