Bug 1559295 - Support multiple picture cache tile sizes in WR texture cache. r=kvark
authorGlenn Watson <github@intuitionlibrary.com>
Wed, 19 Jun 2019 15:46:18 +0000
changeset 479210 33ef5e8d04f8a28ce8f6c6ba35ee6ec37babb97d
parent 479209 265a8451194c0a558fde24db7bb44982ad4a8c8e
child 479211 de952dee0cbf379c8e5f3c3d2ab84203b2ba31e4
push id36174
push useropoprus@mozilla.com
push dateWed, 19 Jun 2019 21:38:13 +0000
treeherdermozilla-central@5b9a3de04646 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1559295
milestone69.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 1559295 - Support multiple picture cache tile sizes in WR texture cache. r=kvark In future, picture cache tiles will support different sizes, depending on the size of the content slice being cached, and how frequently parts of the slice are changing. Although currently unused, this patch adds support for specifying multiple different tile sizes for the picture cache texture array. Differential Revision: https://phabricator.services.mozilla.com/D34989
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/resource_cache.rs
gfx/wr/webrender/src/texture_cache.rs
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1208,17 +1208,22 @@ impl TileCacheInstance {
                     scratch.push_debug_rect(
                         tile.world_rect * frame_context.global_device_pixel_scale,
                         debug_colors::RED,
                     );
                 }
 
                 // Ensure that this texture is allocated.
                 if !resource_cache.texture_cache.is_allocated(&tile.handle) {
+                    let tile_size = DeviceIntSize::new(
+                        TILE_SIZE_WIDTH,
+                        TILE_SIZE_HEIGHT,
+                    );
                     resource_cache.texture_cache.update_picture_cache(
+                        tile_size,
                         &mut tile.handle,
                         gpu_cache,
                     );
                 }
 
                 tile.visibility_mask = PrimitiveVisibilityMask::empty();
 
                 // If we run out of dirty regions, then force the last dirty region to
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -2070,23 +2070,27 @@ impl Renderer {
         };
 
         thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(rb_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_started(&rb_thread_name);
             }
 
+            let picture_tile_sizes = &[
+                DeviceIntSize::new(TILE_SIZE_WIDTH, TILE_SIZE_HEIGHT),
+            ];
+
             let texture_cache = TextureCache::new(
                 max_texture_size,
                 max_texture_layers,
                 if config.enable_picture_caching {
-                    Some(DeviceIntSize::new(TILE_SIZE_WIDTH, TILE_SIZE_HEIGHT))
+                    picture_tile_sizes
                 } else {
-                    None
+                    &[]
                 },
                 start_size,
             );
 
             let resource_cache = ResourceCache::new(
                 texture_cache,
                 glyph_rasterizer,
                 blob_image_handler,
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -2187,17 +2187,17 @@ impl ResourceCache {
                 self.cached_render_tasks = cached.render_tasks;
                 self.texture_cache = cached.textures;
             }
             None => {
                 self.current_frame_id = FrameId::INVALID;
                 self.texture_cache = TextureCache::new(
                     self.texture_cache.max_texture_size(),
                     self.texture_cache.max_texture_layers(),
-                    self.texture_cache.picture_tile_size(),
+                    &self.texture_cache.picture_tile_sizes(),
                     DeviceIntSize::zero(),
                 );
             }
         }
 
         self.glyph_rasterizer.reset();
         let res = &mut self.resources;
         res.font_templates.clear();
