Bug 1441308 - Make GpuCache document-aware r=kvark
authorDoug Thayer <dothayer@mozilla.com>
Thu, 10 Jan 2019 16:59:29 +0000
changeset 453277 da3629e101b9
parent 453276 95537e83071a
child 453278 afb8c84c9677
push id75793
push userdothayer@mozilla.com
push dateThu, 10 Jan 2019 17:05:09 +0000
treeherderautoland@afb8c84c9677 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1441308
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1441308 - Make GpuCache document-aware r=kvark GpuCache can currently evict things out from underneath docs which are not updating this frame. This makes its roots document-specific, so that we only evict items for currently updating documents. Depends on D13343 Differential Revision: https://phabricator.services.mozilla.com/D13840
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/glyph_rasterizer/mod.rs
gfx/wr/webrender/src/gpu_cache.rs
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -387,17 +387,17 @@ impl FrameBuilder {
         );
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(stamp);
-        gpu_cache.begin_frame(stamp.frame_id());
+        gpu_cache.begin_frame(stamp);
 
         let mut transform_palette = TransformPalette::new();
         clip_scroll_tree.update_tree(
             pan,
             scene_properties,
             Some(&mut transform_palette),
         );
         self.clip_store.clear_old_instances();
@@ -493,17 +493,17 @@ impl FrameBuilder {
                 }
                 RenderPassKind::OffScreen { ref texture_cache, ref color, .. } => {
                     has_texture_cache_tasks |= !texture_cache.is_empty();
                     has_texture_cache_tasks |= color.must_be_drawn();
                 }
             }
         }
 
