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 std::mem;
6 use smallvec::SmallVec;
7 use api::{ImageFormat, ImageBufferKind, DebugFlags};
8 use api::units::*;
9 use crate::device::TextureFilter;
10 use crate::internal_types::{
11     CacheTextureId, TextureUpdateList, Swizzle, TextureCacheAllocInfo, TextureCacheCategory,
12     TextureSource, FrameStamp, FrameId,
13 };
14 use crate::profiler::{self, TransactionProfile};
15 use crate::gpu_types::{ImageSource, UvRectKind};
16 use crate::gpu_cache::{GpuCache, GpuCacheHandle};
17 use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
18 
19 
20 #[derive(Debug, PartialEq)]
21 #[cfg_attr(feature = "capture", derive(Serialize))]
22 #[cfg_attr(feature = "replay", derive(Deserialize))]
23 pub enum PictureCacheEntryMarker {}
24 
25 malloc_size_of::malloc_size_of_is_0!(PictureCacheEntryMarker);
26 
27 pub type PictureCacheTextureHandle = WeakFreeListHandle<PictureCacheEntryMarker>;
28 
29 use std::cmp;
30 
31 // Stores information related to a single entry in the texture
32 // cache. This is stored for each item whether it's in the shared
33 // cache or a standalone texture.
34 #[derive(Debug)]
35 #[cfg_attr(feature = "capture", derive(Serialize))]
36 #[cfg_attr(feature = "replay", derive(Deserialize))]
37 pub struct PictureCacheEntry {
38     /// Size of the requested tile.
39     pub size: DeviceIntSize,
40     /// The last frame this item was requested for rendering.
41     // TODO(gw): This stamp is only used for picture cache tiles, and some checks
42     //           in the glyph cache eviction code. We could probably remove it
43     //           entirely in future (or move to EntryDetails::Picture).
44     pub last_access: FrameStamp,
45     /// Handle to the resource rect in the GPU cache.
46     pub uv_rect_handle: GpuCacheHandle,
47     /// Image format of the data that the entry expects.
48     pub filter: TextureFilter,
49     /// The actual device texture ID this is part of.
50     pub texture_id: CacheTextureId,
51 }
52 
53 impl PictureCacheEntry {
update_gpu_cache(&mut self, gpu_cache: &mut GpuCache)54     fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
55         if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) {
56             let origin = DeviceIntPoint::zero();
57             let image_source = ImageSource {
58                 p0: origin.to_f32(),
59                 p1: (origin + self.size).to_f32(),
60                 uv_rect_kind: UvRectKind::Rect,
61                 user_data: [0.0; 4],
62             };
63             image_source.write_gpu_blocks(&mut request);
64         }
65     }
66 }
67 
68 /// The textures used to hold picture cache tiles.
69 #[cfg_attr(feature = "capture", derive(Serialize))]
70 #[cfg_attr(feature = "replay", derive(Deserialize))]
71 struct PictureTexture {
72     texture_id: CacheTextureId,
73     size: DeviceIntSize,
74     is_allocated: bool,
75     last_frame_used: FrameId,
76 }
77 
78 /// The textures used to hold picture cache tiles.
79 #[cfg_attr(feature = "capture", derive(Serialize))]
80 #[cfg_attr(feature = "replay", derive(Deserialize))]
81 pub struct PictureTextures {
82     /// Current list of textures in the pool
83     textures: Vec<PictureTexture>,
84     /// Default tile size for content tiles
85     default_tile_size: DeviceIntSize,
86     /// Number of currently allocated textures in the pool
87     allocated_texture_count: usize,
88     /// Texture filter to use for picture cache textures
89     filter: TextureFilter,
90 
91     debug_flags: DebugFlags,
92 
93     /// Cache of picture cache entries.
94     cache_entries: FreeList<PictureCacheEntry, PictureCacheEntryMarker>,
95     /// Strong handles for the picture_cache_entries FreeList.
96     cache_handles: Vec<FreeListHandle<PictureCacheEntryMarker>>,
97 
98     now: FrameStamp,
99 }
100 
101 impl PictureTextures {
new( default_tile_size: DeviceIntSize, filter: TextureFilter, ) -> Self102     pub fn new(
103         default_tile_size: DeviceIntSize,
104         filter: TextureFilter,
105     ) -> Self {
106         PictureTextures {
107             textures: Vec::new(),
108             default_tile_size,
109             allocated_texture_count: 0,
110             filter,
111             debug_flags: DebugFlags::empty(),
112             cache_entries: FreeList::new(),
113             cache_handles: Vec::new(),
114             now: FrameStamp::INVALID,
115         }
116     }
117 
begin_frame(&mut self, stamp: FrameStamp, pending_updates: &mut TextureUpdateList)118     pub fn begin_frame(&mut self, stamp: FrameStamp, pending_updates: &mut TextureUpdateList) {
119         self.now = stamp;
120 
121         // Expire picture cache tiles that haven't been referenced in the last frame.
122         // The picture cache code manually keeps tiles alive by calling `request` on
123         // them if it wants to retain a tile that is currently not visible.
124         self.expire_old_tiles(pending_updates);
125     }
126 
default_tile_size(&self) -> DeviceIntSize127     pub fn default_tile_size(&self) -> DeviceIntSize {
128         self.default_tile_size
129     }
130 
update( &mut self, tile_size: DeviceIntSize, handle: &mut Option<PictureCacheTextureHandle>, gpu_cache: &mut GpuCache, next_texture_id: &mut CacheTextureId, pending_updates: &mut TextureUpdateList, )131     pub fn update(
132         &mut self,
133         tile_size: DeviceIntSize,
134         handle: &mut Option<PictureCacheTextureHandle>,
135         gpu_cache: &mut GpuCache,
136         next_texture_id: &mut CacheTextureId,
137         pending_updates: &mut TextureUpdateList,
138     ) {
139         debug_assert!(self.now.is_valid());
140         debug_assert!(tile_size.width > 0 && tile_size.height > 0);
141 
142         let need_alloc = match handle {
143             None => true,
144             Some(handle) => {
145                 // Check if the entry has been evicted.
146                 !self.entry_exists(&handle)
147             },
148         };
149 
150         if need_alloc {
151             let new_handle = self.get_or_allocate_tile(
152                 tile_size,
153                 next_texture_id,
154                 pending_updates,
155             );
156 
157             *handle = Some(new_handle);
158         }
159 
160         if let Some(handle) = handle {
161             // Upload the resource rect and texture array layer.
162             self.cache_entries
163                 .get_opt_mut(handle)
164                 .expect("BUG: handle must be valid now")
165                 .update_gpu_cache(gpu_cache);
166         } else {
167             panic!("The handle should be valid picture cache handle now")
168         }
169     }
170 
get_or_allocate_tile( &mut self, tile_size: DeviceIntSize, next_texture_id: &mut CacheTextureId, pending_updates: &mut TextureUpdateList, ) -> PictureCacheTextureHandle171     pub fn get_or_allocate_tile(
172         &mut self,
173         tile_size: DeviceIntSize,
174         next_texture_id: &mut CacheTextureId,
175         pending_updates: &mut TextureUpdateList,
176     ) -> PictureCacheTextureHandle {
177         let mut texture_id = None;
178         self.allocated_texture_count += 1;
179 
180         for texture in &mut self.textures {
181             if texture.size == tile_size && !texture.is_allocated {
182                 // Found a target that's not currently in use which matches. Update
183                 // the last_frame_used for GC purposes.
184                 texture.is_allocated = true;
185                 texture.last_frame_used = FrameId::INVALID;
186                 texture_id = Some(texture.texture_id);
187                 break;
188             }
189         }
190 
191         // Need to create a new render target and add it to the pool
192 
193         let texture_id = texture_id.unwrap_or_else(|| {
194             let texture_id = *next_texture_id;
195             next_texture_id.0 += 1;
196 
197             // Push a command to allocate device storage of the right size / format.
198             let info = TextureCacheAllocInfo {
199                 target: ImageBufferKind::Texture2D,
200                 width: tile_size.width,
201                 height: tile_size.height,
202                 format: ImageFormat::RGBA8,
203                 filter: self.filter,
204                 is_shared_cache: false,
205                 has_depth: true,
206                 category: TextureCacheCategory::PictureTile,
207             };
208 
209             pending_updates.push_alloc(texture_id, info);
210 
211             self.textures.push(PictureTexture {
212                 texture_id,
213                 is_allocated: true,
214                 size: tile_size,
215                 last_frame_used: FrameId::INVALID,
216             });
217 
218             texture_id
219         });
220 
221         let cache_entry = PictureCacheEntry {
222             size: tile_size,
223             last_access: self.now,
224             uv_rect_handle: GpuCacheHandle::new(),
225             filter: self.filter,
226             texture_id,
227         };
228 
229         // Add the cache entry to the picture_textures.cache_entries FreeList.
230         let strong_handle = self.cache_entries.insert(cache_entry);
231         let new_handle = strong_handle.weak();
232 
233         self.cache_handles.push(strong_handle);
234 
235         new_handle
236     }
237 
free_tile( &mut self, id: CacheTextureId, current_frame_id: FrameId, pending_updates: &mut TextureUpdateList, )238     pub fn free_tile(
239         &mut self,
240         id: CacheTextureId,
241         current_frame_id: FrameId,
242         pending_updates: &mut TextureUpdateList,
243     ) {
244         self.allocated_texture_count -= 1;
245 
246         let texture = self.textures
247             .iter_mut()
248             .find(|t| t.texture_id == id)
249             .expect("bug: invalid texture id");
250 
251         assert!(texture.is_allocated);
252         texture.is_allocated = false;
253 
254         assert_eq!(texture.last_frame_used, FrameId::INVALID);
255         texture.last_frame_used = current_frame_id;
256 
257         if self.debug_flags.contains(
258             DebugFlags::TEXTURE_CACHE_DBG |
259             DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
260         {
261             pending_updates.push_debug_clear(
262                 id,
263                 DeviceIntPoint::zero(),
264                 texture.size.width,
265                 texture.size.height,
266             );
267         }
268     }
269 
request(&mut self, handle: &PictureCacheTextureHandle, gpu_cache: &mut GpuCache) -> bool270     pub fn request(&mut self, handle: &PictureCacheTextureHandle, gpu_cache: &mut GpuCache) -> bool {
271         let entry = self.cache_entries.get_opt_mut(handle);
272         let now = self.now;
273         entry.map_or(true, |entry| {
274             // If an image is requested that is already in the cache,
275             // refresh the GPU cache data associated with this item.
276             entry.last_access = now;
277             entry.update_gpu_cache(gpu_cache);
278             false
279         })
280     }
281 
get_texture_source(&self, handle: &PictureCacheTextureHandle) -> TextureSource282     pub fn get_texture_source(&self, handle: &PictureCacheTextureHandle) -> TextureSource {
283         let entry = self.cache_entries.get_opt(handle)
284             .expect("BUG: was dropped from cache or not updated!");
285 
286         debug_assert_eq!(entry.last_access, self.now);
287 
288         TextureSource::TextureCache(entry.texture_id, Swizzle::default())
289     }
290 
291     /// Expire picture cache tiles that haven't been referenced in the last frame.
292     /// The picture cache code manually keeps tiles alive by calling `request` on
293     /// them if it wants to retain a tile that is currently not visible.
expire_old_tiles(&mut self, pending_updates: &mut TextureUpdateList)294     pub fn expire_old_tiles(&mut self, pending_updates: &mut TextureUpdateList) {
295         for i in (0 .. self.cache_handles.len()).rev() {
296             let evict = {
297                 let entry = self.cache_entries.get(
298                     &self.cache_handles[i]
299                 );
300 
301                 // This function is called at the beginning of the frame,
302                 // so we don't yet know which picture cache tiles will be
303                 // requested this frame. Therefore only evict picture cache
304                 // tiles which weren't requested in the *previous* frame.
305                 entry.last_access.frame_id() < self.now.frame_id() - 1
306             };
307 
308             if evict {
309                 let handle = self.cache_handles.swap_remove(i);
310                 let entry = self.cache_entries.free(handle);
311                 self.free_tile(entry.texture_id, self.now.frame_id(), pending_updates);
312             }
313         }
314     }
315 
clear(&mut self, pending_updates: &mut TextureUpdateList)316     pub fn clear(&mut self, pending_updates: &mut TextureUpdateList) {
317         for handle in mem::take(&mut self.cache_handles) {
318             let entry = self.cache_entries.free(handle);
319             self.free_tile(entry.texture_id, self.now.frame_id(), pending_updates);
320         }
321 
322         for texture in self.textures.drain(..) {
323             pending_updates.push_free(texture.texture_id);
324         }
325     }
326 
update_profile(&self, profile: &mut TransactionProfile)327     pub fn update_profile(&self, profile: &mut TransactionProfile) {
328         profile.set(profiler::PICTURE_TILES, self.textures.len());
329     }
330 
331     /// Simple garbage collect of picture cache tiles
gc( &mut self, pending_updates: &mut TextureUpdateList, )332     pub fn gc(
333         &mut self,
334         pending_updates: &mut TextureUpdateList,
335     ) {
336         // Allow the picture cache pool to keep 25% of the current allocated tile count
337         // as free textures to be reused. This ensures the allowed tile count is appropriate
338         // based on current window size.
339         let free_texture_count = self.textures.len() - self.allocated_texture_count;
340         let allowed_retained_count = (self.allocated_texture_count as f32 * 0.25).ceil() as usize;
341         let do_gc = free_texture_count > allowed_retained_count;
342 
343         if do_gc {
344             // Sort the current pool by age, so that we remove oldest textures first
345             self.textures.sort_unstable_by_key(|t| cmp::Reverse(t.last_frame_used));
346 
347             // We can't just use retain() because `PictureTexture` requires manual cleanup.
348             let mut allocated_targets = SmallVec::<[PictureTexture; 32]>::new();
349             let mut retained_targets = SmallVec::<[PictureTexture; 32]>::new();
350 
351             for target in self.textures.drain(..) {
352                 if target.is_allocated {
353                     // Allocated targets can't be collected
354                     allocated_targets.push(target);
355                 } else if retained_targets.len() < allowed_retained_count {
356                     // Retain the most recently used targets up to the allowed count
357                     retained_targets.push(target);
358                 } else {
359                     // The rest of the targets get freed
360                     assert_ne!(target.last_frame_used, FrameId::INVALID);
361                     pending_updates.push_free(target.texture_id);
362                 }
363             }
364 
365             self.textures.extend(retained_targets);
366             self.textures.extend(allocated_targets);
367         }
368     }
369 
entry_exists(&self, handle: &PictureCacheTextureHandle) -> bool370     pub fn entry_exists(&self, handle: &PictureCacheTextureHandle) -> bool {
371         self.cache_entries.get_opt(handle).is_some()
372     }
373 
set_debug_flags(&mut self, flags: DebugFlags)374     pub fn set_debug_flags(&mut self, flags: DebugFlags) {
375         self.debug_flags = flags;
376     }
377 
378     #[cfg(feature = "replay")]
filter(&self) -> TextureFilter379     pub fn filter(&self) -> TextureFilter {
380         self.filter
381     }
382 }
383