1#[derive(Copy, Clone, PartialEq, Eq, Hash)]
19pub enum BindingKind {
20 Texture {
22 visibility: wgpu::ShaderStages,
23 sample_type: wgpu::TextureSampleType,
24 view_dimension: wgpu::TextureViewDimension,
25 multisampled: bool,
26 },
27 StorageTexture {
30 visibility: wgpu::ShaderStages,
31 format: wgpu::TextureFormat,
32 access: wgpu::StorageTextureAccess,
33 view_dimension: wgpu::TextureViewDimension,
34 },
35 Sampler { visibility: wgpu::ShaderStages },
37 ComparisonSampler { visibility: wgpu::ShaderStages },
39 UniformBuffer {
41 visibility: wgpu::ShaderStages,
42 has_dynamic_offset: bool,
43 min_binding_size: Option<wgpu::BufferSize>,
44 },
45 StorageBufferReadOnly {
47 visibility: wgpu::ShaderStages,
48 has_dynamic_offset: bool,
49 min_binding_size: Option<wgpu::BufferSize>,
50 },
51 StorageBufferReadWrite {
53 visibility: wgpu::ShaderStages,
54 has_dynamic_offset: bool,
55 min_binding_size: Option<wgpu::BufferSize>,
56 },
57}
58
59impl BindingKind {
60 pub fn texture_2d(visibility: wgpu::ShaderStages) -> Self {
62 Self::Texture {
63 visibility,
64 sample_type: wgpu::TextureSampleType::Float { filterable: true },
65 view_dimension: wgpu::TextureViewDimension::D2,
66 multisampled: false,
67 }
68 }
69
70 pub fn texture_2d_array(visibility: wgpu::ShaderStages) -> Self {
73 Self::Texture {
74 visibility,
75 sample_type: wgpu::TextureSampleType::Float { filterable: true },
76 view_dimension: wgpu::TextureViewDimension::D2Array,
77 multisampled: false,
78 }
79 }
80
81 pub fn texture_cubemap(visibility: wgpu::ShaderStages) -> Self {
84 Self::Texture {
85 visibility,
86 sample_type: wgpu::TextureSampleType::Float { filterable: true },
87 view_dimension: wgpu::TextureViewDimension::Cube,
88 multisampled: false,
89 }
90 }
91
92 pub fn storage_texture(
95 visibility: wgpu::ShaderStages,
96 format: wgpu::TextureFormat,
97 access: wgpu::StorageTextureAccess,
98 view_dimension: wgpu::TextureViewDimension,
99 ) -> Self {
100 Self::StorageTexture { visibility, format, access, view_dimension }
101 }
102
103 pub fn sampler(visibility: wgpu::ShaderStages) -> Self {
105 Self::Sampler { visibility }
106 }
107
108 pub fn comparison_sampler(visibility: wgpu::ShaderStages) -> Self {
110 Self::ComparisonSampler { visibility }
111 }
112
113 pub fn uniform_buffer(visibility: wgpu::ShaderStages) -> Self {
117 Self::UniformBuffer { visibility, has_dynamic_offset: false, min_binding_size: None }
118 }
119
120 pub fn dynamic_uniform_buffer(visibility: wgpu::ShaderStages, element_size: u64) -> Self {
131 Self::UniformBuffer {
132 visibility,
133 has_dynamic_offset: true,
134 min_binding_size: wgpu::BufferSize::new(element_size),
135 }
136 }
137
138 pub fn storage_buffer_read_only(visibility: wgpu::ShaderStages) -> Self {
140 Self::StorageBufferReadOnly { visibility, has_dynamic_offset: false, min_binding_size: None }
141 }
142
143 pub fn storage_buffer_read_write(visibility: wgpu::ShaderStages) -> Self {
145 Self::StorageBufferReadWrite { visibility, has_dynamic_offset: false, min_binding_size: None }
146 }
147
148 pub fn dynamic_storage_buffer(visibility: wgpu::ShaderStages, element_size: u64, read_only: bool) -> Self {
151 let has_dynamic_offset = true;
152 let min_binding_size = wgpu::BufferSize::new(element_size);
153 if read_only {
154 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
155 } else {
156 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
157 }
158 }
159
160 pub fn visibility(&self) -> wgpu::ShaderStages {
162 match self {
163 Self::Texture { visibility, .. }
164 | Self::StorageTexture { visibility, .. }
165 | Self::Sampler { visibility }
166 | Self::ComparisonSampler { visibility }
167 | Self::UniformBuffer { visibility, .. }
168 | Self::StorageBufferReadOnly { visibility, .. }
169 | Self::StorageBufferReadWrite { visibility, .. } => *visibility,
170 }
171 }
172
173 pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
174 match self {
175 Self::Texture { visibility, sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
176 binding,
177 visibility: *visibility,
178 ty: wgpu::BindingType::Texture {
179 sample_type: *sample_type,
180 view_dimension: *view_dimension,
181 multisampled: *multisampled,
182 },
183 count: None,
184 },
185 Self::StorageTexture { visibility, format, access, view_dimension } => wgpu::BindGroupLayoutEntry {
186 binding,
187 visibility: *visibility,
188 ty: wgpu::BindingType::StorageTexture {
189 access: *access,
190 format: *format,
191 view_dimension: *view_dimension,
192 },
193 count: None,
194 },
195 Self::Sampler { visibility } => wgpu::BindGroupLayoutEntry {
196 binding,
197 visibility: *visibility,
198 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
199 count: None,
200 },
201 Self::ComparisonSampler { visibility } => wgpu::BindGroupLayoutEntry {
202 binding,
203 visibility: *visibility,
204 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
205 count: None,
206 },
207 Self::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
208 binding,
209 visibility: *visibility,
210 ty: wgpu::BindingType::Buffer {
211 ty: wgpu::BufferBindingType::Uniform,
212 has_dynamic_offset: *has_dynamic_offset,
213 min_binding_size: *min_binding_size,
214 },
215 count: None,
216 },
217 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
218 binding,
219 visibility: *visibility,
220 ty: wgpu::BindingType::Buffer {
221 ty: wgpu::BufferBindingType::Storage { read_only: true },
222 has_dynamic_offset: *has_dynamic_offset,
223 min_binding_size: *min_binding_size,
224 },
225 count: None,
226 },
227 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
228 binding,
229 visibility: *visibility,
230 ty: wgpu::BindingType::Buffer {
231 ty: wgpu::BufferBindingType::Storage { read_only: false },
232 has_dynamic_offset: *has_dynamic_offset,
233 min_binding_size: *min_binding_size,
234 },
235 count: None,
236 },
237 }
238 }
239}
240
241#[derive(Clone)]
244pub struct BindingEntry {
245 pub name: &'static str,
248 pub binding: u32,
251 pub kind: BindingKind,
254}
255
256pub fn build_bind_group_layout(
261 device: &wgpu::Device,
262 label: Option<&str>,
263 entries: &[BindingEntry],
264) -> wgpu::BindGroupLayout {
265 let layout_entries: Vec<_> = entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
266
267 let mut seen = std::collections::HashSet::new();
268 for e in entries {
269 if !seen.insert(e.binding) {
270 panic!(
271 "binding {} assigned more than once building bind group layout{} (entry '{}')",
272 e.binding,
273 label.map(|l| format!(" '{l}'")).unwrap_or_default(),
274 e.name
275 );
276 }
277 }
278
279 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
280 label,
281 entries: &layout_entries,
282 })
283}
284
285pub trait BindGroupTarget {
291 fn bind_group_layout(&self) -> &wgpu::BindGroupLayout;
292 fn binding_entries(&self) -> &[BindingEntry];
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
302 fn visibility_reports_back_exactly_what_each_constructor_was_given() {
303 let stages = wgpu::ShaderStages::VERTEX_FRAGMENT;
304 assert_eq!(BindingKind::texture_2d(stages).visibility(), stages);
305 assert_eq!(BindingKind::sampler(stages).visibility(), stages);
306 assert_eq!(BindingKind::uniform_buffer(stages).visibility(), stages);
307 assert_eq!(
308 BindingKind::storage_buffer_read_only(wgpu::ShaderStages::COMPUTE).visibility(),
309 wgpu::ShaderStages::COMPUTE
310 );
311 assert_eq!(
312 BindingKind::storage_buffer_read_write(wgpu::ShaderStages::COMPUTE).visibility(),
313 wgpu::ShaderStages::COMPUTE
314 );
315 }
316
317 #[test]
318 fn dynamic_storage_buffer_picks_read_only_or_read_write_by_flag() {
319 let read_only = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, true);
320 let read_write = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, false);
321 assert!(matches!(read_only, BindingKind::StorageBufferReadOnly { .. }));
322 assert!(matches!(read_write, BindingKind::StorageBufferReadWrite { .. }));
323 }
324
325 #[test]
329 fn unique_bindings_build_without_panicking() {
330 crate::wgpu::test_util::with_device!(device, _queue, {
331 build_bind_group_layout(
332 &device,
333 None,
334 &[
335 BindingEntry { name: "a", binding: 0, kind: BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT) },
336 BindingEntry { name: "b", binding: 1, kind: BindingKind::sampler(wgpu::ShaderStages::FRAGMENT) },
337 ],
338 );
339 });
340 }
341
342 #[test]
343 fn two_entries_claiming_the_same_binding_panics() {
344 crate::wgpu::test_util::with_device!(device, _queue, {
345 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
346 build_bind_group_layout(
347 &device,
348 None,
349 &[
350 BindingEntry { name: "a", binding: 0, kind: BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT) },
351 BindingEntry { name: "b", binding: 0, kind: BindingKind::sampler(wgpu::ShaderStages::FRAGMENT) },
352 ],
353 );
354 }));
355 assert!(result.is_err(), "expected a panic for a duplicate @binding(0)");
356 });
357 }
358}