-        let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
+        let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile).frame_id();
 
         render_tasks.write_task_data(device_pixel_scale);
 
         resource_cache.end_frame(texture_cache_profile);
 
         Frame {
             window_size: self.window_size,
             inner_rect: self.screen_rect,
--- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
@@ -726,17 +726,17 @@ mod test_glyph_rasterizer {
             .thread_name(|idx|{ format!("WRWorker#{}", idx) })
             .start_handler(move |idx| {
                 register_thread_with_profiler(format!("WRWorker#{}", idx));
             })
             .build();
         let workers = Arc::new(worker.unwrap());
         let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
         let mut glyph_cache = GlyphCache::new();
-        let mut gpu_cache = GpuCache::new();
+        let mut gpu_cache = GpuCache::new_for_testing();
         let mut texture_cache = TextureCache::new_for_testing(2048, 1024);
         let mut render_task_cache = RenderTaskCache::new();
         let mut render_task_tree = RenderTaskTree::new(FrameId::INVALID);
         let mut font_file =
             File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
         let mut font_data = vec![];
         font_file
             .read_to_end(&mut font_data)
--- a/gfx/wr/webrender/src/gpu_cache.rs
+++ b/gfx/wr/webrender/src/gpu_cache.rs
@@ -19,21 +19,22 @@
 //! data is not in the cache, the user provided closure
 //! 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::{DebugFlags, PremultipliedColorF, TexelRect};
+use api::{DebugFlags, DocumentId, PremultipliedColorF, IdNamespace, TexelRect};
 use api::{VoidPtrToSizeFn};
 use euclid::TypedRect;
+use internal_types::{FastHashMap};
 use profiler::GpuCacheProfileCounters;
-use render_backend::FrameId;
+use render_backend::{FrameStamp, FrameId};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use std::{mem, u16, u32};
 use std::num::NonZeroU32;
 use std::ops::Add;
 use std::os::raw::c_void;
 use std::time::{Duration, Instant};
 
 
@@ -406,17 +407,17 @@ struct Texture {
     max_epoch: Epoch,
     // Free lists of available blocks for each supported
     // block size in the texture. These are intrusive
     // linked lists.
     free_lists: FreeBlockLists,
     // Linked list of currently occupied blocks. This
     // makes it faster to iterate blocks looking for
     // candidates to be evicted from the cache.
-    occupied_list_head: Option<BlockIndex>,
+    occupied_list_heads: FastHashMap<DocumentId, BlockIndex>,
     // Pending blocks that have been written this frame
     // and will need to be sent to the GPU.
     pending_blocks: Vec<GpuBlockData>,
     // Pending update commands.
     updates: Vec<GpuCacheUpdate>,
     // Profile stats
     allocated_block_count: usize,
     // The stamp at which we first reached our threshold for reclaiming `GpuCache`
@@ -442,17 +443,17 @@ impl Texture {
             height: GPU_CACHE_INITIAL_HEIGHT,
             blocks,
             rows: Vec::new(),
             base_epoch,
             max_epoch: base_epoch,
             free_lists: FreeBlockLists::new(),
             pending_blocks: Vec::new(),
             updates: Vec::new(),
-            occupied_list_head: None,
+            occupied_list_heads: FastHashMap::default(),
             allocated_block_count: 0,
             reached_reclaim_threshold: None,
             debug_commands: Vec::new(),
             debug_flags,
         }
     }
 
     // Reports the CPU heap usage of this Texture struct.
@@ -469,18 +470,19 @@ impl Texture {
 
     // 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,
+        frame_stamp: FrameStamp
     ) -> CacheLocation {
+        debug_assert!(frame_stamp.is_valid());
         // Find the appropriate free list to use based on the block size.
         let (alloc_size, free_list) = self.free_lists
             .get_actual_block_count_and_free_list(block_count);
 
         // See if we need a new row (if free-list has nothing available)
         if free_list.is_none() {
             if self.rows.len() as i32 == self.height {
                 self.height += NEW_ROWS_PER_RESIZE;
@@ -493,35 +495,35 @@ impl Texture {
 
             // Create a ```Block``` for each possible allocation address
             // in this row, and link it in to the free-list for this
             // block size.
             let mut prev_block_index = None;
             for i in 0 .. items_per_row {
                 let address = GpuCacheAddress::new(i * alloc_size, row_index);
                 let block_index = BlockIndex::new(self.blocks.len());
-                let block = Block::new(address, prev_block_index, frame_id, self.base_epoch);
+                let block = Block::new(address, prev_block_index, frame_stamp.frame_id(), self.base_epoch);
                 self.blocks.push(block);
                 prev_block_index = Some(block_index);
             }
 
             *free_list = prev_block_index;
         }
 
         // Given the code above, it's now guaranteed that there is a block
         // available in the appropriate free-list. Pull a block from the
         // head of the list.
         let free_block_index = free_list.take().unwrap();
         let block = &mut self.blocks[free_block_index.get()];
         *free_list = block.next;
 
         // Add the block to the occupied linked list.
-        block.next = self.occupied_list_head;
-        block.last_access_time = frame_id;
-        self.occupied_list_head = Some(free_block_index);
+        block.next = self.occupied_list_heads.get(&frame_stamp.document_id()).cloned();
+        block.last_access_time = frame_stamp.frame_id();
+        self.occupied_list_heads.insert(frame_stamp.document_id(), free_block_index);
         self.allocated_block_count += alloc_size;
 
         if let Some(pending_block_index) = pending_block_index {
             // Add this update to the pending list of blocks that need
             // to be updated on the GPU.
             self.updates.push(GpuCacheUpdate::Copy {
                 block_index: pending_block_index,
                 block_count,
@@ -544,34 +546,35 @@ impl Texture {
         CacheLocation {
             block_index: free_block_index,
             epoch: block.epoch,
         }
     }
 
     // Run through the list of occupied cache blocks and evict
     // any old blocks that haven't been referenced for a while.
-    fn evict_old_blocks(&mut self, frame_id: FrameId) {
+    fn evict_old_blocks(&mut self, frame_stamp: FrameStamp) {
+        debug_assert!(frame_stamp.is_valid());
         // Prune any old items from the list to make room.
         // Traverse the occupied linked list and see
         // which items have not been used for a long time.
-        let mut current_block = self.occupied_list_head;
+        let mut current_block = self.occupied_list_heads.get(&frame_stamp.document_id()).map(|x| *x);
         let mut prev_block: Option<BlockIndex> = None;
 
         while let Some(index) = current_block {
             let (next_block, should_unlink) = {
                 let block = &mut self.blocks[index.get()];
 
                 let next_block = block.next;
                 let mut should_unlink = false;
 
                 // If this resource has not been used in the last
                 // few frames, free it from the texture and mark
                 // as empty.
-                if block.last_access_time + FRAMES_BEFORE_EVICTION < frame_id {
+                if block.last_access_time + FRAMES_BEFORE_EVICTION < frame_stamp.frame_id() {
                     should_unlink = true;
 
                     // Get the row metadata from the address.
                     let row = &mut self.rows[block.address.v as usize];
 
                     // Use the row metadata to determine which free-list
                     // this block belongs to.
                     let (_, free_list) = self.free_lists
@@ -595,17 +598,24 @@ impl Texture {
             // If the block was released, we will need to remove it
             // from the occupied linked list.
             if should_unlink {
                 match prev_block {
                     Some(prev_block) => {
                         self.blocks[prev_block.get()].next = next_block;
                     }
                     None => {
-                        self.occupied_list_head = next_block;
+                        match next_block {
+                            Some(next_block) => {
+                                self.occupied_list_heads.insert(frame_stamp.document_id(), next_block);
+                            }
+                            None => {
+                                self.occupied_list_heads.remove(&frame_stamp.document_id());
+                            }
+                        }
                     }
                 }
             } else {
                 prev_block = current_block;
             }
 
             current_block = next_block;
         }
@@ -622,17 +632,17 @@ impl Texture {
 }
 
 
 /// A wrapper object for GPU data requests,
 /// works as a container that can only grow.
 #[must_use]
 pub struct GpuDataRequest<'a> {
     handle: &'a mut GpuCacheHandle,
-    frame_id: FrameId,
+    frame_stamp: FrameStamp,
     start_index: usize,
     max_block_count: usize,
     texture: &'a mut Texture,
 }
 
 impl<'a> GpuDataRequest<'a> {
     pub fn push<B>(&mut self, block: B)
     where
@@ -648,68 +658,80 @@ impl<'a> GpuDataRequest<'a> {
 
 impl<'a> Drop for GpuDataRequest<'a> {
     fn drop(&mut self) {
         // Push the data to the texture pending updates list.
         let block_count = self.current_used_block_num();
         debug_assert!(block_count <= self.max_block_count);
 
         let location = self.texture
-            .push_data(Some(self.start_index), block_count, self.frame_id);
+            .push_data(Some(self.start_index), block_count, self.frame_stamp);
         self.handle.location = Some(location);
     }
 }
 
 
 /// The main LRU cache interface.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuCache {
-    /// Current frame ID.
-    frame_id: FrameId,
+    /// Current FrameId.
+    now: FrameStamp,
     /// CPU-side texture allocator.
     texture: Texture,
     /// Number of blocks requested this frame that don't
     /// need to be re-uploaded.
     saved_block_count: usize,
     /// The current debug flags for the system.
     debug_flags: DebugFlags,
     /// Whether there is a pending clear to send with the
     /// next update.
     pending_clear: bool,
 }
 
 impl GpuCache {
     pub fn new() -> Self {
         let debug_flags = DebugFlags::empty();
         GpuCache {
-            frame_id: FrameId::INVALID,
+            now: FrameStamp::INVALID,
             texture: Texture::new(Epoch(0), debug_flags),
             saved_block_count: 0,
             debug_flags,
             pending_clear: false,
         }
     }
 
+    /// Creates a GpuCache and sets it up with a valid `FrameStamp`, which
+    /// is useful for avoiding panics when instantiating the `GpuCache`
+    /// directly from unit test code.
+    #[allow(dead_code)]
+    pub fn new_for_testing() -> Self {
+        let mut cache = Self::new();
+        let mut now = FrameStamp::first(DocumentId(IdNamespace(1), 1));
+        now.advance();
+        cache.begin_frame(now);
+        cache
+    }
+
     /// Drops everything in the GPU cache. Must not be called once gpu cache entries
     /// for the next frame have already been requested.
     pub fn clear(&mut self) {
         assert!(self.texture.updates.is_empty(), "Clearing with pending updates");
         let mut next_base_epoch = self.texture.max_epoch;
         next_base_epoch.next();
         self.texture = Texture::new(next_base_epoch, self.debug_flags);
         self.saved_block_count = 0;
         self.pending_clear = true;
     }
 
     /// Begin a new frame.
-    pub fn begin_frame(&mut self, frame_id: FrameId) {
+    pub fn begin_frame(&mut self, stamp: FrameStamp) {
         debug_assert!(self.texture.pending_blocks.is_empty());
-        self.frame_id = frame_id;
-        self.texture.evict_old_blocks(self.frame_id);
+        self.now = stamp;
+        self.texture.evict_old_blocks(self.now);
         self.saved_block_count = 0;
     }
 
     // Invalidate a (possibly) existing block in the cache.
     // This means the next call to request() for this location
     // will rebuild the data and upload it to the GPU.
     pub fn invalidate(&mut self, handle: &GpuCacheHandle) {
         if let Some(ref location) = handle.location {
@@ -726,67 +748,68 @@ impl GpuCache {
     /// is already in the cache, `None` will be returned.
     pub fn request<'a>(&'a mut self, handle: &'a mut GpuCacheHandle) -> Option<GpuDataRequest<'a>> {
         let mut max_block_count = MAX_VERTEX_TEXTURE_WIDTH;
         // Check if the allocation for this handle is still valid.
         if let Some(ref location) = handle.location {
             if let Some(block) = self.texture.blocks.get_mut(location.block_index.get()) {
                 if block.epoch == location.epoch {
                     max_block_count = self.texture.rows[block.address.v as usize].block_count_per_item;
-                    if block.last_access_time != self.frame_id {
+                    if block.last_access_time != self.now.frame_id() {
                         // Mark last access time to avoid evicting this block.
-                        block.last_access_time = self.frame_id;
+                        block.last_access_time = self.now.frame_id();
                         self.saved_block_count += max_block_count;
                     }
                     return None;
                 }
             }
         }
 
+        debug_assert!(self.now.is_valid());
         Some(GpuDataRequest {
             handle,
-            frame_id: self.frame_id,
+            frame_stamp: self.now,
             start_index: self.texture.pending_blocks.len(),
             texture: &mut self.texture,
             max_block_count,
         })
     }
 
     // Push an array of data blocks to be uploaded to the GPU
     // unconditionally for this frame. The cache handle will
     // assert if the caller tries to retrieve the address
     // of this handle on a subsequent frame. This is typically
     // used for uploading data that changes every frame, and
     // therefore makes no sense to try and cache.
     pub fn push_per_frame_blocks(&mut self, blocks: &[GpuBlockData]) -> GpuCacheHandle {
         let start_index = self.texture.pending_blocks.len();
         self.texture.pending_blocks.extend_from_slice(blocks);
         let location = self.texture
-            .push_data(Some(start_index), blocks.len(), self.frame_id);
+            .push_data(Some(start_index), blocks.len(), self.now);
         GpuCacheHandle {
             location: Some(location),
         }
     }
 
     // Reserve space in the cache for per-frame blocks that
     // will be resolved by the render thread via the
     // external image callback.
     pub fn push_deferred_per_frame_blocks(&mut self, block_count: usize) -> GpuCacheHandle {
-        let location = self.texture.push_data(None, block_count, self.frame_id);
+        let location = self.texture.push_data(None, block_count, self.now);
         GpuCacheHandle {
             location: Some(location),
         }
     }
 
     /// End the frame. Return the list of updates to apply to the
     /// device specific cache texture.
     pub fn end_frame(
         &mut self,
         profile_counters: &mut GpuCacheProfileCounters,
-    ) -> FrameId {
+    ) -> FrameStamp {
         profile_counters
             .allocated_rows
             .set(self.texture.rows.len());
         profile_counters
             .allocated_blocks
             .set(self.texture.allocated_block_count);
         profile_counters
             .saved_blocks
@@ -796,32 +819,32 @@ impl GpuCache {
             self.texture.rows.len() > (GPU_CACHE_INITIAL_HEIGHT as usize) &&
             self.texture.utilization() < RECLAIM_THRESHOLD;
         if reached_threshold {
             self.texture.reached_reclaim_threshold.get_or_insert_with(Instant::now);
         } else {
             self.texture.reached_reclaim_threshold = None;
         }
 
-        self.frame_id
+        self.now
     }
 
     /// Returns true if utilization has been low enough for long enough that we
     /// should blow the cache away and rebuild it.
     pub fn should_reclaim_memory(&self) -> bool {
         self.texture.reached_reclaim_threshold
             .map_or(false, |t| t.elapsed() > Duration::from_secs(RECLAIM_DELAY_S))
     }
 
     /// Extract the pending updates from the cache.
     pub fn extract_updates(&mut self) -> GpuCacheUpdateList {
         let clear = self.pending_clear;
         self.pending_clear = false;
         GpuCacheUpdateList {
-            frame_id: self.frame_id,
+            frame_id: self.now.frame_id(),
             clear,
             height: self.texture.height,
             debug_commands: mem::replace(&mut self.texture.debug_commands, Vec::new()),
             updates: mem::replace(&mut self.texture.updates, Vec::new()),
             blocks: mem::replace(&mut self.texture.pending_blocks, Vec::new()),
         }
     }
 
@@ -834,17 +857,17 @@ impl GpuCache {
     /// Get the actual GPU address in the texture for a given slot ID.
     /// It's assumed at this point that the given slot has been requested
     /// and built for this frame. Attempting to get the address for a
     /// 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.get()];
         debug_assert_eq!(block.epoch, location.epoch);
-        debug_assert_eq!(block.last_access_time, self.frame_id);
+        debug_assert_eq!(block.last_access_time, self.now.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)
     }
 }