tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

texture_cache.rs (69759B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use api::{DirtyRect, ExternalImageType, ImageFormat, ImageBufferKind};
      6 use api::{DebugFlags, ImageDescriptor, TextureCacheCategory};
      7 use api::units::*;
      8 #[cfg(test)]
      9 use api::{DocumentId, IdNamespace};
     10 use crate::device::{TextureFilter, TextureFormatPair};
     11 use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
     12 use crate::gpu_types::{ImageSource, UvRectKind};
     13 use crate::internal_types::{
     14    CacheTextureId, Swizzle, SwizzleSettings, FrameStamp, FrameId,
     15    TextureUpdateList, TextureUpdateSource, TextureSource,
     16    TextureCacheAllocInfo, TextureCacheUpdate,
     17 };
     18 use crate::lru_cache::LRUCache;
     19 use crate::profiler::{self, TransactionProfile};
     20 use crate::renderer::{GpuBufferBuilderF, GpuBufferHandle};
     21 use crate::resource_cache::{CacheItem, CachedImageData};
     22 use crate::texture_pack::{
     23    AllocatorList, AllocId, AtlasAllocatorList, ShelfAllocator, ShelfAllocatorOptions,
     24 };
     25 use std::cell::Cell;
     26 use std::mem;
     27 use std::rc::Rc;
     28 use euclid::size2;
     29 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
     30 
     31 /// Information about which shader will use the entry.
     32 ///
     33 /// For batching purposes, it's beneficial to group some items in their
     34 /// own textures if we know that they are used by a specific shader.
     35 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
     36 #[cfg_attr(feature = "capture", derive(Serialize))]
     37 #[cfg_attr(feature = "replay", derive(Deserialize))]
     38 pub enum TargetShader {
     39    Default,
     40    Text,
     41 }
     42 
     43 /// The size of each region in shared cache texture arrays.
     44 pub const TEXTURE_REGION_DIMENSIONS: i32 = 512;
     45 
     46 /// Items in the texture cache can either be standalone textures,
     47 /// or a sub-rect inside the shared cache.
     48 #[derive(Clone, Debug)]
     49 #[cfg_attr(feature = "capture", derive(Serialize))]
     50 #[cfg_attr(feature = "replay", derive(Deserialize))]
     51 pub enum EntryDetails {
     52    Standalone {
     53        /// Number of bytes this entry allocates
     54        size_in_bytes: usize,
     55    },
     56    Cache {
     57        /// Origin within the texture layer where this item exists.
     58        origin: DeviceIntPoint,
     59        /// ID of the allocation specific to its allocator.
     60        alloc_id: AllocId,
     61        /// The allocated size in bytes for this entry.
     62        allocated_size_in_bytes: usize,
     63    },
     64 }
     65 
     66 impl EntryDetails {
     67    fn describe(&self) -> DeviceIntPoint {
     68        match *self {
     69            EntryDetails::Standalone { .. }  => DeviceIntPoint::zero(),
     70            EntryDetails::Cache { origin, .. } => origin,
     71        }
     72    }
     73 }
     74 
     75 #[derive(Debug, PartialEq)]
     76 #[cfg_attr(feature = "capture", derive(Serialize))]
     77 #[cfg_attr(feature = "replay", derive(Deserialize))]
     78 pub enum AutoCacheEntryMarker {}
     79 
     80 #[derive(Debug, PartialEq)]
     81 #[cfg_attr(feature = "capture", derive(Serialize))]
     82 #[cfg_attr(feature = "replay", derive(Deserialize))]
     83 pub enum ManualCacheEntryMarker {}
     84 
     85 // Stores information related to a single entry in the texture
     86 // cache. This is stored for each item whether it's in the shared
     87 // cache or a standalone texture.
     88 #[derive(Debug)]
     89 #[cfg_attr(feature = "capture", derive(Serialize))]
     90 #[cfg_attr(feature = "replay", derive(Deserialize))]
     91 pub struct CacheEntry {
     92    /// Size of the requested item, in device pixels. Does not include any
     93    /// padding for alignment that the allocator may have added to this entry's
     94    /// allocation.
     95    pub size: DeviceIntSize,
     96    /// Details specific to standalone or shared items.
     97    pub details: EntryDetails,
     98    /// Arbitrary user data associated with this item.
     99    pub user_data: [f32; 4],
    100    /// The last frame this item was requested for rendering.
    101    pub last_access: FrameStamp,
    102    /// Address of the resource rect in the GPU cache.
    103    ///
    104    /// The handle is stored in the cache entry to avoid duplicates when an
    105    /// item is used multiple times per frame, but greate care must be taken
    106    /// to not reuse a handle that was created in a previous frame.
    107    /// TODO: For now the validity of the handle can be checked by comparing
    108    /// last_access with the current FrameStamp, but this is error prone.
    109    pub uv_rect_handle: GpuBufferHandle,
    110    /// Image format of the data that the entry expects.
    111    pub input_format: ImageFormat,
    112    pub filter: TextureFilter,
    113    pub swizzle: Swizzle,
    114    /// The actual device texture ID this is part of.
    115    pub texture_id: CacheTextureId,
    116    /// Optional notice when the entry is evicted from the cache.
    117    pub eviction_notice: Option<EvictionNotice>,
    118    /// The type of UV rect this entry specifies.
    119    pub uv_rect_kind: UvRectKind,
    120 
    121    pub shader: TargetShader,
    122 }
    123 
    124 malloc_size_of::malloc_size_of_is_0!(
    125    CacheEntry,
    126    AutoCacheEntryMarker, ManualCacheEntryMarker
    127 );
    128 
    129 impl CacheEntry {
    130    // Create a new entry for a standalone texture.
    131    fn new_standalone(
    132        texture_id: CacheTextureId,
    133        last_access: FrameStamp,
    134        params: &CacheAllocParams,
    135        swizzle: Swizzle,
    136        size_in_bytes: usize,
    137    ) -> Self {
    138        CacheEntry {
    139            size: params.descriptor.size,
    140            user_data: params.user_data,
    141            last_access,
    142            details: EntryDetails::Standalone {
    143                size_in_bytes,
    144            },
    145            texture_id,
    146            input_format: params.descriptor.format,
    147            filter: params.filter,
    148            swizzle,
    149            uv_rect_handle: GpuBufferHandle::INVALID,
    150            eviction_notice: None,
    151            uv_rect_kind: params.uv_rect_kind,
    152            shader: TargetShader::Default,
    153        }
    154    }
    155 
    156    // Update the GPU cache for this texture cache entry.
    157    // This ensures that the UV rect, and texture layer index
    158    // are up to date in the GPU cache for vertex shaders
    159    // to fetch from.
    160    fn write_gpu_blocks(&mut self, gpu_buffer: &mut GpuBufferBuilderF) {
    161        let origin = self.details.describe();
    162        let image_source = ImageSource {
    163            p0: origin.to_f32(),
    164            p1: (origin + self.size).to_f32(),
    165            user_data: self.user_data,
    166            uv_rect_kind: self.uv_rect_kind,
    167        };
    168        self.uv_rect_handle = image_source.write_gpu_blocks(gpu_buffer);
    169    }
    170 
    171    fn evict(&self) {
    172        if let Some(eviction_notice) = self.eviction_notice.as_ref() {
    173            eviction_notice.notify();
    174        }
    175    }
    176 
    177    fn alternative_input_format(&self) -> ImageFormat {
    178        match self.input_format {
    179            ImageFormat::RGBA8 => ImageFormat::BGRA8,
    180            ImageFormat::BGRA8 => ImageFormat::RGBA8,
    181            other => other,
    182        }
    183    }
    184 }
    185 
    186 
    187 /// A texture cache handle is a weak reference to a cache entry.
    188 ///
    189 /// If the handle has not been inserted into the cache yet, or if the entry was
    190 /// previously inserted and then evicted, lookup of the handle will fail, and
    191 /// the cache handle needs to re-upload this item to the texture cache (see
    192 /// request() below).
    193 
    194 #[derive(MallocSizeOf,Clone,PartialEq,Debug)]
    195 #[cfg_attr(feature = "capture", derive(Serialize))]
    196 #[cfg_attr(feature = "replay", derive(Deserialize))]
    197 pub enum TextureCacheHandle {
    198    /// A fresh handle.
    199    Empty,
    200 
    201    /// A handle for an entry with automatic eviction.
    202    Auto(WeakFreeListHandle<AutoCacheEntryMarker>),
    203 
    204    /// A handle for an entry with manual eviction.
    205    Manual(WeakFreeListHandle<ManualCacheEntryMarker>)
    206 }
    207 
    208 impl TextureCacheHandle {
    209    pub fn invalid() -> Self {
    210        TextureCacheHandle::Empty
    211    }
    212 }
    213 
    214 /// Describes the eviction policy for a given entry in the texture cache.
    215 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    216 #[cfg_attr(feature = "capture", derive(Serialize))]
    217 #[cfg_attr(feature = "replay", derive(Deserialize))]
    218 pub enum Eviction {
    219    /// The entry will be evicted under the normal rules (which differ between
    220    /// standalone and shared entries).
    221    Auto,
    222    /// The entry will not be evicted until the policy is explicitly set to a
    223    /// different value.
    224    Manual,
    225 }
    226 
    227 // An eviction notice is a shared condition useful for detecting
    228 // when a TextureCacheHandle gets evicted from the TextureCache.
    229 // It is optionally installed to the TextureCache when an update()
    230 // is scheduled. A single notice may be shared among any number of
    231 // TextureCacheHandle updates. The notice may then be subsequently
    232 // checked to see if any of the updates using it have been evicted.
    233 #[derive(Clone, Debug, Default)]
    234 #[cfg_attr(feature = "capture", derive(Serialize))]
    235 #[cfg_attr(feature = "replay", derive(Deserialize))]
    236 pub struct EvictionNotice {
    237    evicted: Rc<Cell<bool>>,
    238 }
    239 
    240 impl EvictionNotice {
    241    fn notify(&self) {
    242        self.evicted.set(true);
    243    }
    244 
    245    pub fn check(&self) -> bool {
    246        if self.evicted.get() {
    247            self.evicted.set(false);
    248            true
    249        } else {
    250            false
    251        }
    252    }
    253 }
    254 
    255 /// The different budget types for the texture cache. Each type has its own
    256 /// memory budget. Once the budget is exceeded, entries with automatic eviction
    257 /// are evicted. Entries with manual eviction share the same budget but are not
    258 /// evicted once the budget is exceeded.
    259 /// Keeping separate budgets ensures that we don't evict entries from unrelated
    260 /// textures if one texture gets full.
    261 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    262 #[repr(u8)]
    263 #[cfg_attr(feature = "capture", derive(Serialize))]
    264 #[cfg_attr(feature = "replay", derive(Deserialize))]
    265 enum BudgetType {
    266    SharedColor8Linear,
    267    SharedColor8Nearest,
    268    SharedColor8Glyphs,
    269    SharedAlpha8,
    270    SharedAlpha8Glyphs,
    271    SharedAlpha16,
    272    Standalone,
    273 }
    274 
    275 impl BudgetType {
    276    pub const COUNT: usize = 7;
    277 
    278    pub const VALUES: [BudgetType; BudgetType::COUNT] = [
    279        BudgetType::SharedColor8Linear,
    280        BudgetType::SharedColor8Nearest,
    281        BudgetType::SharedColor8Glyphs,
    282        BudgetType::SharedAlpha8,
    283        BudgetType::SharedAlpha8Glyphs,
    284        BudgetType::SharedAlpha16,
    285        BudgetType::Standalone,
    286    ];
    287 
    288    pub const PRESSURE_COUNTERS: [usize; BudgetType::COUNT] = [
    289        profiler::ATLAS_COLOR8_LINEAR_PRESSURE,
    290        profiler::ATLAS_COLOR8_NEAREST_PRESSURE,
    291        profiler::ATLAS_COLOR8_GLYPHS_PRESSURE,
    292        profiler::ATLAS_ALPHA8_PRESSURE,
    293        profiler::ATLAS_ALPHA8_GLYPHS_PRESSURE,
    294        profiler::ATLAS_ALPHA16_PRESSURE,
    295        profiler::ATLAS_STANDALONE_PRESSURE,
    296    ];
    297 
    298    pub fn iter() -> impl Iterator<Item = BudgetType> {
    299        BudgetType::VALUES.iter().cloned()
    300    }
    301 }
    302 
    303 /// A set of lazily allocated, fixed size, texture arrays for each format the
    304 /// texture cache supports.
    305 #[cfg_attr(feature = "capture", derive(Serialize))]
    306 #[cfg_attr(feature = "replay", derive(Deserialize))]
    307 struct SharedTextures {
    308    color8_nearest: AllocatorList<ShelfAllocator, TextureParameters>,
    309    alpha8_linear: AllocatorList<ShelfAllocator, TextureParameters>,
    310    alpha8_glyphs: AllocatorList<ShelfAllocator, TextureParameters>,
    311    alpha16_linear: AllocatorList<ShelfAllocator, TextureParameters>,
    312    color8_linear: AllocatorList<ShelfAllocator, TextureParameters>,
    313    color8_glyphs: AllocatorList<ShelfAllocator, TextureParameters>,
    314    bytes_per_texture_of_type: [i32 ; BudgetType::COUNT],
    315    next_compaction_idx: usize,
    316 }
    317 
    318 impl SharedTextures {
    319    /// Mints a new set of shared textures.
    320    fn new(color_formats: TextureFormatPair<ImageFormat>, config: &TextureCacheConfig) -> Self {
    321        let mut bytes_per_texture_of_type = [0 ; BudgetType::COUNT];
    322 
    323        // Used primarily for cached shadow masks. There can be lots of
    324        // these on some pages like francine, but most pages don't use it
    325        // much.
    326        // Most content tends to fit into two 512x512 textures. We are
    327        // conservatively using 1024x1024 to fit everything in a single
    328        // texture and avoid breaking batches, but it's worth checking
    329        // whether it would actually lead to a lot of batch breaks in
    330        // practice.
    331        let alpha8_linear = AllocatorList::new(
    332            config.alpha8_texture_size,
    333            ShelfAllocatorOptions {
    334                num_columns: 1,
    335                alignment: size2(8, 8),
    336                .. ShelfAllocatorOptions::default()
    337            },
    338            TextureParameters {
    339                formats: TextureFormatPair::from(ImageFormat::R8),
    340                filter: TextureFilter::Linear,
    341            },
    342        );
    343        bytes_per_texture_of_type[BudgetType::SharedAlpha8 as usize] =
    344            config.alpha8_texture_size * config.alpha8_texture_size;
    345 
    346        // The cache for alpha glyphs (separate to help with batching).
    347        let alpha8_glyphs = AllocatorList::new(
    348            config.alpha8_glyph_texture_size,
    349            ShelfAllocatorOptions {
    350                num_columns: if config.alpha8_glyph_texture_size >= 1024 { 2 } else { 1 },
    351                alignment: size2(4, 8),
    352                .. ShelfAllocatorOptions::default()
    353            },
    354            TextureParameters {
    355                formats: TextureFormatPair::from(ImageFormat::R8),
    356                filter: TextureFilter::Linear,
    357            },
    358        );
    359        bytes_per_texture_of_type[BudgetType::SharedAlpha8Glyphs as usize] =
    360            config.alpha8_glyph_texture_size * config.alpha8_glyph_texture_size;
    361 
    362        // Used for experimental hdr yuv texture support, but not used in
    363        // production Firefox.
    364        let alpha16_linear = AllocatorList::new(
    365            config.alpha16_texture_size,
    366            ShelfAllocatorOptions {
    367                num_columns: if config.alpha16_texture_size >= 1024 { 2 } else { 1 },
    368                alignment: size2(8, 8),
    369                .. ShelfAllocatorOptions::default()
    370            },
    371            TextureParameters {
    372                formats: TextureFormatPair::from(ImageFormat::R16),
    373                filter: TextureFilter::Linear,
    374            },
    375        );
    376        bytes_per_texture_of_type[BudgetType::SharedAlpha16 as usize] =
    377            ImageFormat::R16.bytes_per_pixel() *
    378            config.alpha16_texture_size * config.alpha16_texture_size;
    379 
    380        // The primary cache for images, etc.
    381        let color8_linear = AllocatorList::new(
    382            config.color8_linear_texture_size,
    383            ShelfAllocatorOptions {
    384                num_columns: if config.color8_linear_texture_size >= 1024 { 2 } else { 1 },
    385                alignment: size2(16, 16),
    386                .. ShelfAllocatorOptions::default()
    387            },
    388            TextureParameters {
    389                formats: color_formats.clone(),
    390                filter: TextureFilter::Linear,
    391            },
    392        );
    393        bytes_per_texture_of_type[BudgetType::SharedColor8Linear as usize] =
    394            color_formats.internal.bytes_per_pixel() *
    395            config.color8_linear_texture_size * config.color8_linear_texture_size;
    396 
    397        // The cache for subpixel-AA and bitmap glyphs (separate to help with batching).
    398        let color8_glyphs = AllocatorList::new(
    399            config.color8_glyph_texture_size,
    400            ShelfAllocatorOptions {
    401                num_columns: if config.color8_glyph_texture_size >= 1024 { 2 } else { 1 },
    402                alignment: size2(4, 8),
    403                .. ShelfAllocatorOptions::default()
    404            },
    405            TextureParameters {
    406                formats: color_formats.clone(),
    407                filter: TextureFilter::Linear,
    408            },
    409        );
    410        bytes_per_texture_of_type[BudgetType::SharedColor8Glyphs as usize] =
    411            color_formats.internal.bytes_per_pixel() *
    412            config.color8_glyph_texture_size * config.color8_glyph_texture_size;
    413 
    414        // Used for image-rendering: crisp. This is mostly favicons, which
    415        // are small. Some other images use it too, but those tend to be
    416        // larger than 512x512 and thus don't use the shared cache anyway.
    417        let color8_nearest = AllocatorList::new(
    418            config.color8_nearest_texture_size,
    419            ShelfAllocatorOptions::default(),
    420            TextureParameters {
    421                formats: color_formats.clone(),
    422                filter: TextureFilter::Nearest,
    423            }
    424        );
    425        bytes_per_texture_of_type[BudgetType::SharedColor8Nearest as usize] =
    426            color_formats.internal.bytes_per_pixel() *
    427            config.color8_nearest_texture_size * config.color8_nearest_texture_size;
    428 
    429        Self {
    430            alpha8_linear,
    431            alpha8_glyphs,
    432            alpha16_linear,
    433            color8_linear,
    434            color8_glyphs,
    435            color8_nearest,
    436            bytes_per_texture_of_type,
    437            next_compaction_idx: 0,
    438        }
    439    }
    440 
    441    /// Clears each texture in the set, with the given set of pending updates.
    442    fn clear(&mut self, updates: &mut TextureUpdateList) {
    443        let texture_dealloc_cb = &mut |texture_id| {
    444            updates.push_free(texture_id);
    445        };
    446 
    447        self.alpha8_linear.clear(texture_dealloc_cb);
    448        self.alpha8_glyphs.clear(texture_dealloc_cb);
    449        self.alpha16_linear.clear(texture_dealloc_cb);
    450        self.color8_linear.clear(texture_dealloc_cb);
    451        self.color8_nearest.clear(texture_dealloc_cb);
    452        self.color8_glyphs.clear(texture_dealloc_cb);
    453    }
    454 
    455    /// Returns a mutable borrow for the shared texture array matching the parameters.
    456    fn select(
    457        &mut self, external_format: ImageFormat, filter: TextureFilter, shader: TargetShader,
    458    ) -> (&mut dyn AtlasAllocatorList<TextureParameters>, BudgetType) {
    459        match external_format {
    460            ImageFormat::R8 => {
    461                assert_eq!(filter, TextureFilter::Linear);
    462                match shader {
    463                    TargetShader::Text => {
    464                        (&mut self.alpha8_glyphs, BudgetType::SharedAlpha8Glyphs)
    465                    },
    466                    _ => (&mut self.alpha8_linear, BudgetType::SharedAlpha8),
    467                }
    468            }
    469            ImageFormat::R16 => {
    470                assert_eq!(filter, TextureFilter::Linear);
    471                (&mut self.alpha16_linear, BudgetType::SharedAlpha16)
    472            }
    473            ImageFormat::RGBA8 |
    474            ImageFormat::BGRA8 => {
    475                match (filter, shader) {
    476                    (TextureFilter::Linear, TargetShader::Text) => {
    477                        (&mut self.color8_glyphs, BudgetType::SharedColor8Glyphs)
    478                    },
    479                    (TextureFilter::Linear, _) => {
    480                        (&mut self.color8_linear, BudgetType::SharedColor8Linear)
    481                    },
    482                    (TextureFilter::Nearest, _) => {
    483                        (&mut self.color8_nearest, BudgetType::SharedColor8Nearest)
    484                    },
    485                    _ => panic!("Unexpected filter {:?}", filter),
    486                }
    487            }
    488            _ => panic!("Unexpected format {:?}", external_format),
    489        }
    490    }
    491 
    492    /// How many bytes a single texture of the given type takes up, for the
    493    /// configured texture sizes.
    494    fn bytes_per_shared_texture(&self, budget_type: BudgetType) -> usize {
    495        self.bytes_per_texture_of_type[budget_type as usize] as usize
    496    }
    497 
    498    fn has_multiple_textures(&self, budget_type: BudgetType) -> bool {
    499        match budget_type {
    500            BudgetType::SharedColor8Linear => self.color8_linear.allocated_textures() > 1,
    501            BudgetType::SharedColor8Nearest => self.color8_nearest.allocated_textures() > 1,
    502            BudgetType::SharedColor8Glyphs => self.color8_glyphs.allocated_textures() > 1,
    503            BudgetType::SharedAlpha8 => self.alpha8_linear.allocated_textures() > 1,
    504            BudgetType::SharedAlpha8Glyphs => self.alpha8_glyphs.allocated_textures() > 1,
    505            BudgetType::SharedAlpha16 => self.alpha16_linear.allocated_textures() > 1,
    506            BudgetType::Standalone => false,
    507        }
    508    }
    509 }
    510 
    511 /// Container struct for the various parameters used in cache allocation.
    512 struct CacheAllocParams {
    513    descriptor: ImageDescriptor,
    514    filter: TextureFilter,
    515    user_data: [f32; 4],
    516    uv_rect_kind: UvRectKind,
    517    shader: TargetShader,
    518 }
    519 
    520 /// Startup parameters for the texture cache.
    521 ///
    522 /// Texture sizes must be at least 512.
    523 #[derive(Clone)]
    524 pub struct TextureCacheConfig {
    525    pub color8_linear_texture_size: i32,
    526    pub color8_nearest_texture_size: i32,
    527    pub color8_glyph_texture_size: i32,
    528    pub alpha8_texture_size: i32,
    529    pub alpha8_glyph_texture_size: i32,
    530    pub alpha16_texture_size: i32,
    531 }
    532 
    533 impl TextureCacheConfig {
    534    pub const DEFAULT: Self = TextureCacheConfig {
    535        color8_linear_texture_size: 2048,
    536        color8_nearest_texture_size: 512,
    537        color8_glyph_texture_size: 2048,
    538        alpha8_texture_size: 1024,
    539        alpha8_glyph_texture_size: 2048,
    540        alpha16_texture_size: 512,
    541    };
    542 }
    543 
    544 /// General-purpose manager for images in GPU memory. This includes images,
    545 /// rasterized glyphs, rasterized blobs, cached render tasks, etc.
    546 ///
    547 /// The texture cache is owned and managed by the RenderBackend thread, and
    548 /// produces a series of commands to manipulate the textures on the Renderer
    549 /// thread. These commands are executed before any rendering is performed for
    550 /// a given frame.
    551 ///
    552 /// Entries in the texture cache are not guaranteed to live past the end of the
    553 /// frame in which they are requested, and may be evicted. The API supports
    554 /// querying whether an entry is still available.
    555 ///
    556 /// The texture cache can be visualized, which is a good way to understand how
    557 /// it works. Enabling gfx.webrender.debug.texture-cache shows a live view of
    558 /// its contents in Firefox.
    559 #[cfg_attr(feature = "capture", derive(Serialize))]
    560 #[cfg_attr(feature = "replay", derive(Deserialize))]
    561 pub struct TextureCache {
    562    /// Set of texture arrays in different formats used for the shared cache.
    563    shared_textures: SharedTextures,
    564 
    565    /// Maximum texture size supported by hardware.
    566    max_texture_size: i32,
    567 
    568    /// Maximum texture size before it is considered preferable to break the
    569    /// texture into tiles.
    570    tiling_threshold: i32,
    571 
    572    /// Settings on using texture unit swizzling.
    573    swizzle: Option<SwizzleSettings>,
    574 
    575    /// The current set of debug flags.
    576    debug_flags: DebugFlags,
    577 
    578    /// The next unused virtual texture ID. Monotonically increasing.
    579    pub next_id: CacheTextureId,
    580 
    581    /// A list of allocations and updates that need to be applied to the texture
    582    /// cache in the rendering thread this frame.
    583    #[cfg_attr(all(feature = "serde", any(feature = "capture", feature = "replay")), serde(skip))]
    584    pub pending_updates: TextureUpdateList,
    585 
    586    /// The current `FrameStamp`. Used for cache eviction policies.
    587    pub now: FrameStamp,
    588 
    589    /// Cache of texture cache handles with automatic lifetime management, evicted
    590    /// in a least-recently-used order.
    591    lru_cache: LRUCache<CacheEntry, AutoCacheEntryMarker>,
    592 
    593    /// Cache of texture cache entries with manual liftime management.
    594    manual_entries: FreeList<CacheEntry, ManualCacheEntryMarker>,
    595 
    596    /// Strong handles for the manual_entries FreeList.
    597    manual_handles: Vec<FreeListHandle<ManualCacheEntryMarker>>,
    598 
    599    /// Memory usage of allocated entries in all of the shared or standalone
    600    /// textures. Includes both manually and automatically evicted entries.
    601    bytes_allocated: [usize ; BudgetType::COUNT],
    602 }
    603 
    604 impl TextureCache {
    605    /// The maximum number of items that will be evicted per frame. This limit helps avoid jank
    606    /// on frames where we want to evict a large number of items. Instead, we'd prefer to drop
    607    /// the items incrementally over a number of frames, even if that means the total allocated
    608    /// size of the cache is above the desired threshold for a small number of frames.
    609    const MAX_EVICTIONS_PER_FRAME: usize = 32;
    610 
    611    pub fn new(
    612        max_texture_size: i32,
    613        tiling_threshold: i32,
    614        color_formats: TextureFormatPair<ImageFormat>,
    615        swizzle: Option<SwizzleSettings>,
    616        config: &TextureCacheConfig,
    617    ) -> Self {
    618        let pending_updates = TextureUpdateList::new();
    619 
    620        // Shared texture cache controls swizzling on a per-entry basis, assuming that
    621        // the texture as a whole doesn't need to be swizzled (but only some entries do).
    622        // It would be possible to support this, but not needed at the moment.
    623        assert!(color_formats.internal != ImageFormat::BGRA8 ||
    624            swizzle.map_or(true, |s| s.bgra8_sampling_swizzle == Swizzle::default())
    625        );
    626 
    627        let next_texture_id = CacheTextureId(1);
    628 
    629        TextureCache {
    630            shared_textures: SharedTextures::new(color_formats, config),
    631            max_texture_size,
    632            tiling_threshold,
    633            swizzle,
    634            debug_flags: DebugFlags::empty(),
    635            next_id: next_texture_id,
    636            pending_updates,
    637            now: FrameStamp::INVALID,
    638            lru_cache: LRUCache::new(BudgetType::COUNT),
    639            manual_entries: FreeList::new(),
    640            manual_handles: Vec::new(),
    641            bytes_allocated: [0 ; BudgetType::COUNT],
    642        }
    643    }
    644 
    645    /// Creates a TextureCache and sets it up with a valid `FrameStamp`, which
    646    /// is useful for avoiding panics when instantiating the `TextureCache`
    647    /// directly from unit test code.
    648    #[cfg(test)]
    649    pub fn new_for_testing(
    650        max_texture_size: i32,
    651        image_format: ImageFormat,
    652    ) -> Self {
    653        let mut cache = Self::new(
    654            max_texture_size,
    655            max_texture_size,
    656            TextureFormatPair::from(image_format),
    657            None,
    658            &TextureCacheConfig::DEFAULT,
    659        );
    660        let mut now = FrameStamp::first(DocumentId::new(IdNamespace(1), 1));
    661        now.advance();
    662        cache.begin_frame(now, &mut TransactionProfile::new());
    663        cache
    664    }
    665 
    666    pub fn set_debug_flags(&mut self, flags: DebugFlags) {
    667        self.debug_flags = flags;
    668    }
    669 
    670    /// Clear all entries in the texture cache. This is a fairly drastic
    671    /// step that should only be called very rarely.
    672    pub fn clear_all(&mut self) {
    673        // Evict all manual eviction handles
    674        let manual_handles = mem::replace(
    675            &mut self.manual_handles,
    676            Vec::new(),
    677        );
    678        for handle in manual_handles {
    679            let entry = self.manual_entries.free(handle);
    680            self.evict_impl(entry);
    681        }
    682 
    683        // Evict all auto (LRU) cache handles
    684        for budget_type in BudgetType::iter() {
    685            while let Some(entry) = self.lru_cache.pop_oldest(budget_type as u8) {
    686                entry.evict();
    687                self.free(&entry);
    688            }
    689        }
    690 
    691        // Free the picture and shared textures
    692        self.shared_textures.clear(&mut self.pending_updates);
    693        self.pending_updates.note_clear();
    694    }
    695 
    696    /// Called at the beginning of each frame.
    697    pub fn begin_frame(&mut self, stamp: FrameStamp, profile: &mut TransactionProfile) {
    698        debug_assert!(!self.now.is_valid());
    699        profile_scope!("begin_frame");
    700        self.now = stamp;
    701 
    702        // Texture cache eviction is done at the start of the frame. This ensures that
    703        // we won't evict items that have been requested on this frame.
    704        // It also frees up space in the cache for items allocated later in the frame
    705        // potentially reducing texture allocations and fragmentation.
    706        self.evict_items_from_cache_if_required(profile);
    707    }
    708 
    709    pub fn end_frame(&mut self, profile: &mut TransactionProfile) {
    710        debug_assert!(self.now.is_valid());
    711 
    712        let updates = &mut self.pending_updates; // To avoid referring to self in the closure.
    713        let callback = &mut|texture_id| { updates.push_free(texture_id); };
    714 
    715        // Release of empty shared textures is done at the end of the frame. That way, if the
    716        // eviction at the start of the frame frees up a texture, that is then subsequently
    717        // used during the frame, we avoid doing a free/alloc for it.
    718        self.shared_textures.alpha8_linear.release_empty_textures(callback);
    719        self.shared_textures.alpha8_glyphs.release_empty_textures(callback);
    720        self.shared_textures.alpha16_linear.release_empty_textures(callback);
    721        self.shared_textures.color8_linear.release_empty_textures(callback);
    722        self.shared_textures.color8_nearest.release_empty_textures(callback);
    723        self.shared_textures.color8_glyphs.release_empty_textures(callback);
    724 
    725        for budget in BudgetType::iter() {
    726            let threshold = self.get_eviction_threshold(budget);
    727            let pressure = self.bytes_allocated[budget as usize] as f32 / threshold as f32;
    728            profile.set(BudgetType::PRESSURE_COUNTERS[budget as usize], pressure);
    729        }
    730 
    731        profile.set(profiler::ATLAS_A8_PIXELS, self.shared_textures.alpha8_linear.allocated_space());
    732        profile.set(profiler::ATLAS_A8_TEXTURES, self.shared_textures.alpha8_linear.allocated_textures());
    733        profile.set(profiler::ATLAS_A8_GLYPHS_PIXELS, self.shared_textures.alpha8_glyphs.allocated_space());
    734        profile.set(profiler::ATLAS_A8_GLYPHS_TEXTURES, self.shared_textures.alpha8_glyphs.allocated_textures());
    735        profile.set(profiler::ATLAS_A16_PIXELS, self.shared_textures.alpha16_linear.allocated_space());
    736        profile.set(profiler::ATLAS_A16_TEXTURES, self.shared_textures.alpha16_linear.allocated_textures());
    737        profile.set(profiler::ATLAS_RGBA8_LINEAR_PIXELS, self.shared_textures.color8_linear.allocated_space());
    738        profile.set(profiler::ATLAS_RGBA8_LINEAR_TEXTURES, self.shared_textures.color8_linear.allocated_textures());
    739        profile.set(profiler::ATLAS_RGBA8_NEAREST_PIXELS, self.shared_textures.color8_nearest.allocated_space());
    740        profile.set(profiler::ATLAS_RGBA8_NEAREST_TEXTURES, self.shared_textures.color8_nearest.allocated_textures());
    741        profile.set(profiler::ATLAS_RGBA8_GLYPHS_PIXELS, self.shared_textures.color8_glyphs.allocated_space());
    742        profile.set(profiler::ATLAS_RGBA8_GLYPHS_TEXTURES, self.shared_textures.color8_glyphs.allocated_textures());
    743 
    744        let shared_bytes = [
    745            BudgetType::SharedColor8Linear,
    746            BudgetType::SharedColor8Nearest,
    747            BudgetType::SharedColor8Glyphs,
    748            BudgetType::SharedAlpha8,
    749            BudgetType::SharedAlpha8Glyphs,
    750            BudgetType::SharedAlpha16,
    751        ].iter().map(|b| self.bytes_allocated[*b as usize]).sum();
    752 
    753        profile.set(profiler::ATLAS_ITEMS_MEM, profiler::bytes_to_mb(shared_bytes));
    754 
    755        self.now = FrameStamp::INVALID;
    756    }
    757 
    758    pub fn run_compaction(&mut self) {
    759        // Use the same order as BudgetType::VALUES so that we can index self.bytes_allocated
    760        // with the same index.
    761        let allocator_lists = [
    762            &mut self.shared_textures.color8_linear,
    763            &mut self.shared_textures.color8_nearest,
    764            &mut self.shared_textures.color8_glyphs,
    765            &mut self.shared_textures.alpha8_linear,
    766            &mut self.shared_textures.alpha8_glyphs,
    767            &mut self.shared_textures.alpha16_linear,
    768        ];
    769 
    770        // Pick a texture type on which to try to run the compaction logic this frame.
    771        let idx = self.shared_textures.next_compaction_idx;
    772 
    773        // Number of moved pixels after which we stop attempting to move more items for this frame.
    774        // The constant is up for adjustment, the main goal is to avoid causing frame spikes on
    775        // low end GPUs.
    776        let area_threshold = 512*512;
    777 
    778        let mut changes = Vec::new();
    779        allocator_lists[idx].try_compaction(area_threshold, &mut changes);
    780 
    781        if changes.is_empty() {
    782            // Nothing to do, we'll try another texture type next frame.
    783            self.shared_textures.next_compaction_idx = (self.shared_textures.next_compaction_idx + 1) % allocator_lists.len();
    784        }
    785 
    786        for change in changes {
    787            let bpp = allocator_lists[idx].texture_parameters().formats.internal.bytes_per_pixel();
    788 
    789            // While the area of the image does not change, the area it occupies in the texture
    790            // atlas may (in other words the number of wasted pixels can change), so we have
    791            // to keep track of that.
    792            let old_bytes = (change.old_rect.area() * bpp) as usize;
    793            let new_bytes = (change.new_rect.area() * bpp) as usize;
    794            self.bytes_allocated[idx] -= old_bytes;
    795            self.bytes_allocated[idx] += new_bytes;
    796 
    797            let entry = match change.handle {
    798                TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt_mut(&handle).unwrap(),
    799                TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt_mut(&handle).unwrap(),
    800                TextureCacheHandle::Empty => { panic!("invalid handle"); }
    801            };
    802            entry.texture_id = change.new_tex;
    803            entry.details = EntryDetails::Cache {
    804                origin: change.new_rect.min,
    805                alloc_id: change.new_id,
    806                allocated_size_in_bytes: new_bytes,
    807            };
    808 
    809            entry.uv_rect_handle = GpuBufferHandle::INVALID;
    810 
    811            let src_rect = DeviceIntRect::from_origin_and_size(change.old_rect.min, entry.size);
    812            let dst_rect = DeviceIntRect::from_origin_and_size(change.new_rect.min, entry.size);
    813 
    814            self.pending_updates.push_copy(change.old_tex, &src_rect, change.new_tex, &dst_rect);
    815 
    816            if self.debug_flags.contains(
    817                DebugFlags::TEXTURE_CACHE_DBG |
    818                DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
    819            {
    820                self.pending_updates.push_debug_clear(
    821                    change.old_tex,
    822                    src_rect.min,
    823                    src_rect.width(),
    824                    src_rect.height(),
    825                );
    826            }
    827        }
    828    }
    829 
    830    // Request an item in the texture cache. All images that will
    831    // be used on a frame *must* have request() called on their
    832    // handle, to update the last used timestamp and ensure
    833    // that resources are not flushed from the cache too early.
    834    //
    835    // Returns true if the image needs to be uploaded to the
    836    // texture cache (either never uploaded, or has been
    837    // evicted on a previous frame).
    838    pub fn request(&mut self, handle: &TextureCacheHandle, gpu_buffer: &mut GpuBufferBuilderF) -> bool {
    839        let now = self.now;
    840        let entry = match handle {
    841            TextureCacheHandle::Empty => None,
    842            TextureCacheHandle::Auto(handle) => {
    843                // Call touch rather than get_opt_mut so that the LRU index
    844                // knows that the entry has been used.
    845                self.lru_cache.touch(handle)
    846            },
    847            TextureCacheHandle::Manual(handle) => {
    848                self.manual_entries.get_opt_mut(handle)
    849            },
    850        };
    851        entry.map_or(true, |entry| {
    852            if entry.last_access != now {
    853                // If an image is requested that is already in the cache,
    854                // refresh the GPU buffer data associated with this item.
    855                entry.last_access = now;
    856                entry.write_gpu_blocks(gpu_buffer);
    857            }
    858            false
    859        })
    860    }
    861 
    862    fn get_entry_opt(&self, handle: &TextureCacheHandle) -> Option<&CacheEntry> {
    863        match handle {
    864            TextureCacheHandle::Empty => None,
    865            TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt(handle),
    866            TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt(handle),
    867        }
    868    }
    869 
    870    fn get_entry_opt_mut(&mut self, handle: &TextureCacheHandle) -> Option<&mut CacheEntry> {
    871        match handle {
    872            TextureCacheHandle::Empty => None,
    873            TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt_mut(handle),
    874            TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt_mut(handle),
    875        }
    876    }
    877 
    878    // Returns true if the image needs to be uploaded to the
    879    // texture cache (either never uploaded, or has been
    880    // evicted on a previous frame).
    881    pub fn needs_upload(&self, handle: &TextureCacheHandle) -> bool {
    882        !self.is_allocated(handle)
    883    }
    884 
    885    pub fn max_texture_size(&self) -> i32 {
    886        self.max_texture_size
    887    }
    888 
    889    pub fn tiling_threshold(&self) -> i32 {
    890        self.tiling_threshold
    891    }
    892 
    893    #[cfg(feature = "replay")]
    894    pub fn color_formats(&self) -> TextureFormatPair<ImageFormat> {
    895        self.shared_textures.color8_linear.texture_parameters().formats.clone()
    896    }
    897 
    898    #[cfg(feature = "replay")]
    899    pub fn swizzle_settings(&self) -> Option<SwizzleSettings> {
    900        self.swizzle
    901    }
    902 
    903    pub fn pending_updates(&mut self) -> TextureUpdateList {
    904        mem::replace(&mut self.pending_updates, TextureUpdateList::new())
    905    }
    906 
    907    // Update the data stored by a given texture cache handle.
    908    pub fn update(
    909        &mut self,
    910        handle: &mut TextureCacheHandle,
    911        descriptor: ImageDescriptor,
    912        filter: TextureFilter,
    913        data: Option<CachedImageData>,
    914        user_data: [f32; 4],
    915        mut dirty_rect: ImageDirtyRect,
    916        gpu_buffer: &mut GpuBufferBuilderF,
    917        eviction_notice: Option<&EvictionNotice>,
    918        uv_rect_kind: UvRectKind,
    919        eviction: Eviction,
    920        shader: TargetShader,
    921        force_standalone_texture: bool,
    922    ) {
    923        debug_assert!(self.now.is_valid());
    924        // Determine if we need to allocate texture cache memory
    925        // for this item. We need to reallocate if any of the following
    926        // is true:
    927        // - Never been in the cache
    928        // - Has been in the cache but was evicted.
    929        // - Exists in the cache but dimensions / format have changed.
    930        let realloc = match self.get_entry_opt(handle) {
    931            Some(entry) => {
    932                entry.size != descriptor.size || (entry.input_format != descriptor.format &&
    933                    entry.alternative_input_format() != descriptor.format)
    934            }
    935            None => {
    936                // Not allocated, or was previously allocated but has been evicted.
    937                true
    938            }
    939        };
    940 
    941        if realloc {
    942            let params = CacheAllocParams { descriptor, filter, user_data, uv_rect_kind, shader };
    943            self.allocate(&params, handle, eviction, force_standalone_texture);
    944 
    945            // If we reallocated, we need to upload the whole item again.
    946            dirty_rect = DirtyRect::All;
    947        }
    948 
    949        let now = self.now;
    950        let entry = self.get_entry_opt_mut(handle)
    951            .expect("BUG: There must be an entry at this handle now");
    952 
    953        // Install the new eviction notice for this update, if applicable.
    954        entry.eviction_notice = eviction_notice.cloned();
    955        entry.uv_rect_kind = uv_rect_kind;
    956 
    957        // If we just allocated the entry, its framestamp is up to date but it does
    958        // not uset have up-to-date gpu blocks.
    959        if entry.last_access != now || realloc {
    960            entry.last_access = now;
    961            // Upload the resource rect and texture array layer.
    962            entry.write_gpu_blocks(gpu_buffer);
    963        }
    964 
    965        // Create an update command, which the render thread processes
    966        // to upload the new image data into the correct location
    967        // in GPU memory.
    968        if let Some(data) = data {
    969            // If the swizzling is supported, we always upload in the internal
    970            // texture format (thus avoiding the conversion by the driver).
    971            // Otherwise, pass the external format to the driver.
    972            let origin = entry.details.describe();
    973            let texture_id = entry.texture_id;
    974            let size = entry.size;
    975            let use_upload_format = self.swizzle.is_none();
    976            let op = TextureCacheUpdate::new_update(
    977                data,
    978                &descriptor,
    979                origin,
    980                size,
    981                use_upload_format,
    982                &dirty_rect,
    983            );
    984            self.pending_updates.push_update(texture_id, op);
    985        }
    986    }
    987 
    988    // Check if a given texture handle has a valid allocation
    989    // in the texture cache.
    990    pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool {
    991        self.get_entry_opt(handle).is_some()
    992    }
    993 
    994    // Return the allocated size of the texture handle's associated data,
    995    // or otherwise indicate the handle is invalid.
    996    pub fn get_allocated_size(&self, handle: &TextureCacheHandle) -> Option<usize> {
    997        self.get_entry_opt(handle).map(|entry| {
    998            (entry.input_format.bytes_per_pixel() * entry.size.area()) as usize
    999        })
   1000    }
   1001 
   1002    // Retrieve the details of an item in the cache. This is used
   1003    // during batch creation to provide the resource rect address
   1004    // to the shaders and texture ID to the batching logic.
   1005    // This function will assert in debug modes if the caller
   1006    // tries to get a handle that was not requested this frame.
   1007    pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem {
   1008        let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.get_cache_location(handle);
   1009        CacheItem {
   1010            uv_rect_handle,
   1011            texture_id: TextureSource::TextureCache(
   1012                texture_id,
   1013                swizzle,
   1014            ),
   1015            uv_rect,
   1016            user_data,
   1017        }
   1018    }
   1019 
   1020    pub fn try_get(&self, handle: &TextureCacheHandle) -> Option<CacheItem> {
   1021        let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.try_get_cache_location(handle)?;
   1022        Some(CacheItem {
   1023            uv_rect_handle,
   1024            texture_id: TextureSource::TextureCache(
   1025                texture_id,
   1026                swizzle,
   1027            ),
   1028            uv_rect,
   1029            user_data,
   1030        })
   1031    }
   1032 
   1033    pub fn try_get_cache_location(
   1034        &self,
   1035        handle: &TextureCacheHandle,
   1036    ) -> Option<(CacheTextureId, DeviceIntRect, Swizzle, GpuBufferHandle, [f32; 4])> {
   1037        let entry = self.get_entry_opt(handle)?;
   1038        let origin = entry.details.describe();
   1039        if entry.last_access != self.now {
   1040            // On rare occasions we may have an image request that does not materialize
   1041            // into up to date data in the cache. For example if we failed to produce a
   1042            // stacking context snapshot.
   1043            return None;
   1044        }
   1045        Some((
   1046            entry.texture_id,
   1047            DeviceIntRect::from_origin_and_size(origin, entry.size),
   1048            entry.swizzle,
   1049            entry.uv_rect_handle,
   1050            entry.user_data,
   1051        ))
   1052    }
   1053 
   1054    /// A more detailed version of get(). This allows access to the actual
   1055    /// device rect of the cache allocation.
   1056    ///
   1057    /// Returns a tuple identifying the texture, the layer, the region,
   1058    /// and its GPU handle.
   1059    pub fn get_cache_location(
   1060        &self,
   1061        handle: &TextureCacheHandle,
   1062    ) -> (CacheTextureId, DeviceIntRect, Swizzle, GpuBufferHandle, [f32; 4]) {
   1063        self.try_get_cache_location(handle).expect("BUG: was dropped from cache or not updated!")
   1064    }
   1065 
   1066    /// Internal helper function to evict a strong texture cache handle
   1067    fn evict_impl(
   1068        &mut self,
   1069        entry: CacheEntry,
   1070    ) {
   1071        entry.evict();
   1072        self.free(&entry);
   1073    }
   1074 
   1075    /// Evict a texture cache handle that was previously set to be in manual
   1076    /// eviction mode.
   1077    pub fn evict_handle(&mut self, handle: &TextureCacheHandle) {
   1078        match handle {
   1079            TextureCacheHandle::Manual(handle) => {
   1080                // Find the strong handle that matches this weak handle. If this
   1081                // ever shows up in profiles, we can make it a hash (but the number
   1082                // of manual eviction handles is typically small).
   1083                // Alternatively, we could make a more forgiving FreeList variant
   1084                // which does not differentiate between strong and weak handles.
   1085                let index = self.manual_handles.iter().position(|strong_handle| {
   1086                    strong_handle.matches(handle)
   1087                });
   1088                if let Some(index) = index {
   1089                    let handle = self.manual_handles.swap_remove(index);
   1090                    let entry = self.manual_entries.free(handle);
   1091                    self.evict_impl(entry);
   1092                }
   1093            }
   1094            TextureCacheHandle::Auto(handle) => {
   1095                if let Some(entry) = self.lru_cache.remove(handle) {
   1096                    self.evict_impl(entry);
   1097                }
   1098            }
   1099            _ => {}
   1100        }
   1101    }
   1102 
   1103    pub fn dump_color8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
   1104        self.shared_textures.color8_linear.dump_as_svg(output)
   1105    }
   1106 
   1107    pub fn dump_color8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
   1108        self.shared_textures.color8_glyphs.dump_as_svg(output)
   1109    }
   1110 
   1111    pub fn dump_alpha8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
   1112        self.shared_textures.alpha8_glyphs.dump_as_svg(output)
   1113    }
   1114 
   1115    pub fn dump_alpha8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
   1116        self.shared_textures.alpha8_linear.dump_as_svg(output)
   1117    }
   1118 
   1119    /// Get the eviction threshold, in bytes, for the given budget type.
   1120    fn get_eviction_threshold(&self, budget_type: BudgetType) -> usize {
   1121        if budget_type == BudgetType::Standalone {
   1122            // For standalone textures, the only reason to evict textures is
   1123            // to save GPU memory. Batching / draw call concerns do not apply
   1124            // to standalone textures, because unused textures don't cause
   1125            // extra draw calls.
   1126            return 8 * 1024 * 1024;
   1127        }
   1128 
   1129        // For shared textures, evicting an entry only frees up GPU memory if it
   1130        // causes one of the shared textures to become empty, so we want to avoid
   1131        // getting slightly above the capacity of a texture.
   1132        // The other concern for shared textures is batching: The entries that
   1133        // are needed in the current frame should be distributed across as few
   1134        // shared textures as possible, to minimize the number of draw calls.
   1135        // Ideally we only want one texture per type under simple workloads.
   1136 
   1137        let bytes_per_texture = self.shared_textures.bytes_per_shared_texture(budget_type);
   1138 
   1139        // Number of allocated bytes under which we don't bother with evicting anything
   1140        // from the cache. Above the threshold we consider evicting the coldest items
   1141        // depending on how cold they are.
   1142        //
   1143        // Above all else we want to make sure that even after a heavy workload, the
   1144        // shared cache settles back to a single texture atlas per type over some reasonable
   1145        // period of time.
   1146        // This is achieved by the compaction logic which will try to consolidate items that
   1147        // are spread over multiple textures into few ones, and by evicting old items
   1148        // so that the compaction logic has room to do its job.
   1149        //
   1150        // The other goal is to leave enough empty space in the texture atlases
   1151        // so that we are not too likely to have to allocate a new texture atlas on
   1152        // the next frame if we switch to a new tab or load a new page. That's why
   1153        // the following thresholds are rather low. Note that even when above the threshold,
   1154        // we only evict cold items and ramp up the eviction pressure depending on the amount
   1155        // of allocated memory (See should_continue_evicting).
   1156        let ideal_utilization = match budget_type {
   1157            BudgetType::SharedAlpha8Glyphs | BudgetType::SharedColor8Glyphs => {
   1158                // Glyphs are usually small and tightly packed so they waste very little
   1159                // space in the cache.
   1160                bytes_per_texture * 2 / 3
   1161            }
   1162            _ => {
   1163                // Other types of images come with a variety of sizes making them more
   1164                // prone to wasting pixels and causing fragmentation issues so we put
   1165                // more pressure on them.
   1166                bytes_per_texture / 3
   1167            }
   1168        };
   1169 
   1170        ideal_utilization
   1171    }
   1172 
   1173    /// Returns whether to continue eviction and how cold an item need to be to be evicted.
   1174    ///
   1175    /// If the None is returned, stop evicting.
   1176    /// If the Some(n) is returned, continue evicting if the coldest item hasn't been used
   1177    /// for more than n frames.
   1178    fn should_continue_evicting(
   1179        &self,
   1180        budget_type: BudgetType,
   1181        eviction_count: usize,
   1182    ) -> Option<u64> {
   1183 
   1184        let threshold = self.get_eviction_threshold(budget_type);
   1185        let bytes_allocated = self.bytes_allocated[budget_type as usize];
   1186 
   1187        let uses_multiple_atlases = self.shared_textures.has_multiple_textures(budget_type);
   1188 
   1189        // If current memory usage is below selected threshold, we can stop evicting items
   1190        // except when using shared texture atlases and more than one texture is in use.
   1191        // This is not very common but can happen due to fragmentation and the only way
   1192        // to get rid of that fragmentation is to continue evicting.
   1193        if bytes_allocated < threshold && !uses_multiple_atlases {
   1194            return None;
   1195        }
   1196 
   1197        // Number of frames since last use that is considered too recent for eviction,
   1198        // depending on the cache pressure.
   1199        let age_theshold = match bytes_allocated / threshold {
   1200            0 => 400,
   1201            1 => 200,
   1202            2 => 100,
   1203            3 => 50,
   1204            4 => 25,
   1205            5 => 10,
   1206            6 => 5,
   1207            _ => 1,
   1208        };
   1209 
   1210        // If current memory usage is significantly more than the threshold, keep evicting this frame
   1211        if bytes_allocated > 4 * threshold {
   1212            return Some(age_theshold);
   1213        }
   1214 
   1215        // Otherwise, only allow evicting up to a certain number of items per frame. This allows evictions
   1216        // to be spread over a number of frames, to avoid frame spikes.
   1217        if eviction_count < Self::MAX_EVICTIONS_PER_FRAME {
   1218            return Some(age_theshold)
   1219        }
   1220 
   1221        None
   1222    }
   1223 
   1224 
   1225    /// Evict old items from the shared and standalone caches, if we're over a
   1226    /// threshold memory usage value
   1227    fn evict_items_from_cache_if_required(&mut self, profile: &mut TransactionProfile) {
   1228        let previous_frame_id = self.now.frame_id() - 1;
   1229        let mut eviction_count = 0;
   1230        let mut youngest_evicted = FrameId::first();
   1231 
   1232        for budget in BudgetType::iter() {
   1233            while let Some(age_threshold) = self.should_continue_evicting(
   1234                budget,
   1235                eviction_count,
   1236            ) {
   1237                if let Some(entry) = self.lru_cache.peek_oldest(budget as u8) {
   1238                    // Only evict this item if it wasn't used in the previous frame. The reason being that if it
   1239                    // was used the previous frame then it will likely be used in this frame too, and we don't
   1240                    // want to be continually evicting and reuploading the item every frame.
   1241                    if entry.last_access.frame_id() + age_threshold > previous_frame_id {
   1242                        // Since the LRU cache is ordered by frame access, we can break out of the loop here because
   1243                        // we know that all remaining items were also used in the previous frame (or more recently).
   1244                        break;
   1245                    }
   1246                    if entry.last_access.frame_id() > youngest_evicted {
   1247                        youngest_evicted = entry.last_access.frame_id();
   1248                    }
   1249                    let entry = self.lru_cache.pop_oldest(budget as u8).unwrap();
   1250                    entry.evict();
   1251                    self.free(&entry);
   1252                    eviction_count += 1;
   1253                } else {
   1254                    // The LRU cache is empty, all remaining items use manual
   1255                    // eviction. In this case, there's nothing we can do until
   1256                    // the calling code manually evicts items to reduce the
   1257                    // allocated cache size.
   1258                    break;
   1259                }
   1260            }
   1261        }
   1262 
   1263        if eviction_count > 0 {
   1264            profile.set(profiler::TEXTURE_CACHE_EVICTION_COUNT, eviction_count);
   1265            profile.set(
   1266                profiler::TEXTURE_CACHE_YOUNGEST_EVICTION,
   1267                self.now.frame_id().as_u64() - youngest_evicted.as_u64()
   1268            );
   1269        }
   1270    }
   1271 
   1272    // Free a cache entry from the standalone list or shared cache.
   1273    fn free(&mut self, entry: &CacheEntry) {
   1274        match entry.details {
   1275            EntryDetails::Standalone { size_in_bytes, .. } => {
   1276                self.bytes_allocated[BudgetType::Standalone as usize] -= size_in_bytes;
   1277 
   1278                // This is a standalone texture allocation. Free it directly.
   1279                self.pending_updates.push_free(entry.texture_id);
   1280            }
   1281            EntryDetails::Cache { origin, alloc_id, allocated_size_in_bytes } => {
   1282                let (allocator_list, budget_type) = self.shared_textures.select(
   1283                    entry.input_format,
   1284                    entry.filter,
   1285                    entry.shader,
   1286                );
   1287 
   1288                allocator_list.deallocate(entry.texture_id, alloc_id);
   1289 
   1290                self.bytes_allocated[budget_type as usize] -= allocated_size_in_bytes;
   1291 
   1292                if self.debug_flags.contains(
   1293                    DebugFlags::TEXTURE_CACHE_DBG |
   1294                    DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
   1295                {
   1296                    self.pending_updates.push_debug_clear(
   1297                        entry.texture_id,
   1298                        origin,
   1299                        entry.size.width,
   1300                        entry.size.height,
   1301                    );
   1302                }
   1303            }
   1304        }
   1305    }
   1306 
   1307    /// Allocate a block from the shared cache.
   1308    fn allocate_from_shared_cache(
   1309        &mut self,
   1310        params: &CacheAllocParams,
   1311    ) -> (CacheEntry, BudgetType) {
   1312        let (allocator_list, budget_type) = self.shared_textures.select(
   1313            params.descriptor.format,
   1314            params.filter,
   1315            params.shader,
   1316        );
   1317 
   1318        // To avoid referring to self in the closure.
   1319        let next_id = &mut self.next_id;
   1320        let pending_updates = &mut self.pending_updates;
   1321 
   1322        let (texture_id, alloc_id, allocated_rect) = allocator_list.allocate(
   1323            params.descriptor.size,
   1324            &mut |size, parameters| {
   1325                let texture_id = *next_id;
   1326                next_id.0 += 1;
   1327                pending_updates.push_alloc(
   1328                    texture_id,
   1329                    TextureCacheAllocInfo {
   1330                        target: ImageBufferKind::Texture2D,
   1331                        width: size.width,
   1332                        height: size.height,
   1333                        format: parameters.formats.internal,
   1334                        filter: parameters.filter,
   1335                        is_shared_cache: true,
   1336                        has_depth: false,
   1337                        category: TextureCacheCategory::Atlas,
   1338                    },
   1339                );
   1340 
   1341                texture_id
   1342            },
   1343        );
   1344 
   1345        let formats = &allocator_list.texture_parameters().formats;
   1346 
   1347        let swizzle = if formats.external == params.descriptor.format {
   1348            Swizzle::default()
   1349        } else {
   1350            match self.swizzle {
   1351                Some(_) => Swizzle::Bgra,
   1352                None => Swizzle::default(),
   1353            }
   1354        };
   1355 
   1356        let bpp = formats.internal.bytes_per_pixel();
   1357        let allocated_size_in_bytes = (allocated_rect.area() * bpp) as usize;
   1358        self.bytes_allocated[budget_type as usize] += allocated_size_in_bytes;
   1359 
   1360        (CacheEntry {
   1361            size: params.descriptor.size,
   1362            user_data: params.user_data,
   1363            last_access: self.now,
   1364            details: EntryDetails::Cache {
   1365                origin: allocated_rect.min,
   1366                alloc_id,
   1367                allocated_size_in_bytes,
   1368            },
   1369            uv_rect_handle: GpuBufferHandle::INVALID,
   1370            input_format: params.descriptor.format,
   1371            filter: params.filter,
   1372            swizzle,
   1373            texture_id,
   1374            eviction_notice: None,
   1375            uv_rect_kind: params.uv_rect_kind,
   1376            shader: params.shader
   1377        }, budget_type)
   1378    }
   1379 
   1380    // Returns true if the given image descriptor *may* be
   1381    // placed in the shared texture cache.
   1382    pub fn is_allowed_in_shared_cache(
   1383        &self,
   1384        filter: TextureFilter,
   1385        descriptor: &ImageDescriptor,
   1386    ) -> bool {
   1387        let mut allowed_in_shared_cache = true;
   1388 
   1389        if matches!(descriptor.format, ImageFormat::RGBA8 | ImageFormat::BGRA8)
   1390            && filter == TextureFilter::Linear
   1391        {
   1392            // Allow the maximum that can fit in the linear color texture's two column layout.
   1393            let max = self.shared_textures.color8_linear.size() / 2;
   1394            allowed_in_shared_cache = descriptor.size.width.max(descriptor.size.height) <= max;
   1395        } else if descriptor.size.width > TEXTURE_REGION_DIMENSIONS {
   1396            allowed_in_shared_cache = false;
   1397        }
   1398 
   1399        if descriptor.size.height > TEXTURE_REGION_DIMENSIONS {
   1400            allowed_in_shared_cache = false;
   1401        }
   1402 
   1403        // TODO(gw): For now, alpha formats of the texture cache can only be linearly sampled.
   1404        //           Nearest sampling gets a standalone texture.
   1405        //           This is probably rare enough that it can be fixed up later.
   1406        if filter == TextureFilter::Nearest &&
   1407           descriptor.format.bytes_per_pixel() <= 2
   1408        {
   1409            allowed_in_shared_cache = false;
   1410        }
   1411 
   1412        allowed_in_shared_cache
   1413    }
   1414 
   1415    /// Allocate a render target via the pending updates sent to the renderer
   1416    pub fn alloc_render_target(
   1417        &mut self,
   1418        size: DeviceIntSize,
   1419        format: ImageFormat,
   1420    ) -> CacheTextureId {
   1421        let texture_id = self.next_id;
   1422        self.next_id.0 += 1;
   1423 
   1424        // Push a command to allocate device storage of the right size / format.
   1425        let info = TextureCacheAllocInfo {
   1426            target: ImageBufferKind::Texture2D,
   1427            width: size.width,
   1428            height: size.height,
   1429            format,
   1430            filter: TextureFilter::Linear,
   1431            is_shared_cache: false,
   1432            has_depth: false,
   1433            category: TextureCacheCategory::RenderTarget,
   1434        };
   1435 
   1436        self.pending_updates.push_alloc(texture_id, info);
   1437 
   1438        texture_id
   1439    }
   1440 
   1441    /// Free an existing render target
   1442    pub fn free_render_target(
   1443        &mut self,
   1444        id: CacheTextureId,
   1445    ) {
   1446        self.pending_updates.push_free(id);
   1447    }
   1448 
   1449    /// Allocates a new standalone cache entry.
   1450    fn allocate_standalone_entry(
   1451        &mut self,
   1452        params: &CacheAllocParams,
   1453    ) -> (CacheEntry, BudgetType) {
   1454        let texture_id = self.next_id;
   1455        self.next_id.0 += 1;
   1456 
   1457        // Push a command to allocate device storage of the right size / format.
   1458        let info = TextureCacheAllocInfo {
   1459            target: ImageBufferKind::Texture2D,
   1460            width: params.descriptor.size.width,
   1461            height: params.descriptor.size.height,
   1462            format: params.descriptor.format,
   1463            filter: params.filter,
   1464            is_shared_cache: false,
   1465            has_depth: false,
   1466            category: TextureCacheCategory::Standalone,
   1467        };
   1468 
   1469        let size_in_bytes = (info.width * info.height * info.format.bytes_per_pixel()) as usize;
   1470        self.bytes_allocated[BudgetType::Standalone as usize] += size_in_bytes;
   1471 
   1472        self.pending_updates.push_alloc(texture_id, info);
   1473 
   1474        // Special handing for BGRA8 textures that may need to be swizzled.
   1475        let swizzle = if params.descriptor.format == ImageFormat::BGRA8 {
   1476            self.swizzle.map(|s| s.bgra8_sampling_swizzle)
   1477        } else {
   1478            None
   1479        };
   1480 
   1481        (CacheEntry::new_standalone(
   1482            texture_id,
   1483            self.now,
   1484            params,
   1485            swizzle.unwrap_or_default(),
   1486            size_in_bytes,
   1487        ), BudgetType::Standalone)
   1488    }
   1489 
   1490    /// Allocates a cache entry for the given parameters, and updates the
   1491    /// provided handle to point to the new entry.
   1492    fn allocate(
   1493        &mut self,
   1494        params: &CacheAllocParams,
   1495        handle: &mut TextureCacheHandle,
   1496        eviction: Eviction,
   1497        force_standalone_texture: bool,
   1498    ) {
   1499        debug_assert!(self.now.is_valid());
   1500        assert!(!params.descriptor.size.is_empty());
   1501 
   1502        // If this image doesn't qualify to go in the shared (batching) cache,
   1503        // allocate a standalone entry.
   1504        let use_shared_cache = !force_standalone_texture && self.is_allowed_in_shared_cache(params.filter, &params.descriptor);
   1505        let (new_cache_entry, budget_type) = if use_shared_cache {
   1506            self.allocate_from_shared_cache(params)
   1507        } else {
   1508            self.allocate_standalone_entry(params)
   1509        };
   1510 
   1511        let details = new_cache_entry.details.clone();
   1512        let texture_id = new_cache_entry.texture_id;
   1513 
   1514        // If the handle points to a valid cache entry, we want to replace the
   1515        // cache entry with our newly updated location. We also need to ensure
   1516        // that the storage (region or standalone) associated with the previous
   1517        // entry here gets freed.
   1518        //
   1519        // If the handle is invalid, we need to insert the data, and append the
   1520        // result to the corresponding vector.
   1521        let old_entry = match (&mut *handle, eviction) {
   1522            (TextureCacheHandle::Auto(handle), Eviction::Auto) => {
   1523                self.lru_cache.replace_or_insert(handle, budget_type as u8, new_cache_entry)
   1524            },
   1525            (TextureCacheHandle::Manual(handle), Eviction::Manual) => {
   1526                let entry = self.manual_entries.get_opt_mut(handle)
   1527                    .expect("Don't call this after evicting");
   1528                Some(mem::replace(entry, new_cache_entry))
   1529            },
   1530            (TextureCacheHandle::Manual(_), Eviction::Auto) |
   1531            (TextureCacheHandle::Auto(_), Eviction::Manual) => {
   1532                panic!("Can't change eviction policy after initial allocation");
   1533            },
   1534            (TextureCacheHandle::Empty, Eviction::Auto) => {
   1535                let new_handle = self.lru_cache.push_new(budget_type as u8, new_cache_entry);
   1536                *handle = TextureCacheHandle::Auto(new_handle);
   1537                None
   1538            },
   1539            (TextureCacheHandle::Empty, Eviction::Manual) => {
   1540                let manual_handle = self.manual_entries.insert(new_cache_entry);
   1541                let new_handle = manual_handle.weak();
   1542                self.manual_handles.push(manual_handle);
   1543                *handle = TextureCacheHandle::Manual(new_handle);
   1544                None
   1545            },
   1546        };
   1547        if let Some(old_entry) = old_entry {
   1548            old_entry.evict();
   1549            self.free(&old_entry);
   1550        }
   1551 
   1552        if let EntryDetails::Cache { alloc_id, .. } = details {
   1553            let allocator_list = self.shared_textures.select(
   1554                params.descriptor.format,
   1555                params.filter,
   1556                params.shader,
   1557            ).0;
   1558 
   1559            allocator_list.set_handle(texture_id, alloc_id, handle);
   1560        }
   1561    }
   1562 
   1563    pub fn shared_alpha_expected_format(&self) -> ImageFormat {
   1564        self.shared_textures.alpha8_linear.texture_parameters().formats.external
   1565    }
   1566 
   1567    pub fn shared_color_expected_format(&self) -> ImageFormat {
   1568        self.shared_textures.color8_linear.texture_parameters().formats.external
   1569    }
   1570 
   1571 
   1572    #[cfg(test)]
   1573    pub fn total_allocated_bytes_for_testing(&self) -> usize {
   1574        BudgetType::iter().map(|b| self.bytes_allocated[b as usize]).sum()
   1575    }
   1576 
   1577    pub fn report_memory(&self, ops: &mut MallocSizeOfOps) -> usize {
   1578        self.lru_cache.size_of(ops)
   1579    }
   1580 }
   1581 
   1582 #[cfg_attr(feature = "capture", derive(Serialize))]
   1583 #[cfg_attr(feature = "replay", derive(Deserialize))]
   1584 pub struct TextureParameters {
   1585    pub formats: TextureFormatPair<ImageFormat>,
   1586    pub filter: TextureFilter,
   1587 }
   1588 
   1589 impl TextureCacheUpdate {
   1590    // Constructs a TextureCacheUpdate operation to be passed to the
   1591    // rendering thread in order to do an upload to the right
   1592    // location in the texture cache.
   1593    fn new_update(
   1594        data: CachedImageData,
   1595        descriptor: &ImageDescriptor,
   1596        origin: DeviceIntPoint,
   1597        size: DeviceIntSize,
   1598        use_upload_format: bool,
   1599        dirty_rect: &ImageDirtyRect,
   1600    ) -> TextureCacheUpdate {
   1601        let source = match data {
   1602            CachedImageData::Snapshot => {
   1603                panic!("Snapshots should not do texture uploads");
   1604            }
   1605            CachedImageData::Blob => {
   1606                panic!("The vector image should have been rasterized.");
   1607            }
   1608            CachedImageData::External(ext_image) => match ext_image.image_type {
   1609                ExternalImageType::TextureHandle(_) => {
   1610                    panic!("External texture handle should not go through texture_cache.");
   1611                }
   1612                ExternalImageType::Buffer => TextureUpdateSource::External {
   1613                    id: ext_image.id,
   1614                    channel_index: ext_image.channel_index,
   1615                },
   1616            },
   1617            CachedImageData::Raw(bytes) => {
   1618                let finish = descriptor.offset +
   1619                    descriptor.size.width * descriptor.format.bytes_per_pixel() +
   1620                    (descriptor.size.height - 1) * descriptor.compute_stride();
   1621                assert!(bytes.len() >= finish as usize);
   1622 
   1623                TextureUpdateSource::Bytes { data: bytes }
   1624            }
   1625        };
   1626        let format_override = if use_upload_format {
   1627            Some(descriptor.format)
   1628        } else {
   1629            None
   1630        };
   1631 
   1632        match *dirty_rect {
   1633            DirtyRect::Partial(dirty) => {
   1634                // the dirty rectangle doesn't have to be within the area but has to intersect it, at least
   1635                let stride = descriptor.compute_stride();
   1636                let offset = descriptor.offset + dirty.min.y * stride + dirty.min.x * descriptor.format.bytes_per_pixel();
   1637 
   1638                TextureCacheUpdate {
   1639                    rect: DeviceIntRect::from_origin_and_size(
   1640                        DeviceIntPoint::new(origin.x + dirty.min.x, origin.y + dirty.min.y),
   1641                        DeviceIntSize::new(
   1642                            dirty.width().min(size.width - dirty.min.x),
   1643                            dirty.height().min(size.height - dirty.min.y),
   1644                        ),
   1645                    ),
   1646                    source,
   1647                    stride: Some(stride),
   1648                    offset,
   1649                    format_override,
   1650                }
   1651            }
   1652            DirtyRect::All => {
   1653                TextureCacheUpdate {
   1654                    rect: DeviceIntRect::from_origin_and_size(origin, size),
   1655                    source,
   1656                    stride: descriptor.stride,
   1657                    offset: descriptor.offset,
   1658                    format_override,
   1659                }
   1660            }
   1661        }
   1662    }
   1663 }
   1664 
   1665 #[cfg(test)]
   1666 mod test_texture_cache {
   1667    use crate::renderer::GpuBufferBuilderF;
   1668    use crate::internal_types::FrameId;
   1669 
   1670    #[test]
   1671    fn check_allocation_size_balance() {
   1672        // Allocate some glyphs, observe the total allocation size, and free
   1673        // the glyphs again. Check that the total allocation size is back at the
   1674        // original value.
   1675 
   1676        use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
   1677        use crate::device::TextureFilter;
   1678        use crate::gpu_types::UvRectKind;
   1679        use crate::frame_allocator::FrameMemory;
   1680        use api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, DirtyRect};
   1681        use api::units::*;
   1682        use euclid::size2;
   1683        let mut texture_cache = TextureCache::new_for_testing(2048, ImageFormat::BGRA8);
   1684        let memory = FrameMemory::fallback();
   1685        let mut gpu_buffer = GpuBufferBuilderF::new(&memory, 0, FrameId::first());
   1686 
   1687        let sizes: &[DeviceIntSize] = &[
   1688            size2(23, 27),
   1689            size2(15, 22),
   1690            size2(11, 5),
   1691            size2(20, 25),
   1692            size2(38, 41),
   1693            size2(11, 19),
   1694            size2(13, 21),
   1695            size2(37, 40),
   1696            size2(13, 15),
   1697            size2(14, 16),
   1698            size2(10, 9),
   1699            size2(25, 28),
   1700        ];
   1701 
   1702        let bytes_at_start = texture_cache.total_allocated_bytes_for_testing();
   1703 
   1704        let handles: Vec<TextureCacheHandle> = sizes.iter().map(|size| {
   1705            let mut texture_cache_handle = TextureCacheHandle::invalid();
   1706            texture_cache.request(&texture_cache_handle, &mut gpu_buffer);
   1707            texture_cache.update(
   1708                &mut texture_cache_handle,
   1709                ImageDescriptor {
   1710                    size: *size,
   1711                    stride: None,
   1712                    format: ImageFormat::BGRA8,
   1713                    flags: ImageDescriptorFlags::empty(),
   1714                    offset: 0,
   1715                },
   1716                TextureFilter::Linear,
   1717                None,
   1718                [0.0; 4],
   1719                DirtyRect::All,
   1720                &mut gpu_buffer,
   1721                None,
   1722                UvRectKind::Rect,
   1723                Eviction::Manual,
   1724                TargetShader::Text,
   1725                false,
   1726            );
   1727            texture_cache_handle
   1728        }).collect();
   1729 
   1730        let bytes_after_allocating = texture_cache.total_allocated_bytes_for_testing();
   1731        assert!(bytes_after_allocating > bytes_at_start);
   1732 
   1733        for handle in handles {
   1734            texture_cache.evict_handle(&handle);
   1735        }
   1736 
   1737        let bytes_at_end = texture_cache.total_allocated_bytes_for_testing();
   1738        assert_eq!(bytes_at_end, bytes_at_start);
   1739    }
   1740 }