Skip to main content

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