--- a/gfx/wr/webrender/src/texture_cache.rs
+++ b/gfx/wr/webrender/src/texture_cache.rs
@@ -41,31 +41,34 @@ const RECLAIM_THRESHOLD_BYTES: usize = 5
 /// Items in the texture cache can either be standalone textures,
 /// or a sub-rect inside the shared cache.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 enum EntryDetails {
     Standalone,
     Picture {
+        // Index in the picture_textures array
+        texture_index: usize,
+        // Slice in the texture array
         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,
     },
 }
 
 impl EntryDetails {
     fn describe(&self) -> (LayerIndex, DeviceIntPoint) {
         match *self {
             EntryDetails::Standalone => (0, DeviceIntPoint::zero()),
-            EntryDetails::Picture { layer_index } => (layer_index, 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 {
@@ -472,17 +475,17 @@ impl PerDocumentData {
 /// live view of its contents in Firefox.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCache {
     /// Set of texture arrays in different formats used for the shared cache.
     shared_textures: SharedTextures,
 
     /// A single texture array for picture caching.
-    picture_texture: Option<WholeTextureArray>,
+    picture_textures: Vec<WholeTextureArray>,
 
     /// Maximum texture size supported by hardware.
     max_texture_size: i32,
 
     /// Maximum number of texture layers supported by hardware.
     max_texture_layers: usize,
 
     /// The current set of debug flags.
@@ -521,17 +524,17 @@ pub struct TextureCache {
     /// documents to build a frame.
     require_frame_build: bool,
 }
 
 impl TextureCache {
     pub fn new(
         max_texture_size: i32,
         mut max_texture_layers: usize,
-        picture_tile_size: Option<DeviceIntSize>,
+        picture_tile_sizes: &[DeviceIntSize],
         initial_size: DeviceIntSize,
     ) -> Self {
         if cfg!(target_os = "macos") {
             // On MBP integrated Intel GPUs, texture arrays appear to be
             // implemented as a single texture of stacked layers, and that
             // texture appears to be subject to the texture size limit. As such,
             // allocating more than 32 512x512 regions results in a dimension
             // longer than 16k (the max texture size), causing incorrect behavior.
@@ -551,60 +554,65 @@ impl TextureCache {
             //     the same max texture size of 16k. If we do encounter a driver
             //     with the same bug but a lower max texture size, we might need
             //     to rethink our strategy anyway, since a limit below 32MB might
             //     start to introduce performance issues.
             max_texture_layers = max_texture_layers.min(32);
         }
 
         let mut pending_updates = TextureUpdateList::new();
-        let picture_texture = if let Some(tile_size) = picture_tile_size {
+        let mut picture_textures = Vec::new();
+        let mut next_texture_id = 1;
+
+        for tile_size in picture_tile_sizes {
+            // TODO(gw): The way initial size is used here may allocate a lot of memory once
+            //           we are using multiple slice sizes. Do some measurements once we
+            //           have multiple slices here and adjust the calculations as required.
             let picture_texture = WholeTextureArray {
-                size: tile_size,
+                size: *tile_size,
                 filter: TextureFilter::Nearest,
                 format: PICTURE_TILE_FORMAT,
-                texture_id: CacheTextureId(1),
+                texture_id: CacheTextureId(next_texture_id),
                 slices: {
                     let num_x = (initial_size.width + tile_size.width - 1) / tile_size.width;
                     let num_y = (initial_size.height + tile_size.height - 1) / tile_size.height;
-                    let count = (num_x * num_y).max(1) as usize;
+                    let count = (num_x * num_y).max(1).min(16) as usize;
                     info!("Initializing picture texture with {}x{} slices", num_x, num_y);
                     vec![WholeTextureSlice { uv_rect_handle: None }; count]
                 },
                 has_depth: true,
             };
+            next_texture_id += 1;
             pending_updates.push_alloc(picture_texture.texture_id, picture_texture.to_info());
-            Some(picture_texture)
-        } else {
-            None
-        };
+            picture_textures.push(picture_texture);
+        }
 
         TextureCache {
             shared_textures: SharedTextures::new(),
-            picture_texture,
+            picture_textures,
             reached_reclaim_threshold: None,
             entries: FreeList::new(),
             max_texture_size,
             max_texture_layers,
             debug_flags: DebugFlags::empty(),
-            next_id: CacheTextureId(2),
+            next_id: CacheTextureId(next_texture_id),
             pending_updates,
             now: FrameStamp::INVALID,
             per_doc_data: FastHashMap::default(),
             doc_data: PerDocumentData::new(),
             require_frame_build: false,
         }
     }
 
     /// Creates a TextureCache and sets it up with a valid `FrameStamp`, which
     /// is useful for avoiding panics when instantiating the `TextureCache`
     /// directly from unit test code.
     #[cfg(test)]
     pub fn new_for_testing(max_texture_size: i32, max_texture_layers: usize) -> Self {
-        let mut cache = Self::new(max_texture_size, max_texture_layers, None, DeviceIntSize::zero());
+        let mut cache = Self::new(max_texture_size, max_texture_layers, &[], DeviceIntSize::zero());
         let mut now = FrameStamp::first(DocumentId::new(IdNamespace(1), 1));
         now.advance();
         cache.begin_frame(now);
         cache
     }
 
     pub fn set_debug_flags(&mut self, flags: DebugFlags) {
         self.debug_flags = flags;
@@ -633,17 +641,17 @@ impl TextureCache {
 
     fn clear_standalone(&mut self) {
         debug_assert!(!self.now.is_valid());
         self.clear_kind(EntryKind::Standalone);
     }
 
     fn clear_picture(&mut self) {
         self.clear_kind(EntryKind::Picture);
-        if let Some(ref mut picture_texture) = self.picture_texture {
+        for picture_texture in &mut self.picture_textures {
             if let Some(texture_id) = picture_texture.reset(PICTURE_TEXTURE_ADD_SLICES) {
                 self.pending_updates.push_reset(texture_id, picture_texture.to_info());
             }
         }
     }
 
     fn clear_shared(&mut self) {
         self.unset_doc_data();
@@ -761,20 +769,25 @@ impl TextureCache {
             .update_profile(&mut texture_cache_profile.pages_a8_linear);
         self.shared_textures.array_a16_linear
             .update_profile(&mut texture_cache_profile.pages_a16_linear);
         self.shared_textures.array_rgba8_linear
             .update_profile(&mut texture_cache_profile.pages_rgba8_linear);
         self.shared_textures.array_rgba8_nearest
             .update_profile(&mut texture_cache_profile.pages_rgba8_nearest);
 
-        if let Some(ref picture_texture) = self.picture_texture {
-            picture_texture
-                .update_profile(&mut texture_cache_profile.pages_picture);
+        // For now, this profile counter just accumulates the slices and bytes
+        // from all picture cache texture arrays.
+        let mut picture_slices = 0;
+        let mut picture_bytes = 0;
+        for picture_texture in &self.picture_textures {
+            picture_slices += picture_texture.slices.len();
+            picture_bytes += picture_texture.size_in_bytes();
         }
+        texture_cache_profile.pages_picture.set(picture_slices, picture_bytes);
 
         self.unset_doc_data();
         self.now = FrameStamp::INVALID;
     }
 
     // Request an item in the texture cache. All images that will
     // be used on a frame *must* have request() called on their
     // handle, to update the last used timestamp and ensure
@@ -808,18 +821,18 @@ impl TextureCache {
     }
 
     #[cfg(feature = "replay")]
     pub fn max_texture_layers(&self) -> usize {
         self.max_texture_layers
     }
 
     #[cfg(feature = "replay")]
-    pub fn picture_tile_size(&self) -> Option<DeviceIntSize> {
-        self.picture_texture.as_ref().map(|pt| pt.size)
+    pub fn picture_tile_sizes(&self) -> Vec<DeviceIntSize> {
+        self.picture_textures.iter().map(|pt| pt.size).collect()
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         mem::replace(&mut self.pending_updates, TextureUpdateList::new())
     }
 
     // Update the data stored by a given texture cache handle.
     pub fn update(
@@ -1020,20 +1033,18 @@ impl TextureCache {
             self.doc_data.last_shared_cache_expiration = self.now;
         }
         self.doc_data.handles.shared.len() != old_len
     }
 
     // Free a cache entry from the standalone list or shared cache.
     fn free(&mut self, entry: &CacheEntry) {
         match entry.details {
-            EntryDetails::Picture { layer_index } => {
-                let picture_texture = self.picture_texture
-                    .as_mut()
-                    .expect("Picture caching is expecte to be ON");
+            EntryDetails::Picture { texture_index, layer_index } => {
+                let picture_texture = &mut self.picture_textures[texture_index];
                 picture_texture.slices[layer_index].uv_rect_handle = None;
                 if self.debug_flags.contains(
                     DebugFlags::TEXTURE_CACHE_DBG |
                     DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
                 {
                     self.pending_updates.push_debug_clear(
                         entry.texture_id,
                         DeviceIntPoint::zero(),
@@ -1290,36 +1301,44 @@ impl TextureCache {
         debug_assert!(self.now.is_valid());
         let new_cache_entry = self.allocate_cache_entry(params);
         self.upsert_entry(new_cache_entry, handle)
     }
 
     // Update the data stored by a given texture cache handle for picture caching specifically.
     pub fn update_picture_cache(
         &mut self,
+        tile_size: DeviceIntSize,
         handle: &mut TextureCacheHandle,
         gpu_cache: &mut GpuCache,
     ) {
         debug_assert!(self.now.is_valid());
+        debug_assert!(tile_size.width > 0 && tile_size.height > 0);
 
         if self.entries.get_opt(handle).is_none() {
             let cache_entry = {
-                let picture_texture = self.picture_texture
-                    .as_mut()
-                    .expect("Picture caching is expecte to be ON");
+                let texture_index = self.picture_textures
+                    .iter()
+                    .position(|texture| { texture.size == tile_size })
+                    .expect("Picture caching is expected to be ON");
+                let picture_texture = &mut self.picture_textures[texture_index];
                 let layer_index = match picture_texture.find_free() {
                     Some(index) => index,
                     None => {
                         let index = picture_texture.grow(PICTURE_TEXTURE_ADD_SLICES);
                         let info = picture_texture.to_info();
                         self.pending_updates.push_realloc(picture_texture.texture_id, info);
                         index
                     },
                 };
-                picture_texture.occupy(layer_index, self.now)
+                picture_texture.occupy(
+                    texture_index,
+                    layer_index,
+                    self.now,
+                )
             };
             self.upsert_entry(cache_entry, handle)
         }
 
         // Upload the resource rect and texture array layer.
         self.entries
             .get_opt_mut(handle)
             .expect("BUG: handle must be valid now")
@@ -1629,20 +1648,16 @@ impl WholeTextureArray {
     }
 
     /// Returns the number of GPU bytes consumed by this texture array.
     fn size_in_bytes(&self) -> usize {
         let bpp = self.format.bytes_per_pixel() as usize;
         self.slices.len() * (self.size.width * self.size.height) as usize * bpp
     }
 
-    fn update_profile(&self, counter: &mut ResourceProfileCounter) {
-        counter.set(self.slices.len(), self.size_in_bytes());
-    }
-
     /// Find an free slice.
     fn find_free(&self) -> Option<LayerIndex> {
         self.slices.iter().position(|slice| slice.uv_rect_handle.is_none())
     }
 
     /// Grow the array by the specified number of slices
     fn grow(&mut self, count: usize) -> LayerIndex {
         let index = self.slices.len();
@@ -1650,41 +1665,58 @@ impl WholeTextureArray {
             self.slices.push(WholeTextureSlice {
                 uv_rect_handle: None,
             });
         }
         index
     }
 
     fn cache_entry_impl(
-        &self, layer_index: usize, now: FrameStamp, uv_rect_handle: GpuCacheHandle, texture_id: CacheTextureId,
+        &self,
+        texture_index: usize,
+        layer_index: usize,
+        now: FrameStamp,
+        uv_rect_handle: GpuCacheHandle,
+        texture_id: CacheTextureId,
     ) -> CacheEntry {
         CacheEntry {
             size: self.size,
             user_data: [0.0; 3],
             last_access: now,
             details: EntryDetails::Picture {
+                texture_index,
                 layer_index,
             },
             uv_rect_handle,
             format: self.format,
             filter: self.filter,
             texture_id,
             eviction_notice: None,
             uv_rect_kind: UvRectKind::Rect,
             eviction: Eviction::Eager,
         }
     }
 
     /// Occupy a specified slice by a cache entry.
-    fn occupy(&mut self, layer_index: usize, now: FrameStamp) -> CacheEntry {
+    fn occupy(
+        &mut self,
+        texture_index: usize,
+        layer_index: usize,
+        now: FrameStamp,
+    ) -> CacheEntry {
         let uv_rect_handle = GpuCacheHandle::new();
         assert!(self.slices[layer_index].uv_rect_handle.is_none());
         self.slices[layer_index].uv_rect_handle = Some(uv_rect_handle);
-        self.cache_entry_impl(layer_index, now, uv_rect_handle, self.texture_id)
+        self.cache_entry_impl(
+            texture_index,
+            layer_index,
+            now,
+            uv_rect_handle,
+            self.texture_id,
+        )
     }
 
     /// Reset the texture array to the specified number of slices, if it's larger.
     fn reset(
         &mut self, num_slices: usize
     ) -> Option<CacheTextureId> {
         if self.slices.len() <= num_slices {
             None