Bug 1517072 - Update webrender to commit 7fc05244e1400acfde1d0a0a4a564e47dc2ef998 (WR PR #3455). r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Thu, 03 Jan 2019 03:57:27 +0000
changeset 452371 37c0d21de4a5f56800548d867c8ebd5a9d92dc98
parent 452370 c0f59d4934603397b4c466b3e04b3a724a5917f3
child 452372 11a443603ee556a64fc9cbb5a2e85dbc783654ee
push id35304
push userdvarga@mozilla.com
push dateThu, 03 Jan 2019 16:24:35 +0000
treeherdermozilla-central@5b837856dca7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1517072
milestone66.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 1517072 - Update webrender to commit 7fc05244e1400acfde1d0a0a4a564e47dc2ef998 (WR PR #3455). r=kats https://github.com/servo/webrender/pull/3455 Differential Revision: https://phabricator.services.mozilla.com/D15632
gfx/webrender_bindings/revision.txt
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/tiling.rs
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-a970cbaa88a7516758046c8be016c34627d31355
+7fc05244e1400acfde1d0a0a4a564e47dc2ef998
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -15,17 +15,17 @@ use gpu_types::{PrimitiveInstanceData, R
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
 use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind};
 use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex};
 use prim_store::image::ImageSource;
 use render_backend::FrameResources;
-use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
+use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
 use util::{TransformedRectKind};
@@ -279,27 +279,35 @@ impl OpaqueBatchList {
             batch.instances.reverse();
         }
     }
 }
 
 pub struct BatchList {
     pub alpha_batch_list: AlphaBatchList,
     pub opaque_batch_list: OpaqueBatchList,
+    pub scissor_rect: Option<DeviceIntRect>,
+    pub tile_blits: Vec<TileBlit>,
 }
 
 impl BatchList {
-    pub fn new(screen_size: DeviceIntSize) -> Self {
+    pub fn new(
+        screen_size: DeviceIntSize,
+        scissor_rect: Option<DeviceIntRect>,
+        tile_blits: Vec<TileBlit>,
+    ) -> Self {
         // The threshold for creating a new batch is
         // one quarter the screen size.
         let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0;
 
         BatchList {
             alpha_batch_list: AlphaBatchList::new(),
             opaque_batch_list: OpaqueBatchList::new(batch_area_threshold),
+            scissor_rect,
+            tile_blits,
         }
     }
 
     pub fn push_single_instance(
         &mut self,
         key: BatchKey,
         bounding_rect: &PictureRect,
         z_id: ZBufferId,
@@ -368,47 +376,56 @@ impl PrimitiveBatch {
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaBatchContainer {
     pub opaque_batches: Vec<PrimitiveBatch>,
     pub alpha_batches: Vec<PrimitiveBatch>,
-    pub target_rect: Option<DeviceIntRect>,
+    pub scissor_rect: Option<DeviceIntRect>,
+    pub tile_blits: Vec<TileBlit>,
 }
 
 impl AlphaBatchContainer {
-    pub fn new(target_rect: Option<DeviceIntRect>) -> AlphaBatchContainer {
+    pub fn new(
+        scissor_rect: Option<DeviceIntRect>,
+    ) -> AlphaBatchContainer {
         AlphaBatchContainer {
             opaque_batches: Vec::new(),
             alpha_batches: Vec::new(),
-            target_rect,
+            scissor_rect,
+            tile_blits: Vec::new(),
         }
     }
 
-    fn merge(&mut self, builder: AlphaBatchBuilder) {
-        for other_batch in builder.batch_list.opaque_batch_list.batches {
+    pub fn is_empty(&self) -> bool {
+        self.opaque_batches.is_empty() &&
+        self.alpha_batches.is_empty()
+    }
+
+    fn merge(&mut self, batch_list: BatchList) {
+        for other_batch in batch_list.opaque_batch_list.batches {
             let batch_index = self.opaque_batches.iter().position(|batch| {
                 batch.key.is_compatible_with(&other_batch.key)
             });
 
             match batch_index {
                 Some(batch_index) => {
                     self.opaque_batches[batch_index].instances.extend(other_batch.instances);
                 }
                 None => {
                     self.opaque_batches.push(other_batch);
                 }
             }
         }
 
         let mut min_batch_index = 0;
 
-        for other_batch in builder.batch_list.alpha_batch_list.batches {
+        for other_batch in batch_list.alpha_batch_list.batches {
             let batch_index = self.alpha_batches.iter().skip(min_batch_index).position(|batch| {
                 batch.key.is_compatible_with(&other_batch.key)
             });
 
             match batch_index {
                 Some(batch_index) => {
                     let batch_index = batch_index + min_batch_index;
                     self.alpha_batches[batch_index].instances.extend(other_batch.instances);
@@ -428,48 +445,95 @@ impl AlphaBatchContainer {
 #[derive(Debug, Copy, Clone)]
 struct SegmentInstanceData {
     textures: BatchTextures,
     user_data: i32,
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatchBuilder {
-    pub batch_list: BatchList,
+    pub batch_lists: Vec<BatchList>,
+    screen_size: DeviceIntSize,
+    scissor_rect: Option<DeviceIntRect>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
-    target_rect: DeviceIntRect,
-    can_merge: bool,
 }
 
 impl AlphaBatchBuilder {
     pub fn new(
         screen_size: DeviceIntSize,
-        target_rect: DeviceIntRect,
-        can_merge: bool,
+        scissor_rect: Option<DeviceIntRect>,
     ) -> Self {
+        let batch_lists = vec![
+            BatchList::new(
+                screen_size,
+                scissor_rect,
+                Vec::new(),
+            ),
+        ];
+
         AlphaBatchBuilder {
-            batch_list: BatchList::new(screen_size),
+            batch_lists,
+            scissor_rect,
+            screen_size,
             glyph_fetch_buffer: Vec::new(),
-            target_rect,
-            can_merge,
         }
     }
 
-    pub fn build(mut self, merged_batches: &mut AlphaBatchContainer) -> Option<AlphaBatchContainer> {
-        self.batch_list.finalize();
+    fn push_new_batch_list(
+        &mut self,
+        scissor_rect: Option<DeviceIntRect>,
+        tile_blits: Vec<TileBlit>,
+    ) {
+        let scissor_rect = match (scissor_rect, self.scissor_rect) {
+            (Some(rect0), Some(rect1)) => {
+                Some(rect0.intersection(&rect1).unwrap_or(DeviceIntRect::zero()))
+            }
+            (Some(rect0), None) => Some(rect0),
+            (None, Some(rect1)) => Some(rect1),
+            (None, None) => None,
+        };
+
+        self.batch_lists.push(BatchList::new(
+            self.screen_size,
+            scissor_rect,
+            tile_blits,
+        ));
+    }
+
+    fn current_batch_list(&mut self) -> &mut BatchList {
+        self.batch_lists.last_mut().unwrap()
+    }
 
-        if self.can_merge {
-            merged_batches.merge(self);
-            None
+    fn can_merge(&self) -> bool {
+        self.scissor_rect.is_none() &&
+        self.batch_lists.len() == 1
+    }
+
+    pub fn build(
+        mut self,
+        batch_containers: &mut Vec<AlphaBatchContainer>,
+        merged_batches: &mut AlphaBatchContainer,
+    ) {
+        for batch_list in &mut self.batch_lists {
+            batch_list.finalize();
+        }
+
+        if self.can_merge() {
+            let batch_list = self.batch_lists.pop().unwrap();
+            debug_assert!(batch_list.tile_blits.is_empty());
+            merged_batches.merge(batch_list);
         } else {
-            Some(AlphaBatchContainer {
-                alpha_batches: self.batch_list.alpha_batch_list.batches,
-                opaque_batches: self.batch_list.opaque_batch_list.batches,
-                target_rect: Some(self.target_rect),
-            })
+            for batch_list in self.batch_lists {
+                batch_containers.push(AlphaBatchContainer {
+                    alpha_batches: batch_list.alpha_batch_list.batches,
+                    opaque_batches: batch_list.opaque_batch_list.batches,
+                    scissor_rect: batch_list.scissor_rect,
+                    tile_blits: batch_list.tile_blits,
+                });
+            }
         }
     }
 
     pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
@@ -590,17 +654,17 @@ impl AlphaBatchBuilder {
                     segment_index: INVALID_SEGMENT_INDEX,
                     edge_flags: EdgeAaSegmentMask::all(),
                     clip_task_address,
                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                     prim_header_index,
                     user_data: 0,
                 });
 
-                self.batch_list.push_single_instance(
+                self.current_batch_list().push_single_instance(
                     batch_key,
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
             PrimitiveInstanceKind::NormalBorder { data_handle, ref cache_handles, .. } => {
                 let prim_data = &ctx.resources.normal_border_data_store[data_handle];
@@ -680,17 +744,17 @@ impl AlphaBatchBuilder {
             PrimitiveInstanceKind::TextRun { data_handle, run_index, .. } => {
                 let run = &ctx.prim_store.text_runs[run_index];
                 let subpx_dir = run.used_font.get_subpx_dir();
 
                 // The GPU cache data is stored in the template and reused across
                 // frames and display lists.
                 let prim_data = &ctx.resources.text_run_data_store[data_handle];
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
-                let alpha_batch_list = &mut self.batch_list.alpha_batch_list;
+                let alpha_batch_list = &mut self.batch_lists.last_mut().unwrap().alpha_batch_list;
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_instance.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
@@ -860,17 +924,17 @@ impl AlphaBatchBuilder {
                     segment_index: INVALID_SEGMENT_INDEX,
                     edge_flags: EdgeAaSegmentMask::all(),
                     clip_task_address,
                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                     prim_header_index,
                     user_data: segment_user_data,
                 });
 
-                self.batch_list.push_single_instance(
+                self.current_batch_list().push_single_instance(
                     batch_key,
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
@@ -954,17 +1018,17 @@ impl AlphaBatchBuilder {
                             );
 
                             let instance = SplitCompositeInstance::new(
                                 prim_header_index,
                                 child.gpu_address,
                                 z_id,
                             );
 
-                            self.batch_list.push_single_instance(
+                            self.current_batch_list().push_single_instance(
                                 key,
                                 &prim_instance.bounding_rect.as_ref().expect("bug"),
                                 z_id,
                                 PrimitiveInstanceData::from(instance),
                             );
                         }
                     }
                     // Ignore the 3D pictures that are not in the root of preserve-3D
@@ -973,115 +1037,134 @@ impl AlphaBatchBuilder {
                     // Proceed for non-3D pictures.
                     Picture3DContext::Out => ()
                 }
 
                 match picture.raster_config {
                     Some(ref raster_config) => {
                         match raster_config.composite_mode {
                             PictureCompositeMode::TileCache { .. } => {
-
-                                // Step through each tile in the cache, and draw it with an image
-                                // brush primitive if visible.
-
-                                let kind = BatchKind::Brush(
-                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                );
-
-                                let tile_cache = picture.tile_cache.as_ref().unwrap();
-
-                                // If there is a dirty rect for the tile cache, recurse into the
-                                // main picture primitive list, and draw them first.
-                                if let Some(_) = tile_cache.dirty_region {
-                                    self.add_pic_to_batch(
-                                        picture,
-                                        task_id,
-                                        ctx,
-                                        gpu_cache,
-                                        render_tasks,
-                                        deferred_resolves,
-                                        prim_headers,
-                                        transforms,
-                                        root_spatial_node_index,
-                                        z_generator,
-                                    );
-                                }
-
-                                // After drawing the dirty rect, now draw any of the valid tiles that
-                                // will make up the rest of the scene.
-
-                                // Generate a new z id for the tiles, that will place them *after*
-                                // any opaque overdraw from the dirty rect above.
-                                // TODO(gw): We should remove this hack, and also remove some
-                                //           (potential opaque) overdraw by adding support for
-                                //           setting a scissor rect for the dirty rect above.
-                                let tile_zid = z_generator.next();
+                                // Construct a local clip rect that ensures we only draw pixels where
+                                // the local bounds of the picture extend to within the edge tiles.
+                                let local_clip_rect = prim_instance
+                                    .combined_local_clip_rect
+                                    .intersection(&picture.local_rect)
+                                    .and_then(|rect| {
+                                        rect.intersection(&picture.local_clip_rect)
+                                    });
 
-                                for tile_index in &tile_cache.tiles_to_draw {
-                                    let tile = &tile_cache.tiles[tile_index.0];
-
-                                    // Get the local rect of the tile.
-                                    let tile_rect = tile.local_rect;
-
-                                    // Construct a local clip rect that ensures we only draw pixels where
-                                    // the local bounds of the picture extend to within the edge tiles.
-                                    let local_clip_rect = prim_instance
-                                        .combined_local_clip_rect
-                                        .intersection(&picture.local_rect)
-                                        .expect("bug: invalid picture local rect");
+                                if let Some(local_clip_rect) = local_clip_rect {
+                                    // Step through each tile in the cache, and draw it with an image
+                                    // brush primitive if visible.
 
-                                    let prim_header = PrimitiveHeader {
-                                        local_rect: tile_rect,
-                                        local_clip_rect,
-                                        task_address,
-                                        specific_prim_address: prim_cache_address,
-                                        clip_task_address,
-                                        transform_id,
-                                    };
-
-                                    let prim_header_index = prim_headers.push(&prim_header, tile_zid, [
-                                        ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
-                                        RasterizationSpace::Local as i32,
-                                        get_shader_opacity(1.0),
-                                    ]);
-
-                                    let cache_item = ctx
-                                        .resource_cache
-                                        .get_texture_cache_item(&tile.handle);
-
-                                    let key = BatchKey::new(
-                                        kind,
-                                        BlendMode::None,
-                                        BatchTextures::color(cache_item.texture_id),
+                                    let kind = BatchKind::Brush(
+                                        BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                     );
 
-                                    let uv_rect_address = gpu_cache
-                                        .get_address(&cache_item.uv_rect_handle)
-                                        .as_int();
+                                    let tile_cache = picture.tile_cache.as_ref().unwrap();
+
+                                    for tile_index in &tile_cache.tiles_to_draw {
+                                        let tile = &tile_cache.tiles[tile_index.0];
+
+                                        // Get the local rect of the tile.
+                                        let tile_rect = tile.local_rect;
+
+                                        let prim_header = PrimitiveHeader {
+                                            local_rect: tile_rect,
+                                            local_clip_rect,
+                                            task_address,
+                                            specific_prim_address: prim_cache_address,
+                                            clip_task_address,
+                                            transform_id,
+                                        };
+
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                            ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
+                                            RasterizationSpace::Local as i32,
+                                            get_shader_opacity(1.0),
+                                        ]);
+
+                                        let cache_item = ctx
+                                            .resource_cache
+                                            .get_texture_cache_item(&tile.handle);
+
+                                        let key = BatchKey::new(
+                                            kind,
+                                            BlendMode::None,
+                                            BatchTextures::color(cache_item.texture_id),
+                                        );
+
+                                        let uv_rect_address = gpu_cache
+                                            .get_address(&cache_item.uv_rect_handle)
+                                            .as_int();
+
+                                        let instance = BrushInstance {
+                                            prim_header_index,
+                                            clip_task_address,
+                                            segment_index: INVALID_SEGMENT_INDEX,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags: BrushFlags::empty(),
+                                            user_data: uv_rect_address,
+                                        };
 
-                                    let instance = BrushInstance {
-                                        prim_header_index,
-                                        clip_task_address,
-                                        segment_index: INVALID_SEGMENT_INDEX,
-                                        edge_flags: EdgeAaSegmentMask::empty(),
-                                        brush_flags: BrushFlags::empty(),
-                                        user_data: uv_rect_address,
-                                    };
+                                        // Instead of retrieving the batch once and adding each tile instance,
+                                        // use this API to get an appropriate batch for each tile, since
+                                        // the batch textures may be different. The batch list internally
+                                        // caches the current batch if the key hasn't changed.
+                                        let batch = self.current_batch_list().set_params_and_get_batch(
+                                            key,
+                                            bounding_rect,
+                                            z_id,
+                                        );
+
+                                        batch.push(PrimitiveInstanceData::from(instance));
+                                    }
+
+                                    // If there is a dirty rect for the tile cache, recurse into the
+                                    // main picture primitive list, and draw them first.
+                                    if let Some(ref dirty_region) = tile_cache.dirty_region {
+                                        let mut tile_blits = Vec::new();
+
+                                        let (target_rect, _) = render_tasks[task_id].get_target_rect();
 
-                                    // Instead of retrieving the batch once and adding each tile instance,
-                                    // use this API to get an appropriate batch for each tile, since
-                                    // the batch textures may be different. The batch list internally
-                                    // caches the current batch if the key hasn't changed.
-                                    let batch = self.batch_list.set_params_and_get_batch(
-                                        key,
-                                        bounding_rect,
-                                        tile_zid,
-                                    );
+                                        for blit in &tile_cache.pending_blits {
+                                            tile_blits.push(TileBlit {
+                                                dest_offset: blit.dest_offset,
+                                                size: blit.size,
+                                                target: blit.target.clone(),
+                                                src_offset: DeviceIntPoint::new(
+                                                    blit.src_offset.x + target_rect.origin.x,
+                                                    blit.src_offset.y + target_rect.origin.y,
+                                                ),
+                                            })
+                                        }
+
+                                        self.push_new_batch_list(
+                                            Some(dirty_region.dirty_device_rect),
+                                            tile_blits,
+                                        );
 
-                                    batch.push(PrimitiveInstanceData::from(instance));
+                                        self.add_pic_to_batch(
+                                            picture,
+                                            task_id,
+                                            ctx,
+                                            gpu_cache,
+                                            render_tasks,
+                                            deferred_resolves,
+                                            prim_headers,
+                                            transforms,
+                                            root_spatial_node_index,
+                                            z_generator,
+                                        );
+
+                                        self.push_new_batch_list(
+                                            None,
+                                            Vec::new(),
+                                        );
+                                    }
                                 }
                             }
                             PictureCompositeMode::Filter(filter) => {
                                 let surface = ctx.surfaces[raster_config.surface_index.0]
                                     .surface
                                     .as_ref()
                                     .expect("bug: surface must be allocated by now");
                                 assert!(filter.is_visible());
@@ -1111,17 +1194,17 @@ impl AlphaBatchBuilder {
                                             prim_header_index,
                                             segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             clip_task_address,
                                             user_data: uv_rect_address.as_int(),
                                         };
 
-                                        self.batch_list.push_single_instance(
+                                        self.current_batch_list().push_single_instance(
                                             key,
                                             bounding_rect,
                                             z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
                                     FilterOp::DropShadow(offset, ..) => {
                                         // Draw an instance of the shadow first, following by the content.
@@ -1199,24 +1282,24 @@ impl AlphaBatchBuilder {
                                             prim_header_index: content_prim_header_index,
                                             clip_task_address,
                                             segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: content_uv_rect_address,
                                         };
 
-                                        self.batch_list.push_single_instance(
+                                        self.current_batch_list().push_single_instance(
                                             shadow_key,
                                             bounding_rect,
                                             z_id_shadow,
                                             PrimitiveInstanceData::from(shadow_instance),
                                         );
 
-                                        self.batch_list.push_single_instance(
+                                        self.current_batch_list().push_single_instance(
                                             content_key,
                                             bounding_rect,
                                             z_id_content,
                                             PrimitiveInstanceData::from(content_instance),
                                         );
                                     }
                                     _ => {
                                         let filter_mode = match filter {
@@ -1284,17 +1367,17 @@ impl AlphaBatchBuilder {
                                             prim_header_index,
                                             clip_task_address,
                                             segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: 0,
                                         };
 
-                                        self.batch_list.push_single_instance(
+                                        self.current_batch_list().push_single_instance(
                                             key,
                                             bounding_rect,
                                             z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
                                 }
                             }
@@ -1329,17 +1412,17 @@ impl AlphaBatchBuilder {
                                     prim_header_index,
                                     clip_task_address,
                                     segment_index: INVALID_SEGMENT_INDEX,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::empty(),
                                     user_data: 0,
                                 };
 
-                                self.batch_list.push_single_instance(
+                                self.current_batch_list().push_single_instance(
                                     key,
                                     bounding_rect,
                                     z_id,
                                     PrimitiveInstanceData::from(instance),
                                 );
                             }
                             PictureCompositeMode::Blit => {
                                 let surface = ctx.surfaces[raster_config.surface_index.0]
@@ -1369,17 +1452,17 @@ impl AlphaBatchBuilder {
                                     prim_header_index,
                                     clip_task_address,
                                     segment_index: INVALID_SEGMENT_INDEX,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::empty(),
                                     user_data: uv_rect_address,
                                 };
 
-                                self.batch_list.push_single_instance(
+                                self.current_batch_list().push_single_instance(
                                     key,
                                     bounding_rect,
                                     z_id,
                                     PrimitiveInstanceData::from(instance),
                                 );
                             }
                         }
                     }
@@ -1849,17 +1932,17 @@ impl AlphaBatchBuilder {
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
                         BrushBatchKind::LinearGradient,
                         specified_blend_mode,
                         bounding_rect,
                         clip_task_address,
                         gpu_cache,
-                        &mut self.batch_list,
+                        self.current_batch_list(),
                         &prim_header,
                         prim_headers,
                         z_id,
                     );
                 }
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.resources.radial_grad_data_store[data_handle];
@@ -1930,17 +2013,17 @@ impl AlphaBatchBuilder {
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
                         BrushBatchKind::RadialGradient,
                         specified_blend_mode,
                         bounding_rect,
                         clip_task_address,
                         gpu_cache,
-                        &mut self.batch_list,
+                        self.current_batch_list(),
                         &prim_header,
                         prim_headers,
                         z_id,
                     );
                 }
             }
         }
     }
@@ -1966,17 +2049,17 @@ impl AlphaBatchBuilder {
             user_data: uv_rect_address.as_int(),
         };
 
         let batch_key = BatchKey {
             blend_mode,
             kind: BatchKind::Brush(batch_kind),
             textures,
         };
-        self.batch_list.push_single_instance(
+        self.current_batch_list().push_single_instance(
             batch_key,
             bounding_rect,
             z_id,
             PrimitiveInstanceData::from(base_instance),
         );
     }
 
     /// Add a single segment instance to a batch.
@@ -2026,17 +2109,17 @@ impl AlphaBatchBuilder {
         });
 
         let batch_key = BatchKey {
             blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
             kind: BatchKind::Brush(batch_kind),
             textures: segment_data.textures,
         };
 
-        self.batch_list.push_single_instance(
+        self.current_batch_list().push_single_instance(
             batch_key,
             bounding_rect,
             z_id,
             instance,
         );
     }
 
     /// Add any segment(s) from a brush to batches.
@@ -2118,17 +2201,17 @@ impl AlphaBatchBuilder {
                 let instance = PrimitiveInstanceData::from(BrushInstance {
                     segment_index: INVALID_SEGMENT_INDEX,
                     edge_flags: EdgeAaSegmentMask::all(),
                     clip_task_address,
                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                     prim_header_index,
                     user_data: segment_data.user_data,
                 });
-                self.batch_list.push_single_instance(
+                self.current_batch_list().push_single_instance(
                     batch_key,
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
             (None, SegmentDataKind::Instanced(..)) => {
                 // We should never hit the case where there are no segments,
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -334,31 +334,25 @@ impl FrameBuilder {
             pic_state,
             &mut frame_state,
         );
 
         let child_tasks = frame_state
             .surfaces[ROOT_SURFACE_INDEX.0]
             .take_render_tasks();
 
-        let tile_blits = mem::replace(
-            &mut frame_state.surfaces[ROOT_SURFACE_INDEX.0].tile_blits,
-            Vec::new(),
-        );
-
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
             self.root_pic_index,
             DeviceIntPoint::zero(),
             child_tasks,
             UvRectKind::Rect,
             root_spatial_node_index,
             None,
-            tile_blits,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         frame_state
             .surfaces
             .first_mut()
             .unwrap()
             .surface = Some(PictureSurface::RenderTask(render_task_id));
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,30 +1,30 @@
 /* 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, WorldPoint};
 use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
-use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize};
+use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, LayoutSize};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D};
 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 intern::ItemUid;
 use internal_types::{FastHashMap, PlaneSplitter};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
 use plane_split::{Clipper, Polygon, Splitter};
 use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
-use prim_store::{get_raster_rects, CoordinateSpaceMapping};
+use prim_store::{get_raster_rects, CoordinateSpaceMapping, VectorKey};
 use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex};
 use print_tree::PrintTreePrinter;
 use render_backend::FrameResources;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::DocumentResources;
@@ -161,58 +161,67 @@ pub struct TileDescriptor {
     /// to uniquely describe the content of the primitive template, while
     /// the other parameters describe the clip chain and instance params.
     prims: ComparableVec<PrimitiveDescriptor>,
 
     /// List of clip node unique identifiers. The uid is guaranteed
     /// to uniquely describe the content of the clip node.
     clip_uids: ComparableVec<ItemUid>,
 
+    /// List of tile relative offsets of the clip node origins. This
+    /// ensures that if a clip node is supplied but has a different
+    /// transform between frames that the tile is invalidated.
+    clip_vertices: ComparableVec<VectorKey>,
+
     /// List of image keys that this tile depends on.
     image_keys: ComparableVec<ImageKey>,
 
     /// The set of opacity bindings that this tile depends on.
     // TODO(gw): Ugh, get rid of all opacity binding support!
     opacity_bindings: ComparableVec<PropertyBindingId>,
 }
 
 impl TileDescriptor {
     fn new() -> Self {
         TileDescriptor {
             prims: ComparableVec::new(),
             clip_uids: ComparableVec::new(),
+            clip_vertices: ComparableVec::new(),
             opacity_bindings: ComparableVec::new(),
             image_keys: ComparableVec::new(),
         }
     }
 
     /// Clear the dependency information for a tile, when the dependencies
     /// are being rebuilt.
     fn clear(&mut self) {
         self.prims.reset();
         self.clip_uids.reset();
+        self.clip_vertices.reset();
         self.opacity_bindings.reset();
         self.image_keys.reset();
     }
 
     /// Check if the dependencies of this tile are valid.
     fn is_valid(&self) -> bool {
         self.image_keys.is_valid() &&
         self.opacity_bindings.is_valid() &&
         self.clip_uids.is_valid() &&
+        self.clip_vertices.is_valid() &&
         self.prims.is_valid()
     }
 }
 
 /// Represents the dirty region of a tile cache picture.
 /// In future, we will want to support multiple dirty
 /// regions.
 #[derive(Debug)]
 pub struct DirtyRegion {
-    dirty_world_rect: WorldRect,
+    pub dirty_world_rect: WorldRect,
+    pub dirty_device_rect: DeviceIntRect,
 }
 
 /// Represents a cache of tiles that make up a picture primitives.
 pub struct TileCache {
     /// The positioning node for this tile cache.
     spatial_node_index: SpatialNodeIndex,
     /// List of tiles present in this picture (stored as a 2D array)
     pub tiles: Vec<Tile>,
@@ -239,17 +248,17 @@ pub struct TileCache {
     /// Current size of tiles in world units.
     world_tile_size: WorldSize,
     /// Current number of tiles in the allocated grid.
     tile_count: TileSize,
     /// The current scroll offset for this frame builder. Reset when
     /// a new scene arrives.
     scroll_offset: Option<WorldVector2D>,
     /// A list of blits from the framebuffer to be applied during this frame.
-    pending_blits: Vec<TileBlit>,
+    pub pending_blits: Vec<TileBlit>,
     /// Collects the clips that apply to this surface.
     clip_node_collector: ClipNodeCollector,
 }
 
 impl TileCache {
     pub fn new(spatial_node_index: SpatialNodeIndex) -> Self {
         TileCache {
             spatial_node_index,
@@ -609,18 +618,18 @@ impl TileCache {
             return;
         }
 
         // Get the tile coordinates in the picture space.
         let (p0, p1) = self.get_tile_coords_for_rect(&world_rect);
 
         // Build the list of resources that this primitive has dependencies on.
         let mut opacity_bindings: SmallVec<[PropertyBindingId; 4]> = SmallVec::new();
-        let mut clip_chain_spatial_nodes: SmallVec<[SpatialNodeIndex; 8]> = SmallVec::new();
         let mut clip_chain_uids: SmallVec<[ItemUid; 8]> = SmallVec::new();
+        let mut clip_vertices: SmallVec<[WorldPoint; 8]> = SmallVec::new();
         let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
         let mut current_clip_chain_id = prim_instance.clip_chain_id;
 
         // Some primitives can not be cached (e.g. external video images)
         let is_cacheable = prim_instance.is_cacheable(
             &resources,
             resource_cache,
         );
@@ -681,17 +690,35 @@ impl TileCache {
         while current_clip_chain_id != ClipChainId::NONE {
             let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
             // We only care about clip nodes that have transforms that are children
             // of the surface, since clips that are positioned by parents will be
             // handled by the clip collector when these tiles are composited.
             if clip_chain_node.spatial_node_index < self.spatial_node_index {
                 self.clip_node_collector.insert(current_clip_chain_id)
             } else {
-                clip_chain_spatial_nodes.push(clip_chain_node.spatial_node_index);
+                // TODO(gw): Constructing a rect here rather than mapping a point
+                //           is wasteful. We can optimize this by extending the
+                //           SpaceMapper struct to support mapping a point.
+                let local_rect = LayoutRect::new(
+                    clip_chain_node.local_pos,
+                    LayoutSize::zero(),
+                );
+
+                self.map_local_to_world.set_target_spatial_node(
+                    clip_chain_node.spatial_node_index,
+                    clip_scroll_tree,
+                );
+
+                let clip_world_rect = self
+                    .map_local_to_world
+                    .map(&local_rect)
+                    .expect("bug: unable to map clip rect to world");
+
+                clip_vertices.push(clip_world_rect.origin);
                 clip_chain_uids.push(clip_chain_node.handle.uid());
             }
             current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
         // Normalize the tile coordinates before adding to tile dependencies.
         // For each affected tile, mark any of the primitive dependencies.
         for y in p0.y .. p1.y {
@@ -730,33 +757,47 @@ impl TileCache {
                 // Update the tile descriptor, used for tile comparison during scene swaps.
                 tile.descriptor.prims.push(PrimitiveDescriptor {
                     prim_uid: prim_instance.uid(),
                     origin,
                     first_clip: tile.descriptor.clip_uids.len() as u16,
                     clip_count: clip_chain_uids.len() as u16,
                 });
                 tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids);
+
+                // Store tile relative clip vertices.
+                // TODO(gw): We might need to quantize these to avoid
+                //           invalidations due to FP accuracy.
+                for clip_vertex in &clip_vertices {
+                    let clip_vertex = VectorKey {
+                        x: clip_vertex.x - tile.world_rect.origin.x,
+                        y: clip_vertex.y - tile.world_rect.origin.y,
+                    };
+                    tile.descriptor.clip_vertices.push(clip_vertex);
+                }
             }
         }
     }
 
     /// Apply any updates after prim dependency updates. This applies
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         clip_store: &ClipStore,
         frame_context: &FrameBuildingContext,
         resources: &FrameResources,
-    ) {
+    ) -> LayoutRect {
         let mut dirty_world_rect = WorldRect::zero();
 
+        self.dirty_region = None;
+        self.pending_blits.clear();
+
         let descriptor = ImageDescriptor::new(
             TILE_SIZE_WIDTH,
             TILE_SIZE_HEIGHT,
             ImageFormat::BGRA8,
             true,
             false,
         );
 
@@ -765,22 +806,33 @@ impl TileCache {
         let clip_rect = match self
             .clip_node_collector
             .get_world_clip_rect(
                 clip_store,
                 &resources.clip_data_store,
                 frame_context.clip_scroll_tree,
             ) {
             Some(clip_rect) => clip_rect,
-            None => return,
+            None => return LayoutRect::zero(),
         };
 
+        let map_surface_to_world: SpaceMapper<LayoutPixel, WorldPixel> = SpaceMapper::new_with_target(
+            ROOT_SPATIAL_NODE_INDEX,
+            self.spatial_node_index,
+            frame_context.screen_world_rect,
+            frame_context.clip_scroll_tree,
+        );
+
+        let local_clip_rect = map_surface_to_world
+            .unmap(&clip_rect)
+            .expect("bug: unable to map local clip rect");
+
         let clip_rect = match clip_rect.intersection(&frame_context.screen_world_rect) {
             Some(clip_rect) => clip_rect,
-            None => return,
+            None => return LayoutRect::zero(),
         };
 
         let clipped = (clip_rect * frame_context.device_pixel_scale).round().to_i32();
 
         // Step through each tile and invalidate if the dependencies have changed.
         for (i, tile) in self.tiles.iter_mut().enumerate() {
             // Check the content of the tile is the same
             tile.is_valid &= tile.descriptor.is_valid();
@@ -867,20 +919,24 @@ impl TileCache {
                 tile.is_valid = true;
             }
         }
 
         // Store the dirty region for drawing the main scene.
         self.dirty_region = if dirty_world_rect.is_empty() {
             None
         } else {
+            let dirty_device_rect = (dirty_world_rect * frame_context.device_pixel_scale).round().to_i32();
             Some(DirtyRegion {
                 dirty_world_rect,
+                dirty_device_rect,
             })
         };
+
+        local_clip_rect
     }
 }
 
 /// State structure that is used during the tile cache update picture traversal.
 pub struct TileCacheUpdateState {
     pub tile_cache: Option<TileCache>,
 }
 
@@ -982,18 +1038,16 @@ pub struct SurfaceInfo {
     pub raster_spatial_node_index: SpatialNodeIndex,
     pub surface_spatial_node_index: SpatialNodeIndex,
     /// This is set when the render task is created.
     pub surface: Option<PictureSurface>,
     /// A list of render tasks that are dependencies of this surface.
     pub tasks: Vec<RenderTaskId>,
     /// How much the local surface rect should be inflated (for blur radii).
     pub inflation_factor: f32,
-    /// A list of tile blits to be done after drawing this surface.
-    pub tile_blits: Vec<TileBlit>,
 }
 
 impl SurfaceInfo {
     pub fn new(
         surface_spatial_node_index: SpatialNodeIndex,
         raster_spatial_node_index: SpatialNodeIndex,
         inflation_factor: f32,
         world_rect: WorldRect,
@@ -1018,17 +1072,16 @@ impl SurfaceInfo {
         SurfaceInfo {
             rect: PictureRect::zero(),
             map_local_to_surface,
             surface: None,
             raster_spatial_node_index,
             surface_spatial_node_index,
             tasks: Vec::new(),
             inflation_factor,
-            tile_blits: Vec::new(),
         }
     }
 
     /// Take the set of child render tasks for this surface. This is
     /// used when constructing the render task tree.
     pub fn take_render_tasks(&mut self) -> Vec<RenderTaskId> {
         mem::replace(&mut self.tasks, Vec::new())
     }
@@ -1528,17 +1581,22 @@ impl PicturePrimitive {
 
                 (surface.raster_spatial_node_index, self.spatial_node_index, raster_config.surface_index)
             }
             None => {
                 (raster_spatial_node_index, surface_spatial_node_index, surface_index)
             }
         };
 
-        if self.raster_config.is_some() {
+        // Don't bother pushing a clip node collector for a tile cache, it's not
+        // actually an off-screen surface.
+        // TODO(gw): The way this is handled via the picture composite mode is not
+        //           ideal - we should fix this up and then be able to remove hacks
+        //           like this.
+        if self.raster_config.is_some() && self.tile_cache.is_none() {
             frame_state.clip_store
                 .push_surface(surface_spatial_node_index);
         }
 
         let map_pic_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface_spatial_node_index,
             dirty_world_rect,
@@ -1622,16 +1680,23 @@ impl PicturePrimitive {
         prim_list: PrimitiveList,
         context: PictureContext,
         state: PictureState,
         frame_state: &mut FrameBuildingState,
     ) -> Option<ClipNodeCollector> {
         self.prim_list = prim_list;
         self.state = Some((state, context));
 
+        // Don't bother popping a clip node collector for a tile cache, it's not
+        // actually an off-screen surface (see comment when pushing surface for
+        // more information).
+        if self.tile_cache.is_some() {
+            return None;
+        }
+
         self.raster_config.as_ref().map(|_| {
             frame_state.clip_store.pop_surface()
         })
     }
 
     pub fn take_state_and_context(&mut self) -> (PictureState, PictureContext) {
         self.state.take().expect("bug: no state present!")
     }
@@ -2069,23 +2134,20 @@ impl PicturePrimitive {
         //           to store the same type of data. The exception is the filter
         //           with a ColorMatrix, which stores the color matrix here. It's
         //           probably worth tidying this code up to be a bit more consistent.
         //           Perhaps store the color matrix after the common data, even though
         //           it's not used by that shader.
 
         let surface = match raster_config.composite_mode {
             PictureCompositeMode::TileCache { .. } => {
-                let tile_cache = self.tile_cache.as_mut().unwrap();
-
                 // For a picture surface, just push any child tasks and tile
                 // blits up to the parent surface.
                 let surface = &mut surfaces[surface_index.0];
                 surface.tasks.extend(child_tasks);
-                surface.tile_blits.extend(tile_cache.pending_blits.drain(..));
 
                 return true;
             }
             PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
                 let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                 let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
                 // We need to choose whether to cache this picture, or draw
@@ -2140,17 +2202,16 @@ impl PicturePrimitive {
                         RenderTaskLocation::Dynamic(None, device_rect.size),
                         unclipped.size,
                         pic_index,
                         device_rect.origin,
                         child_tasks,
                         uv_rect_kind,
                         pic_context.raster_spatial_node_index,
                         None,
-                        Vec::new(),
                     );
 
                     let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                     let blur_render_task = RenderTask::new_blur(
                         blur_std_deviation,
                         picture_task_id,
                         frame_state.render_tasks,
@@ -2198,17 +2259,16 @@ impl PicturePrimitive {
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 unclipped.size,
                                 pic_index,
                                 device_rect.origin,
                                 child_tasks,
                                 uv_rect_kind,
                                 pic_context.raster_spatial_node_index,
                                 None,
-                                Vec::new(),
                             );
 
                             let picture_task_id = render_tasks.add(picture_task);
 
                             let blur_render_task = RenderTask::new_blur(
                                 blur_std_deviation,
                                 picture_task_id,
                                 render_tasks,
@@ -2256,17 +2316,16 @@ impl PicturePrimitive {
                     RenderTaskLocation::Dynamic(None, device_rect.size),
                     unclipped.size,
                     pic_index,
                     device_rect.origin,
                     child_tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                     None,
-                    Vec::new(),
                 );
                 picture_task.mark_for_saving();
 
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                 let blur_render_task = RenderTask::new_blur(
                     blur_std_deviation.round(),
                     picture_task_id,
@@ -2323,17 +2382,16 @@ impl PicturePrimitive {
                     RenderTaskLocation::Dynamic(None, clipped.size),
                     unclipped.size,
                     pic_index,
                     clipped.origin,
                     child_tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                     None,
-                    Vec::new(),
                 );
 
                 let readback_task_id = frame_state.render_tasks.add(
                     RenderTask::new_readback(clipped)
                 );
 
                 self.secondary_render_task_id = Some(readback_task_id);
                 surfaces[surface_index.0].tasks.push(readback_task_id);
@@ -2363,17 +2421,16 @@ impl PicturePrimitive {
                     RenderTaskLocation::Dynamic(None, clipped.size),
                     unclipped.size,
                     pic_index,
                     clipped.origin,
                     child_tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                     None,
-                    Vec::new(),
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
             PictureCompositeMode::Blit => {
                 // The SplitComposite shader used for 3d contexts doesn't snap
@@ -2395,17 +2452,16 @@ impl PicturePrimitive {
                     RenderTaskLocation::Dynamic(None, clipped.size),
                     unclipped.size,
                     pic_index,
                     clipped.origin,
                     child_tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                     None,
-                    Vec::new(),
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
         };
 
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -500,18 +500,18 @@ impl SizeKey {
     }
 }
 
 /// A hashable vec for using as a key during primitive interning.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Copy, Debug, Clone, PartialEq)]
 pub struct VectorKey {
-    x: f32,
-    y: f32,
+    pub x: f32,
+    pub y: f32,
 }
 
 impl Eq for VectorKey {}
 
 impl hash::Hash for VectorKey {
     fn hash<H: hash::Hasher>(&self, state: &mut H) {
         self.x.to_bits().hash(state);
         self.y.to_bits().hash(state);
@@ -1726,17 +1726,17 @@ impl PrimitiveStore {
             );
         }
 
         let pic = &mut self.pictures[pic_index.0];
         if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = pic.raster_config {
             let mut tile_cache = state.tile_cache.take().unwrap();
 
             // Build the dirty region(s) for this tile cache.
-            tile_cache.post_update(
+            pic.local_clip_rect = tile_cache.post_update(
                 resource_cache,
                 gpu_cache,
                 clip_store,
                 frame_context,
                 resources,
             );
 
             pic.tile_cache = Some(tile_cache);
@@ -3243,16 +3243,21 @@ pub fn get_raster_rects(
 
     let clipped_raster_rect = clipped_raster_rect.intersection(&unclipped_raster_rect)?;
 
     let clipped = raster_rect_to_device_pixels(
         clipped_raster_rect,
         device_pixel_scale,
     );
 
+    // Ensure that we won't try to allocate a zero-sized clip render task.
+    if clipped.is_empty() {
+        return None;
+    }
+
     Some((clipped.to_i32(), unclipped))
 }
 
 /// Get the inline (horizontal) and block (vertical) sizes
 /// for a given line decoration.
 pub fn get_line_decoration_sizes(
     rect_size: &LayoutSize,
     orientation: LineOrientation,
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -265,17 +265,16 @@ pub struct TileBlit {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureTask {
     pub pic_index: PictureIndex,
     pub can_merge: bool,
     pub content_origin: DeviceIntPoint,
     pub uv_rect_handle: GpuCacheHandle,
     pub root_spatial_node_index: SpatialNodeIndex,
     uv_rect_kind: UvRectKind,
-    pub blits: Vec<TileBlit>,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
     pub target_kind: RenderTargetKind,
@@ -429,17 +428,16 @@ impl RenderTask {
         location: RenderTaskLocation,
         unclipped_size: DeviceSize,
         pic_index: PictureIndex,
         content_origin: DeviceIntPoint,
         children: Vec<RenderTaskId>,
         uv_rect_kind: UvRectKind,
         root_spatial_node_index: SpatialNodeIndex,
         clear_color: Option<ColorF>,
-        blits: Vec<TileBlit>,
     ) -> Self {
         let size = match location {
             RenderTaskLocation::Dynamic(_, size) => size,
             RenderTaskLocation::Fixed(rect) => rect.size,
             RenderTaskLocation::TextureCache { rect, .. } => rect.size,
         };
 
         render_task_sanity_check(&size);
@@ -457,17 +455,16 @@ impl RenderTask {
             children,
             kind: RenderTaskKind::Picture(PictureTask {
                 pic_index,
                 content_origin,
                 can_merge,
                 uv_rect_handle: GpuCacheHandle::new(),
                 uv_rect_kind,
                 root_spatial_node_index,
-                blits,
             }),
             clear_mode,
             saved_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
         RenderTask::with_dynamic_location(
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -3275,42 +3275,40 @@ impl Renderer {
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
         }
 
         self.handle_scaling(&target.scalings, TextureSource::PrevPassColor, projection, stats);
 
-        //TODO: record the pixel count for cached primitives
-
-        if target.needs_depth() {
-            let _gl = self.gpu_profile.start_marker("opaque batches");
-            let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
-            self.set_blend(false, framebuffer_kind);
-            //Note: depth equality is needed for split planes
-            self.device.set_depth_func(DepthFunction::LessEqual);
-            self.device.enable_depth();
-            self.device.enable_depth_write();
-
-            for alpha_batch_container in &target.alpha_batch_containers {
-                if let Some(target_rect) = alpha_batch_container.target_rect {
-                    // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
-                    let rect = if draw_target.is_default() {
-                        let mut rect = target_rect
-                            .intersection(&framebuffer_target_rect.to_i32())
-                            .unwrap_or(DeviceIntRect::zero());
-                        rect.origin.y = draw_target.dimensions().height as i32 - rect.origin.y - rect.size.height;
-                        rect
-                    } else {
-                        target_rect
-                    };
-                    self.device.enable_scissor();
-                    self.device.set_scissor_rect(rect);
-                }
+        for alpha_batch_container in &target.alpha_batch_containers {
+            if let Some(scissor_rect) = alpha_batch_container.scissor_rect {
+                // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
+                let rect = if draw_target.is_default() {
+                    let mut rect = scissor_rect
+                        .intersection(&framebuffer_target_rect.to_i32())
+                        .unwrap_or(DeviceIntRect::zero());
+                    rect.origin.y = draw_target.dimensions().height as i32 - rect.origin.y - rect.size.height;
+                    rect
+                } else {
+                    scissor_rect
+                };
+                self.device.enable_scissor();
+                self.device.set_scissor_rect(rect);
+            }
+
+            if !alpha_batch_container.opaque_batches.is_empty() {
+                let _gl = self.gpu_profile.start_marker("opaque batches");
+                let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
+                self.set_blend(false, framebuffer_kind);
+                //Note: depth equality is needed for split planes
+                self.device.set_depth_func(DepthFunction::LessEqual);
+                self.device.enable_depth();
+                self.device.enable_depth_write();
 
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
                 {
@@ -3325,143 +3323,171 @@ impl Renderer {
                     self.draw_instanced_batch(
                         &batch.instances,
                         VertexArrayKind::Primitive,
                         &batch.key.textures,
                         stats
                     );
                 }
 
-                if alpha_batch_container.target_rect.is_some() {
-                    self.device.disable_scissor();
-                }
+                self.device.disable_depth_write();
+                self.gpu_profile.finish_sampler(opaque_sampler);
             }
 
-            self.device.disable_depth_write();
-            self.gpu_profile.finish_sampler(opaque_sampler);
-        }
-
-        let _gl = self.gpu_profile.start_marker("alpha batches");
-        let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
-        self.set_blend(true, framebuffer_kind);
-        let mut prev_blend_mode = BlendMode::None;
-
-        for alpha_batch_container in &target.alpha_batch_containers {
-            if let Some(target_rect) = alpha_batch_container.target_rect {
-                // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
-                let rect = if draw_target.is_default() {
-                    let mut rect = target_rect
-                        .intersection(&framebuffer_target_rect.to_i32())
-                        .unwrap_or(DeviceIntRect::zero());
-                    rect.origin.y = draw_target.dimensions().height as i32 - rect.origin.y - rect.size.height;
-                    rect
-                } else {
-                    target_rect
-                };
-                self.device.enable_scissor();
-                self.device.set_scissor_rect(rect);
-            }
-
-            for batch in &alpha_batch_container.alpha_batches {
-                self.shaders.borrow_mut()
-                    .get(&batch.key, self.debug_flags)
-                    .bind(
-                        &mut self.device, projection,
-                        &mut self.renderer_errors,
+            if !alpha_batch_container.alpha_batches.is_empty() {
+                let _gl = self.gpu_profile.start_marker("alpha batches");
+                let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
+                self.set_blend(true, framebuffer_kind);
+                let mut prev_blend_mode = BlendMode::None;
+
+                for batch in &alpha_batch_container.alpha_batches {
+                    self.shaders.borrow_mut()
+                        .get(&batch.key, self.debug_flags)
+                        .bind(
+                            &mut self.device, projection,
+                            &mut self.renderer_errors,
+                        );
+
+                    if batch.key.blend_mode != prev_blend_mode {
+                        match batch.key.blend_mode {
+                            _ if self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) &&
+                                    framebuffer_kind == FramebufferKind::Main => {
+                                self.device.set_blend_mode_show_overdraw();
+                            }
+                            BlendMode::None => {
+                                unreachable!("bug: opaque blend in alpha pass");
+                            }
+                            BlendMode::Alpha => {
+                                self.device.set_blend_mode_alpha();
+                            }
+                            BlendMode::PremultipliedAlpha => {
+                                self.device.set_blend_mode_premultiplied_alpha();
+                            }
+                            BlendMode::PremultipliedDestOut => {
+                                self.device.set_blend_mode_premultiplied_dest_out();
+                            }
+                            BlendMode::SubpixelDualSource => {
+                                self.device.set_blend_mode_subpixel_dual_source();
+                            }
+                            BlendMode::SubpixelConstantTextColor(color) => {
+                                self.device.set_blend_mode_subpixel_constant_text_color(color);
+                            }
+                            BlendMode::SubpixelWithBgColor => {
+                                // Using the three pass "component alpha with font smoothing
+                                // background color" rendering technique:
+                                //
+                                // /webrender/doc/text-rendering.md
+                                //
+                                self.device.set_blend_mode_subpixel_with_bg_color_pass0();
+                                self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass0 as _);
+                            }
+                        }
+                        prev_blend_mode = batch.key.blend_mode;
+                    }
+
+                    // Handle special case readback for composites.
+                    if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind {
+                        // composites can't be grouped together because
+                        // they may overlap and affect each other.
+                        debug_assert_eq!(batch.instances.len(), 1);
+                        self.handle_readback_composite(
+                            draw_target,
+                            alpha_batch_container.scissor_rect,
+                            &render_tasks[source_id],
+                            &render_tasks[task_id],
+                            &render_tasks[backdrop_id],
+                        );
+                    }
+
+                    let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
+                    self.draw_instanced_batch(
+                        &batch.instances,
+                        VertexArrayKind::Primitive,
+                        &batch.key.textures,
+                        stats
                     );
 
-                if batch.key.blend_mode != prev_blend_mode {
-                    match batch.key.blend_mode {
-                        _ if self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) &&
-                                framebuffer_kind == FramebufferKind::Main => {
-                            self.device.set_blend_mode_show_overdraw();
-                        }
-                        BlendMode::None => {
-                            unreachable!("bug: opaque blend in alpha pass");
-                        }
-                        BlendMode::Alpha => {
-                            self.device.set_blend_mode_alpha();
-                        }
-                        BlendMode::PremultipliedAlpha => {
-                            self.device.set_blend_mode_premultiplied_alpha();
-                        }
-                        BlendMode::PremultipliedDestOut => {
-                            self.device.set_blend_mode_premultiplied_dest_out();
-                        }
-                        BlendMode::SubpixelDualSource => {
-                            self.device.set_blend_mode_subpixel_dual_source();
-                        }
-                        BlendMode::SubpixelConstantTextColor(color) => {
-                            self.device.set_blend_mode_subpixel_constant_text_color(color);
-                        }
-                        BlendMode::SubpixelWithBgColor => {
-                            // Using the three pass "component alpha with font smoothing
-                            // background color" rendering technique:
-                            //
-                            // /webrender/doc/text-rendering.md
-                            //
-                            self.device.set_blend_mode_subpixel_with_bg_color_pass0();
-                            self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass0 as _);
-                        }
+                    if batch.key.blend_mode == BlendMode::SubpixelWithBgColor {
+                        self.set_blend_mode_subpixel_with_bg_color_pass1(framebuffer_kind);
+                        self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass1 as _);
+
+                        // When drawing the 2nd and 3rd passes, we know that the VAO, textures etc
+                        // are all set up from the previous draw_instanced_batch call,
+                        // so just issue a draw call here to avoid re-uploading the
+                        // instances and re-binding textures etc.
+                        self.device
+                            .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
+
+                        self.set_blend_mode_subpixel_with_bg_color_pass2(framebuffer_kind);
+                        self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass2 as _);
+
+                        self.device
+                            .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
+
+                        prev_blend_mode = BlendMode::None;
                     }
-                    prev_blend_mode = batch.key.blend_mode;
                 }
 
-                // Handle special case readback for composites.
-                if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind {
-                    // composites can't be grouped together because
-                    // they may overlap and affect each other.
-                    debug_assert_eq!(batch.instances.len(), 1);
-                    self.handle_readback_composite(
-                        draw_target,
-                        alpha_batch_container.target_rect,
-                        &render_tasks[source_id],
-                        &render_tasks[task_id],
-                        &render_tasks[backdrop_id],
+                self.device.disable_depth();
+                self.set_blend(false, framebuffer_kind);
+                self.gpu_profile.finish_sampler(transparent_sampler);
+            }
+
+            if alpha_batch_container.scissor_rect.is_some() {
+                self.device.disable_scissor();
+            }
+
+            // At the end of rendering a container, blit across any cache tiles
+            // to the texture cache for use on subsequent frames.
+            if !alpha_batch_container.tile_blits.is_empty() {
+                let _timer = self.gpu_profile.start_timer(GPU_TAG_BLIT);
+
+                self.device.bind_read_target(draw_target.into());
+
+                for blit in &alpha_batch_container.tile_blits {
+                    let texture = self.texture_resolver
+                        .resolve(&blit.target.texture_id)
+                        .expect("BUG: invalid target texture");
+
+                    self.device.bind_draw_target(DrawTarget::Texture {
+                        texture,
+                        layer: blit.target.texture_layer as usize,
+                        with_depth: false,
+                    });
+
+                    let mut src_rect = DeviceIntRect::new(
+                        blit.src_offset,
+                        blit.size,
+                    );
+
+                    let target_rect = blit.target.uv_rect.to_i32();
+
+                    let mut dest_rect = DeviceIntRect::new(
+                        DeviceIntPoint::new(
+                            blit.dest_offset.x + target_rect.origin.x,
+                            blit.dest_offset.y + target_rect.origin.y,
+                        ),
+                        blit.size,
+                    );
+
+                    // Modify the src/dest rects since we are blitting from the framebuffer
+                    src_rect.origin.y = draw_target.dimensions().height as i32 - src_rect.size.height - src_rect.origin.y;
+                    dest_rect.origin.y += dest_rect.size.height;
+                    dest_rect.size.height = -dest_rect.size.height;
+
+                    self.device.blit_render_target(
+                        src_rect,
+                        dest_rect,
                     );
                 }
 
-                let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
-                self.draw_instanced_batch(
-                    &batch.instances,
-                    VertexArrayKind::Primitive,
-                    &batch.key.textures,
-                    stats
-                );
-
-                if batch.key.blend_mode == BlendMode::SubpixelWithBgColor {
-                    self.set_blend_mode_subpixel_with_bg_color_pass1(framebuffer_kind);
-                    self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass1 as _);
-
-                    // When drawing the 2nd and 3rd passes, we know that the VAO, textures etc
-                    // are all set up from the previous draw_instanced_batch call,
-                    // so just issue a draw call here to avoid re-uploading the
-                    // instances and re-binding textures etc.
-                    self.device
-                        .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
-
-                    self.set_blend_mode_subpixel_with_bg_color_pass2(framebuffer_kind);
-                    self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass2 as _);
-
-                    self.device
-                        .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32);
-
-                    prev_blend_mode = BlendMode::None;
-                }
-            }
-
-            if alpha_batch_container.target_rect.is_some() {
-                self.device.disable_scissor();
+                self.device.bind_draw_target(draw_target);
             }
         }
 
-        self.device.disable_depth();
-        self.set_blend(false, framebuffer_kind);
-        self.gpu_profile.finish_sampler(transparent_sampler);
-
         // For any registered image outputs on this render target,
         // get the texture from caller and blit it.
         for output in &target.outputs {
             let handler = self.output_image_handler
                 .as_mut()
                 .expect("Found output image, but no handler set!");
             if let Some((texture_id, output_size)) = handler.lock(output.pipeline_id) {
                 let fbo_id = match self.output_targets.entry(texture_id) {
@@ -3487,64 +3513,16 @@ impl Renderer {
                 dest_rect.size.height *= -1;
 
                 self.device.bind_read_target(draw_target.into());
                 self.device.bind_external_draw_target(fbo_id);
                 self.device.blit_render_target(src_rect, dest_rect);
                 handler.unlock(output.pipeline_id);
             }
         }
-
-        // At the end of rendering a target, blit across any cache tiles
-        // to the texture cache for use on subsequent frames.
-        if !target.tile_blits.is_empty() {
-            let _timer = self.gpu_profile.start_timer(GPU_TAG_BLIT);
-
-            self.device.bind_read_target(draw_target.into());
-
-            for blit in &target.tile_blits {
-                let texture = self.texture_resolver
-                    .resolve(&blit.target.texture_id)
-                    .expect("BUG: invalid target texture");
-
-                self.device.bind_draw_target(DrawTarget::Texture {
-                    texture,
-                    layer: blit.target.texture_layer as usize,
-                    with_depth: false,
-                });
-
-                let mut src_rect = DeviceIntRect::new(
-                    blit.src_offset,
-                    blit.size,
-                );
-
-                let target_rect = blit.target.uv_rect.to_i32();
-
-                let mut dest_rect = DeviceIntRect::new(
-                    DeviceIntPoint::new(
-                        blit.dest_offset.x + target_rect.origin.x,
-                        blit.dest_offset.y + target_rect.origin.y,
-                    ),
-                    blit.size,
-                );
-
-                // Modify the src/dest rects since we are blitting from the framebuffer
-                src_rect.origin.y = draw_target.dimensions().height as i32 - src_rect.size.height - src_rect.origin.y;
-                dest_rect.origin.y += dest_rect.size.height;
-                dest_rect.size.height = -dest_rect.size.height;
-
-                self.device.blit_render_target(
-                    src_rect,
-                    dest_rect,
-                );
-            }
-
-            self.device.bind_draw_target(draw_target);
-        }
-
     }
 
     fn draw_alpha_target(
         &mut self,
         draw_target: DrawTarget,
         target: &AlphaRenderTarget,
         projection: &Transform3D<f32>,
         render_tasks: &RenderTaskTree,
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -16,17 +16,17 @@ use gpu_types::{BorderInstance, BlurDire
 use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::SurfaceInfo;
 use prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
 use profiler::FrameProfileCounters;
 use render_backend::{FrameId, FrameResources};
-use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind, TileBlit};
+use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::{ArrayAllocationTracker, FreeRectSlice};
 #[cfg(feature = "pathfinder")]
 use webrender_api::{DevicePixel, FontRenderMode};
 
 const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
@@ -338,17 +338,16 @@ pub struct ColorRenderTarget {
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInstance>,
     pub blits: Vec<BlitJob>,
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
-    pub tile_blits: Vec<TileBlit>,
     pub color_clears: Vec<RenderTaskId>,
     alpha_tasks: Vec<RenderTaskId>,
     screen_size: DeviceIntSize,
     // Track the used rect of the render target, so that
     // we can set a scissor rect and only clear to the
     // used portion of the target as an optimization.
     pub used_rect: DeviceIntRect,
 }
@@ -360,17 +359,16 @@ impl RenderTarget for ColorRenderTarget 
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             scalings: Vec::new(),
             blits: Vec::new(),
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
             color_clears: Vec::new(),
-            tile_blits: Vec::new(),
             screen_size,
             used_rect: DeviceIntRect::zero(),
         }
     }
 
     fn build(
         &mut self,
         ctx: &mut RenderTargetContext,
@@ -398,58 +396,54 @@ impl RenderTarget for ColorRenderTarget 
             }
 
             match task.kind {
                 RenderTaskKind::Picture(ref pic_task) => {
                     let pic = &ctx.prim_store.pictures[pic_task.pic_index.0];
 
                     let (target_rect, _) = task.get_target_rect();
 
+                    let scisor_rect = if pic_task.can_merge {
+                        None
+                    } else {
+                        Some(target_rect)
+                    };
+
                     let mut batch_builder = AlphaBatchBuilder::new(
                         self.screen_size,
-                        target_rect,
-                        pic_task.can_merge,
+                        scisor_rect,
                     );
 
                     batch_builder.add_pic_to_batch(
                         pic,
                         *task_id,
                         ctx,
                         gpu_cache,
                         render_tasks,
                         deferred_resolves,
                         prim_headers,
                         transforms,
                         pic_task.root_spatial_node_index,
                         z_generator,
                     );
 
-                    for blit in &pic_task.blits {
-                        self.tile_blits.push(TileBlit {
-                            dest_offset: blit.dest_offset,
-                            size: blit.size,
-                            target: blit.target.clone(),
-                            src_offset: DeviceIntPoint::new(
-                                blit.src_offset.x + target_rect.origin.x,
-                                blit.src_offset.y + target_rect.origin.y,
-                            ),
-                        })
-                    }
-
-                    if let Some(batch_container) = batch_builder.build(&mut merged_batches) {
-                        self.alpha_batch_containers.push(batch_container);
-                    }
+                    batch_builder.build(
+                        &mut self.alpha_batch_containers,
+                        &mut merged_batches,
+                    );
                 }
                 _ => {
                     unreachable!();
                 }
             }
         }
 
-        self.alpha_batch_containers.push(merged_batches);
+        if !merged_batches.is_empty() {
+            self.alpha_batch_containers.push(merged_batches);
+        }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
@@ -547,17 +541,19 @@ impl RenderTarget for ColorRenderTarget 
                         panic!("BUG: render task blit jobs to render tasks not supported");
                     }
                 }
             }
         }
     }
 
     fn must_be_drawn(&self) -> bool {
-        !self.tile_blits.is_empty()
+        self.alpha_batch_containers.iter().any(|ab| {
+            !ab.tile_blits.is_empty()
+        })
     }
 
     fn needs_depth(&self) -> bool {
         self.alpha_batch_containers.iter().any(|ab| {
             !ab.opaque_batches.is_empty()
         })
     }