Bug 1510090 - Update webrender to commit 05d4eccfa6dd7f667a1f74b12134257a85bea047 (WR PR #3350). r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Tue, 27 Nov 2018 02:55:31 +0000
changeset 504655 d2d2b375ea0aa3b0eddb50428c37e901fa7b90d3
parent 504654 21bc8b9a89ac7658f85a4f2e0fd278e012f2e32e
child 504656 2859c48a1a810f72032d9d592c63900390e689a0
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1510090
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1510090 - Update webrender to commit 05d4eccfa6dd7f667a1f74b12134257a85bea047 (WR PR #3350). r=kats https://github.com/servo/webrender/pull/3350 Differential Revision: https://phabricator.services.mozilla.com/D13031
gfx/webrender_bindings/revision.txt
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store.rs
gfx/wr/webrender/src/surface.rs
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-f3e489eebe9ffd5229c93aa4e17f4c3a7e6cb31d
+05d4eccfa6dd7f667a1f74b12134257a85bea047
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -11,19 +11,19 @@ use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 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::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, DeferredResolve, PrimitiveTemplateKind, PrimitiveDataStore};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind, PrimitiveStore};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind};
 use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
-use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveDetails, Primitive};
+use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, 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 smallvec::SmallVec;
 use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
@@ -902,16 +902,17 @@ impl AlphaBatchBuilder {
                                 PrimitiveInstanceKind::Picture { pic_index } => pic_index,
                                 PrimitiveInstanceKind::LineDecoration { .. } |
                                 PrimitiveInstanceKind::TextRun { .. } |
                                 PrimitiveInstanceKind::LegacyPrimitive { .. } |
                                 PrimitiveInstanceKind::NormalBorder { .. } |
                                 PrimitiveInstanceKind::ImageBorder { .. } |
                                 PrimitiveInstanceKind::Rectangle { .. } |
                                 PrimitiveInstanceKind::YuvImage { .. } |
+                                PrimitiveInstanceKind::Image { .. } |
                                 PrimitiveInstanceKind::Clear => {
                                     unreachable!();
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[pic_index.0];
 
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
@@ -1404,24 +1405,23 @@ impl AlphaBatchBuilder {
             ) => {
                 let prim = &ctx.prim_store.primitives[prim_index.0];
 
                 // If the primitive is internally decomposed into multiple sub-primitives we may not
                 // use some of the per-primitive data and get it from each sub-primitive instead.
                 let is_multiple_primitives = match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         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(),
                         }
                     }
                 };
 
