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