Bug 1509305 - Update webrender to commit 3d7a8fa933769b94875f822b6f4a7803da4320ee (WR PR #3335). r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Thu, 22 Nov 2018 12:55:35 +0000
changeset 504160 edc1ed1cf16731fe81ce50efaa8edc17a386fb42
parent 504159 95d5bb21c934f96482a96579102b3dd853542723
child 504161 333df4b787d2bb1ca15b1985a2dc008ddd07fb37
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1509305
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1509305 - Update webrender to commit 3d7a8fa933769b94875f822b6f4a7803da4320ee (WR PR #3335). r=kats Differential Revision: https://phabricator.services.mozilla.com/D12649
gfx/webrender/src/batch.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/storage.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -534,17 +534,17 @@ impl AlphaBatchBuilder {
         let transform_kind = transform_id.transform_kind();
         let bounding_rect = prim_instance.bounding_rect
                                          .as_ref()
                                          .expect("bug");
         let z_id = z_generator.next();
 
         // Get the clip task address for the global primitive, if one was set.
         let clip_task_address = get_clip_task_address(
-            &ctx.prim_store.clip_mask_instances,
+            &ctx.scratch.clip_mask_instances,
             prim_instance.clip_task_index,
             0,
             render_tasks,
         ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Clear => {
                 let prim_data = &ctx
@@ -612,17 +612,17 @@ impl AlphaBatchBuilder {
                     local_rect: prim_data.prim_rect,
                     local_clip_rect: prim_instance.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
-                let glyph_keys = &ctx.prim_store.glyph_keys[run.glyph_keys_range];
+                let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range];
 
                 ctx.resource_cache.fetch_glyphs(
                     run.used_font.clone(),
                     &glyph_keys,
                     glyph_fetch_buffer,
                     gpu_cache,
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, TextureSource::Invalid);
@@ -823,17 +823,17 @@ impl AlphaBatchBuilder {
                                 PrimitiveInstanceKind::Clear => {
                                     unreachable!();
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[pic_index.0];
 
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
-                                &ctx.prim_store.clip_mask_instances,
+                                &ctx.scratch.clip_mask_instances,
                                 prim_instance.clip_task_index,
                                 0,
                                 render_tasks,
                             ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
                             let prim_header = PrimitiveHeader {
                                 local_rect: pic.local_rect,
                                 local_clip_rect: prim_instance.combined_local_clip_rect,
@@ -1411,17 +1411,17 @@ impl AlphaBatchBuilder {
         clip_task_index: ClipTaskIndex,
         ctx: &RenderTargetContext,
     ) {
         debug_assert!(clip_task_index != ClipTaskIndex::INVALID);
 
         // Get GPU address of clip task for this segment, or None if
         // the entire segment is clipped out.
         let clip_task_address = match get_clip_task_address(
-            &ctx.prim_store.clip_mask_instances,
+            &ctx.scratch.clip_mask_instances,
             clip_task_index,
             segment_index,
             render_tasks,
         ) {
             Some(clip_task_address) => clip_task_address,
             None => return,
         };
 
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -20,17 +20,17 @@ use frame_builder::{ChasePrimitive, Fram
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveInstance, PrimitiveDataInterner, PrimitiveKeyKind};
 use prim_store::{ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind};
-use prim_store::{BorderSource, PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore};
+use prim_store::{BorderSource, PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
@@ -165,16 +165,17 @@ impl<'a> DisplayListFlattener<'a> {
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
         resources: &mut DocumentResources,
+        prim_store_stats: &PrimitiveStoreStats,
     ) -> 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 });
@@ -185,17 +186,17 @@ impl<'a> DisplayListFlattener<'a> {
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
             pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
-            prim_store: PrimitiveStore::new(),
+            prim_store: PrimitiveStore::new(&prim_store_stats),
             clip_store: ClipStore::new(),
             resources,
             prim_count_estimate: 0,
             root_pic_index: PictureIndex(0),
         };
 
         flattener.push_root(
             root_pipeline_id,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -8,17 +8,19 @@ use api::{LayoutPoint, LayoutRect, Layou
 use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex};
-use prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId};
+use prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer};
+#[cfg(feature = "replay")]
+use prim_store::{PrimitiveStoreStats};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameResources, FrameStamp};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
 use spatial_node::SpatialNode;
 use std::f32;
@@ -79,16 +81,17 @@ pub struct FrameBuildingState<'a> {
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
     pub transforms: &'a mut TransformPalette,
     pub resources: &'a mut FrameResources,
     pub segment_builder: SegmentBuilder,
     pub surfaces: &'a mut Vec<SurfaceInfo>,
+    pub scratch: &'a mut PrimitiveScratchBuffer,
 }
 
 /// Immutable context of a picture when processing children.
 #[derive(Debug)]
 pub struct PictureContext {
     pub pic_index: PictureIndex,
     pub pipeline_id: PipelineId,
     pub apply_local_clip_rect: bool,
@@ -132,17 +135,17 @@ impl<'a> PrimitiveContext<'a> {
     }
 }
 
 impl FrameBuilder {
     #[cfg(feature = "replay")]
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
-            prim_store: PrimitiveStore::new(),
+            prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
             clip_store: ClipStore::new(),
             screen_rect: DeviceIntRect::zero(),
             window_size: DeviceIntSize::zero(),
             background_color: None,
             root_pic_index: PictureIndex(0),
             config: FrameBuilderConfig {
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
@@ -181,24 +184,25 @@ impl FrameBuilder {
         render_tasks: &mut RenderTaskTree,
         special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         transform_palette: &mut TransformPalette,
         resources: &mut FrameResources,
         surfaces: &mut Vec<SurfaceInfo>,
+        scratch: &mut PrimitiveScratchBuffer,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.pictures.is_empty() {
             return None
         }
 
-        self.prim_store.reset_clip_instances();
+        scratch.begin_frame();
 
         let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let world_rect = (self.screen_rect.to_f32() / device_pixel_scale).round_out();
 
         let frame_context = FrameBuildingContext {
@@ -244,16 +248,17 @@ impl FrameBuilder {
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             special_render_passes,
             transforms: transform_palette,
             resources,
             segment_builder: SegmentBuilder::new(),
             surfaces: pic_update_state.surfaces,
+            scratch,
         };
 
         let (pic_context, mut pic_state, mut prim_list) = self
             .prim_store
             .pictures[self.root_pic_index.0]
             .take_context(
                 self.root_pic_index,
                 root_spatial_node_index,
@@ -313,16 +318,17 @@ impl FrameBuilder {
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         device_pixel_scale: DevicePixelScale,
         layer: DocumentLayer,
         pan: WorldPoint,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
         scene_properties: &SceneProperties,
         resources: &mut FrameResources,
+        scratch: &mut PrimitiveScratchBuffer,
     ) -> Frame {
         profile_scope!("build");
         debug_assert!(
             DeviceIntRect::new(DeviceIntPoint::zero(), self.window_size)
                 .contains_rect(&self.screen_rect)
         );
 
         let mut profile_counters = FrameProfileCounters::new();
@@ -354,16 +360,17 @@ impl FrameBuilder {
             &mut render_tasks,
             &mut special_render_passes,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
             &mut transform_palette,
             resources,
             &mut surfaces,
+            scratch,
         );
 
         resource_cache.block_until_all_resources_added(gpu_cache,
                                                        &mut render_tasks,
                                                        texture_cache_profile);
 
         let mut passes = vec![
             special_render_passes.alpha_glyph_pass,
@@ -401,16 +408,17 @@ impl FrameBuilder {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 use_dual_source_blending,
                 clip_scroll_tree,
                 resources,
                 surfaces: &surfaces,
+                scratch,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -30,17 +30,17 @@ use render_task::{RenderTaskCacheKeyKind
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use std::{cmp, fmt, mem, ops, u32, usize};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
 use tiling::SpecialRenderPasses;
-use util::{ScaleOffset, MatrixHelpers, MaxRect};
+use util::{ScaleOffset, MatrixHelpers, MaxRect, recycle_vec};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
 /// Counter for unique primitive IDs for debug tracing.
 #[cfg(debug_assertions)]
 static NEXT_PRIM_ID: AtomicUsize = AtomicUsize::new(0);
 
 #[cfg(debug_assertions)]
@@ -1337,40 +1337,40 @@ impl TextRunPrimitive {
         glyphs: &[GlyphInstance],
         device_pixel_scale: DevicePixelScale,
         transform: &LayoutToWorldTransform,
         pic_context: &PictureContext,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         special_render_passes: &mut SpecialRenderPasses,
-        glyph_keys: &mut GlyphKeyStorage,
+        scratch: &mut PrimitiveScratchBuffer,
     ) {
         let cache_dirty = self.update_font_instance(
             specified_font,
             device_pixel_scale,
             transform,
             pic_context.allow_subpixel_aa,
             pic_context.raster_space,
         );
 
         if self.glyph_keys_range.is_empty() || cache_dirty {
             let subpx_dir = self.used_font.get_subpx_dir();
 
-            self.glyph_keys_range = glyph_keys.extend(
+            self.glyph_keys_range = scratch.glyph_keys.extend(
                 glyphs.iter().map(|src| {
                     let world_offset = self.used_font.transform.transform(&src.point);
                     let device_offset = device_pixel_scale.transform_point(&world_offset);
                     GlyphKey::new(src.index, device_offset, subpx_dir)
                 }));
         }
 
         resource_cache.request_glyphs(
             self.used_font.clone(),
-            &glyph_keys[self.glyph_keys_range],
+            &scratch.glyph_keys[self.glyph_keys_range],
             gpu_cache,
             render_tasks,
             special_render_passes,
         );
     }
 }
 
 #[derive(Debug)]
@@ -1907,50 +1907,93 @@ impl PrimitiveInstance {
         false
     }
 }
 
 pub type GlyphKeyStorage = storage::Storage<GlyphKey>;
 pub type TextRunIndex = storage::Index<TextRunPrimitive>;
 pub type TextRunStorage = storage::Storage<TextRunPrimitive>;
 
-pub struct PrimitiveStore {
-    pub primitives: Vec<Primitive>,
-    pub pictures: Vec<PicturePrimitive>,
-
-    /// Written during primitive preparation, and read during
-    /// batching. Contains a list of clip mask instance parameters
+/// Contains various vecs of data that is used only during frame building,
+/// where we want to recycle the memory each new display list, to avoid constantly
+/// re-allocating and moving memory around. Written during primitive preparation,
+/// and read during batching.
+pub struct PrimitiveScratchBuffer {
+    /// Contains a list of clip mask instance parameters
     /// per segment generated.
-    /// TODO(gw): We should be able to completely remove this once
-    ///           the batching and prepare_prim passes are unified.
     pub clip_mask_instances: Vec<ClipMaskKind>,
 
+    /// List of glyphs keys that are allocated by each
+    /// text run instance.
     pub glyph_keys: GlyphKeyStorage,
-    pub text_runs: TextRunStorage,
 }
 
-impl PrimitiveStore {
-    pub fn new() -> PrimitiveStore {
-        PrimitiveStore {
-            primitives: Vec::new(),
-            pictures: Vec::new(),
+impl PrimitiveScratchBuffer {
+    pub fn new() -> Self {
+        PrimitiveScratchBuffer {
             clip_mask_instances: Vec::new(),
-
-            glyph_keys: GlyphKeyStorage::new(),
-            text_runs: TextRunStorage::new(),
+            glyph_keys: GlyphKeyStorage::new(0),
         }
     }
 
-    pub fn reset_clip_instances(&mut self) {
+    pub fn recycle(&mut self) {
+        recycle_vec(&mut self.clip_mask_instances);
+        self.glyph_keys.recycle();
+    }
+
+    pub fn begin_frame(&mut self) {
         // Clear the clip mask tasks for the beginning of the frame. Append
         // a single kind representing no clip mask, at the ClipTaskIndex::INVALID
         // location.
         self.clip_mask_instances.clear();
         self.clip_mask_instances.push(ClipMaskKind::None)
     }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Debug)]
+pub struct PrimitiveStoreStats {
+    primitive_count: usize,
+    picture_count: usize,
+    text_run_count: usize,
+}
+
+impl PrimitiveStoreStats {
+    pub fn empty() -> Self {
+        PrimitiveStoreStats {
+            primitive_count: 0,
+            picture_count: 0,
+            text_run_count: 0,
+        }
+    }
+}
+
+pub struct PrimitiveStore {
+    pub primitives: Vec<Primitive>,
+    pub pictures: Vec<PicturePrimitive>,
+    pub text_runs: TextRunStorage,
+}
+
+impl PrimitiveStore {
+    pub fn new(stats: &PrimitiveStoreStats) -> PrimitiveStore {
+        PrimitiveStore {
+            primitives: Vec::with_capacity(stats.primitive_count),
+            pictures: Vec::with_capacity(stats.picture_count),
+            text_runs: TextRunStorage::new(stats.text_run_count),
+        }
+    }
+
+    pub fn get_stats(&self) -> PrimitiveStoreStats {
+        PrimitiveStoreStats {
+            primitive_count: self.primitives.len(),
+            picture_count: self.pictures.len(),
+            text_run_count: self.text_runs.len(),
+        }
+    }
 
     pub fn create_picture(
         &mut self,
         prim: PicturePrimitive,
     ) -> PictureIndex {
         let index = PictureIndex(self.pictures.len());
         self.pictures.push(prim);
         index
@@ -2335,17 +2378,16 @@ impl PrimitiveStore {
                 pic_context.raster_spatial_node_index,
                 &clip_chain,
                 pic_context.surface_index,
                 pic_state,
                 frame_context,
                 frame_state,
                 &clip_node_collector,
                 &mut self.primitives,
-                &mut self.clip_mask_instances,
             );
 
             if prim_instance.is_chased() {
                 println!("\tconsidered visible and ready with local rect {:?}", local_rect);
             }
         }
 
         #[cfg(debug_assertions)]
@@ -2604,17 +2646,17 @@ impl PrimitiveStore {
                     glyphs,
                     frame_context.device_pixel_scale,
                     &transform,
                     pic_context,
                     frame_state.resource_cache,
                     frame_state.gpu_cache,
                     frame_state.render_tasks,
                     frame_state.special_render_passes,
-                    &mut self.glyph_keys,
+                    frame_state.scratch,
                 );
             }
             (
                 PrimitiveInstanceKind::Clear,
                 PrimitiveTemplateKind::Clear
             ) => {
                 // Nothing specific to prepare for clear rects, since the
                 // GPU cache is updated by the template earlier.
@@ -2920,17 +2962,16 @@ impl PrimitiveInstance {
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         surface_index: SurfaceIndex,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
         primitives: &mut [Primitive],
-        clip_mask_instances: &mut Vec<ClipMaskKind>,
     ) -> bool {
         let brush = match self.kind {
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 return false;
             }
@@ -2958,33 +2999,33 @@ impl PrimitiveInstance {
         // clip task instance location below.
         if segment_desc.segments.is_empty() {
             return true;
         }
 
         // Set where in the clip mask instances array the clip mask info
         // can be found for this primitive. Each segment will push the
         // clip mask information for itself in update_clip_task below.
-        self.clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _);
+        self.clip_task_index = ClipTaskIndex(frame_state.scratch.clip_mask_instances.len() as _);
 
         // If we only built 1 segment, there is no point in re-running
         // the clip chain builder. Instead, just use the clip chain
         // instance that was built for the main primitive. This is a
         // significant optimization for the common case.
         if segment_desc.segments.len() == 1 {
             let clip_mask_kind = segment_desc.segments[0].update_clip_task(
                 Some(prim_clip_chain),
                 prim_bounding_rect,
                 root_spatial_node_index,
                 surface_index,
                 pic_state,
                 frame_context,
                 frame_state,
             );
-            clip_mask_instances.push(clip_mask_kind);
+            frame_state.scratch.clip_mask_instances.push(clip_mask_kind);
         } else {
             for segment in &mut segment_desc.segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
@@ -3007,17 +3048,17 @@ impl PrimitiveInstance {
                     segment_clip_chain.as_ref(),
                     prim_bounding_rect,
                     root_spatial_node_index,
                     surface_index,
                     pic_state,
                     frame_context,
                     frame_state,
                 );
-                clip_mask_instances.push(clip_mask_kind);
+                frame_state.scratch.clip_mask_instances.push(clip_mask_kind);
             }
         }
 
         true
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
@@ -3485,17 +3526,16 @@ impl PrimitiveInstance {
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         surface_index: SurfaceIndex,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
         primitives: &mut [Primitive],
-        clip_mask_instances: &mut Vec<ClipMaskKind>,
     ) {
         if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
 
         // Reset clips from previous frames since we may clip differently each frame.
         self.clip_task_index = ClipTaskIndex::INVALID;
 
@@ -3508,17 +3548,16 @@ impl PrimitiveInstance {
             prim_context,
             &clip_chain,
             surface_index,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
             primitives,
-            clip_mask_instances,
         ) {
             if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
 
         if clip_chain.needs_mask {
@@ -3541,18 +3580,18 @@ impl PrimitiveInstance {
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if self.is_chased() {
                     println!("\tcreated task {:?} with device rect {:?}",
                         clip_task_id, device_rect);
                 }
                 // Set the global clip mask instance for this primitive.
-                let clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _);
-                clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
+                let clip_task_index = ClipTaskIndex(frame_state.scratch.clip_mask_instances.len() as _);
+                frame_state.scratch.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
                 self.clip_task_index = clip_task_index;
                 frame_state.surfaces[surface_index.0].tasks.push(clip_task_id);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -25,17 +25,17 @@ use api::CapturedDocument;
 use clip::ClipDataStore;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
-use prim_store::PrimitiveDataStore;
+use prim_store::{PrimitiveDataStore, PrimitiveScratchBuffer};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
@@ -250,16 +250,21 @@ struct Document {
     frame_is_valid: bool,
     hit_tester_is_valid: bool,
     rendered_frame_is_valid: bool,
     // We track this information to be able to display debugging information from the
     // renderer.
     has_built_scene: bool,
 
     resources: FrameResources,
+
+    /// Contains various vecs of data that is used only during frame building,
+    /// where we want to recycle the memory each new display list, to avoid constantly
+    /// re-allocating and moving memory around.
+    scratch: PrimitiveScratchBuffer,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceIntSize,
         layer: DocumentLayer,
         default_device_pixel_ratio: f32,
     ) -> Self {
@@ -281,16 +286,17 @@ impl Document {
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
             hit_tester_is_valid: false,
             rendered_frame_is_valid: false,
             has_built_scene: false,
             resources: FrameResources::new(),
+            scratch: PrimitiveScratchBuffer::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
     fn has_pixels(&self) -> bool {
@@ -418,16 +424,17 @@ impl Document {
                 &self.scene.pipelines,
                 accumulated_scale_factor,
                 self.view.layer,
                 pan,
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
                 &mut self.resources,
+                &mut self.scratch,
             );
             self.hit_tester = Some(frame_builder.create_hit_tester(
                 &self.clip_scroll_tree,
                 &self.resources.clip_data_store,
             ));
             frame
         };
 
@@ -498,16 +505,17 @@ impl Document {
     }
 
     pub fn new_async_scene_ready(&mut self, built_scene: BuiltScene) {
         self.scene = built_scene.scene;
         self.frame_is_valid = false;
         self.hit_tester_is_valid = false;
 
         self.frame_builder = Some(built_scene.frame_builder);
+        self.scratch.recycle();
 
         let old_scrolling_states = self.clip_scroll_tree.drain();
         self.clip_scroll_tree = built_scene.clip_scroll_tree;
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
 
         // Advance to the next frame.
         self.stamp.advance();
     }
@@ -1584,16 +1592,17 @@ impl RenderBackend {
                 output_pipelines: FastHashSet::default(),
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
                 frame_is_valid: false,
                 hit_tester_is_valid: false,
                 rendered_frame_is_valid: false,
                 has_built_scene: false,
                 resources: frame_resources,
+                scratch: PrimitiveScratchBuffer::new(),
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
 
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -8,17 +8,17 @@ use api::{BuiltDisplayList, ColorF, Layo
 use api::channel::MsgSender;
 #[cfg(feature = "capture")]
 use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
-use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList};
+use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList, PrimitiveStoreStats};
 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;
@@ -175,23 +175,25 @@ impl DocumentResources {
 
 // A document in the scene builder contains the current scene,
 // as well as a persistent clip interner. This allows clips
 // to be de-duplicated, and persisted in the GPU cache between
 // display lists.
 struct Document {
     scene: Scene,
     resources: DocumentResources,
+    prim_store_stats: PrimitiveStoreStats,
 }
 
 impl Document {
     fn new(scene: Scene) -> Self {
         Document {
             scene,
             resources: DocumentResources::new(),
+            prim_store_stats: PrimitiveStoreStats::empty(),
         }
     }
 }
 
 pub struct SceneBuilder {
     documents: FastHashMap<DocumentId, Document>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
@@ -321,16 +323,17 @@ impl SceneBuilder {
                     &item.scene,
                     &mut clip_scroll_tree,
                     item.font_instances,
                     &item.view,
                     &item.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     &mut item.doc_resources,
+                    &PrimitiveStoreStats::empty(),
                 );
 
                 let clip_updates = item
                     .doc_resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
                 let prim_updates = item
@@ -352,16 +355,17 @@ impl SceneBuilder {
                 });
             }
 
             self.documents.insert(
                 item.document_id,
                 Document {
                     scene: item.scene,
                     resources: item.doc_resources,
+                    prim_store_stats: PrimitiveStoreStats::empty(),
                 },
             );
 
             let txn = Box::new(BuiltTransaction {
                 document_id: item.document_id,
                 render_frame: item.build_frame,
                 invalidate_rendered_frame: false,
                 built_scene,
@@ -427,18 +431,22 @@ impl SceneBuilder {
                     &scene,
                     &mut clip_scroll_tree,
                     request.font_instances,
                     &request.view,
                     &request.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     &mut doc.resources,
+                    &doc.prim_store_stats,
                 );
 
+                // Update the allocation stats for next scene
+                doc.prim_store_stats = frame_builder.prim_store.get_stats();
+
                 // Retrieve the list of updates from the clip interner.
                 let clip_updates = doc
                     .resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
                 let prim_updates = doc
                     .resources
--- a/gfx/webrender/src/storage.rs
+++ b/gfx/webrender/src/storage.rs
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use std::{iter::Extend, ops, marker::PhantomData};
+use util::recycle_vec;
 
 #[derive(Debug, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Index<T>(u32, PhantomData<T>);
 
 // We explicitly implement Copy + Clone instead of using #[derive(Copy, Clone)]
 // because we don't want to require that T implements Clone + Copy.
@@ -53,26 +54,36 @@ impl<T> Range<T> {
     }
 }
 
 pub struct Storage<T> {
     data: Vec<T>,
 }
 
 impl<T> Storage<T> {
-    pub fn new() -> Self {
-        Storage { data: vec![] }
+    pub fn new(initial_capacity: usize) -> Self {
+        Storage {
+            data: Vec::with_capacity(initial_capacity),
+        }
+    }
+
+    pub fn len(&self) -> usize {
+        self.data.len()
     }
 
     pub fn push(&mut self, t: T) -> Index<T> {
         let index = self.data.len();
         self.data.push(t);
         Index(index as u32, PhantomData)
     }
 
+    pub fn recycle(&mut self) {
+        recycle_vec(&mut self.data);
+    }
+
     pub fn extend<II: IntoIterator<Item=T>>(&mut self, iter: II) -> Range<T> {
         let start = Index::new(self.data.len());
         self.data.extend(iter);
         let end = Index::new(self.data.len());
         Range { start, end }
     }
 }
 
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -13,17 +13,17 @@ use device::{Texture};
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::SurfaceInfo;
-use prim_store::{PrimitiveStore, DeferredResolve};
+use prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
 use profiler::FrameProfileCounters;
 use render_backend::{FrameId, FrameResources};
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
@@ -47,16 +47,17 @@ pub struct RenderTargetIndex(pub usize);
 pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub use_dual_source_blending: bool,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub resources: &'a FrameResources,
     pub surfaces: &'a [SurfaceInfo],
+    pub scratch: &'a PrimitiveScratchBuffer,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -768,8 +768,20 @@ where
     while i != vec.len() {
         if filter(&mut vec[i]) {
             action(vec.remove(i));
         } else {
             i += 1;
         }
     }
 }
+
+/// Clear a vector for re-use, while retaining the backing memory buffer. May shrink the buffer
+/// if it's currently much larger than was actually used.
+pub fn recycle_vec<T>(vec: &mut Vec<T>) {
+    if vec.capacity() > 2 * vec.len() {
+        // Reduce capacity of 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.
+        vec.shrink_to_fit();
+    }
+    vec.clear();
+}
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-5dba919cac858f9b2972bd24e29c9e353c28051d
+3d7a8fa933769b94875f822b6f4a7803da4320ee