1use crate::wgpu::backend::WGPUBackend;
19use crate::wgpu::flags::ShaderStages;
20use crate::wgpu::texture_format::TextureFormat;
21
22#[derive(Copy, Clone, PartialEq, Eq, Hash)]
25pub enum TextureSampleType {
26 Float { filterable: bool },
27 Depth,
28 Sint,
29 Uint,
30}
31
32impl From<TextureSampleType> for wgpu::TextureSampleType {
33 fn from(value: TextureSampleType) -> Self {
34 match value {
35 TextureSampleType::Float { filterable } => Self::Float { filterable },
36 TextureSampleType::Depth => Self::Depth,
37 TextureSampleType::Sint => Self::Sint,
38 TextureSampleType::Uint => Self::Uint,
39 }
40 }
41}
42
43#[derive(Copy, Clone, PartialEq, Eq, Hash)]
45pub enum TextureViewDimension {
46 D1,
47 D2,
48 D2Array,
49 Cube,
50 CubeArray,
51 D3,
52}
53
54impl From<TextureViewDimension> for wgpu::TextureViewDimension {
55 fn from(value: TextureViewDimension) -> Self {
56 match value {
57 TextureViewDimension::D1 => Self::D1,
58 TextureViewDimension::D2 => Self::D2,
59 TextureViewDimension::D2Array => Self::D2Array,
60 TextureViewDimension::Cube => Self::Cube,
61 TextureViewDimension::CubeArray => Self::CubeArray,
62 TextureViewDimension::D3 => Self::D3,
63 }
64 }
65}
66
67#[derive(Copy, Clone, PartialEq, Eq, Hash)]
70pub enum StorageTextureAccess {
71 WriteOnly,
72 ReadOnly,
73 ReadWrite,
74 Atomic,
75}
76
77impl From<StorageTextureAccess> for wgpu::StorageTextureAccess {
78 fn from(value: StorageTextureAccess) -> Self {
79 match value {
80 StorageTextureAccess::WriteOnly => Self::WriteOnly,
81 StorageTextureAccess::ReadOnly => Self::ReadOnly,
82 StorageTextureAccess::ReadWrite => Self::ReadWrite,
83 StorageTextureAccess::Atomic => Self::Atomic,
84 }
85 }
86}
87
88#[derive(Copy, Clone, PartialEq, Eq, Hash)]
95pub enum BindingKind {
96 Texture {
98 visibility: ShaderStages,
99 sample_type: TextureSampleType,
100 view_dimension: TextureViewDimension,
101 multisampled: bool,
102 },
103 StorageTexture {
106 visibility: ShaderStages,
107 format: TextureFormat,
108 access: StorageTextureAccess,
109 view_dimension: TextureViewDimension,
110 },
111 Sampler { visibility: ShaderStages },
113 ComparisonSampler { visibility: ShaderStages },
115 UniformBuffer {
117 visibility: ShaderStages,
118 has_dynamic_offset: bool,
119 min_binding_size: Option<u64>,
120 },
121 StorageBufferReadOnly {
123 visibility: ShaderStages,
124 has_dynamic_offset: bool,
125 min_binding_size: Option<u64>,
126 },
127 StorageBufferReadWrite {
129 visibility: ShaderStages,
130 has_dynamic_offset: bool,
131 min_binding_size: Option<u64>,
132 },
133}
134
135impl BindingKind {
136 pub fn texture_2d(visibility: ShaderStages) -> Self {
138 Self::Texture {
139 visibility,
140 sample_type: TextureSampleType::Float { filterable: true },
141 view_dimension: TextureViewDimension::D2,
142 multisampled: false,
143 }
144 }
145
146 pub fn texture_2d_array(visibility: ShaderStages) -> Self {
148 Self::Texture {
149 visibility,
150 sample_type: TextureSampleType::Float { filterable: true },
151 view_dimension: TextureViewDimension::D2Array,
152 multisampled: false,
153 }
154 }
155
156 pub fn texture_cubemap(visibility: ShaderStages) -> Self {
158 Self::Texture {
159 visibility,
160 sample_type: TextureSampleType::Float { filterable: true },
161 view_dimension: TextureViewDimension::Cube,
162 multisampled: false,
163 }
164 }
165
166 pub fn storage_texture(
168 visibility: ShaderStages,
169 format: TextureFormat,
170 access: StorageTextureAccess,
171 view_dimension: TextureViewDimension,
172 ) -> Self {
173 Self::StorageTexture { visibility, format, access, view_dimension }
174 }
175
176 pub fn sampler(visibility: ShaderStages) -> Self {
178 Self::Sampler { visibility }
179 }
180
181 pub fn comparison_sampler(visibility: ShaderStages) -> Self {
183 Self::ComparisonSampler { visibility }
184 }
185
186 pub fn uniform_buffer(visibility: ShaderStages) -> Self {
188 Self::UniformBuffer { visibility, has_dynamic_offset: false, min_binding_size: None }
189 }
190
191 pub fn dynamic_uniform_buffer(visibility: ShaderStages, element_size: u64) -> Self {
200 Self::UniformBuffer {
201 visibility,
202 has_dynamic_offset: true,
203 min_binding_size: Some(element_size),
204 }
205 }
206
207 pub fn storage_buffer_read_only(visibility: ShaderStages) -> Self {
209 Self::StorageBufferReadOnly { visibility, has_dynamic_offset: false, min_binding_size: None }
210 }
211
212 pub fn storage_buffer_read_write(visibility: ShaderStages) -> Self {
214 Self::StorageBufferReadWrite { visibility, has_dynamic_offset: false, min_binding_size: None }
215 }
216
217 pub fn dynamic_storage_buffer(visibility: ShaderStages, element_size: u64, read_only: bool) -> Self {
220 let has_dynamic_offset = true;
221 let min_binding_size = Some(element_size);
222 if read_only {
223 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
224 } else {
225 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
226 }
227 }
228
229 pub fn visibility(&self) -> ShaderStages {
231 match self {
232 Self::Texture { visibility, .. }
233 | Self::StorageTexture { visibility, .. }
234 | Self::Sampler { visibility }
235 | Self::ComparisonSampler { visibility }
236 | Self::UniformBuffer { visibility, .. }
237 | Self::StorageBufferReadOnly { visibility, .. }
238 | Self::StorageBufferReadWrite { visibility, .. } => *visibility,
239 }
240 }
241
242 pub(crate) fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
243 match self {
244 Self::Texture { visibility, sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
245 binding,
246 visibility: (*visibility).into(),
247 ty: wgpu::BindingType::Texture {
248 sample_type: (*sample_type).into(),
249 view_dimension: (*view_dimension).into(),
250 multisampled: *multisampled,
251 },
252 count: None,
253 },
254 Self::StorageTexture { visibility, format, access, view_dimension } => wgpu::BindGroupLayoutEntry {
255 binding,
256 visibility: (*visibility).into(),
257 ty: wgpu::BindingType::StorageTexture {
258 access: (*access).into(),
259 format: (*format).into(),
260 view_dimension: (*view_dimension).into(),
261 },
262 count: None,
263 },
264 Self::Sampler { visibility } => wgpu::BindGroupLayoutEntry {
265 binding,
266 visibility: (*visibility).into(),
267 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
268 count: None,
269 },
270 Self::ComparisonSampler { visibility } => wgpu::BindGroupLayoutEntry {
271 binding,
272 visibility: (*visibility).into(),
273 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
274 count: None,
275 },
276 Self::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
277 binding,
278 visibility: (*visibility).into(),
279 ty: wgpu::BindingType::Buffer {
280 ty: wgpu::BufferBindingType::Uniform,
281 has_dynamic_offset: *has_dynamic_offset,
282 min_binding_size: min_binding_size.and_then(wgpu::BufferSize::new),
283 },
284 count: None,
285 },
286 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
287 binding,
288 visibility: (*visibility).into(),
289 ty: wgpu::BindingType::Buffer {
290 ty: wgpu::BufferBindingType::Storage { read_only: true },
291 has_dynamic_offset: *has_dynamic_offset,
292 min_binding_size: min_binding_size.and_then(wgpu::BufferSize::new),
293 },
294 count: None,
295 },
296 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
297 binding,
298 visibility: (*visibility).into(),
299 ty: wgpu::BindingType::Buffer {
300 ty: wgpu::BufferBindingType::Storage { read_only: false },
301 has_dynamic_offset: *has_dynamic_offset,
302 min_binding_size: min_binding_size.and_then(wgpu::BufferSize::new),
303 },
304 count: None,
305 },
306 }
307 }
308}
309
310#[derive(Clone)]
319pub struct BindGroupLayout(wgpu::BindGroupLayout);
320
321impl BindGroupLayout {
322 pub(crate) fn raw(&self) -> &wgpu::BindGroupLayout {
323 &self.0
324 }
325}
326
327#[derive(Clone)]
330pub struct BindingEntry {
331 pub name: &'static str,
334 pub binding: u32,
337 pub kind: BindingKind,
340}
341
342#[derive(Default)]
355pub struct BindGroupLayoutBuilder<'a> {
356 label: Option<&'a str>,
357 entries: Vec<BindingEntry>,
358}
359
360impl<'a> BindGroupLayoutBuilder<'a> {
361 pub fn new() -> Self {
362 Self::default()
363 }
364
365 pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
366 self.label = label.into();
367 self
368 }
369
370 pub fn entry(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
372 self.entries.push(BindingEntry { name, binding, kind });
373 self
374 }
375
376 pub fn entries(mut self, entries: impl IntoIterator<Item = BindingEntry>) -> Self {
380 self.entries.extend(entries);
381 self
382 }
383
384 pub fn build(self, backend: &WGPUBackend) -> BindGroupLayout {
385 self.build_raw(&backend.device)
386 }
387
388 pub(crate) fn build_raw(self, device: &wgpu::Device) -> BindGroupLayout {
391 let layout_entries: Vec<_> =
392 self.entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
393
394 let mut seen = std::collections::HashSet::new();
395 for e in &self.entries {
396 if !seen.insert(e.binding) {
397 panic!(
398 "binding {} assigned more than once building bind group layout{} (entry '{}')",
399 e.binding,
400 self.label.map(|l| format!(" '{l}'")).unwrap_or_default(),
401 e.name
402 );
403 }
404 }
405
406 BindGroupLayout(device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
407 label: self.label,
408 entries: &layout_entries,
409 }))
410 }
411}
412
413pub trait BindGroupTarget {
419 fn bind_group_layout(&self) -> &BindGroupLayout;
420 fn binding_entries(&self) -> &[BindingEntry];
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
430 fn visibility_reports_back_exactly_what_each_constructor_was_given() {
431 let stages = ShaderStages::VERTEX_FRAGMENT;
432 assert!(BindingKind::texture_2d(stages).visibility() == stages);
433 assert!(BindingKind::sampler(stages).visibility() == stages);
434 assert!(BindingKind::uniform_buffer(stages).visibility() == stages);
435 assert!(
436 BindingKind::storage_buffer_read_only(ShaderStages::COMPUTE).visibility()
437 == ShaderStages::COMPUTE
438 );
439 assert!(
440 BindingKind::storage_buffer_read_write(ShaderStages::COMPUTE).visibility()
441 == ShaderStages::COMPUTE
442 );
443 }
444
445 #[test]
446 fn dynamic_storage_buffer_picks_read_only_or_read_write_by_flag() {
447 let read_only = BindingKind::dynamic_storage_buffer(ShaderStages::COMPUTE, 16, true);
448 let read_write = BindingKind::dynamic_storage_buffer(ShaderStages::COMPUTE, 16, false);
449 assert!(matches!(read_only, BindingKind::StorageBufferReadOnly { .. }));
450 assert!(matches!(read_write, BindingKind::StorageBufferReadWrite { .. }));
451 }
452
453 #[test]
457 fn unique_bindings_build_without_panicking() {
458 crate::wgpu::test_util::with_device!(device, _queue, {
459 BindGroupLayoutBuilder::new()
460 .entry("a", 0, BindingKind::texture_2d(ShaderStages::FRAGMENT))
461 .entry("b", 1, BindingKind::sampler(ShaderStages::FRAGMENT))
462 .build_raw(&device);
463 });
464 }
465
466 #[test]
467 fn two_entries_claiming_the_same_binding_panics() {
468 crate::wgpu::test_util::with_device!(device, _queue, {
469 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
470 BindGroupLayoutBuilder::new()
471 .entry("a", 0, BindingKind::texture_2d(ShaderStages::FRAGMENT))
472 .entry("b", 0, BindingKind::sampler(ShaderStages::FRAGMENT))
473 .build_raw(&device);
474 }));
475 assert!(result.is_err(), "expected a panic for a duplicate @binding(0)");
476 });
477 }
478}