Bug 1491130. Update webrender to commit da76f6aad61c1ba769566861ec41b42d511fa456
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Fri, 14 Sep 2018 13:39:33 -0400
changeset 436423 d212d02b75014a20afed1b4eb61ee80fd85a3769
parent 436422 85c0ed4ef9c07f9ed83e1279c1c0b4479881b74c
child 436424 1cff3816f35ac450f103aa1a8c017afce69cba01
push id107849
push userjmuizelaar@mozilla.com
push dateFri, 14 Sep 2018 17:40:54 +0000
treeherdermozilla-inbound@d212d02b7501 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1491130
milestone64.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 1491130. Update webrender to commit da76f6aad61c1ba769566861ec41b42d511fa456
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/snap.glsl
gfx/webrender/src/clip.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/util.rs
gfx/webrender_api/src/api.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -43,17 +43,17 @@ ImageBrushData fetch_image_data(int addr
 }
 
 #ifdef WR_FEATURE_ALPHA_PASS
 vec2 transform_point_snapped(
     vec2 local_pos,
     RectWithSize local_rect,
     mat4 transform
 ) {
-    vec2 snap_offset = compute_snap_offset(local_pos, transform, local_rect, vec2(0.5));
+    vec2 snap_offset = compute_snap_offset(local_pos, transform, local_rect);
     vec4 world_pos = transform * vec4(local_pos, 0.0, 1.0);
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
     return device_pos + snap_offset;
 }
 #endif
 
 void brush_vs(
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -69,18 +69,17 @@ ClipVertexInfo write_clip_tile_vertex(Re
             local_clip_rect
         );
 
         vec2 snap_offsets = compute_snap_offset_impl(
             device_pos,
             clip_transform.m,
             local_clip_rect,
             RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
-            snap_positions,
-            vec2(0.5)
+            snap_positions
         );
 
         device_pos -= snap_offsets;
     }
 
     vec2 world_pos = device_pos / uDevicePixelRatio;
 
     vec4 pos = prim_transform.m * vec4(world_pos, 0.0, 1.0);
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -109,18 +109,17 @@ VertexInfo write_vertex(RectWithSize ins
 
     // Clamp to the two local clip rects.
     vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect);
 
     /// Compute the snapping offset.
     vec2 snap_offset = compute_snap_offset(
         clamped_local_pos,
         transform.m,
-        snap_rect,
-        vec2(0.5)
+        snap_rect
     );
 
     // Transform the current vertex to world space.
     vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -56,74 +56,96 @@ struct TextRun {
     vec2 offset;
 };
 
 TextRun fetch_text_run(int address) {
     vec4 data[3] = fetch_from_resource_cache_3(address);
     return TextRun(data[0], data[1], data[2].xy);
 }
 
