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