1use api::{DirtyRect, ExternalImageType, ImageFormat, ImageBufferKind};
6use api::{DebugFlags, ImageDescriptor, TextureCacheCategory};
7use api::units::*;
8#[cfg(test)]
9use api::{DocumentId, IdNamespace};
10use crate::device::{TextureFilter, TextureFormatPair};
11use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
12use crate::gpu_types::{ImageSource, UvRectKind};
13use crate::internal_types::{
14 CacheTextureId, Swizzle, SwizzleSettings, FrameStamp, FrameId,
15 TextureUpdateList, TextureUpdateSource, TextureSource,
16 TextureCacheAllocInfo, TextureCacheUpdate,
17};
18use crate::lru_cache::LRUCache;
19use crate::profiler::{self, TransactionProfile};
20use crate::renderer::{GpuBufferBuilderF, GpuBufferHandle};
21use crate::resource_cache::{CacheItem, CachedImageData};
22use crate::texture_pack::{
23 AllocatorList, AllocId, AtlasAllocatorList, ShelfAllocator, ShelfAllocatorOptions,
24};
25use std::cell::Cell;
26use std::mem;
27use std::rc::Rc;
28use euclid::size2;
29use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
30
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
36#[cfg_attr(feature = "capture", derive(Serialize))]
37#[cfg_attr(feature = "replay", derive(Deserialize))]
38pub enum TargetShader {
39 Default,
40 Text,
41}
42
43pub const TEXTURE_REGION_DIMENSIONS: i32 = 512;
45
46#[derive(Clone, Debug)]
49#[cfg_attr(feature = "capture", derive(Serialize))]
50#[cfg_attr(feature = "replay", derive(Deserialize))]
51pub enum EntryDetails {
52 Standalone {
53 size_in_bytes: usize,
55 },
56 Cache {
57 origin: DeviceIntPoint,
59 alloc_id: AllocId,
61 allocated_size_in_bytes: usize,
63 },
64}
65
66impl EntryDetails {
67 fn describe(&self) -> DeviceIntPoint {
68 match *self {
69 EntryDetails::Standalone { .. } => DeviceIntPoint::zero(),
70 EntryDetails::Cache { origin, .. } => origin,
71 }
72 }
73}
74
75#[derive(Debug, PartialEq)]
76#[cfg_attr(feature = "capture", derive(Serialize))]
77#[cfg_attr(feature = "replay", derive(Deserialize))]
78pub enum AutoCacheEntryMarker {}
79
80#[derive(Debug, PartialEq)]
81#[cfg_attr(feature = "capture", derive(Serialize))]
82#[cfg_attr(feature = "replay", derive(Deserialize))]
83pub enum ManualCacheEntryMarker {}
84
85#[derive(Debug)]
89#[cfg_attr(feature = "capture", derive(Serialize))]
90#[cfg_attr(feature = "replay", derive(Deserialize))]
91pub struct CacheEntry {
92 pub size: DeviceIntSize,
96 pub details: EntryDetails,
98 pub user_data: [f32; 4],
100 pub last_access: FrameStamp,
102 pub uv_rect_handle: GpuBufferHandle,
110 pub input_format: ImageFormat,
112 pub filter: TextureFilter,
113 pub swizzle: Swizzle,
114 pub texture_id: CacheTextureId,
116 pub eviction_notice: Option<EvictionNotice>,
118 pub uv_rect_kind: UvRectKind,
120
121 pub shader: TargetShader,
122}
123
124malloc_size_of::malloc_size_of_is_0!(
125 CacheEntry,
126 AutoCacheEntryMarker, ManualCacheEntryMarker
127);
128
129impl CacheEntry {
130 fn new_standalone(
132 texture_id: CacheTextureId,
133 last_access: FrameStamp,
134 params: &CacheAllocParams,
135 swizzle: Swizzle,
136 size_in_bytes: usize,
137 ) -> Self {
138 CacheEntry {
139 size: params.descriptor.size,
140 user_data: params.user_data,
141 last_access,
142 details: EntryDetails::Standalone {
143 size_in_bytes,
144 },
145 texture_id,
146 input_format: params.descriptor.format,
147 filter: params.filter,
148 swizzle,
149 uv_rect_handle: GpuBufferHandle::INVALID,
150 eviction_notice: None,
151 uv_rect_kind: params.uv_rect_kind,
152 shader: TargetShader::Default,
153 }
154 }
155
156 fn write_gpu_blocks(&mut self, gpu_buffer: &mut GpuBufferBuilderF) {
161 let origin = self.details.describe();
162 let image_source = ImageSource {
163 p0: origin.to_f32(),
164 p1: (origin + self.size).to_f32(),
165 user_data: self.user_data,
166 uv_rect_kind: self.uv_rect_kind,
167 };
168 self.uv_rect_handle = image_source.write_gpu_blocks(gpu_buffer);
169 }
170
171 fn evict(&self) {
172 if let Some(eviction_notice) = self.eviction_notice.as_ref() {
173 eviction_notice.notify();
174 }
175 }
176
177 fn alternative_input_format(&self) -> ImageFormat {
178 match self.input_format {
179 ImageFormat::RGBA8 => ImageFormat::BGRA8,
180 ImageFormat::BGRA8 => ImageFormat::RGBA8,
181 other => other,
182 }
183 }
184}
185
186
187#[derive(MallocSizeOf,Clone,PartialEq,Debug)]
195#[cfg_attr(feature = "capture", derive(Serialize))]
196#[cfg_attr(feature = "replay", derive(Deserialize))]
197pub enum TextureCacheHandle {
198 Empty,
200
201 Auto(WeakFreeListHandle<AutoCacheEntryMarker>),
203
204 Manual(WeakFreeListHandle<ManualCacheEntryMarker>)
206}
207
208impl TextureCacheHandle {
209 pub fn invalid() -> Self {
210 TextureCacheHandle::Empty
211 }
212}
213
214#[derive(Copy, Clone, Debug, PartialEq, Eq)]
216#[cfg_attr(feature = "capture", derive(Serialize))]
217#[cfg_attr(feature = "replay", derive(Deserialize))]
218pub enum Eviction {
219 Auto,
222 Manual,
225}
226
227#[derive(Clone, Debug, Default)]
234#[cfg_attr(feature = "capture", derive(Serialize))]
235#[cfg_attr(feature = "replay", derive(Deserialize))]
236pub struct EvictionNotice {
237 evicted: Rc<Cell<bool>>,
238}
239
240impl EvictionNotice {
241 fn notify(&self) {
242 self.evicted.set(true);
243 }
244
245 pub fn check(&self) -> bool {
246 if self.evicted.get() {
247 self.evicted.set(false);
248 true
249 } else {
250 false
251 }
252 }
253}
254
255#[derive(Copy, Clone, Debug, PartialEq, Eq)]
262#[repr(u8)]
263#[cfg_attr(feature = "capture", derive(Serialize))]
264#[cfg_attr(feature = "replay", derive(Deserialize))]
265enum BudgetType {
266 SharedColor8Linear,
267 SharedColor8Nearest,
268 SharedColor8Glyphs,
269 SharedAlpha8,
270 SharedAlpha8Glyphs,
271 SharedAlpha16,
272 Standalone,
273}
274
275impl BudgetType {
276 pub const COUNT: usize = 7;
277
278 pub const VALUES: [BudgetType; BudgetType::COUNT] = [
279 BudgetType::SharedColor8Linear,
280 BudgetType::SharedColor8Nearest,
281 BudgetType::SharedColor8Glyphs,
282 BudgetType::SharedAlpha8,
283 BudgetType::SharedAlpha8Glyphs,
284 BudgetType::SharedAlpha16,
285 BudgetType::Standalone,
286 ];
287
288 pub const PRESSURE_COUNTERS: [usize; BudgetType::COUNT] = [
289 profiler::ATLAS_COLOR8_LINEAR_PRESSURE,
290 profiler::ATLAS_COLOR8_NEAREST_PRESSURE,
291 profiler::ATLAS_COLOR8_GLYPHS_PRESSURE,
292 profiler::ATLAS_ALPHA8_PRESSURE,
293 profiler::ATLAS_ALPHA8_GLYPHS_PRESSURE,
294 profiler::ATLAS_ALPHA16_PRESSURE,
295 profiler::ATLAS_STANDALONE_PRESSURE,
296 ];
297
298 pub fn iter() -> impl Iterator<Item = BudgetType> {
299 BudgetType::VALUES.iter().cloned()
300 }
301}
302
303#[cfg_attr(feature = "capture", derive(Serialize))]
306#[cfg_attr(feature = "replay", derive(Deserialize))]
307struct SharedTextures {
308 color8_nearest: AllocatorList<ShelfAllocator, TextureParameters>,
309 alpha8_linear: AllocatorList<ShelfAllocator, TextureParameters>,
310 alpha8_glyphs: AllocatorList<ShelfAllocator, TextureParameters>,
311 alpha16_linear: AllocatorList<ShelfAllocator, TextureParameters>,
312 color8_linear: AllocatorList<ShelfAllocator, TextureParameters>,
313 color8_glyphs: AllocatorList<ShelfAllocator, TextureParameters>,
314 bytes_per_texture_of_type: [i32 ; BudgetType::COUNT],
315 next_compaction_idx: usize,
316}
317
318impl SharedTextures {
319 fn new(color_formats: TextureFormatPair<ImageFormat>, config: &TextureCacheConfig) -> Self {
321 let mut bytes_per_texture_of_type = [0 ; BudgetType::COUNT];
322
323 let alpha8_linear = AllocatorList::new(
332 config.alpha8_texture_size,
333 ShelfAllocatorOptions {
334 num_columns: 1,
335 alignment: size2(8, 8),
336 .. ShelfAllocatorOptions::default()
337 },
338 TextureParameters {
339 formats: TextureFormatPair::from(ImageFormat::R8),
340 filter: TextureFilter::Linear,
341 },
342 );
343 bytes_per_texture_of_type[BudgetType::SharedAlpha8 as usize] =
344 config.alpha8_texture_size * config.alpha8_texture_size;
345
346 let alpha8_glyphs = AllocatorList::new(
348 config.alpha8_glyph_texture_size,
349 ShelfAllocatorOptions {
350 num_columns: if config.alpha8_glyph_texture_size >= 1024 { 2 } else { 1 },
351 alignment: size2(4, 8),
352 .. ShelfAllocatorOptions::default()
353 },
354 TextureParameters {
355 formats: TextureFormatPair::from(ImageFormat::R8),
356 filter: TextureFilter::Linear,
357 },
358 );
359 bytes_per_texture_of_type[BudgetType::SharedAlpha8Glyphs as usize] =
360 config.alpha8_glyph_texture_size * config.alpha8_glyph_texture_size;
361
362 let alpha16_linear = AllocatorList::new(
365 config.alpha16_texture_size,
366 ShelfAllocatorOptions {
367 num_columns: if config.alpha16_texture_size >= 1024 { 2 } else { 1 },
368 alignment: size2(8, 8),
369 .. ShelfAllocatorOptions::default()
370 },
371 TextureParameters {
372 formats: TextureFormatPair::from(ImageFormat::R16),
373 filter: TextureFilter::Linear,
374 },
375 );
376 bytes_per_texture_of_type[BudgetType::SharedAlpha16 as usize] =
377 ImageFormat::R16.bytes_per_pixel() *
378 config.alpha16_texture_size * config.alpha16_texture_size;
379
380 let color8_linear = AllocatorList::new(
382 config.color8_linear_texture_size,
383 ShelfAllocatorOptions {
384 num_columns: if config.color8_linear_texture_size >= 1024 { 2 } else { 1 },
385 alignment: size2(16, 16),
386 .. ShelfAllocatorOptions::default()
387 },
388 TextureParameters {
389 formats: color_formats.clone(),
390 filter: TextureFilter::Linear,
391 },
392 );
393 bytes_per_texture_of_type[BudgetType::SharedColor8Linear as usize] =
394 color_formats.internal.bytes_per_pixel() *
395 config.color8_linear_texture_size * config.color8_linear_texture_size;
396
397 let color8_glyphs = AllocatorList::new(
399 config.color8_glyph_texture_size,
400 ShelfAllocatorOptions {
401 num_columns: if config.color8_glyph_texture_size >= 1024 { 2 } else { 1 },
402 alignment: size2(4, 8),
403 .. ShelfAllocatorOptions::default()
404 },
405 TextureParameters {
406 formats: color_formats.clone(),
407 filter: TextureFilter::Linear,
408 },
409 );
410 bytes_per_texture_of_type[BudgetType::SharedColor8Glyphs as usize] =
411 color_formats.internal.bytes_per_pixel() *
412 config.color8_glyph_texture_size * config.color8_glyph_texture_size;
413
414 let color8_nearest = AllocatorList::new(
418 config.color8_nearest_texture_size,
419 ShelfAllocatorOptions::default(),
420 TextureParameters {
421 formats: color_formats.clone(),
422 filter: TextureFilter::Nearest,
423 }
424 );
425 bytes_per_texture_of_type[BudgetType::SharedColor8Nearest as usize] =
426 color_formats.internal.bytes_per_pixel() *
427 config.color8_nearest_texture_size * config.color8_nearest_texture_size;
428
429 Self {
430 alpha8_linear,
431 alpha8_glyphs,
432 alpha16_linear,
433 color8_linear,
434 color8_glyphs,
435 color8_nearest,
436 bytes_per_texture_of_type,
437 next_compaction_idx: 0,
438 }
439 }
440
441 fn clear(&mut self, updates: &mut TextureUpdateList) {
443 let texture_dealloc_cb = &mut |texture_id| {
444 updates.push_free(texture_id);
445 };
446
447 self.alpha8_linear.clear(texture_dealloc_cb);
448 self.alpha8_glyphs.clear(texture_dealloc_cb);
449 self.alpha16_linear.clear(texture_dealloc_cb);
450 self.color8_linear.clear(texture_dealloc_cb);
451 self.color8_nearest.clear(texture_dealloc_cb);
452 self.color8_glyphs.clear(texture_dealloc_cb);
453 }
454
455 fn select(
457 &mut self, external_format: ImageFormat, filter: TextureFilter, shader: TargetShader,
458 ) -> (&mut dyn AtlasAllocatorList<TextureParameters>, BudgetType) {
459 match external_format {
460 ImageFormat::R8 => {
461 assert_eq!(filter, TextureFilter::Linear);
462 match shader {
463 TargetShader::Text => {
464 (&mut self.alpha8_glyphs, BudgetType::SharedAlpha8Glyphs)
465 },
466 _ => (&mut self.alpha8_linear, BudgetType::SharedAlpha8),
467 }
468 }
469 ImageFormat::R16 => {
470 assert_eq!(filter, TextureFilter::Linear);
471 (&mut self.alpha16_linear, BudgetType::SharedAlpha16)
472 }
473 ImageFormat::RGBA8 |
474 ImageFormat::BGRA8 => {
475 match (filter, shader) {
476 (TextureFilter::Linear, TargetShader::Text) => {
477 (&mut self.color8_glyphs, BudgetType::SharedColor8Glyphs)
478 },
479 (TextureFilter::Linear, _) => {
480 (&mut self.color8_linear, BudgetType::SharedColor8Linear)
481 },
482 (TextureFilter::Nearest, _) => {
483 (&mut self.color8_nearest, BudgetType::SharedColor8Nearest)
484 },
485 _ => panic!("Unexpected filter {:?}", filter),
486 }
487 }
488 _ => panic!("Unexpected format {:?}", external_format),
489 }
490 }
491
492 fn bytes_per_shared_texture(&self, budget_type: BudgetType) -> usize {
495 self.bytes_per_texture_of_type[budget_type as usize] as usize
496 }
497
498 fn has_multiple_textures(&self, budget_type: BudgetType) -> bool {
499 match budget_type {
500 BudgetType::SharedColor8Linear => self.color8_linear.allocated_textures() > 1,
501 BudgetType::SharedColor8Nearest => self.color8_nearest.allocated_textures() > 1,
502 BudgetType::SharedColor8Glyphs => self.color8_glyphs.allocated_textures() > 1,
503 BudgetType::SharedAlpha8 => self.alpha8_linear.allocated_textures() > 1,
504 BudgetType::SharedAlpha8Glyphs => self.alpha8_glyphs.allocated_textures() > 1,
505 BudgetType::SharedAlpha16 => self.alpha16_linear.allocated_textures() > 1,
506 BudgetType::Standalone => false,
507 }
508 }
509}
510
511struct CacheAllocParams {
513 descriptor: ImageDescriptor,
514 filter: TextureFilter,
515 user_data: [f32; 4],
516 uv_rect_kind: UvRectKind,
517 shader: TargetShader,
518}
519
520#[derive(Clone)]
524pub struct TextureCacheConfig {
525 pub color8_linear_texture_size: i32,
526 pub color8_nearest_texture_size: i32,
527 pub color8_glyph_texture_size: i32,
528 pub alpha8_texture_size: i32,
529 pub alpha8_glyph_texture_size: i32,
530 pub alpha16_texture_size: i32,
531}
532
533impl TextureCacheConfig {
534 pub const DEFAULT: Self = TextureCacheConfig {
535 color8_linear_texture_size: 2048,
536 color8_nearest_texture_size: 512,
537 color8_glyph_texture_size: 2048,
538 alpha8_texture_size: 1024,
539 alpha8_glyph_texture_size: 2048,
540 alpha16_texture_size: 512,
541 };
542}
543
544#[cfg_attr(feature = "capture", derive(Serialize))]
560#[cfg_attr(feature = "replay", derive(Deserialize))]
561pub struct TextureCache {
562 shared_textures: SharedTextures,
564
565 max_texture_size: i32,
567
568 tiling_threshold: i32,
571
572 swizzle: Option<SwizzleSettings>,
574
575 debug_flags: DebugFlags,
577
578 pub next_id: CacheTextureId,
580
581 #[cfg_attr(all(feature = "serde", any(feature = "capture", feature = "replay")), serde(skip))]
584 pub pending_updates: TextureUpdateList,
585
586 pub now: FrameStamp,
588
589 lru_cache: LRUCache<CacheEntry, AutoCacheEntryMarker>,
592
593 manual_entries: FreeList<CacheEntry, ManualCacheEntryMarker>,
595
596 manual_handles: Vec<FreeListHandle<ManualCacheEntryMarker>>,
598
599 bytes_allocated: [usize ; BudgetType::COUNT],
602}
603
604impl TextureCache {
605 const MAX_EVICTIONS_PER_FRAME: usize = 32;
610
611 pub fn new(
612 max_texture_size: i32,
613 tiling_threshold: i32,
614 color_formats: TextureFormatPair<ImageFormat>,
615 swizzle: Option<SwizzleSettings>,
616 config: &TextureCacheConfig,
617 ) -> Self {
618 let pending_updates = TextureUpdateList::new();
619
620 assert!(color_formats.internal != ImageFormat::BGRA8 ||
624 swizzle.map_or(true, |s| s.bgra8_sampling_swizzle == Swizzle::default())
625 );
626
627 let next_texture_id = CacheTextureId(1);
628
629 TextureCache {
630 shared_textures: SharedTextures::new(color_formats, config),
631 max_texture_size,
632 tiling_threshold,
633 swizzle,
634 debug_flags: DebugFlags::empty(),
635 next_id: next_texture_id,
636 pending_updates,
637 now: FrameStamp::INVALID,
638 lru_cache: LRUCache::new(BudgetType::COUNT),
639 manual_entries: FreeList::new(),
640 manual_handles: Vec::new(),
641 bytes_allocated: [0 ; BudgetType::COUNT],
642 }
643 }
644
645 #[cfg(test)]
649 pub fn new_for_testing(
650 max_texture_size: i32,
651 image_format: ImageFormat,
652 ) -> Self {
653 let mut cache = Self::new(
654 max_texture_size,
655 max_texture_size,
656 TextureFormatPair::from(image_format),
657 None,
658 &TextureCacheConfig::DEFAULT,
659 );
660 let mut now = FrameStamp::first(DocumentId::new(IdNamespace(1), 1));
661 now.advance();
662 cache.begin_frame(now, &mut TransactionProfile::new());
663 cache
664 }
665
666 pub fn set_debug_flags(&mut self, flags: DebugFlags) {
667 self.debug_flags = flags;
668 }
669
670 pub fn clear_all(&mut self) {
673 let manual_handles = mem::replace(
675 &mut self.manual_handles,
676 Vec::new(),
677 );
678 for handle in manual_handles {
679 let entry = self.manual_entries.free(handle);
680 self.evict_impl(entry);
681 }
682
683 for budget_type in BudgetType::iter() {
685 while let Some(entry) = self.lru_cache.pop_oldest(budget_type as u8) {
686 entry.evict();
687 self.free(&entry);
688 }
689 }
690
691 self.shared_textures.clear(&mut self.pending_updates);
693 self.pending_updates.note_clear();
694 }
695
696 pub fn begin_frame(&mut self, stamp: FrameStamp, profile: &mut TransactionProfile) {
698 debug_assert!(!self.now.is_valid());
699 profile_scope!("begin_frame");
700 self.now = stamp;
701
702 self.evict_items_from_cache_if_required(profile);
707 }
708
709 pub fn end_frame(&mut self, profile: &mut TransactionProfile) {
710 debug_assert!(self.now.is_valid());
711
712 let updates = &mut self.pending_updates; let callback = &mut|texture_id| { updates.push_free(texture_id); };
714
715 self.shared_textures.alpha8_linear.release_empty_textures(callback);
719 self.shared_textures.alpha8_glyphs.release_empty_textures(callback);
720 self.shared_textures.alpha16_linear.release_empty_textures(callback);
721 self.shared_textures.color8_linear.release_empty_textures(callback);
722 self.shared_textures.color8_nearest.release_empty_textures(callback);
723 self.shared_textures.color8_glyphs.release_empty_textures(callback);
724
725 for budget in BudgetType::iter() {
726 let threshold = self.get_eviction_threshold(budget);
727 let pressure = self.bytes_allocated[budget as usize] as f32 / threshold as f32;
728 profile.set(BudgetType::PRESSURE_COUNTERS[budget as usize], pressure);
729 }
730
731 profile.set(profiler::ATLAS_A8_PIXELS, self.shared_textures.alpha8_linear.allocated_space());
732 profile.set(profiler::ATLAS_A8_TEXTURES, self.shared_textures.alpha8_linear.allocated_textures());
733 profile.set(profiler::ATLAS_A8_GLYPHS_PIXELS, self.shared_textures.alpha8_glyphs.allocated_space());
734 profile.set(profiler::ATLAS_A8_GLYPHS_TEXTURES, self.shared_textures.alpha8_glyphs.allocated_textures());
735 profile.set(profiler::ATLAS_A16_PIXELS, self.shared_textures.alpha16_linear.allocated_space());
736 profile.set(profiler::ATLAS_A16_TEXTURES, self.shared_textures.alpha16_linear.allocated_textures());
737 profile.set(profiler::ATLAS_RGBA8_LINEAR_PIXELS, self.shared_textures.color8_linear.allocated_space());
738 profile.set(profiler::ATLAS_RGBA8_LINEAR_TEXTURES, self.shared_textures.color8_linear.allocated_textures());
739 profile.set(profiler::ATLAS_RGBA8_NEAREST_PIXELS, self.shared_textures.color8_nearest.allocated_space());
740 profile.set(profiler::ATLAS_RGBA8_NEAREST_TEXTURES, self.shared_textures.color8_nearest.allocated_textures());
741 profile.set(profiler::ATLAS_RGBA8_GLYPHS_PIXELS, self.shared_textures.color8_glyphs.allocated_space());
742 profile.set(profiler::ATLAS_RGBA8_GLYPHS_TEXTURES, self.shared_textures.color8_glyphs.allocated_textures());
743
744 let shared_bytes = [
745 BudgetType::SharedColor8Linear,
746 BudgetType::SharedColor8Nearest,
747 BudgetType::SharedColor8Glyphs,
748 BudgetType::SharedAlpha8,
749 BudgetType::SharedAlpha8Glyphs,
750 BudgetType::SharedAlpha16,
751 ].iter().map(|b| self.bytes_allocated[*b as usize]).sum();
752
753 profile.set(profiler::ATLAS_ITEMS_MEM, profiler::bytes_to_mb(shared_bytes));
754
755 self.now = FrameStamp::INVALID;
756 }
757
758 pub fn run_compaction(&mut self) {
759 let allocator_lists = [
762 &mut self.shared_textures.color8_linear,
763 &mut self.shared_textures.color8_nearest,
764 &mut self.shared_textures.color8_glyphs,
765 &mut self.shared_textures.alpha8_linear,
766 &mut self.shared_textures.alpha8_glyphs,
767 &mut self.shared_textures.alpha16_linear,
768 ];
769
770 let idx = self.shared_textures.next_compaction_idx;
772
773 let area_threshold = 512*512;
777
778 let mut changes = Vec::new();
779 allocator_lists[idx].try_compaction(area_threshold, &mut changes);
780
781 if changes.is_empty() {
782 self.shared_textures.next_compaction_idx = (self.shared_textures.next_compaction_idx + 1) % allocator_lists.len();
784 }
785
786 for change in changes {
787 let bpp = allocator_lists[idx].texture_parameters().formats.internal.bytes_per_pixel();
788
789 let old_bytes = (change.old_rect.area() * bpp) as usize;
793 let new_bytes = (change.new_rect.area() * bpp) as usize;
794 self.bytes_allocated[idx] -= old_bytes;
795 self.bytes_allocated[idx] += new_bytes;
796
797 let entry = match change.handle {
798 TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt_mut(&handle).unwrap(),
799 TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt_mut(&handle).unwrap(),
800 TextureCacheHandle::Empty => { panic!("invalid handle"); }
801 };
802 entry.texture_id = change.new_tex;
803 entry.details = EntryDetails::Cache {
804 origin: change.new_rect.min,
805 alloc_id: change.new_id,
806 allocated_size_in_bytes: new_bytes,
807 };
808
809 entry.uv_rect_handle = GpuBufferHandle::INVALID;
810
811 let src_rect = DeviceIntRect::from_origin_and_size(change.old_rect.min, entry.size);
812 let dst_rect = DeviceIntRect::from_origin_and_size(change.new_rect.min, entry.size);
813
814 self.pending_updates.push_copy(change.old_tex, &src_rect, change.new_tex, &dst_rect);
815
816 if self.debug_flags.contains(
817 DebugFlags::TEXTURE_CACHE_DBG |
818 DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
819 {
820 self.pending_updates.push_debug_clear(
821 change.old_tex,
822 src_rect.min,
823 src_rect.width(),
824 src_rect.height(),
825 );
826 }
827 }
828 }
829
830 pub fn request(&mut self, handle: &TextureCacheHandle, gpu_buffer: &mut GpuBufferBuilderF) -> bool {
839 let now = self.now;
840 let entry = match handle {
841 TextureCacheHandle::Empty => None,
842 TextureCacheHandle::Auto(handle) => {
843 self.lru_cache.touch(handle)
846 },
847 TextureCacheHandle::Manual(handle) => {
848 self.manual_entries.get_opt_mut(handle)
849 },
850 };
851 entry.map_or(true, |entry| {
852 if entry.last_access != now {
853 entry.last_access = now;
856 entry.write_gpu_blocks(gpu_buffer);
857 }
858 false
859 })
860 }
861
862 fn get_entry_opt(&self, handle: &TextureCacheHandle) -> Option<&CacheEntry> {
863 match handle {
864 TextureCacheHandle::Empty => None,
865 TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt(handle),
866 TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt(handle),
867 }
868 }
869
870 fn get_entry_opt_mut(&mut self, handle: &TextureCacheHandle) -> Option<&mut CacheEntry> {
871 match handle {
872 TextureCacheHandle::Empty => None,
873 TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt_mut(handle),
874 TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt_mut(handle),
875 }
876 }
877
878 pub fn needs_upload(&self, handle: &TextureCacheHandle) -> bool {
882 !self.is_allocated(handle)
883 }
884
885 pub fn max_texture_size(&self) -> i32 {
886 self.max_texture_size
887 }
888
889 pub fn tiling_threshold(&self) -> i32 {
890 self.tiling_threshold
891 }
892
893 #[cfg(feature = "replay")]
894 pub fn color_formats(&self) -> TextureFormatPair<ImageFormat> {
895 self.shared_textures.color8_linear.texture_parameters().formats.clone()
896 }
897
898 #[cfg(feature = "replay")]
899 pub fn swizzle_settings(&self) -> Option<SwizzleSettings> {
900 self.swizzle
901 }
902
903 pub fn pending_updates(&mut self) -> TextureUpdateList {
904 mem::replace(&mut self.pending_updates, TextureUpdateList::new())
905 }
906
907 pub fn update(
909 &mut self,
910 handle: &mut TextureCacheHandle,
911 descriptor: ImageDescriptor,
912 filter: TextureFilter,
913 data: Option<CachedImageData>,
914 user_data: [f32; 4],
915 mut dirty_rect: ImageDirtyRect,
916 gpu_buffer: &mut GpuBufferBuilderF,
917 eviction_notice: Option<&EvictionNotice>,
918 uv_rect_kind: UvRectKind,
919 eviction: Eviction,
920 shader: TargetShader,
921 force_standalone_texture: bool,
922 ) {
923 debug_assert!(self.now.is_valid());
924 let realloc = match self.get_entry_opt(handle) {
931 Some(entry) => {
932 entry.size != descriptor.size || (entry.input_format != descriptor.format &&
933 entry.alternative_input_format() != descriptor.format)
934 }
935 None => {
936 true
938 }
939 };
940
941 if realloc {
942 let params = CacheAllocParams { descriptor, filter, user_data, uv_rect_kind, shader };
943 self.allocate(¶ms, handle, eviction, force_standalone_texture);
944
945 dirty_rect = DirtyRect::All;
947 }
948
949 let now = self.now;
950 let entry = self.get_entry_opt_mut(handle)
951 .expect("BUG: There must be an entry at this handle now");
952
953 entry.eviction_notice = eviction_notice.cloned();
955 entry.uv_rect_kind = uv_rect_kind;
956
957 if entry.last_access != now || realloc {
960 entry.last_access = now;
961 entry.write_gpu_blocks(gpu_buffer);
963 }
964
965 if let Some(data) = data {
969 let origin = entry.details.describe();
973 let texture_id = entry.texture_id;
974 let size = entry.size;
975 let use_upload_format = self.swizzle.is_none();
976 let op = TextureCacheUpdate::new_update(
977 data,
978 &descriptor,
979 origin,
980 size,
981 use_upload_format,
982 &dirty_rect,
983 );
984 self.pending_updates.push_update(texture_id, op);
985 }
986 }
987
988 pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool {
991 self.get_entry_opt(handle).is_some()
992 }
993
994 pub fn get_allocated_size(&self, handle: &TextureCacheHandle) -> Option<usize> {
997 self.get_entry_opt(handle).map(|entry| {
998 (entry.input_format.bytes_per_pixel() * entry.size.area()) as usize
999 })
1000 }
1001
1002 pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem {
1008 let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.get_cache_location(handle);
1009 CacheItem {
1010 uv_rect_handle,
1011 texture_id: TextureSource::TextureCache(
1012 texture_id,
1013 swizzle,
1014 ),
1015 uv_rect,
1016 user_data,
1017 }
1018 }
1019
1020 pub fn try_get(&self, handle: &TextureCacheHandle) -> Option<CacheItem> {
1021 let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.try_get_cache_location(handle)?;
1022 Some(CacheItem {
1023 uv_rect_handle,
1024 texture_id: TextureSource::TextureCache(
1025 texture_id,
1026 swizzle,
1027 ),
1028 uv_rect,
1029 user_data,
1030 })
1031 }
1032
1033 pub fn try_get_cache_location(
1034 &self,
1035 handle: &TextureCacheHandle,
1036 ) -> Option<(CacheTextureId, DeviceIntRect, Swizzle, GpuBufferHandle, [f32; 4])> {
1037 let entry = self.get_entry_opt(handle)?;
1038 let origin = entry.details.describe();
1039 if entry.last_access != self.now {
1040 return None;
1044 }
1045 Some((
1046 entry.texture_id,
1047 DeviceIntRect::from_origin_and_size(origin, entry.size),
1048 entry.swizzle,
1049 entry.uv_rect_handle,
1050 entry.user_data,
1051 ))
1052 }
1053
1054 pub fn get_cache_location(
1060 &self,
1061 handle: &TextureCacheHandle,
1062 ) -> (CacheTextureId, DeviceIntRect, Swizzle, GpuBufferHandle, [f32; 4]) {
1063 self.try_get_cache_location(handle).expect("BUG: was dropped from cache or not updated!")
1064 }
1065
1066 fn evict_impl(
1068 &mut self,
1069 entry: CacheEntry,
1070 ) {
1071 entry.evict();
1072 self.free(&entry);
1073 }
1074
1075 pub fn evict_handle(&mut self, handle: &TextureCacheHandle) {
1078 match handle {
1079 TextureCacheHandle::Manual(handle) => {
1080 let index = self.manual_handles.iter().position(|strong_handle| {
1086 strong_handle.matches(handle)
1087 });
1088 if let Some(index) = index {
1089 let handle = self.manual_handles.swap_remove(index);
1090 let entry = self.manual_entries.free(handle);
1091 self.evict_impl(entry);
1092 }
1093 }
1094 TextureCacheHandle::Auto(handle) => {
1095 if let Some(entry) = self.lru_cache.remove(handle) {
1096 self.evict_impl(entry);
1097 }
1098 }
1099 _ => {}
1100 }
1101 }
1102
1103 pub fn dump_color8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1104 self.shared_textures.color8_linear.dump_as_svg(output)
1105 }
1106
1107 pub fn dump_color8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1108 self.shared_textures.color8_glyphs.dump_as_svg(output)
1109 }
1110
1111 pub fn dump_alpha8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1112 self.shared_textures.alpha8_glyphs.dump_as_svg(output)
1113 }
1114
1115 pub fn dump_alpha8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1116 self.shared_textures.alpha8_linear.dump_as_svg(output)
1117 }
1118
1119 fn get_eviction_threshold(&self, budget_type: BudgetType) -> usize {
1121 if budget_type == BudgetType::Standalone {
1122 return 8 * 1024 * 1024;
1127 }
1128
1129 let bytes_per_texture = self.shared_textures.bytes_per_shared_texture(budget_type);
1138
1139 let ideal_utilization = match budget_type {
1157 BudgetType::SharedAlpha8Glyphs | BudgetType::SharedColor8Glyphs => {
1158 bytes_per_texture * 2 / 3
1161 }
1162 _ => {
1163 bytes_per_texture / 3
1167 }
1168 };
1169
1170 ideal_utilization
1171 }
1172
1173 fn should_continue_evicting(
1179 &self,
1180 budget_type: BudgetType,
1181 eviction_count: usize,
1182 ) -> Option<u64> {
1183
1184 let threshold = self.get_eviction_threshold(budget_type);
1185 let bytes_allocated = self.bytes_allocated[budget_type as usize];
1186
1187 let uses_multiple_atlases = self.shared_textures.has_multiple_textures(budget_type);
1188
1189 if bytes_allocated < threshold && !uses_multiple_atlases {
1194 return None;
1195 }
1196
1197 let age_theshold = match bytes_allocated / threshold {
1200 0 => 400,
1201 1 => 200,
1202 2 => 100,
1203 3 => 50,
1204 4 => 25,
1205 5 => 10,
1206 6 => 5,
1207 _ => 1,
1208 };
1209
1210 if bytes_allocated > 4 * threshold {
1212 return Some(age_theshold);
1213 }
1214
1215 if eviction_count < Self::MAX_EVICTIONS_PER_FRAME {
1218 return Some(age_theshold)
1219 }
1220
1221 None
1222 }
1223
1224
1225 fn evict_items_from_cache_if_required(&mut self, profile: &mut TransactionProfile) {
1228 let previous_frame_id = self.now.frame_id() - 1;
1229 let mut eviction_count = 0;
1230 let mut youngest_evicted = FrameId::first();
1231
1232 for budget in BudgetType::iter() {
1233 while let Some(age_threshold) = self.should_continue_evicting(
1234 budget,
1235 eviction_count,
1236 ) {
1237 if let Some(entry) = self.lru_cache.peek_oldest(budget as u8) {
1238 if entry.last_access.frame_id() + age_threshold > previous_frame_id {
1242 break;
1245 }
1246 if entry.last_access.frame_id() > youngest_evicted {
1247 youngest_evicted = entry.last_access.frame_id();
1248 }
1249 let entry = self.lru_cache.pop_oldest(budget as u8).unwrap();
1250 entry.evict();
1251 self.free(&entry);
1252 eviction_count += 1;
1253 } else {
1254 break;
1259 }
1260 }
1261 }
1262
1263 if eviction_count > 0 {
1264 profile.set(profiler::TEXTURE_CACHE_EVICTION_COUNT, eviction_count);
1265 profile.set(
1266 profiler::TEXTURE_CACHE_YOUNGEST_EVICTION,
1267 self.now.frame_id().as_u64() - youngest_evicted.as_u64()
1268 );
1269 }
1270 }
1271
1272 fn free(&mut self, entry: &CacheEntry) {
1274 match entry.details {
1275 EntryDetails::Standalone { size_in_bytes, .. } => {
1276 self.bytes_allocated[BudgetType::Standalone as usize] -= size_in_bytes;
1277
1278 self.pending_updates.push_free(entry.texture_id);
1280 }
1281 EntryDetails::Cache { origin, alloc_id, allocated_size_in_bytes } => {
1282 let (allocator_list, budget_type) = self.shared_textures.select(
1283 entry.input_format,
1284 entry.filter,
1285 entry.shader,
1286 );
1287
1288 allocator_list.deallocate(entry.texture_id, alloc_id);
1289
1290 self.bytes_allocated[budget_type as usize] -= allocated_size_in_bytes;
1291
1292 if self.debug_flags.contains(
1293 DebugFlags::TEXTURE_CACHE_DBG |
1294 DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
1295 {
1296 self.pending_updates.push_debug_clear(
1297 entry.texture_id,
1298 origin,
1299 entry.size.width,
1300 entry.size.height,
1301 );
1302 }
1303 }
1304 }
1305 }
1306
1307 fn allocate_from_shared_cache(
1309 &mut self,
1310 params: &CacheAllocParams,
1311 ) -> (CacheEntry, BudgetType) {
1312 let (allocator_list, budget_type) = self.shared_textures.select(
1313 params.descriptor.format,
1314 params.filter,
1315 params.shader,
1316 );
1317
1318 let next_id = &mut self.next_id;
1320 let pending_updates = &mut self.pending_updates;
1321
1322 let (texture_id, alloc_id, allocated_rect) = allocator_list.allocate(
1323 params.descriptor.size,
1324 &mut |size, parameters| {
1325 let texture_id = *next_id;
1326 next_id.0 += 1;
1327 pending_updates.push_alloc(
1328 texture_id,
1329 TextureCacheAllocInfo {
1330 target: ImageBufferKind::Texture2D,
1331 width: size.width,
1332 height: size.height,
1333 format: parameters.formats.internal,
1334 filter: parameters.filter,
1335 is_shared_cache: true,
1336 has_depth: false,
1337 category: TextureCacheCategory::Atlas,
1338 },
1339 );
1340
1341 texture_id
1342 },
1343 );
1344
1345 let formats = &allocator_list.texture_parameters().formats;
1346
1347 let swizzle = if formats.external == params.descriptor.format {
1348 Swizzle::default()
1349 } else {
1350 match self.swizzle {
1351 Some(_) => Swizzle::Bgra,
1352 None => Swizzle::default(),
1353 }
1354 };
1355
1356 let bpp = formats.internal.bytes_per_pixel();
1357 let allocated_size_in_bytes = (allocated_rect.area() * bpp) as usize;
1358 self.bytes_allocated[budget_type as usize] += allocated_size_in_bytes;
1359
1360 (CacheEntry {
1361 size: params.descriptor.size,
1362 user_data: params.user_data,
1363 last_access: self.now,
1364 details: EntryDetails::Cache {
1365 origin: allocated_rect.min,
1366 alloc_id,
1367 allocated_size_in_bytes,
1368 },
1369 uv_rect_handle: GpuBufferHandle::INVALID,
1370 input_format: params.descriptor.format,
1371 filter: params.filter,
1372 swizzle,
1373 texture_id,
1374 eviction_notice: None,
1375 uv_rect_kind: params.uv_rect_kind,
1376 shader: params.shader
1377 }, budget_type)
1378 }
1379
1380 pub fn is_allowed_in_shared_cache(
1383 &self,
1384 filter: TextureFilter,
1385 descriptor: &ImageDescriptor,
1386 ) -> bool {
1387 let mut allowed_in_shared_cache = true;
1388
1389 if matches!(descriptor.format, ImageFormat::RGBA8 | ImageFormat::BGRA8)
1390 && filter == TextureFilter::Linear
1391 {
1392 let max = self.shared_textures.color8_linear.size() / 2;
1394 allowed_in_shared_cache = descriptor.size.width.max(descriptor.size.height) <= max;
1395 } else if descriptor.size.width > TEXTURE_REGION_DIMENSIONS {
1396 allowed_in_shared_cache = false;
1397 }
1398
1399 if descriptor.size.height > TEXTURE_REGION_DIMENSIONS {
1400 allowed_in_shared_cache = false;
1401 }
1402
1403 if filter == TextureFilter::Nearest &&
1407 descriptor.format.bytes_per_pixel() <= 2
1408 {
1409 allowed_in_shared_cache = false;
1410 }
1411
1412 allowed_in_shared_cache
1413 }
1414
1415 pub fn alloc_render_target(
1417 &mut self,
1418 size: DeviceIntSize,
1419 format: ImageFormat,
1420 ) -> CacheTextureId {
1421 let texture_id = self.next_id;
1422 self.next_id.0 += 1;
1423
1424 let info = TextureCacheAllocInfo {
1426 target: ImageBufferKind::Texture2D,
1427 width: size.width,
1428 height: size.height,
1429 format,
1430 filter: TextureFilter::Linear,
1431 is_shared_cache: false,
1432 has_depth: false,
1433 category: TextureCacheCategory::RenderTarget,
1434 };
1435
1436 self.pending_updates.push_alloc(texture_id, info);
1437
1438 texture_id
1439 }
1440
1441 pub fn free_render_target(
1443 &mut self,
1444 id: CacheTextureId,
1445 ) {
1446 self.pending_updates.push_free(id);
1447 }
1448
1449 fn allocate_standalone_entry(
1451 &mut self,
1452 params: &CacheAllocParams,
1453 ) -> (CacheEntry, BudgetType) {
1454 let texture_id = self.next_id;
1455 self.next_id.0 += 1;
1456
1457 let info = TextureCacheAllocInfo {
1459 target: ImageBufferKind::Texture2D,
1460 width: params.descriptor.size.width,
1461 height: params.descriptor.size.height,
1462 format: params.descriptor.format,
1463 filter: params.filter,
1464 is_shared_cache: false,
1465 has_depth: false,
1466 category: TextureCacheCategory::Standalone,
1467 };
1468
1469 let size_in_bytes = (info.width * info.height * info.format.bytes_per_pixel()) as usize;
1470 self.bytes_allocated[BudgetType::Standalone as usize] += size_in_bytes;
1471
1472 self.pending_updates.push_alloc(texture_id, info);
1473
1474 let swizzle = if params.descriptor.format == ImageFormat::BGRA8 {
1476 self.swizzle.map(|s| s.bgra8_sampling_swizzle)
1477 } else {
1478 None
1479 };
1480
1481 (CacheEntry::new_standalone(
1482 texture_id,
1483 self.now,
1484 params,
1485 swizzle.unwrap_or_default(),
1486 size_in_bytes,
1487 ), BudgetType::Standalone)
1488 }
1489
1490 fn allocate(
1493 &mut self,
1494 params: &CacheAllocParams,
1495 handle: &mut TextureCacheHandle,
1496 eviction: Eviction,
1497 force_standalone_texture: bool,
1498 ) {
1499 debug_assert!(self.now.is_valid());
1500 assert!(!params.descriptor.size.is_empty());
1501
1502 let use_shared_cache = !force_standalone_texture && self.is_allowed_in_shared_cache(params.filter, ¶ms.descriptor);
1505 let (new_cache_entry, budget_type) = if use_shared_cache {
1506 self.allocate_from_shared_cache(params)
1507 } else {
1508 self.allocate_standalone_entry(params)
1509 };
1510
1511 let details = new_cache_entry.details.clone();
1512 let texture_id = new_cache_entry.texture_id;
1513
1514 let old_entry = match (&mut *handle, eviction) {
1522 (TextureCacheHandle::Auto(handle), Eviction::Auto) => {
1523 self.lru_cache.replace_or_insert(handle, budget_type as u8, new_cache_entry)
1524 },
1525 (TextureCacheHandle::Manual(handle), Eviction::Manual) => {
1526 let entry = self.manual_entries.get_opt_mut(handle)
1527 .expect("Don't call this after evicting");
1528 Some(mem::replace(entry, new_cache_entry))
1529 },
1530 (TextureCacheHandle::Manual(_), Eviction::Auto) |
1531 (TextureCacheHandle::Auto(_), Eviction::Manual) => {
1532 panic!("Can't change eviction policy after initial allocation");
1533 },
1534 (TextureCacheHandle::Empty, Eviction::Auto) => {
1535 let new_handle = self.lru_cache.push_new(budget_type as u8, new_cache_entry);
1536 *handle = TextureCacheHandle::Auto(new_handle);
1537 None
1538 },
1539 (TextureCacheHandle::Empty, Eviction::Manual) => {
1540 let manual_handle = self.manual_entries.insert(new_cache_entry);
1541 let new_handle = manual_handle.weak();
1542 self.manual_handles.push(manual_handle);
1543 *handle = TextureCacheHandle::Manual(new_handle);
1544 None
1545 },
1546 };
1547 if let Some(old_entry) = old_entry {
1548 old_entry.evict();
1549 self.free(&old_entry);
1550 }
1551
1552 if let EntryDetails::Cache { alloc_id, .. } = details {
1553 let allocator_list = self.shared_textures.select(
1554 params.descriptor.format,
1555 params.filter,
1556 params.shader,
1557 ).0;
1558
1559 allocator_list.set_handle(texture_id, alloc_id, handle);
1560 }
1561 }
1562
1563 pub fn shared_alpha_expected_format(&self) -> ImageFormat {
1564 self.shared_textures.alpha8_linear.texture_parameters().formats.external
1565 }
1566
1567 pub fn shared_color_expected_format(&self) -> ImageFormat {
1568 self.shared_textures.color8_linear.texture_parameters().formats.external
1569 }
1570
1571
1572 #[cfg(test)]
1573 pub fn total_allocated_bytes_for_testing(&self) -> usize {
1574 BudgetType::iter().map(|b| self.bytes_allocated[b as usize]).sum()
1575 }
1576
1577 pub fn report_memory(&self, ops: &mut MallocSizeOfOps) -> usize {
1578 self.lru_cache.size_of(ops)
1579 }
1580
1581 pub fn allocated_color_bytes(&self) -> usize {
1582 self.bytes_allocated[BudgetType::SharedColor8Linear as usize]
1583 }
1584}
1585
1586#[cfg_attr(feature = "capture", derive(Serialize))]
1587#[cfg_attr(feature = "replay", derive(Deserialize))]
1588pub struct TextureParameters {
1589 pub formats: TextureFormatPair<ImageFormat>,
1590 pub filter: TextureFilter,
1591}
1592
1593impl TextureCacheUpdate {
1594 fn new_update(
1598 data: CachedImageData,
1599 descriptor: &ImageDescriptor,
1600 origin: DeviceIntPoint,
1601 size: DeviceIntSize,
1602 use_upload_format: bool,
1603 dirty_rect: &ImageDirtyRect,
1604 ) -> TextureCacheUpdate {
1605 let source = match data {
1606 CachedImageData::Snapshot => {
1607 panic!("Snapshots should not do texture uploads");
1608 }
1609 CachedImageData::Blob => {
1610 panic!("The vector image should have been rasterized.");
1611 }
1612 CachedImageData::External(ext_image) => match ext_image.image_type {
1613 ExternalImageType::TextureHandle(_) => {
1614 panic!("External texture handle should not go through texture_cache.");
1615 }
1616 ExternalImageType::Buffer => TextureUpdateSource::External {
1617 id: ext_image.id,
1618 channel_index: ext_image.channel_index,
1619 },
1620 },
1621 CachedImageData::Raw(bytes) => {
1622 let finish = descriptor.offset +
1623 descriptor.size.width * descriptor.format.bytes_per_pixel() +
1624 (descriptor.size.height - 1) * descriptor.compute_stride();
1625 assert!(bytes.len() >= finish as usize);
1626
1627 TextureUpdateSource::Bytes { data: bytes }
1628 }
1629 };
1630 let format_override = if use_upload_format {
1631 Some(descriptor.format)
1632 } else {
1633 None
1634 };
1635
1636 match *dirty_rect {
1637 DirtyRect::Partial(dirty) => {
1638 let stride = descriptor.compute_stride();
1640 let offset = descriptor.offset + dirty.min.y * stride + dirty.min.x * descriptor.format.bytes_per_pixel();
1641
1642 TextureCacheUpdate {
1643 rect: DeviceIntRect::from_origin_and_size(
1644 DeviceIntPoint::new(origin.x + dirty.min.x, origin.y + dirty.min.y),
1645 DeviceIntSize::new(
1646 dirty.width().min(size.width - dirty.min.x),
1647 dirty.height().min(size.height - dirty.min.y),
1648 ),
1649 ),
1650 source,
1651 stride: Some(stride),
1652 offset,
1653 format_override,
1654 }
1655 }
1656 DirtyRect::All => {
1657 TextureCacheUpdate {
1658 rect: DeviceIntRect::from_origin_and_size(origin, size),
1659 source,
1660 stride: descriptor.stride,
1661 offset: descriptor.offset,
1662 format_override,
1663 }
1664 }
1665 }
1666 }
1667}
1668
1669#[cfg(test)]
1670mod test_texture_cache {
1671 use crate::renderer::GpuBufferBuilderF;
1672 use crate::internal_types::FrameId;
1673
1674 #[test]
1675 fn check_allocation_size_balance() {
1676 use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
1681 use crate::device::TextureFilter;
1682 use crate::gpu_types::UvRectKind;
1683 use crate::frame_allocator::FrameMemory;
1684 use api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, DirtyRect};
1685 use api::units::*;
1686 use euclid::size2;
1687 let mut texture_cache = TextureCache::new_for_testing(2048, ImageFormat::BGRA8);
1688 let memory = FrameMemory::fallback();
1689 let mut gpu_buffer = GpuBufferBuilderF::new(&memory, 0, FrameId::first());
1690
1691 let sizes: &[DeviceIntSize] = &[
1692 size2(23, 27),
1693 size2(15, 22),
1694 size2(11, 5),
1695 size2(20, 25),
1696 size2(38, 41),
1697 size2(11, 19),
1698 size2(13, 21),
1699 size2(37, 40),
1700 size2(13, 15),
1701 size2(14, 16),
1702 size2(10, 9),
1703 size2(25, 28),
1704 ];
1705
1706 let bytes_at_start = texture_cache.total_allocated_bytes_for_testing();
1707
1708 let handles: Vec<TextureCacheHandle> = sizes.iter().map(|size| {
1709 let mut texture_cache_handle = TextureCacheHandle::invalid();
1710 texture_cache.request(&texture_cache_handle, &mut gpu_buffer);
1711 texture_cache.update(
1712 &mut texture_cache_handle,
1713 ImageDescriptor {
1714 size: *size,
1715 stride: None,
1716 format: ImageFormat::BGRA8,
1717 flags: ImageDescriptorFlags::empty(),
1718 offset: 0,
1719 },
1720 TextureFilter::Linear,
1721 None,
1722 [0.0; 4],
1723 DirtyRect::All,
1724 &mut gpu_buffer,
1725 None,
1726 UvRectKind::Rect,
1727 Eviction::Manual,
1728 TargetShader::Text,
1729 false,
1730 );
1731 texture_cache_handle
1732 }).collect();
1733
1734 let bytes_after_allocating = texture_cache.total_allocated_bytes_for_testing();
1735 assert!(bytes_after_allocating > bytes_at_start);
1736
1737 for handle in handles {
1738 texture_cache.evict_handle(&handle);
1739 }
1740
1741 let bytes_at_end = texture_cache.total_allocated_bytes_for_testing();
1742 assert_eq!(bytes_at_end, bytes_at_start);
1743 }
1744}