azul_webrender/
texture_cache.rs

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