-VertexInfo write_text_vertex(vec2 clamped_local_pos,
-                             RectWithSize local_clip_rect,
+VertexInfo write_text_vertex(RectWithSize local_clip_rect,
                              float z,
                              Transform transform,
                              PictureTask task,
                              vec2 text_offset,
-                             RectWithSize snap_rect,
+                             vec2 glyph_offset,
+                             RectWithSize glyph_rect,
                              vec2 snap_bias) {
-    // Transform the current vertex to world space.
-    vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
+    // The offset to snap the glyph rect to a device pixel
+    vec2 snap_offset = vec2(0.0);
+    mat2 local_transform;
 
-    // Convert the world positions to device pixel space.
-    float device_scale = uDevicePixelRatio / world_pos.w;
-    vec2 device_pos = world_pos.xy * device_scale;
-    vec2 snap_offset = vec2(0.0);
-
-#if defined(WR_FEATURE_GLYPH_TRANSFORM)
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
     bool remove_subpx_offset = true;
 #else
     bool remove_subpx_offset = transform.is_axis_aligned;
 #endif
     // Compute the snapping offset only if the scroll node transform is axis-aligned.
     if (remove_subpx_offset) {
+        // Transform from local space to device space.
+        float device_scale = uDevicePixelRatio / transform.m[3].w;
+        mat2 device_transform = mat2(transform.m) * device_scale;
+
         // Ensure the transformed text offset does not contain a subpixel translation
         // such that glyph snapping is stable for equivalent glyph subpixel positions.
-        vec2 world_text_offset = mat2(transform.m) * text_offset;
-        vec2 device_text_pos = (transform.m[3].xy + world_text_offset) * device_scale;
-        snap_offset += floor(device_text_pos + 0.5) - device_text_pos;
+        vec2 device_text_pos = device_transform * text_offset + transform.m[3].xy * device_scale;
+        snap_offset = floor(device_text_pos + 0.5) - device_text_pos;
+
+        // Snap the glyph offset to a device pixel, using an appropriate bias depending
+        // on whether subpixel positioning is required.
+        vec2 device_glyph_offset = device_transform * glyph_offset;
+        snap_offset += floor(device_glyph_offset + snap_bias) - device_glyph_offset;
 
-#ifdef WR_FEATURE_GLYPH_TRANSFORM
-        // For transformed subpixels, we just need to align the glyph origin to a device pixel.
-        // The transformed text offset has already been snapped, so remove it from the glyph
-        // origin when snapping the glyph.
-        vec2 rough_offset = snap_rect.p0 - world_text_offset * device_scale;
-        snap_offset += floor(rough_offset + snap_bias) - rough_offset;
-#else
-        // The transformed text offset has already been snapped, so remove it from the transform
-        // when snapping the glyph.
-        mat4 snap_transform = transform.m;
-        snap_transform[3].xy = -world_text_offset;
-        snap_offset += compute_snap_offset(
-            clamped_local_pos,
-            snap_transform,
-            snap_rect,
-            snap_bias
-        );
+        // Transform from device space back to local space.
+        local_transform = inverse(device_transform);
+
+#ifndef WR_FEATURE_GLYPH_TRANSFORM
+        // If not using transformed subpixels, the glyph rect is actually in local space.
+        // So convert the snap offset back to local space.
+        snap_offset = local_transform * snap_offset;
 #endif
     }
 
+    // Actually translate the glyph rect to a device pixel using the snap offset.
+    glyph_rect.p0 += snap_offset;
+
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+    // The glyph rect is in device space, so transform it back to local space.
+    RectWithSize local_rect = transform_rect(glyph_rect, local_transform);
+
+    // Select the corner of the glyph's local space rect that we are processing.
+    vec2 local_pos = local_rect.p0 + local_rect.size * aPosition.xy;
+
+    // If the glyph's local rect would fit inside the local clip rect, then select a corner from
+    // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
+    // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
+    if (rect_inside_rect(local_rect, local_clip_rect)) {
+        local_pos = local_transform * (glyph_rect.p0 + glyph_rect.size * aPosition.xy);
+    }
+#else
+    // Select the corner of the glyph rect that we are processing.
+    vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
+#endif
+
+    // Clamp to the local clip rect.
+    local_pos = clamp_rect(local_pos, local_clip_rect);
+
+    // Map the clamped local space corner into device space.
+    vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
+    vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
+
     // Apply offsets for the render task to get correct screen location.
-    vec2 final_pos = device_pos + snap_offset -
+    vec2 final_pos = device_pos -
                      task.content_origin +
                      task.common_data.task_rect.p0;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     VertexInfo vi = VertexInfo(
-        clamped_local_pos,
+        local_pos,
         snap_offset,
         world_pos
     );
 
     return vi;
 }
 
 void main(void) {
@@ -151,42 +173,23 @@ void main(void) {
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     // Transform from local space to glyph space.
     mat2 glyph_transform = mat2(transform.m) * uDevicePixelRatio;
 
     // Compute the glyph rect in glyph space.
     RectWithSize glyph_rect = RectWithSize(res.offset + glyph_transform * (text.offset + glyph.offset),
                                            res.uv_rect.zw - res.uv_rect.xy);
 
-    // Transform the glyph rect back to local space.
-    mat2 inv = inverse(glyph_transform);
-    RectWithSize local_rect = transform_rect(glyph_rect, inv);
-
-    // Select the corner of the glyph's local space rect that we are processing.
-    vec2 local_pos = local_rect.p0 + local_rect.size * aPosition.xy;
-
-    // If the glyph's local rect would fit inside the local clip rect, then select a corner from
-    // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
-    // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
-    local_pos = rect_inside_rect(local_rect, ph.local_clip_rect) ?
-                    inv * (glyph_rect.p0 + glyph_rect.size * aPosition.xy) :
-                    clamp_rect(local_pos, ph.local_clip_rect);
 #else
     // Scale from glyph space to local space.
     float scale = res.scale / uDevicePixelRatio;
 
     // Compute the glyph rect in local space.
     RectWithSize glyph_rect = RectWithSize(scale * res.offset + text.offset + glyph.offset,
                                            scale * (res.uv_rect.zw - res.uv_rect.xy));
-
-    // Select the corner of the glyph rect that we are processing.
-    vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
-
-    // Clamp to the local clip rect.
-    local_pos = clamp_rect(local_pos, ph.local_clip_rect);
 #endif
 
     vec2 snap_bias;
     // In subpixel mode, the subpixel offset has already been
     // accounted for while rasterizing the glyph. However, we
     // must still round with a subpixel bias rather than rounding
     // to the nearest whole pixel, depending on subpixel direciton.
     switch (subpx_dir) {
@@ -204,24 +207,25 @@ void main(void) {
         case SUBPX_DIR_VERTICAL:
             snap_bias = vec2(0.5, 0.125);
             break;
         case SUBPX_DIR_MIXED:
             snap_bias = vec2(0.125);
             break;
     }
 
-    VertexInfo vi = write_text_vertex(local_pos,
-                                      ph.local_clip_rect,
+    VertexInfo vi = write_text_vertex(ph.local_clip_rect,
                                       ph.z,
                                       transform,
                                       task,
                                       text.offset,
+                                      glyph.offset,
                                       glyph_rect,
                                       snap_bias);
+    glyph_rect.p0 += vi.snap_offset;
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
     vUvClip = vec4(f, 1.0 - f);
 #else
     vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size;
 #endif
 
--- a/gfx/webrender/res/snap.glsl
+++ b/gfx/webrender/res/snap.glsl
@@ -22,45 +22,42 @@ vec4 compute_snap_positions(mat4 transfo
     return world_snap;
 }
 
 vec2 compute_snap_offset_impl(
     vec2 reference_pos,
     mat4 transform,
     RectWithSize snap_rect,
     RectWithSize reference_rect,
-    vec4 snap_positions,
-    vec2 snap_bias) {
+    vec4 snap_positions) {
 
     /// World offsets applied to the corners of the snap rectangle.
-    vec4 snap_offsets = floor(snap_positions + snap_bias.xyxy) - snap_positions;
+    vec4 snap_offsets = floor(snap_positions + 0.5) - snap_positions;
 
     /// Compute the position of this vertex inside the snap rectangle.
     vec2 normalized_snap_pos = (reference_pos - reference_rect.p0) / reference_rect.size;
 
     /// Compute the actual world offset for this vertex needed to make it snap.
     return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
 }
 
 // Compute a snapping offset in world space (adjusted to pixel ratio),
 // given local position on the transform and a snap rectangle.
 vec2 compute_snap_offset(vec2 local_pos,
                          mat4 transform,
-                         RectWithSize snap_rect,
-                         vec2 snap_bias) {
+                         RectWithSize snap_rect) {
     vec4 snap_positions = compute_snap_positions(
         transform,
         snap_rect
     );
 
     vec2 snap_offsets = compute_snap_offset_impl(
         local_pos,
         transform,
         snap_rect,
         snap_rect,
-        snap_positions,
-        snap_bias
+        snap_positions
     );
 
     return snap_offsets;
 }
 
 #endif //WR_VERTEX_SHADER
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,28 +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::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel};
 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
+use api::{VoidPtrToSizeFn};
 use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode};
 use internal_types::FastHashSet;
 use prim_store::{ClipData, ImageMaskData, SpaceMapper};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use std::{cmp, u32};
-use util::{extract_inner_rect_safe, pack_as_float, project_rect, recycle_vec, ScaleOffset};
+use std::os::raw::c_void;
+use util::{extract_inner_rect_safe, pack_as_float, project_rect, ScaleOffset};
 
 /*
 
  Module Overview
 
  There are a number of data structures involved in the clip module:
 
  ClipStore - Main interface used by other modules.
@@ -342,26 +344,16 @@ impl ClipStore {
             clip_nodes: Vec::new(),
             clip_chain_nodes: Vec::new(),
             clip_node_indices: Vec::new(),
             clip_node_info: Vec::new(),
             clip_node_collectors: Vec::new(),
         }
     }
 
-    pub fn recycle(self) -> Self {
-        ClipStore {
-            clip_nodes: recycle_vec(self.clip_nodes),
-            clip_chain_nodes: recycle_vec(self.clip_chain_nodes),
-            clip_node_indices: recycle_vec(self.clip_node_indices),
-            clip_node_info: recycle_vec(self.clip_node_info),
-            clip_node_collectors: recycle_vec(self.clip_node_collectors),
-        }
-    }
-
     pub fn add_clip_items(
         &mut self,
         clip_items: Vec<ClipItem>,
         spatial_node_index: SpatialNodeIndex,
     ) -> ClipItemRange {
         debug_assert!(!clip_items.is_empty());
 
         let range = ClipItemRange {
@@ -624,16 +616,28 @@ impl ClipStore {
             clips_range,
             has_non_root_coord_system,
             has_non_local_clips,
             local_clip_rect,
             pic_clip_rect,
             needs_mask,
         })
     }
+
+    /// Reports the heap usage of this clip store.
+    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
+        let mut size = 0;
+        unsafe {
+            size += op(self.clip_nodes.as_ptr() as *const c_void);
+            size += op(self.clip_chain_nodes.as_ptr() as *const c_void);
+            size += op(self.clip_node_indices.as_ptr() as *const c_void);
+            size += op(self.clip_node_info.as_ptr() as *const c_void);
+        }
+        size
+    }
 }
 
 #[derive(Debug)]
 pub struct LineDecorationClipSource {
     rect: LayoutRect,
     style: LineStyle,
     orientation: LineOrientation,
     wavy_line_thickness: f32,
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -18,28 +18,28 @@ use clip_scroll_tree::{ClipScrollTree, S
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
-use picture::{PictureCompositeMode, PictureId, PicturePrimitive};
+use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{BorderSource, BrushSegment, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, iter, mem};
 use tiling::{CompositeOps, ScrollbarPrimitive};
-use util::{MaxRect, RectHelpers, recycle_vec};
+use util::{MaxRect, RectHelpers};
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
 
@@ -88,16 +88,19 @@ impl ClipIdToIndexMapper {
 /// members are typically those that are destructured into the FrameBuilder.
 pub struct DisplayListFlattener<'a> {
     /// The scene that we are currently flattening.
     scene: &'a Scene,
 
     /// The ClipScrollTree that we are currently building during flattening.
     clip_scroll_tree: &'a mut ClipScrollTree,
 
+    /// A counter for generating unique picture ids.
+    picture_id_generator: &'a mut PictureIdGenerator,
+
     /// The map of all font instances.
     font_instances: FontInstanceMap,
 
     /// A set of pipelines that the caller has requested be made available as
     /// output textures.
     output_pipelines: &'a FastHashSet<PipelineId>,
 
     /// The data structure that converting between ClipId and the various index
@@ -123,55 +126,53 @@ pub struct DisplayListFlattener<'a> {
     pub hit_testing_runs: Vec<HitTestingRun>,
 
     /// The store which holds all complex clipping information.
     pub clip_store: ClipStore,
 
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
-
-    pub next_picture_id: u64,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
-        old_builder: FrameBuilder,
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
         scene_id: u64,
+        picture_id_generator: &mut PictureIdGenerator,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
 
         let background_color = root_pipeline
             .background_color
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
 
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
-            hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
-            scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
+            hit_testing_runs: Vec::new(),
+            scrollbar_prims: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
-            next_picture_id: old_builder.next_picture_id,
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
-            prim_store: old_builder.prim_store.recycle(),
-            clip_store: old_builder.clip_store.recycle(),
+            prim_store: PrimitiveStore::new(),
+            clip_store: ClipStore::new(),
+            picture_id_generator,
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
         flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
@@ -882,22 +883,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             self.add_primitive_to_draw_list(
                 prim_index,
             );
         }
     }
 
-    fn get_next_picture_id(&mut self) -> PictureId {
-        let id = PictureId(self.next_picture_id);
-        self.next_picture_id += 1;
-        id
-    }
-
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
         spatial_node: ClipId,
@@ -967,17 +962,17 @@ impl<'a> DisplayListFlattener<'a> {
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
             composite_mode = Some(PictureCompositeMode::Blit);
         }
 
         // Add picture for this actual stacking context contents to render into.
         let leaf_picture = PicturePrimitive::new_image(
-            self.get_next_picture_id(),
+            self.picture_id_generator.next(),
             composite_mode,
             participating_in_3d_context,
             pipeline_id,
             frame_output_pipeline_id,
             true,
         );
 
         // Create a brush primitive that draws this picture.
@@ -996,17 +991,17 @@ impl<'a> DisplayListFlattener<'a> {
 
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
         let mut current_prim_index = leaf_prim_index;
 
         // For each filter, create a new image with that composite mode.
         for filter in &composite_ops.filters {
             let mut filter_picture = PicturePrimitive::new_image(
-                self.get_next_picture_id(),
+                self.picture_id_generator.next(),
                 Some(PictureCompositeMode::Filter(*filter)),
                 false,
                 pipeline_id,
                 None,
                 true,
             );
 
             filter_picture.add_primitive(current_prim_index);
@@ -1021,17 +1016,17 @@ impl<'a> DisplayListFlattener<'a> {
                 None,
                 PrimitiveContainer::Brush(filter_prim),
             );
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
             let mut blend_picture = PicturePrimitive::new_image(
-                self.get_next_picture_id(),
+                self.picture_id_generator.next(),
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
                 false,
                 pipeline_id,
                 None,
                 true,
             );
 
             blend_picture.add_primitive(current_prim_index);
@@ -1048,17 +1043,17 @@ impl<'a> DisplayListFlattener<'a> {
             );
         }
 
         if establishes_3d_context {
             // If establishing a 3d context, we need to add a picture
             // that will be the container for all the planes and any
             // un-transformed content.
             let mut container_picture = PicturePrimitive::new_image(
-                self.get_next_picture_id(),
+                self.picture_id_generator.next(),
                 None,
                 false,
                 pipeline_id,
                 None,
                 true,
             );
 
             container_picture.add_primitive(current_prim_index);
@@ -1323,17 +1318,17 @@ impl<'a> DisplayListFlattener<'a> {
         // the local clip rect to primitives.
         let apply_local_clip_rect = shadow.blur_radius == 0.0;
 
         // Create a picture that the shadow primitives will be added to. If the
         // blur radius is 0, the code in Picture::prepare_for_render will
         // detect this and mark the picture to be drawn directly into the
         // parent picture, which avoids an intermediate surface and blur.
         let shadow_pic = PicturePrimitive::new_image(
-            self.get_next_picture_id(),
+            self.picture_id_generator.next(),
             Some(PictureCompositeMode::Filter(FilterOp::Blur(std_deviation))),
             false,
             pipeline_id,
             None,
             apply_local_clip_rect,
         );
 
         // Create the primitive to draw the shadow picture into the scene.
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -53,17 +53,16 @@ pub struct FrameBuilderConfig {
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
-    pub next_picture_id: u64,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
     pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 }
 
 pub struct FrameBuildingContext<'a> {
@@ -123,27 +122,27 @@ impl<'a> PrimitiveContext<'a> {
         PrimitiveContext {
             spatial_node,
             spatial_node_index,
         }
     }
 }
 
 impl FrameBuilder {
+    #[cfg(feature = "replay")]
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
             scrollbar_prims: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
-            next_picture_id: 0,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
             },
         }
@@ -161,17 +160,16 @@ impl FrameBuilder {
             scrollbar_prims: flattener.scrollbar_prims,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             screen_rect,
             background_color,
             window_size,
             scene_id,
             config: flattener.config,
-            next_picture_id: flattener.next_picture_id,
         }
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -20,22 +20,24 @@
 //! will be invoked to build the data.
 //!
 //! After ```end_frame``` has occurred, callers can
 //! use the ```get_address``` API to get the allocated
 //! address in the GPU cache of a given resource slot
 //! for this frame.
 
 use api::{PremultipliedColorF, TexelRect};
+use api::{VoidPtrToSizeFn};
 use device::FrameId;
 use euclid::TypedRect;
 use profiler::GpuCacheProfileCounters;
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use std::{mem, u16, u32};
 use std::ops::Add;
+use std::os::raw::c_void;
 
 
 pub const GPU_CACHE_INITIAL_HEIGHT: u32 = 512;
 const FRAMES_BEFORE_EVICTION: usize = 10;
 const NEW_ROWS_PER_RESIZE: u32 = 512;
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -335,16 +337,28 @@ impl Texture {
             free_lists: FreeBlockLists::new(),
             pending_blocks: Vec::new(),
             updates: Vec::new(),
             occupied_list_head: None,
             allocated_block_count: 0,
         }
     }
 
+    // Reports the CPU heap usage of this Texture struct.
+    fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
+        let mut size = 0;
+        unsafe {
+            size += op(self.blocks.as_ptr() as *const c_void);
+            size += op(self.rows.as_ptr() as *const c_void);
+            size += op(self.pending_blocks.as_ptr() as *const c_void);
+            size += op(self.updates.as_ptr() as *const c_void);
+        }
+        size
+    }
+
     // Push new data into the cache. The ```pending_block_index``` field represents
     // where the data was pushed into the texture ```pending_blocks``` array.
     // Return the allocated address for this data.
     fn push_data(
         &mut self,
         pending_block_index: Option<usize>,
         block_count: usize,
         frame_id: FrameId,
@@ -640,9 +654,14 @@ impl GpuCache {
     /// freed or pending slot will panic!
     pub fn get_address(&self, id: &GpuCacheHandle) -> GpuCacheAddress {
         let location = id.location.expect("handle not requested or allocated!");
         let block = &self.texture.blocks[location.block_index.0];
         debug_assert_eq!(block.epoch, location.epoch);
         debug_assert_eq!(block.last_access_time, self.frame_id);
         block.address
     }
+
+    /// Reports the CPU heap usage of this GpuCache struct.
+    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
+        self.texture.malloc_size_of(op)
+    }
 }
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,19 +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, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
-use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, WorldPoint};
+use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, VoidPtrToSizeFn, WorldPoint};
 use clip::{ClipNodeIndex, ClipChainNode, ClipNode, ClipItem, ClipStore};
 use clip::{ClipChainId, rounded_rectangle_contains_point};
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 use internal_types::FastHashMap;
 use prim_store::ScrollNodeAndClipChain;
+use std::os::raw::c_void;
 use util::LayoutToWorldFastTransform;
 
 /// A copy of important clip scroll node data to use during hit testing. This a copy of
 /// data from the ClipScrollTree that will persist as a new frame is under construction,
 /// allowing hit tests consistent with the currently rendered frame.
 pub struct HitTestSpatialNode {
     /// The pipeline id of this node.
     pipeline_id: PipelineId,
@@ -331,16 +332,31 @@ impl HitTester {
 
         result.items.dedup();
         result
     }
 
     pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
         &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0]
     }
+
+    // Reports the CPU heap usage of this HitTester struct.
+    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
+        let mut size = 0;
+        unsafe {
+            size += op(self.runs.as_ptr() as *const c_void);
+            size += op(self.spatial_nodes.as_ptr() as *const c_void);
+            size += op(self.clip_nodes.as_ptr() as *const c_void);
+            size += op(self.clip_chains.as_ptr() as *const c_void);
+            // We can't measure pipeline_root_nodes because we don't have the
+            // real machinery from the malloc_size_of crate. We could estimate
+            // it but it should generally be very small so we don't bother.
+        }
+        size
+    }
 }
 
 #[derive(Clone, Copy, PartialEq)]
 enum ClippedIn {
     ClippedIn,
     NotClippedIn,
 }
 
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -73,16 +73,39 @@ pub enum PictureSurface {
 // doing deep compares of picture content, these
 // may be the same across display lists, but that's
 // not currently supported.
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureId(pub u64);
 
+// TODO(gw): Having to generate globally unique picture
+//           ids for caching is not ideal. We should be
+//           able to completely remove this once we cache
+//           pictures based on their content, rather than
+//           the current cache key structure.
+pub struct PictureIdGenerator {
+    next: u64,
+}
+
+impl PictureIdGenerator {
+    pub fn new() -> Self {
+        PictureIdGenerator {
+            next: 0,
+        }
+    }
+
+    pub fn next(&mut self) -> PictureId {
+        let id = PictureId(self.next);
+        self.next += 1;
+        id
+    }
+}
+
 // Cache key that determines whether a pre-existing
 // picture in the texture cache matches the content
 // of the current picture.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureCacheKey {
     // NOTE: We specifically want to ensure that we
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -25,17 +25,17 @@ use picture::{PictureCompositeMode, Pict
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, mem, usize};
-use util::{ScaleOffset, MatrixHelpers, pack_as_float, recycle_vec, project_rect, raster_rect_to_device_pixels};
+use util::{ScaleOffset, MatrixHelpers, pack_as_float, project_rect, raster_rect_to_device_pixels};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
     pub spatial_node_index: SpatialNodeIndex,
@@ -1381,23 +1381,16 @@ pub struct PrimitiveStore {
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             primitives: Vec::new(),
             chase_id: None,
         }
     }
 
-    pub fn recycle(self) -> Self {
-        PrimitiveStore {
-            primitives: recycle_vec(self.primitives),
-            chase_id: self.chase_id,
-        }
-    }
-
     pub fn get_pic(&self, index: PrimitiveIndex) -> &PicturePrimitive {
         self.primitives[index.0].as_pic()
     }
 
     pub fn get_pic_mut(&mut self, index: PrimitiveIndex) -> &mut PicturePrimitive {
         self.primitives[index.0].as_pic_mut()
     }
 
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
+use api::{MemoryReport, VoidPtrToSizeFn};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, ImageKey};
 use api::{NotificationRequest, Checkpoint};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
@@ -35,16 +36,17 @@ use scene_builder::*;
 #[cfg(feature = "serialize")]
 use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
 use std::mem::replace;
+use std::os::raw::c_void;
 use std::sync::mpsc::{channel, Sender, Receiver};
 use std::u32;
 #[cfg(feature = "replay")]
 use tiling::Frame;
 use time::precise_time_ns;
 use util::drain_filter;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -379,16 +381,17 @@ pub struct RenderBackend {
     resource_cache: ResourceCache,
 
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, Document>,
 
     notifier: Box<RenderNotifier>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     sampler: Option<Box<AsyncPropertySampler + Send>>,
+    size_of_op: Option<VoidPtrToSizeFn>,
 
     last_scene_id: u64,
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: Receiver<Payload>,
@@ -397,16 +400,17 @@ impl RenderBackend {
         low_priority_scene_tx: Sender<SceneBuilderRequest>,
         scene_rx: Receiver<SceneBuilderResult>,
         default_device_pixel_ratio: f32,
         resource_cache: ResourceCache,
         notifier: Box<RenderNotifier>,
         frame_config: FrameBuilderConfig,
         recorder: Option<Box<ApiRecordingReceiver>>,
         sampler: Option<Box<AsyncPropertySampler + Send>>,
+        size_of_op: Option<VoidPtrToSizeFn>,
     ) -> RenderBackend {
         // The namespace_id should start from 1.
         NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
 
         RenderBackend {
             api_rx,
             payload_rx,
             result_tx,
@@ -417,16 +421,17 @@ impl RenderBackend {
             default_device_pixel_ratio,
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
             recorder,
             sampler,
+            size_of_op,
             last_scene_id: 0,
         }
     }
 
     fn process_scene_msg(
         &mut self,
         document_id: DocumentId,
         message: SceneMsg,
@@ -729,16 +734,19 @@ impl RenderBackend {
                 let pending_update = self.resource_cache.pending_updates();
                 let msg = ResultMsg::UpdateResources {
                     updates: pending_update,
                     cancel_rendering: true,
                 };
                 self.result_tx.send(msg).unwrap();
                 self.notifier.wake_up();
             }
+            ApiMsg::ReportMemory(tx) => {
+                tx.send(self.report_memory()).unwrap();
+            }
             ApiMsg::DebugCommand(option) => {
                 let msg = match option {
                     DebugCommand::EnableDualSourceBlending(enable) => {
                         // Set in the config used for any future documents
                         // that are created.
                         self.frame_config
                             .dual_source_blending_is_enabled = enable;
 
@@ -1134,16 +1142,37 @@ impl RenderBackend {
 
             doc.clip_scroll_tree.print_with(&mut builder);
 
             debug_root.add(builder.build());
         }
 
         serde_json::to_string(&debug_root).unwrap()
     }
+
+    fn size_of<T>(&self, ptr: *const T) -> usize {
+        let op = self.size_of_op.as_ref().unwrap();
+        unsafe { op(ptr as *const c_void) }
+    }
+
+    fn report_memory(&self) -> MemoryReport {
+        let mut report = MemoryReport::default();
+        let op = self.size_of_op.as_ref().unwrap();
+        report.gpu_cache_metadata = self.gpu_cache.malloc_size_of(*op);
+        for (_id, doc) in &self.documents {
+            if let Some(ref fb) = doc.frame_builder {
+                report.primitive_stores += self.size_of(fb.prim_store.primitives.as_ptr());
+                report.clip_stores += fb.clip_store.malloc_size_of(*op);
+            }
+            report.hit_testers +=
+                doc.hit_tester.as_ref().map_or(0, |ht| ht.malloc_size_of(*op));
+        }
+
+        report
+    }
 }
 
 fn get_blob_image_updates(updates: &[ResourceUpdate]) -> Vec<ImageKey> {
     let mut requests = Vec::new();
     for update in updates {
         match *update {
             ResourceUpdate::AddImage(ref img) => {
                 if img.data.is_blob() {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -8,16 +8,17 @@
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
 use api::{BlobImageHandler, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, Epoch, ExternalImageId};
 use api::{ExternalImageType, FontRenderMode, FrameMsg, ImageFormat, PipelineId};
 use api::{ImageRendering};
+use api::{MemoryReport, VoidPtrToSizeFn};
 use api::{RenderApiSender, RenderNotifier, TexelRect, TextureTarget};
 use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
@@ -50,16 +51,17 @@ use render_task::{RenderTask, RenderTask
 use resource_cache::ResourceCache;
 
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
 use std::mem;
+use std::os::raw::c_void;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
@@ -1375,16 +1377,19 @@ pub struct Renderer {
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
     /// Optional trait object that allows the client
     /// application to provide a texture handle to
     /// copy the WR output to.
     output_image_handler: Option<Box<OutputImageHandler>>,
 
+    /// Optional function pointer for memory reporting.
+    size_of_op: Option<VoidPtrToSizeFn>,
+
     // Currently allocated FBOs for output frames.
     output_targets: FastHashMap<u32, FrameOutput>,
 
     pub renderer_errors: Vec<RendererError>,
 
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
@@ -1665,16 +1670,17 @@ impl Renderer {
                         if let Some(ref thread_listener) = *thread_listener_for_rayon_end {
                             thread_listener.thread_stopped(&format!("WRWorker#{}", idx));
                         }
                     })
                     .build();
                 Arc::new(worker.unwrap())
             });
         let sampler = options.sampler;
+        let size_of_op = options.size_of_op;
 
         let blob_image_handler = options.blob_image_handler.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
         let thread_listener_for_lp_scene_builder = thread_listener.clone();
         let scene_builder_hooks = options.scene_builder_hooks;
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
@@ -1746,16 +1752,17 @@ impl Renderer {
                 low_priority_scene_tx,
                 scene_rx,
                 device_pixel_ratio,
                 resource_cache,
                 backend_notifier,
                 config,
                 recorder,
                 sampler,
+                size_of_op,
             );
             backend.run(backend_profile_counters);
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_stopped(&rb_thread_name);
             }
         })?;
 
         let ext_debug_marker = device.supports_extension("GL_EXT_debug_marker");
@@ -1799,16 +1806,17 @@ impl Renderer {
             transforms_texture,
             prim_header_i_texture,
             prim_header_f_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
             external_image_handler: None,
             output_image_handler: None,
+            size_of_op: options.size_of_op,
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             gpu_cache_frame_id: FrameId::new(0),
             gpu_cache_overflow: false,
             texture_cache_upload_pbo,
             texture_resolver,
@@ -4028,16 +4036,36 @@ impl Renderer {
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
         self.device.end_frame();
     }
 
+    fn size_of<T>(&self, ptr: *const T) -> usize {
+        let op = self.size_of_op.as_ref().unwrap();
+        unsafe { op(ptr as *const c_void) }
+    }
+
+    /// Collects a memory report.
+    pub fn report_memory(&self) -> MemoryReport {
+        let mut report = MemoryReport::default();
+        if let CacheBus::PixelBuffer{ref cpu_blocks, ..} = self.gpu_cache_texture.bus {
+            report.gpu_cache_cpu_mirror += self.size_of(cpu_blocks.as_ptr());
+        }
+
+        for (_id, doc) in &self.active_documents {
+            report.render_tasks += self.size_of(doc.frame.render_tasks.tasks.as_ptr());
+            report.render_tasks += self.size_of(doc.frame.render_tasks.task_data.as_ptr());
+        }
+
+        report
+    }
+
     // Sets the blend mode. Blend is unconditionally set if the "show overdraw" debugging mode is
     // enabled.
     fn set_blend(&self, mut blend: bool, framebuffer_kind: FramebufferKind) {
         if framebuffer_kind == FramebufferKind::Main &&
                 self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) {
             blend = true
         }
         self.device.set_blend(blend)
@@ -4187,16 +4215,17 @@ pub struct RendererOptions {
     pub enable_clear_scissor: bool,
     pub max_texture_size: Option<u32>,
     pub scatter_gpu_cache_updates: bool,
     pub upload_method: UploadMethod,
     pub workers: Option<Arc<ThreadPool>>,
     pub blob_image_handler: Option<Box<BlobImageHandler>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
     pub thread_listener: Option<Box<ThreadListener + Send + Sync>>,
+    pub size_of_op: Option<VoidPtrToSizeFn>,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
     pub scene_builder_hooks: Option<Box<SceneBuilderHooks + Send>>,
     pub sampler: Option<Box<AsyncPropertySampler + Send>>,
     pub chase_primitive: ChasePrimitive,
     pub support_low_priority_transactions: bool,
@@ -4222,16 +4251,17 @@ impl Default for RendererOptions {
             scatter_gpu_cache_updates: false,
             // This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL,
             // but we are unable to make this decision here, so picking the reasonable medium.
             upload_method: UploadMethod::PixelBuffer(VertexUsageHint::Stream),
             workers: None,
             blob_image_handler: None,
             recorder: None,
             thread_listener: None,
+            size_of_op: None,
             renderer_id: None,
             cached_programs: None,
             disable_dual_source_blending: false,
             scene_builder_hooks: None,
             sampler: None,
             chase_primitive: ChasePrimitive::Nothing,
             support_low_priority_transactions: false,
         }
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -5,16 +5,17 @@
 use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult};
 use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch};
 use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint};
 use api::channel::MsgSender;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
+use picture::PictureIdGenerator;
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
 use util::drain_filter;
@@ -134,16 +135,17 @@ pub enum SceneSwapResult {
 
 pub struct SceneBuilder {
     documents: FastHashMap<DocumentId, Scene>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
     api_tx: MsgSender<ApiMsg>,
     config: FrameBuilderConfig,
     hooks: Option<Box<SceneBuilderHooks + Send>>,
+    picture_id_generator: PictureIdGenerator,
 }
 
 impl SceneBuilder {
     pub fn new(
         config: FrameBuilderConfig,
         api_tx: MsgSender<ApiMsg>,
         hooks: Option<Box<SceneBuilderHooks + Send>>,
     ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) {
@@ -152,16 +154,17 @@ impl SceneBuilder {
         (
             SceneBuilder {
                 documents: FastHashMap::default(),
                 rx: in_rx,
                 tx: out_tx,
                 api_tx,
                 config,
                 hooks,
+                picture_id_generator: PictureIdGenerator::new(),
             },
             in_tx,
             out_rx,
         )
     }
 
     /// The scene builder thread's event loop.
     pub fn run(&mut self) {
@@ -219,25 +222,25 @@ impl SceneBuilder {
             let scene_build_start_time = precise_time_ns();
 
             let mut built_scene = None;
             if item.scene.has_root_pipeline() {
                 let mut clip_scroll_tree = ClipScrollTree::new();
                 let mut new_scene = Scene::new();
 
                 let frame_builder = DisplayListFlattener::create_frame_builder(
-                    FrameBuilder::empty(),
                     &item.scene,
                     &mut clip_scroll_tree,
                     item.font_instances,
                     &item.view,
                     &item.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     item.scene_id,
+                    &mut self.picture_id_generator,
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
@@ -295,25 +298,25 @@ impl SceneBuilder {
 
         let mut built_scene = None;
         if scene.has_root_pipeline() {
             if let Some(request) = txn.request_scene_build.take() {
                 let mut clip_scroll_tree = ClipScrollTree::new();
                 let mut new_scene = Scene::new();
 
                 let frame_builder = DisplayListFlattener::create_frame_builder(
-                    FrameBuilder::empty(),
                     &scene,
                     &mut clip_scroll_tree,
                     request.font_instances,
                     &request.view,
                     &request.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     request.scene_id,
+                    &mut self.picture_id_generator,
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -371,32 +371,16 @@ pub fn extract_inner_rect_safe<U>(
     rect: &TypedRect<f32, U>,
     radii: &BorderRadius,
 ) -> Option<TypedRect<f32, U>> {
     // value of `k==1.0` is used for extraction of the corner rectangles
     // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
     extract_inner_rect_impl(rect, radii, 1.0)
 }
 
-/// Consumes the old vector and returns a new one that may reuse the old vector's allocated
-/// memory.
-pub fn recycle_vec<T>(mut old_vec: Vec<T>) -> Vec<T> {
-    if old_vec.capacity() > 2 * old_vec.len() {
-        // Avoid reusing the buffer if it is a lot larger than it needs to be. This prevents
-        // a frame with exceptionally large allocations to cause subsequent frames to retain
-        // more memory than they need.
-        return Vec::with_capacity(old_vec.len());
-    }
-
-    old_vec.clear();
-
-    old_vec
-}
-
-
 #[cfg(test)]
 pub mod test {
     use super::*;
     use euclid::{Point2D, Angle, Transform3D};
     use std::f32::consts::PI;
 
     #[test]
     fn inverse_project() {
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -4,16 +4,17 @@
 
 extern crate serde_bytes;
 
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
+use std::os::raw::c_void;
 use std::path::PathBuf;
 use std::sync::Arc;
 use std::u32;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect};
 use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
 use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphIndex, ImageData};
 use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
 use {NativeFontHandle, WorldPoint, NormalizedRect};
@@ -662,16 +663,18 @@ pub enum ApiMsg {
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
     /// Removes all resources associated with a namespace.
     ClearNamespace(IdNamespace),
     /// Flush from the caches anything that isn't necessary, to free some memory.
     MemoryPressure,
+    /// Collects a memory report.
+    ReportMemory(MsgSender<MemoryReport>),
     /// Change debugging options.
     DebugCommand(DebugCommand),
     /// Wakes the render backend's event loop up. Needed when an event is communicated
     /// through another channel.
     WakeUp,
     WakeSceneBuilder,
     FlushSceneBuilder(MsgSender<()>),
     ShutDown,
@@ -685,16 +688,17 @@ impl fmt::Debug for ApiMsg {
             ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
             ApiMsg::AddDocument(..) => "ApiMsg::AddDocument",
             ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
             ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
             ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure",
+            ApiMsg::ReportMemory(..) => "ApiMsg::ReportMemory",
             ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
             ApiMsg::WakeUp => "ApiMsg::WakeUp",
             ApiMsg::WakeSceneBuilder => "ApiMsg::WakeSceneBuilder",
             ApiMsg::FlushSceneBuilder(..) => "ApiMsg::FlushSceneBuilder",
         })
     }
 }
@@ -730,16 +734,44 @@ pub type PipelineSourceId = u32;
 pub struct PipelineId(pub PipelineSourceId, pub u32);
 
 impl PipelineId {
     pub fn dummy() -> Self {
         PipelineId(0, 0)
     }
 }
 
+/// Collection of heap sizes, in bytes.
+#[repr(C)]
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct MemoryReport {
+    pub primitive_stores: usize,
+    pub clip_stores: usize,
+    pub gpu_cache_metadata: usize,
+    pub gpu_cache_cpu_mirror: usize,
+    pub render_tasks: usize,
+    pub hit_testers: usize,
+}
+
+impl ::std::ops::AddAssign for MemoryReport {
+    fn add_assign(&mut self, other: MemoryReport) {
+        self.primitive_stores += other.primitive_stores;
+        self.clip_stores += other.clip_stores;
+        self.gpu_cache_metadata += other.gpu_cache_metadata;
+        self.gpu_cache_cpu_mirror += other.gpu_cache_cpu_mirror;
+        self.render_tasks += other.render_tasks;
+        self.hit_testers += other.hit_testers;
+    }
+}
+
+/// A C function that takes a pointer to a heap allocation and returns its size.
+///
+/// This is borrowed from the malloc_size_of crate, upon which we want to avoid
+/// a dependency from WebRender.
+pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct ResourceId(pub u32);
 
 /// An opaque pointer-sized value.
 #[repr(C)]
 #[derive(Clone, Deserialize, Serialize)]
@@ -892,16 +924,22 @@ impl RenderApi {
         let msg = ApiMsg::ExternalEvent(evt);
         self.api_sender.send(msg).unwrap();
     }
 
     pub fn notify_memory_pressure(&self) {
         self.api_sender.send(ApiMsg::MemoryPressure).unwrap();
     }
 
+    pub fn report_memory(&self) -> MemoryReport {
+        let (tx, rx) = channel::msg_channel().unwrap();
+        self.api_sender.send(ApiMsg::ReportMemory(tx)).unwrap();
+        rx.recv().unwrap()
+    }
+
     pub fn shut_down(&self) {
         self.api_sender.send(ApiMsg::ShutDown).unwrap();
     }
 
     /// Create a new unique key that can be used for
     /// animated property bindings.
     pub fn generate_property_binding_key<T: Copy>(&self) -> PropertyBindingKey<T> {
         let new_id = self.next_unique_id();
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-0f142521b86f201a0f0957cc852aa14923ebfc73
+da76f6aad61c1ba769566861ec41b42d511fa456