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::{ColorF, DeviceUintPoint, DeviceUintRect, DeviceUintSize}; 6 use api::{ExternalImageType, ImageData, ImageFormat, PremultipliedColorF}; 7 use api::ImageDescriptor; 8 use device::TextureFilter; 9 use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle}; 10 use gpu_cache::{GpuCache, GpuCacheHandle}; 11 use gpu_types::ImageSource; 12 use internal_types::{CacheTextureId, FastHashMap, TextureUpdateList, TextureUpdateSource}; 13 use internal_types::{RenderTargetInfo, SourceTexture, TextureUpdate, TextureUpdateOp}; 14 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters}; 15 use render_backend::FrameId; 16 use resource_cache::CacheItem; 17 use std::cmp; 18 use std::mem; 19 20 // The fixed number of layers for the shared texture cache. 21 // There is one array texture per image format, allocated lazily. 22 const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4; 23 const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1; 24 25 // The dimensions of each layer in the texture cache. 26 const TEXTURE_LAYER_DIMENSIONS: u32 = 2048; 27 28 // The size of each region (page) in a texture layer. 29 const TEXTURE_REGION_DIMENSIONS: u32 = 512; 30 31 // Maintains a simple freelist of texture IDs that are mapped 32 // to real API-specific texture IDs in the renderer. 33 #[cfg_attr(feature = "capture", derive(Serialize))] 34 #[cfg_attr(feature = "replay", derive(Deserialize))] 35 struct CacheTextureIdList { 36 free_lists: FastHashMap<ImageFormat, Vec<CacheTextureId>>, 37 next_id: usize, 38 } 39 40 impl CacheTextureIdList { new() -> Self41 fn new() -> Self { 42 CacheTextureIdList { 43 next_id: 0, 44 free_lists: FastHashMap::default(), 45 } 46 } 47 allocate(&mut self, format: ImageFormat) -> CacheTextureId48 fn allocate(&mut self, format: ImageFormat) -> CacheTextureId { 49 // If nothing on the free list of texture IDs, 50 // allocate a new one. 51 self.free_lists.get_mut(&format) 52 .and_then(|fl| fl.pop()) 53 .unwrap_or_else(|| { 54 self.next_id += 1; 55 CacheTextureId(self.next_id - 1) 56 }) 57 } 58 free(&mut self, id: CacheTextureId, format: ImageFormat)59 fn free(&mut self, id: CacheTextureId, format: ImageFormat) { 60 self.free_lists 61 .entry(format) 62 .or_insert(Vec::new()) 63 .push(id); 64 } 65 } 66 67 // Items in the texture cache can either be standalone textures, 68 // or a sub-rect inside the shared cache. 69 #[derive(Debug)] 70 #[cfg_attr(feature = "capture", derive(Serialize))] 71 #[cfg_attr(feature = "replay", derive(Deserialize))] 72 enum EntryKind { 73 Standalone, 74 Cache { 75 // Origin within the texture layer where this item exists. 76 origin: DeviceUintPoint, 77 // The layer index of the texture array. 78 layer_index: u16, 79 // The region that this entry belongs to in the layer. 80 region_index: u16, 81 }, 82 } 83 84 // Stores information related to a single entry in the texture 85 // cache. This is stored for each item whether it's in the shared 86 // cache or a standalone texture. 87 #[derive(Debug)] 88 #[cfg_attr(feature = "capture", derive(Serialize))] 89 #[cfg_attr(feature = "replay", derive(Deserialize))] 90 struct CacheEntry { 91 // Size the requested item, in device pixels. 92 size: DeviceUintSize, 93 // Details specific to standalone or shared items. 94 kind: EntryKind, 95 // Arbitrary user data associated with this item. 96 user_data: [f32; 3], 97 // The last frame this item was requested for rendering. 98 last_access: FrameId, 99 // Handle to the resource rect in the GPU cache. 100 uv_rect_handle: GpuCacheHandle, 101 // Image format of the item. 102 format: ImageFormat, 103 filter: TextureFilter, 104 // The actual device texture ID this is part of. 105 texture_id: CacheTextureId, 106 // Color to modulate this cache item by. 107 color: PremultipliedColorF, 108 } 109 110 impl CacheEntry { 111 // Create a new entry for a standalone texture. new_standalone( texture_id: CacheTextureId, size: DeviceUintSize, format: ImageFormat, filter: TextureFilter, user_data: [f32; 3], last_access: FrameId, ) -> Self112 fn new_standalone( 113 texture_id: CacheTextureId, 114 size: DeviceUintSize, 115 format: ImageFormat, 116 filter: TextureFilter, 117 user_data: [f32; 3], 118 last_access: FrameId, 119 ) -> Self { 120 CacheEntry { 121 size, 122 user_data, 123 last_access, 124 kind: EntryKind::Standalone, 125 texture_id, 126 format, 127 filter, 128 uv_rect_handle: GpuCacheHandle::new(), 129 color: ColorF::new(1.0, 1.0, 1.0, 1.0).premultiplied(), 130 } 131 } 132 133 // Update the GPU cache for this texture cache entry. 134 // This ensures that the UV rect, and texture layer index 135 // are up to date in the GPU cache for vertex shaders 136 // to fetch from. update_gpu_cache(&mut self, gpu_cache: &mut GpuCache)137 fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) { 138 if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) { 139 let (origin, layer_index) = match self.kind { 140 EntryKind::Standalone { .. } => (DeviceUintPoint::zero(), 0.0), 141 EntryKind::Cache { 142 origin, 143 layer_index, 144 .. 145 } => (origin, layer_index as f32), 146 }; 147 let image_source = ImageSource { 148 p0: origin.to_f32(), 149 p1: (origin + self.size).to_f32(), 150 color: self.color, 151 texture_layer: layer_index, 152 user_data: self.user_data, 153 }; 154 image_source.write_gpu_blocks(&mut request); 155 } 156 } 157 } 158 159 type WeakCacheEntryHandle = WeakFreeListHandle<CacheEntry>; 160 161 // A texture cache handle is a weak reference to a cache entry. 162 // If the handle has not been inserted into the cache yet, the 163 // value will be None. Even when the value is Some(), the location 164 // may not actually be valid if it has been evicted by the cache. 165 // In this case, the cache handle needs to re-upload this item 166 // to the texture cache (see request() below). 167 #[derive(Debug)] 168 #[cfg_attr(feature = "capture", derive(Clone, Serialize))] 169 #[cfg_attr(feature = "replay", derive(Deserialize))] 170 pub struct TextureCacheHandle { 171 entry: Option<WeakCacheEntryHandle>, 172 } 173 174 impl TextureCacheHandle { new() -> Self175 pub fn new() -> Self { 176 TextureCacheHandle { entry: None } 177 } 178 } 179 180 #[cfg_attr(feature = "capture", derive(Serialize))] 181 #[cfg_attr(feature = "replay", derive(Deserialize))] 182 pub struct TextureCache { 183 // A lazily allocated, fixed size, texture array for 184 // each format the texture cache supports. 185 array_rgba8_nearest: TextureArray, 186 array_a8_linear: TextureArray, 187 array_rgba8_linear: TextureArray, 188 189 // Maximum texture size supported by hardware. 190 max_texture_size: u32, 191 192 // A list of texture IDs that represent native 193 // texture handles. This indirection allows the texture 194 // cache to create / destroy / reuse texture handles 195 // without knowing anything about the device code. 196 cache_textures: CacheTextureIdList, 197 198 // A list of updates that need to be applied to the 199 // texture cache in the rendering thread this frame. 200 #[cfg_attr(feature = "serde", serde(skip))] 201 pending_updates: TextureUpdateList, 202 203 // The current frame ID. Used for cache eviction policies. 204 frame_id: FrameId, 205 206 // Maintains the list of all current items in 207 // the texture cache. 208 entries: FreeList<CacheEntry>, 209 210 // A list of the strong handles of items that were 211 // allocated in the standalone texture pool. Used 212 // for evicting old standalone textures. 213 standalone_entry_handles: Vec<FreeListHandle<CacheEntry>>, 214 215 // A list of the strong handles of items that were 216 // allocated in the shared texture cache. Used 217 // for evicting old cache items. 218 shared_entry_handles: Vec<FreeListHandle<CacheEntry>>, 219 } 220 221 impl TextureCache { new(max_texture_size: u32) -> Self222 pub fn new(max_texture_size: u32) -> Self { 223 TextureCache { 224 max_texture_size, 225 array_a8_linear: TextureArray::new( 226 ImageFormat::R8, 227 TextureFilter::Linear, 228 TEXTURE_ARRAY_LAYERS_LINEAR, 229 ), 230 array_rgba8_linear: TextureArray::new( 231 ImageFormat::BGRA8, 232 TextureFilter::Linear, 233 TEXTURE_ARRAY_LAYERS_LINEAR, 234 ), 235 array_rgba8_nearest: TextureArray::new( 236 ImageFormat::BGRA8, 237 TextureFilter::Nearest, 238 TEXTURE_ARRAY_LAYERS_NEAREST, 239 ), 240 cache_textures: CacheTextureIdList::new(), 241 pending_updates: TextureUpdateList::new(), 242 frame_id: FrameId(0), 243 entries: FreeList::new(), 244 standalone_entry_handles: Vec::new(), 245 shared_entry_handles: Vec::new(), 246 } 247 } 248 begin_frame(&mut self, frame_id: FrameId)249 pub fn begin_frame(&mut self, frame_id: FrameId) { 250 self.frame_id = frame_id; 251 } 252 end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters)253 pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) { 254 self.expire_old_standalone_entries(); 255 256 self.array_a8_linear 257 .update_profile(&mut texture_cache_profile.pages_a8_linear); 258 self.array_rgba8_linear 259 .update_profile(&mut texture_cache_profile.pages_rgba8_linear); 260 self.array_rgba8_nearest 261 .update_profile(&mut texture_cache_profile.pages_rgba8_nearest); 262 } 263 264 // Request an item in the texture cache. All images that will 265 // be used on a frame *must* have request() called on their 266 // handle, to update the last used timestamp and ensure 267 // that resources are not flushed from the cache too early. 268 // 269 // Returns true if the image needs to be uploaded to the 270 // texture cache (either never uploaded, or has been 271 // evicted on a previous frame). request(&mut self, handle: &TextureCacheHandle, gpu_cache: &mut GpuCache) -> bool272 pub fn request(&mut self, handle: &TextureCacheHandle, gpu_cache: &mut GpuCache) -> bool { 273 match handle.entry { 274 Some(ref handle) => { 275 match self.entries.get_opt_mut(handle) { 276 // If an image is requested that is already in the cache, 277 // refresh the GPU cache data associated with this item. 278 Some(entry) => { 279 entry.last_access = self.frame_id; 280 entry.update_gpu_cache(gpu_cache); 281 false 282 } 283 None => true, 284 } 285 } 286 None => true, 287 } 288 } 289 max_texture_size(&self) -> u32290 pub fn max_texture_size(&self) -> u32 { 291 self.max_texture_size 292 } 293 pending_updates(&mut self) -> TextureUpdateList294 pub fn pending_updates(&mut self) -> TextureUpdateList { 295 mem::replace(&mut self.pending_updates, TextureUpdateList::new()) 296 } 297 298 // Update the data stored by a given texture cache handle. update( &mut self, handle: &mut TextureCacheHandle, descriptor: ImageDescriptor, filter: TextureFilter, data: Option<ImageData>, user_data: [f32; 3], mut dirty_rect: Option<DeviceUintRect>, gpu_cache: &mut GpuCache, )299 pub fn update( 300 &mut self, 301 handle: &mut TextureCacheHandle, 302 descriptor: ImageDescriptor, 303 filter: TextureFilter, 304 data: Option<ImageData>, 305 user_data: [f32; 3], 306 mut dirty_rect: Option<DeviceUintRect>, 307 gpu_cache: &mut GpuCache, 308 ) { 309 // Determine if we need to allocate texture cache memory 310 // for this item. We need to reallocate if any of the following 311 // is true: 312 // - Never been in the cache 313 // - Has been in the cache but was evicted. 314 // - Exists in the cache but dimensions / format have changed. 315 let realloc = match handle.entry { 316 Some(ref handle) => { 317 match self.entries.get_opt(handle) { 318 Some(entry) => { 319 entry.size.width != descriptor.width || 320 entry.size.height != descriptor.height || 321 entry.format != descriptor.format 322 } 323 None => { 324 // Was previously allocated but has been evicted. 325 true 326 } 327 } 328 } 329 None => { 330 // This handle has not been allocated yet. 331 true 332 } 333 }; 334 335 if realloc { 336 self.allocate(handle, descriptor, filter, user_data); 337 338 // If we reallocated, we need to upload the whole item again. 339 dirty_rect = None; 340 } 341 342 let entry = self.entries 343 .get_opt_mut(handle.entry.as_ref().unwrap()) 344 .expect("BUG: handle must be valid now"); 345 346 // Invalidate the contents of the resource rect in the GPU cache. 347 // This ensures that the update_gpu_cache below will add 348 // the new information to the GPU cache. 349 gpu_cache.invalidate(&entry.uv_rect_handle); 350 351 // Upload the resource rect and texture array layer. 352 entry.update_gpu_cache(gpu_cache); 353 354 // Create an update command, which the render thread processes 355 // to upload the new image data into the correct location 356 // in GPU memory. 357 if let Some(data) = data { 358 let (layer_index, origin) = match entry.kind { 359 EntryKind::Standalone { .. } => (0, DeviceUintPoint::zero()), 360 EntryKind::Cache { 361 layer_index, 362 origin, 363 .. 364 } => (layer_index, origin), 365 }; 366 367 let op = TextureUpdate::new_update( 368 data, 369 &descriptor, 370 origin, 371 entry.size, 372 entry.texture_id, 373 layer_index as i32, 374 dirty_rect, 375 ); 376 self.pending_updates.push(op); 377 } 378 } 379 380 // Get a specific region by index from a shared texture array. get_region_mut(&mut self, format: ImageFormat, filter: TextureFilter, region_index: u16 ) -> &mut TextureRegion381 fn get_region_mut(&mut self, 382 format: ImageFormat, 383 filter: TextureFilter, 384 region_index: u16 385 ) -> &mut TextureRegion { 386 let texture_array = match (format, filter) { 387 (ImageFormat::R8, TextureFilter::Linear) => &mut self.array_a8_linear, 388 (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear, 389 (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest, 390 (ImageFormat::RGBAF32, _) | 391 (ImageFormat::RG8, _) | 392 (ImageFormat::R8, TextureFilter::Nearest) | 393 (ImageFormat::R8, TextureFilter::Trilinear) | 394 (ImageFormat::BGRA8, TextureFilter::Trilinear) => unreachable!(), 395 }; 396 397 &mut texture_array.regions[region_index as usize] 398 } 399 400 // Check if a given texture handle has a valid allocation 401 // in the texture cache. is_allocated(&self, handle: &TextureCacheHandle) -> bool402 pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool { 403 handle.entry.as_ref().map_or(false, |handle| { 404 self.entries.get_opt(handle).is_some() 405 }) 406 } 407 408 // Retrieve the details of an item in the cache. This is used 409 // during batch creation to provide the resource rect address 410 // to the shaders and texture ID to the batching logic. 411 // This function will asssert in debug modes if the caller 412 // tries to get a handle that was not requested this frame. get(&self, handle: &TextureCacheHandle) -> CacheItem413 pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem { 414 match handle.entry { 415 Some(ref handle) => { 416 let entry = self.entries 417 .get_opt(handle) 418 .expect("BUG: was dropped from cache or not updated!"); 419 debug_assert_eq!(entry.last_access, self.frame_id); 420 let (layer_index, origin) = match entry.kind { 421 EntryKind::Standalone { .. } => { 422 (0, DeviceUintPoint::zero()) 423 } 424 EntryKind::Cache { 425 layer_index, 426 origin, 427 .. 428 } => (layer_index, origin), 429 }; 430 CacheItem { 431 uv_rect_handle: entry.uv_rect_handle, 432 texture_id: SourceTexture::TextureCache(entry.texture_id), 433 uv_rect: DeviceUintRect::new(origin, entry.size), 434 texture_layer: layer_index as i32, 435 } 436 } 437 None => panic!("BUG: handle not requested earlier in frame"), 438 } 439 } 440 441 // A more detailed version of get(). This allows access to the actual 442 // device rect of the cache allocation. get_cache_location( &self, handle: &TextureCacheHandle, ) -> (SourceTexture, i32, DeviceUintRect)443 pub fn get_cache_location( 444 &self, 445 handle: &TextureCacheHandle, 446 ) -> (SourceTexture, i32, DeviceUintRect) { 447 let handle = handle 448 .entry 449 .as_ref() 450 .expect("BUG: handle not requested earlier in frame"); 451 452 let entry = self.entries 453 .get_opt(handle) 454 .expect("BUG: was dropped from cache or not updated!"); 455 debug_assert_eq!(entry.last_access, self.frame_id); 456 let (layer_index, origin) = match entry.kind { 457 EntryKind::Standalone { .. } => { 458 (0, DeviceUintPoint::zero()) 459 } 460 EntryKind::Cache { 461 layer_index, 462 origin, 463 .. 464 } => (layer_index, origin), 465 }; 466 (SourceTexture::TextureCache(entry.texture_id), 467 layer_index as i32, 468 DeviceUintRect::new(origin, entry.size)) 469 } 470 471 // Expire old standalone textures. expire_old_standalone_entries(&mut self)472 fn expire_old_standalone_entries(&mut self) { 473 let mut eviction_candidates = Vec::new(); 474 let mut retained_entries = Vec::new(); 475 476 // Build a list of eviction candidates (which are 477 // anything not used this frame). 478 for handle in self.standalone_entry_handles.drain(..) { 479 let entry = self.entries.get(&handle); 480 if entry.last_access == self.frame_id { 481 retained_entries.push(handle); 482 } else { 483 eviction_candidates.push(handle); 484 } 485 } 486 487 // Sort by access time so we remove the oldest ones first. 488 eviction_candidates.sort_by_key(|handle| { 489 let entry = self.entries.get(handle); 490 entry.last_access 491 }); 492 493 // We only allow an arbitrary number of unused 494 // standalone textures to remain in GPU memory. 495 // TODO(gw): We should make this a better heuristic, 496 // for example based on total memory size. 497 if eviction_candidates.len() > 32 { 498 let entries_to_keep = eviction_candidates.split_off(32); 499 retained_entries.extend(entries_to_keep); 500 } 501 502 // Free the selected items 503 for handle in eviction_candidates { 504 let entry = self.entries.free(handle); 505 self.free(entry); 506 } 507 508 // Keep a record of the remaining handles for next frame. 509 self.standalone_entry_handles = retained_entries; 510 } 511 512 // Expire old shared items. Pass in the allocation size 513 // that is being requested, so we know when we've evicted 514 // enough items to guarantee we can fit this allocation in 515 // the cache. expire_old_shared_entries(&mut self, required_alloc: &ImageDescriptor)516 fn expire_old_shared_entries(&mut self, required_alloc: &ImageDescriptor) { 517 let mut eviction_candidates = Vec::new(); 518 let mut retained_entries = Vec::new(); 519 520 // Build a list of eviction candidates (which are 521 // anything not used this frame). 522 for handle in self.shared_entry_handles.drain(..) { 523 let entry = self.entries.get(&handle); 524 if entry.last_access == self.frame_id { 525 retained_entries.push(handle); 526 } else { 527 eviction_candidates.push(handle); 528 } 529 } 530 531 // Sort by access time so we remove the oldest ones first. 532 eviction_candidates.sort_by_key(|handle| { 533 let entry = self.entries.get(handle); 534 entry.last_access 535 }); 536 537 // Doing an eviction is quite expensive, so we don't want to 538 // do it all the time. To avoid this, try and evict a 539 // significant number of items each cycle. However, we don't 540 // want to evict everything we can, since that will result in 541 // more items being uploaded than necessary. 542 // Instead, we say we will keep evicting until both of these 543 // conditions are met: 544 // - We have evicted some arbitrary number of items (512 currently). 545 // AND 546 // - We have freed an item that will definitely allow us to 547 // fit the currently requested allocation. 548 let needed_slab_size = 549 SlabSize::new(required_alloc.width, required_alloc.height).get_size(); 550 let mut found_matching_slab = false; 551 let mut freed_complete_page = false; 552 let mut evicted_items = 0; 553 554 for handle in eviction_candidates { 555 if evicted_items > 512 && (found_matching_slab || freed_complete_page) { 556 retained_entries.push(handle); 557 } else { 558 let entry = self.entries.free(handle); 559 if let Some(region) = self.free(entry) { 560 found_matching_slab |= region.slab_size == needed_slab_size; 561 freed_complete_page |= region.is_empty(); 562 } 563 evicted_items += 1; 564 } 565 } 566 567 // Keep a record of the remaining handles for next eviction cycle. 568 self.shared_entry_handles = retained_entries; 569 } 570 571 // Free a cache entry from the standalone list or shared cache. free(&mut self, entry: CacheEntry) -> Option<&TextureRegion>572 fn free(&mut self, entry: CacheEntry) -> Option<&TextureRegion> { 573 match entry.kind { 574 EntryKind::Standalone { .. } => { 575 // This is a standalone texture allocation. Just push it back onto the free 576 // list. 577 self.pending_updates.push(TextureUpdate { 578 id: entry.texture_id, 579 op: TextureUpdateOp::Free, 580 }); 581 self.cache_textures.free(entry.texture_id, entry.format); 582 None 583 } 584 EntryKind::Cache { 585 origin, 586 region_index, 587 .. 588 } => { 589 // Free the block in the given region. 590 let region = self.get_region_mut( 591 entry.format, 592 entry.filter, 593 region_index 594 ); 595 region.free(origin); 596 Some(region) 597 } 598 } 599 } 600 601 // Attempt to allocate a block from the shared cache. allocate_from_shared_cache( &mut self, descriptor: &ImageDescriptor, filter: TextureFilter, user_data: [f32; 3], ) -> Option<CacheEntry>602 fn allocate_from_shared_cache( 603 &mut self, 604 descriptor: &ImageDescriptor, 605 filter: TextureFilter, 606 user_data: [f32; 3], 607 ) -> Option<CacheEntry> { 608 // Work out which cache it goes in, based on format. 609 let texture_array = match (descriptor.format, filter) { 610 (ImageFormat::R8, TextureFilter::Linear) => &mut self.array_a8_linear, 611 (ImageFormat::BGRA8, TextureFilter::Linear) => &mut self.array_rgba8_linear, 612 (ImageFormat::BGRA8, TextureFilter::Nearest) => &mut self.array_rgba8_nearest, 613 (ImageFormat::RGBAF32, _) | 614 (ImageFormat::R8, TextureFilter::Nearest) | 615 (ImageFormat::R8, TextureFilter::Trilinear) | 616 (ImageFormat::BGRA8, TextureFilter::Trilinear) | 617 (ImageFormat::RG8, _) => unreachable!(), 618 }; 619 620 // Lazy initialize this texture array if required. 621 if texture_array.texture_id.is_none() { 622 let texture_id = self.cache_textures.allocate(descriptor.format); 623 624 let update_op = TextureUpdate { 625 id: texture_id, 626 op: TextureUpdateOp::Create { 627 width: TEXTURE_LAYER_DIMENSIONS, 628 height: TEXTURE_LAYER_DIMENSIONS, 629 format: descriptor.format, 630 filter: texture_array.filter, 631 // TODO(gw): Creating a render target here is only used 632 // for the texture cache debugger display. In 633 // the future, we should change the debug 634 // display to use a shader that blits the 635 // texture, and then we can remove this 636 // memory allocation (same for the other 637 // standalone texture below). 638 render_target: Some(RenderTargetInfo { has_depth: false }), 639 layer_count: texture_array.layer_count as i32, 640 }, 641 }; 642 self.pending_updates.push(update_op); 643 644 texture_array.texture_id = Some(texture_id); 645 } 646 647 // Do the allocation. This can fail and return None 648 // if there are no free slots or regions available. 649 texture_array.alloc( 650 descriptor.width, 651 descriptor.height, 652 user_data, 653 self.frame_id, 654 ) 655 } 656 657 // Returns true if the given image descriptor *may* be 658 // placed in the shared texture cache. is_allowed_in_shared_cache( &self, filter: TextureFilter, descriptor: &ImageDescriptor, ) -> bool659 pub fn is_allowed_in_shared_cache( 660 &self, 661 filter: TextureFilter, 662 descriptor: &ImageDescriptor, 663 ) -> bool { 664 let mut allowed_in_shared_cache = true; 665 666 // TODO(gw): For now, anything that requests nearest filtering and isn't BGRA8 667 // just fails to allocate in a texture page, and gets a standalone 668 // texture. This is probably rare enough that it can be fixed up later. 669 if filter == TextureFilter::Nearest && 670 descriptor.format != ImageFormat::BGRA8 { 671 allowed_in_shared_cache = false; 672 } 673 674 // Anything larger than 512 goes in a standalone texture. 675 // TODO(gw): If we find pages that suffer from batch breaks in this 676 // case, add support for storing these in a standalone 677 // texture array. 678 if descriptor.width > 512 || descriptor.height > 512 { 679 allowed_in_shared_cache = false; 680 } 681 682 allowed_in_shared_cache 683 } 684 685 // Allocate storage for a given image. This attempts to allocate 686 // from the shared cache, but falls back to standalone texture 687 // if the image is too large, or the cache is full. allocate( &mut self, handle: &mut TextureCacheHandle, descriptor: ImageDescriptor, filter: TextureFilter, user_data: [f32; 3], )688 fn allocate( 689 &mut self, 690 handle: &mut TextureCacheHandle, 691 descriptor: ImageDescriptor, 692 filter: TextureFilter, 693 user_data: [f32; 3], 694 ) { 695 assert!(descriptor.width > 0 && descriptor.height > 0); 696 697 // Work out if this image qualifies to go in the shared (batching) cache. 698 let allowed_in_shared_cache = self.is_allowed_in_shared_cache( 699 filter, 700 &descriptor, 701 ); 702 let mut allocated_in_shared_cache = true; 703 let mut new_cache_entry = None; 704 let size = DeviceUintSize::new(descriptor.width, descriptor.height); 705 let frame_id = self.frame_id; 706 707 // If it's allowed in the cache, see if there is a spot for it. 708 if allowed_in_shared_cache { 709 new_cache_entry = self.allocate_from_shared_cache( 710 &descriptor, 711 filter, 712 user_data 713 ); 714 715 // If we failed to allocate in the shared cache, run an 716 // eviction cycle, and then try to allocate again. 717 if new_cache_entry.is_none() { 718 self.expire_old_shared_entries(&descriptor); 719 720 new_cache_entry = self.allocate_from_shared_cache( 721 &descriptor, 722 filter, 723 user_data 724 ); 725 } 726 } 727 728 // If not allowed in the cache, or if the shared cache is full, then it 729 // will just have to be in a unique texture. This hurts batching but should 730 // only occur on a small number of images (or pathological test cases!). 731 if new_cache_entry.is_none() { 732 let texture_id = self.cache_textures.allocate(descriptor.format); 733 734 // Create an update operation to allocate device storage 735 // of the right size / format. 736 let update_op = TextureUpdate { 737 id: texture_id, 738 op: TextureUpdateOp::Create { 739 width: descriptor.width, 740 height: descriptor.height, 741 format: descriptor.format, 742 filter, 743 render_target: Some(RenderTargetInfo { has_depth: false }), 744 layer_count: 1, 745 }, 746 }; 747 self.pending_updates.push(update_op); 748 749 new_cache_entry = Some(CacheEntry::new_standalone( 750 texture_id, 751 size, 752 descriptor.format, 753 filter, 754 user_data, 755 frame_id, 756 )); 757 758 allocated_in_shared_cache = false; 759 } 760 761 let new_cache_entry = new_cache_entry.expect("BUG: must have allocated by now"); 762 763 // We need to update the texture cache handle now, so that it 764 // points to the correct location. 765 let new_entry_handle = match handle.entry { 766 Some(ref existing_entry) => { 767 // If the handle already exists, there's two possibilities: 768 // 1) It points to a valid entry in the freelist. 769 // 2) It points to a stale entry in the freelist (i.e. has been evicted). 770 // 771 // For (1) we want to replace the cache entry with our 772 // newly updated location. We also need to ensure that 773 // the storage (region or standalone) associated with the 774 // previous entry here gets freed. 775 // 776 // For (2) we need to add the data to a new location 777 // in the freelist. 778 // 779 // This is managed with a database style upsert operation. 780 match self.entries.upsert(existing_entry, new_cache_entry) { 781 UpsertResult::Updated(old_entry) => { 782 self.free(old_entry); 783 None 784 } 785 UpsertResult::Inserted(new_handle) => Some(new_handle), 786 } 787 } 788 None => { 789 // This handle has never been allocated, so just 790 // insert a new cache entry. 791 Some(self.entries.insert(new_cache_entry)) 792 } 793 }; 794 795 // If the cache entry is new, update it in the cache handle. 796 if let Some(new_entry_handle) = new_entry_handle { 797 handle.entry = Some(new_entry_handle.weak()); 798 // Store the strong handle in the list that we scan for 799 // cache evictions. 800 if allocated_in_shared_cache { 801 self.shared_entry_handles.push(new_entry_handle); 802 } else { 803 self.standalone_entry_handles.push(new_entry_handle); 804 } 805 } 806 } 807 } 808 809 // A list of the block sizes that a region can be initialized with. 810 #[derive(Copy, Clone, PartialEq)] 811 enum SlabSize { 812 Size16x16, 813 Size32x32, 814 Size64x64, 815 Size128x128, 816 Size256x256, 817 Size512x512, 818 } 819 820 impl SlabSize { new(width: u32, height: u32) -> SlabSize821 fn new(width: u32, height: u32) -> SlabSize { 822 // TODO(gw): Consider supporting non-square 823 // allocator sizes here. 824 let max_dim = cmp::max(width, height); 825 826 match max_dim { 827 0 => unreachable!(), 828 1...16 => SlabSize::Size16x16, 829 17...32 => SlabSize::Size32x32, 830 33...64 => SlabSize::Size64x64, 831 65...128 => SlabSize::Size128x128, 832 129...256 => SlabSize::Size256x256, 833 257...512 => SlabSize::Size512x512, 834 _ => panic!("Invalid dimensions for cache!"), 835 } 836 } 837 get_size(&self) -> u32838 fn get_size(&self) -> u32 { 839 match *self { 840 SlabSize::Size16x16 => 16, 841 SlabSize::Size32x32 => 32, 842 SlabSize::Size64x64 => 64, 843 SlabSize::Size128x128 => 128, 844 SlabSize::Size256x256 => 256, 845 SlabSize::Size512x512 => 512, 846 } 847 } 848 } 849 850 // The x/y location within a texture region of an allocation. 851 #[cfg_attr(feature = "capture", derive(Serialize))] 852 #[cfg_attr(feature = "replay", derive(Deserialize))] 853 struct TextureLocation(u8, u8); 854 855 impl TextureLocation { new(x: u32, y: u32) -> Self856 fn new(x: u32, y: u32) -> Self { 857 debug_assert!(x < 0x100 && y < 0x100); 858 TextureLocation(x as u8, y as u8) 859 } 860 } 861 862 // A region is a sub-rect of a texture array layer. 863 // All allocations within a region are of the same size. 864 #[cfg_attr(feature = "capture", derive(Serialize))] 865 #[cfg_attr(feature = "replay", derive(Deserialize))] 866 struct TextureRegion { 867 layer_index: i32, 868 region_size: u32, 869 slab_size: u32, 870 free_slots: Vec<TextureLocation>, 871 slots_per_axis: u32, 872 total_slot_count: usize, 873 origin: DeviceUintPoint, 874 } 875 876 impl TextureRegion { new(region_size: u32, layer_index: i32, origin: DeviceUintPoint) -> Self877 fn new(region_size: u32, layer_index: i32, origin: DeviceUintPoint) -> Self { 878 TextureRegion { 879 layer_index, 880 region_size, 881 slab_size: 0, 882 free_slots: Vec::new(), 883 slots_per_axis: 0, 884 total_slot_count: 0, 885 origin, 886 } 887 } 888 889 // Initialize a region to be an allocator for a specific slab size. init(&mut self, slab_size: SlabSize)890 fn init(&mut self, slab_size: SlabSize) { 891 debug_assert!(self.slab_size == 0); 892 debug_assert!(self.free_slots.is_empty()); 893 894 self.slab_size = slab_size.get_size(); 895 self.slots_per_axis = self.region_size / self.slab_size; 896 897 // Add each block to a freelist. 898 for y in 0 .. self.slots_per_axis { 899 for x in 0 .. self.slots_per_axis { 900 self.free_slots.push(TextureLocation::new(x, y)); 901 } 902 } 903 904 self.total_slot_count = self.free_slots.len(); 905 } 906 907 // Deinit a region, allowing it to become a region with 908 // a different allocator size. deinit(&mut self)909 fn deinit(&mut self) { 910 self.slab_size = 0; 911 self.free_slots.clear(); 912 self.slots_per_axis = 0; 913 self.total_slot_count = 0; 914 } 915 is_empty(&self) -> bool916 fn is_empty(&self) -> bool { 917 self.slab_size == 0 918 } 919 920 // Attempt to allocate a fixed size block from this region. alloc(&mut self) -> Option<DeviceUintPoint>921 fn alloc(&mut self) -> Option<DeviceUintPoint> { 922 self.free_slots.pop().map(|location| { 923 DeviceUintPoint::new( 924 self.origin.x + self.slab_size * location.0 as u32, 925 self.origin.y + self.slab_size * location.1 as u32, 926 ) 927 }) 928 } 929 930 // Free a block in this region. free(&mut self, point: DeviceUintPoint)931 fn free(&mut self, point: DeviceUintPoint) { 932 let x = (point.x - self.origin.x) / self.slab_size; 933 let y = (point.y - self.origin.y) / self.slab_size; 934 self.free_slots.push(TextureLocation::new(x, y)); 935 936 // If this region is completely unused, deinit it 937 // so that it can become a different slab size 938 // as required. 939 if self.free_slots.len() == self.total_slot_count { 940 self.deinit(); 941 } 942 } 943 } 944 945 // A texture array contains a number of texture layers, where 946 // each layer contains one or more regions that can act 947 // as slab allocators. 948 #[cfg_attr(feature = "capture", derive(Serialize))] 949 #[cfg_attr(feature = "replay", derive(Deserialize))] 950 struct TextureArray { 951 filter: TextureFilter, 952 layer_count: usize, 953 format: ImageFormat, 954 is_allocated: bool, 955 regions: Vec<TextureRegion>, 956 texture_id: Option<CacheTextureId>, 957 } 958 959 impl TextureArray { new( format: ImageFormat, filter: TextureFilter, layer_count: usize ) -> Self960 fn new( 961 format: ImageFormat, 962 filter: TextureFilter, 963 layer_count: usize 964 ) -> Self { 965 TextureArray { 966 format, 967 filter, 968 layer_count, 969 is_allocated: false, 970 regions: Vec::new(), 971 texture_id: None, 972 } 973 } 974 update_profile(&self, counter: &mut ResourceProfileCounter)975 fn update_profile(&self, counter: &mut ResourceProfileCounter) { 976 if self.is_allocated { 977 let size = self.layer_count as u32 * TEXTURE_LAYER_DIMENSIONS * 978 TEXTURE_LAYER_DIMENSIONS * self.format.bytes_per_pixel(); 979 counter.set(self.layer_count as usize, size as usize); 980 } else { 981 counter.set(0, 0); 982 } 983 } 984 985 // Allocate space in this texture array. alloc( &mut self, width: u32, height: u32, user_data: [f32; 3], frame_id: FrameId, ) -> Option<CacheEntry>986 fn alloc( 987 &mut self, 988 width: u32, 989 height: u32, 990 user_data: [f32; 3], 991 frame_id: FrameId, 992 ) -> Option<CacheEntry> { 993 // Lazily allocate the regions if not already created. 994 // This means that very rarely used image formats can be 995 // added but won't allocate a cache if never used. 996 if !self.is_allocated { 997 debug_assert!(TEXTURE_LAYER_DIMENSIONS % TEXTURE_REGION_DIMENSIONS == 0); 998 let regions_per_axis = TEXTURE_LAYER_DIMENSIONS / TEXTURE_REGION_DIMENSIONS; 999 for layer_index in 0 .. self.layer_count { 1000 for y in 0 .. regions_per_axis { 1001 for x in 0 .. regions_per_axis { 1002 let origin = DeviceUintPoint::new( 1003 x * TEXTURE_REGION_DIMENSIONS, 1004 y * TEXTURE_REGION_DIMENSIONS, 1005 ); 1006 let region = TextureRegion::new( 1007 TEXTURE_REGION_DIMENSIONS, 1008 layer_index as i32, 1009 origin 1010 ); 1011 self.regions.push(region); 1012 } 1013 } 1014 } 1015 self.is_allocated = true; 1016 } 1017 1018 // Quantize the size of the allocation to select a region to 1019 // allocate from. 1020 let slab_size = SlabSize::new(width, height); 1021 let slab_size_dim = slab_size.get_size(); 1022 1023 // TODO(gw): For simplicity, the initial implementation just 1024 // has a single vec<> of regions. We could easily 1025 // make this more efficient by storing a list of 1026 // regions for each slab size specifically... 1027 1028 // Keep track of the location of an empty region, 1029 // in case we need to select a new empty region 1030 // after the loop. 1031 let mut empty_region_index = None; 1032 let mut entry_kind = None; 1033 1034 // Run through the existing regions of this size, and see if 1035 // we can find a free block in any of them. 1036 for (i, region) in self.regions.iter_mut().enumerate() { 1037 if region.slab_size == 0 { 1038 empty_region_index = Some(i); 1039 } else if region.slab_size == slab_size_dim { 1040 if let Some(location) = region.alloc() { 1041 entry_kind = Some(EntryKind::Cache { 1042 layer_index: region.layer_index as u16, 1043 region_index: i as u16, 1044 origin: location, 1045 }); 1046 break; 1047 } 1048 } 1049 } 1050 1051 // Find a region of the right size and try to allocate from it. 1052 if entry_kind.is_none() { 1053 if let Some(empty_region_index) = empty_region_index { 1054 let region = &mut self.regions[empty_region_index]; 1055 region.init(slab_size); 1056 entry_kind = region.alloc().map(|location| { 1057 EntryKind::Cache { 1058 layer_index: region.layer_index as u16, 1059 region_index: empty_region_index as u16, 1060 origin: location, 1061 } 1062 }); 1063 } 1064 } 1065 1066 entry_kind.map(|kind| { 1067 CacheEntry { 1068 size: DeviceUintSize::new(width, height), 1069 user_data, 1070 last_access: frame_id, 1071 kind, 1072 uv_rect_handle: GpuCacheHandle::new(), 1073 format: self.format, 1074 filter: self.filter, 1075 texture_id: self.texture_id.unwrap(), 1076 color: ColorF::new(1.0, 1.0, 1.0, 1.0).premultiplied(), 1077 } 1078 }) 1079 } 1080 } 1081 1082 impl TextureUpdate { 1083 // Constructs a TextureUpdate operation to be passed to the 1084 // rendering thread in order to do an upload to the right 1085 // location in the texture cache. new_update( data: ImageData, descriptor: &ImageDescriptor, origin: DeviceUintPoint, size: DeviceUintSize, texture_id: CacheTextureId, layer_index: i32, dirty_rect: Option<DeviceUintRect>, ) -> TextureUpdate1086 fn new_update( 1087 data: ImageData, 1088 descriptor: &ImageDescriptor, 1089 origin: DeviceUintPoint, 1090 size: DeviceUintSize, 1091 texture_id: CacheTextureId, 1092 layer_index: i32, 1093 dirty_rect: Option<DeviceUintRect>, 1094 ) -> TextureUpdate { 1095 let data_src = match data { 1096 ImageData::Blob(..) => { 1097 panic!("The vector image should have been rasterized."); 1098 } 1099 ImageData::External(ext_image) => match ext_image.image_type { 1100 ExternalImageType::TextureHandle(_) => { 1101 panic!("External texture handle should not go through texture_cache."); 1102 } 1103 ExternalImageType::Buffer => TextureUpdateSource::External { 1104 id: ext_image.id, 1105 channel_index: ext_image.channel_index, 1106 }, 1107 }, 1108 ImageData::Raw(bytes) => { 1109 let finish = descriptor.offset + 1110 descriptor.width * descriptor.format.bytes_per_pixel() + 1111 (descriptor.height - 1) * descriptor.compute_stride(); 1112 assert!(bytes.len() >= finish as usize); 1113 1114 TextureUpdateSource::Bytes { data: bytes } 1115 } 1116 }; 1117 1118 let update_op = match dirty_rect { 1119 Some(dirty) => { 1120 let stride = descriptor.compute_stride(); 1121 let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x * descriptor.format.bytes_per_pixel(); 1122 let origin = 1123 DeviceUintPoint::new(origin.x + dirty.origin.x, origin.y + dirty.origin.y); 1124 TextureUpdateOp::Update { 1125 rect: DeviceUintRect::new(origin, dirty.size), 1126 source: data_src, 1127 stride: Some(stride), 1128 offset, 1129 layer_index, 1130 } 1131 } 1132 None => TextureUpdateOp::Update { 1133 rect: DeviceUintRect::new(origin, size), 1134 source: data_src, 1135 stride: descriptor.stride, 1136 offset: descriptor.offset, 1137 layer_index, 1138 }, 1139 }; 1140 1141 TextureUpdate { 1142 id: texture_id, 1143 op: update_op, 1144 } 1145 } 1146 } 1147