Bug 1480433 - Update webrender to commit c939a61b83bcc9dc10742977704793e9a85b3858. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 07 Aug 2018 14:44:43 -0400
changeset 430410 d0eb4a82624b60c8b63b6f3cb7edf3de45ab9df7
parent 430409 f3022e524fc720ecb71b8b3dcda3dfa1ce6bba2a
child 430411 ec949bf303bdf21cc5d8d61fbada8285685c182e
push id106162
push userkgupta@mozilla.com
push dateTue, 07 Aug 2018 20:32:43 +0000
treeherdermozilla-inbound@62e129cc3495 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1480433
milestone63.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 1480433 - Update webrender to commit c939a61b83bcc9dc10742977704793e9a85b3858. r=jrmuizel MozReview-Commit-ID: 8msYpcE1tCx
gfx/webrender/Cargo.toml
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/device/gl.rs
gfx/webrender/src/device/query_gl.rs
gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/util.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/image.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/blob.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -26,17 +26,17 @@ byteorder = "1.0"
 cfg-if = "0.1.2"
 euclid = "0.19"
 fxhash = "0.2.1"
 gleam = "0.6"
 image = { optional = true, version = "0.19" }
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.2"
-plane-split = "0.12"
+plane-split = "0.12.1"
 png = { optional = true, version = "0.12" }
 rayon = "1"
 ron = { optional = true, version = "0.1.7" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 serde_json = { optional = true, version = "1.0" }
 smallvec = "0.6"
 thread_profiler = "0.1.1"
 time = "0.1"
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,38 +1,37 @@
 /* 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, ClipMode, DeviceIntRect, DeviceIntSize};
-use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering, LayoutRect};
-use api::{DeviceIntPoint, YuvColorSpace, YuvFormat};
-use api::{LayoutToWorldTransform, WorldPixel};
+use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize, DeviceIntPoint};
+use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering};
+use api::{YuvColorSpace, YuvFormat, WorldPixel};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
-use euclid::{TypedTransform3D, vec3};
+use euclid::vec3;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
-use plane_split::{BspSplitter, Polygon, Splitter};
+use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
 use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
 use prim_store::{BorderSource};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
-use renderer::{BlendMode, ImageBufferKind};
-use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
+use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
+use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
-use std::{usize, f32, i32};
+use std::{f32, i32};
 use tiling::{RenderTargetContext};
 use util::{TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -671,29 +670,40 @@ impl AlphaBatchBuilder {
                         let picture = &ctx.prim_store.pictures[pic_index.0];
 
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
                             debug_assert!(picture.surface.is_some());
-
-                            let real_xf = &ctx
-                                .transforms
+                            let transform = &ctx.transforms
                                 .get_transform(picture.reference_frame_index);
-                            match make_polygon(
-                                picture.real_local_rect,
-                                &real_xf.m,
-                                prim_index.0,
-                            ) {
-                                Some(polygon) => splitter.add(polygon),
-                                None => {
-                                    // this shouldn't happen, the path will ultimately be
-                                    // turned into `expect` when the splitting code is fixed
+
+                            match transform.transform_kind {
+                                TransformedRectKind::AxisAligned => {
+                                    let polygon = Polygon::from_transformed_rect(
+                                        picture.real_local_rect.cast(),
+                                        transform.m.cast(),
+                                        prim_index.0,
+                                    ).unwrap();
+                                    splitter.add(polygon);
+                                }
+                                TransformedRectKind::Complex => {
+                                    let mut clipper = Clipper::new();
+                                    let bounds = (screen_rect.clipped.to_f32() / ctx.device_pixel_scale).to_f64();
+                                    let matrix = transform.m.cast();
+                                    let results = clipper.clip_transformed(
+                                        Polygon::from_rect(picture.real_local_rect.cast(), prim_index.0),
+                                        &matrix,
+                                        Some(bounds),
+                                    );
+                                    for poly in results {
+                                        splitter.add(poly);
+                                    }
                                 }
                             }
 
                             return;
                         }
 
                         let add_to_parent_pic = match picture.composite_mode {
                             Some(PictureCompositeMode::Filter(filter)) => {
@@ -1704,43 +1714,16 @@ pub fn resolve_image(
             }
         }
         None => {
             CacheItem::invalid()
         }
     }
 }
 
-/// Construct a polygon from stacking context boundaries.
-/// `anchor` here is an index that's going to be preserved in all the
-/// splits of the polygon.
-fn make_polygon(
-    rect: LayoutRect,
-    transform: &LayoutToWorldTransform,
-    anchor: usize,
-) -> Option<Polygon<f64, WorldPixel>> {
-    let mat = TypedTransform3D::row_major(
-        transform.m11 as f64,
-        transform.m12 as f64,
-        transform.m13 as f64,
-        transform.m14 as f64,
-        transform.m21 as f64,
-        transform.m22 as f64,
-        transform.m23 as f64,
-        transform.m24 as f64,
-        transform.m31 as f64,
-        transform.m32 as f64,
-        transform.m33 as f64,
-        transform.m34 as f64,
-        transform.m41 as f64,
-        transform.m42 as f64,
-        transform.m43 as f64,
-        transform.m44 as f64);
-    Polygon::from_transformed_rect(rect.cast(), mat, anchor)
-}
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -4,29 +4,32 @@
 
 use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF};
 use api::{ColorU, DeviceRect, DeviceSize, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
 use api::{DevicePixel, DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
 use app_units::Au;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegment, VECS_PER_SEGMENT};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegment};
 use prim_store::{BorderSource, EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
-use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use util::{lerp, RectHelpers};
 
 // Using 2048 as the maximum radius in device space before which we
 // start stretching is up for debate.
 // the value must be chosen so that the corners will not use an
 // unreasonable amount of memory but should allow crisp corners in the
 // common cases.
 
 /// Maximum resolution in device pixels at which borders are rasterized.
 pub const MAX_BORDER_RESOLUTION: u32 = 2048;
+/// Maximum number of dots or dashes per segment to avoid freezing and filling up
+/// memory with unreasonable inputs. It would be better to address this by not building
+/// a list of per-dot information in the first place.
+pub const MAX_DASH_COUNT: usize = 2048;
 
 trait AuSizeConverter {
     fn to_au(&self) -> LayoutSizeAu;
 }
 
 impl AuSizeConverter for LayoutSize {
     fn to_au(&self) -> LayoutSizeAu {
         LayoutSizeAu::new(
@@ -350,33 +353,30 @@ impl BorderCornerClipSource {
             outer_scale.x * self.radius.width,
             outer_scale.y * self.radius.height,
         );
         let clip_sign = DeviceVector2D::new(
             1.0 - 2.0 * outer_scale.x,
             1.0 - 2.0 * outer_scale.y,
         );
 
-        // No point in pushing more clips as it will blow up the maximum amount of
-        // segments per primitive later down the road.
-        // See #2915 for a better fix.
-        let clip_limit = MAX_VERTEX_TEXTURE_WIDTH / VECS_PER_SEGMENT;
-        let max_clip_count = self.max_clip_count.min(clip_limit);
+        let max_clip_count = self.max_clip_count.min(MAX_DASH_COUNT);
 
         match self.kind {
             BorderCornerClipKind::Dash => {
                 // Get the correct dash arc length.
                 let dash_arc_length =
                     0.5 * self.ellipse.total_arc_length / max_clip_count as f32;
                 // Start the first dash at one quarter the length of a single dash
                 // along the arc line. This is arbitrary but looks reasonable in
                 // most cases. We need to spend some time working on a more
                 // sophisticated dash placement algorithm that takes into account
                 // the offset of the dashes along edge segments.
                 let mut current_arc_length = 0.25 * dash_arc_length;
+                dot_dash_data.reserve(max_clip_count);
                 for _ in 0 .. max_clip_count {
                     let arc_length0 = current_arc_length;
                     current_arc_length += dash_arc_length;
 
                     let arc_length1 = current_arc_length;
                     current_arc_length += dash_arc_length;
 
                     let alpha = self.ellipse.find_angle_for_arc_length(arc_length0);
@@ -420,18 +420,18 @@ impl BorderCornerClipSource {
             BorderCornerClipKind::Dot if max_clip_count == 1 => {
                 let dot_diameter = lerp(self.widths.width, self.widths.height, 0.5);
                 dot_dash_data.push([
                     self.widths.width / 2.0, self.widths.height / 2.0, 0.5 * dot_diameter, 0.,
                     0., 0., 0., 0.,
                 ]);
             }
             BorderCornerClipKind::Dot => {
-                let mut forward_dots = Vec::new();
-                let mut back_dots = Vec::new();
+                let mut forward_dots = Vec::with_capacity(max_clip_count / 2 + 1);
+                let mut back_dots = Vec::with_capacity(max_clip_count / 2 + 1);
                 let mut leftover_arc_length = 0.0;
 
                 // Alternate between adding dots at the start and end of the
                 // ellipse arc. This ensures that we always end up with an exact
                 // half dot at each end of the arc, to match up with the edges.
                 forward_dots.push(DotInfo::new(self.widths.width, self.widths.width));
                 back_dots.push(DotInfo::new(
                     self.ellipse.total_arc_length - self.widths.height,
@@ -496,16 +496,18 @@ impl BorderCornerClipSource {
                     let center = DevicePoint::new(
                         outer.x + clip_sign.x * (self.radius.width - center.x),
                         outer.y + clip_sign.y * (self.radius.height - center.y),
                     );
 
                     [center.x, center.y, radius, 0.0, 0.0, 0.0, 0.0, 0.0]
                 };
 
+                dot_dash_data.reserve(forward_dots.len() + back_dots.len());
+
                 for (i, dot) in forward_dots.iter().enumerate() {
                     let extra_dist = i as f32 * extra_space_per_dot;
                     let dot_data = create_dot_data(&self.ellipse, dot.arc_pos + extra_dist, 0.5 * dot.diameter);
                     dot_dash_data.push(dot_data);
                 }
 
                 for (i, dot) in back_dots.iter().enumerate() {
                     let extra_dist = i as f32 * extra_space_per_dot;
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -2308,58 +2308,70 @@ impl<'a, T> Drop for TextureUploader<'a,
 
 impl<'a, T> TextureUploader<'a, T> {
     pub fn upload(
         &mut self,
         rect: DeviceUintRect,
         layer_index: i32,
         stride: Option<u32>,
         data: &[T],
-    ) {
+    ) -> usize {
+        let bytes_pp = self.target.texture.format.bytes_per_pixel();
+        let upload_size = match stride {
+            Some(stride) => ((rect.size.height - 1) * stride + rect.size.width * bytes_pp) as usize,
+            None => (rect.size.area() * bytes_pp) as usize,
+        };
+        assert!(upload_size <= data.len() * mem::size_of::<T>());
+
         match self.buffer {
             Some(ref mut buffer) => {
-                let upload_size = mem::size_of::<T>() * data.len();
+                let elem_count = upload_size / mem::size_of::<T>();
+                assert_eq!(elem_count * mem::size_of::<T>(), upload_size);
+                let slice = &data[.. elem_count];
+
                 if buffer.size_used + upload_size > buffer.size_allocated {
                     // flush
                     for chunk in buffer.chunks.drain() {
                         self.target.update_impl(chunk);
                     }
                     buffer.size_used = 0;
                 }
 
                 if upload_size > buffer.size_allocated {
                     gl::buffer_data(
                         self.target.gl,
                         gl::PIXEL_UNPACK_BUFFER,
-                        data,
+                        slice,
                         buffer.usage,
                     );
                     buffer.size_allocated = upload_size;
                 } else {
                     gl::buffer_sub_data(
                         self.target.gl,
                         gl::PIXEL_UNPACK_BUFFER,
                         buffer.size_used as _,
-                        data,
+                        slice,
                     );
                 }
 
                 buffer.chunks.push(UploadChunk {
                     rect, layer_index, stride,
                     offset: buffer.size_used,
                 });
                 buffer.size_used += upload_size;
             }
             None => {
                 self.target.update_impl(UploadChunk {
                     rect, layer_index, stride,
                     offset: data.as_ptr() as _,
                 });
             }
         }
+
+        upload_size
     }
 }
 
 impl<'a> UploadTarget<'a> {
     fn update_impl(&mut self, chunk: UploadChunk) {
         let (gl_format, bpp, data_type) = match self.texture.format {
             ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE),
             ImageFormat::BGRA8 => (self.bgra_format, 4, gl::UNSIGNED_BYTE),
--- a/gfx/webrender/src/device/query_gl.rs
+++ b/gfx/webrender/src/device/query_gl.rs
@@ -278,17 +278,17 @@ impl<T: NamedTag> GpuProfiler<T> {
 #[must_use]
 pub struct GpuMarker {
     gl: Option<Rc<gl::Gl>>
 }
 
 impl GpuMarker {
     fn new(gl: &Rc<gl::Gl>, message: &str, ext_debug_marker: bool) -> Self {
         let gl = if ext_debug_marker {
-            gl.push_group_marker_ext(message);            
+            gl.push_group_marker_ext(message);
             Some(Rc::clone(gl))
         } else {
             None
         };
         GpuMarker { gl }
     }
 
     fn fire(gl: &Rc<gl::Gl>, message: &str, ext_debug_marker: bool) {
--- a/gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
+++ b/gfx/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -10,17 +10,17 @@ use device::TextureFilter;
 use euclid::size2;
 use gpu_types::UvRectKind;
 use rayon::prelude::*;
 use std::sync::{Arc, MutexGuard};
 use platform::font::FontContext;
 use glyph_rasterizer::{FontInstance, FontContexts, GlyphKey};
 use glyph_rasterizer::{GlyphRasterizer, GlyphRasterJob, GlyphRasterJobs, GlyphRasterResult};
 use glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry};
-use texture_cache::{TextureCache, TextureCacheHandle};
+use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use gpu_cache::GpuCache;
 use render_task::{RenderTaskTree, RenderTaskCache};
 use tiling::SpecialRenderPasses;
 use profiler::TextureCacheProfileCounters;
 use std::collections::hash_map::Entry;
 
 impl FontContexts {
     /// Get access to the font context associated to the current thread.
@@ -183,16 +183,17 @@ impl GlyphRasterizer {
                             },
                             TextureFilter::Linear,
                             Some(ImageData::Raw(Arc::new(glyph.bytes))),
                             [glyph.left, -glyph.top, glyph.scale],
                             None,
                             gpu_cache,
                             Some(glyph_key_cache.eviction_notice()),
                             UvRectKind::Rect,
+                            Eviction::Auto,
                         );
                         GlyphCacheEntry::Cached(CachedGlyphInfo {
                             texture_cache_handle,
                             format: glyph.format,
                         })
                     }
                 };
                 glyph_key_cache.insert(key, glyph_info);
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -32,17 +32,17 @@ cfg_if! {
 
 const ONE_SECOND_NS: u64 = 1000000000;
 
 #[derive(Debug, Clone)]
 pub struct GpuProfileTag {
     pub label: &'static str,
     pub color: ColorF,
 }
- 
+
 impl NamedTag for GpuProfileTag {
     fn get_label(&self) -> &str {
         self.label
     }
 }
 
 trait ProfileCounter {
     fn description(&self) -> &'static str;
@@ -452,16 +452,17 @@ impl BackendProfileCounters {
 pub struct RendererProfileCounters {
     pub frame_counter: IntProfileCounter,
     pub frame_time: AverageTimeProfileCounter,
     pub draw_calls: IntProfileCounter,
     pub vertices: IntProfileCounter,
     pub vao_count_and_size: ResourceProfileCounter,
     pub color_targets: IntProfileCounter,
     pub alpha_targets: IntProfileCounter,
+    pub texture_data_uploaded: IntProfileCounter,
 }
 
 pub struct RendererProfileTimers {
     pub cpu_time: TimeProfileCounter,
     pub gpu_time: TimeProfileCounter,
     pub gpu_samples: Vec<GpuTimer<GpuProfileTag>>,
 }
 
@@ -470,24 +471,26 @@ impl RendererProfileCounters {
         RendererProfileCounters {
             frame_counter: IntProfileCounter::new("Frame"),
             frame_time: AverageTimeProfileCounter::new("FPS", true, ONE_SECOND_NS / 2),
             draw_calls: IntProfileCounter::new("Draw Calls"),
             vertices: IntProfileCounter::new("Vertices"),
             vao_count_and_size: ResourceProfileCounter::new("VAO"),
             color_targets: IntProfileCounter::new("Color Targets"),
             alpha_targets: IntProfileCounter::new("Alpha Targets"),
+            texture_data_uploaded: IntProfileCounter::new("Texture data, kb"),
         }
     }
 
     pub fn reset(&mut self) {
         self.draw_calls.reset();
         self.vertices.reset();
         self.color_targets.reset();
         self.alpha_targets.reset();
+        self.texture_data_uploaded.reset();
     }
 }
 
 impl RendererProfileTimers {
     pub fn new() -> Self {
         RendererProfileTimers {
             cpu_time: TimeProfileCounter::new("Compositor CPU Time", false),
             gpu_samples: Vec::new(),
@@ -787,17 +790,16 @@ pub struct Profiler {
     compositor_time: ProfileGraph,
     gpu_time: ProfileGraph,
     gpu_frames: GpuFrameCollection,
     ipc_time: ProfileGraph,
 }
 
 #[cfg(feature = "debug_renderer")]
 impl Profiler {
-
     pub fn new() -> Self {
         Profiler {
             draw_state: DrawState {
                 x_left: 0.0,
                 y_left: 0.0,
                 x_right: 0.0,
                 y_right: 0.0,
             },
@@ -1016,16 +1018,17 @@ impl Profiler {
     ) {
         Profiler::draw_counters(
             &[
                 &renderer_profile.frame_time as &ProfileCounter,
                 &renderer_profile.color_targets,
                 &renderer_profile.alpha_targets,
                 &renderer_profile.draw_calls,
                 &renderer_profile.vertices,
+                &renderer_profile.texture_data_uploaded,
                 &self.backend_time,
                 &self.compositor_time,
                 &self.gpu_time,
             ],
             debug_renderer,
             true,
             &mut self.draw_state,
         );
@@ -1042,16 +1045,17 @@ impl Profiler {
         debug_renderer: &mut DebugRenderer,
     ) {
         Profiler::draw_counters(
             &[
                 &renderer_profile.frame_time as &ProfileCounter,
                 &renderer_profile.frame_counter,
                 &renderer_profile.color_targets,
                 &renderer_profile.alpha_targets,
+                &renderer_profile.texture_data_uploaded,
             ],
             debug_renderer,
             true,
             &mut self.draw_state
         );
 
         self.draw_gpu_cache_bars(
             &backend_profile.resources.gpu_cache,
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -21,17 +21,17 @@ use internal_types::{FastHashMap, SavedT
 use pathfinder_partitioner::mesh::Mesh;
 use picture::PictureCacheKey;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
-use texture_cache::{TextureCache, TextureCacheHandle};
+use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 #[cfg(feature = "pathfinder")]
 use webrender_api::DevicePixel;
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 8;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
@@ -1099,16 +1099,17 @@ impl RenderTaskCache {
                     descriptor,
                     TextureFilter::Linear,
                     None,
                     entry.user_data.unwrap_or([0.0; 3]),
                     None,
                     gpu_cache,
                     None,
                     render_task.uv_rect_kind(),
+                    Eviction::Auto,
                 );
 
                 // Get the allocation details in the texture cache, and store
                 // this in the render task. The renderer will draw this
                 // task into the appropriate layer and rect of the texture
                 // cache on this frame.
                 let (texture_id, texture_layer, uv_rect) =
                     texture_cache.get_cache_location(&entry.handle);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -2580,51 +2580,54 @@ impl Renderer {
                     } => {
                         let texture = &self.texture_resolver.cache_texture_map[update.id.0];
                         let mut uploader = self.device.upload_texture(
                             texture,
                             &self.texture_cache_upload_pbo,
                             0,
                         );
 
-                        match source {
+                        let bytes_uploaded = match source {
                             TextureUpdateSource::Bytes { data } => {
                                 uploader.upload(
                                     rect, layer_index, stride,
                                     &data[offset as usize ..],
-                                );
+                                )
                             }
                             TextureUpdateSource::External { id, channel_index } => {
                                 let handler = self.external_image_handler
                                     .as_mut()
                                     .expect("Found external image, but no handler set!");
-                                match handler.lock(id, channel_index).source {
+                                let size = match handler.lock(id, channel_index).source {
                                     ExternalImageSource::RawData(data) => {
                                         uploader.upload(
                                             rect, layer_index, stride,
                                             &data[offset as usize ..],
-                                        );
+                                        )
                                     }
                                     ExternalImageSource::Invalid => {
                                         // Create a local buffer to fill the pbo.
                                         let bpp = texture.get_format().bytes_per_pixel();
                                         let width = stride.unwrap_or(rect.size.width * bpp);
                                         let total_size = width * rect.size.height;
                                         // WR haven't support RGBAF32 format in texture_cache, so
                                         // we use u8 type here.
                                         let dummy_data: Vec<u8> = vec![255; total_size as usize];
-                                        uploader.upload(rect, layer_index, stride, &dummy_data);
+                                        uploader.upload(rect, layer_index, stride, &dummy_data)
                                     }
                                     ExternalImageSource::NativeTexture(eid) => {
                                         panic!("Unexpected external texture {:?} for the texture cache update of {:?}", eid, id);
                                     }
                                 };
                                 handler.unlock(id, channel_index);
+                                size
                             }
-                        }
+                        };
+
+                        self.profile_counters.texture_data_uploaded.add(bytes_uploaded >> 10);
                     }
                     TextureUpdateOp::Free => {
                         let texture = &mut self.texture_resolver.cache_texture_map[update.id.0];
                         self.device.free_texture_storage(texture);
                     }
                 }
             }
         }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -35,17 +35,17 @@ use render_task::{RenderTaskCacheEntry, 
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 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 texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::SpecialRenderPasses;
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphFetchResult {
     pub index_in_text_run: i32,
@@ -212,16 +212,20 @@ where
         }
     }
 
     pub fn get(&self, key: &K) -> &V {
         self.resources.get(key)
             .expect("Didn't find a cached resource with that ID!")
     }
 
+    pub fn try_get(&self, key: &K) -> Option<&V> {
+        self.resources.get(key)
+    }
+
     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);
     }
 
@@ -767,16 +771,28 @@ impl ResourceCache {
             },
             viewport_tiles: image.viewport_tiles,
         };
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         let value = self.resources.image_templates.remove(image_key);
 
+        match self.cached_images.try_get(&image_key) {
+            Some(&ImageResult::UntiledAuto(ref entry)) => {
+                self.texture_cache.mark_unused(&entry.texture_cache_handle);
+            }
+            Some(&ImageResult::Multi(ref entries)) => {
+                for (_, entry) in &entries.resources {
+                    self.texture_cache.mark_unused(&entry.texture_cache_handle);
+                }
+            }
+            _ => {}
+        }
+
         self.cached_images.remove(&image_key);
 
         match value {
             Some(image) => if image.data.is_blob() {
                 self.blob_image_handler.as_mut().unwrap().delete(image_key);
                 self.blob_image_templates.remove(&image_key);
                 self.rasterized_blob_images.remove(&image_key);
             },
@@ -1004,48 +1020,57 @@ impl ResourceCache {
                         size: compute_tile_size(
                             &template.descriptor,
                             tile_size,
                             tile,
                         ),
                         format: template.descriptor.format,
                     };
 
+                    // TODO: We only track dirty rects for non-tiled blobs but we
+                    // should also do it with tiled ones unless we settle for a small
+                    // tile size.
                     blob_request_params.push(
                         BlobImageParams {
                             request: BlobImageRequest {
                                 key: *key,
                                 tile: Some(tile),
                             },
                             descriptor,
                             dirty_rect: None,
                         }
                     );
                 });
             } else {
-                // TODO: to support partial rendering of non-tiled blobs we
-                // need to know that the current version of the blob is uploaded
-                // to the texture cache and get the guarantee that it will not
-                // get evicted by the time the updated blob is rasterized and
-                // uploaded.
-                // Alternatively we could make it the responsibility of the blob
-                // renderer to always output the full image. This could be based
-                // a similar copy-on-write mechanism as gecko tiling.
+                let needs_upload = match self.cached_images.try_get(&key) {
+                    Some(&ImageResult::UntiledAuto(ref entry)) => {
+                        self.texture_cache.needs_upload(&entry.texture_cache_handle)
+                    }
+                    _ => true,
+                };
+
+                let dirty_rect = if needs_upload {
+                    // The texture cache entry has been evicted, treat it as all dirty.
+                    None
+                } else {
+                    template.dirty_rect
+                };
+
                 blob_request_params.push(
                     BlobImageParams {
                         request: BlobImageRequest {
                             key: *key,
                             tile: None,
                         },
                         descriptor: BlobImageDescriptor {
                             offset: DevicePoint::zero(),
                             size: template.descriptor.size,
                             format: template.descriptor.format,
                         },
-                        dirty_rect: None,
+                        dirty_rect,
                     }
                 );
             }
             template.dirty_rect = None;
         }
         let handler = self.blob_image_handler.as_mut().unwrap();
         handler.prepare_resources(&self.resources, &blob_request_params);
         (Some(handler.create_blob_rasterizer()), blob_request_params)
@@ -1348,47 +1373,58 @@ impl ResourceCache {
         self.missing_blob_images.clear();
     }
 
     fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
         for request in self.pending_image_requests.drain() {
             let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
             debug_assert!(image_template.data.uses_texture_cache());
 
+            let mut blob_rasterized_rect = None;
             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.
                     image_template.data.clone()
                 }
                 ImageData::Blob(..) => {
                     let blob_image = self.rasterized_blob_images.get(&request.key).unwrap();
                     match blob_image.data.get(&request.tile) {
                         Some(result) => {
                             let result = result
                                 .as_ref()
                                 .expect("Failed to render a blob image");
 
                             // TODO: we may want to not panic and show a placeholder instead.
 
+                            blob_rasterized_rect = Some(result.rasterized_rect);
+
                             ImageData::Raw(Arc::clone(&result.data))
                         }
                         None => {
                             debug_assert!(false, "invalid blob image request during frame building");
                             continue;
                         }
                     }
                 }
             };
 
             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")
             };
+
+            match (blob_rasterized_rect, entry.dirty_rect) {
+                (Some(rasterized), Some(dirty)) => {
+                    debug_assert!(request.tile.is_some() || rasterized.contains_rect(&dirty));
+                }
+                _ => {}
+            }
+
             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(rect) = entry.dirty_rect.take() {
@@ -1441,27 +1477,34 @@ impl ResourceCache {
                     ) {
                         TextureFilter::Trilinear
                     } else {
                         TextureFilter::Linear
                     }
                 }
             };
 
+            let eviction = if image_template.data.is_blob() {
+                Eviction::Manual
+            } else {
+                Eviction::Auto
+            };
+
             //Note: at this point, the dirty rectangle is local to the descriptor space
             self.texture_cache.update(
                 &mut entry.texture_cache_handle,
                 descriptor,
                 filter,
                 Some(image_data),
                 [0.0; 3],
                 local_dirty_rect,
                 gpu_cache,
                 None,
                 UvRectKind::Rect,
+                eviction,
             );
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
@@ -1477,16 +1520,19 @@ impl ResourceCache {
             self.cached_glyph_dimensions.clear();
         }
         if what.contains(ClearCache::RENDER_TASKS) {
             self.cached_render_tasks.clear();
         }
         if what.contains(ClearCache::TEXTURE_CACHE) {
             self.texture_cache.clear();
         }
+        if what.contains(ClearCache::RASTERIZED_BLOBS) {
+            self.rasterized_blob_images.clear();
+        }
     }
 
     pub fn clear_namespace(&mut self, namespace: IdNamespace) {
         self.resources
             .image_templates
             .images
             .retain(|key, _| key.0 != namespace);
         self.cached_images
@@ -1705,17 +1751,17 @@ impl ResourceCache {
                     ];
 
                     let blob_handler = self.blob_image_handler.as_mut().unwrap();
                     blob_handler.prepare_resources(&self.resources, blob_request_params);
                     let mut rasterizer = blob_handler.create_blob_rasterizer();
                     let (_, result) = rasterizer.rasterize(blob_request_params).pop().unwrap();
                     let result = result.expect("Blob rasterization failed");
 
-                    assert_eq!(result.size, desc.size);
+                    assert_eq!(result.rasterized_rect.size, desc.size);
                     assert_eq!(result.data.len(), desc.compute_total_size() as usize);
 
                     num_blobs += 1;
                     #[cfg(feature = "png")]
                     CaptureConfig::save_png(
                         root.join(format!("blobs/{}.png", num_blobs)),
                         (desc.size.width, desc.size.height),
                         ReadPixelsFormat::Standard(desc.format),
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -88,35 +88,37 @@ pub enum CacheEntryMarker {}
 
 // Stores information related to a single entry in the texture
 // cache. This is stored for each item whether it's in the shared
 // cache or a standalone texture.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CacheEntry {
-    // Size the requested item, in device pixels.
+    /// Size the requested item, in device pixels.
     size: DeviceUintSize,
-    // Details specific to standalone or shared items.
+    /// Details specific to standalone or shared items.
     kind: EntryKind,
-    // Arbitrary user data associated with this item.
+    /// Arbitrary user data associated with this item.
     user_data: [f32; 3],
-    // The last frame this item was requested for rendering.
+    /// The last frame this item was requested for rendering.
     last_access: FrameId,
-    // Handle to the resource rect in the GPU cache.
+    /// Handle to the resource rect in the GPU cache.
     uv_rect_handle: GpuCacheHandle,
-    // Image format of the item.
+    /// Image format of the item.
     format: ImageFormat,
     filter: TextureFilter,
-    // The actual device texture ID this is part of.
+    /// The actual device texture ID this is part of.
     texture_id: CacheTextureId,
-    // Optional notice when the entry is evicted from the cache.
+    /// Optional notice when the entry is evicted from the cache.
     eviction_notice: Option<EvictionNotice>,
-    // The type of UV rect this entry specifies.
+    /// The type of UV rect this entry specifies.
     uv_rect_kind: UvRectKind,
+    /// If set to `Auto` the cache entry may be evicted if unused for a number of frames.
+    eviction: Eviction,
 }
 
 impl CacheEntry {
     // Create a new entry for a standalone texture.
     fn new_standalone(
         texture_id: CacheTextureId,
         size: DeviceUintSize,
         format: ImageFormat,
@@ -131,16 +133,17 @@ impl CacheEntry {
             last_access,
             kind: EntryKind::Standalone,
             texture_id,
             format,
             filter,
             uv_rect_handle: GpuCacheHandle::new(),
             eviction_notice: None,
             uv_rect_kind,
+            eviction: Eviction::Auto,
         }
     }
 
     // Update the GPU cache for this texture cache entry.
     // This ensures that the UV rect, and texture layer index
     // are up to date in the GPU cache for vertex shaders
     // to fetch from.
     fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
@@ -187,16 +190,24 @@ pub struct TextureCacheHandle {
 }
 
 impl TextureCacheHandle {
     pub fn new() -> Self {
         TextureCacheHandle { entry: None }
     }
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum Eviction {
+    Auto,
+    Manual,
+}
+
 // An eviction notice is a shared condition useful for detecting
 // when a TextureCacheHandle gets evicted from the TextureCache.
 // It is optionally installed to the TextureCache when an update()
 // is scheduled. A single notice may be shared among any number of
 // TextureCacheHandle updates. The notice may then be subsequently
 // checked to see if any of the updates using it have been evicted.
 #[derive(Clone, Debug, Default)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -405,16 +416,17 @@ impl TextureCache {
         descriptor: ImageDescriptor,
         filter: TextureFilter,
         data: Option<ImageData>,
         user_data: [f32; 3],
         mut dirty_rect: Option<DeviceUintRect>,
         gpu_cache: &mut GpuCache,
         eviction_notice: Option<&EvictionNotice>,
         uv_rect_kind: UvRectKind,
+        eviction: Eviction,
     ) {
         // Determine if we need to allocate texture cache memory
         // for this item. We need to reallocate if any of the following
         // is true:
         // - Never been in the cache
         // - Has been in the cache but was evicted.
         // - Exists in the cache but dimensions / format have changed.
         let realloc = match handle.entry {
@@ -458,16 +470,18 @@ impl TextureCache {
         // Invalidate the contents of the resource rect in the GPU cache.
         // This ensures that the update_gpu_cache below will add
         // the new information to the GPU cache.
         gpu_cache.invalidate(&entry.uv_rect_handle);
 
         // Upload the resource rect and texture array layer.
         entry.update_gpu_cache(gpu_cache);
 
+        entry.eviction = eviction;
+
         // Create an update command, which the render thread processes
         // to upload the new image data into the correct location
         // in GPU memory.
         if let Some(data) = data {
             let (layer_index, origin) = match entry.kind {
                 EntryKind::Standalone { .. } => (0, DeviceUintPoint::zero()),
                 EntryKind::Cache {
                     layer_index,
@@ -576,26 +590,37 @@ impl TextureCache {
                 ..
             } => (layer_index, origin),
         };
         (SourceTexture::TextureCache(entry.texture_id),
          layer_index as i32,
          DeviceUintRect::new(origin, entry.size))
     }
 
+    pub fn mark_unused(&mut self, handle: &TextureCacheHandle) {
+        if let Some(ref handle) = handle.entry {
+            if let Some(entry) = self.entries.get_opt_mut(handle) {
+                // Set a very low last accessed frame to make it very likely that this entry
+                // will get cleaned up next time we try to expire entries.
+                entry.last_access = FrameId(0);
+                entry.eviction = Eviction::Auto;
+            }
+        }
+    }
+
     // Expire old standalone textures.
     fn expire_old_standalone_entries(&mut self) {
         let mut eviction_candidates = Vec::new();
         let mut retained_entries = Vec::new();
 
         // Build a list of eviction candidates (which are
         // anything not used this frame).
         for handle in self.standalone_entry_handles.drain(..) {
             let entry = self.entries.get(&handle);
-            if entry.last_access == self.frame_id {
+            if entry.eviction == Eviction::Manual || entry.last_access == self.frame_id {
                 retained_entries.push(handle);
             } else {
                 eviction_candidates.push(handle);
             }
         }
 
         // Sort by access time so we remove the oldest ones first.
         eviction_candidates.sort_by_key(|handle| {
@@ -1200,16 +1225,17 @@ impl TextureArray {
                 last_access: frame_id,
                 kind,
                 uv_rect_handle: GpuCacheHandle::new(),
                 format: self.format,
                 filter: self.filter,
                 texture_id: self.texture_id.unwrap(),
                 eviction_notice: None,
                 uv_rect_kind,
+                eviction: Eviction::Auto,
             }
         })
     }
 }
 
 impl TextureUpdate {
     // Constructs a TextureUpdate operation to be passed to the
     // rendering thread in order to do an upload to the right
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,20 +1,20 @@
 /* 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::{BorderRadius, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
 use api::{DevicePoint, DeviceRect, DeviceSize, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize};
-use api::{WorldPixel, WorldPoint, WorldRect};
+use api::{WorldPixel, WorldRect};
 use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D};
-use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D, TypedVector3D};
+use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D};
 use euclid::{HomogeneousVector};
 use num_traits::Zero;
-use plane_split::{Clipper, Plane, Polygon};
+use plane_split::{Clipper, Polygon};
 use std::{i32, f32};
 use std::borrow::Cow;
 
 
 // Matches the definition of SK_ScalarNearlyZero in Skia.
 const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 
 // TODO: Implement these in euclid!
@@ -170,170 +170,82 @@ pub fn lerp(a: f32, b: f32, t: f32) -> f
 }
 
 pub fn calculate_screen_bounding_rect(
     transform: &LayoutToWorldFastTransform,
     rect: &LayoutRect,
     device_pixel_scale: DevicePixelScale,
     screen_bounds: Option<&DeviceIntRect>,
 ) -> Option<DeviceIntRect> {
-    debug!("rect {:?}", rect);
-    debug!("transform {:?}", transform);
-    debug!("screen_bounds: {:?}", screen_bounds);
+    debug!("calculate_screen_bounding_rect for {:?}", rect);
     let homogens = [
         transform.transform_point2d_homogeneous(&rect.origin),
         transform.transform_point2d_homogeneous(&rect.top_right()),
         transform.transform_point2d_homogeneous(&rect.bottom_left()),
         transform.transform_point2d_homogeneous(&rect.bottom_right()),
     ];
-    debug!("homogeneous points {:?}", homogens);
     let max_rect = match screen_bounds {
         Some(bounds) => bounds.to_f32(),
         None => DeviceRect::max_rect(),
     };
 
     // Note: we only do the full frustum collision when the polygon approaches the camera plane.
     // Otherwise, it will be clamped to the screen bounds anyway.
     let world_rect = if homogens.iter().any(|h| h.w <= 0.0) {
-        let mut clipper = Clipper::new();
-        // using inverse-transpose of the inversed transform
-        let t = transform.to_transform();
-        // camera plane
-        {
-            let normal = TypedVector3D::new(t.m14, t.m24, t.m34);
-            let kf = 1.0 / normal.length();
-            clipper.add(Plane {
-                normal: normal * kf,
-                offset: t.m44 * kf,
-            });
-        }
-
-        // The equations for clip planes come from the following one:
-        // (v * M).x < right
-        // (v, Mx) < right
-        // (v, Mx) - right = 0;
+        debug!("transform {:?}", transform);
+        debug!("screen_bounds: {:?}", screen_bounds);
+        debug!("homogeneous points {:?}", homogens);
 
-        // left/right planes
-        if let Some(bounds) = screen_bounds {
-            let normal = TypedVector3D::new(t.m11, t.m21, t.m31);
-            let kf = 1.0 / normal.length();
-            clipper.add(Plane {
-                normal: normal * kf,
-                offset: t.m41 * kf - (bounds.origin.x) as f32 / device_pixel_scale.0,
-            });
-            clipper.add(Plane {
-                normal: normal * -kf,
-                offset: t.m41 * -kf + (bounds.origin.x + bounds.size.width) as f32 / device_pixel_scale.0,
-            });
-        }
-        // top/bottom planes
-        if let Some(bounds) = screen_bounds {
-            let normal = TypedVector3D::new(t.m12, t.m22, t.m32);
-            let kf = 1.0 / normal.length();
-            clipper.add(Plane {
-                normal: normal * kf,
-                offset: t.m42 * kf - (bounds.origin.y) as f32 / device_pixel_scale.0,
-            });
-            clipper.add(Plane {
-                normal: normal * -kf,
-                offset: t.m42 * -kf + (bounds.origin.y + bounds.size.height) as f32 / device_pixel_scale.0,
-            });
-        }
+        let mut clipper = Clipper::new();
+        clipper.add_frustum(
+            &transform.to_transform(),
+            screen_bounds.map(|b| b.to_f32() / device_pixel_scale),
+        );
 
-        let polygon = Polygon::from_points(
-            [
-                rect.origin.to_3d(),
-                rect.top_right().to_3d(),
-                rect.bottom_left().to_3d(),
-                rect.bottom_right().to_3d(),
-            ],
-            1,
-        );
+        let polygon = Polygon::from_rect(*rect, 1);
         debug!("crossing detected for poly {:?}", polygon);
         let results = clipper.clip(polygon);
         debug!("clip results: {:?}", results);
         if results.is_empty() {
             return None
         }
 
         debug!("points:");
         WorldRect::from_points(results
             .into_iter()
             // filter out parts behind the view plane
             .flat_map(|poly| &poly.points)
             .map(|p| {
-                debug!("\tpoint {:?} -> {:?} -> {:?}", p,
-                    transform.transform_point2d_homogeneous(&p.to_2d()),
-                    transform.transform_point2d(&p.to_2d())
-                );
-                //TODO: change to `expect` when the near splitting code is ready
-                transform
-                    .transform_point2d(&p.to_2d())
-                    .unwrap_or(WorldPoint::zero())
+                let mut homo = transform.transform_point2d_homogeneous(&p.to_2d());
+                homo.w = homo.w.max(0.00000001); // avoid infinite values
+                debug!("\tpoint {:?} -> {:?} -> {:?}", p, homo, homo.to_point2d());
+                homo.to_point2d().unwrap()
             })
         )
     } else {
         // we just checked for all the points to be in positive hemisphere, so `unwrap` is valid
         WorldRect::from_points(&[
             homogens[0].to_point2d().unwrap(),
             homogens[1].to_point2d().unwrap(),
             homogens[2].to_point2d().unwrap(),
             homogens[3].to_point2d().unwrap(),
         ])
     };
 
-    debug!("world rect {:?}", world_rect);
-    (world_rect * device_pixel_scale)
-        .round_out()
-        .intersection(&max_rect)
-        .map(|r| r.to_i32())
+    let result = (world_rect * device_pixel_scale)
+         .round_out()
+         .intersection(&max_rect)
+         .map(|r| r.to_i32());
+    if homogens.iter().any(|h| h.w <= 0.0) {
+        debug!("world rect {:?}", world_rect);
+        debug!("result {:?}", result);
+    }
+    result
 }
 
-pub fn _subtract_rect<U>(
-    rect: &TypedRect<f32, U>,
-    other: &TypedRect<f32, U>,
-    results: &mut Vec<TypedRect<f32, U>>,
-) {
-    results.clear();
-
-    let int = rect.intersection(other);
-    match int {
-        Some(int) => {
-            let rx0 = rect.origin.x;
-            let ry0 = rect.origin.y;
-            let rx1 = rx0 + rect.size.width;
-            let ry1 = ry0 + rect.size.height;
-
-            let ox0 = int.origin.x;
-            let oy0 = int.origin.y;
-            let ox1 = ox0 + int.size.width;
-            let oy1 = oy0 + int.size.height;
-
-            let r = TypedRect::from_untyped(&rect_from_points_f(rx0, ry0, ox0, ry1));
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-            let r = TypedRect::from_untyped(&rect_from_points_f(ox0, ry0, ox1, oy0));
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-            let r = TypedRect::from_untyped(&rect_from_points_f(ox0, oy1, ox1, ry1));
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-            let r = TypedRect::from_untyped(&rect_from_points_f(ox1, ry0, rx1, ry1));
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-        }
-        None => {
-            results.push(*rect);
-        }
-    }
-}
 
 #[repr(u32)]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformedRectKind {
     AxisAligned = 0,
     Complex = 1,
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -530,21 +530,22 @@ bitflags!{
         const FRAME = 0x2;
     }
 }
 
 bitflags!{
     /// Mask for clearing caches in debug commands.
     #[derive(Deserialize, Serialize)]
     pub struct ClearCache: u8 {
-        const IMAGES = 0x1;
-        const GLYPHS = 0x2;
-        const GLYPH_DIMENSIONS = 0x4;
-        const RENDER_TASKS = 0x8;
-        const TEXTURE_CACHE = 0x16;
+        const IMAGES = 0b1;
+        const GLYPHS = 0b01;
+        const GLYPH_DIMENSIONS = 0b001;
+        const RENDER_TASKS = 0b0001;
+        const TEXTURE_CACHE = 0b00001;
+        const RASTERIZED_BLOBS = 0b000001;
     }
 }
 
 /// Information about a loaded capture of each document
 /// that is returned by `RenderBackend`.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct CapturedDocument {
     pub document_id: DocumentId,
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -240,17 +240,17 @@ pub type BlobImageResult = Result<Raster
 #[derive(Copy, Clone, Debug)]
 pub struct BlobImageDescriptor {
     pub size: DeviceUintSize,
     pub offset: DevicePoint,
     pub format: ImageFormat,
 }
 
 pub struct RasterizedBlobImage {
-    pub size: DeviceUintSize,
+    pub rasterized_rect: DeviceUintRect,
     pub data: Arc<Vec<u8>>,
 }
 
 #[derive(Clone, Debug)]
 pub enum BlobImageError {
     Oom,
     InvalidKey,
     InvalidData,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-7a1b919e37d6cd0155077aa90f98cfcdf9fa5bae
+c939a61b83bcc9dc10742977704793e9a85b3858
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -98,17 +98,17 @@ fn render_blob(
                     ));
                 }
             }
         }
     }
 
     Ok(RasterizedBlobImage {
         data: Arc::new(texels),
-        size: descriptor.size,
+        rasterized_rect: dirty_rect,
     })
 }
 
 /// See rawtest.rs. We use this to test that blob images are requested the right
 /// amount of times.
 pub struct BlobCallbacks {
     pub request: Box<Fn(&[BlobImageParams]) + Send + 'static>,
 }