-                let specified_blend_mode = prim_instance.get_blend_mode(&prim.details);
+                let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         let non_segmented_blend_mode = if !brush.opacity.is_opaque ||
                             prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
                             transform_kind == TransformedRectKind::Complex
                         {
                             specified_blend_mode
@@ -1445,51 +1445,16 @@ impl AlphaBatchBuilder {
                         };
 
                         if prim_instance.is_chased() {
                             println!("\ttask target {:?}", self.target_rect);
                             println!("\t{:?}", prim_header);
                         }
 
                         match brush.kind {
-                            BrushKind::Image { alpha_type, request, opacity_binding_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
-                                let opacity_binding = ctx.prim_store.get_opacity_binding(opacity_binding_index);
-
-                                for tile in visible_tiles {
-                                    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),
-                                            alpha_type,
-                                            get_shader_opacity(opacity_binding),
-                                    ) {
-                                        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, z_id, user_data);
-
-                                        self.add_image_tile_to_batch(
-                                            batch_kind,
-                                            specified_blend_mode,
-                                            textures,
-                                            prim_header_index,
-                                            clip_task_address,
-                                            bounding_rect,
-                                            tile.edge_flags,
-                                            uv_rect_address,
-                                            z_id,
-                                        );
-                                    }
-                                }
-                            }
                             BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                                 add_gradient_tiles(
                                     visible_tiles,
                                     stops_handle,
                                     BrushBatchKind::LinearGradient,
                                     specified_blend_mode,
                                     bounding_rect,
                                     clip_task_address,
@@ -1512,21 +1477,17 @@ impl AlphaBatchBuilder {
                                     &mut self.batch_list,
                                     &prim_header,
                                     prim_headers,
                                     z_id,
                                 );
                             }
                             _ => {
                                 if let Some(params) = brush.get_batch_params(
-                                    ctx.resource_cache,
                                     gpu_cache,
-                                    deferred_resolves,
-                                    prim_instance,
-                                    ctx.prim_store,
                                 ) {
                                     let prim_header_index = prim_headers.push(&prim_header, z_id, params.prim_user_data);
                                     if prim_instance.is_chased() {
                                         println!("\t{:?} {:?}, task relative bounds {:?}",
                                             params.batch_kind, prim_header_index, bounding_rect);
                                     }
 
                                     self.add_segmented_prim_to_batch(
@@ -1788,16 +1749,156 @@ impl AlphaBatchBuilder {
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
                     prim_instance.clip_task_index,
                     ctx,
                 );
             }
+            (
+                PrimitiveInstanceKind::Image { image_instance_index, .. },
+                PrimitiveTemplateKind::Image { source, alpha_type, key, image_rendering, .. }
+            ) => {
+                let image_instance = &ctx.prim_store.images[*image_instance_index];
+                let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index);
+                let specified_blend_mode = match alpha_type {
+                    AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
+                    AlphaType::Alpha => BlendMode::Alpha,
+                };
+                let request = ImageRequest {
+                    key: *key,
+                    rendering: *image_rendering,
+                    tile: None,
+                };
+
+                if image_instance.visible_tiles.is_empty() {
+                    let cache_item = match *source {
+                        ImageSource::Default => {
+                            resolve_image(
+                                request,
+                                ctx.resource_cache,
+                                gpu_cache,
+                                deferred_resolves,
+                            )
+                        }
+                        ImageSource::Cache { ref handle, .. } => {
+                            let rt_handle = handle
+                                .as_ref()
+                                .expect("bug: render task handle not allocated");
+                            let rt_cache_entry = ctx.resource_cache
+                                .get_cached_render_task(rt_handle);
+                            ctx.resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
+                        }
+                    };
+
+                    if cache_item.texture_id == TextureSource::Invalid {
+                        return;
+                    }
+
+                    let textures = BatchTextures::color(cache_item.texture_id);
+
+                    let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
+                    let opacity = opacity.combine(prim_data.opacity);
+
+                    let non_segmented_blend_mode = if !opacity.is_opaque ||
+                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        transform_kind == TransformedRectKind::Complex
+                    {
+                        specified_blend_mode
+                    } else {
+                        BlendMode::None
+                    };
+
+                    let batch_params = BrushBatchParameters::shared(
+                        BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
+                        textures,
+                        [
+                            ShaderColorMode::Image as i32 | ((*alpha_type as i32) << 16),
+                            RasterizationSpace::Local as i32,
+                            get_shader_opacity(opacity_binding),
+                        ],
+                        cache_item.uv_rect_handle.as_int(gpu_cache),
+                    );
+
+                    debug_assert!(image_instance.segment_instance_index != SegmentInstanceIndex::INVALID);
+                    let (prim_cache_address, segments) = if image_instance.segment_instance_index == SegmentInstanceIndex::UNUSED {
+                        (gpu_cache.get_address(&prim_data.gpu_cache_handle), None)
+                    } else {
+                        let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index];
+                        let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
+                        (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
+                    };
+
+                    let prim_header = PrimitiveHeader {
+                        local_rect: prim_data.prim_rect,
+                        local_clip_rect: prim_instance.combined_local_clip_rect,
+                        task_address,
+                        specific_prim_address: prim_cache_address,
+                        clip_task_address,
+                        transform_id,
+                    };
+
+                    let prim_header_index = prim_headers.push(
+                        &prim_header,
+                        z_id,
+                        batch_params.prim_user_data,
+                    );
+
+                    self.add_segmented_prim_to_batch(
+                        segments,
+                        opacity,
+                        &batch_params,
+                        specified_blend_mode,
+                        non_segmented_blend_mode,
+                        prim_header_index,
+                        clip_task_address,
+                        bounding_rect,
+                        transform_kind,
+                        render_tasks,
+                        z_id,
+                        prim_instance.clip_task_index,
+                        ctx,
+                    );
+                } else {
+                    for tile in &image_instance.visible_tiles {
+                        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),
+                            *alpha_type,
+                            get_shader_opacity(opacity_binding),
+                        ) {
+                            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,
+                                task_address,
+                                clip_task_address,
+                                transform_id,
+                            };
+                            let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
+
+                            self.add_image_tile_to_batch(
+                                batch_kind,
+                                specified_blend_mode,
+                                textures,
+                                prim_header_index,
+                                clip_task_address,
+                                bounding_rect,
+                                tile.edge_flags,
+                                uv_rect_address,
+                                z_id,
+                            );
+                        }
+                    }
+                }
+            }
             _ => {
                 unreachable!();
             }
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
@@ -2122,64 +2223,19 @@ impl BrushBatchParameters {
             ),
         }
     }
 }
 
 impl BrushPrimitive {
     fn get_batch_params(
         &self,
-        resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
-        deferred_resolves: &mut Vec<DeferredResolve>,
-        prim_instance: &PrimitiveInstance,
-        prim_store: &PrimitiveStore,
     ) -> Option<BrushBatchParameters> {
         match self.kind {
-            BrushKind::Image { alpha_type, request, ref source, opacity_binding_index, .. } => {
-                let cache_item = match *source {
-                    ImageSource::Default => {
-                        resolve_image(
-                            request,
-                            resource_cache,
-                            gpu_cache,
-                            deferred_resolves,
-                        )
-                    }
-                    ImageSource::Cache { ref handle, .. } => {
-                        let rt_handle = handle
-                            .as_ref()
-                            .expect("bug: render task handle not allocated");
-                        let rt_cache_entry = resource_cache
-                            .get_cached_render_task(rt_handle);
-                        resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
-                    }
-                };
-                if prim_instance.is_chased() {
-                    println!("\tsource {:?}", cache_item);
-                }
-
-                if cache_item.texture_id == TextureSource::Invalid {
-                    None
-                } else {
-                    let textures = BatchTextures::color(cache_item.texture_id);
-                    let opacity_binding = prim_store.get_opacity_binding(opacity_binding_index);
-
-                    Some(BrushBatchParameters::shared(
-                        BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
-                        textures,
-                        [
-                            ShaderColorMode::Image as i32 | ((alpha_type as i32) << 16),
-                            RasterizationSpace::Local as i32,
-                            get_shader_opacity(opacity_binding),
-                        ],
-                        cache_item.uv_rect_handle.as_int(gpu_cache),
-                    ))
-                }
-            }
             BrushKind::RadialGradient { ref stops_handle, .. } => {
                 Some(BrushBatchParameters::shared(
                     BrushBatchKind::RadialGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
@@ -2199,65 +2255,36 @@ impl BrushPrimitive {
                     0,
                 ))
             }
         }
     }
 }
 
 impl PrimitiveInstance {
-    fn get_blend_mode(
-        &self,
-        details: &PrimitiveDetails,
-    ) -> BlendMode {
-        match *details {
-            PrimitiveDetails::Brush(ref brush) => {
-                match brush.kind {
-                    BrushKind::Image { alpha_type, .. } => {
-                        match alpha_type {
-                            AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
-                            AlphaType::Alpha => BlendMode::Alpha,
-                        }
-                    }
-                    BrushKind::RadialGradient { .. } |
-                    BrushKind::LinearGradient { .. } => {
-                        BlendMode::PremultipliedAlpha
-                    }
-                }
-            }
-        }
-    }
-
     pub fn is_cacheable(
         &self,
-        primitives: &[Primitive],
         prim_data_store: &PrimitiveDataStore,
         resource_cache: &ResourceCache,
     ) -> bool {
         let image_key = match self.kind {
-            PrimitiveInstanceKind::LegacyPrimitive { prim_index, .. } => {
-                let prim = &primitives[prim_index.0];
-                match prim.details {
-                    PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Image{ request, ..  }, .. }) => {
-                        request.key
-                    }
-                    PrimitiveDetails::Brush(_) => {
-                        return true
-                    }
-                }
-            }
+            PrimitiveInstanceKind::Image { .. } |
             PrimitiveInstanceKind::YuvImage { .. } => {
                 let prim_data = &prim_data_store[self.prim_data_handle];
                 match prim_data.kind {
                     PrimitiveTemplateKind::YuvImage { ref yuv_key, .. } => {
                         yuv_key[0]
                     }
+                    PrimitiveTemplateKind::Image { key, .. } => {
+                        key
+                    }
                     _ => unreachable!(),
                 }
             }
+            PrimitiveInstanceKind::LegacyPrimitive { .. } |
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::Clear => {
                 return true;
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -19,19 +19,19 @@ use clip_scroll_tree::{ROOT_SPATIAL_NODE
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveInstance, PrimitiveDataInterner, PrimitiveKeyKind};
-use prim_store::{ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind};
+use prim_store::{PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind};
 use prim_store::{PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats, BrushSegmentDescriptor};
-use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, OpacityBindingIndex};
+use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use smallvec::SmallVec;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
@@ -2045,40 +2045,29 @@ impl<'a> DisplayListFlattener<'a> {
                 ),
                 DeviceIntSize::new(
                     (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
                     (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
                 ),
             )
         });
 
-        let prim = BrushPrimitive::new(
-            BrushKind::Image {
-                request: ImageRequest {
-                    key: image_key,
-                    rendering: image_rendering,
-                    tile: None,
-                },
-                alpha_type,
-                stretch_size,
-                tile_spacing,
-                color,
-                source: ImageSource::Default,
-                sub_rect,
-                visible_tiles: Vec::new(),
-                opacity_binding_index: OpacityBindingIndex::INVALID,
-            },
-            None,
-        );
-
         self.add_primitive(
             clip_and_scroll,
             &info,
             Vec::new(),
-            PrimitiveContainer::Brush(prim),
+            PrimitiveContainer::Image {
+                key: image_key,
+                tile_spacing,
+                stretch_size,
+                color,
+                sub_rect,
+                image_rendering,
+                alpha_type,
+            },
         );
     }
 
     pub fn add_yuv_image(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         yuv_data: YuvData,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -15,17 +15,17 @@ use euclid::approxeq::ApproxEq;
 use internal_types::{FastHashMap, PlaneSplitter};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
 use internal_types::FastHashSet;
 use plane_split::{Clipper, Polygon, Splitter};
 use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
 use prim_store::{get_raster_rects, PrimitiveDataInterner, PrimitiveDataStore, CoordinateSpaceMapping};
-use prim_store::{PrimitiveDetails, BrushKind, Primitive, OpacityBindingStorage, PrimitiveTemplateKind};
+use prim_store::{OpacityBindingStorage, PrimitiveTemplateKind, ImageInstanceStorage};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use smallvec::SmallVec;
 use surface::{SurfaceDescriptor, TransformKey};
 use std::{mem, ops};
 use texture_cache::{Eviction, TextureCacheHandle};
@@ -340,21 +340,21 @@ impl TileCache {
     /// Update the dependencies for each tile for a given primitive instance.
     pub fn update_prim_dependencies(
         &mut self,
         prim_instance: &PrimitiveInstance,
         surface_spatial_node_index: SpatialNodeIndex,
         clip_scroll_tree: &ClipScrollTree,
         prim_data_store: &PrimitiveDataStore,
         clip_chain_nodes: &[ClipChainNode],
-        primitives: &[Primitive],
         pictures: &[PicturePrimitive],
         resource_cache: &ResourceCache,
         scene_properties: &SceneProperties,
         opacity_binding_store: &OpacityBindingStorage,
+        image_instances: &ImageInstanceStorage,
     ) {
         self.space_mapper.set_target_spatial_node(
             prim_instance.spatial_node_index,
             clip_scroll_tree,
         );
 
         let prim_data = &prim_data_store[prim_instance.prim_data_handle];
 
@@ -384,76 +384,69 @@ impl TileCache {
         // Build the list of resources that this primitive has dependencies on.
         let mut opacity_bindings: SmallVec<[(PropertyBindingId, f32); 4]> = SmallVec::new();
         let mut clip_chain_spatial_nodes: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new();
         let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
         let mut current_clip_chain_id = prim_instance.clip_chain_id;
 
         // Some primitives can not be cached (e.g. external video images)
         let is_cacheable = prim_instance.is_cacheable(
-            primitives,
             prim_data_store,
             resource_cache,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index } => {
                 // Pictures can depend on animated opacity bindings.
                 let pic = &pictures[pic_index.0];
                 if let Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) = pic.requested_composite_mode {
                     if let PropertyBinding::Binding(key, default) = binding {
                         opacity_bindings.push((key.id, default));
                     }
                 }
             }
-            PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
-                let prim = &primitives[prim_index.0];
-
-                match prim.details {
-                    PrimitiveDetails::Brush(ref brush) => {
-                        match brush.kind {
-                            // Rectangles and images may depend on opacity bindings.
-                            // TODO(gw): In future, we might be able to completely remove
-                            //           opacity collapsing support. It's of limited use
-                            //           once we have full picture caching.
-                            BrushKind::Image { opacity_binding_index, ref request, .. } => {
-                                let opacity_binding = &opacity_binding_store[opacity_binding_index];
-                                for binding in &opacity_binding.bindings {
-                                    if let PropertyBinding::Binding(key, default) = binding {
-                                        opacity_bindings.push((key.id, *default));
-                                    }
-                                }
-
-                                image_keys.push(request.key);
-                            }
-                            BrushKind::RadialGradient { .. } |
-                            BrushKind::LinearGradient { .. } => {
-                            }
-                        }
-                    }
-                }
-            }
             PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => {
                 let opacity_binding = &opacity_binding_store[opacity_binding_index];
                 for binding in &opacity_binding.bindings {
                     if let PropertyBinding::Binding(key, default) = binding {
                         opacity_bindings.push((key.id, *default));
                     }
                 }
             }
+            PrimitiveInstanceKind::Image { image_instance_index, .. } => {
+                let image_instance = &image_instances[image_instance_index];
+                let opacity_binding_index = image_instance.opacity_binding_index;
+
+                let opacity_binding = &opacity_binding_store[opacity_binding_index];
+                for binding in &opacity_binding.bindings {
+                    if let PropertyBinding::Binding(key, default) = binding {
+                        opacity_bindings.push((key.id, *default));
+                    }
+                }
+
+                match prim_data.kind {
+                    PrimitiveTemplateKind::Image { key, .. } => {
+                        image_keys.push(key);
+                    }
+                    _ => {
+                        unreachable!();
+                    }
+                }
+            }
             PrimitiveInstanceKind::YuvImage { .. } => {
                 match prim_data.kind {
                     PrimitiveTemplateKind::YuvImage { ref yuv_key, .. } => {
                         image_keys.extend_from_slice(yuv_key);
                     }
                     _ => {
                         unreachable!();
                     }
                 }
             }
+            PrimitiveInstanceKind::LegacyPrimitive { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } => {
                 // These don't contribute dependencies
             }
         }
@@ -1578,38 +1571,38 @@ impl PicturePrimitive {
     /// Update the primitive dependencies for any active tile caches,
     /// but only *if* the transforms have made the mappings out of date.
     pub fn update_prim_dependencies(
         &self,
         state: &mut PictureUpdateState,
         frame_context: &FrameBuildingContext,
         resource_cache: &mut ResourceCache,
         prim_data_store: &PrimitiveDataStore,
-        primitives: &[Primitive],
         pictures: &[PicturePrimitive],
         clip_store: &ClipStore,
         opacity_binding_store: &OpacityBindingStorage,
+        image_instances: &ImageInstanceStorage,
     ) {
         if state.tile_cache_update_count == 0 {
             return;
         }
 
         for prim_instance in &self.prim_list.prim_instances {
             for tile_cache in &mut state.tile_cache_stack {
                 tile_cache.update_prim_dependencies(
                     prim_instance,
                     self.spatial_node_index,
                     &frame_context.clip_scroll_tree,
                     prim_data_store,
                     &clip_store.clip_chain_nodes,
-                    primitives,
                     pictures,
                     resource_cache,
                     frame_context.scene_properties,
                     opacity_binding_store,
+                    image_instances,
                 );
             }
         }
     }
 
     /// Called after updating child pictures during the initial
     /// picture traversal.
     pub fn post_update(
--- a/gfx/wr/webrender/src/prim_store.rs
+++ b/gfx/wr/webrender/src/prim_store.rs
@@ -389,16 +389,25 @@ pub enum PrimitiveKeyKind {
     },
     YuvImage {
         color_depth: ColorDepth,
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
+    Image {
+        key: ImageKey,
+        stretch_size: SizeKey,
+        tile_spacing: SizeKey,
+        color: ColorU,
+        sub_rect: Option<DeviceIntRect>,
+        image_rendering: ImageRendering,
+        alpha_type: AlphaType,
+    },
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
 pub struct RectangleKey {
     x: f32,
     y: f32,
@@ -432,16 +441,49 @@ impl From<LayoutRect> for RectangleKey {
             x: rect.origin.x,
             y: rect.origin.y,
             w: rect.size.width,
             h: rect.size.height,
         }
     }
 }
 
+/// A hashable size for using as a key during primitive interning.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct SizeKey {
+    w: f32,
+    h: f32,
+}
+
+impl Eq for SizeKey {}
+
+impl hash::Hash for SizeKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.w.to_bits().hash(state);
+        self.h.to_bits().hash(state);
+    }
+}
+
+impl From<SizeKey> for LayoutSize {
+    fn from(key: SizeKey) -> LayoutSize {
+        LayoutSize::new(key.w, key.h)
+    }
+}
+
+impl From<LayoutSize> for SizeKey {
+    fn from(size: LayoutSize) -> SizeKey {
+        SizeKey {
+            w: size.width,
+            h: size.height,
+        }
+    }
+}
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct PrimitiveKey {
     pub is_backface_visible: bool,
     pub prim_rect: RectangleKey,
     pub clip_rect: RectangleKey,
     pub kind: PrimitiveKeyKind,
@@ -503,16 +545,29 @@ impl PrimitiveKey {
                     segment_instance_index: SegmentInstanceIndex::INVALID,
                 }
             }
             PrimitiveKeyKind::YuvImage { .. } => {
                 PrimitiveInstanceKind::YuvImage {
                     segment_instance_index: SegmentInstanceIndex::INVALID,
                 }
             }
