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    ) {
922        debug_assert!(self.now.is_valid());
923        // Determine if we need to allocate texture cache memory
924        // for this item. We need to reallocate if any of the following
925        // is true:
926        // - Never been in the cache
927        // - Has been in the cache but was evicted.
928        // - Exists in the cache but dimensions / format have changed.
929        let realloc = match self.get_entry_opt(handle) {
930            Some(entry) => {
931                entry.size != descriptor.size || (entry.input_format != descriptor.format &&
932                    entry.alternative_input_format() != descriptor.format)
933            }
934            None => {
935                // Not allocated, or was previously allocated but has been evicted.
936                true
937            }
938        };
939
940        if realloc {
941            let params = CacheAllocParams { descriptor, filter, user_data, uv_rect_kind, shader };
942            self.allocate(&params, handle, eviction);
943
944            // If we reallocated, we need to upload the whole item again.
945            dirty_rect = DirtyRect::All;
946        }
947
948        let entry = self.get_entry_opt_mut(handle)
949            .expect("BUG: There must be an entry at this handle now");
950
951        // Install the new eviction notice for this update, if applicable.
952        entry.eviction_notice = eviction_notice.cloned();
953        entry.uv_rect_kind = uv_rect_kind;
954
955        // Invalidate the contents of the resource rect in the GPU cache.
956        // This ensures that the update_gpu_cache below will add
957        // the new information to the GPU cache.
958        //TODO: only invalidate if the parameters change?
959        gpu_cache.invalidate(&entry.uv_rect_handle);
960
961        // Upload the resource rect and texture array layer.
962        entry.update_gpu_cache(gpu_cache);
963
964        // Create an update command, which the render thread processes
965        // to upload the new image data into the correct location
966        // in GPU memory.
967        if let Some(data) = data {
968            // If the swizzling is supported, we always upload in the internal
969            // texture format (thus avoiding the conversion by the driver).
970            // Otherwise, pass the external format to the driver.
971            let origin = entry.details.describe();
972            let texture_id = entry.texture_id;
973            let size = entry.size;
974            let use_upload_format = self.swizzle.is_none();
975            let op = TextureCacheUpdate::new_update(
976                data,
977                &descriptor,
978                origin,
979                size,
980                use_upload_format,
981                &dirty_rect,
982            );
983            self.pending_updates.push_update(texture_id, op);
984        }
985    }
986
987    // Check if a given texture handle has a valid allocation
988    // in the texture cache.
989    pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool {
990        self.get_entry_opt(handle).is_some()
991    }
992
993    // Return the allocated size of the texture handle's associated data,
994    // or otherwise indicate the handle is invalid.
995    pub fn get_allocated_size(&self, handle: &TextureCacheHandle) -> Option<usize> {
996        self.get_entry_opt(handle).map(|entry| {
997            (entry.input_format.bytes_per_pixel() * entry.size.area()) as usize
998        })
999    }
1000
1001    // Retrieve the details of an item in the cache. This is used
1002    // during batch creation to provide the resource rect address
1003    // to the shaders and texture ID to the batching logic.
1004    // This function will assert in debug modes if the caller
1005    // tries to get a handle that was not requested this frame.
1006    pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem {
1007        let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.get_cache_location(handle);
1008        CacheItem {
1009            uv_rect_handle,
1010            texture_id: TextureSource::TextureCache(
1011                texture_id,
1012                swizzle,
1013            ),
1014            uv_rect,
1015            user_data,
1016        }
1017    }
1018
1019    pub fn try_get(&self, handle: &TextureCacheHandle) -> Option<CacheItem> {
1020        let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.try_get_cache_location(handle)?;
1021        Some(CacheItem {
1022            uv_rect_handle,
1023            texture_id: TextureSource::TextureCache(
1024                texture_id,
1025                swizzle,
1026            ),
1027            uv_rect,
1028            user_data,
1029        })
1030    }
1031
1032    pub fn try_get_cache_location(
1033        &self,
1034        handle: &TextureCacheHandle,
1035    ) -> Option<(CacheTextureId, DeviceIntRect, Swizzle, GpuCacheHandle, [f32; 4])> {
1036        let entry = self.get_entry_opt(handle)?;
1037        let origin = entry.details.describe();
1038        Some((
1039            entry.texture_id,
1040            DeviceIntRect::from_origin_and_size(origin, entry.size),
1041            entry.swizzle,
1042            entry.uv_rect_handle,
1043            entry.user_data,
1044        ))
1045    }
1046
1047    /// A more detailed version of get(). This allows access to the actual
1048    /// device rect of the cache allocation.
1049    ///
1050    /// Returns a tuple identifying the texture, the layer, the region,
1051    /// and its GPU handle.
1052    pub fn get_cache_location(
1053        &self,
1054        handle: &TextureCacheHandle,
1055    ) -> (CacheTextureId, DeviceIntRect, Swizzle, GpuCacheHandle, [f32; 4]) {
1056        self.try_get_cache_location(handle).expect("BUG: was dropped from cache or not updated!")
1057    }
1058
1059    /// Internal helper function to evict a strong texture cache handle
1060    fn evict_impl(
1061        &mut self,
1062        entry: CacheEntry,
1063    ) {
1064        entry.evict();
1065        self.free(&entry);
1066    }
1067
1068    /// Evict a texture cache handle that was previously set to be in manual
1069    /// eviction mode.
1070    pub fn evict_handle(&mut self, handle: &TextureCacheHandle) {
1071        match handle {
1072            TextureCacheHandle::Manual(handle) => {
1073                // Find the strong handle that matches this weak handle. If this
1074                // ever shows up in profiles, we can make it a hash (but the number
1075                // of manual eviction handles is typically small).
1076                // Alternatively, we could make a more forgiving FreeList variant
1077                // which does not differentiate between strong and weak handles.
1078                let index = self.manual_handles.iter().position(|strong_handle| {
1079                    strong_handle.matches(handle)
1080                });
1081                if let Some(index) = index {
1082                    let handle = self.manual_handles.swap_remove(index);
1083                    let entry = self.manual_entries.free(handle);
1084                    self.evict_impl(entry);
1085                }
1086            }
1087            TextureCacheHandle::Auto(handle) => {
1088                if let Some(entry) = self.lru_cache.remove(handle) {
1089                    self.evict_impl(entry);
1090                }
1091            }
1092            _ => {}
1093        }
1094    }
1095
1096    pub fn dump_color8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1097        self.shared_textures.color8_linear.dump_as_svg(output)
1098    }
1099
1100    pub fn dump_color8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1101        self.shared_textures.color8_glyphs.dump_as_svg(output)
1102    }
1103
1104    pub fn dump_alpha8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1105        self.shared_textures.alpha8_glyphs.dump_as_svg(output)
1106    }
1107
1108    pub fn dump_alpha8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1109        self.shared_textures.alpha8_linear.dump_as_svg(output)
1110    }
1111
1112    /// Get the eviction threshold, in bytes, for the given budget type.
1113    fn get_eviction_threshold(&self, budget_type: BudgetType) -> usize {
1114        if budget_type == BudgetType::Standalone {
1115            // For standalone textures, the only reason to evict textures is
1116            // to save GPU memory. Batching / draw call concerns do not apply
1117            // to standalone textures, because unused textures don't cause
1118            // extra draw calls.
1119            return 8 * 1024 * 1024;
1120        }
1121
1122        // For shared textures, evicting an entry only frees up GPU memory if it
1123        // causes one of the shared textures to become empty, so we want to avoid
1124        // getting slightly above the capacity of a texture.
1125        // The other concern for shared textures is batching: The entries that
1126        // are needed in the current frame should be distributed across as few
1127        // shared textures as possible, to minimize the number of draw calls.
1128        // Ideally we only want one texture per type under simple workloads.
1129
1130        let bytes_per_texture = self.shared_textures.bytes_per_shared_texture(budget_type);
1131
1132        // Number of allocated bytes under which we don't bother with evicting anything
1133        // from the cache. Above the threshold we consider evicting the coldest items
1134        // depending on how cold they are.
1135        //
1136        // Above all else we want to make sure that even after a heavy workload, the
1137        // shared cache settles back to a single texture atlas per type over some reasonable
1138        // period of time.
1139        // This is achieved by the compaction logic which will try to consolidate items that
1140        // are spread over multiple textures into few ones, and by evicting old items
1141        // so that the compaction logic has room to do its job.
1142        //
1143        // The other goal is to leave enough empty space in the texture atlases
1144        // so that we are not too likely to have to allocate a new texture atlas on
1145        // the next frame if we switch to a new tab or load a new page. That's why
1146        // the following thresholds are rather low. Note that even when above the threshold,
1147        // we only evict cold items and ramp up the eviction pressure depending on the amount
1148        // of allocated memory (See should_continue_evicting).
1149        let ideal_utilization = match budget_type {
1150            BudgetType::SharedAlpha8Glyphs | BudgetType::SharedColor8Glyphs => {
1151                // Glyphs are usually small and tightly packed so they waste very little
1152                // space in the cache.
1153                bytes_per_texture * 2 / 3
1154            }
1155            _ => {
1156                // Other types of images come with a variety of sizes making them more
1157                // prone to wasting pixels and causing fragmentation issues so we put
1158                // more pressure on them.
1159                bytes_per_texture / 3
1160            }
1161        };
1162
1163        ideal_utilization
1164    }
1165
1166    /// Returns whether to continue eviction and how cold an item need to be to be evicted.
1167    ///
1168    /// If the None is returned, stop evicting.
1169    /// If the Some(n) is returned, continue evicting if the coldest item hasn't been used
1170    /// for more than n frames.
1171    fn should_continue_evicting(
1172        &self,
1173        budget_type: BudgetType,
1174        eviction_count: usize,
1175    ) -> Option<u64> {
1176
1177        let threshold = self.get_eviction_threshold(budget_type);
1178        let bytes_allocated = self.bytes_allocated[budget_type as usize];
1179
1180        let uses_multiple_atlases = self.shared_textures.has_multiple_textures(budget_type);
1181
1182        // If current memory usage is below selected threshold, we can stop evicting items
1183        // except when using shared texture atlases and more than one texture is in use.
1184        // This is not very common but can happen due to fragmentation and the only way
1185        // to get rid of that fragmentation is to continue evicting.
1186        if bytes_allocated < threshold && !uses_multiple_atlases {
1187            return None;
1188        }
1189
1190        // Number of frames since last use that is considered too recent for eviction,
1191        // depending on the cache pressure.
1192        let age_theshold = match bytes_allocated / threshold {
1193            0 => 400,
1194            1 => 200,
1195            2 => 100,
1196            3 => 50,
1197            4 => 25,
1198            5 => 10,
1199            6 => 5,
1200            _ => 1,
1201        };
1202
1203        // If current memory usage is significantly more than the threshold, keep evicting this frame
1204        if bytes_allocated > 4 * threshold {
1205            return Some(age_theshold);
1206        }
1207
1208        // Otherwise, only allow evicting up to a certain number of items per frame. This allows evictions
1209        // to be spread over a number of frames, to avoid frame spikes.
1210        if eviction_count < Self::MAX_EVICTIONS_PER_FRAME {
1211            return Some(age_theshold)
1212        }
1213
1214        None
1215    }
1216
1217
1218    /// Evict old items from the shared and standalone caches, if we're over a
1219    /// threshold memory usage value
1220    fn evict_items_from_cache_if_required(&mut self, profile: &mut TransactionProfile) {
1221        let previous_frame_id = self.now.frame_id() - 1;
1222        let mut eviction_count = 0;
1223        let mut youngest_evicted = FrameId::first();
1224
1225        for budget in BudgetType::iter() {
1226            while let Some(age_threshold) = self.should_continue_evicting(
1227                budget,
1228                eviction_count,
1229            ) {
1230                if let Some(entry) = self.lru_cache.peek_oldest(budget as u8) {
1231                    // Only evict this item if it wasn't used in the previous frame. The reason being that if it
1232                    // was used the previous frame then it will likely be used in this frame too, and we don't
1233                    // want to be continually evicting and reuploading the item every frame.
1234                    if entry.last_access.frame_id() + age_threshold > previous_frame_id {
1235                        // Since the LRU cache is ordered by frame access, we can break out of the loop here because
1236                        // we know that all remaining items were also used in the previous frame (or more recently).
1237                        break;
1238                    }
1239                    if entry.last_access.frame_id() > youngest_evicted {
1240                        youngest_evicted = entry.last_access.frame_id();
1241                    }
1242                    let entry = self.lru_cache.pop_oldest(budget as u8).unwrap();
1243                    entry.evict();
1244                    self.free(&entry);
1245                    eviction_count += 1;
1246                } else {
1247                    // The LRU cache is empty, all remaining items use manual
1248                    // eviction. In this case, there's nothing we can do until
1249                    // the calling code manually evicts items to reduce the
1250                    // allocated cache size.
1251                    break;
1252                }
1253            }
1254        }
1255
1256        if eviction_count > 0 {
1257            profile.set(profiler::TEXTURE_CACHE_EVICTION_COUNT, eviction_count);
1258            profile.set(
1259                profiler::TEXTURE_CACHE_YOUNGEST_EVICTION,
1260                self.now.frame_id().as_u64() - youngest_evicted.as_u64()
1261            );
1262        }
1263    }
1264
1265    // Free a cache entry from the standalone list or shared cache.
1266    fn free(&mut self, entry: &CacheEntry) {
1267        match entry.details {
1268            EntryDetails::Standalone { size_in_bytes, .. } => {
1269                self.bytes_allocated[BudgetType::Standalone as usize] -= size_in_bytes;
1270
1271                // This is a standalone texture allocation. Free it directly.
1272                self.pending_updates.push_free(entry.texture_id);
1273            }
1274            EntryDetails::Cache { origin, alloc_id, allocated_size_in_bytes } => {
1275                let (allocator_list, budget_type) = self.shared_textures.select(
1276                    entry.input_format,
1277                    entry.filter,
1278                    entry.shader,
1279                );
1280
1281                allocator_list.deallocate(entry.texture_id, alloc_id);
1282
1283                self.bytes_allocated[budget_type as usize] -= allocated_size_in_bytes;
1284
1285                if self.debug_flags.contains(
1286                    DebugFlags::TEXTURE_CACHE_DBG |
1287                    DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
1288                {
1289                    self.pending_updates.push_debug_clear(
1290                        entry.texture_id,
1291                        origin,
1292                        entry.size.width,
1293                        entry.size.height,
1294                    );
1295                }
1296            }
1297        }
1298    }
1299
1300    /// Allocate a block from the shared cache.
1301    fn allocate_from_shared_cache(
1302        &mut self,
1303        params: &CacheAllocParams,
1304    ) -> (CacheEntry, BudgetType) {
1305        let (allocator_list, budget_type) = self.shared_textures.select(
1306            params.descriptor.format,
1307            params.filter,
1308            params.shader,
1309        );
1310
1311        // To avoid referring to self in the closure.
1312        let next_id = &mut self.next_id;
1313        let pending_updates = &mut self.pending_updates;
1314
1315        let (texture_id, alloc_id, allocated_rect) = allocator_list.allocate(
1316            params.descriptor.size,
1317            &mut |size, parameters| {
1318                let texture_id = *next_id;
1319                next_id.0 += 1;
1320                pending_updates.push_alloc(
1321                    texture_id,
1322                    TextureCacheAllocInfo {
1323                        target: ImageBufferKind::Texture2D,
1324                        width: size.width,
1325                        height: size.height,
1326                        format: parameters.formats.internal,
1327                        filter: parameters.filter,
1328                        is_shared_cache: true,
1329                        has_depth: false,
1330                        category: TextureCacheCategory::Atlas,
1331                    },
1332                );
1333
1334                texture_id
1335            },
1336        );
1337
1338        let formats = &allocator_list.texture_parameters().formats;
1339
1340        let swizzle = if formats.external == params.descriptor.format {
1341            Swizzle::default()
1342        } else {
1343            match self.swizzle {
1344                Some(_) => Swizzle::Bgra,
1345                None => Swizzle::default(),
1346            }
1347        };
1348
1349        let bpp = formats.internal.bytes_per_pixel();
1350        let allocated_size_in_bytes = (allocated_rect.area() * bpp) as usize;
1351        self.bytes_allocated[budget_type as usize] += allocated_size_in_bytes;
1352
1353        (CacheEntry {
1354            size: params.descriptor.size,
1355            user_data: params.user_data,
1356            last_access: self.now,
1357            details: EntryDetails::Cache {
1358                origin: allocated_rect.min,
1359                alloc_id,
1360                allocated_size_in_bytes,
1361            },
1362            uv_rect_handle: GpuCacheHandle::new(),
1363            input_format: params.descriptor.format,
1364            filter: params.filter,
1365            swizzle,
1366            texture_id,
1367            eviction_notice: None,
1368            uv_rect_kind: params.uv_rect_kind,
1369            shader: params.shader
1370        }, budget_type)
1371    }
1372
1373    // Returns true if the given image descriptor *may* be
1374    // placed in the shared texture cache.
1375    pub fn is_allowed_in_shared_cache(
1376        &self,
1377        filter: TextureFilter,
1378        descriptor: &ImageDescriptor,
1379    ) -> bool {
1380        let mut allowed_in_shared_cache = true;
1381
1382        if matches!(descriptor.format, ImageFormat::RGBA8 | ImageFormat::BGRA8)
1383            && filter == TextureFilter::Linear
1384        {
1385            // Allow the maximum that can fit in the linear color texture's two column layout.
1386            let max = self.shared_textures.color8_linear.size() / 2;
1387            allowed_in_shared_cache = descriptor.size.width.max(descriptor.size.height) <= max;
1388        } else if descriptor.size.width > TEXTURE_REGION_DIMENSIONS {
1389            allowed_in_shared_cache = false;
1390        }
1391
1392        if descriptor.size.height > TEXTURE_REGION_DIMENSIONS {
1393            allowed_in_shared_cache = false;
1394        }
1395
1396        // TODO(gw): For now, alpha formats of the texture cache can only be linearly sampled.
1397        //           Nearest sampling gets a standalone texture.
1398        //           This is probably rare enough that it can be fixed up later.
1399        if filter == TextureFilter::Nearest &&
1400           descriptor.format.bytes_per_pixel() <= 2
1401        {
1402            allowed_in_shared_cache = false;
1403        }
1404
1405        allowed_in_shared_cache
1406    }
1407
1408    /// Allocate a render target via the pending updates sent to the renderer
1409    pub fn alloc_render_target(
1410        &mut self,
1411        size: DeviceIntSize,
1412        format: ImageFormat,
1413    ) -> CacheTextureId {
1414        let texture_id = self.next_id;
1415        self.next_id.0 += 1;
1416
1417        // Push a command to allocate device storage of the right size / format.
1418        let info = TextureCacheAllocInfo {
1419            target: ImageBufferKind::Texture2D,
1420            width: size.width,
1421            height: size.height,
1422            format,
1423            filter: TextureFilter::Linear,
1424            is_shared_cache: false,
1425            has_depth: false,
1426            category: TextureCacheCategory::RenderTarget,
1427        };
1428
1429        self.pending_updates.push_alloc(texture_id, info);
1430
1431        texture_id
1432    }
1433
1434    /// Free an existing render target
1435    pub fn free_render_target(
1436        &mut self,
1437        id: CacheTextureId,
1438    ) {
1439        self.pending_updates.push_free(id);
1440    }
1441
1442    /// Allocates a new standalone cache entry.
1443    fn allocate_standalone_entry(
1444        &mut self,
1445        params: &CacheAllocParams,
1446    ) -> (CacheEntry, BudgetType) {
1447        let texture_id = self.next_id;
1448        self.next_id.0 += 1;
1449
1450        // Push a command to allocate device storage of the right size / format.
1451        let info = TextureCacheAllocInfo {
1452            target: ImageBufferKind::Texture2D,
1453            width: params.descriptor.size.width,
1454            height: params.descriptor.size.height,
1455            format: params.descriptor.format,
1456            filter: params.filter,
1457            is_shared_cache: false,
1458            has_depth: false,
1459            category: TextureCacheCategory::Standalone,
1460        };
1461
1462        let size_in_bytes = (info.width * info.height * info.format.bytes_per_pixel()) as usize;
1463        self.bytes_allocated[BudgetType::Standalone as usize] += size_in_bytes;
1464
1465        self.pending_updates.push_alloc(texture_id, info);
1466
1467        // Special handing for BGRA8 textures that may need to be swizzled.
1468        let swizzle = if params.descriptor.format == ImageFormat::BGRA8 {
1469            self.swizzle.map(|s| s.bgra8_sampling_swizzle)
1470        } else {
1471            None
1472        };
1473
1474        (CacheEntry::new_standalone(
1475            texture_id,
1476            self.now,
1477            params,
1478            swizzle.unwrap_or_default(),
1479            size_in_bytes,
1480        ), BudgetType::Standalone)
1481    }
1482
1483    /// Allocates a cache entry for the given parameters, and updates the
1484    /// provided handle to point to the new entry.
1485    fn allocate(
1486        &mut self,
1487        params: &CacheAllocParams,
1488        handle: &mut TextureCacheHandle,
1489        eviction: Eviction,
1490    ) {
1491        debug_assert!(self.now.is_valid());
1492        assert!(!params.descriptor.size.is_empty());
1493
1494        // If this image doesn't qualify to go in the shared (batching) cache,
1495        // allocate a standalone entry.
1496        let use_shared_cache = self.is_allowed_in_shared_cache(params.filter, &params.descriptor);
1497        let (new_cache_entry, budget_type) = if use_shared_cache {
1498            self.allocate_from_shared_cache(params)
1499        } else {
1500            self.allocate_standalone_entry(params)
1501        };
1502
1503        let details = new_cache_entry.details.clone();
1504        let texture_id = new_cache_entry.texture_id;
1505
1506        // If the handle points to a valid cache entry, we want to replace the
1507        // cache entry with our newly updated location. We also need to ensure
1508        // that the storage (region or standalone) associated with the previous
1509        // entry here gets freed.
1510        //
1511        // If the handle is invalid, we need to insert the data, and append the
1512        // result to the corresponding vector.
1513        let old_entry = match (&mut *handle, eviction) {
1514            (TextureCacheHandle::Auto(handle), Eviction::Auto) => {
1515                self.lru_cache.replace_or_insert(handle, budget_type as u8, new_cache_entry)
1516            },
1517            (TextureCacheHandle::Manual(handle), Eviction::Manual) => {
1518                let entry = self.manual_entries.get_opt_mut(handle)
1519                    .expect("Don't call this after evicting");
1520                Some(mem::replace(entry, new_cache_entry))
1521            },
1522            (TextureCacheHandle::Manual(_), Eviction::Auto) |
1523            (TextureCacheHandle::Auto(_), Eviction::Manual) => {
1524                panic!("Can't change eviction policy after initial allocation");
1525            },
1526            (TextureCacheHandle::Empty, Eviction::Auto) => {
1527                let new_handle = self.lru_cache.push_new(budget_type as u8, new_cache_entry);
1528                *handle = TextureCacheHandle::Auto(new_handle);
1529                None
1530            },
1531            (TextureCacheHandle::Empty, Eviction::Manual) => {
1532                let manual_handle = self.manual_entries.insert(new_cache_entry);
1533                let new_handle = manual_handle.weak();
1534                self.manual_handles.push(manual_handle);
1535                *handle = TextureCacheHandle::Manual(new_handle);
1536                None
1537            },
1538        };
1539        if let Some(old_entry) = old_entry {
1540            old_entry.evict();
1541            self.free(&old_entry);
1542        }
1543
1544        if let EntryDetails::Cache { alloc_id, .. } = details {
1545            let allocator_list = self.shared_textures.select(
1546                params.descriptor.format,
1547                params.filter,
1548                params.shader,
1549            ).0;
1550
1551            allocator_list.set_handle(texture_id, alloc_id, handle);
1552        }
1553    }
1554
1555    pub fn shared_alpha_expected_format(&self) -> ImageFormat {
1556        self.shared_textures.alpha8_linear.texture_parameters().formats.external
1557    }
1558
1559    pub fn shared_color_expected_format(&self) -> ImageFormat {
1560        self.shared_textures.color8_linear.texture_parameters().formats.external
1561    }
1562
1563
1564    #[cfg(test)]
1565    pub fn total_allocated_bytes_for_testing(&self) -> usize {
1566        BudgetType::iter().map(|b| self.bytes_allocated[b as usize]).sum()
1567    }
1568
1569    pub fn report_memory(&self, ops: &mut MallocSizeOfOps) -> usize {
1570        self.lru_cache.size_of(ops)
1571    }
1572}
1573
1574#[cfg_attr(feature = "capture", derive(Serialize))]
1575#[cfg_attr(feature = "replay", derive(Deserialize))]
1576pub struct TextureParameters {
1577    pub formats: TextureFormatPair<ImageFormat>,
1578    pub filter: TextureFilter,
1579}
1580
1581impl TextureCacheUpdate {
1582    // Constructs a TextureCacheUpdate operation to be passed to the
1583    // rendering thread in order to do an upload to the right
1584    // location in the texture cache.
1585    fn new_update(
1586        data: CachedImageData,
1587        descriptor: &ImageDescriptor,
1588        origin: DeviceIntPoint,
1589        size: DeviceIntSize,
1590        use_upload_format: bool,
1591        dirty_rect: &ImageDirtyRect,
1592    ) -> TextureCacheUpdate {
1593        let source = match data {
1594            CachedImageData::Snapshot => {
1595                panic!("Snapshots should not do texture uploads");
1596            }
1597            CachedImageData::Blob => {
1598                panic!("The vector image should have been rasterized.");
1599            }
1600            CachedImageData::External(ext_image) => match ext_image.image_type {
1601                ExternalImageType::TextureHandle(_) => {
1602                    panic!("External texture handle should not go through texture_cache.");
1603                }
1604                ExternalImageType::Buffer => TextureUpdateSource::External {
1605                    id: ext_image.id,
1606                    channel_index: ext_image.channel_index,
1607                },
1608            },
1609            CachedImageData::Raw(bytes) => {
1610                let finish = descriptor.offset +
1611                    descriptor.size.width * descriptor.format.bytes_per_pixel() +
1612                    (descriptor.size.height - 1) * descriptor.compute_stride();
1613                assert!(bytes.len() >= finish as usize);
1614
1615                TextureUpdateSource::Bytes { data: bytes }
1616            }
1617        };
1618        let format_override = if use_upload_format {
1619            Some(descriptor.format)
1620        } else {
1621            None
1622        };
1623
1624        match *dirty_rect {
1625            DirtyRect::Partial(dirty) => {
1626                // the dirty rectangle doesn't have to be within the area but has to intersect it, at least
1627                let stride = descriptor.compute_stride();
1628                let offset = descriptor.offset + dirty.min.y * stride + dirty.min.x * descriptor.format.bytes_per_pixel();
1629
1630                TextureCacheUpdate {
1631                    rect: DeviceIntRect::from_origin_and_size(
1632                        DeviceIntPoint::new(origin.x + dirty.min.x, origin.y + dirty.min.y),
1633                        DeviceIntSize::new(
1634                            dirty.width().min(size.width - dirty.min.x),
1635                            dirty.height().min(size.height - dirty.min.y),
1636                        ),
1637                    ),
1638                    source,
1639                    stride: Some(stride),
1640                    offset,
1641                    format_override,
1642                }
1643            }
1644            DirtyRect::All => {
1645                TextureCacheUpdate {
1646                    rect: DeviceIntRect::from_origin_and_size(origin, size),
1647                    source,
1648                    stride: descriptor.stride,
1649                    offset: descriptor.offset,
1650                    format_override,
1651                }
1652            }
1653        }
1654    }
1655}
1656
1657#[cfg(test)]
1658mod test_texture_cache {
1659    #[test]
1660    fn check_allocation_size_balance() {
1661        // Allocate some glyphs, observe the total allocation size, and free
1662        // the glyphs again. Check that the total allocation size is back at the
1663        // original value.
1664
1665        use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
1666        use crate::gpu_cache::GpuCache;
1667        use crate::device::TextureFilter;
1668        use crate::gpu_types::UvRectKind;
1669        use api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, DirtyRect};
1670        use api::units::*;
1671        use euclid::size2;
1672        let mut texture_cache = TextureCache::new_for_testing(2048, ImageFormat::BGRA8);
1673        let mut gpu_cache = GpuCache::new_for_testing();
1674
1675        let sizes: &[DeviceIntSize] = &[
1676            size2(23, 27),
1677            size2(15, 22),
1678            size2(11, 5),
1679            size2(20, 25),
1680            size2(38, 41),
1681            size2(11, 19),
1682            size2(13, 21),
1683            size2(37, 40),
1684            size2(13, 15),
1685            size2(14, 16),
1686            size2(10, 9),
1687            size2(25, 28),
1688        ];
1689
1690        let bytes_at_start = texture_cache.total_allocated_bytes_for_testing();
1691
1692        let handles: Vec<TextureCacheHandle> = sizes.iter().map(|size| {
1693            let mut texture_cache_handle = TextureCacheHandle::invalid();
1694            texture_cache.request(&texture_cache_handle, &mut gpu_cache);
1695            texture_cache.update(
1696                &mut texture_cache_handle,
1697                ImageDescriptor {
1698                    size: *size,
1699                    stride: None,
1700                    format: ImageFormat::BGRA8,
1701                    flags: ImageDescriptorFlags::empty(),
1702                    offset: 0,
1703                },
1704                TextureFilter::Linear,
1705                None,
1706                [0.0; 4],
1707                DirtyRect::All,
1708                &mut gpu_cache,
1709                None,
1710                UvRectKind::Rect,
1711                Eviction::Manual,
1712                TargetShader::Text,
1713            );
1714            texture_cache_handle
1715        }).collect();
1716
1717        let bytes_after_allocating = texture_cache.total_allocated_bytes_for_testing();
1718        assert!(bytes_after_allocating > bytes_at_start);
1719
1720        for handle in handles {
1721            texture_cache.evict_handle(&handle);
1722        }
1723
1724        let bytes_at_end = texture_cache.total_allocated_bytes_for_testing();
1725        assert_eq!(bytes_at_end, bytes_at_start);
1726    }
1727}