Bug 1595768 - Keep empty items in the texture cache. r=gw
authorNicolas Silva <nsilva@mozilla.com>
Fri, 13 Dec 2019 04:55:38 +0000
changeset 506843 0112c670bd65f1214aee2764657690b211eabb6f
parent 506842 a96a38c0b28f9db0359ab77e20fdb6147ca12945
child 506844 2a87eb08886e30ca7bbbc4438220aa5c97a61630
push id36913
push useropoprus@mozilla.com
push dateFri, 13 Dec 2019 16:53:24 +0000
treeherdermozilla-central@1ed684598bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1595768
milestone73.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 1595768 - Keep empty items in the texture cache. r=gw Adds a notion of empty cache items in the texture cache, that are not uploaded into textures but have a cache entry and expire like other types of entries. The motivation for this is to avoid continuously requesting invalid glyphs to be re-rasterized. Currently if a page contains invalid glyphs we gracefully fail to reasterize it but since we don't keep a trace of it in the cache it appears new each frame which cause us to schedule work on the rayon thread pool every frame at great costs. Differential Revision: https://phabricator.services.mozilla.com/D56958
gfx/wr/webrender/src/texture_cache.rs
--- a/gfx/wr/webrender/src/texture_cache.rs
+++ b/gfx/wr/webrender/src/texture_cache.rs
@@ -20,16 +20,17 @@ use crate::profiler::{ResourceProfileCou
 use crate::render_backend::{FrameId, FrameStamp};
 use crate::resource_cache::{CacheItem, CachedImageData};
 use smallvec::SmallVec;
 use std::cell::Cell;
 use std::cmp;
 use std::mem;
 use std::time::{Duration, SystemTime};
 use std::rc::Rc;
+use euclid::size2;
 
 /// The size of each region/layer in shared cache texture arrays.
 pub const TEXTURE_REGION_DIMENSIONS: i32 = 512;
 
 const PICTURE_TEXTURE_ADD_SLICES: usize = 4;
 
 /// The chosen image format for picture tiles.
 const PICTURE_TILE_FORMAT: ImageFormat = ImageFormat::RGBA8;
@@ -56,45 +57,48 @@ enum EntryDetails {
         layer_index: usize,
     },
     Cache {
         /// Origin within the texture layer where this item exists.
         origin: DeviceIntPoint,
         /// The layer index of the texture array.
         layer_index: usize,
     },
+    Empty,
 }
 
 impl EntryDetails {
     fn describe(&self) -> (LayerIndex, DeviceIntPoint) {
         match *self {
-            EntryDetails::Standalone => (0, DeviceIntPoint::zero()),
+            EntryDetails::Standalone | EntryDetails::Empty => (0, DeviceIntPoint::zero()),
             EntryDetails::Picture { layer_index, .. } => (layer_index, DeviceIntPoint::zero()),
             EntryDetails::Cache { origin, layer_index } => (layer_index, origin),
         }
     }
 }
 
 impl EntryDetails {
     /// Returns the kind associated with the details.
     fn kind(&self) -> EntryKind {
         match *self {
             EntryDetails::Standalone => EntryKind::Standalone,
             EntryDetails::Picture { .. } => EntryKind::Picture,
             EntryDetails::Cache { .. } => EntryKind::Shared,
+            EntryDetails::Empty => EntryKind::Empty,
         }
     }
 }
 
 /// Tag identifying standalone-versus-shared, without the details.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 enum EntryKind {
     Standalone,
     Picture,
     Shared,
+    Empty,
 }
 
 #[derive(Debug)]
 pub enum CacheEntryMarker {}
 
 // Stores information related to a single entry in the texture
 // cache. This is stored for each item whether it's in the shared
 // cache or a standalone texture.
@@ -145,16 +149,34 @@ impl CacheEntry {
             swizzle,
             uv_rect_handle: GpuCacheHandle::new(),
             eviction_notice: None,
             uv_rect_kind: params.uv_rect_kind,
             eviction: Eviction::Auto,
         }
     }
 