+            PrimitiveKeyKind::Image { .. } => {
+                // TODO(gw): Refactor this to not need a separate image
+                //           instance (see ImageInstance struct).
+                let image_instance_index = prim_store.images.push(ImageInstance {
+                    opacity_binding_index: OpacityBindingIndex::INVALID,
+                    segment_instance_index: SegmentInstanceIndex::INVALID,
+                    visible_tiles: Vec::new(),
+                });
+
+                PrimitiveInstanceKind::Image {
+                    image_instance_index,
+                }
+            }
             PrimitiveKeyKind::Unused => {
                 // Should never be hit as this method should not be
                 // called for old style primitives.
                 unreachable!();
             }
         }
     }
 }
@@ -552,16 +607,26 @@ pub enum PrimitiveTemplateKind {
     },
     YuvImage {
         color_depth: ColorDepth,
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
+    Image {
+        key: ImageKey,
+        stretch_size: LayoutSize,
+        tile_spacing: LayoutSize,
+        color: ColorF,
+        source: ImageSource,
+        image_rendering: ImageRendering,
+        sub_rect: Option<DeviceIntRect>,
+        alpha_type: AlphaType,
+    },
     Clear,
     Unused,
 }
 
 /// Construct the primitive template data from a primitive key. This
 /// is invoked when a primitive key is created and the interner
 /// doesn't currently contain a primitive with this key.
 impl PrimitiveKeyKind {
@@ -653,16 +718,28 @@ impl PrimitiveKeyKind {
                 PrimitiveTemplateKind::YuvImage {
                     color_depth,
                     yuv_key,
                     format,
                     color_space,
                     image_rendering,
                 }
             }
+            PrimitiveKeyKind::Image { alpha_type, key, color, stretch_size, tile_spacing, image_rendering, sub_rect, .. } => {
+                PrimitiveTemplateKind::Image {
+                    key,
+                    color: color.into(),
+                    stretch_size: stretch_size.into(),
+                    tile_spacing: tile_spacing.into(),
+                    source: ImageSource::Default,
+                    sub_rect,
+                    image_rendering,
+                    alpha_type,
+                }
+            }
             PrimitiveKeyKind::LineDecoration { cache_key, color } => {
                 PrimitiveTemplateKind::LineDecoration {
                     cache_key,
                     color: color.into(),
                 }
             }
         }
     }
@@ -795,16 +872,28 @@ impl PrimitiveTemplateKind {
             PrimitiveTemplateKind::YuvImage { color_depth, .. } => {
                 request.push([
                     color_depth.rescaling_factor(),
                     0.0,
                     0.0,
                     0.0
                 ]);
             }
+            PrimitiveTemplateKind::Image { stretch_size, tile_spacing, color, .. } => {
+                // Images are drawn as a white color, modulated by the total
+                // opacity coming from any collapsed property bindings.
+                request.push(color.premultiplied());
+                request.push(PremultipliedColorF::WHITE);
+                request.push([
+                    stretch_size.width + tile_spacing.width,
+                    stretch_size.height + tile_spacing.height,
+                    0.0,
+                    0.0,
+                ]);
+            }
             PrimitiveTemplateKind::Unused => {}
         }
     }
 
     fn write_segment_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
         prim_rect: LayoutRect,
@@ -835,31 +924,38 @@ impl PrimitiveTemplateKind {
                 }
             }
             PrimitiveTemplateKind::LineDecoration { .. } => {
                 request.write_segment(
                     prim_rect,
                     [0.0; 4],
                 );
             }
+            PrimitiveTemplateKind::Image { .. } |
             PrimitiveTemplateKind::Rectangle { .. } |
             PrimitiveTemplateKind::TextRun { .. } |
             PrimitiveTemplateKind::YuvImage { .. } |
             PrimitiveTemplateKind::Unused => {}
         }
     }
 }
 
 impl PrimitiveTemplate {
     /// Update the GPU cache for a given primitive template. This may be called multiple
     /// times per frame, by each primitive reference that refers to this interned
     /// template. The initial request call to the GPU cache ensures that work is only
     /// done if the cache entry is invalid (due to first use or eviction).
     pub fn update(
         &mut self,
+        // TODO(gw): Passing in surface_index here is not ideal. The primitive template
+        //           code shouldn't depend on current surface state. This is due to a
+        //           limitation in how render task caching works. We should fix this by
+        //           allowing render task caching to assign to surfaces implicitly
+        //           during pass allocation.
+        surface_index: SurfaceIndex,
         frame_state: &mut FrameBuildingState,
     ) {
         if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
             self.kind.write_prim_gpu_blocks(&mut request, self.prim_rect);
             self.kind.write_segment_gpu_blocks(&mut request, self.prim_rect);
         }
 
         self.opacity = match self.kind {
@@ -910,16 +1006,147 @@ impl PrimitiveTemplate {
                             tile: None,
                         },
                         frame_state.gpu_cache,
                     );
                 }
 
                 PrimitiveOpacity::translucent()
             }
