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