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)]
255pub struct BindGroupLayout(wgpu::BindGroupLayout);
256
257impl BindGroupLayout {
258 pub(crate) fn raw(&self) -> &wgpu::BindGroupLayout {
259 &self.0
260 }
261}
262
263#[derive(Clone)]
266pub struct BindingEntry {
267 pub name: &'static str,
270 pub binding: u32,
273 pub kind: BindingKind,
276}
277
278#[derive(Default)]
291pub struct BindGroupLayoutBuilder<'a> {
292 label: Option<&'a str>,
293 entries: Vec<BindingEntry>,
294}
295
296impl<'a> BindGroupLayoutBuilder<'a> {
297 pub fn new() -> Self {
298 Self::default()
299 }
300
301 pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
302 self.label = label.into();
303 self
304 }
305
306 pub fn entry(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
308 self.entries.push(BindingEntry { name, binding, kind });
309 self
310 }
311
312 pub fn entries(mut self, entries: impl IntoIterator<Item = BindingEntry>) -> Self {
316 self.entries.extend(entries);
317 self
318 }
319
320 pub fn build(self, device: &wgpu::Device) -> BindGroupLayout {
321 let layout_entries: Vec<_> =
322 self.entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
323
324 let mut seen = std::collections::HashSet::new();
325 for e in &self.entries {
326 if !seen.insert(e.binding) {
327 panic!(
328 "binding {} assigned more than once building bind group layout{} (entry '{}')",
329 e.binding,
330 self.label.map(|l| format!(" '{l}'")).unwrap_or_default(),
331 e.name
332 );
333 }
334 }
335
336 BindGroupLayout(device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
337 label: self.label,
338 entries: &layout_entries,
339 }))
340 }
341}
342
343pub trait BindGroupTarget {
349 fn bind_group_layout(&self) -> &BindGroupLayout;
350 fn binding_entries(&self) -> &[BindingEntry];
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
360 fn visibility_reports_back_exactly_what_each_constructor_was_given() {
361 let stages = wgpu::ShaderStages::VERTEX_FRAGMENT;
362 assert_eq!(BindingKind::texture_2d(stages).visibility(), stages);
363 assert_eq!(BindingKind::sampler(stages).visibility(), stages);
364 assert_eq!(BindingKind::uniform_buffer(stages).visibility(), stages);
365 assert_eq!(
366 BindingKind::storage_buffer_read_only(wgpu::ShaderStages::COMPUTE).visibility(),
367 wgpu::ShaderStages::COMPUTE
368 );
369 assert_eq!(
370 BindingKind::storage_buffer_read_write(wgpu::ShaderStages::COMPUTE).visibility(),
371 wgpu::ShaderStages::COMPUTE
372 );
373 }
374
375 #[test]
376 fn dynamic_storage_buffer_picks_read_only_or_read_write_by_flag() {
377 let read_only = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, true);
378 let read_write = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, false);
379 assert!(matches!(read_only, BindingKind::StorageBufferReadOnly { .. }));
380 assert!(matches!(read_write, BindingKind::StorageBufferReadWrite { .. }));
381 }
382
383 #[test]
387 fn unique_bindings_build_without_panicking() {
388 crate::wgpu::test_util::with_device!(device, _queue, {
389 BindGroupLayoutBuilder::new()
390 .entry("a", 0, BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT))
391 .entry("b", 1, BindingKind::sampler(wgpu::ShaderStages::FRAGMENT))
392 .build(&device);
393 });
394 }
395
396 #[test]
397 fn two_entries_claiming_the_same_binding_panics() {
398 crate::wgpu::test_util::with_device!(device, _queue, {
399 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
400 BindGroupLayoutBuilder::new()
401 .entry("a", 0, BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT))
402 .entry("b", 0, BindingKind::sampler(wgpu::ShaderStages::FRAGMENT))
403 .build(&device);
404 }));
405 assert!(result.is_err(), "expected a panic for a duplicate @binding(0)");
406 });
407 }
408}