1use std::collections::HashMap;
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum TextureFormat {
17 R8Unorm,
18 R16Float,
19 R32Float,
20 Rg8Unorm,
21 Rg16Float,
22 Rg32Float,
23 Rgba8Unorm,
24 Rgba8Srgb,
25 Rgba16Float,
26 Rgba32Float,
27 Bgra8Unorm,
28 Bgra8Srgb,
29 Depth16Unorm,
30 Depth24PlusStencil8,
31 Depth32Float,
32 R11G11B10Float,
33 Rgb10A2Unorm,
34 Bc1Unorm,
35 Bc3Unorm,
36 Bc5Unorm,
37 Bc7Unorm,
38}
39
40impl TextureFormat {
41 pub fn bytes_per_pixel(self) -> u32 {
43 match self {
44 Self::R8Unorm => 1,
45 Self::R16Float => 2,
46 Self::R32Float => 4,
47 Self::Rg8Unorm => 2,
48 Self::Rg16Float => 4,
49 Self::Rg32Float => 8,
50 Self::Rgba8Unorm | Self::Rgba8Srgb => 4,
51 Self::Rgba16Float => 8,
52 Self::Rgba32Float => 16,
53 Self::Bgra8Unorm | Self::Bgra8Srgb => 4,
54 Self::Depth16Unorm => 2,
55 Self::Depth24PlusStencil8 => 4,
56 Self::Depth32Float => 4,
57 Self::R11G11B10Float => 4,
58 Self::Rgb10A2Unorm => 4,
59 Self::Bc1Unorm => 1, Self::Bc3Unorm | Self::Bc5Unorm | Self::Bc7Unorm => 1,
61 }
62 }
63
64 pub fn is_depth(self) -> bool {
66 matches!(
67 self,
68 Self::Depth16Unorm | Self::Depth24PlusStencil8 | Self::Depth32Float
69 )
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum UsageFlags {
76 RenderTarget,
77 DepthStencil,
78 ShaderRead,
79 ShaderWrite,
80 CopySource,
81 CopyDest,
82 StorageBuffer,
83 UniformBuffer,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq)]
88pub enum SizePolicy {
89 Absolute { width: u32, height: u32 },
91 Relative { width_scale: f32, height_scale: f32 },
93}
94
95impl SizePolicy {
96 pub fn resolve(self, backbuffer_width: u32, backbuffer_height: u32) -> (u32, u32) {
98 match self {
99 Self::Absolute { width, height } => (width, height),
100 Self::Relative {
101 width_scale,
102 height_scale,
103 } => {
104 let w = ((backbuffer_width as f32) * width_scale).max(1.0) as u32;
105 let h = ((backbuffer_height as f32) * height_scale).max(1.0) as u32;
106 (w, h)
107 }
108 }
109 }
110}
111
112#[derive(Debug, Clone)]
118pub struct ResourceDescriptor {
119 pub name: String,
120 pub size: SizePolicy,
121 pub format: TextureFormat,
122 pub mip_levels: u32,
123 pub array_layers: u32,
124 pub sample_count: u32,
125 pub usages: Vec<UsageFlags>,
126}
127
128impl ResourceDescriptor {
129 pub fn new(name: &str, format: TextureFormat) -> Self {
130 Self {
131 name: name.to_string(),
132 size: SizePolicy::Relative {
133 width_scale: 1.0,
134 height_scale: 1.0,
135 },
136 format,
137 mip_levels: 1,
138 array_layers: 1,
139 sample_count: 1,
140 usages: vec![UsageFlags::RenderTarget, UsageFlags::ShaderRead],
141 }
142 }
143
144 pub fn with_size(mut self, size: SizePolicy) -> Self {
145 self.size = size;
146 self
147 }
148
149 pub fn with_mip_levels(mut self, levels: u32) -> Self {
150 self.mip_levels = levels;
151 self
152 }
153
154 pub fn with_array_layers(mut self, layers: u32) -> Self {
155 self.array_layers = layers;
156 self
157 }
158
159 pub fn with_sample_count(mut self, count: u32) -> Self {
160 self.sample_count = count;
161 self
162 }
163
164 pub fn with_usages(mut self, usages: Vec<UsageFlags>) -> Self {
165 self.usages = usages;
166 self
167 }
168
169 pub fn estimated_bytes(&self, bb_w: u32, bb_h: u32) -> u64 {
171 let (w, h) = self.size.resolve(bb_w, bb_h);
172 let bpp = self.format.bytes_per_pixel() as u64;
173 let base = (w as u64) * (h as u64) * bpp * (self.array_layers as u64) * (self.sample_count as u64);
174 if self.mip_levels > 1 {
176 let mut total: u64 = 0;
177 let mut mw = w as u64;
178 let mut mh = h as u64;
179 for _ in 0..self.mip_levels {
180 total += mw * mh * bpp * (self.array_layers as u64) * (self.sample_count as u64);
181 mw = (mw / 2).max(1);
182 mh = (mh / 2).max(1);
183 }
184 total
185 } else {
186 base
187 }
188 }
189
190 pub fn is_compatible_with(&self, other: &ResourceDescriptor, bb_w: u32, bb_h: u32) -> bool {
192 let (sw, sh) = self.size.resolve(bb_w, bb_h);
193 let (ow, oh) = other.size.resolve(bb_w, bb_h);
194 sw == ow
195 && sh == oh
196 && self.format == other.format
197 && self.mip_levels == other.mip_levels
198 && self.array_layers == other.array_layers
199 && self.sample_count == other.sample_count
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
209pub struct ResourceHandle {
210 pub index: u32,
211 pub version: u32,
212}
213
214impl ResourceHandle {
215 pub fn new(index: u32, version: u32) -> Self {
216 Self { index, version }
217 }
218
219 pub fn next_version(self) -> Self {
220 Self {
221 index: self.index,
222 version: self.version + 1,
223 }
224 }
225}
226
227impl fmt::Display for ResourceHandle {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 write!(f, "Res({}v{})", self.index, self.version)
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum ResourceLifetime {
240 Transient,
242 Imported,
244}
245
246#[derive(Debug, Clone)]
248pub struct PhysicalResource {
249 pub id: u64,
250 pub descriptor: ResourceDescriptor,
251 pub lifetime: ResourceLifetime,
252 pub last_used_frame: u64,
254 pub first_write_pass: Option<usize>,
256 pub last_read_pass: Option<usize>,
258}
259
260impl PhysicalResource {
261 pub fn new(id: u64, descriptor: ResourceDescriptor, lifetime: ResourceLifetime) -> Self {
262 Self {
263 id,
264 descriptor,
265 lifetime,
266 last_used_frame: 0,
267 first_write_pass: None,
268 last_read_pass: None,
269 }
270 }
271
272 pub fn is_alive_at(&self, pass_idx: usize) -> bool {
274 let first = self.first_write_pass.unwrap_or(usize::MAX);
275 let last = self.last_read_pass.unwrap_or(0);
276 pass_idx >= first && pass_idx <= last
277 }
278}
279
280#[derive(Debug, Clone)]
287pub struct ResourceVersionChain {
288 pub handle: ResourceHandle,
289 pub descriptor: ResourceDescriptor,
290 pub lifetime: ResourceLifetime,
291 pub versions: Vec<(u32, String)>,
293 pub readers: HashMap<u32, Vec<String>>,
295}
296
297impl ResourceVersionChain {
298 pub fn new(handle: ResourceHandle, descriptor: ResourceDescriptor, lifetime: ResourceLifetime) -> Self {
299 Self {
300 handle,
301 descriptor,
302 lifetime,
303 versions: Vec::new(),
304 readers: HashMap::new(),
305 }
306 }
307
308 pub fn record_write(&mut self, pass_name: &str) -> u32 {
310 let ver = if let Some((last, _)) = self.versions.last() {
311 last + 1
312 } else {
313 0
314 };
315 self.versions.push((ver, pass_name.to_string()));
316 ver
317 }
318
319 pub fn record_read(&mut self, version: u32, pass_name: &str) {
321 self.readers
322 .entry(version)
323 .or_default()
324 .push(pass_name.to_string());
325 }
326
327 pub fn current_version(&self) -> u32 {
329 self.versions.last().map(|(v, _)| *v).unwrap_or(0)
330 }
331
332 pub fn has_raw_hazard(&self) -> bool {
335 for (ver, _writer) in &self.versions {
336 if *ver == 0 {
337 continue;
338 }
339 let prev = ver - 1;
340 if let Some(readers) = self.readers.get(&prev) {
341 if !readers.is_empty() {
342 return true;
345 }
346 }
347 }
348 false
349 }
350}
351
352pub struct ResourcePool {
359 resources: Vec<PhysicalResource>,
360 next_id: u64,
361 name_map: HashMap<String, usize>,
363 free_list: Vec<usize>,
365 current_frame: u64,
367 pub max_idle_frames: u64,
369 version_chains: HashMap<u32, ResourceVersionChain>,
371 alias_groups: Vec<Vec<usize>>,
373}
374
375impl ResourcePool {
376 pub fn new() -> Self {
377 Self {
378 resources: Vec::new(),
379 next_id: 1,
380 name_map: HashMap::new(),
381 free_list: Vec::new(),
382 current_frame: 0,
383 max_idle_frames: 3,
384 version_chains: HashMap::new(),
385 alias_groups: Vec::new(),
386 }
387 }
388
389 pub fn begin_frame(&mut self) {
391 self.current_frame += 1;
392 self.version_chains.clear();
393 self.alias_groups.clear();
394
395 let mut to_free = Vec::new();
397 for (i, r) in self.resources.iter().enumerate() {
398 if r.lifetime == ResourceLifetime::Transient {
399 to_free.push(i);
400 }
401 }
402 for idx in to_free {
403 if !self.free_list.contains(&idx) {
404 self.free_list.push(idx);
405 }
406 }
407
408 let max_idle = self.max_idle_frames;
410 let cur = self.current_frame;
411 let mut evicted = Vec::new();
412 for &idx in &self.free_list {
413 if cur.saturating_sub(self.resources[idx].last_used_frame) > max_idle {
414 evicted.push(idx);
415 }
416 }
417 for idx in &evicted {
418 self.free_list.retain(|&i| i != *idx);
419 }
420 for idx in evicted {
422 let name = self.resources[idx].descriptor.name.clone();
423 self.name_map.remove(&name);
424 }
425
426 for r in &mut self.resources {
428 r.first_write_pass = None;
429 r.last_read_pass = None;
430 }
431 }
432
433 pub fn end_frame(&self) -> PoolFrameStats {
435 let active = self.resources.len() - self.free_list.len();
436 let free = self.free_list.len();
437 let total_bytes: u64 = self
438 .resources
439 .iter()
440 .enumerate()
441 .filter(|(i, _)| !self.free_list.contains(i))
442 .map(|(_, r)| r.descriptor.estimated_bytes(1920, 1080))
443 .sum();
444 PoolFrameStats {
445 active_resources: active,
446 free_resources: free,
447 total_resources: self.resources.len(),
448 estimated_memory_bytes: total_bytes,
449 alias_groups: self.alias_groups.len(),
450 }
451 }
452
453 pub fn acquire(
455 &mut self,
456 descriptor: ResourceDescriptor,
457 lifetime: ResourceLifetime,
458 bb_w: u32,
459 bb_h: u32,
460 ) -> ResourceHandle {
461 if let Some(&idx) = self.name_map.get(&descriptor.name) {
463 self.resources[idx].last_used_frame = self.current_frame;
464 self.free_list.retain(|&i| i != idx);
465 return ResourceHandle::new(idx as u32, 0);
466 }
467
468 let compatible = self.free_list.iter().position(|&idx| {
470 self.resources[idx]
471 .descriptor
472 .is_compatible_with(&descriptor, bb_w, bb_h)
473 });
474
475 if let Some(free_pos) = compatible {
476 let idx = self.free_list.remove(free_pos);
477 self.resources[idx].descriptor.name = descriptor.name.clone();
478 self.resources[idx].last_used_frame = self.current_frame;
479 self.name_map.insert(descriptor.name, idx);
480 return ResourceHandle::new(idx as u32, 0);
481 }
482
483 let id = self.next_id;
485 self.next_id += 1;
486 let idx = self.resources.len();
487 let name = descriptor.name.clone();
488 self.resources
489 .push(PhysicalResource::new(id, descriptor, lifetime));
490 self.resources[idx].last_used_frame = self.current_frame;
491 self.name_map.insert(name, idx);
492 ResourceHandle::new(idx as u32, 0)
493 }
494
495 pub fn release(&mut self, handle: ResourceHandle) {
497 let idx = handle.index as usize;
498 if idx < self.resources.len() && !self.free_list.contains(&idx) {
499 self.free_list.push(idx);
500 }
501 }
502
503 pub fn record_write(&mut self, handle: ResourceHandle, pass_idx: usize, pass_name: &str) {
505 let idx = handle.index as usize;
506 if idx < self.resources.len() {
507 let r = &mut self.resources[idx];
508 if r.first_write_pass.is_none() {
509 r.first_write_pass = Some(pass_idx);
510 }
511 r.last_read_pass = Some(r.last_read_pass.map_or(pass_idx, |v| v.max(pass_idx)));
512 }
513
514 let chain = self
515 .version_chains
516 .entry(handle.index)
517 .or_insert_with(|| {
518 let desc = self.resources[idx].descriptor.clone();
519 let lt = self.resources[idx].lifetime;
520 ResourceVersionChain::new(handle, desc, lt)
521 });
522 chain.record_write(pass_name);
523 }
524
525 pub fn record_read(&mut self, handle: ResourceHandle, pass_idx: usize, _pass_name: &str) {
527 let idx = handle.index as usize;
528 if idx < self.resources.len() {
529 let r = &mut self.resources[idx];
530 r.last_read_pass = Some(r.last_read_pass.map_or(pass_idx, |v| v.max(pass_idx)));
531 }
532
533 if let Some(chain) = self.version_chains.get_mut(&handle.index) {
534 let ver = chain.current_version();
535 chain.record_read(ver, _pass_name);
536 }
537 }
538
539 pub fn descriptor(&self, handle: ResourceHandle) -> Option<&ResourceDescriptor> {
541 self.resources
542 .get(handle.index as usize)
543 .map(|r| &r.descriptor)
544 }
545
546 pub fn physical(&self, handle: ResourceHandle) -> Option<&PhysicalResource> {
548 self.resources.get(handle.index as usize)
549 }
550
551 pub fn compute_aliasing(&mut self, total_passes: usize) -> &[Vec<usize>] {
553 self.alias_groups.clear();
554
555 let transient_indices: Vec<usize> = self
556 .resources
557 .iter()
558 .enumerate()
559 .filter(|(i, r)| r.lifetime == ResourceLifetime::Transient && !self.free_list.contains(i))
560 .map(|(i, _)| i)
561 .collect();
562
563 let mut assigned: Vec<bool> = vec![false; transient_indices.len()];
565
566 for (i, &idx_a) in transient_indices.iter().enumerate() {
567 if assigned[i] {
568 continue;
569 }
570 let mut group = vec![idx_a];
571 assigned[i] = true;
572
573 for (j, &idx_b) in transient_indices.iter().enumerate().skip(i + 1) {
574 if assigned[j] {
575 continue;
576 }
577 let overlaps = group.iter().any(|&g| {
579 self.lifetimes_overlap(g, idx_b, total_passes)
580 });
581 if !overlaps {
582 group.push(idx_b);
583 assigned[j] = true;
584 }
585 }
586
587 if group.len() > 1 {
588 self.alias_groups.push(group);
589 }
590 }
591
592 &self.alias_groups
593 }
594
595 fn lifetimes_overlap(&self, a: usize, b: usize, _total: usize) -> bool {
596 let ra = &self.resources[a];
597 let rb = &self.resources[b];
598 let a_start = ra.first_write_pass.unwrap_or(0);
599 let a_end = ra.last_read_pass.unwrap_or(0);
600 let b_start = rb.first_write_pass.unwrap_or(0);
601 let b_end = rb.last_read_pass.unwrap_or(0);
602 a_start <= b_end && b_start <= a_end
603 }
604
605 pub fn detect_raw_hazards(&self) -> Vec<(String, u32)> {
607 let mut hazards = Vec::new();
608 for (_, chain) in &self.version_chains {
609 if chain.has_raw_hazard() {
610 hazards.push((chain.descriptor.name.clone(), chain.handle.index));
611 }
612 }
613 hazards
614 }
615
616 pub fn estimate_memory_budget(&self, bb_w: u32, bb_h: u32) -> MemoryBudget {
618 let mut total = 0u64;
619 let mut transient = 0u64;
620 let mut imported = 0u64;
621 let mut peak = 0u64;
622
623 let max_pass = self
625 .resources
626 .iter()
627 .filter_map(|r| r.last_read_pass)
628 .max()
629 .unwrap_or(0);
630
631 for pass_idx in 0..=max_pass {
632 let mut frame_mem = 0u64;
633 for (i, r) in self.resources.iter().enumerate() {
634 if self.free_list.contains(&i) {
635 continue;
636 }
637 if r.is_alive_at(pass_idx) {
638 frame_mem += r.descriptor.estimated_bytes(bb_w, bb_h);
639 }
640 }
641 peak = peak.max(frame_mem);
642 }
643
644 for (i, r) in self.resources.iter().enumerate() {
645 if self.free_list.contains(&i) {
646 continue;
647 }
648 let bytes = r.descriptor.estimated_bytes(bb_w, bb_h);
649 total += bytes;
650 match r.lifetime {
651 ResourceLifetime::Transient => transient += bytes,
652 ResourceLifetime::Imported => imported += bytes,
653 }
654 }
655
656 MemoryBudget {
657 total_bytes: total,
658 transient_bytes: transient,
659 imported_bytes: imported,
660 peak_frame_bytes: peak,
661 resource_count: self.resources.len() - self.free_list.len(),
662 }
663 }
664
665 pub fn total_slots(&self) -> usize {
667 self.resources.len()
668 }
669
670 pub fn active_count(&self) -> usize {
672 self.resources.len() - self.free_list.len()
673 }
674
675 pub fn version_chain(&self, handle: ResourceHandle) -> Option<&ResourceVersionChain> {
677 self.version_chains.get(&handle.index)
678 }
679}
680
681impl Default for ResourcePool {
682 fn default() -> Self {
683 Self::new()
684 }
685}
686
687#[derive(Debug, Clone)]
693pub struct ImportedResource {
694 pub name: String,
695 pub descriptor: ResourceDescriptor,
696 pub external_id: u64,
698}
699
700impl ImportedResource {
701 pub fn new(name: &str, descriptor: ResourceDescriptor, external_id: u64) -> Self {
702 Self {
703 name: name.to_string(),
704 descriptor,
705 external_id,
706 }
707 }
708}
709
710#[derive(Debug, Clone)]
716pub struct PoolFrameStats {
717 pub active_resources: usize,
718 pub free_resources: usize,
719 pub total_resources: usize,
720 pub estimated_memory_bytes: u64,
721 pub alias_groups: usize,
722}
723
724impl fmt::Display for PoolFrameStats {
725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726 write!(
727 f,
728 "Pool: {} active, {} free, {} total, {:.2} MB, {} alias groups",
729 self.active_resources,
730 self.free_resources,
731 self.total_resources,
732 self.estimated_memory_bytes as f64 / (1024.0 * 1024.0),
733 self.alias_groups,
734 )
735 }
736}
737
738#[derive(Debug, Clone)]
740pub struct MemoryBudget {
741 pub total_bytes: u64,
742 pub transient_bytes: u64,
743 pub imported_bytes: u64,
744 pub peak_frame_bytes: u64,
745 pub resource_count: usize,
746}
747
748impl MemoryBudget {
749 pub fn total_mb(&self) -> f64 {
750 self.total_bytes as f64 / (1024.0 * 1024.0)
751 }
752
753 pub fn peak_mb(&self) -> f64 {
754 self.peak_frame_bytes as f64 / (1024.0 * 1024.0)
755 }
756}
757
758impl fmt::Display for MemoryBudget {
759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760 write!(
761 f,
762 "Budget: {:.2} MB total ({:.2} MB transient, {:.2} MB imported), peak {:.2} MB, {} resources",
763 self.total_mb(),
764 self.transient_bytes as f64 / (1024.0 * 1024.0),
765 self.imported_bytes as f64 / (1024.0 * 1024.0),
766 self.peak_mb(),
767 self.resource_count,
768 )
769 }
770}
771
772#[derive(Debug, Clone)]
780pub struct TransientResource {
781 pub handle: ResourceHandle,
782 pub descriptor: ResourceDescriptor,
783}
784
785impl TransientResource {
786 pub fn new(handle: ResourceHandle, descriptor: ResourceDescriptor) -> Self {
787 Self { handle, descriptor }
788 }
789
790 pub fn name(&self) -> &str {
791 &self.descriptor.name
792 }
793
794 pub fn format(&self) -> TextureFormat {
795 self.descriptor.format
796 }
797
798 pub fn estimated_bytes(&self, bb_w: u32, bb_h: u32) -> u64 {
799 self.descriptor.estimated_bytes(bb_w, bb_h)
800 }
801}
802
803pub struct ResourceTable {
810 entries: Vec<ResourceTableEntry>,
811 name_to_index: HashMap<String, usize>,
812}
813
814#[derive(Debug, Clone)]
815pub struct ResourceTableEntry {
816 pub name: String,
817 pub descriptor: ResourceDescriptor,
818 pub handle: ResourceHandle,
819 pub lifetime: ResourceLifetime,
820 pub writers: Vec<String>,
822 pub readers: Vec<String>,
824}
825
826impl ResourceTable {
827 pub fn new() -> Self {
828 Self {
829 entries: Vec::new(),
830 name_to_index: HashMap::new(),
831 }
832 }
833
834 pub fn declare_transient(&mut self, descriptor: ResourceDescriptor) -> ResourceHandle {
836 let name = descriptor.name.clone();
837 if let Some(&idx) = self.name_to_index.get(&name) {
838 return self.entries[idx].handle;
839 }
840 let idx = self.entries.len();
841 let handle = ResourceHandle::new(idx as u32, 0);
842 self.entries.push(ResourceTableEntry {
843 name: name.clone(),
844 descriptor,
845 handle,
846 lifetime: ResourceLifetime::Transient,
847 writers: Vec::new(),
848 readers: Vec::new(),
849 });
850 self.name_to_index.insert(name, idx);
851 handle
852 }
853
854 pub fn declare_imported(&mut self, descriptor: ResourceDescriptor) -> ResourceHandle {
856 let name = descriptor.name.clone();
857 if let Some(&idx) = self.name_to_index.get(&name) {
858 return self.entries[idx].handle;
859 }
860 let idx = self.entries.len();
861 let handle = ResourceHandle::new(idx as u32, 0);
862 self.entries.push(ResourceTableEntry {
863 name: name.clone(),
864 descriptor,
865 handle,
866 lifetime: ResourceLifetime::Imported,
867 writers: Vec::new(),
868 readers: Vec::new(),
869 });
870 self.name_to_index.insert(name, idx);
871 handle
872 }
873
874 pub fn add_writer(&mut self, handle: ResourceHandle, pass_name: &str) {
876 if let Some(entry) = self.entries.get_mut(handle.index as usize) {
877 if !entry.writers.contains(&pass_name.to_string()) {
878 entry.writers.push(pass_name.to_string());
879 }
880 }
881 }
882
883 pub fn add_reader(&mut self, handle: ResourceHandle, pass_name: &str) {
885 if let Some(entry) = self.entries.get_mut(handle.index as usize) {
886 if !entry.readers.contains(&pass_name.to_string()) {
887 entry.readers.push(pass_name.to_string());
888 }
889 }
890 }
891
892 pub fn lookup(&self, name: &str) -> Option<ResourceHandle> {
894 self.name_to_index
895 .get(name)
896 .map(|&idx| self.entries[idx].handle)
897 }
898
899 pub fn entry(&self, handle: ResourceHandle) -> Option<&ResourceTableEntry> {
901 self.entries.get(handle.index as usize)
902 }
903
904 pub fn entries(&self) -> &[ResourceTableEntry] {
906 &self.entries
907 }
908
909 pub fn find_dangling(&self) -> Vec<DanglingResource> {
911 let mut result = Vec::new();
912 for entry in &self.entries {
913 if entry.writers.is_empty() && entry.lifetime == ResourceLifetime::Transient {
914 result.push(DanglingResource {
915 name: entry.name.clone(),
916 kind: DanglingKind::NeverWritten,
917 });
918 }
919 if entry.readers.is_empty() && entry.lifetime == ResourceLifetime::Transient {
920 result.push(DanglingResource {
921 name: entry.name.clone(),
922 kind: DanglingKind::NeverRead,
923 });
924 }
925 }
926 result
927 }
928
929 pub fn len(&self) -> usize {
930 self.entries.len()
931 }
932
933 pub fn is_empty(&self) -> bool {
934 self.entries.is_empty()
935 }
936}
937
938impl Default for ResourceTable {
939 fn default() -> Self {
940 Self::new()
941 }
942}
943
944#[derive(Debug, Clone)]
946pub struct DanglingResource {
947 pub name: String,
948 pub kind: DanglingKind,
949}
950
951#[derive(Debug, Clone, Copy, PartialEq, Eq)]
952pub enum DanglingKind {
953 NeverWritten,
954 NeverRead,
955}
956
957impl fmt::Display for DanglingResource {
958 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
959 match self.kind {
960 DanglingKind::NeverWritten => write!(f, "'{}' is never written", self.name),
961 DanglingKind::NeverRead => write!(f, "'{}' is never read", self.name),
962 }
963 }
964}
965
966#[cfg(test)]
971mod tests {
972 use super::*;
973
974 fn make_desc(name: &str) -> ResourceDescriptor {
975 ResourceDescriptor::new(name, TextureFormat::Rgba16Float)
976 }
977
978 #[test]
979 fn test_pool_acquire_release() {
980 let mut pool = ResourcePool::new();
981 pool.begin_frame();
982 let h = pool.acquire(make_desc("color"), ResourceLifetime::Transient, 1920, 1080);
983 assert_eq!(h.index, 0);
984 assert_eq!(pool.active_count(), 1);
985 pool.release(h);
986 assert_eq!(pool.active_count(), 0);
987 }
988
989 #[test]
990 fn test_pool_reuse() {
991 let mut pool = ResourcePool::new();
992 pool.begin_frame();
993 let h1 = pool.acquire(make_desc("a"), ResourceLifetime::Transient, 1920, 1080);
994 pool.release(h1);
995 let h2 = pool.acquire(make_desc("b"), ResourceLifetime::Transient, 1920, 1080);
996 assert_eq!(h1.index, h2.index);
998 }
999
1000 #[test]
1001 fn test_memory_budget() {
1002 let mut pool = ResourcePool::new();
1003 pool.begin_frame();
1004 let _h = pool.acquire(make_desc("color"), ResourceLifetime::Transient, 1920, 1080);
1005 let budget = pool.estimate_memory_budget(1920, 1080);
1006 assert!(budget.total_bytes > 0);
1007 assert!(budget.transient_bytes > 0);
1008 assert_eq!(budget.imported_bytes, 0);
1009 }
1010
1011 #[test]
1012 fn test_resource_table_dangling() {
1013 let mut table = ResourceTable::new();
1014 let h = table.declare_transient(make_desc("unused"));
1015 let dangling = table.find_dangling();
1017 assert_eq!(dangling.len(), 2); table.add_writer(h, "some_pass");
1020 let dangling = table.find_dangling();
1021 assert_eq!(dangling.len(), 1); }
1023
1024 #[test]
1025 fn test_version_chain_hazard() {
1026 let handle = ResourceHandle::new(0, 0);
1027 let desc = make_desc("test");
1028 let mut chain = ResourceVersionChain::new(handle, desc, ResourceLifetime::Transient);
1029 let v0 = chain.record_write("pass_a");
1030 chain.record_read(v0, "pass_b");
1031 let _v1 = chain.record_write("pass_c");
1032 assert!(chain.has_raw_hazard());
1034 }
1035
1036 #[test]
1037 fn test_size_policy_resolve() {
1038 let abs = SizePolicy::Absolute {
1039 width: 512,
1040 height: 256,
1041 };
1042 assert_eq!(abs.resolve(1920, 1080), (512, 256));
1043
1044 let rel = SizePolicy::Relative {
1045 width_scale: 0.5,
1046 height_scale: 0.25,
1047 };
1048 assert_eq!(rel.resolve(1920, 1080), (960, 270));
1049 }
1050
1051 #[test]
1052 fn test_descriptor_estimated_bytes() {
1053 let desc = ResourceDescriptor::new("color", TextureFormat::Rgba16Float)
1054 .with_size(SizePolicy::Absolute {
1055 width: 1920,
1056 height: 1080,
1057 });
1058 let bytes = desc.estimated_bytes(1920, 1080);
1059 assert_eq!(bytes, 1920 * 1080 * 8);
1061 }
1062
1063 #[test]
1064 fn test_descriptor_mip_chain_bytes() {
1065 let desc = ResourceDescriptor::new("color", TextureFormat::Rgba8Unorm)
1066 .with_size(SizePolicy::Absolute {
1067 width: 256,
1068 height: 256,
1069 })
1070 .with_mip_levels(3);
1071 let bytes = desc.estimated_bytes(256, 256);
1072 assert_eq!(bytes, 262144 + 65536 + 16384);
1074 }
1075}