1use std::collections::{HashMap, VecDeque};
2use std::sync::Mutex;
3
4use crate::RenderSize;
5use crate::Shared;
6use crate::retire::{Retired, Retirement};
7
8#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
9pub(crate) struct TextureKey {
10 size: RenderSize,
11 format: wgpu::TextureFormat,
12}
13
14#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
16pub struct TexturePoolStats {
17 pub created: u64,
19 pub reused: u64,
21 pub active: usize,
23 pub active_bytes: u64,
25 pub high_water_bytes: u64,
27 pub available: usize,
29 pub available_bytes: u64,
31 pub budget_bytes: u64,
33 pub evicted: u64,
35}
36
37#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
39pub struct UploadTexturePoolStats {
40 pub created: u64,
42 pub reused: u64,
44 pub discarded: u64,
46 pub active: usize,
48 pub active_bytes: u64,
50 pub high_water_bytes: u64,
52 pub available: usize,
54 pub available_bytes: u64,
56 pub budget_bytes: u64,
58}
59
60#[derive(Debug)]
61struct UploadTextureEntry {
62 key: TextureKey,
63 texture: wgpu::Texture,
64 view: wgpu::TextureView,
65 generation: u64,
67}
68
69#[derive(Debug)]
70pub(crate) struct UploadTexturePool {
71 state: Mutex<UploadTexturePoolState>,
72 retirement: Shared<Retirement>,
73}
74
75#[derive(Debug)]
76struct UploadTexturePoolState {
77 available: VecDeque<UploadTextureEntry>,
78 active_by_key: HashMap<TextureKey, usize>,
79 peak_active_by_key: HashMap<TextureKey, usize>,
80 created: u64,
81 reused: u64,
82 discarded: u64,
83 active: usize,
84 active_bytes: u64,
85 high_water_bytes: u64,
86 available_bytes: u64,
87 budget_bytes: u64,
88 checked_generation: u64,
90}
91
92impl UploadTexturePool {
93 pub(crate) fn new(retirement: Shared<Retirement>) -> Self {
94 Self {
95 retirement,
96 state: Mutex::new(UploadTexturePoolState {
97 available: VecDeque::new(),
98 active_by_key: HashMap::new(),
99 peak_active_by_key: HashMap::new(),
100 created: 0,
101 reused: 0,
102 discarded: 0,
103 active: 0,
104 active_bytes: 0,
105 high_water_bytes: 0,
106 available_bytes: 0,
107 budget_bytes: 128 * 1024 * 1024,
108 checked_generation: 0,
109 }),
110 }
111 }
112}
113
114#[derive(Debug)]
116pub struct UploadTexture {
117 pool: Shared<UploadTexturePool>,
118 entry: Option<UploadTextureEntry>,
119}
120
121const MAX_IDLE_UPLOAD_TEXTURES_HARD_LIMIT: usize = 64;
124
125#[derive(Debug)]
126pub(crate) struct TexturePool {
127 state: Mutex<TexturePoolState>,
128 retirement: Shared<Retirement>,
129}
130
131#[derive(Debug)]
133struct IdleTexture {
134 key: TextureKey,
135 texture: wgpu::Texture,
136 view: wgpu::TextureView,
139 second_chance: bool,
142 generation: u64,
144}
145
146#[derive(Debug)]
147struct TexturePoolState {
148 available: VecDeque<IdleTexture>,
149 created: u64,
150 reused: u64,
151 active: usize,
152 active_bytes: u64,
153 high_water_bytes: u64,
154 available_bytes: u64,
155 budget_bytes: u64,
156 evicted: u64,
157 checked_generation: u64,
159}
160
161impl Default for TexturePoolState {
162 fn default() -> Self {
163 Self {
164 available: VecDeque::new(),
165 created: 0,
166 reused: 0,
167 active: 0,
168 active_bytes: 0,
169 high_water_bytes: 0,
170 available_bytes: 0,
171 budget_bytes: 256 * 1024 * 1024,
172 evicted: 0,
173 checked_generation: 0,
174 }
175 }
176}
177
178#[derive(Debug)]
179pub(crate) struct TextureLease {
180 pool: Shared<TexturePool>,
181 key: TextureKey,
182 texture: Option<(wgpu::Texture, wgpu::TextureView)>,
183 generation: u64,
184}
185
186#[derive(Debug)]
187pub(crate) struct RecycleToken {
188 pool: Shared<TexturePool>,
189 key: TextureKey,
190 generation: u64,
191}
192
193const MAX_IDLE_READBACK_BUFFERS: usize = 3;
194
195#[derive(Debug)]
196pub(crate) struct ReadbackBufferPool {
197 state: Mutex<ReadbackBufferPoolState>,
198 retirement: Shared<Retirement>,
199}
200
201#[derive(Debug, Default)]
202struct ReadbackBufferPoolState {
203 available: Vec<(u64, wgpu::Buffer, u64)>,
205 checked_generation: u64,
207 created: u64,
208 reused: u64,
209 discarded: u64,
210 active: usize,
211 active_bytes: u64,
212 high_water_bytes: u64,
213 available_bytes: u64,
214}
215
216#[derive(Debug)]
217pub(crate) struct ReadbackBufferLease {
218 pool: Shared<ReadbackBufferPool>,
219 size: u64,
220 buffer: Option<wgpu::Buffer>,
221 generation: u64,
222}
223
224#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
226pub struct ReadbackBufferPoolStats {
227 pub created: u64,
229 pub reused: u64,
231 pub discarded: u64,
233 pub active: usize,
235 pub active_bytes: u64,
237 pub high_water_bytes: u64,
239 pub available: usize,
241 pub available_bytes: u64,
243}
244
245impl TexturePool {
246 pub(crate) fn new(retirement: Shared<Retirement>) -> Self {
247 Self {
248 state: Mutex::default(),
249 retirement,
250 }
251 }
252
253 pub(crate) fn acquire(
254 self: &Shared<Self>,
255 device: &wgpu::Device,
256 size: RenderSize,
257 format: wgpu::TextureFormat,
258 label: &'static str,
259 ) -> TextureLease {
260 let key = TextureKey { size, format };
261 let (texture, generation) = {
262 let mut state = self
263 .state
264 .lock()
265 .unwrap_or_else(std::sync::PoisonError::into_inner);
266 let untrusted = self.drop_untrusted(&mut state);
267 let index = state
268 .available
269 .iter()
270 .rposition(|available| available.key == key);
271 let reused = index
272 .and_then(|index| state.available.remove(index))
273 .map(|idle| ((idle.texture, idle.view), idle.generation));
274 if reused.is_some() {
275 state.reused = state.reused.saturating_add(1);
276 state.available_bytes = state.available_bytes.saturating_sub(key.byte_len());
277 }
278 drop(state);
279 self.retirement.retire(untrusted);
280 reused
281 }
282 .unwrap_or_else(|| {
283 let mut state = self
284 .state
285 .lock()
286 .unwrap_or_else(std::sync::PoisonError::into_inner);
287 state.created = state.created.saturating_add(1);
288 drop(state);
289 let generation = self.retirement.generation();
290 let texture = create_texture(device, size, format, label);
291 let view = default_view(&texture);
292 ((texture, view), generation)
293 });
294 {
295 let mut state = self
296 .state
297 .lock()
298 .unwrap_or_else(std::sync::PoisonError::into_inner);
299 state.active = state.active.saturating_add(1);
300 state.active_bytes = state.active_bytes.saturating_add(key.byte_len());
301 state.high_water_bytes = state.high_water_bytes.max(state.active_bytes);
302 }
303 TextureLease {
304 pool: Shared::clone(self),
305 key,
306 texture: Some(texture),
307 generation,
308 }
309 }
310
311 fn drop_untrusted(&self, state: &mut TexturePoolState) -> Vec<(Retired, u64)> {
314 let generation = self.retirement.generation();
315 if state.checked_generation == generation {
316 return Vec::new();
317 }
318 state.checked_generation = generation;
319 let (kept, untrusted): (VecDeque<_>, VecDeque<_>) = std::mem::take(&mut state.available)
320 .into_iter()
321 .partition(|idle| idle.generation == generation);
322 state.available = kept;
323 state.available_bytes = state.available.iter().map(|idle| idle.key.byte_len()).sum();
324 state.evicted = state
325 .evicted
326 .saturating_add(u64::try_from(untrusted.len()).unwrap_or(u64::MAX));
327 untrusted.into_iter().map(IdleTexture::retired).collect()
328 }
329
330 pub(crate) fn stats(&self) -> TexturePoolStats {
331 let state = self
332 .state
333 .lock()
334 .unwrap_or_else(std::sync::PoisonError::into_inner);
335 TexturePoolStats {
336 created: state.created,
337 reused: state.reused,
338 active: state.active,
339 active_bytes: state.active_bytes,
340 high_water_bytes: state.high_water_bytes,
341 available: state.available.len(),
342 available_bytes: state.available_bytes,
343 budget_bytes: state.budget_bytes,
344 evicted: state.evicted,
345 }
346 }
347
348 pub(crate) fn clear(&self) {
350 let mut state = self
351 .state
352 .lock()
353 .unwrap_or_else(std::sync::PoisonError::into_inner);
354 let idle = std::mem::take(&mut state.available);
355 state.available_bytes = 0;
356 drop(state);
357 self.retirement
358 .retire(idle.into_iter().map(IdleTexture::retired));
359 }
360
361 pub(crate) fn set_budget(&self, budget_bytes: u64) {
362 let mut state = self
363 .state
364 .lock()
365 .unwrap_or_else(std::sync::PoisonError::into_inner);
366 state.budget_bytes = budget_bytes;
367 let mut evicted = Vec::new();
368 while state.available_bytes > state.budget_bytes {
369 let Some(idle) = state.available.pop_front() else {
370 state.available_bytes = 0;
371 break;
372 };
373 state.available_bytes = state.available_bytes.saturating_sub(idle.key.byte_len());
374 state.evicted = state.evicted.saturating_add(1);
375 evicted.push(idle.retired());
376 }
377 drop(state);
378 self.retirement.retire(evicted);
379 }
380}
381
382impl IdleTexture {
383 fn retired(self) -> (Retired, u64) {
384 (Retired::Texture(self.texture), self.key.byte_len())
385 }
386}
387
388impl UploadTexturePool {
389 pub(crate) fn acquire(
390 self: &Shared<Self>,
391 device: &wgpu::Device,
392 size: RenderSize,
393 format: wgpu::TextureFormat,
394 label: &'static str,
395 ) -> UploadTexture {
396 let key = TextureKey { size, format };
397 let reused = {
398 let mut state = self
399 .state
400 .lock()
401 .unwrap_or_else(std::sync::PoisonError::into_inner);
402 let untrusted = self.drop_untrusted(&mut state);
403 let index = state.available.iter().position(|entry| entry.key == key);
404 let reused = index.and_then(|index| {
405 let entry = state.available.remove(index)?;
406 state.reused = state.reused.saturating_add(1);
407 state.available_bytes = state.available_bytes.saturating_sub(key.byte_len());
408 Some(entry)
409 });
410 drop(state);
411 self.retirement.retire(untrusted);
412 reused
413 };
414 let entry = reused.unwrap_or_else(|| {
415 let generation = self.retirement.generation();
416 let texture = create_upload_texture(device, size, format, label);
417 let view = default_view(&texture);
418 let mut state = self
419 .state
420 .lock()
421 .unwrap_or_else(std::sync::PoisonError::into_inner);
422 state.created = state.created.saturating_add(1);
423 UploadTextureEntry {
424 key,
425 texture,
426 view,
427 generation,
428 }
429 });
430 {
431 let mut state = self
432 .state
433 .lock()
434 .unwrap_or_else(std::sync::PoisonError::into_inner);
435 state.active = state.active.saturating_add(1);
436 state.active_bytes = state.active_bytes.saturating_add(key.byte_len());
437 state.high_water_bytes = state.high_water_bytes.max(state.active_bytes);
438 let active = state.active_by_key.entry(key).or_default();
439 *active = active.saturating_add(1);
440 let active = *active;
441 let peak = state.peak_active_by_key.entry(key).or_default();
442 *peak = (*peak).max(active);
443 }
444 UploadTexture {
445 pool: Shared::clone(self),
446 entry: Some(entry),
447 }
448 }
449
450 pub(crate) fn stats(&self) -> UploadTexturePoolStats {
451 let state = self
452 .state
453 .lock()
454 .unwrap_or_else(std::sync::PoisonError::into_inner);
455 UploadTexturePoolStats {
456 created: state.created,
457 reused: state.reused,
458 discarded: state.discarded,
459 active: state.active,
460 active_bytes: state.active_bytes,
461 high_water_bytes: state.high_water_bytes,
462 available: state.available.len(),
463 available_bytes: state.available_bytes,
464 budget_bytes: state.budget_bytes,
465 }
466 }
467
468 pub(crate) fn clear(&self) {
470 let mut state = self
471 .state
472 .lock()
473 .unwrap_or_else(std::sync::PoisonError::into_inner);
474 let discarded = u64::try_from(state.available.len()).unwrap_or(u64::MAX);
475 let idle = std::mem::take(&mut state.available);
476 state.discarded = state.discarded.saturating_add(discarded);
477 state.available_bytes = 0;
478 drop(state);
479 self.retirement
480 .retire(idle.into_iter().map(UploadTextureEntry::retired));
481 }
482
483 #[cfg(test)]
485 pub(crate) fn set_budget_bytes(&self, bytes: u64) {
486 self.state
487 .lock()
488 .unwrap_or_else(std::sync::PoisonError::into_inner)
489 .budget_bytes = bytes;
490 }
491
492 fn release(&self, entry: UploadTextureEntry) {
493 let mut retired = Vec::new();
494 self.keep_or_retire(entry, &mut retired);
495 self.retirement.retire(retired);
496 }
497
498 fn drop_untrusted(&self, state: &mut UploadTexturePoolState) -> Vec<(Retired, u64)> {
501 let generation = self.retirement.generation();
502 if state.checked_generation == generation {
503 return Vec::new();
504 }
505 state.checked_generation = generation;
506 let (kept, untrusted): (VecDeque<_>, VecDeque<_>) = std::mem::take(&mut state.available)
507 .into_iter()
508 .partition(|entry| entry.generation == generation);
509 state.available = kept;
510 state.available_bytes = state
511 .available
512 .iter()
513 .map(|entry| entry.key.byte_len())
514 .sum();
515 state.discarded = state
516 .discarded
517 .saturating_add(u64::try_from(untrusted.len()).unwrap_or(u64::MAX));
518 for entry in &untrusted {
519 prune_upload_texture_key(state, entry.key);
520 }
521 untrusted
522 .into_iter()
523 .map(UploadTextureEntry::retired)
524 .collect()
525 }
526
527 fn keep_or_retire(&self, entry: UploadTextureEntry, retired: &mut Vec<(Retired, u64)>) {
530 let bytes = entry.key.byte_len();
531 let mut state = self
532 .state
533 .lock()
534 .unwrap_or_else(std::sync::PoisonError::into_inner);
535 state.active = state.active.saturating_sub(1);
536 state.active_bytes = state.active_bytes.saturating_sub(bytes);
537 if let Some(active) = state.active_by_key.get_mut(&entry.key) {
538 *active = active.saturating_sub(1);
539 if *active == 0 {
540 state.active_by_key.remove(&entry.key);
541 }
542 }
543 if !self.retirement.trusts(entry.generation) {
544 state.discarded = state.discarded.saturating_add(1);
545 prune_upload_texture_key(&mut state, entry.key);
546 retired.push(entry.retired());
547 return;
548 }
549 let retention_target = state
550 .peak_active_by_key
551 .get(&entry.key)
552 .copied()
553 .unwrap_or(1);
554 let available_for_key = state
555 .available
556 .iter()
557 .filter(|available| available.key == entry.key)
558 .count();
559 if available_for_key >= retention_target {
560 state.discarded = state.discarded.saturating_add(1);
561 retired.push(entry.retired());
562 return;
563 }
564 if bytes > state.budget_bytes {
567 state.discarded = state.discarded.saturating_add(1);
568 prune_upload_texture_key(&mut state, entry.key);
569 retired.push(entry.retired());
570 return;
571 }
572 while state.available.len() >= MAX_IDLE_UPLOAD_TEXTURES_HARD_LIMIT
573 || state.available_bytes.saturating_add(bytes) > state.budget_bytes
574 {
575 let Some(discarded) = state.available.pop_front() else {
576 state.discarded = state.discarded.saturating_add(1);
577 prune_upload_texture_key(&mut state, entry.key);
578 retired.push(entry.retired());
579 return;
580 };
581 state.available_bytes = state
582 .available_bytes
583 .saturating_sub(discarded.key.byte_len());
584 state.discarded = state.discarded.saturating_add(1);
585 prune_upload_texture_key(&mut state, discarded.key);
586 retired.push(discarded.retired());
587 }
588 state.available.push_back(entry);
589 state.available_bytes = state.available_bytes.saturating_add(bytes);
590 }
591}
592
593impl UploadTextureEntry {
594 fn retired(self) -> (Retired, u64) {
595 (Retired::Texture(self.texture), self.key.byte_len())
596 }
597}
598
599fn prune_upload_texture_key(state: &mut UploadTexturePoolState, key: TextureKey) {
600 if !state.active_by_key.contains_key(&key)
601 && !state.available.iter().any(|entry| entry.key == key)
602 {
603 state.peak_active_by_key.remove(&key);
604 }
605}
606
607impl UploadTexture {
608 fn entry(&self) -> &UploadTextureEntry {
609 self.entry.as_ref().expect("live upload texture lease")
610 }
611
612 #[must_use]
614 #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
615 pub fn texture(&self) -> &wgpu::Texture {
616 &self.entry().texture
617 }
618
619 #[must_use]
621 #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
622 pub fn view(&self) -> &wgpu::TextureView {
623 &self.entry().view
624 }
625
626 #[must_use]
628 pub fn retained_bytes(&self) -> u64 {
629 self.entry().key.byte_len()
630 }
631}
632
633impl Drop for UploadTexture {
634 fn drop(&mut self) {
635 if let Some(entry) = self.entry.take() {
636 self.pool.release(entry);
637 }
638 }
639}
640
641impl ReadbackBufferPool {
642 pub(crate) fn new(retirement: Shared<Retirement>) -> Self {
643 Self {
644 state: Mutex::default(),
645 retirement,
646 }
647 }
648
649 pub(crate) fn acquire(
650 self: &Shared<Self>,
651 device: &wgpu::Device,
652 size: u64,
653 label: &'static str,
654 ) -> ReadbackBufferLease {
655 let reused = {
656 let mut state = self
657 .state
658 .lock()
659 .unwrap_or_else(std::sync::PoisonError::into_inner);
660 let untrusted = self.drop_untrusted(&mut state);
661 let index = state
662 .available
663 .iter()
664 .position(|(available_size, _, _)| *available_size == size);
665 let reused = index.map(|index| {
666 state.reused = state.reused.saturating_add(1);
667 state.available_bytes = state.available_bytes.saturating_sub(size);
668 let (_, buffer, generation) = state.available.remove(index);
669 (buffer, generation)
670 });
671 drop(state);
672 self.retirement.retire(untrusted);
673 reused
674 };
675 let (buffer, generation) = reused.unwrap_or_else(|| {
676 let mut state = self
677 .state
678 .lock()
679 .unwrap_or_else(std::sync::PoisonError::into_inner);
680 state.created = state.created.saturating_add(1);
681 drop(state);
682 let generation = self.retirement.generation();
683 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
684 label: Some(label),
685 size,
686 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
687 mapped_at_creation: false,
688 });
689 (buffer, generation)
690 });
691 {
692 let mut state = self
693 .state
694 .lock()
695 .unwrap_or_else(std::sync::PoisonError::into_inner);
696 state.active = state.active.saturating_add(1);
697 state.active_bytes = state.active_bytes.saturating_add(size);
698 state.high_water_bytes = state.high_water_bytes.max(state.active_bytes);
699 }
700 ReadbackBufferLease {
701 pool: Shared::clone(self),
702 size,
703 buffer: Some(buffer),
704 generation,
705 }
706 }
707
708 fn drop_untrusted(&self, state: &mut ReadbackBufferPoolState) -> Vec<(Retired, u64)> {
711 let generation = self.retirement.generation();
712 if state.checked_generation == generation {
713 return Vec::new();
714 }
715 state.checked_generation = generation;
716 let (kept, untrusted): (Vec<_>, Vec<_>) = std::mem::take(&mut state.available)
717 .into_iter()
718 .partition(|(_, _, created)| *created == generation);
719 state.available = kept;
720 state.available_bytes = state.available.iter().map(|(size, _, _)| *size).sum();
721 state.discarded = state
722 .discarded
723 .saturating_add(u64::try_from(untrusted.len()).unwrap_or(u64::MAX));
724 untrusted
725 .into_iter()
726 .map(|(size, buffer, _)| (Retired::Buffer(buffer), size))
727 .collect()
728 }
729
730 pub(crate) fn stats(&self) -> ReadbackBufferPoolStats {
731 let state = self
732 .state
733 .lock()
734 .unwrap_or_else(std::sync::PoisonError::into_inner);
735 ReadbackBufferPoolStats {
736 created: state.created,
737 reused: state.reused,
738 discarded: state.discarded,
739 active: state.active,
740 active_bytes: state.active_bytes,
741 high_water_bytes: state.high_water_bytes,
742 available: state.available.len(),
743 available_bytes: state.available_bytes,
744 }
745 }
746
747 pub(crate) fn clear(&self) {
749 let mut state = self
750 .state
751 .lock()
752 .unwrap_or_else(std::sync::PoisonError::into_inner);
753 let discarded = u64::try_from(state.available.len()).unwrap_or(u64::MAX);
754 let idle = std::mem::take(&mut state.available);
755 state.discarded = state.discarded.saturating_add(discarded);
756 state.available_bytes = 0;
757 drop(state);
758 self.retirement.retire(
759 idle.into_iter()
760 .map(|(size, buffer, _)| (Retired::Buffer(buffer), size)),
761 );
762 }
763
764 fn release(&self, size: u64, buffer: wgpu::Buffer, generation: u64) {
765 if !self.retirement.trusts(generation) {
766 self.discard(size, buffer);
767 return;
768 }
769 let mut state = self
770 .state
771 .lock()
772 .unwrap_or_else(std::sync::PoisonError::into_inner);
773 state.active = state.active.saturating_sub(1);
774 state.active_bytes = state.active_bytes.saturating_sub(size);
775 let mut retired = None;
776 if state.available.len() == MAX_IDLE_READBACK_BUFFERS {
777 let (discarded_size, discarded, _) = state.available.remove(0);
778 state.available_bytes = state.available_bytes.saturating_sub(discarded_size);
779 state.discarded = state.discarded.saturating_add(1);
780 retired = Some((Retired::Buffer(discarded), discarded_size));
781 }
782 state.available.push((size, buffer, generation));
783 state.available_bytes = state.available_bytes.saturating_add(size);
784 drop(state);
785 self.retirement.retire(retired);
786 }
787
788 fn discard(&self, size: u64, buffer: wgpu::Buffer) {
790 let mut state = self
791 .state
792 .lock()
793 .unwrap_or_else(std::sync::PoisonError::into_inner);
794 state.active = state.active.saturating_sub(1);
795 state.active_bytes = state.active_bytes.saturating_sub(size);
796 state.discarded = state.discarded.saturating_add(1);
797 drop(state);
798 self.retirement.retire([(Retired::Buffer(buffer), size)]);
799 }
800}
801
802impl ReadbackBufferLease {
803 pub(crate) fn buffer(&self) -> &wgpu::Buffer {
804 self.buffer.as_ref().expect("live readback buffer lease")
805 }
806
807 pub(crate) fn recycle(mut self) {
808 let buffer = self.buffer.take().expect("live readback buffer lease");
809 self.pool.release(self.size, buffer, self.generation);
810 }
811}
812
813impl Drop for ReadbackBufferLease {
814 fn drop(&mut self) {
815 if let Some(buffer) = self.buffer.take() {
816 self.pool.discard(self.size, buffer);
817 }
818 }
819}
820
821impl TextureLease {
822 pub(crate) fn texture(&self) -> &wgpu::Texture {
823 &self.texture.as_ref().expect("live texture lease").0
824 }
825
826 pub(crate) fn view(&self) -> &wgpu::TextureView {
828 &self.texture.as_ref().expect("live texture lease").1
829 }
830
831 pub(crate) fn into_parts(mut self) -> (wgpu::Texture, wgpu::TextureView, RecycleToken) {
832 let (texture, view) = self.texture.take().expect("live texture lease");
833 let token = RecycleToken {
834 pool: Shared::clone(&self.pool),
835 key: self.key,
836 generation: self.generation,
837 };
838 (texture, view, token)
839 }
840}
841
842impl Drop for TextureLease {
843 fn drop(&mut self) {
844 if let Some((texture, view)) = self.texture.take() {
845 self.pool.release(self.key, texture, view, self.generation);
846 }
847 }
848}
849
850impl RecycleToken {
851 pub(crate) fn recycle(self, texture: wgpu::Texture, view: wgpu::TextureView) {
852 self.pool.release(self.key, texture, view, self.generation);
853 }
854}
855
856impl TexturePool {
857 fn release(
872 &self,
873 key: TextureKey,
874 texture: wgpu::Texture,
875 view: wgpu::TextureView,
876 generation: u64,
877 ) {
878 let mut retired = Vec::new();
879 self.keep_or_retire(
880 IdleTexture {
881 key,
882 texture,
883 view,
884 second_chance: true,
885 generation,
886 },
887 &mut retired,
888 );
889 self.retirement.retire(retired);
890 }
891
892 fn keep_or_retire(&self, incoming: IdleTexture, retired: &mut Vec<(Retired, u64)>) {
893 let mut state = self
894 .state
895 .lock()
896 .unwrap_or_else(std::sync::PoisonError::into_inner);
897 let key = incoming.key;
898 let bytes = key.byte_len();
899 state.active = state.active.saturating_sub(1);
900 state.active_bytes = state.active_bytes.saturating_sub(bytes);
901 if bytes > state.budget_bytes || !self.retirement.trusts(incoming.generation) {
904 state.evicted = state.evicted.saturating_add(1);
905 retired.push(incoming.retired());
906 return;
907 }
908 let mut index = 0;
909 while state.available_bytes.saturating_add(bytes) > state.budget_bytes {
910 let Some(idle) = state.available.get_mut(index) else {
911 state.evicted = state.evicted.saturating_add(1);
914 retired.push(incoming.retired());
915 return;
916 };
917 if idle.key == key {
918 index += 1;
919 } else if idle.second_chance {
920 idle.second_chance = false;
921 index += 1;
922 } else if let Some(evicted) = state.available.remove(index) {
923 state.available_bytes =
924 state.available_bytes.saturating_sub(evicted.key.byte_len());
925 state.evicted = state.evicted.saturating_add(1);
926 retired.push(evicted.retired());
927 }
928 }
929 state.available.push_back(incoming);
930 state.available_bytes = state.available_bytes.saturating_add(bytes);
931 }
932}
933
934impl TextureKey {
935 fn byte_len(self) -> u64 {
936 texture_byte_len(self.size.width, self.size.height, self.format)
937 }
938}
939
940fn texture_byte_len(width: u32, height: u32, format: wgpu::TextureFormat) -> u64 {
941 let block = format.block_copy_size(None).unwrap_or(4);
942 u64::from(width)
943 .saturating_mul(u64::from(height))
944 .saturating_mul(u64::from(block))
945}
946
947#[cfg(test)]
948thread_local! {
949 pub(crate) static VIEWS_CREATED: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
952}
953
954pub(crate) fn default_view(texture: &wgpu::Texture) -> wgpu::TextureView {
957 #[cfg(test)]
958 VIEWS_CREATED.set(VIEWS_CREATED.get() + 1);
959 texture.create_view(&wgpu::TextureViewDescriptor::default())
960}
961
962#[cfg(test)]
963thread_local! {
964 pub(crate) static INVALID_TEXTURES: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
968}
969
970fn create_texture(
971 device: &wgpu::Device,
972 size: RenderSize,
973 format: wgpu::TextureFormat,
974 label: &'static str,
975) -> wgpu::Texture {
976 let usage = wgpu::TextureUsages::RENDER_ATTACHMENT
977 | wgpu::TextureUsages::TEXTURE_BINDING
978 | wgpu::TextureUsages::COPY_SRC
979 | wgpu::TextureUsages::COPY_DST;
981 #[cfg(test)]
982 let usage = if INVALID_TEXTURES.get() > 0 {
983 INVALID_TEXTURES.set(INVALID_TEXTURES.get() - 1);
984 wgpu::TextureUsages::empty()
985 } else {
986 usage
987 };
988 device.create_texture(&wgpu::TextureDescriptor {
989 label: Some(label),
990 size: wgpu::Extent3d {
991 width: size.width,
992 height: size.height,
993 depth_or_array_layers: 1,
994 },
995 mip_level_count: 1,
996 sample_count: 1,
997 dimension: wgpu::TextureDimension::D2,
998 format,
999 usage,
1000 view_formats: &[],
1001 })
1002}
1003
1004fn create_upload_texture(
1005 device: &wgpu::Device,
1006 size: RenderSize,
1007 format: wgpu::TextureFormat,
1008 label: &'static str,
1009) -> wgpu::Texture {
1010 device.create_texture(&wgpu::TextureDescriptor {
1011 label: Some(label),
1012 size: wgpu::Extent3d {
1013 width: size.width,
1014 height: size.height,
1015 depth_or_array_layers: 1,
1016 },
1017 mip_level_count: 1,
1018 sample_count: 1,
1019 dimension: wgpu::TextureDimension::D2,
1020 format,
1021 usage: upload_texture_usage(format),
1022 view_formats: &[],
1023 })
1024}
1025
1026pub(crate) fn upload_texture_usage(format: wgpu::TextureFormat) -> wgpu::TextureUsages {
1037 let base = wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING;
1038 if format == wgpu::TextureFormat::Rgba8Unorm {
1039 base | wgpu::TextureUsages::RENDER_ATTACHMENT
1040 } else {
1041 base
1042 }
1043}