+            PrimitiveTemplateKind::Image { key, stretch_size, ref color, tile_spacing, ref mut source, sub_rect, image_rendering, .. } => {
+                let image_properties = frame_state
+                    .resource_cache
+                    .get_image_properties(key);
+
+                match image_properties {
+                    Some(image_properties) => {
+                        let is_tiled = image_properties.tiling.is_some();
+
+                        if tile_spacing != LayoutSize::zero() && !is_tiled {
+                            *source = ImageSource::Cache {
+                                // Size in device-pixels we need to allocate in render task cache.
+                                size: image_properties.descriptor.size.to_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);
+                            *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;
+                        let mut is_opaque = image_properties.descriptor.is_opaque;
+                        let request = ImageRequest {
+                            key,
+                            rendering: image_rendering,
+                            tile: None,
+                        };
+
+                        // Every frame, for cached items, we need to request the render
+                        // task cache item. The closure will be invoked on the first
+                        // time through, and any time the render task output has been
+                        // evicted from the texture cache.
+                        match *source {
+                            ImageSource::Cache { ref mut size, ref mut handle } => {
+                                let padding = DeviceIntSideOffsets::new(
+                                    0,
+                                    (tile_spacing.width * size.width as f32 / stretch_size.width) as i32,
+                                    (tile_spacing.height * size.height as f32 / stretch_size.height) as i32,
+                                    0,
+                                );
+
+                                let inner_size = *size;
+                                size.width += padding.horizontal();
+                                size.height += padding.vertical();
+
+                                is_opaque &= padding == DeviceIntSideOffsets::zero();
+
+                                let image_cache_key = ImageCacheKey {
+                                    request,
+                                    texel_rect: sub_rect,
+                                };
+                                let surfaces = &mut frame_state.surfaces;
+
+                                // Request a pre-rendered image task.
+                                *handle = Some(frame_state.resource_cache.request_render_task(
+                                    RenderTaskCacheKey {
+                                        size: *size,
+                                        kind: RenderTaskCacheKeyKind::Image(image_cache_key),
+                                    },
+                                    frame_state.gpu_cache,
+                                    frame_state.render_tasks,
+                                    None,
+                                    image_properties.descriptor.is_opaque,
+                                    |render_tasks| {
+                                        // We need to render the image cache this frame,
+                                        // so will need access to the source texture.
+                                        request_source_image = true;
+
+                                        // Create a task to blit from the texture cache to
+                                        // a normal transient render task surface. This will
+                                        // copy only the sub-rect, if specified.
+                                        let cache_to_target_task = RenderTask::new_blit_with_padding(
+                                            inner_size,
+                                            &padding,
+                                            BlitSource::Image { key: image_cache_key },
+                                        );
+                                        let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
+
+                                        // Create a task to blit the rect from the child render
+                                        // task above back into the right spot in the persistent
+                                        // render target cache.
+                                        let target_to_cache_task = RenderTask::new_blit(
+                                            *size,
+                                            BlitSource::RenderTask {
+                                                task_id: cache_to_target_task_id,
+                                            },
+                                        );
+                                        let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
+
+                                        // Hook this into the render task tree at the right spot.
+                                        surfaces[surface_index.0].tasks.push(target_to_cache_task_id);
+
+                                        // Pass the image opacity, so that the cached render task
+                                        // item inherits the same opacity properties.
+                                        target_to_cache_task_id
+                                    }
+                                ));
+                            }
+                            ImageSource::Default => {
+                                // Normal images just reference the source texture each frame.
+                                request_source_image = true;
+                            }
+                        }
+
+                        if request_source_image && !is_tiled {
+                            frame_state.resource_cache.request_image(
+                                request,
+                                frame_state.gpu_cache,
+                            );
+                        }
+
+                        if is_opaque {
+                            PrimitiveOpacity::from_alpha(color.a)
+                        } else {
+                            PrimitiveOpacity::translucent()
+                        }
+                    }
+                    None => {
+                        PrimitiveOpacity::opaque()
+                    }
+                }
+            }
             PrimitiveTemplateKind::Unused => {
                 PrimitiveOpacity::translucent()
             }
         };
     }
 }
 
 // Type definitions for interning primitives.
