Bug 1509495 - Update webrender to commit af2b372624db589115511b4705849a33e6acd35d (WR PR #3277). r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Fri, 23 Nov 2018 23:32:57 +0000
changeset 507110 6d9d2397f1153e5e9062f342015f28ca3576bfee
parent 507109 3b50a5b394b9467ba99fa0e956b53c49b66f3238
child 507111 b75eb61d2048a0a0dabeb08ad390a0286358b55e
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1509495
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 1509495 - Update webrender to commit af2b372624db589115511b4705849a33e6acd35d (WR PR #3277). r=kats https://github.com/servo/webrender/pull/3277 Differential Revision: https://phabricator.services.mozilla.com/D12809
gfx/webrender_bindings/revision.txt
gfx/wr/examples/blob.rs
gfx/wr/examples/image_resize.rs
gfx/wr/examples/texture_cache_stress.rs
gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
gfx/wr/webrender/src/lib.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/resource_cache.rs
gfx/wr/webrender/src/texture_cache.rs
gfx/wr/webrender_api/src/api.rs
gfx/wr/webrender_api/src/image.rs
gfx/wr/webrender_api/src/units.rs
gfx/wr/wrench/src/blob.rs
gfx/wr/wrench/src/json_frame_writer.rs
gfx/wr/wrench/src/rawtest.rs
gfx/wr/wrench/src/ron_frame_writer.rs
gfx/wr/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-523be3a9461de2716828cd2271aabaffc5e4caa0
+af2b372624db589115511b4705849a33e6acd35d
--- a/gfx/wr/examples/blob.rs
+++ b/gfx/wr/examples/blob.rs
@@ -12,29 +12,30 @@ extern crate winit;
 mod boilerplate;
 
 use boilerplate::{Example, HandyDandyRectBuilder};
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use rayon::prelude::*;
 use std::collections::HashMap;
 use std::sync::Arc;
 use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, Transaction};
-use webrender::api::{ColorF, DeviceIntRect, DeviceIntPoint};
+use webrender::api::ColorF;
+use webrender::euclid::size2;
 
 // This example shows how to implement a very basic BlobImageHandler that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
 type ImageRenderingCommands = api::ColorU;
 
 // Serialize/deserialize the blob.
 // For real usecases you should probably use serde rather than doing it by hand.
 
-fn serialize_blob(color: api::ColorU) -> Vec<u8> {
-    vec![color.r, color.g, color.b, color.a]
+fn serialize_blob(color: api::ColorU) -> Arc<Vec<u8>> {
+    Arc::new(vec![color.r, color.g, color.b, color.a])
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> {
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
         (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(api::ColorU::new(r, g, b, a)),
         (Some(&a), None, None, None) => Ok(api::ColorU::new(a, a, a, a)),
         _ => Err(()),
@@ -45,33 +46,40 @@ fn deserialize_blob(blob: &[u8]) -> Resu
 // actual image data.
 fn render_blob(
     commands: Arc<ImageRenderingCommands>,
     descriptor: &api::BlobImageDescriptor,
     tile: Option<api::TileOffset>,
 ) -> api::BlobImageResult {
     let color = *commands;
 
+    // Note: This implementation ignores the dirty rect which isn't incorrect
+    // but is a missed optimization.
+
     // Allocate storage for the result. Right now the resource cache expects the
     // tiles to have have no stride or offset.
-    let mut texels = Vec::with_capacity((descriptor.size.width * descriptor.size.height * 4) as usize);
+    let bpp = 4;
+    let mut texels = Vec::with_capacity((descriptor.rect.size.area() * bpp) as usize);
 
     // Generate a per-tile pattern to see it in the demo. For a real use case it would not
     // make sense for the rendered content to depend on its tile.
     let tile_checker = match tile {
         Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
         None => true,
     };
 
-    for y in 0 .. descriptor.size.height {
-        for x in 0 .. descriptor.size.width {
+    let [w, h] = descriptor.rect.size.to_array();
+    let offset = descriptor.rect.origin;
+
+    for y in 0..h {
+        for x in 0..w {
             // Apply the tile's offset. This is important: all drawing commands should be
             // translated by this offset to give correct results with tiled blob images.
-            let x2 = x + descriptor.offset.x as i32;
-            let y2 = y + descriptor.offset.y as i32;
+            let x2 = x + offset.x;
+            let y2 = y + offset.y;
 
             // Render a simple checkerboard pattern
             let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
                 1
             } else {
                 0
             };
             // ..nested in the per-tile checkerboard pattern
@@ -93,61 +101,58 @@ fn render_blob(
                     ));
                 }
             }
         }
     }
 
     Ok(api::RasterizedBlobImage {
         data: Arc::new(texels),
-        rasterized_rect: DeviceIntRect {
-            origin: DeviceIntPoint::origin(),
-            size: descriptor.size,
-        },
+        rasterized_rect: size2(w, h).into(),
     })
 }
 
 struct CheckerboardRenderer {
     // We are going to defer the rendering work to worker threads.
     // Using a pre-built Arc<ThreadPool> rather than creating our own threads
     // makes it possible to share the same thread pool as the glyph renderer (if we
     // want to).
     workers: Arc<ThreadPool>,
 
     // The deserialized drawing commands.
     // In this example we store them in Arcs. This isn't necessary since in this simplified
     // case the command list is a simple 32 bits value and would be cheap to clone before sending
     // to the workers. But in a more realistic scenario the commands would typically be bigger
     // and more expensive to clone, so let's pretend it is also the case here.
-    image_cmds: HashMap<api::ImageKey, Arc<ImageRenderingCommands>>,
+    image_cmds: HashMap<api::BlobImageKey, Arc<ImageRenderingCommands>>,
 }
 
 impl CheckerboardRenderer {
     fn new(workers: Arc<ThreadPool>) -> Self {
         CheckerboardRenderer {
             image_cmds: HashMap::new(),
             workers,
         }
     }
 }
 
 impl api::BlobImageHandler for CheckerboardRenderer {
-    fn add(&mut self, key: api::ImageKey, cmds: Arc<api::BlobImageData>, _: Option<api::TileSize>) {
+    fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, _: Option<api::TileSize>) {
         self.image_cmds
             .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
-    fn update(&mut self, key: api::ImageKey, cmds: Arc<api::BlobImageData>, _dirty_rect: Option<api::DeviceIntRect>) {
+    fn update(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, _dirty_rect: &api::BlobDirtyRect) {
         // Here, updating is just replacing the current version of the commands with
         // the new one (no incremental updates).
         self.image_cmds
             .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
-    fn delete(&mut self, key: api::ImageKey) {
+    fn delete(&mut self, key: api::BlobImageKey) {
         self.image_cmds.remove(&key);
     }
 
     fn prepare_resources(
         &mut self,
         _services: &api::BlobImageResources,
         _requests: &[api::BlobImageParams],
     ) {}
@@ -160,17 +165,17 @@ impl api::BlobImageHandler for Checkerbo
             workers: Arc::clone(&self.workers),
             image_cmds: self.image_cmds.clone(),
         })
     }
 }
 
 struct Rasterizer {
     workers: Arc<ThreadPool>,
-    image_cmds: HashMap<api::ImageKey, Arc<ImageRenderingCommands>>,
+    image_cmds: HashMap<api::BlobImageKey, Arc<ImageRenderingCommands>>,
 }
 
 impl api::AsyncBlobImageRasterizer for Rasterizer {
     fn rasterize(
         &mut self,
         requests: &[api::BlobImageParams],
         _low_priority: bool
     ) -> Vec<(api::BlobImageRequest, api::BlobImageResult)> {
@@ -193,29 +198,29 @@ impl Example for App {
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: api::DeviceIntSize,
         _pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
-        let blob_img1 = api.generate_image_key();
-        txn.add_image(
+        let blob_img1 = api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img1,
             api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
-            api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 50, 150, 255))),
+            serialize_blob(api::ColorU::new(50, 50, 150, 255)),
             Some(128),
         );
 
-        let blob_img2 = api.generate_image_key();
-        txn.add_image(
+        let blob_img2 = api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img2,
             api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true, false),
-            api::ImageData::new_blob_image(serialize_blob(api::ColorU::new(50, 150, 50, 255))),
+            serialize_blob(api::ColorU::new(50, 150, 50, 255)),
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
         let info = api::LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
             None,
@@ -227,28 +232,28 @@ impl Example for App {
 
         let info = api::LayoutPrimitiveInfo::new((30, 30).by(500, 500));
         builder.push_image(
             &info,
             api::LayoutSize::new(500.0, 500.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
             api::AlphaType::PremultipliedAlpha,
-            blob_img1,
+            blob_img1.as_image(),
             ColorF::WHITE,
         );
 
         let info = api::LayoutPrimitiveInfo::new((600, 600).by(200, 200));
         builder.push_image(
             &info,
             api::LayoutSize::new(200.0, 200.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
             api::AlphaType::PremultipliedAlpha,
-            blob_img2,
+            blob_img2.as_image(),
             ColorF::WHITE,
         );
 
         builder.pop_stacking_context();
     }
 }
 
 fn main() {
--- a/gfx/wr/examples/image_resize.rs
+++ b/gfx/wr/examples/image_resize.rs
@@ -100,17 +100,17 @@ impl Example for App {
                     }
                 }
 
                 let mut txn = Transaction::new();
                 txn.update_image(
                     self.image_key,
                     ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true, false),
                     ImageData::new(image_data),
-                    None,
+                    &DirtyRect::All,
                 );
                 let mut txn = Transaction::new();
                 txn.generate_frame();
                 api.send_transaction(document_id, txn);
             }
             _ => {}
         }
 
--- a/gfx/wr/examples/texture_cache_stress.rs
+++ b/gfx/wr/examples/texture_cache_stress.rs
@@ -238,17 +238,17 @@ impl Example for App {
                     winit::VirtualKeyCode::U => if let Some(image_key) = self.image_key {
                         let size = 128;
                         self.image_generator.generate_image(size);
 
                         txn.update_image(
                             image_key,
                             ImageDescriptor::new(size, size, ImageFormat::BGRA8, true, false),
                             ImageData::new(self.image_generator.take()),
-                            None,
+                            &DirtyRect::All,
                         );
                     },
                     winit::VirtualKeyCode::E => {
                         if let Some(image_key) = self.image_key.take() {
                             txn.delete_image(image_key);
                         }
 
                         let size = 32;
--- a/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -1,25 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Module only available when pathfinder is deactivated when webrender is
 //! compiled regularly (i.e. any configuration without feature = "pathfinder")
 
-use api::{ImageData, ImageDescriptor, ImageFormat};
+use api::{ImageDescriptor, ImageFormat, DirtyRect};
 use device::TextureFilter;
 use euclid::size2;
 use gpu_types::UvRectKind;
 use rayon::prelude::*;
 use std::sync::{Arc, MutexGuard};
 use platform::font::FontContext;
 use glyph_rasterizer::{FontInstance, FontContexts, GlyphKey};
 use glyph_rasterizer::{GlyphRasterizer, GlyphRasterJob, GlyphRasterJobs, GlyphRasterResult};
 use glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry};
+use resource_cache::CachedImageData;
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use gpu_cache::GpuCache;
 use render_task::{RenderTaskTree, RenderTaskCache};
 use tiling::SpecialRenderPasses;
 use profiler::TextureCacheProfileCounters;
 use std::collections::hash_map::Entry;
 
 impl FontContexts {
@@ -177,19 +178,19 @@ impl GlyphRasterizer {
                                 size: size2(glyph.width, glyph.height),
                                 stride: None,
                                 format: ImageFormat::BGRA8,
                                 is_opaque: false,
                                 allow_mipmaps: false,
                                 offset: 0,
                             },
                             TextureFilter::Linear,
-                            Some(ImageData::Raw(Arc::new(glyph.bytes))),
+                            Some(CachedImageData::Raw(Arc::new(glyph.bytes))),
                             [glyph.left, -glyph.top, glyph.scale],
-                            None,
+                            DirtyRect::All,
                             gpu_cache,
                             Some(glyph_key_cache.eviction_notice()),
                             UvRectKind::Rect,
                             Eviction::Auto,
                         );
                         GlyphCacheEntry::Cached(CachedGlyphInfo {
                             texture_cache_handle,
                             format: glyph.format,
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -200,9 +200,8 @@ pub use frame_builder::ChasePrimitive;
 pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 pub use shade::{Shaders, WrShaders};
 pub use webrender_api as api;
 pub use webrender_api::euclid;
-pub use resource_cache::intersect_for_tile;
\ No newline at end of file
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint};
 use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
-use api::{DevicePixelScale, RasterRect, RasterSpace, PictureSize, DeviceIntPoint, ColorF, ImageKey};
+use api::{DevicePixelScale, RasterRect, RasterSpace, PictureSize, DeviceIntPoint, ColorF, ImageKey, DirtyRect};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use device::TextureFilter;
 use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use internal_types::{FastHashMap, PlaneSplitter};
@@ -1838,17 +1838,17 @@ impl PicturePrimitive {
                                     // Notify the texture cache that we want to use this handle
                                     // and make sure it is allocated.
                                     frame_state.resource_cache.texture_cache.update(
                                         &mut tile.handle,
                                         descriptor,
                                         TextureFilter::Linear,
                                         None,
                                         [0.0; 3],
-                                        None,
+                                        DirtyRect::All,
                                         frame_state.gpu_cache,
                                         None,
                                         UvRectKind::Rect,
                                         Eviction::Eager,
                                     );
 
                                     let cache_item = frame_state
                                         .resource_cache
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -10,17 +10,17 @@
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DevicePixelScale, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
 use api::{MemoryReport, VoidPtrToSizeFn};
-use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, ImageKey};
+use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, BlobImageKey};
 use api::{NotificationRequest, Checkpoint};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip::ClipDataStore;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
@@ -1388,29 +1388,25 @@ impl RenderBackend {
         }
 
         report += self.resource_cache.report_memory(op);
 
         report
     }
 }
 
-fn get_blob_image_updates(updates: &[ResourceUpdate]) -> Vec<ImageKey> {
+fn get_blob_image_updates(updates: &[ResourceUpdate]) -> Vec<BlobImageKey> {
     let mut requests = Vec::new();
     for update in updates {
         match *update {
-            ResourceUpdate::AddImage(ref img) => {
-                if img.data.is_blob() {
-                    requests.push(img.key);
-                }
+            ResourceUpdate::AddBlobImage(ref img) => {
+                requests.push(img.key);
             }
-            ResourceUpdate::UpdateImage(ref img) => {
-                if img.data.is_blob() {
-                    requests.push(img.key);
-                }
+            ResourceUpdate::UpdateBlobImage(ref img) => {
+                requests.push(img.key);
             }
             _ => {}
         }
     }
 
     requests
 }
 
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets};
 use api::{DevicePixelScale, ImageDescriptor, ImageFormat};
-use api::{LineStyle, LineOrientation, LayoutSize, ColorF};
+use api::{LineStyle, LineOrientation, LayoutSize, ColorF, DirtyRect};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use border::{BorderCornerCacheKey, BorderEdgeCacheKey};
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange};
 use clip_scroll_tree::SpatialNodeIndex;
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
@@ -1250,17 +1250,17 @@ impl RenderTaskCache {
                 // more expensive than borders, for example). Telemetry could
                 // inform our decisions here as well.
                 texture_cache.update(
                     &mut entry.handle,
                     descriptor,
                     TextureFilter::Linear,
                     None,
                     entry.user_data.unwrap_or([0.0; 3]),
-                    None,
+                    DirtyRect::All,
                     gpu_cache,
                     None,
                     render_task.uv_rect_kind(),
                     Eviction::Eager,
                 );
 
                 // Get the allocation details in the texture cache, and store
                 // this in the render task. The renderer will draw this
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -1,22 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AddFont, BlobImageResources, AsyncBlobImageRasterizer, ResourceUpdate};
 use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest, RasterizedBlobImage};
-use api::{ClearCache, ColorF, DevicePoint, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+use api::{ClearCache, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DebugFlags, FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
 use api::{ExternalImageData, ExternalImageType, BlobImageResult, BlobImageParams};
 use api::{FontInstanceData, FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, IdNamespace};
-use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
-use api::{MemoryReport, VoidPtrToSizeFn};
-use api::{TileOffset, TileSize, TileRange, BlobImageData};
+use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering, ImageDirtyRect, DirtyRect};
+use api::{BlobImageKey, BlobDirtyRect, MemoryReport, VoidPtrToSizeFn};
+use api::{TileOffset, TileSize, TileRange, BlobImageData, LayoutIntRect, LayoutIntSize};
 use app_units::Au;
 #[cfg(feature = "capture")]
 use capture::ExternalCaptureImage;
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use capture::CaptureConfig;
 use device::TextureFilter;
@@ -81,16 +81,67 @@ impl CacheItem {
             texture_id: TextureSource::Invalid,
             uv_rect_handle: GpuCacheHandle::new(),
             uv_rect: DeviceIntRect::zero(),
             texture_layer: 0,
         }
     }
 }
 
