1pub struct GroupLayout<'a> {
3 pub group: u32,
4 pub layout: &'a wgpu::BindGroupLayout,
5}
6
7pub struct OwnedGroupLayout {
10 pub group: u32,
11 pub layout: wgpu::BindGroupLayout,
12}
13
14pub fn assemble_bind_group_layouts<'a>(
26 label: Option<&str>,
27 slots: Vec<GroupLayout<'a>>,
28) -> Vec<Option<&'a wgpu::BindGroupLayout>> {
29 if slots.is_empty() {
30 return Vec::new();
31 }
32
33 let max_group = slots.iter().map(|s| s.group).max().unwrap();
34 let mut assembled: Vec<Option<&wgpu::BindGroupLayout>> = vec![None; (max_group + 1) as usize];
35
36 for GroupLayout { group, layout } in slots {
37 let slot = &mut assembled[group as usize];
38 if slot.is_some() {
39 panic!(
40 "bind group {group} assigned more than once building pipeline layout{}",
41 label.map(|l| format!(" '{l}'")).unwrap_or_default()
42 );
43 }
44 *slot = Some(layout);
45 }
46
47 for (i, slot) in assembled.iter().enumerate() {
48 if slot.is_none() {
49 panic!(
50 "bind group {i} has no layout assigned building pipeline layout{} (groups must be contiguous from 0)",
51 label.map(|l| format!(" '{l}'")).unwrap_or_default()
52 );
53 }
54 }
55
56 assembled
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use crate::wgpu::test_util::with_device;
63
64 fn empty_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
65 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
66 label: None,
67 entries: &[],
68 })
69 }
70
71 #[test]
72 fn no_slots_at_all_means_zero_bind_groups_not_a_missing_group_zero() {
73 let assembled = assemble_bind_group_layouts(None, vec![]);
79 assert!(assembled.is_empty());
80 }
81
82 #[test]
83 fn slots_out_of_order_still_assemble_into_group_order() {
84 with_device!(device, _queue, {
85 let a = empty_layout(&device);
86 let b = empty_layout(&device);
87 let c = empty_layout(&device);
88
89 let assembled = assemble_bind_group_layouts(
91 None,
92 vec![
93 GroupLayout { group: 2, layout: &c },
94 GroupLayout { group: 0, layout: &a },
95 GroupLayout { group: 1, layout: &b },
96 ],
97 );
98
99 assert_eq!(assembled.len(), 3);
100 assert!(std::ptr::eq(assembled[0].unwrap(), &a));
101 assert!(std::ptr::eq(assembled[1].unwrap(), &b));
102 assert!(std::ptr::eq(assembled[2].unwrap(), &c));
103 });
104 }
105
106 #[test]
112 fn two_slots_claiming_the_same_group_panics() {
113 with_device!(device, _queue, {
114 let a = empty_layout(&device);
115 let b = empty_layout(&device);
116 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
117 assemble_bind_group_layouts(
118 None,
119 vec![GroupLayout { group: 0, layout: &a }, GroupLayout { group: 0, layout: &b }],
120 );
121 }));
122 assert!(result.is_err(), "expected a panic for a duplicate group index");
123 });
124 }
125
126 #[test]
127 fn a_gap_in_group_indices_panics() {
128 with_device!(device, _queue, {
129 let a = empty_layout(&device);
130 let c = empty_layout(&device);
131 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
133 assemble_bind_group_layouts(
134 None,
135 vec![GroupLayout { group: 0, layout: &a }, GroupLayout { group: 2, layout: &c }],
136 );
137 }));
138 assert!(result.is_err(), "expected a panic for a gap in group indices");
139 });
140 }
141}