1use api::{DirtyRect, ExternalImageType, ImageFormat, ImageBufferKind};
6use api::{DebugFlags, ImageDescriptor};
7use api::units::*;
8#[cfg(test)]
9use api::{DocumentId, IdNamespace};
10use crate::device::{TextureFilter, TextureFormatPair};
11use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
12use crate::gpu_cache::{GpuCache, GpuCacheHandle};
13use crate::gpu_types::{ImageSource, UvRectKind};
14use crate::internal_types::{
15 CacheTextureId, Swizzle, SwizzleSettings, FrameStamp, FrameId,
16 TextureUpdateList, TextureUpdateSource, TextureSource,
17 TextureCacheAllocInfo, TextureCacheUpdate, TextureCacheCategory,
18};
19use crate::lru_cache::LRUCache;
20use crate::profiler::{self, TransactionProfile};
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,
105 pub uv_rect_handle: GpuCacheHandle,
107 pub input_format: ImageFormat,
109 pub filter: TextureFilter,
110 pub swizzle: Swizzle,
111 pub texture_id: CacheTextureId,
113 pub eviction_notice: Option<EvictionNotice>,
115 pub uv_rect_kind: UvRectKind,
117
118 pub shader: TargetShader,
119}
120
121malloc_size_of::malloc_size_of_is_0!(
122 CacheEntry,
123 AutoCacheEntryMarker, ManualCacheEntryMarker
124);
125
126impl CacheEntry {
127 fn new_standalone(
129 texture_id: CacheTextureId,
130 last_access: FrameStamp,
131 params: &CacheAllocParams,
132 swizzle: Swizzle,
133 size_in_bytes: usize,
134 ) -> Self {
135 CacheEntry {
136 size: params.descriptor.size,
137 user_data: params.user_data,
138 last_access,
139 details: EntryDetails::Standalone {
140 size_in_bytes,
141 },
142 texture_id,
143 input_format: params.descriptor.format,
144 filter: params.filter,
145 swizzle,
146 uv_rect_handle: GpuCacheHandle::new(),
147 eviction_notice: None,
148 uv_rect_kind: params.uv_rect_kind,
149 shader: TargetShader::Default,
150 }
151 }
152
153 fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
158 if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) {
159 let origin = self.details.describe();
160 let image_source = ImageSource {
161 p0: origin.to_f32(),
162 p1: (origin + self.size).to_f32(),
163 user_data: self.user_data,
164 uv_rect_kind: self.uv_rect_kind,
165 };
166 image_source.write_gpu_blocks(&mut request);
167 }
168 }
169
170 fn evict(&self) {
171 if let Some(eviction_notice) = self.eviction_notice.as_ref() {
172 eviction_notice.notify();
173 }
174 }
175
176 fn alternative_input_format(&self) -> ImageFormat {
177 match self.input_format {
178 ImageFormat::RGBA8 => ImageFormat::BGRA8,
179 ImageFormat::BGRA8 => ImageFormat::RGBA8,
180 other => other,
181 }
182 }
183}
184
185
186#[derive(MallocSizeOf,Clone,PartialEq,Debug)]
194#[cfg_attr(feature = "capture", derive(Serialize))]
195#[cfg_attr(feature = "replay", derive(Deserialize))]
196pub enum TextureCacheHandle {
197 Empty,
199
200 Auto(WeakFreeListHandle<AutoCacheEntryMarker>),
202
203 Manual(WeakFreeListHandle<ManualCacheEntryMarker>)
205}
206
207impl TextureCacheHandle {
208 pub fn invalid() -> Self {
209 TextureCacheHandle::Empty
210 }
211}
212
213#[derive(Copy, Clone, Debug, PartialEq, Eq)]
215#[cfg_attr(feature = "capture", derive(Serialize))]
216#[cfg_attr(feature = "replay", derive(Deserialize))]
217pub enum Eviction {
218 Auto,
221 Manual,
224}
225
226#[derive(Clone, Debug, Default)]
233#[cfg_attr(feature = "capture", derive(Serialize))]
234#[cfg_attr(feature = "replay", derive(Deserialize))]
235pub struct EvictionNotice {
236 evicted: Rc<Cell<bool>>,
237}
238
239impl EvictionNotice {
240 fn notify(&self) {
241 self.evicted.set(true);
242 }
243
244 pub fn check(&self) -> bool {
245 if self.evicted.get() {
246 self.evicted.set(false);
247 true
248 } else {
249 false
250 }
251 }
252}
253
254#[derive(Copy, Clone, Debug, PartialEq, Eq)]
261#[repr(u8)]
262#[cfg_attr(feature = "capture", derive(Serialize))]
263#[cfg_attr(feature = "replay", derive(Deserialize))]
264enum BudgetType {
265 SharedColor8Linear,
266 SharedColor8Nearest,
267 SharedColor8Glyphs,
268 SharedAlpha8,
269 SharedAlpha8Glyphs,
270 SharedAlpha16,
271 Standalone,
272}
273
274impl BudgetType {
275 pub const COUNT: usize = 7;
276
277 pub const VALUES: [BudgetType; BudgetType::COUNT] = [
278 BudgetType::SharedColor8Linear,
279 BudgetType::SharedColor8Nearest,
280 BudgetType::SharedColor8Glyphs,
281 BudgetType::SharedAlpha8,
282 BudgetType::SharedAlpha8Glyphs,
283 BudgetType::SharedAlpha16,
284 BudgetType::Standalone,
285 ];
286
287 pub const PRESSURE_COUNTERS: [usize; BudgetType::COUNT] = [
288 profiler::ATLAS_COLOR8_LINEAR_PRESSURE,
289 profiler::ATLAS_COLOR8_NEAREST_PRESSURE,
290 profiler::ATLAS_COLOR8_GLYPHS_PRESSURE,
291 profiler::ATLAS_ALPHA8_PRESSURE,
292 profiler::ATLAS_ALPHA8_GLYPHS_PRESSURE,
293 profiler::ATLAS_ALPHA16_PRESSURE,
294 profiler::ATLAS_STANDALONE_PRESSURE,
295 ];
296
297 pub fn iter() -> impl Iterator<Item = BudgetType> {
298 BudgetType::VALUES.iter().cloned()
299 }
300}
301
302#[cfg_attr(feature = "capture", derive(Serialize))]
305#[cfg_attr(feature = "replay", derive(Deserialize))]
306struct SharedTextures {
307 color8_nearest: AllocatorList<ShelfAllocator, TextureParameters>,
308 alpha8_linear: AllocatorList<ShelfAllocator, TextureParameters>,
309 alpha8_glyphs: AllocatorList<ShelfAllocator, TextureParameters>,
310 alpha16_linear: AllocatorList<ShelfAllocator, TextureParameters>,
311 color8_linear: AllocatorList<ShelfAllocator, TextureParameters>,
312 color8_glyphs: AllocatorList<ShelfAllocator, TextureParameters>,
313 bytes_per_texture_of_type: [i32 ; BudgetType::COUNT],
314 next_compaction_idx: usize,
315}
316
317impl SharedTextures {
318 fn new(color_formats: TextureFormatPair<ImageFormat>, config: &TextureCacheConfig) -> Self {
320 let mut bytes_per_texture_of_type = [0 ; BudgetType::COUNT];
321
322 let alpha8_linear = AllocatorList::new(
331 config.alpha8_texture_size,
332 ShelfAllocatorOptions {
333 num_columns: 1,
334 alignment: size2(8, 8),
335 .. ShelfAllocatorOptions::default()
336 },
337 TextureParameters {
338 formats: TextureFormatPair::from(ImageFormat::R8),
339 filter: TextureFilter::Linear,
340 },
341 );
342 bytes_per_texture_of_type[BudgetType::SharedAlpha8 as usize] =
343 config.alpha8_texture_size * config.alpha8_texture_size;
344
345 let alpha8_glyphs = AllocatorList::new(
347 config.alpha8_glyph_texture_size,
348 ShelfAllocatorOptions {
349 num_columns: if config.alpha8_glyph_texture_size >= 1024 { 2 } else { 1 },
350 alignment: size2(4, 8),
351 .. ShelfAllocatorOptions::default()
352 },
353 TextureParameters {
354 formats: TextureFormatPair::from(ImageFormat::R8),
355 filter: TextureFilter::Linear,
356 },
357 );
358 bytes_per_texture_of_type[BudgetType::SharedAlpha8Glyphs as usize] =
359 config.alpha8_glyph_texture_size * config.alpha8_glyph_texture_size;
360
361 let alpha16_linear = AllocatorList::new(
364 config.alpha16_texture_size,
365 ShelfAllocatorOptions {
366 num_columns: if config.alpha16_texture_size >= 1024 { 2 } else { 1 },
367 alignment: size2(8, 8),
368 .. ShelfAllocatorOptions::default()
369 },
370 TextureParameters {
371 formats: TextureFormatPair::from(ImageFormat::R16),
372 filter: TextureFilter::Linear,
373 },
374 );
375 bytes_per_texture_of_type[BudgetType::SharedAlpha16 as usize] =
376 ImageFormat::R16.bytes_per_pixel() *
377 config.alpha16_texture_size * config.alpha16_texture_size;
378
379 let color8_linear = AllocatorList::new(
381 config.color8_linear_texture_size,
382 ShelfAllocatorOptions {
383 num_columns: if config.color8_linear_texture_size >= 1024 { 2 } else { 1 },
384 alignment: size2(16, 16),
385 .. ShelfAllocatorOptions::default()
386 },
387 TextureParameters {
388 formats: color_formats.clone(),
389 filter: TextureFilter::Linear,
390 },
391 );
392 bytes_per_texture_of_type[BudgetType::SharedColor8Linear as usize] =
393 color_formats.internal.bytes_per_pixel() *
394 config.color8_linear_texture_size * config.color8_linear_texture_size;
395
396 let color8_glyphs = AllocatorList::new(
398 config.color8_glyph_texture_size,
399 ShelfAllocatorOptions {
400 num_columns: if config.color8_glyph_texture_size >= 1024 { 2 } else { 1 },
401 alignment: size2(4, 8),
402 .. ShelfAllocatorOptions::default()
403 },
404 TextureParameters {
405 formats: color_formats.clone(),
406 filter: TextureFilter::Linear,
407 },
408 );
409 bytes_per_texture_of_type[BudgetType::SharedColor8Glyphs as usize] =
410 color_formats.internal.bytes_per_pixel() *
411 config.color8_glyph_texture_size * config.color8_glyph_texture_size;
412
413 let color8_nearest = AllocatorList::new(
417 config.color8_nearest_texture_size,
418 ShelfAllocatorOptions::default(),
419 TextureParameters {
420 formats: color_formats.clone(),
421 filter: TextureFilter::Nearest,
422 }
423 );
424 bytes_per_texture_of_type[BudgetType::SharedColor8Nearest as usize] =
425 color_formats.internal.bytes_per_pixel() *
426 config.color8_nearest_texture_size * config.color8_nearest_texture_size;
427
428 Self {
429 alpha8_linear,
430 alpha8_glyphs,
431 alpha16_linear,
432 color8_linear,
433 color8_glyphs,
434 color8_nearest,
435 bytes_per_texture_of_type,
436 next_compaction_idx: 0,
437 }
438 }
439
440 fn clear(&mut self, updates: &mut TextureUpdateList) {
442 let texture_dealloc_cb = &mut |texture_id| {
443 updates.push_free(texture_id);
444 };
445
446 self.alpha8_linear.clear(texture_dealloc_cb);
447 self.alpha8_glyphs.clear(texture_dealloc_cb);
448 self.alpha16_linear.clear(texture_dealloc_cb);
449 self.color8_linear.clear(texture_dealloc_cb);
450 self.color8_nearest.clear(texture_dealloc_cb);
451 self.color8_glyphs.clear(texture_dealloc_cb);
452 }
453
454 fn select(
456 &mut self, external_format: ImageFormat, filter: TextureFilter, shader: TargetShader,
457 ) -> (&mut dyn AtlasAllocatorList<TextureParameters>, BudgetType) {
458 match external_format {
459 ImageFormat::R8 => {
460 assert_eq!(filter, TextureFilter::Linear);
461 match shader {
462 TargetShader::Text => {
463 (&mut self.alpha8_glyphs, BudgetType::SharedAlpha8Glyphs)
464 },
465 _ => (&mut self.alpha8_linear, BudgetType::SharedAlpha8),
466 }
467 }
468 ImageFormat::R16 => {
469 assert_eq!(filter, TextureFilter::Linear);
470 (&mut self.alpha16_linear, BudgetType::SharedAlpha16)
471 }
472 ImageFormat::RGBA8 |
473 ImageFormat::BGRA8 => {
474 match (filter, shader) {
475 (TextureFilter::Linear, TargetShader::Text) => {
476 (&mut self.color8_glyphs, BudgetType::SharedColor8Glyphs)
477 },
478 (TextureFilter::Linear, _) => {
479 (&mut self.color8_linear, BudgetType::SharedColor8Linear)
480 },
481 (TextureFilter::Nearest, _) => {
482 (&mut self.color8_nearest, BudgetType::SharedColor8Nearest)
483 },
484 _ => panic!("Unexpected filter {:?}", filter),
485 }
486 }
487 _ => panic!("Unexpected format {:?}", external_format),
488 }
489 }
490
491 fn bytes_per_shared_texture(&self, budget_type: BudgetType) -> usize {
494 self.bytes_per_texture_of_type[budget_type as usize] as usize
495 }
496
497 fn has_multiple_textures(&self, budget_type: BudgetType) -> bool {
498 match budget_type {
499 BudgetType::SharedColor8Linear => self.color8_linear.allocated_textures() > 1,
500 BudgetType::SharedColor8Nearest => self.color8_nearest.allocated_textures() > 1,
501 BudgetType::SharedColor8Glyphs => self.color8_glyphs.allocated_textures() > 1,
502 BudgetType::SharedAlpha8 => self.alpha8_linear.allocated_textures() > 1,
503 BudgetType::SharedAlpha8Glyphs => self.alpha8_glyphs.allocated_textures() > 1,
504 BudgetType::SharedAlpha16 => self.alpha16_linear.allocated_textures() > 1,
505 BudgetType::Standalone => false,
506 }
507 }
508}
509
510struct CacheAllocParams {
512 descriptor: ImageDescriptor,
513 filter: TextureFilter,
514 user_data: [f32; 4],
515 uv_rect_kind: UvRectKind,
516 shader: TargetShader,
517}
518
519#[derive(Clone)]
523pub struct TextureCacheConfig {
524 pub color8_linear_texture_size: i32,
525 pub color8_nearest_texture_size: i32,
526 pub color8_glyph_texture_size: i32,
527 pub alpha8_texture_size: i32,
528 pub alpha8_glyph_texture_size: i32,
529 pub alpha16_texture_size: i32,
530}
531
532impl TextureCacheConfig {
533 pub const DEFAULT: Self = TextureCacheConfig {
534 color8_linear_texture_size: 2048,
535 color8_nearest_texture_size: 512,
536 color8_glyph_texture_size: 2048,
537 alpha8_texture_size: 1024,
538 alpha8_glyph_texture_size: 2048,
539 alpha16_texture_size: 512,
540 };
541}
542
543#[cfg_attr(feature = "capture", derive(Serialize))]
561#[cfg_attr(feature = "replay", derive(Deserialize))]
562pub struct TextureCache {
563 shared_textures: SharedTextures,
565
566 max_texture_size: i32,
568
569 tiling_threshold: i32,
572
573 swizzle: Option<SwizzleSettings>,
575
576 debug_flags: DebugFlags,
578
579 pub next_id: CacheTextureId,
581
582 #[cfg_attr(all(feature = "serde", any(feature = "capture", feature = "replay")), serde(skip))]
585 pub pending_updates: TextureUpdateList,
586
587 now: FrameStamp,
589
590 lru_cache: LRUCache<CacheEntry, AutoCacheEntryMarker>,
593
594 manual_entries: FreeList<CacheEntry, ManualCacheEntryMarker>,
596
597 manual_handles: Vec<FreeListHandle<ManualCacheEntryMarker>>,
599
600 bytes_allocated: [usize ; BudgetType::COUNT],
603}
604
605impl TextureCache {
606 const MAX_EVICTIONS_PER_FRAME: usize = 32;
611
612 pub fn new(
613 max_texture_size: i32,
614 tiling_threshold: i32,
615 color_formats: TextureFormatPair<ImageFormat>,
616 swizzle: Option<SwizzleSettings>,
617 config: &TextureCacheConfig,
618 ) -> Self {
619 let pending_updates = TextureUpdateList::new();
620
621 assert!(color_formats.internal != ImageFormat::BGRA8 ||
625 swizzle.map_or(true, |s| s.bgra8_sampling_swizzle == Swizzle::default())
626 );
627
628 let next_texture_id = CacheTextureId(1);
629
630 TextureCache {
631 shared_textures: SharedTextures::new(color_formats, config),
632 max_texture_size,
633 tiling_threshold,
634 swizzle,
635 debug_flags: DebugFlags::empty(),
636 next_id: next_texture_id,
637 pending_updates,
638 now: FrameStamp::INVALID,
639 lru_cache: LRUCache::new(BudgetType::COUNT),
640 manual_entries: FreeList::new(),
641 manual_handles: Vec::new(),
642 bytes_allocated: [0 ; BudgetType::COUNT],
643 }
644 }
645
646 #[cfg(test)]
650 pub fn new_for_testing(
651 max_texture_size: i32,
652 image_format: ImageFormat,
653 ) -> Self {
654 let mut cache = Self::new(
655 max_texture_size,
656 max_texture_size,
657 TextureFormatPair::from(image_format),
658 None,
659 &TextureCacheConfig::DEFAULT,
660 );
661 let mut now = FrameStamp::first(DocumentId::new(IdNamespace(1), 1));
662 now.advance();
663 cache.begin_frame(now, &mut TransactionProfile::new());
664 cache
665 }
666
667 pub fn set_debug_flags(&mut self, flags: DebugFlags) {
668 self.debug_flags = flags;
669 }
670
671 pub fn clear_all(&mut self) {
674 let manual_handles = mem::replace(
676 &mut self.manual_handles,
677 Vec::new(),
678 );
679 for handle in manual_handles {
680 let entry = self.manual_entries.free(handle);
681 self.evict_impl(entry);
682 }
683
684 for budget_type in BudgetType::iter() {
686 while let Some(entry) = self.lru_cache.pop_oldest(budget_type as u8) {
687 entry.evict();
688 self.free(&entry);
689 }
690 }
691
692 self.shared_textures.clear(&mut self.pending_updates);
694 self.pending_updates.note_clear();
695 }
696
697 pub fn begin_frame(&mut self, stamp: FrameStamp, profile: &mut TransactionProfile) {
699 debug_assert!(!self.now.is_valid());
700 profile_scope!("begin_frame");
701 self.now = stamp;
702
703 self.evict_items_from_cache_if_required(profile);
708 }
709
710 pub fn end_frame(&mut self, profile: &mut TransactionProfile) {
711 debug_assert!(self.now.is_valid());
712
713 let updates = &mut self.pending_updates; let callback = &mut|texture_id| { updates.push_free(texture_id); };
715
716 self.shared_textures.alpha8_linear.release_empty_textures(callback);
720 self.shared_textures.alpha8_glyphs.release_empty_textures(callback);
721 self.shared_textures.alpha16_linear.release_empty_textures(callback);
722 self.shared_textures.color8_linear.release_empty_textures(callback);
723 self.shared_textures.color8_nearest.release_empty_textures(callback);
724 self.shared_textures.color8_glyphs.release_empty_textures(callback);
725
726 for budget in BudgetType::iter() {
727 let threshold = self.get_eviction_threshold(budget);
728 let pressure = self.bytes_allocated[budget as usize] as f32 / threshold as f32;
729 profile.set(BudgetType::PRESSURE_COUNTERS[budget as usize], pressure);
730 }
731
732 profile.set(profiler::ATLAS_A8_PIXELS, self.shared_textures.alpha8_linear.allocated_space());
733 profile.set(profiler::ATLAS_A8_TEXTURES, self.shared_textures.alpha8_linear.allocated_textures());
734 profile.set(profiler::ATLAS_A8_GLYPHS_PIXELS, self.shared_textures.alpha8_glyphs.allocated_space());
735 profile.set(profiler::ATLAS_A8_GLYPHS_TEXTURES, self.shared_textures.alpha8_glyphs.allocated_textures());
736 profile.set(profiler::ATLAS_A16_PIXELS, self.shared_textures.alpha16_linear.allocated_space());
737 profile.set(profiler::ATLAS_A16_TEXTURES, self.shared_textures.alpha16_linear.allocated_textures());
738 profile.set(profiler::ATLAS_RGBA8_LINEAR_PIXELS, self.shared_textures.color8_linear.allocated_space());
739 profile.set(profiler::ATLAS_RGBA8_LINEAR_TEXTURES, self.shared_textures.color8_linear.allocated_textures());
740 profile.set(profiler::ATLAS_RGBA8_NEAREST_PIXELS, self.shared_textures.color8_nearest.allocated_space());
741 profile.set(profiler::ATLAS_RGBA8_NEAREST_TEXTURES, self.shared_textures.color8_nearest.allocated_textures());
742 profile.set(profiler::ATLAS_RGBA8_GLYPHS_PIXELS, self.shared_textures.color8_glyphs.allocated_space());
743 profile.set(profiler::ATLAS_RGBA8_GLYPHS_TEXTURES, self.shared_textures.color8_glyphs.allocated_textures());
744
745 let shared_bytes = [
746 BudgetType::SharedColor8Linear,
747 BudgetType::SharedColor8Nearest,
748 BudgetType::SharedColor8Glyphs,
749 BudgetType::SharedAlpha8,
750 BudgetType::SharedAlpha8Glyphs,
751 BudgetType::SharedAlpha16,
752 ].iter().map(|b| self.bytes_allocated[*b as usize]).sum();
753
754 profile.set(profiler::ATLAS_ITEMS_MEM, profiler::bytes_to_mb(shared_bytes));
755
756 self.now = FrameStamp::INVALID;
757 }
758
759 pub fn run_compaction(&mut self, gpu_cache: &mut GpuCache) {
760 let allocator_lists = [
763 &mut self.shared_textures.color8_linear,
764 &mut self.shared_textures.color8_nearest,
765 &mut self.shared_textures.color8_glyphs,
766 &mut self.shared_textures.alpha8_linear,
767 &mut self.shared_textures.alpha8_glyphs,
768 &mut self.shared_textures.alpha16_linear,
769 ];
770
771 let idx = self.shared_textures.next_compaction_idx;
773
774 let area_threshold = 512*512;
778
779 let mut changes = Vec::new();
780 allocator_lists[idx].try_compaction(area_threshold, &mut changes);
781
782 if changes.is_empty() {
783 self.shared_textures.next_compaction_idx = (self.shared_textures.next_compaction_idx + 1) % allocator_lists.len();
785 }
786
787 for change in changes {
788 let bpp = allocator_lists[idx].texture_parameters().formats.internal.bytes_per_pixel();
789
790 let old_bytes = (change.old_rect.area() * bpp) as usize;
794 let new_bytes = (change.new_rect.area() * bpp) as usize;
795 self.bytes_allocated[idx] -= old_bytes;
796 self.bytes_allocated[idx] += new_bytes;
797
798 let entry = match change.handle {
799 TextureCacheHandle::Auto(handle) => self.lru_cache.get_opt_mut(&handle).unwrap(),
800 TextureCacheHandle::Manual(handle) => self.manual_entries.get_opt_mut(&handle).unwrap(),
801 TextureCacheHandle::Empty => { panic!("invalid handle"); }
802 };
803 entry.texture_id = change.new_tex;
804 entry.details = EntryDetails::Cache {
805 origin: change.new_rect.min,
806 alloc_id: change.new_id,
807 allocated_size_in_bytes: new_bytes,
808 };
809
810 gpu_cache.invalidate(&entry.uv_rect_handle);
811 entry.uv_rect_handle = GpuCacheHandle::new();
812
813 let src_rect = DeviceIntRect::from_origin_and_size(change.old_rect.min, entry.size);
814 let dst_rect = DeviceIntRect::from_origin_and_size(change.new_rect.min, entry.size);
815
816 self.pending_updates.push_copy(change.old_tex, &src_rect, change.new_tex, &dst_rect);
817
818 if self.debug_flags.contains(
819 DebugFlags::TEXTURE_CACHE_DBG |
820 DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
821 {
822 self.pending_updates.push_debug_clear(
823 change.old_tex,
824 src_rect.min,
825 src_rect.width(),
826 src_rect.height(),
827 );
828 }
829 }
830 }
831
832 pub fn request(&mut self, handle: &TextureCacheHandle, gpu_cache: &mut GpuCache) -> bool {
841 let now = self.now;
842 let entry = match handle {
843 TextureCacheHandle::Empty => None,
844 TextureCacheHandle::Auto(handle) => {
845 self.lru_cache.touch(handle)
848 },
849 TextureCacheHandle::Manual(handle) => {
850 self.manual_entries.get_opt_mut(handle)
851 },
852 };
853 entry.map_or(true, |entry| {
854 entry.last_access = now;
857 entry.update_gpu_cache(gpu_cache);
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_cache: &mut GpuCache,
917 eviction_notice: Option<&EvictionNotice>,
918 uv_rect_kind: UvRectKind,
919 eviction: Eviction,
920 shader: TargetShader,
921 ) {
922 debug_assert!(self.now.is_valid());
923 let realloc = match self.get_entry_opt(handle) {
930 Some(entry) => {
931 entry.size != descriptor.size || (entry.input_format != descriptor.format &&
932 entry.alternative_input_format() != descriptor.format)
933 }
934 None => {
935 true
937 }
938 };
939
940 if realloc {
941 let params = CacheAllocParams { descriptor, filter, user_data, uv_rect_kind, shader };
942 self.allocate(¶ms, handle, eviction);
943
944 dirty_rect = DirtyRect::All;
946 }
947
948 let entry = self.get_entry_opt_mut(handle)
949 .expect("BUG: There must be an entry at this handle now");
950
951 entry.eviction_notice = eviction_notice.cloned();
953 entry.uv_rect_kind = uv_rect_kind;
954
955 gpu_cache.invalidate(&entry.uv_rect_handle);
960
961 entry.update_gpu_cache(gpu_cache);
963
964 if let Some(data) = data {
968 let origin = entry.details.describe();
972 let texture_id = entry.texture_id;
973 let size = entry.size;
974 let use_upload_format = self.swizzle.is_none();
975 let op = TextureCacheUpdate::new_update(
976 data,
977 &descriptor,
978 origin,
979 size,
980 use_upload_format,
981 &dirty_rect,
982 );
983 self.pending_updates.push_update(texture_id, op);
984 }
985 }
986
987 pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool {
990 self.get_entry_opt(handle).is_some()
991 }
992
993 pub fn get_allocated_size(&self, handle: &TextureCacheHandle) -> Option<usize> {
996 self.get_entry_opt(handle).map(|entry| {
997 (entry.input_format.bytes_per_pixel() * entry.size.area()) as usize
998 })
999 }
1000
1001 pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem {
1007 let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.get_cache_location(handle);
1008 CacheItem {
1009 uv_rect_handle,
1010 texture_id: TextureSource::TextureCache(
1011 texture_id,
1012 swizzle,
1013 ),
1014 uv_rect,
1015 user_data,
1016 }
1017 }
1018
1019 pub fn try_get(&self, handle: &TextureCacheHandle) -> Option<CacheItem> {
1020 let (texture_id, uv_rect, swizzle, uv_rect_handle, user_data) = self.try_get_cache_location(handle)?;
1021 Some(CacheItem {
1022 uv_rect_handle,
1023 texture_id: TextureSource::TextureCache(
1024 texture_id,
1025 swizzle,
1026 ),
1027 uv_rect,
1028 user_data,
1029 })
1030 }
1031
1032 pub fn try_get_cache_location(
1033 &self,
1034 handle: &TextureCacheHandle,
1035 ) -> Option<(CacheTextureId, DeviceIntRect, Swizzle, GpuCacheHandle, [f32; 4])> {
1036 let entry = self.get_entry_opt(handle)?;
1037 let origin = entry.details.describe();
1038 Some((
1039 entry.texture_id,
1040 DeviceIntRect::from_origin_and_size(origin, entry.size),
1041 entry.swizzle,
1042 entry.uv_rect_handle,
1043 entry.user_data,
1044 ))
1045 }
1046
1047 pub fn get_cache_location(
1053 &self,
1054 handle: &TextureCacheHandle,
1055 ) -> (CacheTextureId, DeviceIntRect, Swizzle, GpuCacheHandle, [f32; 4]) {
1056 self.try_get_cache_location(handle).expect("BUG: was dropped from cache or not updated!")
1057 }
1058
1059 fn evict_impl(
1061 &mut self,
1062 entry: CacheEntry,
1063 ) {
1064 entry.evict();
1065 self.free(&entry);
1066 }
1067
1068 pub fn evict_handle(&mut self, handle: &TextureCacheHandle) {
1071 match handle {
1072 TextureCacheHandle::Manual(handle) => {
1073 let index = self.manual_handles.iter().position(|strong_handle| {
1079 strong_handle.matches(handle)
1080 });
1081 if let Some(index) = index {
1082 let handle = self.manual_handles.swap_remove(index);
1083 let entry = self.manual_entries.free(handle);
1084 self.evict_impl(entry);
1085 }
1086 }
1087 TextureCacheHandle::Auto(handle) => {
1088 if let Some(entry) = self.lru_cache.remove(handle) {
1089 self.evict_impl(entry);
1090 }
1091 }
1092 _ => {}
1093 }
1094 }
1095
1096 pub fn dump_color8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1097 self.shared_textures.color8_linear.dump_as_svg(output)
1098 }
1099
1100 pub fn dump_color8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1101 self.shared_textures.color8_glyphs.dump_as_svg(output)
1102 }
1103
1104 pub fn dump_alpha8_glyphs_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1105 self.shared_textures.alpha8_glyphs.dump_as_svg(output)
1106 }
1107
1108 pub fn dump_alpha8_linear_as_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
1109 self.shared_textures.alpha8_linear.dump_as_svg(output)
1110 }
1111
1112 fn get_eviction_threshold(&self, budget_type: BudgetType) -> usize {
1114 if budget_type == BudgetType::Standalone {
1115 return 8 * 1024 * 1024;
1120 }
1121
1122 let bytes_per_texture = self.shared_textures.bytes_per_shared_texture(budget_type);
1131
1132 let ideal_utilization = match budget_type {
1150 BudgetType::SharedAlpha8Glyphs | BudgetType::SharedColor8Glyphs => {
1151 bytes_per_texture * 2 / 3
1154 }
1155 _ => {
1156 bytes_per_texture / 3
1160 }
1161 };
1162
1163 ideal_utilization
1164 }
1165
1166 fn should_continue_evicting(
1172 &self,
1173 budget_type: BudgetType,
1174 eviction_count: usize,
1175 ) -> Option<u64> {
1176
1177 let threshold = self.get_eviction_threshold(budget_type);
1178 let bytes_allocated = self.bytes_allocated[budget_type as usize];
1179
1180 let uses_multiple_atlases = self.shared_textures.has_multiple_textures(budget_type);
1181
1182 if bytes_allocated < threshold && !uses_multiple_atlases {
1187 return None;
1188 }
1189
1190 let age_theshold = match bytes_allocated / threshold {
1193 0 => 400,
1194 1 => 200,
1195 2 => 100,
1196 3 => 50,
1197 4 => 25,
1198 5 => 10,
1199 6 => 5,
1200 _ => 1,
1201 };
1202
1203 if bytes_allocated > 4 * threshold {
1205 return Some(age_theshold);
1206 }
1207
1208 if eviction_count < Self::MAX_EVICTIONS_PER_FRAME {
1211 return Some(age_theshold)
1212 }
1213
1214 None
1215 }
1216
1217
1218 fn evict_items_from_cache_if_required(&mut self, profile: &mut TransactionProfile) {
1221 let previous_frame_id = self.now.frame_id() - 1;
1222 let mut eviction_count = 0;
1223 let mut youngest_evicted = FrameId::first();
1224
1225 for budget in BudgetType::iter() {
1226 while let Some(age_threshold) = self.should_continue_evicting(
1227 budget,
1228 eviction_count,
1229 ) {
1230 if let Some(entry) = self.lru_cache.peek_oldest(budget as u8) {
1231 if entry.last_access.frame_id() + age_threshold > previous_frame_id {
1235 break;
1238 }
1239 if entry.last_access.frame_id() > youngest_evicted {
1240 youngest_evicted = entry.last_access.frame_id();
1241 }
1242 let entry = self.lru_cache.pop_oldest(budget as u8).unwrap();
1243 entry.evict();
1244 self.free(&entry);
1245 eviction_count += 1;
1246 } else {
1247 break;
1252 }
1253 }
1254 }
1255
1256 if eviction_count > 0 {
1257 profile.set(profiler::TEXTURE_CACHE_EVICTION_COUNT, eviction_count);
1258 profile.set(
1259 profiler::TEXTURE_CACHE_YOUNGEST_EVICTION,
1260 self.now.frame_id().as_u64() - youngest_evicted.as_u64()
1261 );
1262 }
1263 }
1264
1265 fn free(&mut self, entry: &CacheEntry) {
1267 match entry.details {
1268 EntryDetails::Standalone { size_in_bytes, .. } => {
1269 self.bytes_allocated[BudgetType::Standalone as usize] -= size_in_bytes;
1270
1271 self.pending_updates.push_free(entry.texture_id);
1273 }
1274 EntryDetails::Cache { origin, alloc_id, allocated_size_in_bytes } => {
1275 let (allocator_list, budget_type) = self.shared_textures.select(
1276 entry.input_format,
1277 entry.filter,
1278 entry.shader,
1279 );
1280
1281 allocator_list.deallocate(entry.texture_id, alloc_id);
1282
1283 self.bytes_allocated[budget_type as usize] -= allocated_size_in_bytes;
1284
1285 if self.debug_flags.contains(
1286 DebugFlags::TEXTURE_CACHE_DBG |
1287 DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
1288 {
1289 self.pending_updates.push_debug_clear(
1290 entry.texture_id,
1291 origin,
1292 entry.size.width,
1293 entry.size.height,
1294 );
1295 }
1296 }
1297 }
1298 }
1299
1300 fn allocate_from_shared_cache(
1302 &mut self,
1303 params: &CacheAllocParams,
1304 ) -> (CacheEntry, BudgetType) {
1305 let (allocator_list, budget_type) = self.shared_textures.select(
1306 params.descriptor.format,
1307 params.filter,
1308 params.shader,
1309 );
1310
1311 let next_id = &mut self.next_id;
1313 let pending_updates = &mut self.pending_updates;
1314
1315 let (texture_id, alloc_id, allocated_rect) = allocator_list.allocate(
1316 params.descriptor.size,
1317 &mut |size, parameters| {
1318 let texture_id = *next_id;
1319 next_id.0 += 1;
1320 pending_updates.push_alloc(
1321 texture_id,
1322 TextureCacheAllocInfo {
1323 target: ImageBufferKind::Texture2D,
1324 width: size.width,
1325 height: size.height,
1326 format: parameters.formats.internal,
1327 filter: parameters.filter,
1328 is_shared_cache: true,
1329 has_depth: false,
1330 category: TextureCacheCategory::Atlas,
1331 },
1332 );
1333
1334 texture_id
1335 },
1336 );
1337
1338 let formats = &allocator_list.texture_parameters().formats;
1339
1340 let swizzle = if formats.external == params.descriptor.format {
1341 Swizzle::default()
1342 } else {
1343 match self.swizzle {
1344 Some(_) => Swizzle::Bgra,
1345 None => Swizzle::default(),
1346 }
1347 };
1348
1349 let bpp = formats.internal.bytes_per_pixel();
1350 let allocated_size_in_bytes = (allocated_rect.area() * bpp) as usize;
1351 self.bytes_allocated[budget_type as usize] += allocated_size_in_bytes;
1352
1353 (CacheEntry {
1354 size: params.descriptor.size,
1355 user_data: params.user_data,
1356 last_access: self.now,
1357 details: EntryDetails::Cache {
1358 origin: allocated_rect.min,
1359 alloc_id,
1360 allocated_size_in_bytes,
1361 },
1362 uv_rect_handle: GpuCacheHandle::new(),
1363 input_format: params.descriptor.format,
1364 filter: params.filter,
1365 swizzle,
1366 texture_id,
1367 eviction_notice: None,
1368 uv_rect_kind: params.uv_rect_kind,
1369 shader: params.shader
1370 }, budget_type)
1371 }
1372
1373 pub fn is_allowed_in_shared_cache(
1376 &self,
1377 filter: TextureFilter,
1378 descriptor: &ImageDescriptor,
1379 ) -> bool {
1380 let mut allowed_in_shared_cache = true;
1381
1382 if matches!(descriptor.format, ImageFormat::RGBA8 | ImageFormat::BGRA8)
1383 && filter == TextureFilter::Linear
1384 {
1385 let max = self.shared_textures.color8_linear.size() / 2;
1387 allowed_in_shared_cache = descriptor.size.width.max(descriptor.size.height) <= max;
1388 } else if descriptor.size.width > TEXTURE_REGION_DIMENSIONS {
1389 allowed_in_shared_cache = false;
1390 }
1391
1392 if descriptor.size.height > TEXTURE_REGION_DIMENSIONS {
1393 allowed_in_shared_cache = false;
1394 }
1395
1396 if filter == TextureFilter::Nearest &&
1400 descriptor.format.bytes_per_pixel() <= 2
1401 {
1402 allowed_in_shared_cache = false;
1403 }
1404
1405 allowed_in_shared_cache
1406 }
1407
1408 pub fn alloc_render_target(
1410 &mut self,
1411 size: DeviceIntSize,
1412 format: ImageFormat,
1413 ) -> CacheTextureId {
1414 let texture_id = self.next_id;
1415 self.next_id.0 += 1;
1416
1417 let info = TextureCacheAllocInfo {
1419 target: ImageBufferKind::Texture2D,
1420 width: size.width,
1421 height: size.height,
1422 format,
1423 filter: TextureFilter::Linear,
1424 is_shared_cache: false,
1425 has_depth: false,
1426 category: TextureCacheCategory::RenderTarget,
1427 };
1428
1429 self.pending_updates.push_alloc(texture_id, info);
1430
1431 texture_id
1432 }
1433
1434 pub fn free_render_target(
1436 &mut self,
1437 id: CacheTextureId,
1438 ) {
1439 self.pending_updates.push_free(id);
1440 }
1441
1442 fn allocate_standalone_entry(
1444 &mut self,
1445 params: &CacheAllocParams,
1446 ) -> (CacheEntry, BudgetType) {
1447 let texture_id = self.next_id;
1448 self.next_id.0 += 1;
1449
1450 let info = TextureCacheAllocInfo {
1452 target: ImageBufferKind::Texture2D,
1453 width: params.descriptor.size.width,
1454 height: params.descriptor.size.height,
1455 format: params.descriptor.format,
1456 filter: params.filter,
1457 is_shared_cache: false,
1458 has_depth: false,
1459 category: TextureCacheCategory::Standalone,
1460 };
1461
1462 let size_in_bytes = (info.width * info.height * info.format.bytes_per_pixel()) as usize;
1463 self.bytes_allocated[BudgetType::Standalone as usize] += size_in_bytes;
1464
1465 self.pending_updates.push_alloc(texture_id, info);
1466
1467 let swizzle = if params.descriptor.format == ImageFormat::BGRA8 {
1469 self.swizzle.map(|s| s.bgra8_sampling_swizzle)
1470 } else {
1471 None
1472 };
1473
1474 (CacheEntry::new_standalone(
1475 texture_id,
1476 self.now,
1477 params,
1478 swizzle.unwrap_or_default(),
1479 size_in_bytes,
1480 ), BudgetType::Standalone)
1481 }
1482
1483 fn allocate(
1486 &mut self,
1487 params: &CacheAllocParams,
1488 handle: &mut TextureCacheHandle,
1489 eviction: Eviction,
1490 ) {
1491 debug_assert!(self.now.is_valid());
1492 assert!(!params.descriptor.size.is_empty());
1493
1494 let use_shared_cache = self.is_allowed_in_shared_cache(params.filter, ¶ms.descriptor);
1497 let (new_cache_entry, budget_type) = if use_shared_cache {
1498 self.allocate_from_shared_cache(params)
1499 } else {
1500 self.allocate_standalone_entry(params)
1501 };
1502
1503 let details = new_cache_entry.details.clone();
1504 let texture_id = new_cache_entry.texture_id;
1505
1506 let old_entry = match (&mut *handle, eviction) {
1514 (TextureCacheHandle::Auto(handle), Eviction::Auto) => {
1515 self.lru_cache.replace_or_insert(handle, budget_type as u8, new_cache_entry)
1516 },
1517 (TextureCacheHandle::Manual(handle), Eviction::Manual) => {
1518 let entry = self.manual_entries.get_opt_mut(handle)
1519 .expect("Don't call this after evicting");
1520 Some(mem::replace(entry, new_cache_entry))
1521 },
1522 (TextureCacheHandle::Manual(_), Eviction::Auto) |
1523 (TextureCacheHandle::Auto(_), Eviction::Manual) => {
1524 panic!("Can't change eviction policy after initial allocation");
1525 },
1526 (TextureCacheHandle::Empty, Eviction::Auto) => {
1527 let new_handle = self.lru_cache.push_new(budget_type as u8, new_cache_entry);
1528 *handle = TextureCacheHandle::Auto(new_handle);
1529 None
1530 },
1531 (TextureCacheHandle::Empty, Eviction::Manual) => {
1532 let manual_handle = self.manual_entries.insert(new_cache_entry);
1533 let new_handle = manual_handle.weak();
1534 self.manual_handles.push(manual_handle);
1535 *handle = TextureCacheHandle::Manual(new_handle);
1536 None
1537 },
1538 };
1539 if let Some(old_entry) = old_entry {
1540 old_entry.evict();
1541 self.free(&old_entry);
1542 }
1543
1544 if let EntryDetails::Cache { alloc_id, .. } = details {
1545 let allocator_list = self.shared_textures.select(
1546 params.descriptor.format,
1547 params.filter,
1548 params.shader,
1549 ).0;
1550
1551 allocator_list.set_handle(texture_id, alloc_id, handle);
1552 }
1553 }
1554
1555 pub fn shared_alpha_expected_format(&self) -> ImageFormat {
1556 self.shared_textures.alpha8_linear.texture_parameters().formats.external
1557 }
1558
1559 pub fn shared_color_expected_format(&self) -> ImageFormat {
1560 self.shared_textures.color8_linear.texture_parameters().formats.external
1561 }
1562
1563
1564 #[cfg(test)]
1565 pub fn total_allocated_bytes_for_testing(&self) -> usize {
1566 BudgetType::iter().map(|b| self.bytes_allocated[b as usize]).sum()
1567 }
1568
1569 pub fn report_memory(&self, ops: &mut MallocSizeOfOps) -> usize {
1570 self.lru_cache.size_of(ops)
1571 }
1572}
1573
1574#[cfg_attr(feature = "capture", derive(Serialize))]
1575#[cfg_attr(feature = "replay", derive(Deserialize))]
1576pub struct TextureParameters {
1577 pub formats: TextureFormatPair<ImageFormat>,
1578 pub filter: TextureFilter,
1579}
1580
1581impl TextureCacheUpdate {
1582 fn new_update(
1586 data: CachedImageData,
1587 descriptor: &ImageDescriptor,
1588 origin: DeviceIntPoint,
1589 size: DeviceIntSize,
1590 use_upload_format: bool,
1591 dirty_rect: &ImageDirtyRect,
1592 ) -> TextureCacheUpdate {
1593 let source = match data {
1594 CachedImageData::Snapshot => {
1595 panic!("Snapshots should not do texture uploads");
1596 }
1597 CachedImageData::Blob => {
1598 panic!("The vector image should have been rasterized.");
1599 }
1600 CachedImageData::External(ext_image) => match ext_image.image_type {
1601 ExternalImageType::TextureHandle(_) => {
1602 panic!("External texture handle should not go through texture_cache.");
1603 }
1604 ExternalImageType::Buffer => TextureUpdateSource::External {
1605 id: ext_image.id,
1606 channel_index: ext_image.channel_index,
1607 },
1608 },
1609 CachedImageData::Raw(bytes) => {
1610 let finish = descriptor.offset +
1611 descriptor.size.width * descriptor.format.bytes_per_pixel() +
1612 (descriptor.size.height - 1) * descriptor.compute_stride();
1613 assert!(bytes.len() >= finish as usize);
1614
1615 TextureUpdateSource::Bytes { data: bytes }
1616 }
1617 };
1618 let format_override = if use_upload_format {
1619 Some(descriptor.format)
1620 } else {
1621 None
1622 };
1623
1624 match *dirty_rect {
1625 DirtyRect::Partial(dirty) => {
1626 let stride = descriptor.compute_stride();
1628 let offset = descriptor.offset + dirty.min.y * stride + dirty.min.x * descriptor.format.bytes_per_pixel();
1629
1630 TextureCacheUpdate {
1631 rect: DeviceIntRect::from_origin_and_size(
1632 DeviceIntPoint::new(origin.x + dirty.min.x, origin.y + dirty.min.y),
1633 DeviceIntSize::new(
1634 dirty.width().min(size.width - dirty.min.x),
1635 dirty.height().min(size.height - dirty.min.y),
1636 ),
1637 ),
1638 source,
1639 stride: Some(stride),
1640 offset,
1641 format_override,
1642 }
1643 }
1644 DirtyRect::All => {
1645 TextureCacheUpdate {
1646 rect: DeviceIntRect::from_origin_and_size(origin, size),
1647 source,
1648 stride: descriptor.stride,
1649 offset: descriptor.offset,
1650 format_override,
1651 }
1652 }
1653 }
1654 }
1655}
1656
1657#[cfg(test)]
1658mod test_texture_cache {
1659 #[test]
1660 fn check_allocation_size_balance() {
1661 use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
1666 use crate::gpu_cache::GpuCache;
1667 use crate::device::TextureFilter;
1668 use crate::gpu_types::UvRectKind;
1669 use api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, DirtyRect};
1670 use api::units::*;
1671 use euclid::size2;
1672 let mut texture_cache = TextureCache::new_for_testing(2048, ImageFormat::BGRA8);
1673 let mut gpu_cache = GpuCache::new_for_testing();
1674
1675 let sizes: &[DeviceIntSize] = &[
1676 size2(23, 27),
1677 size2(15, 22),
1678 size2(11, 5),
1679 size2(20, 25),
1680 size2(38, 41),
1681 size2(11, 19),
1682 size2(13, 21),
1683 size2(37, 40),
1684 size2(13, 15),
1685 size2(14, 16),
1686 size2(10, 9),
1687 size2(25, 28),
1688 ];
1689
1690 let bytes_at_start = texture_cache.total_allocated_bytes_for_testing();
1691
1692 let handles: Vec<TextureCacheHandle> = sizes.iter().map(|size| {
1693 let mut texture_cache_handle = TextureCacheHandle::invalid();
1694 texture_cache.request(&texture_cache_handle, &mut gpu_cache);
1695 texture_cache.update(
1696 &mut texture_cache_handle,
1697 ImageDescriptor {
1698 size: *size,
1699 stride: None,
1700 format: ImageFormat::BGRA8,
1701 flags: ImageDescriptorFlags::empty(),
1702 offset: 0,
1703 },
1704 TextureFilter::Linear,
1705 None,
1706 [0.0; 4],
1707 DirtyRect::All,
1708 &mut gpu_cache,
1709 None,
1710 UvRectKind::Rect,
1711 Eviction::Manual,
1712 TargetShader::Text,
1713 );
1714 texture_cache_handle
1715 }).collect();
1716
1717 let bytes_after_allocating = texture_cache.total_allocated_bytes_for_testing();
1718 assert!(bytes_after_allocating > bytes_at_start);
1719
1720 for handle in handles {
1721 texture_cache.evict_handle(&handle);
1722 }
1723
1724 let bytes_at_end = texture_cache.total_allocated_bytes_for_testing();
1725 assert_eq!(bytes_at_end, bytes_at_start);
1726 }
1727}