+    // Create a new entry for a standalone texture.
+    fn new_empty(last_access: FrameStamp) -> Self {
+        CacheEntry {
+            size: size2(0, 0),
+            user_data: [0.0; 3],
+            last_access,
+            details: EntryDetails::Empty,
+            texture_id: CacheTextureId(std::u64::MAX),
+            input_format: ImageFormat::BGRA8,
+            filter: TextureFilter::Linear,
+            swizzle: Swizzle::default(),
+            uv_rect_handle: GpuCacheHandle::new(),
+            eviction_notice: None,
+            uv_rect_kind: UvRectKind::Rect,
+            eviction: Eviction::Auto,
+        }
+    }
+
     // Update the GPU cache for this texture cache entry.
     // This ensures that the UV rect, and texture layer index
     // are up to date in the GPU cache for vertex shaders
     // to fetch from.
     fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
         if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) {
             let (layer_index, origin) = self.details.describe();
             let image_source = ImageSource {
@@ -338,25 +360,28 @@ impl SharedTextures {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct EntryHandles {
     /// Handles for each standalone texture cache entry.
     standalone: Vec<FreeListHandle<CacheEntryMarker>>,
     /// Handles for each picture cache entry.
     picture: Vec<FreeListHandle<CacheEntryMarker>>,
     /// Handles for each shared texture cache entry.
     shared: Vec<FreeListHandle<CacheEntryMarker>>,
+    /// Handles for each shared texture cache entry.
+    empty: Vec<FreeListHandle<CacheEntryMarker>>,
 }
 
 impl EntryHandles {
     /// Mutably borrows the requested handle list.
     fn select(&mut self, kind: EntryKind) -> &mut Vec<FreeListHandle<CacheEntryMarker>> {
         match kind {
             EntryKind::Standalone => &mut self.standalone,
             EntryKind::Picture => &mut self.picture,
             EntryKind::Shared => &mut self.shared,
+            EntryKind::Empty => &mut self.empty,
         }
     }
 }
 
 /// Container struct for the various parameters used in cache allocation.
 struct CacheAllocParams {
     descriptor: ImageDescriptor,
     filter: TextureFilter,
@@ -1162,17 +1187,18 @@ impl TextureCache {
                         origin,
                         region.slab_size.width,
                         region.slab_size.height,
                         layer_index,
                     );
                 }
                 region.free(origin, &mut unit.empty_regions);
             }
-        }
+            EntryDetails::Empty => {}
+         }
     }
 
     /// Check if we can allocate this entry without growing any of the texture cache arrays.
     fn has_space_in_shared_cache(
         &mut self,
         params: &CacheAllocParams,
     ) -> bool {
         let texture_array = self.shared_textures.select(
@@ -1257,17 +1283,18 @@ impl TextureCache {
     ) -> bool {
         let mut allowed_in_shared_cache = true;
 
         // Anything larger than TEXTURE_REGION_DIMENSIONS goes in a standalone texture.
         // TODO(gw): If we find pages that suffer from batch breaks in this
         //           case, add support for storing these in a standalone
         //           texture array.
         if descriptor.size.width > TEXTURE_REGION_DIMENSIONS ||
-           descriptor.size.height > TEXTURE_REGION_DIMENSIONS
+           descriptor.size.height > TEXTURE_REGION_DIMENSIONS ||
+           descriptor.size.is_empty_or_negative()
         {
             allowed_in_shared_cache = false;
         }
 
         // TODO(gw): For now, alpha formats of the texture cache can only be linearly sampled.
         //           Nearest sampling gets a standalone texture.
         //           This is probably rare enough that it can be fixed up later.
         if filter == TextureFilter::Nearest &&
@@ -1318,18 +1345,19 @@ impl TextureCache {
     ///
     /// This allocates from the shared cache unless the parameters do not meet
     /// the shared cache requirements, in which case a standalone texture is
     /// used.
     fn allocate_cache_entry(
         &mut self,
         params: &CacheAllocParams,
     ) -> CacheEntry {
-        assert!(!params.descriptor.size.is_empty_or_negative());
-
+        if params.descriptor.size.is_empty_or_negative() {
+            return CacheEntry::new_empty(self.now);
+        }
         // If this image doesn't qualify to go in the shared (batching) cache,
         // allocate a standalone entry.
         if self.is_allowed_in_shared_cache(params.filter, &params.descriptor) {
             if !self.has_space_in_shared_cache(params) {
                 // If we don't have extra space and haven't GCed this frame, do so.
                 let threshold = self.default_eviction();
                 self.maybe_expire_old_shared_entries(threshold);
             }
@@ -1358,17 +1386,17 @@ impl TextureCache {
             UpsertResult::Updated(old_entry) => {
                 if new_kind != old_entry.details.kind() {
                     // Handle the rare case than an update moves an entry from
                     // shared to standalone or vice versa. This involves a linear
                     // search, but should be rare enough not to matter.
                     let (from, to) = match new_kind {
                         EntryKind::Standalone =>
                             (&mut self.doc_data.handles.shared, &mut self.doc_data.handles.standalone),
-                        EntryKind::Picture => unreachable!(),
+                        EntryKind::Picture | EntryKind::Empty => unreachable!(),
                         EntryKind::Shared =>
                             (&mut self.doc_data.handles.standalone, &mut self.doc_data.handles.shared),
                     };
                     let idx = from.iter().position(|h| h.weak() == *handle).unwrap();
                     to.push(from.remove(idx));
                 }
                 self.free(&old_entry);
             }