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