+/// Represents the backing store of an image in the cache.
+/// This storage can take several forms.
+#[derive(Clone, Debug)]
+pub enum CachedImageData {
+    /// A simple series of bytes, provided by the embedding and owned by WebRender.
+    /// The format is stored out-of-band, currently in ImageDescriptor.
+    Raw(Arc<Vec<u8>>),
+    /// An series of commands that can be rasterized into an image via an
+    /// embedding-provided callback.
+    ///
+    /// The commands are stored elsewhere and this variant is used as a placeholder.
+    Blob,
+    /// An image owned by the embedding, and referenced by WebRender. This may
+    /// take the form of a texture or a heap-allocated buffer.
+    External(ExternalImageData),
+}
+
+impl From<ImageData> for CachedImageData {
+    fn from(img_data: ImageData) -> Self {
+        match img_data {
+            ImageData::Raw(data) => CachedImageData::Raw(data),
+            ImageData::External(data) => CachedImageData::External(data),
+        }
+    }
+}
+
+impl CachedImageData {
+    /// Returns true if this represents a blob.
+    #[inline]
+    pub fn is_blob(&self) -> bool {
+        match *self {
+            CachedImageData::Blob => true,
+            _ => false,
+        }
+    }
+
+    /// Returns true if this variant of CachedImageData should go through the texture
+    /// cache.
+    #[inline]
+    pub fn uses_texture_cache(&self) -> bool {
+        match *self {
+            CachedImageData::External(ref ext_data) => match ext_data.image_type {
+                ExternalImageType::TextureHandle(_) => false,
+                ExternalImageType::Buffer => true,
+            },
+            CachedImageData::Blob => true,
+            CachedImageData::Raw(_) => true,
+        }
+    }
+}
+
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
     pub external_image: Option<ExternalImageData>,
     pub tiling: Option<TileSize>,
 }
