Bug 1473284 - Update webrender to commit 0e9563688e575cd662570f54bc9d6f849040dbf8. r?Gankro draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 09 Jul 2018 09:29:15 -0400
changeset 815601 4ec2dde7bc046a104009dde96438c28c67d45b7a
parent 815592 3d20b0701781731e0f9b08e1cd40ac842f385e03
push id115566
push userkgupta@mozilla.com
push dateMon, 09 Jul 2018 13:29:42 +0000
reviewersGankro
bugs1473284
milestone63.0a1
Bug 1473284 - Update webrender to commit 0e9563688e575cd662570f54bc9d6f849040dbf8. r?Gankro MozReview-Commit-ID: 8iH1bGPKpsd
gfx/webrender/res/render_task.glsl
gfx/webrender/src/device/mod.rs
gfx/webrender/src/device/query_gl.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/query.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/blob.rs
--- a/gfx/webrender/res/render_task.glsl
+++ b/gfx/webrender/res/render_task.glsl
@@ -80,30 +80,31 @@ PictureTask fetch_picture_task(int addre
     PictureTask task = PictureTask(
         task_data.common_data,
         task_data.data1.xy
     );
 
     return task;
 }
 
+#define CLIP_TASK_EMPTY 0x7FFF
+
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
     bool local_space;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
 