@@ -1004,27 +1231,16 @@ pub struct VisibleGradientTile {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug)]
 pub struct BorderSegmentInfo {
     pub local_task_size: LayoutSize,
     pub cache_key: BorderSegmentCacheKey,
 }
 
 pub enum BrushKind {
-    Image {
-        request: ImageRequest,
-        alpha_type: AlphaType,
-        stretch_size: LayoutSize,
-        tile_spacing: LayoutSize,
-        color: ColorF,
-        source: ImageSource,
-        sub_rect: Option<DeviceIntRect>,
-        opacity_binding_index: OpacityBindingIndex,
-        visible_tiles: Vec<VisibleImageTile>,
-    },
     RadialGradient {
         stops_handle: GpuCacheHandle,
         stops_range: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
@@ -1041,53 +1257,16 @@ pub enum BrushKind {
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
         stops_opacity: PrimitiveOpacity,
     },
 }
 
-impl BrushKind {
-    fn supports_segments(&self, resource_cache: &ResourceCache) -> bool {
-        match *self {
-            BrushKind::Image { ref request, .. } => {
-                // tiled images don't support segmentation
-                resource_cache
-                    .get_image_properties(request.key)
-                    .and_then(|properties| properties.tiling)
-                    .is_none()
-            }
-
-            BrushKind::RadialGradient { .. } |
-            BrushKind::LinearGradient { .. } => true,
-        }
-    }
-
-    // Construct a brush that is an image wisth `stretch_size` dimensions and
-    // `color`.
-    pub fn new_image(
-        request: ImageRequest,
-        stretch_size: LayoutSize,
-        color: ColorF
-    ) -> BrushKind {
-        BrushKind::Image {
-            request,
-            alpha_type: AlphaType::PremultipliedAlpha,
-            stretch_size,
-            tile_spacing: LayoutSize::new(0., 0.),
-            color,
-            source: ImageSource::Default,
-            sub_rect: None,
-            opacity_binding_index: OpacityBindingIndex::INVALID,
-            visible_tiles: Vec::new(),
-        }
-    }
-}
-
 bitflags! {
     /// Each bit of the edge AA mask is:
     /// 0, when the edge of the primitive needs to be considered for AA
     /// 1, when the edge of the segment needs to be considered for AA
     ///
     /// *Note*: the bit values have to match the shader logic in
     /// `write_transform_vertex()` function.
     #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -1224,28 +1403,16 @@ impl BrushPrimitive {
     fn write_gpu_blocks_if_required(
         &mut self,
         local_rect: LayoutRect,
         gpu_cache: &mut GpuCache,
     ) {
         if let Some(mut request) = gpu_cache.request(&mut self.gpu_location) {
             // has to match VECS_PER_SPECIFIC_BRUSH
             match self.kind {
-                // Images are drawn as a white color, modulated by the total
-                // opacity coming from any collapsed property bindings.
-                BrushKind::Image { stretch_size, tile_spacing, color, .. } => {
-                    request.push(color.premultiplied());
-                    request.push(PremultipliedColorF::WHITE);
-                    request.push([
-                        stretch_size.width + tile_spacing.width,
-                        stretch_size.height + tile_spacing.height,
-                        0.0,
-                        0.0,
-                    ]);
-                }
                 BrushKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
                     request.push([
                         start_point.x,
                         start_point.y,
                         end_point.x,
                         end_point.y,
                     ]);
                     request.push([
@@ -1308,16 +1475,18 @@ pub struct ImageCacheKey {
 pub struct LineDecorationCacheKey {
     style: LineStyle,
     orientation: LineOrientation,
     wavy_line_thickness: Au,
     size: LayoutSizeAu,
 }
 
 // Where to find the texture data for an image primitive.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug)]
 pub enum ImageSource {
     // A normal image - just reference the texture cache.
     Default,
     // An image that is pre-rendered into the texture cache
     // via a render task.
     Cache {
         size: DeviceIntSize,
@@ -1850,16 +2019,25 @@ pub enum PrimitiveContainer {
     },
     YuvImage {
         color_depth: ColorDepth,
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
+    Image {
+        key: ImageKey,
+        color: ColorF,
+        tile_spacing: LayoutSize,
+        stretch_size: LayoutSize,
+        sub_rect: Option<DeviceIntRect>,
+        image_rendering: ImageRendering,
+        alpha_type: AlphaType,
+    },
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
     //           add_primitive() call. In the future
@@ -1867,26 +2045,26 @@ impl PrimitiveContainer {
     //           primitive types to use this.
     pub fn is_visible(&self) -> bool {
         match *self {
             PrimitiveContainer::TextRun { ref font, .. } => {
                 font.color.a > 0
             }
             PrimitiveContainer::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Image { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
             PrimitiveContainer::NormalBorder { .. } |
             PrimitiveContainer::ImageBorder { .. } |
             PrimitiveContainer::YuvImage { .. } |
+            PrimitiveContainer::Image { .. } |
             PrimitiveContainer::Clear => {
                 true
             }
             PrimitiveContainer::Rectangle { ref color, .. } |
             PrimitiveContainer::LineDecoration { ref color, .. } => {
                 color.a > 0.0
             }
         }
@@ -1914,16 +2092,29 @@ impl PrimitiveContainer {
             }
             PrimitiveContainer::Rectangle { color, .. } => {
                 let key = PrimitiveKeyKind::Rectangle {
                     color: color.into(),
                 };
 
                 (key, None)
             }
+            PrimitiveContainer::Image { alpha_type, key, stretch_size, color, tile_spacing, image_rendering, sub_rect, .. } => {
+                let key = PrimitiveKeyKind::Image {
+                    key,
+                    tile_spacing: tile_spacing.into(),
+                    stretch_size: stretch_size.into(),
+                    color: color.into(),
+                    sub_rect,
+                    image_rendering,
+                    alpha_type,
+                };
+
+                (key, None)
+            }
             PrimitiveContainer::YuvImage { color_depth, yuv_key, format, color_space, image_rendering, .. } => {
                 let key = PrimitiveKeyKind::YuvImage {
                     color_depth,
                     yuv_key,
                     format,
                     color_space,
                     image_rendering,
                 };
@@ -2073,32 +2264,28 @@ impl PrimitiveContainer {
             }
             PrimitiveContainer::NormalBorder { border, widths, .. } => {
                 let border = border.with_color(shadow.color);
                 PrimitiveContainer::NormalBorder {
                     border,
                     widths,
                 }
             }
-            PrimitiveContainer::Brush(ref brush) => {
-                match brush.kind {
-                    BrushKind::Image { request, stretch_size, .. } => {
-                        PrimitiveContainer::Brush(BrushPrimitive::new(
-                            BrushKind::new_image(request.clone(),
-                                                 stretch_size.clone(),
-                                                 shadow.color),
-                            None,
-                        ))
-                    }
-                    BrushKind::RadialGradient { .. } |
-                    BrushKind::LinearGradient { .. } => {
-                        panic!("bug: other brush kinds not expected here yet");
-                    }
+            PrimitiveContainer::Image { alpha_type, image_rendering, tile_spacing, stretch_size, key, sub_rect, .. } => {
+                PrimitiveContainer::Image {
+                    tile_spacing,
+                    stretch_size,
+                    key,
+                    sub_rect,
+                    image_rendering,
+                    alpha_type,
+                    color: shadow.color,
                 }
             }
+            PrimitiveContainer::Brush(..) |
             PrimitiveContainer::ImageBorder { .. } |
             PrimitiveContainer::YuvImage { .. } |
             PrimitiveContainer::Clear => {
                 panic!("bug: this prim is not supported in shadow contexts");
             }
         }
     }
 }
@@ -2108,16 +2295,35 @@ pub enum PrimitiveDetails {
 }
 
 pub struct Primitive {
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
     pub details: PrimitiveDetails,
 }
 
+/// Instance specific fields for an image primitive. These are
+/// currently stored in a separate array to avoid bloating the
+/// size of PrimitiveInstance. In the future, we should be able
+/// to remove this and store the information inline, by:
+/// (a) Removing opacity collapse / binding support completely.
+///     Once we have general picture caching, we don't need this.
+/// (b) Change visible_tiles to use Storage in the primitive
+///     scratch buffer. This will reduce the size of the
+///     visible_tiles field here, and save memory allocation
+///     when image tiling is used. I've left it as a Vec for
+///     now to reduce the number of changes, and because image
+///     tiling is very rare on real pages.
+#[derive(Debug)]
+pub struct ImageInstance {
+    pub opacity_binding_index: OpacityBindingIndex,
+    pub segment_instance_index: SegmentInstanceIndex,
+    pub visible_tiles: Vec<VisibleImageTile>,
+}
+
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveDebugId(pub usize);
 
 #[derive(Clone, Debug)]
 pub enum PrimitiveInstanceKind {
     /// Direct reference to a Picture
@@ -2153,16 +2359,19 @@ pub enum PrimitiveInstanceKind {
     },
     Rectangle {
         opacity_binding_index: OpacityBindingIndex,
         segment_instance_index: SegmentInstanceIndex,
     },
     YuvImage {
         segment_instance_index: SegmentInstanceIndex,
     },
+    Image {
+        image_instance_index: ImageInstanceIndex,
+    },
     /// Clear out a rect, used for special effects.
     Clear,
 }
 
 #[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
@@ -2252,16 +2461,18 @@ pub type TextRunIndex = storage::Index<T
 pub type TextRunStorage = storage::Storage<TextRunPrimitive>;
 pub type OpacityBindingIndex = storage::Index<OpacityBinding>;
 pub type OpacityBindingStorage = storage::Storage<OpacityBinding>;
 pub type BorderHandleStorage = storage::Storage<RenderTaskCacheEntryHandle>;
 pub type SegmentStorage = storage::Storage<BrushSegment>;
 pub type SegmentsRange = storage::Range<BrushSegment>;
 pub type SegmentInstanceStorage = storage::Storage<SegmentedInstance>;
 pub type SegmentInstanceIndex = storage::Index<SegmentedInstance>;
+pub type ImageInstanceStorage = storage::Storage<ImageInstance>;
+pub type ImageInstanceIndex = storage::Index<ImageInstance>;
 
 /// Contains various vecs of data that is used only during frame building,
 /// where we want to recycle the memory each new display list, to avoid constantly
 /// re-allocating and moving memory around. Written during primitive preparation,
 /// and read during batching.
 pub struct PrimitiveScratchBuffer {
     /// Contains a list of clip mask instance parameters
     /// per segment generated.
@@ -2317,53 +2528,62 @@ impl PrimitiveScratchBuffer {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Debug)]
 pub struct PrimitiveStoreStats {
     primitive_count: usize,
     picture_count: usize,
     text_run_count: usize,
     opacity_binding_count: usize,
+    image_count: usize,
 }
 
 impl PrimitiveStoreStats {
     pub fn empty() -> Self {
         PrimitiveStoreStats {
             primitive_count: 0,
             picture_count: 0,
             text_run_count: 0,
             opacity_binding_count: 0,
+            image_count: 0,
         }
     }
 }
 
 pub struct PrimitiveStore {
     pub primitives: Vec<Primitive>,
     pub pictures: Vec<PicturePrimitive>,
     pub text_runs: TextRunStorage,
 
+    /// A list of image instances. These are stored separately as
+    /// storing them inline in the instance makes the structure bigger
+    /// for other types.
+    pub images: ImageInstanceStorage,
+
     /// List of animated opacity bindings for a primitive.
     pub opacity_bindings: OpacityBindingStorage,
 }
 
 impl PrimitiveStore {
     pub fn new(stats: &PrimitiveStoreStats) -> PrimitiveStore {
         PrimitiveStore {
             primitives: Vec::with_capacity(stats.primitive_count),
             pictures: Vec::with_capacity(stats.picture_count),
             text_runs: TextRunStorage::new(stats.text_run_count),
+            images: ImageInstanceStorage::new(stats.image_count),
             opacity_bindings: OpacityBindingStorage::new(stats.opacity_binding_count),
         }
     }
 
     pub fn get_stats(&self) -> PrimitiveStoreStats {
         PrimitiveStoreStats {
             primitive_count: self.primitives.len(),
             picture_count: self.pictures.len(),
             text_run_count: self.text_runs.len(),
+            image_count: self.images.len(),
             opacity_binding_count: self.opacity_bindings.len(),
         }
     }
 
     pub fn create_picture(
         &mut self,
         prim: PicturePrimitive,
     ) -> PictureIndex {
@@ -2399,20 +2619,20 @@ impl PrimitiveStore {
                 );
             }
 
             self.pictures[pic_index.0].update_prim_dependencies(
                 state,
                 frame_context,
                 resource_cache,
                 prim_data_store,
-                &self.primitives,
                 &self.pictures,
                 clip_store,
                 &self.opacity_bindings,
+                &self.images,
             );
 
             self.pictures[pic_index.0].post_update(
                 children,
                 state,
                 frame_context,
                 resource_cache,
             );
@@ -2465,51 +2685,41 @@ impl PrimitiveStore {
 
         let prim_instance = &pic.prim_list.prim_instances[0];
 
         // For now, we only support opacity collapse on solid rects and images.
         // This covers the most common types of opacity filters that can be
         // handled by this optimization. In the future, we can easily extend
         // this to other primitives, such as text runs and gradients.
         match prim_instance.kind {
-            PrimitiveInstanceKind::Rectangle { .. } => {
+            // If we find a single rect or image, we can use that
+            // as the primitive to collapse the opacity into.
+            PrimitiveInstanceKind::Rectangle { .. } |
+            PrimitiveInstanceKind::Image { .. } => {
                 return Some(pic_index);
             }
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
-            PrimitiveInstanceKind::LineDecoration { .. } => {}
+            PrimitiveInstanceKind::LegacyPrimitive { .. } |
+            PrimitiveInstanceKind::LineDecoration { .. } => {
+                // These prims don't support opacity collapse
+            }
             PrimitiveInstanceKind::Picture { pic_index } => {
                 let pic = &self.pictures[pic_index.0];
 
                 // If we encounter a picture that is a pass-through
                 // (i.e. no composite mode), then we can recurse into
                 // that to try and find a primitive to collapse to.
                 if pic.requested_composite_mode.is_none() {
                     return self.get_opacity_collapse_prim(pic_index);
                 }
             }
-            PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
-                let prim = &self.primitives[prim_index.0];
-                match prim.details {
-                    PrimitiveDetails::Brush(ref brush) => {
-                        match brush.kind {
-                            // If we find a single rect or image, we can use that
-                            // as the primitive to collapse the opacity into.
-                            BrushKind::Image { .. } => {
-                                return Some(pic_index)
-                            }
-                            BrushKind::LinearGradient { .. } |
-                            BrushKind::RadialGradient { .. } => {}
-                        }
-                    }
-                }
-            }
         }
 
         None
     }
 
     // Apply any optimizations to drawing this picture. Currently,
     // we just support collapsing pictures with an opacity filter
     // by pushing that opacity value into the color of a primitive
@@ -2530,45 +2740,35 @@ impl PrimitiveStore {
 
         // See if this picture contains a single primitive that supports
         // opacity collapse.
         match self.get_opacity_collapse_prim(pic_index) {
             Some(pic_index) => {
                 let pic = &mut self.pictures[pic_index.0];
                 let prim_instance = &mut pic.prim_list.prim_instances[0];
                 match prim_instance.kind {
+                    PrimitiveInstanceKind::Image { image_instance_index, .. } => {
+                        let image_instance = &mut self.images[image_instance_index];
+                        // By this point, we know we should only have found a primitive
+                        // that supports opacity collapse.
+                        if image_instance.opacity_binding_index == OpacityBindingIndex::INVALID {
+                            image_instance.opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new());
+                        }
+                        let opacity_binding = &mut self.opacity_bindings[image_instance.opacity_binding_index];
+                        opacity_binding.push(binding);
+                    }
                     PrimitiveInstanceKind::Rectangle { ref mut opacity_binding_index, .. } => {
+                        // By this point, we know we should only have found a primitive
+                        // that supports opacity collapse.
                         if *opacity_binding_index == OpacityBindingIndex::INVALID {
                             *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new());
                         }
                         let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index];
                         opacity_binding.push(binding);
                     }
-                    PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
-                        let prim = &mut self.primitives[prim_index.0];
-                        match prim.details {
-                            PrimitiveDetails::Brush(ref mut brush) => {
-                                // By this point, we know we should only have found a primitive
-                                // that supports opacity collapse.
-                                match brush.kind {
-                                    BrushKind::Image { ref mut opacity_binding_index, .. } => {
-                                        if *opacity_binding_index == OpacityBindingIndex::INVALID {
-                                            *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new());
-                                        }
-                                        let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index];
-                                        opacity_binding.push(binding);
-                                    }
-                                    BrushKind::LinearGradient { .. } |
-                                    BrushKind::RadialGradient { .. } => {
-                                        unreachable!("bug: invalid prim type for opacity collapse");
-                                    }
-                                }
-                            }
-                        }
-                    }
                     _ => {
                         unreachable!();
                     }
                 }
             }
             None => {
                 return;
             }
@@ -2630,16 +2830,17 @@ impl PrimitiveStore {
                 }
                 PrimitiveInstanceKind::TextRun { .. } |
                 PrimitiveInstanceKind::Rectangle { .. } |
                 PrimitiveInstanceKind::LineDecoration { .. } |
                 PrimitiveInstanceKind::LegacyPrimitive { .. } |
                 PrimitiveInstanceKind::NormalBorder { .. } |
                 PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::YuvImage { .. } |
+                PrimitiveInstanceKind::Image { .. } |
                 PrimitiveInstanceKind::Clear => {
                     None
                 }
             }
         };
 
         let (is_passthrough, clip_node_collector) = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
@@ -2683,16 +2884,17 @@ impl PrimitiveStore {
                 (pic.local_rect, LayoutRect::max_rect())
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
+            PrimitiveInstanceKind::Image { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 let prim_data = &resources
                     .prim_data_store[prim_instance.prim_data_handle];
                 (prim_data.prim_rect, prim_data.clip_rect)
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim = &self.primitives[prim_index.0];
                 (prim.local_rect, prim.local_clip_rect)
@@ -2816,33 +3018,32 @@ impl PrimitiveStore {
                 clipped_world_rect,
                 pic_context.raster_spatial_node_index,
                 &clip_chain,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 &clip_node_collector,
-                &mut self.primitives,
+                self,
                 resources,
                 scratch,
             );
 
             if prim_instance.is_chased() {
                 println!("\tconsidered visible and ready with local rect {:?}", local_rect);
             }
         }
 
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         pic_state.is_cacheable &= prim_instance.is_cacheable(
-            &self.primitives,
             &resources.prim_data_store,
             frame_state.resource_cache,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index } => {
                 let pic = &mut self.pictures[pic_index.0];
                 if pic.prepare_for_render(
@@ -2884,16 +3085,17 @@ impl PrimitiveStore {
                 }
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
+            PrimitiveInstanceKind::Image { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 self.prepare_interned_prim_for_render(
                     prim_instance,
                     prim_context,
                     pic_context,
                     frame_context,
                     frame_state,
                     resources,
@@ -2903,20 +3105,18 @@ impl PrimitiveStore {
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
                 let prim_details = &mut self.primitives[prim_index.0].details;
 
                 prim_instance.prepare_prim_for_render_inner(
                     prim_local_rect,
                     prim_details,
                     prim_context,
                     pic_context,
-                    frame_context,
                     frame_state,
                     display_list,
-                    &mut self.opacity_bindings,
                 );
             }
         }
 
         true
     }
 
     pub fn prepare_primitives(
@@ -3029,16 +3229,17 @@ impl PrimitiveStore {
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let prim_data = &mut resources
             .prim_data_store[prim_instance.prim_data_handle];
 
         // Update the template this instane references, which may refresh the GPU
         // cache with any shared template data.
         prim_data.update(
+            pic_context.surface_index,
             frame_state,
         );
 
         let is_chased = prim_instance.is_chased();
 
         let segment_instance_index = match (&mut prim_instance.kind, &mut prim_data.kind) {
             (
                 PrimitiveInstanceKind::LineDecoration { ref mut cache_handle, .. },
@@ -3201,16 +3402,119 @@ impl PrimitiveStore {
                 *segment_instance_index
             }
             (
                 PrimitiveInstanceKind::YuvImage { segment_instance_index, .. },
                 PrimitiveTemplateKind::YuvImage { .. }
             ) => {
                 *segment_instance_index
             }
+            (
+                PrimitiveInstanceKind::Image { image_instance_index, .. },
+                PrimitiveTemplateKind::Image { key, stretch_size, tile_spacing, image_rendering, .. }
+            ) => {
+                let image_instance = &mut self.images[*image_instance_index];
+
+                update_opacity_binding(
+                    &mut self.opacity_bindings,
+                    image_instance.opacity_binding_index,
+                    frame_context.scene_properties,
+                );
+
+                image_instance.visible_tiles.clear();
+
+                let image_properties = frame_state
+                    .resource_cache
+                    .get_image_properties(*key);
+
+                if let Some(image_properties) = image_properties {
+                    if let Some(tile_size) = image_properties.tiling {
+                        let device_image_size = image_properties.descriptor.size;
+
+                        // 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 = prim_instance
+                            .combined_local_clip_rect
+                            .intersection(&prim_data.prim_rect).unwrap();
+
+                        let visible_rect = compute_conservative_visible_rect(
+                            prim_context,
+                            &pic_context.dirty_world_rect,
+                            &tight_clip_rect
+                        );
+
+                        let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
+
+                        let stride = *stretch_size + *tile_spacing;
+
+                        let repetitions = image::repetitions(
+                            &prim_data.prim_rect,
+                            &visible_rect,
+                            stride,
+                        );
+
+                        let request = ImageRequest {
+                            key: *key,
+                            rendering: *image_rendering,
+                            tile: None,
+                        };
+
+                        for Repetition { origin, edge_flags } in repetitions {
+                            let edge_flags = base_edge_flags | edge_flags;
+
+                            let image_rect = LayoutRect {
+                                origin,
+                                size: *stretch_size,
+                            };
+
+                            let tiles = image::tiles(
+                                &image_rect,
+                                &visible_rect,
+                                &device_image_size,
+                                tile_size as i32,
+                            );
+
+                            for tile in tiles {
+                                frame_state.resource_cache.request_image(
+                                    request.with_tile(tile.offset),
+                                    frame_state.gpu_cache,
+                                );
+
+                                let mut handle = GpuCacheHandle::new();
+                                if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
+                                    request.push(PremultipliedColorF::WHITE);
+                                    request.push(PremultipliedColorF::WHITE);
+                                    request.push([tile.rect.size.width, tile.rect.size.height, 0.0, 0.0]);
+                                    request.write_segment(tile.rect, [0.0; 4]);
+                                }
+
+                                image_instance.visible_tiles.push(VisibleImageTile {
+                                    tile_offset: tile.offset,
+                                    handle,
+                                    edge_flags: tile.edge_flags & edge_flags,
+                                    local_rect: tile.rect,
+                                    local_clip_rect: tight_clip_rect,
+                                });
+                            }
+                        }
+
+                        if image_instance.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.
+                            prim_instance.bounding_rect = None;
+                        }
+                    }
+                }
+
+                image_instance.segment_instance_index
+            }
             _ => {
                 unreachable!();
             }
         };
 
         debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
         if segment_instance_index != SegmentInstanceIndex::UNUSED {
             let segment_instance = &mut scratch.segment_instances[segment_instance_index];
@@ -3477,150 +3781,192 @@ impl<'a> GpuDataRequest<'a> {
 
 impl PrimitiveInstance {
     fn build_segments_if_needed(
         &mut self,
         prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
         prim_clip_chain: &ClipChainInstance,
         frame_state: &mut FrameBuildingState,
-        primitives: &mut [Primitive],
+        prim_store: &mut PrimitiveStore,
         resources: &FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
-        match self.kind {
+        let segment_instance_index = match self.kind {
             PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } |
             PrimitiveInstanceKind::YuvImage { ref mut segment_instance_index, .. } => {
-                if *segment_instance_index == SegmentInstanceIndex::INVALID {
-                    let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new();
-
-                    if write_brush_segment_description(
-                        prim_local_rect,
-                        prim_local_clip_rect,
-                        prim_clip_chain,
-                        &mut frame_state.segment_builder,
-                        frame_state.clip_store,
-                        resources,
-                    ) {
-                        frame_state.segment_builder.build(|segment| {
-                            segments.push(
-                                BrushSegment::new(
-                                    segment.rect,
-                                    segment.has_mask,
-                                    segment.edge_flags,
-                                    [0.0; 4],
-                                    BrushFlags::empty(),
-                                ),
-                            );
-                        });
+                segment_instance_index
+            }
+            PrimitiveInstanceKind::Image { image_instance_index, .. } => {
+                let prim_data = &resources.prim_data_store[self.prim_data_handle];
+                let image_instance = &mut prim_store.images[image_instance_index];
+                match prim_data.kind {
+                    PrimitiveTemplateKind::Image { key, .. } => {
+                        // tiled images don't support segmentation
+                        if frame_state
+                            .resource_cache
+                            .get_image_properties(key)
+                            .and_then(|properties| properties.tiling)
+                            .is_some() {
+                            image_instance.segment_instance_index = SegmentInstanceIndex::UNUSED;
+                            return;
+                        }
                     }
-
-                    if segments.is_empty() {
-                        *segment_instance_index = SegmentInstanceIndex::UNUSED;
-                    } else {
-                        let segments_range = scratch
-                            .segments
-                            .extend(segments);
-
-                        let instance = SegmentedInstance {
-                            segments_range,
-                            gpu_cache_handle: GpuCacheHandle::new(),
-                        };
-
-                        *segment_instance_index = scratch
-                            .segment_instances
-                            .push(instance);
-                    };
+                    _ => unreachable!(),
                 }
+                &mut image_instance.segment_instance_index
+            }
+            PrimitiveInstanceKind::Picture { .. } |
+            PrimitiveInstanceKind::TextRun { .. } |
+            PrimitiveInstanceKind::NormalBorder { .. } |
+            PrimitiveInstanceKind::ImageBorder { .. } |
+            PrimitiveInstanceKind::Clear |
+            PrimitiveInstanceKind::LineDecoration { .. } => {
+                // These primitives don't support / need segments.
+                return;
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
-                let prim = &mut primitives[prim_index.0];
+                let prim = &mut prim_store.primitives[prim_index.0];
                 match prim.details {
                     PrimitiveDetails::Brush(ref mut brush) => {
                         match brush.segment_desc {
                             Some(..) => {
                                 // If we already have a segment descriptor, skip segment build.
                                 return;
                             }
                             None => {
                                 // If no segment descriptor built yet, see if it is a brush
                                 // type that wants to be segmented.
-                                if brush.kind.supports_segments(frame_state.resource_cache) {
-                                    let mut segments = BrushSegmentVec::new();
-
-                                    if write_brush_segment_description(
-                                        prim_local_rect,
-                                        prim_local_clip_rect,
-                                        prim_clip_chain,
-                                        &mut frame_state.segment_builder,
-                                        frame_state.clip_store,
-                                        resources,
-                                    ) {
-                                        frame_state.segment_builder.build(|segment| {
-                                            segments.push(
-                                                BrushSegment::new(
-                                                    segment.rect,
-                                                    segment.has_mask,
-                                                    segment.edge_flags,
-                                                    [0.0; 4],
-                                                    BrushFlags::empty(),
-                                                ),
-                                            );
-                                        });
-                                    }
-
-                                    if !segments.is_empty() {
-                                        brush.segment_desc = Some(BrushSegmentDescriptor {
-                                            segments,
-                                        });
-                                    }
+                                let mut segments = BrushSegmentVec::new();
+
+                                if write_brush_segment_description(
+                                    prim_local_rect,
+                                    prim_local_clip_rect,
+                                    prim_clip_chain,
+                                    &mut frame_state.segment_builder,
+                                    frame_state.clip_store,
+                                    resources,
+                                ) {
+                                    frame_state.segment_builder.build(|segment| {
+                                        segments.push(
+                                            BrushSegment::new(
+                                                segment.rect,
+                                                segment.has_mask,
+                                                segment.edge_flags,
+                                                [0.0; 4],
+                                                BrushFlags::empty(),
+                                            ),
+                                        );
+                                    });
+                                }
+
+                                if !segments.is_empty() {
+                                    brush.segment_desc = Some(BrushSegmentDescriptor {
+                                        segments,
+                                    });
                                 }
                             }
                         }
                     }
                 }
+
+                return;
             }
-            _ => {}
+        };
+
+        if *segment_instance_index == SegmentInstanceIndex::INVALID {
+            let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new();
+
+            if write_brush_segment_description(
+                prim_local_rect,
+                prim_local_clip_rect,
+                prim_clip_chain,
+                &mut frame_state.segment_builder,
+                frame_state.clip_store,
+                resources,
+            ) {
+                frame_state.segment_builder.build(|segment| {
+                    segments.push(
+                        BrushSegment::new(
+                            segment.rect,
+                            segment.has_mask,
+                            segment.edge_flags,
+                            [0.0; 4],
+                            BrushFlags::empty(),
+                        ),
+                    );
+                });
+            }
+
+            if segments.is_empty() {
+                *segment_instance_index = SegmentInstanceIndex::UNUSED;
+            } else {
+                let segments_range = scratch
+                    .segments
+                    .extend(segments);
+
+                let instance = SegmentedInstance {
+                    segments_range,
+                    gpu_cache_handle: GpuCacheHandle::new(),
+                };
+
+                *segment_instance_index = scratch
+                    .segment_instances
+                    .push(instance);
+            };
         }
     }
 
     fn update_clip_task_for_brush(
         &mut self,
         prim_local_clip_rect: LayoutRect,
         root_spatial_node_index: SpatialNodeIndex,
         prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
-        primitives: &[Primitive],
+        prim_store: &PrimitiveStore,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) -> bool {
         let segments = match self.kind {
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 return false;
             }
+            PrimitiveInstanceKind::Image { image_instance_index, .. } => {
+                let segment_instance_index = prim_store
+                    .images[image_instance_index]
+                    .segment_instance_index;
+
+                if segment_instance_index == SegmentInstanceIndex::UNUSED {
+                    return false;
+                }
+
+                let segment_instance = &scratch.segment_instances[segment_instance_index];
+
+                &scratch.segments[segment_instance.segments_range]
+            }
             PrimitiveInstanceKind::YuvImage { segment_instance_index, .. } |
             PrimitiveInstanceKind::Rectangle { segment_instance_index, .. } => {
                 debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
 
                 if segment_instance_index == SegmentInstanceIndex::UNUSED {
                     return false;
                 }
 
                 let segment_instance = &scratch.segment_instances[segment_instance_index];
 
-                &mut scratch.segments[segment_instance.segments_range]
+                &scratch.segments[segment_instance.segments_range]
             }
             PrimitiveInstanceKind::ImageBorder { .. } => {
                 let prim_data = &resources.prim_data_store[self.prim_data_handle];
 
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
                 match prim_data.kind {
                     PrimitiveTemplateKind::ImageBorder { ref brush_segments, .. } => {
@@ -3641,17 +3987,17 @@ impl PrimitiveInstance {
                         template.brush_segments.as_slice()
                     }
                     _ => {
                         unreachable!();
                     }
                 }
             }
             PrimitiveInstanceKind::LegacyPrimitive { prim_index } => {
-                let prim = &primitives[prim_index.0];
+                let prim = &prim_store.primitives[prim_index.0];
                 match prim.details {
                     PrimitiveDetails::Brush(ref brush) => {
                         match brush.segment_desc {
                             Some(ref description) => {
                                 &description.segments
                             }
                             None => {
                                 return false;
@@ -3730,241 +4076,24 @@ impl PrimitiveInstance {
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_local_rect: LayoutRect,
         prim_details: &mut PrimitiveDetails,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
-        frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
-        opacity_bindings: &mut OpacityBindingStorage,
     ) {
         let mut is_tiled = false;
 
         match *prim_details {
             PrimitiveDetails::Brush(ref mut brush) => {
                 brush.opacity = match brush.kind {
-                    BrushKind::Image {
-                        request,
-                        sub_rect,
-                        stretch_size,
-                        color,
-                        ref mut tile_spacing,
-                        ref mut source,
-                        opacity_binding_index,
-                        ref mut visible_tiles,
-                        ..
-                    } => {
-                        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 {
-                            is_tiled = image_properties.tiling.is_some();
-                            let current_opacity = update_opacity_binding(
-                                opacity_bindings,
-                                opacity_binding_index,
-                                frame_context.scene_properties,
-                            );
-
-                            if *tile_spacing != LayoutSize::zero() && !is_tiled {
-                                *source = ImageSource::Cache {
-                                    // Size in device-pixels we need to allocate in render task cache.
-                                    size: image_properties.descriptor.size.to_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);
-                                *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;
-                            let mut is_opaque = image_properties.descriptor.is_opaque;
-
-                            // Every frame, for cached items, we need to request the render
-                            // task cache item. The closure will be invoked on the first
-                            // time through, and any time the render task output has been
-                            // evicted from the texture cache.
-                            match *source {
-                                ImageSource::Cache { ref mut size, ref mut handle } => {
-                                    let padding = DeviceIntSideOffsets::new(
-                                        0,
-                                        (tile_spacing.width * size.width as f32 / stretch_size.width) as i32,
-                                        (tile_spacing.height * size.height as f32 / stretch_size.height) as i32,
-                                        0,
-                                    );
-
-                                    let inner_size = *size;
-                                    size.width += padding.horizontal();
-                                    size.height += padding.vertical();
-
-                                    is_opaque &= padding == DeviceIntSideOffsets::zero();
-
-                                    let image_cache_key = ImageCacheKey {
-                                        request,
-                                        texel_rect: sub_rect,
-                                    };
-                                    let surfaces = &mut frame_state.surfaces;
-
-                                    // Request a pre-rendered image task.
-                                    *handle = Some(frame_state.resource_cache.request_render_task(
-                                        RenderTaskCacheKey {
-                                            size: *size,
-                                            kind: RenderTaskCacheKeyKind::Image(image_cache_key),
-                                        },
-                                        frame_state.gpu_cache,
-                                        frame_state.render_tasks,
-                                        None,
-                                        image_properties.descriptor.is_opaque,
-                                        |render_tasks| {
-                                            // We need to render the image cache this frame,
-                                            // so will need access to the source texture.
-                                            request_source_image = true;
-
-                                            // Create a task to blit from the texture cache to
-                                            // a normal transient render task surface. This will
-                                            // copy only the sub-rect, if specified.
-                                            let cache_to_target_task = RenderTask::new_blit_with_padding(
-                                                inner_size,
-                                                &padding,
-                                                BlitSource::Image { key: image_cache_key },
-                                            );
-                                            let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
-
-                                            // Create a task to blit the rect from the child render
-                                            // task above back into the right spot in the persistent
-                                            // render target cache.
-                                            let target_to_cache_task = RenderTask::new_blit(
-                                                *size,
-                                                BlitSource::RenderTask {
-                                                    task_id: cache_to_target_task_id,
-                                                },
-                                            );
-                                            let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
-
-                                            // Hook this into the render task tree at the right spot.
-                                            surfaces[pic_context.surface_index.0].tasks.push(target_to_cache_task_id);
-
-                                            // Pass the image opacity, so that the cached render task
-                                            // item inherits the same opacity properties.
-                                            target_to_cache_task_id
-                                        }
-                                    ));
-                                }
-                                ImageSource::Default => {
-                                    // Normal images just reference the source texture each frame.
-                                    request_source_image = true;
-                                }
-                            }
-
-                            if let Some(tile_size) = image_properties.tiling {
-                                let device_image_size = image_properties.descriptor.size;
-
-                                // 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 = self
-                                    .combined_local_clip_rect
-                                    .intersection(&prim_local_rect).unwrap();
-
-                                let visible_rect = compute_conservative_visible_rect(
-                                    prim_context,
-                                    &pic_context.dirty_world_rect,
-                                    &tight_clip_rect
-                                );
-
-                                let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
-
-                                let stride = stretch_size + *tile_spacing;
-
-                                visible_tiles.clear();
-
-                                let repetitions = image::repetitions(
-                                    &prim_local_rect,
-                                    &visible_rect,
-                                    stride,
-                                );
-
-                                for Repetition { origin, edge_flags } in repetitions {
-                                    let edge_flags = base_edge_flags | edge_flags;
-
-                                    let image_rect = LayoutRect {
-                                        origin,
-                                        size: stretch_size,
-                                    };
-
-                                    let tiles = image::tiles(
-                                        &image_rect,
-                                        &visible_rect,
-                                        &device_image_size,
-                                        tile_size as i32,
-                                    );
-
-                                    for tile in tiles {
-                                        frame_state.resource_cache.request_image(
-                                            request.with_tile(tile.offset),
-                                            frame_state.gpu_cache,
-                                        );
-
-                                        let mut handle = GpuCacheHandle::new();
-                                        if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
-                                            request.push(PremultipliedColorF::WHITE);
-                                            request.push(PremultipliedColorF::WHITE);
-                                            request.push([tile.rect.size.width, tile.rect.size.height, 0.0, 0.0]);
-                                            request.write_segment(tile.rect, [0.0; 4]);
-                                        }
-
-                                        visible_tiles.push(VisibleImageTile {
-                                            tile_offset: tile.offset,
-                                            handle,
-                                            edge_flags: tile.edge_flags & edge_flags,
-                                            local_rect: tile.rect,
-                                            local_clip_rect: tight_clip_rect,
-                                        });
-                                    }
-                                }
-
-                                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.
-                                    self.bounding_rect = None;
-                                }
-                            } else if request_source_image {
-                                frame_state.resource_cache.request_image(
-                                    request,
-                                    frame_state.gpu_cache,
-                                );
-                            }
-
-                            if is_opaque {
-                                PrimitiveOpacity::from_alpha(current_opacity * color.a)
-                            } else {
-                                PrimitiveOpacity::translucent()
-                            }
-                        } else {
-                            PrimitiveOpacity::opaque()
-                        }
-                    }
                     BrushKind::RadialGradient {
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
                         ratio_xy,
                         extend_mode,
                         stretch_size,
@@ -4106,50 +4235,50 @@ impl PrimitiveInstance {
         prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
-        primitives: &mut [Primitive],
+        prim_store: &mut PrimitiveStore,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
 
         // Reset clips from previous frames since we may clip differently each frame.
         self.clip_task_index = ClipTaskIndex::INVALID;
 
         self.build_segments_if_needed(
             prim_local_rect,
             prim_local_clip_rect,
             clip_chain,
             frame_state,
-            primitives,
+            prim_store,
             resources,
             scratch,
         );
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
             prim_local_clip_rect,
             root_spatial_node_index,
             prim_bounding_rect,
             prim_context,
             &clip_chain,
             pic_context,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
-            primitives,
+            prim_store,
             resources,
             scratch,
         ) {
             if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
@@ -4281,17 +4410,17 @@ fn update_opacity_binding(
 #[cfg(target_os = "linux")]
 fn test_struct_sizes() {
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<PrimitiveContainer>(), 216, "PrimitiveContainer size changed");
+    assert_eq!(mem::size_of::<PrimitiveContainer>(), 200, "PrimitiveContainer size changed");
     assert_eq!(mem::size_of::<PrimitiveInstance>(), 120, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 16, "PrimitiveInstanceKind size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplate>(), 176, "PrimitiveTemplate size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 112, "PrimitiveTemplateKind size changed");
     assert_eq!(mem::size_of::<PrimitiveKey>(), 152, "PrimitiveKey size changed");
     assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 112, "PrimitiveKeyKind size changed");
-    assert_eq!(mem::size_of::<Primitive>(), 240, "Primitive size changed");
+    assert_eq!(mem::size_of::<Primitive>(), 224, "Primitive size changed");
 }
--- a/gfx/wr/webrender/src/surface.rs
+++ b/gfx/wr/webrender/src/surface.rs
@@ -235,16 +235,17 @@ impl SurfaceDescriptor {
             // For now, we only handle interned primitives. If we encounter
             // a legacy primitive or picture, then fail to create a cache
             // descriptor.
             match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { .. } |
                 PrimitiveInstanceKind::LegacyPrimitive { .. } => {
                     return None;
                 }
+                PrimitiveInstanceKind::Image { .. } |
                 PrimitiveInstanceKind::YuvImage { .. } |
                 PrimitiveInstanceKind::LineDecoration { .. } |
                 PrimitiveInstanceKind::TextRun { .. } |
                 PrimitiveInstanceKind::NormalBorder { .. } |
                 PrimitiveInstanceKind::Rectangle { .. } |
                 PrimitiveInstanceKind::ImageBorder { .. } |
                 PrimitiveInstanceKind::Clear => {}
             }