1#[derive(Copy, Clone, PartialEq, Eq, Hash)]
25pub enum BindingKind {
26 Texture {
28 visibility: wgpu::ShaderStages,
29 sample_type: wgpu::TextureSampleType,
30 view_dimension: wgpu::TextureViewDimension,
31 multisampled: bool,
32 },
33 StorageTexture {
36 visibility: wgpu::ShaderStages,
37 format: wgpu::TextureFormat,
38 access: wgpu::StorageTextureAccess,
39 view_dimension: wgpu::TextureViewDimension,
40 },
41 Sampler { visibility: wgpu::ShaderStages },
43 ComparisonSampler { visibility: wgpu::ShaderStages },
45 UniformBuffer {
47 visibility: wgpu::ShaderStages,
48 has_dynamic_offset: bool,
49 min_binding_size: Option<wgpu::BufferSize>,
50 },
51 StorageBufferReadOnly {
53 visibility: wgpu::ShaderStages,
54 has_dynamic_offset: bool,
55 min_binding_size: Option<wgpu::BufferSize>,
56 },
57 StorageBufferReadWrite {
59 visibility: wgpu::ShaderStages,
60 has_dynamic_offset: bool,
61 min_binding_size: Option<wgpu::BufferSize>,
62 },
63}
64
65impl BindingKind {
66 pub fn texture_2d(visibility: wgpu::ShaderStages) -> Self {
68 Self::Texture {
69 visibility,
70 sample_type: wgpu::TextureSampleType::Float { filterable: true },
71 view_dimension: wgpu::TextureViewDimension::D2,
72 multisampled: false,
73 }
74 }
75
76 pub fn texture_2d_array(visibility: wgpu::ShaderStages) -> Self {
79 Self::Texture {
80 visibility,
81 sample_type: wgpu::TextureSampleType::Float { filterable: true },
82 view_dimension: wgpu::TextureViewDimension::D2Array,
83 multisampled: false,
84 }
85 }
86
87 pub fn texture_cubemap(visibility: wgpu::ShaderStages) -> Self {
90 Self::Texture {
91 visibility,
92 sample_type: wgpu::TextureSampleType::Float { filterable: true },
93 view_dimension: wgpu::TextureViewDimension::Cube,
94 multisampled: false,
95 }
96 }
97
98 pub fn storage_texture(
101 visibility: wgpu::ShaderStages,
102 format: wgpu::TextureFormat,
103 access: wgpu::StorageTextureAccess,
104 view_dimension: wgpu::TextureViewDimension,
105 ) -> Self {
106 Self::StorageTexture { visibility, format, access, view_dimension }
107 }
108
109 pub fn sampler(visibility: wgpu::ShaderStages) -> Self {
111 Self::Sampler { visibility }
112 }
113
114 pub fn comparison_sampler(visibility: wgpu::ShaderStages) -> Self {
116 Self::ComparisonSampler { visibility }
117 }
118
119 pub fn uniform_buffer(visibility: wgpu::ShaderStages) -> Self {
123 Self::UniformBuffer { visibility, has_dynamic_offset: false, min_binding_size: None }
124 }
125
126 pub fn dynamic_uniform_buffer(visibility: wgpu::ShaderStages, element_size: u64) -> Self {
138 Self::UniformBuffer {
139 visibility,
140 has_dynamic_offset: true,
141 min_binding_size: wgpu::BufferSize::new(element_size),
142 }
143 }
144
145 pub fn storage_buffer_read_only(visibility: wgpu::ShaderStages) -> Self {
147 Self::StorageBufferReadOnly { visibility, has_dynamic_offset: false, min_binding_size: None }
148 }
149
150 pub fn storage_buffer_read_write(visibility: wgpu::ShaderStages) -> Self {
152 Self::StorageBufferReadWrite { visibility, has_dynamic_offset: false, min_binding_size: None }
153 }
154
155 pub fn dynamic_storage_buffer(visibility: wgpu::ShaderStages, element_size: u64, read_only: bool) -> Self {
158 let has_dynamic_offset = true;
159 let min_binding_size = wgpu::BufferSize::new(element_size);
160 if read_only {
161 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
162 } else {
163 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
164 }
165 }
166
167 pub fn visibility(&self) -> wgpu::ShaderStages {
169 match self {
170 Self::Texture { visibility, .. }
171 | Self::StorageTexture { visibility, .. }
172 | Self::Sampler { visibility }
173 | Self::ComparisonSampler { visibility }
174 | Self::UniformBuffer { visibility, .. }
175 | Self::StorageBufferReadOnly { visibility, .. }
176 | Self::StorageBufferReadWrite { visibility, .. } => *visibility,
177 }
178 }
179
180 pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
181 match self {
182 Self::Texture { visibility, sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
183 binding,
184 visibility: *visibility,
185 ty: wgpu::BindingType::Texture {
186 sample_type: *sample_type,
187 view_dimension: *view_dimension,
188 multisampled: *multisampled,
189 },
190 count: None,
191 },
192 Self::StorageTexture { visibility, format, access, view_dimension } => wgpu::BindGroupLayoutEntry {
193 binding,
194 visibility: *visibility,
195 ty: wgpu::BindingType::StorageTexture {
196 access: *access,
197 format: *format,
198 view_dimension: *view_dimension,
199 },
200 count: None,
201 },
202 Self::Sampler { visibility } => wgpu::BindGroupLayoutEntry {
203 binding,
204 visibility: *visibility,
205 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
206 count: None,
207 },
208 Self::ComparisonSampler { visibility } => wgpu::BindGroupLayoutEntry {
209 binding,
210 visibility: *visibility,
211 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
212 count: None,
213 },
214 Self::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
215 binding,
216 visibility: *visibility,
217 ty: wgpu::BindingType::Buffer {
218 ty: wgpu::BufferBindingType::Uniform,
219 has_dynamic_offset: *has_dynamic_offset,
220 min_binding_size: *min_binding_size,
221 },
222 count: None,
223 },
224 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
225 binding,
226 visibility: *visibility,
227 ty: wgpu::BindingType::Buffer {
228 ty: wgpu::BufferBindingType::Storage { read_only: true },
229 has_dynamic_offset: *has_dynamic_offset,
230 min_binding_size: *min_binding_size,
231 },
232 count: None,
233 },
234 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
235 binding,
236 visibility: *visibility,
237 ty: wgpu::BindingType::Buffer {
238 ty: wgpu::BufferBindingType::Storage { read_only: false },
239 has_dynamic_offset: *has_dynamic_offset,
240 min_binding_size: *min_binding_size,
241 },
242 count: None,
243 },
244 }
245 }
246}
247
248#[derive(Clone)]
251pub struct BindingEntry {
252 pub name: &'static str,
255 pub binding: u32,
258 pub kind: BindingKind,
261}
262
263#[derive(Default)]
276pub struct BindGroupLayoutBuilder<'a> {
277 label: Option<&'a str>,
278 entries: Vec<BindingEntry>,
279}
280
281impl<'a> BindGroupLayoutBuilder<'a> {
282 pub fn new() -> Self {
283 Self::default()
284 }
285
286 pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
287 self.label = label.into();
288 self
289 }
290
291 pub fn entry(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
293 self.entries.push(BindingEntry { name, binding, kind });
294 self
295 }
296
297 pub fn entries(mut self, entries: impl IntoIterator<Item = BindingEntry>) -> Self {
301 self.entries.extend(entries);
302 self
303 }
304
305 pub fn build(self, device: &wgpu::Device) -> wgpu::BindGroupLayout {
306 let layout_entries: Vec<_> =
307 self.entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
308
309 let mut seen = std::collections::HashSet::new();
310 for e in &self.entries {
311 if !seen.insert(e.binding) {
312 panic!(
313 "binding {} assigned more than once building bind group layout{} (entry '{}')",
314 e.binding,
315 self.label.map(|l| format!(" '{l}'")).unwrap_or_default(),
316 e.name
317 );
318 }
319 }
320
321 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
322 label: self.label,
323 entries: &layout_entries,
324 })
325 }
326}
327
328pub trait BindGroupTarget {
334 fn bind_group_layout(&self) -> &wgpu::BindGroupLayout;
335 fn binding_entries(&self) -> &[BindingEntry];
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
345 fn visibility_reports_back_exactly_what_each_constructor_was_given() {
346 let stages = wgpu::ShaderStages::VERTEX_FRAGMENT;
347 assert_eq!(BindingKind::texture_2d(stages).visibility(), stages);
348 assert_eq!(BindingKind::sampler(stages).visibility(), stages);
349 assert_eq!(BindingKind::uniform_buffer(stages).visibility(), stages);
350 assert_eq!(
351 BindingKind::storage_buffer_read_only(wgpu::ShaderStages::COMPUTE).visibility(),
352 wgpu::ShaderStages::COMPUTE
353 );
354 assert_eq!(
355 BindingKind::storage_buffer_read_write(wgpu::ShaderStages::COMPUTE).visibility(),
356 wgpu::ShaderStages::COMPUTE
357 );
358 }
359
360 #[test]
361 fn dynamic_storage_buffer_picks_read_only_or_read_write_by_flag() {
362 let read_only = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, true);
363 let read_write = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, false);
364 assert!(matches!(read_only, BindingKind::StorageBufferReadOnly { .. }));
365 assert!(matches!(read_write, BindingKind::StorageBufferReadWrite { .. }));
366 }
367
368 #[test]
372 fn unique_bindings_build_without_panicking() {
373 crate::wgpu::test_util::with_device!(device, _queue, {
374 BindGroupLayoutBuilder::new()
375 .entry("a", 0, BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT))
376 .entry("b", 1, BindingKind::sampler(wgpu::ShaderStages::FRAGMENT))
377 .build(&device);
378 });
379 }
380
381 #[test]
382 fn two_entries_claiming_the_same_binding_panics() {
383 crate::wgpu::test_util::with_device!(device, _queue, {
384 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
385 BindGroupLayoutBuilder::new()
386 .entry("a", 0, BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT))
387 .entry("b", 0, BindingKind::sampler(wgpu::ShaderStages::FRAGMENT))
388 .build(&device);
389 }));
390 assert!(result.is_err(), "expected a panic for a duplicate @binding(0)");
391 });
392 }
393}