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)]
317pub struct BindGroupLayout(wgpu::BindGroupLayout);
318
319impl BindGroupLayout {
320 pub(crate) fn raw(&self) -> &wgpu::BindGroupLayout {
321 &self.0
322 }
323}
324
325#[derive(Clone)]
328pub struct BindingEntry {
329 pub name: &'static str,
332 pub binding: u32,
335 pub kind: BindingKind,
338}
339
340#[derive(Default)]
353pub struct BindGroupLayoutBuilder<'a> {
354 label: Option<&'a str>,
355 entries: Vec<BindingEntry>,
356}
357
358impl<'a> BindGroupLayoutBuilder<'a> {
359 pub fn new() -> Self {
360 Self::default()
361 }
362
363 pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
364 self.label = label.into();
365 self
366 }
367
368 pub fn entry(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
370 self.entries.push(BindingEntry { name, binding, kind });
371 self
372 }
373
374 pub fn entries(mut self, entries: impl IntoIterator<Item = BindingEntry>) -> Self {
378 self.entries.extend(entries);
379 self
380 }
381
382 pub fn build(self, backend: &WGPUBackend) -> BindGroupLayout {
383 self.build_raw(&backend.device)
384 }
385
386 pub(crate) fn build_raw(self, device: &wgpu::Device) -> BindGroupLayout {
389 let layout_entries: Vec<_> =
390 self.entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
391
392 let mut seen = std::collections::HashSet::new();
393 for e in &self.entries {
394 if !seen.insert(e.binding) {
395 panic!(
396 "binding {} assigned more than once building bind group layout{} (entry '{}')",
397 e.binding,
398 self.label.map(|l| format!(" '{l}'")).unwrap_or_default(),
399 e.name
400 );
401 }
402 }
403
404 BindGroupLayout(device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
405 label: self.label,
406 entries: &layout_entries,
407 }))
408 }
409}
410
411pub trait BindGroupTarget {
417 fn bind_group_layout(&self) -> &BindGroupLayout;
418 fn binding_entries(&self) -> &[BindingEntry];
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
428 fn visibility_reports_back_exactly_what_each_constructor_was_given() {
429 let stages = ShaderStages::VERTEX_FRAGMENT;
430 assert!(BindingKind::texture_2d(stages).visibility() == stages);
431 assert!(BindingKind::sampler(stages).visibility() == stages);
432 assert!(BindingKind::uniform_buffer(stages).visibility() == stages);
433 assert!(
434 BindingKind::storage_buffer_read_only(ShaderStages::COMPUTE).visibility()
435 == ShaderStages::COMPUTE
436 );
437 assert!(
438 BindingKind::storage_buffer_read_write(ShaderStages::COMPUTE).visibility()
439 == ShaderStages::COMPUTE
440 );
441 }
442
443 #[test]
444 fn dynamic_storage_buffer_picks_read_only_or_read_write_by_flag() {
445 let read_only = BindingKind::dynamic_storage_buffer(ShaderStages::COMPUTE, 16, true);
446 let read_write = BindingKind::dynamic_storage_buffer(ShaderStages::COMPUTE, 16, false);
447 assert!(matches!(read_only, BindingKind::StorageBufferReadOnly { .. }));
448 assert!(matches!(read_write, BindingKind::StorageBufferReadWrite { .. }));
449 }
450
451 #[test]
455 fn unique_bindings_build_without_panicking() {
456 crate::wgpu::test_util::with_device!(device, _queue, {
457 BindGroupLayoutBuilder::new()
458 .entry("a", 0, BindingKind::texture_2d(ShaderStages::FRAGMENT))
459 .entry("b", 1, BindingKind::sampler(ShaderStages::FRAGMENT))
460 .build_raw(&device);
461 });
462 }
463
464 #[test]
465 fn two_entries_claiming_the_same_binding_panics() {
466 crate::wgpu::test_util::with_device!(device, _queue, {
467 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
468 BindGroupLayoutBuilder::new()
469 .entry("a", 0, BindingKind::texture_2d(ShaderStages::FRAGMENT))
470 .entry("b", 0, BindingKind::sampler(ShaderStages::FRAGMENT))
471 .build_raw(&device);
472 }));
473 assert!(result.is_err(), "expected a panic for a duplicate @binding(0)");
474 });
475 }
476}