@@ -108,22 +159,22 @@ enum RasterizedBlob {
     NonTiled(Vec<RasterizedBlobImage>),
 }
 
 /// Pre scene building state.
 /// We use this to generate the async blob rendering requests.
 struct BlobImageTemplate {
     descriptor: ImageDescriptor,
     tiling: Option<TileSize>,
-    dirty_rect: Option<DeviceIntRect>,
+    dirty_rect: BlobDirtyRect,
     viewport_tiles: Option<TileRange>,
 }
 
 struct ImageResource {
-    data: ImageData,
+    data: CachedImageData,
     descriptor: ImageDescriptor,
     tiling: Option<TileSize>,
     viewport_tiles: Option<TileRange>,
 }
 
 #[derive(Clone, Debug)]
 pub struct ImageTiling {
     pub image_size: DeviceIntSize,
@@ -152,17 +203,17 @@ impl ImageTemplates {
         self.images.get_mut(&key)
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CachedImageInfo {
     texture_cache_handle: TextureCacheHandle,
-    dirty_rect: Option<DeviceIntRect>,
+    dirty_rect: ImageDirtyRect,
     manual_eviction: bool,
 }
 
 #[cfg(debug_assertions)]
 impl Drop for CachedImageInfo {
     fn drop(&mut self) {
         debug_assert!(!self.manual_eviction, "Manual eviction requires cleanup");
     }
@@ -170,56 +221,16 @@ impl Drop for CachedImageInfo {
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ResourceClassCache<K: Hash + Eq, V, U: Default> {
     resources: FastHashMap<K, V>,
     pub user_data: U,
 }
 
-pub fn intersect_for_tile(
-    dirty: DeviceIntRect,
-    clipped_tile_size: DeviceIntSize,
-    tile_size: TileSize,
-    tile_offset: TileOffset,
-
-) -> Option<DeviceIntRect> {
-    dirty.intersection(&DeviceIntRect::new(
-        DeviceIntPoint::new(
-            tile_offset.x as i32 * tile_size as i32,
-            tile_offset.y as i32 * tile_size as i32
-        ),
-        clipped_tile_size,
-    )).map(|mut r| {
-        // we can't translate by a negative size so do it manually
-        r.origin.x -= tile_offset.x as i32 * tile_size as i32;
-        r.origin.y -= tile_offset.y as i32 * tile_size as i32;
-        r
-    })
-}
-
-fn merge_dirty_rect(
-    prev_dirty_rect: &Option<DeviceIntRect>,
-    dirty_rect: &Option<DeviceIntRect>,
-    descriptor: &ImageDescriptor,
-) -> Option<DeviceIntRect> {
-    // It is important to never assume an empty dirty rect implies a full reupload here,
-    // although we are able to do so elsewhere. We store the descriptor's full rect instead
-    // There are update sequences which could cause us to forget the correct dirty regions
-    // regions if we cleared the dirty rect when we received None, e.g.:
-    //      1) Update with no dirty rect. We want to reupload everything.
-    //      2) Update with dirty rect B. We still want to reupload everything, not just B.
-    //      3) Perform the upload some time later.
-    match (dirty_rect, prev_dirty_rect) {
-        (&Some(ref rect), &Some(ref prev_rect)) => Some(rect.union(&prev_rect)),
-        (&Some(ref rect), &None) => Some(*rect),
-        (&None, _) => Some(descriptor.full_rect()),
-    }
-}
-
 impl<K, V, U> ResourceClassCache<K, V, U>
 where
     K: Clone + Hash + Eq + Debug,
     U: Default,
 {
     pub fn new() -> Self {
         ResourceClassCache {
             resources: FastHashMap::default(),
@@ -302,17 +313,17 @@ impl ImageRequest {
     pub fn is_untiled_auto(&self) -> bool {
         self.tile.is_none() && self.rendering == ImageRendering::Auto
     }
 }
 
 impl Into<BlobImageRequest> for ImageRequest {
     fn into(self) -> BlobImageRequest {
         BlobImageRequest {
-            key: self.key,
+            key: BlobImageKey(self.key),
             tile: self.tile,
         }
     }
 }
 
 impl Into<CachedImageKey> for ImageRequest {
     fn into(self) -> CachedImageKey {
         CachedImageKey {
@@ -382,21 +393,16 @@ impl BlobImageResources for Resources {
                   synthetic_italics: instance.synthetic_italics,
                 }),
                 platform_options: instance.platform_options,
                 variations: instance.variations.clone(),
             }),
             None => None,
         }
     }
-    fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)> {
-        self.image_templates
-            .get(key)
-            .map(|resource| (&resource.data, &resource.descriptor))
-    }
 }
 
 pub type GlyphDimensionsCache = FastHashMap<(FontInstance, GlyphIndex), Option<GlyphDimensions>>;
 
 /// High-level container for resources managed by the `RenderBackend`.
 ///
 /// This includes a variety of things, including images, fonts, and glyphs,
 /// which may be stored as memory buffers, GPU textures, or handles to resources
@@ -417,18 +423,18 @@ pub struct ResourceCache {
     glyph_rasterizer: GlyphRasterizer,
 
     // The set of images that aren't present or valid in the texture cache,
     // and need to be rasterized and/or uploaded this frame. This includes
     // both blobs and regular images.
     pending_image_requests: FastHashSet<ImageRequest>,
 
     blob_image_handler: Option<Box<BlobImageHandler>>,
-    rasterized_blob_images: FastHashMap<ImageKey, RasterizedBlob>,
-    blob_image_templates: FastHashMap<ImageKey, BlobImageTemplate>,
+    rasterized_blob_images: FastHashMap<BlobImageKey, RasterizedBlob>,
+    blob_image_templates: FastHashMap<BlobImageKey, BlobImageTemplate>,
 
     // If while building a frame we encounter blobs that we didn't already
     // rasterize, add them to this list and rasterize them synchronously.
     missing_blob_images: Vec<BlobImageParams>,
     // The rasterizer associated with the current scene.
     blob_image_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
 }
 
@@ -456,21 +462,21 @@ impl ResourceCache {
             blob_image_rasterizer: None,
         }
     }
 
     pub fn max_texture_size(&self) -> i32 {
         self.texture_cache.max_texture_size()
     }
 
-    fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &ImageData) -> bool {
+    fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &CachedImageData) -> bool {
         let size_check = descriptor.size.width > limit || descriptor.size.height > limit;
         match *data {
-            ImageData::Raw(_) | ImageData::Blob(_) => size_check,
-            ImageData::External(info) => {
+            CachedImageData::Raw(_) | CachedImageData::Blob => size_check,
+            CachedImageData::External(info) => {
                 // External handles already represent existing textures so it does
                 // not make sense to tile them into smaller ones.
                 info.image_type == ExternalImageType::Buffer && size_check
             }
         }
     }
 
     // Request the texture cache item for a cacheable render
@@ -508,31 +514,49 @@ impl ResourceCache {
         // in a way that reduces fragmentation in the atlas).
 
         for update in updates {
             match update {
                 ResourceUpdate::AddImage(img) => {
                     if let ImageData::Raw(ref bytes) = img.data {
                         profile_counters.image_templates.inc(bytes.len());
                     }
-                    self.add_image_template(img.key, img.descriptor, img.data, img.tiling);
+                    self.add_image_template(img.key, img.descriptor, img.data.into(), img.tiling);
                 }
                 ResourceUpdate::UpdateImage(img) => {
-                    self.update_image_template(img.key, img.descriptor, img.data, img.dirty_rect);
+                    self.update_image_template(img.key, img.descriptor, img.data.into(), &img.dirty_rect);
+                }
+                ResourceUpdate::AddBlobImage(img) => {
+                    self.add_image_template(
+                        img.key.as_image(),
+                        img.descriptor,
+                        CachedImageData::Blob,
+                        img.tiling,
+                    );
+                }
+                ResourceUpdate::UpdateBlobImage(img) => {
+                    self.update_image_template(
+                        img.key.as_image(),
+                        img.descriptor,
+                        CachedImageData::Blob,
+                        &to_image_dirty_rect(
+                            &img.dirty_rect
+                        ),
+                    );
                 }
                 ResourceUpdate::DeleteImage(img) => {
                     self.delete_image_template(img);
                 }
                 ResourceUpdate::DeleteFont(font) => {
                     self.delete_font_template(font);
                 }
                 ResourceUpdate::DeleteFontInstance(font) => {
                     self.delete_font_instance(font);
                 }
-                ResourceUpdate::SetImageVisibleArea(key, area) => {
+                ResourceUpdate::SetBlobImageVisibleArea(key, area) => {
                     self.discard_tiles_outside_visible_area(key, &area);
                 }
                 ResourceUpdate::AddFont(_) |
                 ResourceUpdate::AddFontInstance(_) => {
                     // Handled in update_resources_pre_scene_building
                 }
             }
         }
@@ -540,37 +564,33 @@ impl ResourceCache {
 
     pub fn pre_scene_building_update(
         &mut self,
         updates: &mut Vec<ResourceUpdate>,
         profile_counters: &mut ResourceProfileCounters,
     ) {
         for update in updates.iter() {
             match *update {
-                ResourceUpdate::AddImage(ref img) => {
-                    if let ImageData::Blob(ref blob_data) = img.data {
-                        self.add_blob_image(
-                            img.key,
-                            &img.descriptor,
-                            img.tiling,
-                            Arc::clone(blob_data),
-                        );
-                    }
+                ResourceUpdate::AddBlobImage(ref img) => {
+                    self.add_blob_image(
+                        img.key,
+                        &img.descriptor,
+                        img.tiling,
+                        Arc::clone(&img.data),
+                    );
                 }
-                ResourceUpdate::UpdateImage(ref img) => {
-                    if let ImageData::Blob(ref blob_data) = img.data {
-                        self.update_blob_image(
-                            img.key,
-                            &img.descriptor,
-                            &img.dirty_rect,
-                            Arc::clone(blob_data)
-                        );
-                    }
+                ResourceUpdate::UpdateBlobImage(ref img) => {
+                    self.update_blob_image(
+                        img.key,
+                        &img.descriptor,
+                        &img.dirty_rect,
+                        Arc::clone(&img.data),
+                    );
                 }
-                ResourceUpdate::SetImageVisibleArea(ref key, ref area) => {
+                ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => {
                     if let Some(template) = self.blob_image_templates.get_mut(&key) {
                         if let Some(tile_size) = template.tiling {
                             template.viewport_tiles = Some(compute_tile_range(
                                 &area,
                                 tile_size,
                             ));
                         }
                     }
@@ -725,17 +745,17 @@ impl ResourceCache {
         let instance_map = self.resources.font_instances.read().unwrap();
         instance_map.get(&instance_key).cloned()
     }
 
     pub fn add_image_template(
         &mut self,
         image_key: ImageKey,
         descriptor: ImageDescriptor,
-        data: ImageData,
+        data: CachedImageData,
         mut tiling: Option<TileSize>,
     ) {
         if tiling.is_none() && Self::should_tile(self.max_texture_size(), &descriptor, &data) {
             // We aren't going to be able to upload a texture this big, so tile it, even
             // if tiling was not requested.
             tiling = Some(DEFAULT_TILE_SIZE);
         }
 
@@ -748,158 +768,153 @@ impl ResourceCache {
 
         self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(
         &mut self,
         image_key: ImageKey,
         descriptor: ImageDescriptor,
-        data: ImageData,
-        dirty_rect: Option<DeviceIntRect>,
+        data: CachedImageData,
+        dirty_rect: &ImageDirtyRect,
     ) {
         let max_texture_size = self.max_texture_size();
         let image = match self.resources.image_templates.get_mut(image_key) {
             Some(res) => res,
             None => panic!("Attempt to update non-existent image"),
         };
 
         let mut tiling = image.tiling;
         if tiling.is_none() && Self::should_tile(max_texture_size, &descriptor, &data) {
             tiling = Some(DEFAULT_TILE_SIZE);
         }
 
         // Each cache entry stores its own copy of the image's dirty rect. This allows them to be
         // updated independently.
         match self.cached_images.try_get_mut(&image_key) {
             Some(&mut ImageResult::UntiledAuto(ref mut entry)) => {
-                entry.dirty_rect = merge_dirty_rect(&entry.dirty_rect, &dirty_rect, &descriptor);
+                entry.dirty_rect = entry.dirty_rect.union(dirty_rect);
             }
             Some(&mut ImageResult::Multi(ref mut entries)) => {
+                let tile_size = tiling.unwrap();
                 for (key, entry) in entries.iter_mut() {
-                    let merged_rect = merge_dirty_rect(&entry.dirty_rect, &dirty_rect, &descriptor);
-
-                    entry.dirty_rect = match (key.tile, merged_rect) {
-                        (Some(tile), Some(rect)) => {
-                            let tile_size = image.tiling.unwrap();
-                            let clipped_tile_size = compute_tile_size(&descriptor, tile_size, tile);
+                    // We want the dirty rect relative to the tile and not the whole image.
+                    let local_dirty_rect = match key.tile {
+                        Some(tile) => {
+                            dirty_rect.map(|mut rect|{
+                                let tile_offset = DeviceIntPoint::new(
+                                    tile.x as i32,
+                                    tile.y as i32,
+                                ) * tile_size as i32;
+                                rect.origin -= tile_offset.to_vector();
 
-                            rect.intersection(&DeviceIntRect::new(
-                                DeviceIntPoint::new(tile.x as i32, tile.y as i32) * tile_size as i32,
-                                clipped_tile_size,
-                            ))
+                                rect
+                            })
                         }
-                        _ => merged_rect,
+                        None => *dirty_rect,
                     };
+                    entry.dirty_rect = entry.dirty_rect.union(&local_dirty_rect);
                 }
             }
             _ => {}
         }
 
         *image = ImageResource {
             descriptor,
             data,
             tiling,
             viewport_tiles: image.viewport_tiles,
         };
     }
 
     // Happens before scene building.
     pub fn add_blob_image(
         &mut self,
-        key: ImageKey,
+        key: BlobImageKey,
         descriptor: &ImageDescriptor,
         mut tiling: Option<TileSize>,
         data: Arc<BlobImageData>,
     ) {
         let max_texture_size = self.max_texture_size();
         tiling = get_blob_tiling(tiling, descriptor, max_texture_size);
 
         self.blob_image_handler.as_mut().unwrap().add(key, data, tiling);
 
         self.blob_image_templates.insert(
             key,
             BlobImageTemplate {
                 descriptor: *descriptor,
                 tiling,
-                dirty_rect: Some(
-                    DeviceIntRect::new(
-                        DeviceIntPoint::zero(),
-                        descriptor.size,
-                    )
-                ),
+                dirty_rect: DirtyRect::All,
                 viewport_tiles: None,
             },
         );
     }
 
     // Happens before scene building.
     pub fn update_blob_image(
         &mut self,
-        key: ImageKey,
+        key: BlobImageKey,
         descriptor: &ImageDescriptor,
-        dirty_rect: &Option<DeviceIntRect>,
+        dirty_rect: &BlobDirtyRect,
         data: Arc<BlobImageData>,
     ) {
-        self.blob_image_handler.as_mut().unwrap().update(key, data, *dirty_rect);
+        self.blob_image_handler.as_mut().unwrap().update(key, data, dirty_rect);
 
         let max_texture_size = self.max_texture_size();
 
         let image = self.blob_image_templates
             .get_mut(&key)
             .expect("Attempt to update non-existent blob image");
 
         let tiling = get_blob_tiling(image.tiling, descriptor, max_texture_size);
 
         *image = BlobImageTemplate {
             descriptor: *descriptor,
             tiling,
-            dirty_rect: match (*dirty_rect, image.dirty_rect) {
-                (Some(rect), Some(prev_rect)) => Some(rect.union(&prev_rect)),
-                (Some(rect), None) => Some(rect),
-                (None, _) => None,
-            },
+            dirty_rect: dirty_rect.union(&image.dirty_rect),
             viewport_tiles: image.viewport_tiles,
         };
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         // Remove the template.
         let value = self.resources.image_templates.remove(image_key);
 
         // Release the corresponding texture cache entry, if any.
         if let Some(mut cached) = self.cached_images.remove(&image_key) {
             cached.drop_from_cache(&mut self.texture_cache);
         }
 
         match value {
             Some(image) => if image.data.is_blob() {
-                self.blob_image_handler.as_mut().unwrap().delete(image_key);
-                self.blob_image_templates.remove(&image_key);
-                self.rasterized_blob_images.remove(&image_key);
+                let blob_key = BlobImageKey(image_key);
+                self.blob_image_handler.as_mut().unwrap().delete(blob_key);
+                self.blob_image_templates.remove(&blob_key);
+                self.rasterized_blob_images.remove(&blob_key);
             },
             None => {
                 warn!("Delete the non-exist key");
                 debug!("key={:?}", image_key);
             }
         }
     }
 
     /// Check if an image has changed since it was last requested.
     pub fn is_image_dirty(
         &self,
         image_key: ImageKey,
     ) -> bool {
         match self.cached_images.try_get(&image_key) {
             Some(ImageResult::UntiledAuto(ref info)) => {
-                info.dirty_rect.is_some()
+                !info.dirty_rect.is_empty()
             }
             Some(ImageResult::Multi(ref entries)) => {
                 for (_, entry) in &entries.resources {
-                    if entry.dirty_rect.is_some() {
+                    if !entry.dirty_rect.is_empty() {
                         return true;
                     }
                 }
                 false
             }
             Some(ImageResult::Err(..)) => {
                 false
             }
@@ -948,17 +963,17 @@ impl ResourceCache {
                 // out first, replacing it with a dummy entry, and then creating
                 // the tiled/multi-entry variant.
                 let entry = e.into_mut();
                 if !request.is_untiled_auto() {
                     let untiled_entry = match entry {
                         &mut ImageResult::UntiledAuto(ref mut entry) => {
                             Some(mem::replace(entry, CachedImageInfo {
                                 texture_cache_handle: TextureCacheHandle::invalid(),
-                                dirty_rect: None,
+                                dirty_rect: DirtyRect::All,
                                 manual_eviction: false,
                             }))
                         }
                         _ => None
                     };
 
                     if let Some(untiled_entry) = untiled_entry {
                         let mut entries = ResourceClassCache::new();
@@ -971,43 +986,43 @@ impl ResourceCache {
                     }
                 }
                 entry
             }
             Vacant(entry) => {
                 entry.insert(if request.is_untiled_auto() {
                     ImageResult::UntiledAuto(CachedImageInfo {
                         texture_cache_handle: TextureCacheHandle::invalid(),
-                        dirty_rect: Some(template.descriptor.full_rect()),
+                        dirty_rect: DirtyRect::All,
                         manual_eviction: false,
                     })
                 } else {
                     ImageResult::Multi(ResourceClassCache::new())
                 })
             }
         };
 
         // If this image exists in the texture cache, *and* the dirty rect
         // in the cache is empty, then it is valid to use as-is.
         let entry = match *storage {
             ImageResult::UntiledAuto(ref mut entry) => entry,
             ImageResult::Multi(ref mut entries) => {
                 entries.entry(request.into())
                     .or_insert(CachedImageInfo {
                         texture_cache_handle: TextureCacheHandle::invalid(),
-                        dirty_rect: Some(template.descriptor.full_rect()),
+                        dirty_rect: DirtyRect::All,
                         manual_eviction: false,
                     })
             },
             ImageResult::Err(_) => panic!("Errors should already have been handled"),
         };
 
         let needs_upload = self.texture_cache.request(&entry.texture_cache_handle, gpu_cache);
 
-        if !needs_upload && entry.dirty_rect.is_none() {
+        if !needs_upload && entry.dirty_rect.is_empty() {
             return
         }
 
         if !self.pending_image_requests.insert(request) {
             return
         }
 
         if template.data.is_blob() {
@@ -1016,57 +1031,50 @@ impl ResourceCache {
                 (Some(RasterizedBlob::Tiled(tiles)), Some(tile)) => !tiles.contains_key(&tile),
                 (Some(RasterizedBlob::NonTiled(ref queue)), None) => queue.is_empty(),
                 _ => true,
             };
 
             // For some reason the blob image is missing. We'll fall back to
             // rasterizing it on the render backend thread.
             if missing {
-                let descriptor = match template.tiling {
-                    Some(tile_size) => {
-                        let tile = request.tile.unwrap();
-                        BlobImageDescriptor {
-                            offset: DevicePoint::new(
-                                tile.x as f32 * tile_size as f32,
-                                tile.y as f32 * tile_size as f32,
-                            ),
-                            size: compute_tile_size(
-                                &template.descriptor,
-                                tile_size,
-                                tile,
-                            ),
-                            format: template.descriptor.format,
+                let descriptor = BlobImageDescriptor {
+                    rect: match template.tiling {
+                        Some(tile_size) => {
+                            let tile = request.tile.unwrap();
+                            LayoutIntRect {
+                                origin: point2(tile.x, tile.y) * tile_size as i32,
+                                size: blob_size(compute_tile_size(
+                                    &template.descriptor,
+                                    tile_size,
+                                    tile,
+                                )),
+                            }
                         }
-                    }
-                    None => {
-                        BlobImageDescriptor {
-                            offset: DevicePoint::origin(),
-                            size: template.descriptor.size,
-                            format: template.descriptor.format,
-                        }
-                    }
+                        None => blob_size(template.descriptor.size).into(),
+                    },
+                    format: template.descriptor.format,
                 };
 
-                assert!(descriptor.size.width != 0 && descriptor.size.height != 0);
+                assert!(!descriptor.rect.is_empty());
 
                 self.missing_blob_images.push(
                     BlobImageParams {
                         request,
                         descriptor,
-                        dirty_rect: None,
+                        dirty_rect: DirtyRect::All,
                     }
                 );
             }
         }
     }
 
     pub fn create_blob_scene_builder_requests(
         &mut self,
-        keys: &[ImageKey]
+        keys: &[BlobImageKey]
     ) -> (Option<Box<AsyncBlobImageRasterizer>>, Vec<BlobImageParams>) {
         if self.blob_image_handler.is_none() {
             return (None, Vec::new());
         }
 
         let mut blob_request_params = Vec::new();
         for key in keys {
             let template = self.blob_image_templates.get_mut(key).unwrap();
@@ -1080,18 +1088,19 @@ impl ResourceCache {
                         &DeviceIntRect {
                             origin: point2(0, 0),
                             size: template.descriptor.size,
                         },
                         tile_size,
                     )
                 });
 
+                let image_dirty_rect = to_image_dirty_rect(&template.dirty_rect);
                 // Don't request tiles that weren't invalidated.
-                if let Some(dirty_rect) = template.dirty_rect {
+                if let DirtyRect::Partial(dirty_rect) = image_dirty_rect {
                     let dirty_rect = DeviceIntRect {
                         origin: point2(
                             dirty_rect.origin.x,
                             dirty_rect.origin.y,
                         ),
                         size: size2(
                             dirty_rect.size.width,
                             dirty_rect.size.height,
@@ -1117,101 +1126,99 @@ impl ResourceCache {
                     } else {
                         tiles.size.height -= 2;
                         tiles.origin.y += 1;
                     }
                 }
 
                 for_each_tile_in_range(&tiles, |tile| {
                     let descriptor = BlobImageDescriptor {
-                        offset: DevicePoint::new(
-                            tile.x as f32 * tile_size as f32,
-                            tile.y as f32 * tile_size as f32,
-                        ),
-                        size: compute_tile_size(
-                            &template.descriptor,
-                            tile_size,
-                            tile,
-                        ),
+                        rect: LayoutIntRect {
+                            origin: point2(tile.x, tile.y) * tile_size as i32,
+                            size: blob_size(compute_tile_size(
+                                &template.descriptor,
+                                tile_size,
+                                tile,
+                            )),
+                        },
                         format: template.descriptor.format,
                     };
 
                     // TODO: We only track dirty rects for non-tiled blobs but we
                     // should also do it with tiled ones unless we settle for a small
                     // tile size.
                     blob_request_params.push(
                         BlobImageParams {
                             request: BlobImageRequest {
                                 key: *key,
                                 tile: Some(tile),
                             },
                             descriptor,
-                            dirty_rect: None,
+                            dirty_rect: DirtyRect::All,
                         }
                     );
                 });
             } else {
-                let mut needs_upload = match self.cached_images.try_get(&key) {
+                let mut needs_upload = match self.cached_images.try_get(&key.as_image()) {
                     Some(&ImageResult::UntiledAuto(ref entry)) => {
                         self.texture_cache.needs_upload(&entry.texture_cache_handle)
                     }
                     _ => true,
                 };
 
                 // If the queue of ratserized updates is growing it probably means that
                 // the texture is not getting uploaded because the display item is off-screen.
                 // In that case we are better off
                 // - Either not kicking rasterization for that image (avoid wasted cpu work
                 //   but will jank next time the item is visible because of lazy rasterization.
                 // - Clobber the update queue by pushing an update with a larger dirty rect
                 //   to prevent it from accumulating.
                 //
                 // We do the latter here but it's not ideal and might want to revisit and do
                 // the former instead.
-                match self.rasterized_blob_images.get(&key) {
+                match self.rasterized_blob_images.get(key) {
                     Some(RasterizedBlob::NonTiled(ref queue)) => {
                         if queue.len() > 2 {
                             needs_upload = true;
                         }
                     }
                     _ => {},
                 };
 
                 let dirty_rect = if needs_upload {
                     // The texture cache entry has been evicted, treat it as all dirty.
-                    None
+                    DirtyRect::All
                 } else {
                     template.dirty_rect
                 };
 
                 blob_request_params.push(
                     BlobImageParams {
                         request: BlobImageRequest {
                             key: *key,
                             tile: None,
                         },
                         descriptor: BlobImageDescriptor {
-                            offset: DevicePoint::zero(),
-                            size: template.descriptor.size,
+                            rect: blob_size(template.descriptor.size).into(),
                             format: template.descriptor.format,
                         },
                         dirty_rect,
                     }
                 );
             }
-            template.dirty_rect = None;
+            template.dirty_rect = DirtyRect::empty();
         }
         let handler = self.blob_image_handler.as_mut().unwrap();
         handler.prepare_resources(&self.resources, &blob_request_params);
         (Some(handler.create_blob_rasterizer()), blob_request_params)
     }
 
     fn discard_tiles_outside_visible_area(
         &mut self,
-        key: ImageKey,
+        key: BlobImageKey,
         area: &DeviceIntRect
     ) {
         let template = match self.blob_image_templates.get(&key) {
             Some(template) => template,
             None => {
                 //println!("Missing image template (key={:?})!", key);
                 return;
             }
@@ -1229,17 +1236,17 @@ impl ResourceCache {
         let tile_range = compute_tile_range(
             &area,
             tile_size,
         );
 
         tiles.retain(|tile, _| { tile_range.contains(tile) });
 
         let texture_cache = &mut self.texture_cache;
-        match self.cached_images.try_get_mut(&key) {
+        match self.cached_images.try_get_mut(&key.as_image()) {
             Some(&mut ImageResult::Multi(ref mut entries)) => {
                 entries.retain(|key, entry| {
                     if key.tile.is_none() || tile_range.contains(&key.tile.unwrap()) {
                         return true;
                     }
                     texture_cache.mark_unused(&entry.texture_cache_handle);
                     return false;
                 });
@@ -1421,23 +1428,23 @@ impl ResourceCache {
         self.texture_cache.get(handle)
     }
 
     pub fn get_image_properties(&self, image_key: ImageKey) -> Option<ImageProperties> {
         let image_template = &self.resources.image_templates.get(image_key);
 
         image_template.map(|image_template| {
             let external_image = match image_template.data {
-                ImageData::External(ext_image) => match ext_image.image_type {
+                CachedImageData::External(ext_image) => match ext_image.image_type {
                     ExternalImageType::TextureHandle(_) => Some(ext_image),
                     // external buffer uses resource_cache.
                     ExternalImageType::Buffer => None,
                 },
                 // raw and blob image are all using resource_cache.
-                ImageData::Raw(..) | ImageData::Blob(..) => None,
+                CachedImageData::Raw(..) | CachedImageData::Blob => None,
             };
 
             ImageProperties {
                 descriptor: image_template.descriptor,
                 external_image,
                 tiling: image_template.tiling,
             }
         })
@@ -1506,39 +1513,39 @@ impl ResourceCache {
         self.missing_blob_images.clear();
     }
 
     fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
         for request in self.pending_image_requests.drain() {
             let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
             debug_assert!(image_template.data.uses_texture_cache());
 
-            let mut updates: SmallVec<[(ImageData, Option<DeviceIntRect>); 1]> = SmallVec::new();
+            let mut updates: SmallVec<[(CachedImageData, Option<DeviceIntRect>); 1]> = SmallVec::new();
 
             match image_template.data {
-                ImageData::Raw(..) | ImageData::External(..) => {
+                CachedImageData::Raw(..) | CachedImageData::External(..) => {
                     // Safe to clone here since the Raw image data is an
                     // Arc, and the external image data is small.
                     updates.push((image_template.data.clone(), None));
                 }
-                ImageData::Blob(..) => {
+                CachedImageData::Blob => {
 
-                    let blob_image = self.rasterized_blob_images.get_mut(&request.key).unwrap();
+                    let blob_image = self.rasterized_blob_images.get_mut(&BlobImageKey(request.key)).unwrap();
                     match (blob_image, request.tile) {
                         (RasterizedBlob::Tiled(ref tiles), Some(tile)) => {
                             let img = &tiles[&tile];
                             updates.push((
-                                ImageData::Raw(Arc::clone(&img.data)),
+                                CachedImageData::Raw(Arc::clone(&img.data)),
                                 Some(img.rasterized_rect)
                             ));
                         }
                         (RasterizedBlob::NonTiled(ref mut queue), None) => {
                             for img in queue.drain(..) {
                                 updates.push((
-                                    ImageData::Raw(img.data),
+                                    CachedImageData::Raw(img.data),
                                     Some(img.rasterized_rect)
                                 ));
                             }
                         }
                         _ =>  {
                             debug_assert!(false, "invalid blob image request during frame building");
                             continue;
                         }
@@ -1549,56 +1556,43 @@ impl ResourceCache {
             for (image_data, blob_rasterized_rect) in updates {
                 let entry = match *self.cached_images.get_mut(&request.key) {
                     ImageResult::UntiledAuto(ref mut entry) => entry,
                     ImageResult::Multi(ref mut entries) => entries.get_mut(&request.into()),
                     ImageResult::Err(_) => panic!("Update requested for invalid entry")
                 };
 
                 let mut descriptor = image_template.descriptor.clone();
-                let mut local_dirty_rect;
+                let mut dirty_rect = entry.dirty_rect.replace_with_empty();
 
                 if let Some(tile) = request.tile {
                     let tile_size = image_template.tiling.unwrap();
                     let clipped_tile_size = compute_tile_size(&descriptor, tile_size, tile);
 
-                    local_dirty_rect = if let Some(rect) = entry.dirty_rect.take() {
-                        // We should either have a dirty rect, or we are re-uploading where the dirty
-                        // rect is ignored anyway.
-                        let intersection = intersect_for_tile(rect, clipped_tile_size, tile_size, tile);
-                        debug_assert!(intersection.is_some() ||
-                                      self.texture_cache.needs_upload(&entry.texture_cache_handle));
-                        intersection
-                    } else {
-                        None
-                    };
-
                     // The tiled image could be stored on the CPU as one large image or be
                     // already broken up into tiles. This affects the way we compute the stride
                     // and offset.
                     let tiled_on_cpu = image_template.data.is_blob();
                     if !tiled_on_cpu {
                         let bpp = descriptor.format.bytes_per_pixel();
                         let stride = descriptor.compute_stride();
                         descriptor.stride = Some(stride);
                         descriptor.offset +=
                             tile.y as i32 * tile_size as i32 * stride +
                             tile.x as i32 * tile_size as i32 * bpp;
                     }
 
                     descriptor.size = clipped_tile_size;
-                } else {
-                    local_dirty_rect = entry.dirty_rect.take();
                 }
 
                 // If we are uploading the dirty region of a blob image we might have several
                 // rects to upload so we use each of these rasterized rects rather than the
                 // overall dirty rect of the image.
-                if blob_rasterized_rect.is_some() {
-                    local_dirty_rect = blob_rasterized_rect;
+                if let Some(rect) = blob_rasterized_rect {
+                    dirty_rect = DirtyRect::Partial(rect);
                 }
 
                 let filter = match request.rendering {
                     ImageRendering::Pixelated => {
                         TextureFilter::Nearest
                     }
                     ImageRendering::Auto | ImageRendering::CrispEdges => {
                         // If the texture uses linear filtering, enable mipmaps and
@@ -1631,17 +1625,17 @@ impl ResourceCache {
 
                 //Note: at this point, the dirty rectangle is local to the descriptor space
                 self.texture_cache.update(
                     &mut entry.texture_cache_handle,
                     descriptor,
                     filter,
                     Some(image_data),
                     [0.0; 3],
-                    local_dirty_rect,
+                    dirty_rect,
                     gpu_cache,
                     None,
                     UvRectKind::Rect,
                     eviction,
                 );
             }
         }
     }
@@ -1709,19 +1703,18 @@ impl ResourceCache {
             if let FontTemplate::Raw(ref raw, _) = font {
                 report.fonts += unsafe { op(raw.as_ptr() as *const c_void) };
             }
         }
 
         // Measure images.
         for (_, image) in self.resources.image_templates.images.iter() {
             report.images += match image.data {
-                ImageData::Raw(ref v) => unsafe { op(v.as_ptr() as *const c_void) },
-                ImageData::Blob(ref v) => unsafe { op(v.as_ptr() as *const c_void) },
-                ImageData::External(..) => 0,
+                CachedImageData::Raw(ref v) => unsafe { op(v.as_ptr() as *const c_void) },
+                CachedImageData::Blob | CachedImageData::External(..) => 0,
             }
         }
 
         // Mesure rasterized blobs.
         // TODO(gw): Temporarily disabled while we roll back a crash. We can re-enable
         //           these when that crash is fixed.
         /*
         for (_, image) in self.rasterized_blob_images.iter() {
@@ -1742,21 +1735,21 @@ impl ResourceCache {
     fn clear_images<F: Fn(&ImageKey) -> bool>(&mut self, f: F) {
         let keys = self.resources.image_templates.images.keys().filter(|k| f(*k))
             .cloned().collect::<SmallVec<[ImageKey; 16]>>();
 
         for key in keys {
             self.delete_image_template(key);
         }
 
+        let blob_f = |key: &BlobImageKey| { f(&key.as_image()) };
         debug_assert!(!self.resources.image_templates.images.keys().any(&f));
         debug_assert!(!self.cached_images.resources.keys().any(&f));
-        debug_assert!(!self.blob_image_templates.keys().any(&f));
-        debug_assert!(!self.rasterized_blob_images.keys().any(&f));
-
+        debug_assert!(!self.blob_image_templates.keys().any(&blob_f));
+        debug_assert!(!self.rasterized_blob_images.keys().any(&blob_f));
     }
 }
 
 impl Drop for ResourceCache {
     fn drop(&mut self) {
         self.clear_images(|_| true);
     }
 }
@@ -1850,16 +1843,29 @@ pub struct PlainCacheOwn {
     images: ImageCache,
     render_tasks: RenderTaskCache,
     textures: TextureCache,
 }
 
 #[cfg(feature = "replay")]
 const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf");
 
+// This currently only casts the unit but will soon apply an offset
+fn to_image_dirty_rect(blob_dirty_rect: &BlobDirtyRect) -> ImageDirtyRect {
+    match *blob_dirty_rect {
+        DirtyRect::Partial(rect) => DirtyRect::Partial(
+            DeviceIntRect {
+                origin: DeviceIntPoint::new(rect.origin.x, rect.origin.y),
+                size: DeviceIntSize::new(rect.size.width, rect.size.height),
+            }
+        ),
+        DirtyRect::All => DirtyRect::All,
+    }
+}
+
 impl ResourceCache {
     #[cfg(feature = "capture")]
     pub fn save_capture(
         &mut self, root: &PathBuf
     ) -> (PlainResources, Vec<ExternalCaptureImage>) {
         #[cfg(feature = "png")]
         use device::ReadPixelsFormat;
         use std::fs;
@@ -1908,17 +1914,17 @@ impl ResourceCache {
         info!("\timage templates");
         let mut image_paths = FastHashMap::default();
         let mut other_paths = FastHashMap::default();
         let mut num_blobs = 0;
         let mut external_images = Vec::new();
         for (&key, template) in res.image_templates.images.iter() {
             let desc = &template.descriptor;
             match template.data {
-                ImageData::Raw(ref arc) => {
+                CachedImageData::Raw(ref arc) => {
                     let image_id = image_paths.len() + 1;
                     let entry = match image_paths.entry(arc.as_ptr()) {
                         Entry::Occupied(_) => continue,
                         Entry::Vacant(e) => e,
                     };
 
                     #[cfg(feature = "png")]
                     CaptureConfig::save_png(
@@ -1930,32 +1936,31 @@ impl ResourceCache {
                     let file_name = format!("{}.raw", image_id);
                     let short_path = format!("images/{}", file_name);
                     fs::File::create(path_images.join(file_name))
                         .expect(&format!("Unable to create {}", short_path))
                         .write_all(&*arc)
                         .unwrap();
                     entry.insert(short_path);
                 }
-                ImageData::Blob(_) => {
+                CachedImageData::Blob => {
                     assert_eq!(template.tiling, None);
                     let blob_request_params = &[
                         BlobImageParams {
                             request: BlobImageRequest {
-                                key,
+                                key: BlobImageKey(key),
                                 //TODO: support tiled blob images
                                 // https://github.com/servo/webrender/issues/2236
                                 tile: None,
                             },
                             descriptor: BlobImageDescriptor {
-                                size: desc.size,
-                                offset: DevicePoint::zero(),
+                                rect: blob_size(desc.size).into(),
                                 format: desc.format,
                             },
-                            dirty_rect: None,
+                            dirty_rect: DirtyRect::All,
                         }
                     ];
 
                     let blob_handler = self.blob_image_handler.as_mut().unwrap();
                     blob_handler.prepare_resources(&self.resources, blob_request_params);
                     let mut rasterizer = blob_handler.create_blob_rasterizer();
                     let (_, result) = rasterizer.rasterize(blob_request_params, false).pop().unwrap();
                     let result = result.expect("Blob rasterization failed");
@@ -1975,17 +1980,17 @@ impl ResourceCache {
                     let short_path = format!("blobs/{}", file_name);
                     let full_path = path_blobs.clone().join(&file_name);
                     fs::File::create(full_path)
                         .expect(&format!("Unable to create {}", short_path))
                         .write_all(&result.data)
                         .unwrap();
                     other_paths.insert(key, short_path);
                 }
-                ImageData::External(ref ext) => {
+                CachedImageData::External(ref ext) => {
                     let short_path = format!("externals/{}", external_images.len() + 1);
                     other_paths.insert(key, short_path.clone());
                     external_images.push(ExternalCaptureImage {
                         short_path,
                         descriptor: desc.clone(),
                         external: ext.clone(),
                     });
                 }
@@ -2010,17 +2015,17 @@ impl ResourceCache {
                 })
                 .collect(),
             font_instances: res.font_instances.read().unwrap().clone(),
             image_templates: res.image_templates.images
                 .iter()
                 .map(|(key, template)| {
                     (*key, PlainImageTemplate {
                         data: match template.data {
-                            ImageData::Raw(ref arc) => image_paths[&arc.as_ptr()].clone(),
+                            CachedImageData::Raw(ref arc) => image_paths[&arc.as_ptr()].clone(),
                             _ => other_paths[key].clone(),
                         },
                         descriptor: template.descriptor.clone(),
                         tiling: template.tiling,
                     })
                 })
                 .collect(),
         };
@@ -2116,40 +2121,49 @@ impl ResourceCache {
 
         info!("\timage templates...");
         let mut external_images = Vec::new();
         for (key, template) in resources.image_templates {
             let data = match CaptureConfig::deserialize::<PlainExternalImage, _>(root, &template.data) {
                 Some(plain) => {
                     let ext_data = plain.external;
                     external_images.push(plain);
-                    ImageData::External(ext_data)
+                    CachedImageData::External(ext_data)
                 }
                 None => {
                     let arc = match raw_map.entry(template.data) {
                         Entry::Occupied(e) => {
                             e.get().clone()
                         }
                         Entry::Vacant(e) => {
                             let mut buffer = Vec::new();
                             File::open(root.join(e.key()))
                                 .expect(&format!("Unable to open {}", e.key()))
                                 .read_to_end(&mut buffer)
                                 .unwrap();
                             e.insert(Arc::new(buffer))
                                 .clone()
                         }
                     };
-                    ImageData::Raw(arc)
+                    CachedImageData::Raw(arc)
                 }
             };
 
             res.image_templates.images.insert(key, ImageResource {
                 data,
                 descriptor: template.descriptor,
                 tiling: template.tiling,
                 viewport_tiles: None,
             });
         }
 
         external_images
     }
 }
+
+/// For now the blob's coordinate space have the same pixel sizes as the
+/// rendered texture's device space (only a translation is applied).
+/// So going from one to the other is only a matter of casting away the unit
+/// for sizes.
+#[inline]
+fn blob_size(device_size: DeviceIntSize) -> LayoutIntSize {
+    size2(device_size.width, device_size.height)
+}
--- a/gfx/wr/webrender/src/texture_cache.rs
+++ b/gfx/wr/webrender/src/texture_cache.rs
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{DebugFlags, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{ExternalImageType, ImageData, ImageFormat};
+use api::{DebugFlags, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DirtyRect, ImageDirtyRect};
+use api::{ExternalImageType, ImageFormat};
 use api::ImageDescriptor;
 use device::{TextureFilter, total_gpu_bytes_allocated};
 use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use gpu_types::{ImageSource, UvRectKind};
 use internal_types::{CacheTextureId, LayerIndex, TextureUpdateList, TextureUpdateSource};
 use internal_types::{TextureSource, TextureCacheAllocInfo, TextureCacheUpdate};
 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use render_backend::{FrameId, FrameStamp};
-use resource_cache::CacheItem;
+use resource_cache::{CacheItem, CachedImageData};
 use std::cell::Cell;
 use std::cmp;
 use std::mem;
 use std::time::{Duration, SystemTime};
 use std::rc::Rc;
 
 /// The size of each region/layer in shared cache texture arrays.
 const TEXTURE_REGION_DIMENSIONS: i32 = 512;
@@ -668,19 +668,19 @@ impl TextureCache {
     }
 
     // Update the data stored by a given texture cache handle.
     pub fn update(
         &mut self,
         handle: &mut TextureCacheHandle,
         descriptor: ImageDescriptor,
         filter: TextureFilter,
-        data: Option<ImageData>,
+        data: Option<CachedImageData>,
         user_data: [f32; 3],
-        mut dirty_rect: Option<DeviceIntRect>,
+        mut dirty_rect: ImageDirtyRect,
         gpu_cache: &mut GpuCache,
         eviction_notice: Option<&EvictionNotice>,
         uv_rect_kind: UvRectKind,
         eviction: Eviction,
     ) {
         // Determine if we need to allocate texture cache memory
         // for this item. We need to reallocate if any of the following
         // is true:
@@ -697,17 +697,17 @@ impl TextureCache {
             }
         };
 
         if realloc {
             let params = CacheAllocParams { descriptor, filter, user_data, uv_rect_kind };
             self.allocate(&params, handle);
 
             // If we reallocated, we need to upload the whole item again.
-            dirty_rect = None;
+            dirty_rect = DirtyRect::All;
         }
 
         let entry = self.entries.get_opt_mut(handle)
             .expect("BUG: handle must be valid now");
 
         // Install the new eviction notice for this update, if applicable.
         entry.eviction_notice = eviction_notice.cloned();
         entry.uv_rect_kind = uv_rect_kind;
@@ -737,17 +737,17 @@ impl TextureCache {
 
             let op = TextureCacheUpdate::new_update(
                 data,
                 &descriptor,
                 origin,
                 entry.size,
                 entry.texture_id,
                 layer_index as i32,
-                dirty_rect,
+                &dirty_rect,
             );
             self.pending_updates.push_update(op);
         }
     }
 
     // Check if a given texture handle has a valid allocation
     // in the texture cache.
     pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool {
@@ -1380,49 +1380,49 @@ impl TextureArray {
     }
 }
 
 impl TextureCacheUpdate {
     // Constructs a TextureCacheUpdate operation to be passed to the
     // rendering thread in order to do an upload to the right
     // location in the texture cache.
     fn new_update(
-        data: ImageData,
+        data: CachedImageData,
         descriptor: &ImageDescriptor,
         origin: DeviceIntPoint,
         size: DeviceIntSize,
         texture_id: CacheTextureId,
         layer_index: i32,
-        dirty_rect: Option<DeviceIntRect>,
+        dirty_rect: &ImageDirtyRect,
     ) -> TextureCacheUpdate {
         let source = match data {
-            ImageData::Blob(..) => {
+            CachedImageData::Blob => {
                 panic!("The vector image should have been rasterized.");
             }
-            ImageData::External(ext_image) => match ext_image.image_type {
+            CachedImageData::External(ext_image) => match ext_image.image_type {
                 ExternalImageType::TextureHandle(_) => {
                     panic!("External texture handle should not go through texture_cache.");
                 }
                 ExternalImageType::Buffer => TextureUpdateSource::External {
                     id: ext_image.id,
                     channel_index: ext_image.channel_index,
                 },
             },
-            ImageData::Raw(bytes) => {
+            CachedImageData::Raw(bytes) => {
                 let finish = descriptor.offset +
                     descriptor.size.width * descriptor.format.bytes_per_pixel() +
                     (descriptor.size.height - 1) * descriptor.compute_stride();
                 assert!(bytes.len() >= finish as usize);
 
                 TextureUpdateSource::Bytes { data: bytes }
             }
         };
 
-        let update_op = match dirty_rect {
-            Some(dirty) => {
+        let update_op = match *dirty_rect {
+            DirtyRect::Partial(dirty) => {
                 // the dirty rectangle doesn't have to be within the area but has to intersect it, at least
                 let stride = descriptor.compute_stride();
                 let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x * descriptor.format.bytes_per_pixel();
 
                 TextureCacheUpdate {
                     id: texture_id,
                     rect: DeviceIntRect::new(
                         DeviceIntPoint::new(origin.x + dirty.origin.x, origin.y + dirty.origin.y),
@@ -1432,17 +1432,17 @@ impl TextureCacheUpdate {
                         ),
                     ),
                     source,
                     stride: Some(stride),
                     offset,
                     layer_index,
                 }
             }
-            None => {
+            DirtyRect::All => {
                 TextureCacheUpdate {
                     id: texture_id,
                     rect: DeviceIntRect::new(origin, size),
                     source,
                     stride: descriptor.stride,
                     offset: descriptor.offset,
                     layer_index,
                 }
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -6,33 +6,37 @@ extern crate serde_bytes;
 
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use std::os::raw::c_void;
 use std::path::PathBuf;
+use std::sync::Arc;
 use std::u32;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceIntRect};
 use {DeviceIntSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
 use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphIndex, ImageData};
-use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
+use {ImageDescriptor, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
+use {BlobDirtyRect, ImageDirtyRect, ImageKey, BlobImageKey, BlobImageData};
 use {NativeFontHandle, WorldPoint};
 
 pub type TileSize = u16;
 /// Documents are rendered in the ascending order of their associated layer values.
 pub type DocumentLayer = i8;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ResourceUpdate {
     AddImage(AddImage),
     UpdateImage(UpdateImage),
+    AddBlobImage(AddBlobImage),
+    UpdateBlobImage(UpdateBlobImage),
     DeleteImage(ImageKey),
-    SetImageVisibleArea(ImageKey, DeviceIntRect),
+    SetBlobImageVisibleArea(BlobImageKey, DeviceIntRect),
     AddFont(AddFont),
     DeleteFont(FontKey),
     AddFontInstance(AddFontInstance),
     DeleteFontInstance(FontInstanceKey),
 }
 
 /// A Transaction is a group of commands to apply atomically to a document.
 ///
@@ -316,32 +320,70 @@ impl Transaction {
         }));
     }
 
     pub fn update_image(
         &mut self,
         key: ImageKey,
         descriptor: ImageDescriptor,
         data: ImageData,
-        dirty_rect: Option<DeviceIntRect>,
+        dirty_rect: &ImageDirtyRect,
     ) {
         self.resource_updates.push(ResourceUpdate::UpdateImage(UpdateImage {
             key,
             descriptor,
             data,
-            dirty_rect,
+            dirty_rect: *dirty_rect,
         }));
     }
 
     pub fn delete_image(&mut self, key: ImageKey) {
         self.resource_updates.push(ResourceUpdate::DeleteImage(key));
     }
 
-    pub fn set_image_visible_area(&mut self, key: ImageKey, area: DeviceIntRect) {
-        self.resource_updates.push(ResourceUpdate::SetImageVisibleArea(key, area))
+    pub fn add_blob_image(
+        &mut self,
+        key: BlobImageKey,
+        descriptor: ImageDescriptor,
+        data: Arc<BlobImageData>,
+        tiling: Option<TileSize>,
+    ) {
+        self.resource_updates.push(
+            ResourceUpdate::AddBlobImage(AddBlobImage {
+                key,
+                descriptor,
+                data,
+                tiling,
+            })
+        );
+    }
+
+    pub fn update_blob_image(
+        &mut self,
+        key: BlobImageKey,
+        descriptor: ImageDescriptor,
+        data: Arc<BlobImageData>,
+        dirty_rect: &BlobDirtyRect,
+    ) {
+        self.resource_updates.push(
+            ResourceUpdate::UpdateBlobImage(UpdateBlobImage {
+                key,
+                descriptor,
+                data,
+                dirty_rect: *dirty_rect,
+            })
+        );
+    }
+
+    pub fn delete_blob_image(&mut self, key: BlobImageKey) {
+        self.resource_updates.push(ResourceUpdate::DeleteImage(key.as_image()));
+    }
+
+    pub fn set_blob_image_visible_area(&mut self, key: BlobImageKey, area: DeviceIntRect) {
+        self.resource_updates.push(ResourceUpdate::SetBlobImageVisibleArea(key, area))
     }
 
     pub fn add_raw_font(&mut self, key: FontKey, bytes: Vec<u8>, index: u32) {
         self.resource_updates
             .push(ResourceUpdate::AddFont(AddFont::Raw(key, bytes, index)));
     }
 
     pub fn add_native_font(&mut self, key: FontKey, native_handle: NativeFontHandle) {
@@ -459,17 +501,35 @@ pub struct AddImage {
     pub tiling: Option<TileSize>,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct UpdateImage {
     pub key: ImageKey,
     pub descriptor: ImageDescriptor,
     pub data: ImageData,
-    pub dirty_rect: Option<DeviceIntRect>,
+    pub dirty_rect: ImageDirtyRect,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub struct AddBlobImage {
+    pub key: BlobImageKey,
+    pub descriptor: ImageDescriptor,
+    //#[serde(with = "serde_image_data_raw")]
+    pub data: Arc<BlobImageData>,
+    pub tiling: Option<TileSize>,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+pub struct UpdateBlobImage {
+    pub key: BlobImageKey,
+    pub descriptor: ImageDescriptor,
+    //#[serde(with = "serde_image_data_raw")]
+    pub data: Arc<BlobImageData>,
+    pub dirty_rect: BlobDirtyRect,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum AddFont {
     Raw(
         FontKey,
         #[serde(with = "serde_bytes")] Vec<u8>,
         u32
@@ -1006,16 +1066,21 @@ impl RenderApi {
     }
 
     /// Creates an `ImageKey`.
     pub fn generate_image_key(&self) -> ImageKey {
         let new_id = self.next_unique_id();
         ImageKey::new(self.namespace_id, new_id)
     }
 
+    /// Creates a `BlobImageKey`.
+    pub fn generate_blob_image_key(&self) -> BlobImageKey {
+        BlobImageKey(self.generate_image_key())
+    }
+
     /// Add/remove/update resources such as images and fonts.
     pub fn update_resources(&self, resources: Vec<ResourceUpdate>) {
         if resources.is_empty() {
             return;
         }
         self.api_sender
             .send(ApiMsg::UpdateResources(resources))
             .unwrap();
--- a/gfx/wr/webrender_api/src/image.rs
+++ b/gfx/wr/webrender_api/src/image.rs
@@ -3,19 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![deny(missing_docs)]
 
 extern crate serde_bytes;
 
 use font::{FontInstanceKey, FontInstanceData, FontKey, FontTemplate};
 use std::sync::Arc;
-use {DevicePoint, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use {IdNamespace, TileOffset, TileSize};
-use euclid::size2;
+use {DeviceIntPoint, DeviceIntRect, DeviceIntSize, LayoutIntRect};
+use {BlobDirtyRect, IdNamespace, TileOffset, TileSize};
+use euclid::{size2, TypedRect, num::Zero};
+use std::ops::{Add, Sub};
 
 /// An opaque identifier describing an image registered with WebRender.
 /// This is used as a handle to reference images, and is used as the
 /// hash map key for the actual image storage in the `ResourceCache`.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub IdNamespace, pub u32);
 
@@ -24,16 +25,30 @@ impl ImageKey {
     pub const DUMMY: Self = ImageKey(IdNamespace(0), 0);
 
     /// Mints a new ImageKey. The given ID must be unique.
     pub fn new(namespace: IdNamespace, key: u32) -> Self {
         ImageKey(namespace, key)
     }
 }
 
+/// An opaque identifier describing a blob image registered with WebRender.
+/// This is used as a handle to reference blob images, and can be used as an
+/// image in display items.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct BlobImageKey(pub ImageKey);
+
+impl BlobImageKey {
+    /// Interpret this blob image as an image for a display item.
+    pub fn as_image(&self) -> ImageKey {
+        self.0
+    }
+}
+
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct ExternalImageId(pub u64);
 
 /// Specifies the type of texture target in driver terms.
@@ -223,19 +238,16 @@ impl ImageDescriptor {
 
 /// Represents the backing store of an arbitrary series of pixels for display by
 /// WebRender. This storage can take several forms.
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum ImageData {
     /// A simple series of bytes, provided by the embedding and owned by WebRender.
     /// The format is stored out-of-band, currently in ImageDescriptor.
     Raw(#[serde(with = "serde_image_data_raw")] Arc<Vec<u8>>),
-    /// An series of commands that can be rasterized into an image via an
-    /// embedding-provided callback.
-    Blob(#[serde(with = "serde_image_data_raw")] Arc<BlobImageData>),
     /// An image owned by the embedding, and referenced by WebRender. This may
     /// take the form of a texture or a heap-allocated buffer.
     External(ExternalImageData),
 }
 
 mod serde_image_data_raw {
     extern crate serde_bytes;
 
@@ -256,54 +268,24 @@ impl ImageData {
     pub fn new(bytes: Vec<u8>) -> Self {
         ImageData::Raw(Arc::new(bytes))
     }
 
     /// Mints a new raw ImageData from Arc-ed bytes.
     pub fn new_shared(bytes: Arc<Vec<u8>>) -> Self {
         ImageData::Raw(bytes)
     }
-
-    /// Mints a new Blob ImageData.
-    pub fn new_blob_image(commands: BlobImageData) -> Self {
-        ImageData::Blob(Arc::new(commands))
-    }
-
-    /// Returns true if this ImageData represents a blob.
-    #[inline]
-    pub fn is_blob(&self) -> bool {
-        match *self {
-            ImageData::Blob(_) => true,
-            _ => false,
-        }
-    }
-
-    /// Returns true if this variant of ImageData should go through the texture
-    /// cache.
-    #[inline]
-    pub fn uses_texture_cache(&self) -> bool {
-        match *self {
-            ImageData::External(ref ext_data) => match ext_data.image_type {
-                ExternalImageType::TextureHandle(_) => false,
-                ExternalImageType::Buffer => true,
-            },
-            ImageData::Blob(_) => true,
-            ImageData::Raw(_) => true,
-        }
-    }
 }
 
 /// The resources exposed by the resource cache available for use by the blob rasterizer.
 pub trait BlobImageResources {
     /// Returns the `FontTemplate` for the given key.
     fn get_font_data(&self, key: FontKey) -> &FontTemplate;
     /// Returns the `FontInstanceData` for the given key, if found.
     fn get_font_instance_data(&self, key: FontInstanceKey) -> Option<FontInstanceData>;
-    /// Returns the image metadata and backing store for the given key, if found.
-    fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)>;
 }
 
 /// A handler on the render backend that can create rasterizer objects which will
 /// be sent to the scene builder thread to execute the rasterization.
 ///
 /// The handler is responsible for collecting resources, managing/updating blob commands
 /// and creating the rasterizer objects, but isn't expected to do any rasterization itself.
 pub trait BlobImageHandler: Send {
@@ -314,23 +296,23 @@ pub trait BlobImageHandler: Send {
     /// are not bundled in the blob recording itself.
     fn prepare_resources(
         &mut self,
         services: &BlobImageResources,
         requests: &[BlobImageParams],
     );
 
     /// Register a blob image.
-    fn add(&mut self, key: ImageKey, data: Arc<BlobImageData>, tiling: Option<TileSize>);
+    fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tiling: Option<TileSize>);
 
     /// Update an already registered blob image.
-    fn update(&mut self, key: ImageKey, data: Arc<BlobImageData>, dirty_rect: Option<DeviceIntRect>);
+    fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect);
 
     /// Delete an already registered blob image.
-    fn delete(&mut self, key: ImageKey);
+    fn delete(&mut self, key: BlobImageKey);
 
     /// A hook to let the handler clean up any state related to a font which the resource
     /// cache is about to delete.
     fn delete_font(&mut self, key: FontKey);
 
     /// A hook to let the handler clean up any state related to a font instance which the
     /// resource cache is about to delete.
     fn delete_font_instance(&mut self, key: FontInstanceKey);
@@ -360,42 +342,134 @@ pub struct BlobImageParams {
     /// A key that identifies the blob image rasterization request.
     pub request: BlobImageRequest,
     /// Description of the format of the blob's output image.
     pub descriptor: BlobImageDescriptor,
     /// An optional sub-rectangle of the image to avoid re-rasterizing
     /// the entire image when only a portion is updated.
     ///
     /// If set to None the entire image is rasterized.
-    pub dirty_rect: Option<DeviceIntRect>,
+    pub dirty_rect: BlobDirtyRect,
+}
+
+/// The possible states of a Dirty rect.
+///
+/// This exists because people kept getting confused with `Option<Rect>`.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum DirtyRect<T: Copy, U> {
+    /// Everything is Dirty, equivalent to Partial(image_bounds)
+    All,
+    /// Some specific amount is dirty
+    Partial(TypedRect<T, U>)
+}
+
+impl<T, U> DirtyRect<T, U>
+where
+    T: Copy + Clone
+        + PartialOrd + PartialEq
+        + Add<T, Output = T>
+        + Sub<T, Output = T>
+        + Zero
+{
+    /// Creates an empty DirtyRect (indicating nothing is invalid)
+    pub fn empty() -> Self {
+        DirtyRect::Partial(TypedRect::zero())
+    }
+
+    /// Returns whether the dirty rect is empty
+    pub fn is_empty(&self) -> bool {
+        match self {
+            DirtyRect::All => false,
+            DirtyRect::Partial(rect) => rect.is_empty(),
+        }
+    }
+
+    /// Replaces self with the empty rect and returns the old value.
+    pub fn replace_with_empty(&mut self) -> Self {
+        ::std::mem::replace(self, DirtyRect::empty())
+    }
+
+    /// Maps over the contents of Partial.
+    pub fn map<F>(self, func: F) -> Self
+        where F: FnOnce(TypedRect<T, U>) -> TypedRect<T, U>,
+    {
+        use DirtyRect::*;
+
+        match self {
+            All        => All,
+            Partial(rect) => Partial(func(rect)),
+        }
+    }
+
+    /// Unions the dirty rects.
+    pub fn union(&self, other: &Self) -> Self {
+        use DirtyRect::*;
+
+        match (*self, *other) {
+            (All, _) | (_, All)        => All,
+            (Partial(rect1), Partial(rect2)) => Partial(rect1.union(&rect2)),
+        }
+    }
+
+    /// Intersects the dirty rects.
+    pub fn intersection(&self, other: &Self) -> Self {
+        use DirtyRect::*;
+
+        match (*self, *other) {
+            (All, rect) | (rect, All)  => rect,
+            (Partial(rect1), Partial(rect2)) => Partial(rect1.intersection(&rect2)
+                                                                   .unwrap_or(TypedRect::zero()))
+        }
+    }
+
+    /// Converts the dirty rect into a subrect of the given one via intersection.
+    pub fn to_subrect_of(&self, rect: &TypedRect<T, U>) -> TypedRect<T, U> {
+        use DirtyRect::*;
+
+        match *self {
+            All              => *rect,
+            Partial(dirty_rect) => dirty_rect.intersection(rect)
+                                               .unwrap_or(TypedRect::zero()),
+        }
+    }
+}
+
+impl<T: Copy, U> Copy for DirtyRect<T, U> {}
+impl<T: Copy, U> Clone for DirtyRect<T, U> {
+    fn clone(&self) -> Self { *self }
+}
+
+impl<T: Copy, U> From<TypedRect<T, U>> for DirtyRect<T, U> {
+    fn from(rect: TypedRect<T, U>) -> Self {
+        DirtyRect::Partial(rect)
+    }
 }
 
 /// Backing store for blob image command streams.
 pub type BlobImageData = Vec<u8>;
 
 /// Result type for blob raserization.
 pub type BlobImageResult = Result<RasterizedBlobImage, BlobImageError>;
 
 /// Metadata (but not storage) for a blob image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug)]
 pub struct BlobImageDescriptor {
-    /// Size in device pixels of the blob's output image.
-    pub size: DeviceIntSize,
-    /// When tiling, offset point in device pixels of this tile in the full
-    /// image. Generally (0, 0) outside of tiling.
-    pub offset: DevicePoint,
+    /// Surface of the image or tile to render in the same coordinate space as
+    /// the drawing commands.
+    pub rect: LayoutIntRect,
     /// Format for the data in the backing store.
     pub format: ImageFormat,
 }
 
 /// Representation of a rasterized blob image. This is obtained by passing
 /// `BlobImageData` to the embedding via the rasterization callback.
 pub struct RasterizedBlobImage {
-    /// The bounding rectangle for this blob image.
+    /// The rectangle that was rasterized in device pixels, relative to the
+    /// image or tile.
     pub rasterized_rect: DeviceIntRect,
     /// Backing store. The format is stored out of band in `BlobImageDescriptor`.
     pub data: Arc<Vec<u8>>,
 }
 
 /// Error code for when blob rasterization failed.
 #[derive(Clone, Debug)]
 pub enum BlobImageError {
@@ -407,14 +481,14 @@ pub enum BlobImageError {
 
 
 
 /// A key identifying blob image rasterization work requested from the blob
 /// image rasterizer.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct BlobImageRequest {
     /// Unique handle to the image.
-    pub key: ImageKey,
+    pub key: BlobImageKey,
     /// Tiling offset in number of tiles, if applicable.
     ///
     /// `None` if the image will not be tiled.
     pub tile: Option<TileOffset>,
 }
--- a/gfx/wr/webrender_api/src/units.rs
+++ b/gfx/wr/webrender_api/src/units.rs
@@ -8,22 +8,23 @@
 //! to correspond to the allocated size of resources in memory, while logical pixels
 //! don't have the device pixel ratio applied which means they are agnostic to the usage
 //! of hidpi screens and the like.
 //!
 //! The terms "layer" and "stacking context" can be used interchangeably
 //! in the context of coordinate systems.
 
 use app_units::Au;
-use euclid::{Length, TypedRect, TypedScale, TypedSize2D, TypedTransform3D};
+use euclid::{Length, TypedRect, TypedScale, TypedSize2D, TypedTransform3D, TypedTranslation2D};
 use euclid::{TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D, TypedSideOffsets2D};
+use DirtyRect;
 
 /// Geometry in the coordinate system of the render target (screen or intermediate
 /// surface) in physical pixels.
-#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
 pub struct DevicePixel;
 
 pub type DeviceIntRect = TypedRect<i32, DevicePixel>;
 pub type DeviceIntPoint = TypedPoint2D<i32, DevicePixel>;
 pub type DeviceIntSize = TypedSize2D<i32, DevicePixel>;
 pub type DeviceIntLength = Length<i32, DevicePixel>;
 pub type DeviceIntSideOffsets = TypedSideOffsets2D<i32, DevicePixel>;
 
@@ -70,16 +71,20 @@ pub struct LayoutPixel;
 pub type LayoutRect = TypedRect<f32, LayoutPixel>;
 pub type LayoutPoint = TypedPoint2D<f32, LayoutPixel>;
 pub type LayoutPoint3D = TypedPoint3D<f32, LayoutPixel>;
 pub type LayoutVector2D = TypedVector2D<f32, LayoutPixel>;
 pub type LayoutVector3D = TypedVector3D<f32, LayoutPixel>;
 pub type LayoutSize = TypedSize2D<f32, LayoutPixel>;
 pub type LayoutSideOffsets = TypedSideOffsets2D<f32, LayoutPixel>;
 
+pub type LayoutIntRect = TypedRect<i32, LayoutPixel>;
+pub type LayoutIntPoint = TypedPoint2D<i32, LayoutPixel>;
+pub type LayoutIntSize = TypedSize2D<i32, LayoutPixel>;
+
 /// Geometry in the document's coordinate space (logical pixels).
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct WorldPixel;
 
 pub type WorldRect = TypedRect<f32, WorldPixel>;
 pub type WorldPoint = TypedPoint2D<f32, WorldPixel>;
 pub type WorldSize = TypedSize2D<f32, WorldPixel>;
 pub type WorldPoint3D = TypedPoint3D<f32, WorldPixel>;
@@ -114,16 +119,21 @@ pub type PictureToRasterTransform = Type
 pub type RasterToPictureTransform = TypedTransform3D<f32, RasterPixel, PicturePixel>;
 
 // Fixed position coordinates, to avoid float precision errors.
 pub type LayoutPointAu = TypedPoint2D<Au, LayoutPixel>;
 pub type LayoutRectAu = TypedRect<Au, LayoutPixel>;
 pub type LayoutSizeAu = TypedSize2D<Au, LayoutPixel>;
 pub type LayoutVector2DAu = TypedVector2D<Au, LayoutPixel>;
 
+pub type ImageDirtyRect = DirtyRect<i32, DevicePixel>;
+pub type BlobDirtyRect = DirtyRect<i32, LayoutPixel>;
+
+pub type BlobToDeviceTranslation = TypedTranslation2D<i32, LayoutPixel, DevicePixel>;
+
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
 /// may grow. Storing them as texel coords and normalizing
 /// the UVs in the vertex shader means nothing needs to be
 /// updated on the CPU when the texture size changes.
 #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
 pub struct TexelRect {
     pub uv0: DevicePoint,
--- a/gfx/wr/wrench/src/blob.rs
+++ b/gfx/wr/wrench/src/blob.rs
@@ -3,23 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // A very basic BlobImageRasterizer that can only render a checkerboard pattern.
 
 use std::collections::HashMap;
 use std::sync::Arc;
 use std::sync::Mutex;
 use webrender::api::*;
-use webrender::intersect_for_tile;
-use euclid::size2;
 
 // Serialize/deserialize the blob.
 
-pub fn serialize_blob(color: ColorU) -> Vec<u8> {
-    vec![color.r, color.g, color.b, color.a]
+pub fn serialize_blob(color: ColorU) -> Arc<Vec<u8>> {
+    Arc::new(vec![color.r, color.g, color.b, color.a])
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ColorU, ()> {
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
         (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
         (Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
         _ => Err(()),
@@ -33,124 +31,120 @@ fn premul(x: u8, a: u8) -> u8 {
 }
 
 // This is the function that applies the deserialized drawing commands and generates
 // actual image data.
 fn render_blob(
     color: ColorU,
     descriptor: &BlobImageDescriptor,
     tile: Option<(TileSize, TileOffset)>,
-    dirty_rect: Option<DeviceIntRect>,
+    dirty_rect: &BlobDirtyRect,
 ) -> BlobImageResult {
     // Allocate storage for the result. Right now the resource cache expects the
     // tiles to have have no stride or offset.
-    let buf_size = descriptor.size.width *
-        descriptor.size.height *
+    let buf_size = descriptor.rect.size.area() *
         descriptor.format.bytes_per_pixel();
     let mut texels = vec![0u8; (buf_size) as usize];
 
     // Generate a per-tile pattern to see it in the demo. For a real use case it would not
     // make sense for the rendered content to depend on its tile.
     let tile_checker = match tile {
         Some((_, tile)) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
         None => true,
     };
 
-    let mut dirty_rect = dirty_rect.unwrap_or(DeviceIntRect::new(
-        descriptor.offset.to_i32(),
-        descriptor.size,
-    ));
+    let dirty_rect = dirty_rect.to_subrect_of(&descriptor.rect);
+
+    // We want the dirty rect local to the tile rather than the whole image.
+    let tx: BlobToDeviceTranslation = (-descriptor.rect.origin.to_vector()).into();
 
-    if let Some((tile_size, tile)) = tile {
-        dirty_rect = intersect_for_tile(dirty_rect, size2(tile_size as i32, tile_size as i32),
-                                        tile_size, tile)
-            .expect("empty rects should be culled by webrender");
-    }
+    let rasterized_rect = tx.transform_rect(&dirty_rect);
 
-    for y in dirty_rect.min_y() .. dirty_rect.max_y() {
-        for x in dirty_rect.min_x() .. dirty_rect.max_x() {
+    for y in rasterized_rect.min_y() .. rasterized_rect.max_y() {
+        for x in rasterized_rect.min_x() .. rasterized_rect.max_x() {
             // Apply the tile's offset. This is important: all drawing commands should be
             // translated by this offset to give correct results with tiled blob images.
-            let x2 = x + descriptor.offset.x as i32;
-            let y2 = y + descriptor.offset.y as i32;
+            let x2 = x + descriptor.rect.origin.x;
+            let y2 = y + descriptor.rect.origin.y;
 
             // Render a simple checkerboard pattern
             let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
                 1
             } else {
                 0
             };
             // ..nested in the per-tile checkerboard pattern
             let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
 
             match descriptor.format {
                 ImageFormat::BGRA8 => {
                     let a = color.a * checker + tc;
-                    texels[((y * descriptor.size.width + x) * 4 + 0) as usize] = premul(color.b * checker + tc, a);
-                    texels[((y * descriptor.size.width + x) * 4 + 1) as usize] = premul(color.g * checker + tc, a);
-                    texels[((y * descriptor.size.width + x) * 4 + 2) as usize] = premul(color.r * checker + tc, a);
-                    texels[((y * descriptor.size.width + x) * 4 + 3) as usize] = a;
+                    let pixel_offset = ((y * descriptor.rect.size.width + x) * 4) as usize;
+                    texels[pixel_offset + 0] = premul(color.b * checker + tc, a);
+                    texels[pixel_offset + 1] = premul(color.g * checker + tc, a);
+                    texels[pixel_offset + 2] = premul(color.r * checker + tc, a);
+                    texels[pixel_offset + 3] = a;
                 }
                 ImageFormat::R8 => {
-                    texels[(y * descriptor.size.width + x) as usize] = color.a * checker + tc;
+                    texels[(y * descriptor.rect.size.width + x) as usize] = color.a * checker + tc;
                 }
                 _ => {
                     return Err(BlobImageError::Other(
                         format!("Unsupported image format {:?}", descriptor.format),
                     ));
                 }
             }
         }
     }
 
     Ok(RasterizedBlobImage {
         data: Arc::new(texels),
-        rasterized_rect: dirty_rect,
+        rasterized_rect,
     })
 }
 
 /// See rawtest.rs. We use this to test that blob images are requested the right
 /// amount of times.
 pub struct BlobCallbacks {
     pub request: Box<Fn(&[BlobImageParams]) + Send + 'static>,
 }
 
 impl BlobCallbacks {
     pub fn new() -> Self {
         BlobCallbacks { request: Box::new(|_|()) }
     }
 }
 
 pub struct CheckerboardRenderer {
-    image_cmds: HashMap<ImageKey, (ColorU, Option<TileSize>)>,
+    image_cmds: HashMap<BlobImageKey, (ColorU, Option<TileSize>)>,
     callbacks: Arc<Mutex<BlobCallbacks>>,
 }
 
 impl CheckerboardRenderer {
     pub fn new(callbacks: Arc<Mutex<BlobCallbacks>>) -> Self {
         CheckerboardRenderer {
             callbacks,
             image_cmds: HashMap::new(),
         }
     }
 }
 
 impl BlobImageHandler for CheckerboardRenderer {
-    fn add(&mut self, key: ImageKey, cmds: Arc<BlobImageData>, tile_size: Option<TileSize>) {
+    fn add(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>, tile_size: Option<TileSize>) {
         self.image_cmds
             .insert(key, (deserialize_blob(&cmds[..]).unwrap(), tile_size));
     }
 
-    fn update(&mut self, key: ImageKey, cmds: Arc<BlobImageData>, _dirty_rect: Option<DeviceIntRect>) {
+    fn update(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>, _dirty_rect: &BlobDirtyRect) {
         // Here, updating is just replacing the current version of the commands with
         // the new one (no incremental updates).
         self.image_cmds.get_mut(&key).unwrap().0 = deserialize_blob(&cmds[..]).unwrap();
     }
 
-    fn delete(&mut self, key: ImageKey) {
+    fn delete(&mut self, key: BlobImageKey) {
         self.image_cmds.remove(&key);
     }
 
     fn delete_font(&mut self, _key: FontKey) {}
 
     fn delete_font_instance(&mut self, _key: FontInstanceKey) {}
 
     fn clear_namespace(&mut self, _namespace: IdNamespace) {}
@@ -170,21 +164,21 @@ impl BlobImageHandler for CheckerboardRe
     }
 }
 
 struct Command {
     request: BlobImageRequest,
     color: ColorU,
     descriptor: BlobImageDescriptor,
     tile: Option<(TileSize, TileOffset)>,
-    dirty_rect: Option<DeviceIntRect>
+    dirty_rect: BlobDirtyRect,
 }
 
 struct Rasterizer {
-    image_cmds: HashMap<ImageKey, (ColorU, Option<TileSize>)>,
+    image_cmds: HashMap<BlobImageKey, (ColorU, Option<TileSize>)>,
 }
 
 impl AsyncBlobImageRasterizer for Rasterizer {
     fn rasterize(
         &mut self,
         requests: &[BlobImageParams],
         _low_priority: bool
     ) -> Vec<(BlobImageRequest, BlobImageResult)> {
@@ -200,12 +194,12 @@ impl AsyncBlobImageRasterizer for Raster
                     tile,
                     descriptor: item.descriptor,
                     dirty_rect: item.dirty_rect,
                 }
             }
         ).collect();
 
         requests.iter().map(|cmd| {
-            (cmd.request, render_blob(cmd.color, &cmd.descriptor, cmd.tile, cmd.dirty_rect))
+            (cmd.request, render_blob(cmd.color, &cmd.descriptor, cmd.tile, &cmd.dirty_rect))
         }).collect()
     }
 }
--- a/gfx/wr/wrench/src/json_frame_writer.rs
+++ b/gfx/wr/wrench/src/json_frame_writer.rs
@@ -115,17 +115,17 @@ impl JsonFrameWriter {
         for update in updates {
             match *update {
                 ResourceUpdate::AddImage(ref img) => {
                     let stride = img.descriptor.stride.unwrap_or(
                         img.descriptor.size.width * img.descriptor.format.bytes_per_pixel(),
                     );
                     let bytes = match img.data {
                         ImageData::Raw(ref v) => (**v).clone(),
-                        ImageData::External(_) | ImageData::Blob(_) => {
+                        ImageData::External(_) => {
                             return;
                         }
                     };
                     self.images.insert(
                         img.key,
                         CachedImage {
                             width: img.descriptor.size.width,
                             height: img.descriptor.size.height,
@@ -149,16 +149,20 @@ impl JsonFrameWriter {
                             // Other existing image types only make sense within the gecko integration.
                             println!(
                                 "Wrench only supports updating buffer images ({}).",
                                 "ignoring update commands"
                             );
                         }
                     }
                 }
+                ResourceUpdate::AddBlobImage(..)
+                | ResourceUpdate::UpdateBlobImage(..) => {
+                    println!("Blob images not supported (ignoring command).");
+                }
                 ResourceUpdate::DeleteImage(img) => {
                     self.images.remove(&img);
                 }
                 ResourceUpdate::AddFont(ref font) => match font {
                     &AddFont::Raw(key, ref bytes, index) => {
                         self.fonts
                             .insert(key, CachedFont::Raw(Some(bytes.clone()), index, None));
                     }
@@ -172,17 +176,17 @@ impl JsonFrameWriter {
                         instance.key,
                         CachedFontInstance {
                             font_key: instance.font_key,
                             glyph_size: instance.glyph_size,
                         },
                     );
                 }
                 ResourceUpdate::DeleteFontInstance(_) => {}
-                ResourceUpdate::SetImageVisibleArea(..) => {}
+                ResourceUpdate::SetBlobImageVisibleArea(..) => {}
             }
         }
     }
 
     fn next_rsrc_paths(
         prefix: &str,
         counter: &mut u32,
         base_path: &Path,
--- a/gfx/wr/wrench/src/rawtest.rs
+++ b/gfx/wr/wrench/src/rawtest.rs
@@ -89,50 +89,50 @@ impl<'a> RawtestHarness<'a> {
 
 
     fn test_tile_decomposition(&mut self) {
         println!("\ttile decomposition...");
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
         let mut txn = Transaction::new();
 
-        let blob_img = self.wrench.api.generate_image_key();
-        txn.add_image(
+        let blob_img = self.wrench.api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img,
             ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             Some(128),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(448.899994, 74.0, 151.000031, 56.));
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             size(151., 56.0),
             size(151.0, 56.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         self.rx.recv().unwrap();
         self.wrench.render();
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
         txn = Transaction::new();
-        txn.delete_image(blob_img);
+        txn.delete_blob_image(blob_img);
         self.wrench.api.update_resources(txn.resource_updates);
     }
 
     fn test_very_large_blob(&mut self) {
         println!("\tvery large blob...");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
@@ -144,21 +144,21 @@ impl<'a> RawtestHarness<'a> {
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
 
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
         let mut txn = Transaction::new();
 
-        let blob_img = self.wrench.api.generate_image_key();
-        txn.add_image(
+        let blob_img = self.wrench.api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img,
             ImageDescriptor::new(1510, 111256, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             Some(31),
         );
 
         let called = Arc::new(AtomicIsize::new(0));
         let called_inner = Arc::clone(&called);
 
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
             called_inner.fetch_add(1, Ordering::SeqCst);
@@ -175,20 +175,20 @@ impl<'a> RawtestHarness<'a> {
         builder.push_clip_id(clip_id);
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             image_size * 2.,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
-        txn.set_image_visible_area(
+        txn.set_blob_image_visible_area(
             blob_img,
             DeviceIntRect {
                 origin: point2(0, 111256 / 30),
                 size: size2(1510, 111256 / 30),
             }
         );
 
         builder.pop_clip_id();
@@ -230,17 +230,17 @@ impl<'a> RawtestHarness<'a> {
                 pixels[(132 +
                     (window_rect.size.height as usize - 148) *
                         window_rect.size.width as usize) * 4 + 3] == 255
         );
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
         txn = Transaction::new();
-        txn.delete_image(blob_img);
+        txn.delete_blob_image(blob_img);
         self.wrench.api.update_resources(txn.resource_updates);
 
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_insufficient_blob_visible_area(&mut self) {
         println!("\tinsufficient blob visible area.");
 
@@ -260,84 +260,84 @@ impl<'a> RawtestHarness<'a> {
         );
         let layout_size = LayoutSize::new(800.0, 800.0);
         let image_size = size(800.0, 800.0);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 800.0, 800.0));
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let mut txn = Transaction::new();
 
-        let blob_img1 = self.wrench.api.generate_image_key();
-        txn.add_image(
+        let blob_img1 = self.wrench.api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img1,
             ImageDescriptor::new(
                 image_size.width as i32,
                 image_size.height as i32,
                 ImageFormat::BGRA8,
                 false,
                 false
             ),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             Some(100),
         );
 
         builder.push_image(
             &info,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img1,
+            blob_img1.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
         let pixels1 = self.render_and_get_pixels(window_rect);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let mut txn = Transaction::new();
 
-        let blob_img2 = self.wrench.api.generate_image_key();
-        txn.add_image(
+        let blob_img2 = self.wrench.api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img2,
             ImageDescriptor::new(
                 image_size.width as i32,
                 image_size.height as i32,
                 ImageFormat::BGRA8,
                 false,
                 false
             ),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             Some(100),
         );
         // Set a visible rectangle that is too small.
         // This will force sync rasterization of the missing tiles during frame building.
-        txn.set_image_visible_area(blob_img2, DeviceIntRect {
+        txn.set_blob_image_visible_area(blob_img2, DeviceIntRect {
             origin: point2(200, 200),
             size: size2(80, 80),
         });
 
         builder.push_image(
             &info,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img2,
+            blob_img2.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut Epoch(1), layout_size, builder, &txn.resource_updates);
         let pixels2 = self.render_and_get_pixels(window_rect);
 
         assert!(pixels1 == pixels2);
 
         txn = Transaction::new();
-        txn.delete_image(blob_img1);
-        txn.delete_image(blob_img2);
+        txn.delete_blob_image(blob_img1);
+        txn.delete_blob_image(blob_img2);
         self.wrench.api.update_resources(txn.resource_updates);
     }
 
     fn test_offscreen_blob(&mut self) {
         println!("\toffscreen blob update.");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
@@ -349,38 +349,38 @@ impl<'a> RawtestHarness<'a> {
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
 
         // This exposes a crash in tile decomposition
         let mut txn = Transaction::new();
         let layout_size = LayoutSize::new(800., 800.);
 
-        let blob_img = self.wrench.api.generate_image_key();
-        txn.add_image(
+        let blob_img = self.wrench.api.generate_blob_image_key();
+        txn.add_blob_image(
             blob_img,
             ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
 
         let image_size = size(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let original_pixels = self.render_and_get_pixels(window_rect);
@@ -395,62 +395,62 @@ impl<'a> RawtestHarness<'a> {
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &[]);
 
         let _offscreen_pixels = self.render_and_get_pixels(window_rect);
 
         let mut txn = Transaction::new();
 
-        txn.update_image(
+        txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
-            Some(rect(10, 10, 100, 100)),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            &rect(10, 10, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
 
         let image_size = size(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(2);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels = self.render_and_get_pixels(window_rect);
 
         assert!(pixels == original_pixels);
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
         txn = Transaction::new();
-        txn.delete_image(blob_img);
+        txn.delete_blob_image(blob_img);
         self.wrench.api.update_resources(txn.resource_updates);
     }
 
     fn test_retained_blob_images_test(&mut self) {
         println!("\tretained blob images test...");
         let blob_img;
         let window_size = self.window.get_inner_size();
 
@@ -460,21 +460,21 @@ impl<'a> RawtestHarness<'a> {
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let mut txn = Transaction::new();
         {
             let api = &self.wrench.api;
 
-            blob_img = api.generate_image_key();
-            txn.add_image(
+            blob_img = api.generate_blob_image_key();
+            txn.add_blob_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                 None,
             );
         }
 
         let called = Arc::new(AtomicIsize::new(0));
         let called_inner = Arc::clone(&called);
 
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
@@ -486,17 +486,17 @@ impl<'a> RawtestHarness<'a> {
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels_first = self.render_and_get_pixels(window_rect);
@@ -509,17 +509,17 @@ impl<'a> RawtestHarness<'a> {
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         txn.resource_updates.clear();
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels_second = self.render_and_get_pixels(window_rect);
@@ -548,28 +548,28 @@ impl<'a> RawtestHarness<'a> {
             point(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let mut txn = Transaction::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
 
-            blob_img = api.generate_image_key();
-            txn.add_image(
+            blob_img = api.generate_blob_image_key();
+            txn.add_blob_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                 None,
             );
-            blob_img2 = api.generate_image_key();
-            txn.add_image(
+            blob_img2 = api.generate_blob_image_key();
+            txn.add_blob_image(
                 blob_img2,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(80, 50, 150, 255))),
+                blob::serialize_blob(ColorU::new(80, 50, 150, 255)),
                 None,
             );
             (blob_img, blob_img2)
         };
 
         // setup some counters to count how many times each image is requested
         let img1_requested = Arc::new(AtomicIsize::new(0));
         let img1_requested_inner = Arc::clone(&img1_requested);
@@ -594,66 +594,66 @@ impl<'a> RawtestHarness<'a> {
         let info2 = LayoutPrimitiveInfo::new(rect(200.0, 60.0, 200.0, 200.0));
         let push_images = |builder: &mut DisplayListBuilder| {
             builder.push_image(
                 &info,
                 size(200.0, 200.0),
                 size(0.0, 0.0),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
-                blob_img,
+                blob_img.as_image(),
                 ColorF::WHITE,
             );
             builder.push_image(
                 &info2,
                 size(200.0, 200.0),
                 size(0.0, 0.0),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
-                blob_img2,
+                blob_img2.as_image(),
                 ColorF::WHITE,
             );
         };
 
         push_images(&mut builder);
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_first = self.render_and_get_pixels(window_rect);
 
 
         // update and redraw both images
         let mut txn = Transaction::new();
-        txn.update_image(
+        txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
-            Some(rect(100, 100, 100, 100)),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            &rect(100, 100, 100, 100).into(),
         );
-        txn.update_image(
+        txn.update_blob_image(
             blob_img2,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(59, 50, 150, 255))),
-            Some(rect(100, 100, 100, 100)),
+            blob::serialize_blob(ColorU::new(59, 50, 150, 255)),
+            &rect(100, 100, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_second = self.render_and_get_pixels(window_rect);
 
 
         // only update the first image
         let mut txn = Transaction::new();
-        txn.update_image(
+        txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
-            Some(rect(200, 200, 100, 100)),
+            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
+            &rect(200, 200, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         push_images(&mut builder);
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let _pixels_third = self.render_and_get_pixels(window_rect);
 
         // the first image should be requested 3 times
@@ -674,89 +674,89 @@ impl<'a> RawtestHarness<'a> {
         let window_rect = DeviceIntRect::new(
             point(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let mut txn = Transaction::new();
 
         let blob_img = {
-            let img = self.wrench.api.generate_image_key();
-            txn.add_image(
+            let img = self.wrench.api.generate_blob_image_key();
+            txn.add_blob_image(
                 img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-                ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
+                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                 None,
             );
             img
         };
 
         // draw the blobs the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_first = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a second time after updating it with the same color
         let mut txn = Transaction::new();
-        txn.update_image(
+        txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
-            Some(rect(100, 100, 100, 100)),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            &rect(100, 100, 100, 100).into(),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_second = self.render_and_get_pixels(window_rect);
 
         // draw the blob image a third time after updating it with a different color
         let mut txn = Transaction::new();
-        txn.update_image(
+        txn.update_blob_image(
             blob_img,
             ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
-            ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 150, 150, 255))),
-            Some(rect(200, 200, 100, 100)),
+            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
+            &rect(200, 200, 100, 100).into(),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img,
+            blob_img.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_third = self.render_and_get_pixels(window_rect);
 
         assert!(pixels_first == pixels_second);
         assert!(pixels_first != pixels_third);
--- a/gfx/wr/wrench/src/ron_frame_writer.rs
+++ b/gfx/wr/wrench/src/ron_frame_writer.rs
@@ -88,17 +88,17 @@ impl RonFrameWriter {
     }
 
     fn update_resources(&mut self, updates: &[ResourceUpdate]) {
         for update in updates {
             match *update {
                 ResourceUpdate::AddImage(ref img) => {
                     let bytes = match img.data {
                         ImageData::Raw(ref v) => (**v).clone(),
-                        ImageData::External(_) | ImageData::Blob(_) => {
+                        ImageData::External(_) => {
                             return;
                         }
                     };
                     self.images.insert(
                         img.key,
                         CachedImage {
                             width: img.descriptor.size.width,
                             height: img.descriptor.size.height,
@@ -113,40 +113,43 @@ impl RonFrameWriter {
                         assert_eq!(data.width, img.descriptor.size.width);
                         assert_eq!(data.height, img.descriptor.size.height);
                         assert_eq!(data.format, img.descriptor.format);
 
                         if let ImageData::Raw(ref bytes) = img.data {
                             data.path = None;
                             data.bytes = Some((**bytes).clone());
                         } else {
-                            // Other existing image types only make sense within the gecko integration.
                             println!(
                                 "Wrench only supports updating buffer images ({}).",
                                 "ignoring update commands"
                             );
                         }
                     }
                 }
+                ResourceUpdate::AddBlobImage(..)
+                | ResourceUpdate::UpdateBlobImage(..) => {
+                    println!("Blob images not supported (ignoring command).");
+                }
                 ResourceUpdate::DeleteImage(img) => {
                     self.images.remove(&img);
                 }
                 ResourceUpdate::AddFont(ref font) => match font {
                     &AddFont::Raw(key, ref bytes, index) => {
                         self.fonts
                             .insert(key, CachedFont::Raw(Some(bytes.clone()), index, None));
                     }
                     &AddFont::Native(key, ref handle) => {
                         self.fonts.insert(key, CachedFont::Native(handle.clone()));
                     }
                 },
                 ResourceUpdate::DeleteFont(_) => {}
                 ResourceUpdate::AddFontInstance(_) => {}
                 ResourceUpdate::DeleteFontInstance(_) => {}
-                ResourceUpdate::SetImageVisibleArea(..) => {}
+                ResourceUpdate::SetBlobImageVisibleArea(..) => {}
             }
         }
     }
 }
 
 impl fmt::Debug for RonFrameWriter {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "RonFrameWriter")
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -545,17 +545,17 @@ impl YamlFrameWriter {
                           }
                     }
 
                     let stride = img.descriptor.stride.unwrap_or(
                         img.descriptor.size.width * img.descriptor.format.bytes_per_pixel(),
                     );
                     let bytes = match img.data {
                         ImageData::Raw(ref v) => (**v).clone(),
-                        ImageData::External(_) | ImageData::Blob(_) => {
+                        ImageData::External(_) => {
                             return;
                         }
                     };
                     self.images.insert(
                         img.key,
                         CachedImage {
                             width: img.descriptor.size.width,
                             height: img.descriptor.size.height,
@@ -581,16 +581,20 @@ impl YamlFrameWriter {
                             // within the gecko integration.
                             println!(
                                 "Wrench only supports updating buffer images ({}).",
                                 "ignoring update command"
                             );
                         }
                     }
                 }
+                ResourceUpdate::AddBlobImage(..)
+                | ResourceUpdate::UpdateBlobImage(..) => {
+                    println!("Blob images not supported (ignoring command).");
+                }
                 ResourceUpdate::DeleteImage(img) => {
                     self.images.remove(&img);
                 }
                 ResourceUpdate::AddFont(ref font) => match font {
                     &AddFont::Raw(key, ref bytes, index) => {
                         self.fonts
                             .insert(key, CachedFont::Raw(Some(bytes.clone()), index, None));
                     }
@@ -604,17 +608,17 @@ impl YamlFrameWriter {
                         instance.key,
                         CachedFontInstance {
                             font_key: instance.font_key,
                             glyph_size: instance.glyph_size,
                         },
                     );
                 }
                 ResourceUpdate::DeleteFontInstance(_) => {}
-                ResourceUpdate::SetImageVisibleArea(..) => {}
+                ResourceUpdate::SetBlobImageVisibleArea(..) => {}
             }
         }
     }
 
 
 
     fn path_for_image(&mut self, key: ImageKey) -> Option<PathBuf> {
         let data = match self.images.get_mut(&key) {