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(¶ms, 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, ¶ms.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