-    if (index == 0x7FFF) { //special sentinel task index
-        area.common_data = RenderTaskCommonData(
-            RectWithSize(vec2(0.0), vec2(0.0)),
-            0.0
-        );
+    if (index >= CLIP_TASK_EMPTY) {
+        RectWithSize rect = RectWithSize(vec2(0.0), vec2(0.0));
+
+        area.common_data = RenderTaskCommonData(rect, 0.0);
         area.screen_origin = vec2(0.0);
         area.local_space = false;
     } else {
         RenderTaskData task_data = fetch_render_task_data(index);
 
         area.common_data = task_data.common_data;
         area.screen_origin = task_data.data1.xy;
         area.local_space = task_data.data1.z == 0.0;
--- a/gfx/webrender/src/device/mod.rs
+++ b/gfx/webrender/src/device/mod.rs
@@ -1,7 +1,9 @@
 /* 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/. */
 
 mod gl;
+pub mod query_gl;
 
 pub use self::gl::*;
+pub use self::query_gl as query;
rename from gfx/webrender/src/query.rs
rename to gfx/webrender/src/device/query_gl.rs
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -1,16 +1,16 @@
 
 /* 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::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, Epoch, ExtendMode, ExternalScrollId};
+use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
@@ -145,19 +145,16 @@ pub struct DisplayListFlattener<'a> {
     scene: &'a Scene,
 
     /// The ClipScrollTree that we are currently building during flattening.
     clip_scroll_tree: &'a mut ClipScrollTree,
 
     /// The map of all font instances.
     font_instances: FontInstanceMap,
 
-    /// Used to track the latest flattened epoch for each pipeline.
-    pipeline_epochs: Vec<(PipelineId, Epoch)>,
-
     /// A set of pipelines that the caller has requested be made available as
     /// output textures.
     output_pipelines: &'a FastHashSet<PipelineId>,
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
@@ -202,28 +199,25 @@ impl<'a> DisplayListFlattener<'a> {
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
         scene_id: u64,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
 
-        let root_epoch = scene.pipeline_epochs[&root_pipeline_id];
-
         let background_color = root_pipeline
             .background_color
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
 
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
-            pipeline_epochs: Vec::new(),
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
             scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
             reference_frame_stack: Vec::new(),
             picture_stack: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
@@ -238,18 +232,17 @@ impl<'a> DisplayListFlattener<'a> {
             &root_pipeline.content_size,
         );
         flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
         flattener.flatten_root(root_pipeline, &root_pipeline.viewport_size);
 
         debug_assert!(flattener.picture_stack.is_empty());
 
         new_scene.root_pipeline_id = Some(root_pipeline_id);
-        new_scene.pipeline_epochs.insert(root_pipeline_id, root_epoch);
-        new_scene.pipeline_epochs.extend(flattener.pipeline_epochs.drain(..));
+        new_scene.pipeline_epochs = scene.pipeline_epochs.clone();
         new_scene.pipelines = scene.pipelines.clone();
 
         FrameBuilder::with_display_list_flattener(
             view.inner_rect,
             background_color,
             view.window_size,
             scene_id,
             flattener,
@@ -524,19 +517,16 @@ impl<'a> DisplayListFlattener<'a> {
             info.clip_id,
             clip_and_scroll_ids.scroll_node_id,
             ClipRegion::create_for_clip_node_with_local_clip(
                 &LocalClip::from(*item.clip_rect()),
                 reference_frame_relative_offset
             ),
         );
 
-        let epoch = self.scene.pipeline_epochs[&iframe_pipeline_id];
-        self.pipeline_epochs.push((iframe_pipeline_id, epoch));
-
         let bounds = item.rect();
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
         self.push_reference_frame(
             ClipId::root_reference_frame(iframe_pipeline_id),
             Some(info.clip_id),
             iframe_pipeline_id,
             None,
             None,
@@ -1887,17 +1877,16 @@ impl<'a> DisplayListFlattener<'a> {
 
         let prim = BrushPrimitive::new(
             BrushKind::Image {
                 request: ImageRequest {
                     key: image_key,
                     rendering: image_rendering,
                     tile: None,
                 },
-                current_epoch: Epoch::invalid(),
                 alpha_type,
                 stretch_size,
                 tile_spacing,
                 source: ImageSource::Default,
                 sub_rect,
                 visible_tiles: Vec::new(),
                 opacity_binding: OpacityBinding::new(),
             },
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -84,17 +84,16 @@ mod gpu_glyph_renderer;
 mod gpu_types;
 mod hit_test;
 mod image;
 mod internal_types;
 mod picture;
 mod prim_store;
 mod print_tree;
 mod profiler;
-mod query;
 mod record;
 mod render_backend;
 mod render_task;
 mod renderer;
 mod resource_cache;
 mod scene;
 mod scene_builder;
 mod segment;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,14 +1,14 @@
 /* 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::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF};
-use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode};
+use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
@@ -279,17 +279,16 @@ pub enum BrushKind {
         opacity_binding: OpacityBinding,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
     },
     Image {
         request: ImageRequest,
-        current_epoch: Epoch,
         alpha_type: AlphaType,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         source: ImageSource,
         sub_rect: Option<DeviceIntRect>,
         opacity_binding: OpacityBinding,
         visible_tiles: Vec<VisibleImageTile>,
     },
@@ -1572,30 +1571,28 @@ impl PrimitiveStore {
                 let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
                 match brush.kind {
                     BrushKind::Image {
                         request,
                         sub_rect,
                         stretch_size,
                         ref mut tile_spacing,
-                        ref mut current_epoch,
                         ref mut source,
                         ref mut opacity_binding,
                         ref mut visible_tiles,
                         ..
                     } => {
                         let image_properties = frame_state
                             .resource_cache
                             .get_image_properties(request.key);
 
 
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
-                            *current_epoch = image_properties.epoch;
                             is_tiled = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
                                 frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                             }
 
@@ -2271,17 +2268,18 @@ impl PrimitiveStore {
                 return false;
             }
         };
 
         let mut combined_outer_rect =
             prim_screen_rect.intersection(&prim_run_context.clip_chain.combined_outer_screen_rect);
         let clip_chain = prim_run_context.clip_chain.nodes.clone();
         if cfg!(debug_assertions) && Some(prim_index) == self.chase_id {
-            println!("\tbase combined outer rect {:?}", combined_outer_rect);
+            println!("\tbase screen {:?}, combined clip chain {:?}",
+                prim_screen_rect, prim_run_context.clip_chain.combined_outer_screen_rect);
         }
 
         let prim_coordinate_system_id = prim_run_context.scroll_node.coordinate_system_id;
         let transform = &prim_run_context.scroll_node.world_content_transform;
         let extra_clip =  {
             let metadata = &self.cpu_metadata[prim_index.0];
             metadata.clip_sources.as_ref().map(|clip_sources| {
                 let prim_clips = frame_state.clip_store.get_mut(clip_sources);
@@ -2690,16 +2688,30 @@ impl PrimitiveStore {
                         .nodes[original_reference_frame_index.0]
                         .world_content_transform
                         .inverse()
                 })
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
+            if run.is_chasing(self.chase_id) {
+                println!("\teffective clip chain from {:?} {}",
+                    scroll_node.coordinate_system_id,
+                    if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
+                );
+                let iter = ClipChainNodeIter { current: clip_chain.nodes.clone() };
+                for node in iter {
+                    println!("\t\t{:?} {:?}",
+                        node.work_item.coordinate_system_id,
+                        node.local_clip_rect,
+                    );
+                }
+            }
+
             let clip_chain_rect = if pic_context.apply_local_clip_rect {
                 get_local_clip_rect_for_nodes(scroll_node, clip_chain)
             } else {
                 None
             };
 
             let local_clip_chain_rect = match clip_chain_rect {
                 Some(rect) if rect.is_empty() => continue,
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.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::ColorF;
-use query::{GpuTimer, NamedTag};
+use device::query::{GpuTimer, NamedTag};
 use std::collections::vec_deque::VecDeque;
 use std::f32;
 use time::precise_time_ns;
 
 cfg_if! {
     if #[cfg(feature = "debug_renderer")] {
         use api::ColorU;
         use debug_render::DebugRenderer;
         use euclid::{Point2D, Rect, Size2D, vec2};
-        use query::GpuSampler;
+        use device::query::GpuSampler;
         use internal_types::FastHashMap;
         use renderer::MAX_VERTEX_TEXTURE_WIDTH;
         use std::mem;
     }
 }
 
 cfg_if! {
     if #[cfg(feature = "debug_renderer")] {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -34,17 +34,17 @@ use gpu_cache::{GpuBlockData, GpuCacheUp
 use gpu_glyph_renderer::GpuGlyphRenderer;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
 use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
                GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
-use query::GpuProfiler;
+use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use scene_builder::SceneBuilder;
 use shade::Shaders;
 use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 
@@ -78,17 +78,17 @@ cfg_if! {
     }
 }
 
 cfg_if! {
     if #[cfg(feature = "debug_renderer")] {
         use api::ColorU;
         use debug_render::DebugRenderer;
         use profiler::{Profiler, ChangeIndicator};
-        use query::GpuTimer;
+        use device::query::GpuTimer;
     }
 }
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 /// Enabling this toggle would force the GPU cache scattered texture to
 /// be resized every frame, which enables GPU debuggers to see if this
 /// is performed correctly.
 const GPU_CACHE_RESIZE_TEST: bool = false;
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,16 +1,16 @@
 /* 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, ResourceUpdate};
 use api::{BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
 use api::{ClearCache, ColorF, DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{Epoch, FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
+use api::{FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
 use api::{ExternalImageData, ExternalImageType};
 use api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize};
 use app_units::Au;
 #[cfg(feature = "capture")]
 use capture::ExternalCaptureImage;
@@ -27,17 +27,18 @@ use glyph_rasterizer::{FontInstance, Gly
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::UvRectKind;
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
 use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
-use std::cmp;
+use std::collections::hash_map::ValuesMut;
+use std::{cmp, mem};
 use std::fmt::Debug;
 use std::hash::Hash;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::SpecialRenderPasses;
 
@@ -80,33 +81,30 @@ impl CacheItem {
 
 #[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>,
-    pub epoch: Epoch,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
 }
 
 #[derive(Debug)]
 struct ImageResource {
     data: ImageData,
     descriptor: ImageDescriptor,
-    epoch: Epoch,
     tiling: Option<TileSize>,
-    dirty_rect: Option<DeviceUintRect>,
 }
 
 #[derive(Clone, Debug)]
 pub struct ImageTiling {
     pub image_size: DeviceUintSize,
     pub tile_size: TileSize,
 }
 
@@ -132,17 +130,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,
-    epoch: Epoch,
+    dirty_rect: Option<DeviceUintRect>,
 }
 
 #[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,
 }
@@ -163,16 +161,34 @@ pub fn intersect_for_tile(
     )).map(|mut r| {
         // we can't translate by a negative size so do it manually
         r.origin.x -= tile_offset.x as u32 * tile_size as u32;
         r.origin.y -= tile_offset.y as u32 * tile_size as u32;
         r
     })
 }
 
+fn merge_dirty_rect(
+    prev_dirty_rect: &Option<DeviceUintRect>,
+    dirty_rect: &Option<DeviceUintRect>,
+    descriptor: &ImageDescriptor,
+) -> Option<DeviceUintRect> {
+    // 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 {
@@ -185,25 +201,37 @@ where
         self.resources.get(key)
             .expect("Didn't find a cached resource with that ID!")
     }
 
     pub fn insert(&mut self, key: K, value: V) {
         self.resources.insert(key, value);
     }
 
+    pub fn remove(&mut self, key: &K) {
+        self.resources.remove(key);
+    }
+
     pub fn get_mut(&mut self, key: &K) -> &mut V {
         self.resources.get_mut(key)
             .expect("Didn't find a cached resource with that ID!")
     }
 
+    pub fn try_get_mut(&mut self, key: &K) -> Option<&mut V> {
+        self.resources.get_mut(key)
+    }
+
     pub fn entry(&mut self, key: K) -> Entry<K, V> {
         self.resources.entry(key)
     }
 
+    pub fn values_mut(&mut self) -> ValuesMut<K, V> {
+        self.resources.values_mut()
+    }
+
     pub fn clear(&mut self) {
         self.resources.clear();
     }
 
     fn clear_keys<F>(&mut self, key_fun: F)
     where
         for<'r> F: Fn(&'r &K) -> bool,
     {
@@ -220,16 +248,23 @@ where
     pub fn retain<F>(&mut self, f: F)
     where
         F: FnMut(&K, &mut V) -> bool,
     {
         self.resources.retain(f);
     }
 }
 
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+struct CachedImageKey {
+    pub rendering: ImageRendering,
+    pub tile: Option<TileOffset>,
+}
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageRequest {
     pub key: ImageKey,
     pub rendering: ImageRendering,
     pub tile: Option<TileOffset>,
@@ -238,35 +273,56 @@ pub struct ImageRequest {
 impl ImageRequest {
     pub fn with_tile(&self, offset: TileOffset) -> Self {
         ImageRequest {
             key: self.key,
             rendering: self.rendering,
             tile: Some(offset),
         }
     }
+
+    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,
             tile: self.tile,
         }
     }
 }
 
+impl Into<CachedImageKey> for ImageRequest {
+    fn into(self) -> CachedImageKey {
+        CachedImageKey {
+            rendering: self.rendering,
+            tile: self.tile,
+        }
+    }
+}
+
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Clone, Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ImageCacheError {
     OverLimitSize,
 }
 
-type ImageCache = ResourceClassCache<ImageRequest, Result<CachedImageInfo, ImageCacheError>, ()>;
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+enum ImageResult {
+    UntiledAuto(CachedImageInfo),
+    Multi(ResourceClassCache<CachedImageKey, CachedImageInfo, ()>),
+    Err(ImageCacheError),
+}
+
+type ImageCache = ResourceClassCache<ImageKey, ImageResult, ()>;
 pub type FontInstanceMap = Arc<RwLock<FastHashMap<FontInstanceKey, FontInstance>>>;
 
 #[derive(Default)]
 struct Resources {
     font_templates: FastHashMap<FontKey, FontTemplate>,
     font_instances: FontInstanceMap,
     image_templates: ImageTemplates,
 }
@@ -505,24 +561,21 @@ impl ResourceCache {
 
         if let ImageData::Blob(ref blob) = data {
             self.blob_image_renderer.as_mut().unwrap().add(
                 image_key,
                 Arc::clone(&blob),
                 tiling,
             );
         }
-        let dirty_rect = Some(descriptor.full_rect());
 
         let resource = ImageResource {
             descriptor,
             data,
-            epoch: Epoch(0),
             tiling,
-            dirty_rect,
         };
 
         self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(
         &mut self,
         image_key: ImageKey,
@@ -543,34 +596,41 @@ impl ResourceCache {
 
         if let ImageData::Blob(ref blob) = data {
             self.blob_image_renderer
                 .as_mut()
                 .unwrap()
                 .update(image_key, Arc::clone(blob), dirty_rect);
         }
 
+        // 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);
+            }
+            Some(&mut ImageResult::Multi(ref mut entries)) => {
+                for entry in entries.values_mut() {
+                    entry.dirty_rect = merge_dirty_rect(&entry.dirty_rect, &dirty_rect, &descriptor);
+                }
+            }
+            _ => {}
+        }
+
         *image = ImageResource {
             descriptor,
             data,
-            epoch: Epoch(image.epoch.0 + 1),
             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,
-            },
         };
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         let value = self.resources.image_templates.remove(image_key);
 
-        self.cached_images
-            .clear_keys(|request| request.key == image_key);
+        self.cached_images.remove(&image_key);
 
         match value {
             Some(image) => if image.data.is_blob() {
                 self.blob_image_renderer.as_mut().unwrap().delete(image_key);
             },
             None => {
                 warn!("Delete the non-exist key");
                 debug!("key={:?}", image_key);
@@ -601,51 +661,86 @@ impl ResourceCache {
 
         let side_size =
             template.tiling.map_or(cmp::max(template.descriptor.size.width, template.descriptor.size.height),
                                    |tile_size| tile_size as u32);
         if side_size > self.texture_cache.max_texture_size() {
             // The image or tiling size is too big for hardware texture size.
             warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!",
                   template.descriptor.size.width, template.descriptor.size.height, template.tiling.unwrap_or(0));
-            self.cached_images.insert(request, Err(ImageCacheError::OverLimitSize));
+            self.cached_images.insert(request.key, ImageResult::Err(ImageCacheError::OverLimitSize));
             return;
         }
 
-        // If this image exists in the texture cache, *and* the epoch
-        // in the cache matches that of the template, then it is
-        // valid to use as-is.
-        let (entry, needs_update) = match self.cached_images.entry(request) {
-            Occupied(entry) => {
-                let info = entry.into_mut();
-                let needs_update = info.as_mut().unwrap().epoch != template.epoch;
-                info.as_mut().unwrap().epoch = template.epoch;
-                (info, needs_update)
+        let storage = match self.cached_images.entry(request.key) {
+            Occupied(e) => {
+                // We might have an existing untiled entry, and need to insert
+                // a second entry. In such cases we need to move the old entry
+                // 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::new(),
+                                dirty_rect: None,
+                            }))
+                        }
+                        _ => None
+                    };
+
+                    if let Some(untiled_entry) = untiled_entry {
+                        let mut entries = ResourceClassCache::new();
+                        let untiled_key = CachedImageKey {
+                            rendering: ImageRendering::Auto,
+                            tile: None,
+                        };
+                        entries.insert(untiled_key, untiled_entry);
+                        *entry = ImageResult::Multi(entries);
+                    }
+                }
+                entry
             }
-            Vacant(entry) => (
-                entry.insert(Ok(
-                    CachedImageInfo {
-                        epoch: template.epoch,
+            Vacant(entry) => {
+                entry.insert(if request.is_untiled_auto() {
+                    ImageResult::UntiledAuto(CachedImageInfo {
                         texture_cache_handle: TextureCacheHandle::new(),
-                    }
-                )),
-                true,
-            ),
+                        dirty_rect: Some(template.descriptor.full_rect()),
+                    })
+                } 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::new(),
+                        dirty_rect: Some(template.descriptor.full_rect()),
+                    })
+            },
+            ImageResult::Err(_) => panic!("Errors should already have been handled"),
         };
 
         let needs_upload = self.texture_cache
-            .request(&entry.as_ref().unwrap().texture_cache_handle, gpu_cache);
+            .request(&entry.texture_cache_handle, gpu_cache);
 
         let dirty_rect = if needs_upload {
             // the texture cache entry has been evicted, treat it as all dirty
-            Some(template.descriptor.full_rect())
-        } else if needs_update {
-            template.dirty_rect
+            None
+        } else if entry.dirty_rect.is_none() {
+            return
         } else {
-            return
+            entry.dirty_rect
         };
 
         if !self.pending_image_requests.insert(request) {
             return
         }
 
         // If we are tiling, then we need to confirm the dirty rect intersects
         // the tile before leaving the request in the pending queue.
@@ -661,16 +756,17 @@ impl ResourceCache {
                         &template.descriptor,
                         tile_size,
                         tile_offset,
                     );
 
                     if let Some(dirty) = dirty_rect {
                         if intersect_for_tile(dirty, actual_size, tile_size, tile_offset).is_none() {
                             // don't bother requesting unchanged tiles
+                            entry.dirty_rect = None;
                             self.pending_image_requests.remove(&request);
                             return
                         }
                     }
 
                     let offset = DevicePoint::new(
                         tile_offset.x as f32 * tile_size as f32,
                         tile_offset.y as f32 * tile_size as f32,
@@ -844,21 +940,25 @@ impl ResourceCache {
     pub fn get_cached_image(
         &self,
         request: ImageRequest,
     ) -> Result<CacheItem, ()> {
         debug_assert_eq!(self.state, State::QueryResources);
 
         // TODO(Jerry): add a debug option to visualize the corresponding area for
         // the Err() case of CacheItem.
-        match *self.cached_images.get(&request) {
-            Ok(ref image_info) => {
+        match *self.cached_images.get(&request.key) {
+            ImageResult::UntiledAuto(ref image_info) => {
                 Ok(self.texture_cache.get(&image_info.texture_cache_handle))
             }
-            Err(_) => {
+            ImageResult::Multi(ref entries) => {
+                let image_info = entries.get(&request.into());
+                Ok(self.texture_cache.get(&image_info.texture_cache_handle))
+            }
+            ImageResult::Err(_) => {
                 Err(())
             }
         }
     }
 
     pub fn get_cached_render_task(
         &self,
         handle: &RenderTaskCacheEntryHandle,
@@ -883,17 +983,16 @@ impl ResourceCache {
                 // raw and blob image are all using resource_cache.
                 ImageData::Raw(..) | ImageData::Blob(..) => None,
             };
 
             ImageProperties {
                 descriptor: image_template.descriptor,
                 external_image,
                 tiling: image_template.tiling,
-                epoch: image_template.epoch,
             }
         })
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
@@ -929,18 +1028,16 @@ impl ResourceCache {
             gpu_cache,
             &mut self.texture_cache,
             render_tasks,
         );
         self.texture_cache.end_frame(texture_cache_profile);
     }
 
     fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
-        let mut keys_to_clear_dirty_rect = FastHashSet::default();
-
         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 image_data = match image_template.data {
                 ImageData::Raw(..) | ImageData::External(..) => {
                     // Safe to clone here since the Raw image data is an
                     // Arc, and the external image data is small.
@@ -969,30 +1066,32 @@ impl ResourceCache {
                         }
                         Err(BlobImageError::Other(msg)) => {
                             panic!("Vector image error {}", msg);
                         }
                     }
                 }
             };
 
-            let entry = self.cached_images.get_mut(&request).as_mut().unwrap();
+            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 local_dirty_rect;
 
             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(ref rect) = image_template.dirty_rect {
-                    keys_to_clear_dirty_rect.insert(request.key.clone());
-
+                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);
+                    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
@@ -1005,17 +1104,17 @@ impl ResourceCache {
                     descriptor.stride = Some(stride);
                     descriptor.offset +=
                         tile.y as u32 * tile_size as u32 * stride +
                         tile.x as u32 * tile_size as u32 * bpp;
                 }
 
                 descriptor.size = clipped_tile_size;
             } else {
-                local_dirty_rect = image_template.dirty_rect.take();
+                local_dirty_rect = entry.dirty_rect.take();
             }
 
             let filter = match request.rendering {
                 ImageRendering::Pixelated => {
                     TextureFilter::Nearest
                 }
                 ImageRendering::Auto | ImageRendering::CrispEdges => {
                     // If the texture uses linear filtering, enable mipmaps and
@@ -1047,21 +1146,16 @@ impl ResourceCache {
                 Some(image_data),
                 [0.0; 3],
                 local_dirty_rect,
                 gpu_cache,
                 None,
                 UvRectKind::Rect,
             );
         }
-
-        for key in keys_to_clear_dirty_rect.drain() {
-            let image_template = self.resources.image_templates.get_mut(key).unwrap();
-            image_template.dirty_rect.take();
-        }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
 
     pub fn clear(&mut self, what: ClearCache) {
@@ -1083,17 +1177,17 @@ impl ResourceCache {
     }
 
     pub fn clear_namespace(&mut self, namespace: IdNamespace) {
         self.resources
             .image_templates
             .images
             .retain(|key, _| key.0 != namespace);
         self.cached_images
-            .clear_keys(|request| request.key.0 == namespace);
+            .clear_keys(|key| key.0 == namespace);
 
         self.resources.font_instances
             .write()
             .unwrap()
             .retain(|key, _| key.0 != namespace);
         for &key in self.resources.font_templates.keys().filter(|key| key.0 == namespace) {
             self.glyph_rasterizer.delete_font(key);
         }
@@ -1146,17 +1240,16 @@ enum PlainFontTemplate {
 }
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainImageTemplate {
     data: String,
     descriptor: ImageDescriptor,
-    epoch: Epoch,
     tiling: Option<TileSize>,
 }
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PlainResources {
     font_templates: FastHashMap<FontKey, PlainFontTemplate>,
@@ -1345,17 +1438,16 @@ impl ResourceCache {
                 .map(|(key, template)| {
                     (*key, PlainImageTemplate {
                         data: match template.data {
                             ImageData::Raw(ref arc) => image_paths[&arc.as_ptr()].clone(),
                             _ => other_paths[key].clone(),
                         },
                         descriptor: template.descriptor.clone(),
                         tiling: template.tiling,
-                        epoch: template.epoch,
                     })
                 })
                 .collect(),
         };
 
         (resources, external_images)
     }
 
@@ -1470,16 +1562,14 @@ impl ResourceCache {
                     ImageData::Raw(arc)
                 }
             };
 
             res.image_templates.images.insert(key, ImageResource {
                 data,
                 descriptor: template.descriptor,
                 tiling: template.tiling,
-                epoch: template.epoch,
-                dirty_rect: None,
             });
         }
 
         external_images
     }
 }
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-34a498f7e46c385a189299e7369e204e1cb2060c
+0e9563688e575cd662570f54bc9d6f849040dbf8
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -50,17 +50,17 @@ fn render_blob(
     // 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(DeviceUintRect::new(
-        DeviceUintPoint::origin(),
+        descriptor.offset.to_u32(),
         descriptor.size,
     ));
 
     if let Some((tile_size, tile)) = tile {
         dirty_rect = intersect_for_tile(dirty_rect, size2(tile_size as u32, tile_size as u32),
                                         tile_size, tile)
             .expect("empty rects should be culled by webrender");
     }