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