#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum BindingKind {
Texture {
visibility: wgpu::ShaderStages,
sample_type: wgpu::TextureSampleType,
view_dimension: wgpu::TextureViewDimension,
multisampled: bool,
},
StorageTexture {
visibility: wgpu::ShaderStages,
format: wgpu::TextureFormat,
access: wgpu::StorageTextureAccess,
view_dimension: wgpu::TextureViewDimension,
},
Sampler { visibility: wgpu::ShaderStages },
ComparisonSampler { visibility: wgpu::ShaderStages },
UniformBuffer {
visibility: wgpu::ShaderStages,
has_dynamic_offset: bool,
min_binding_size: Option<wgpu::BufferSize>,
},
StorageBufferReadOnly {
visibility: wgpu::ShaderStages,
has_dynamic_offset: bool,
min_binding_size: Option<wgpu::BufferSize>,
},
StorageBufferReadWrite {
visibility: wgpu::ShaderStages,
has_dynamic_offset: bool,
min_binding_size: Option<wgpu::BufferSize>,
},
}
impl BindingKind {
pub fn texture_2d(visibility: wgpu::ShaderStages) -> Self {
Self::Texture {
visibility,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
}
}
pub fn texture_2d_array(visibility: wgpu::ShaderStages) -> Self {
Self::Texture {
visibility,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
}
}
pub fn texture_cubemap(visibility: wgpu::ShaderStages) -> Self {
Self::Texture {
visibility,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
}
}
pub fn storage_texture(
visibility: wgpu::ShaderStages,
format: wgpu::TextureFormat,
access: wgpu::StorageTextureAccess,
view_dimension: wgpu::TextureViewDimension,
) -> Self {
Self::StorageTexture { visibility, format, access, view_dimension }
}
pub fn sampler(visibility: wgpu::ShaderStages) -> Self {
Self::Sampler { visibility }
}
pub fn comparison_sampler(visibility: wgpu::ShaderStages) -> Self {
Self::ComparisonSampler { visibility }
}
pub fn uniform_buffer(visibility: wgpu::ShaderStages) -> Self {
Self::UniformBuffer { visibility, has_dynamic_offset: false, min_binding_size: None }
}
pub fn dynamic_uniform_buffer(visibility: wgpu::ShaderStages, element_size: u64) -> Self {
Self::UniformBuffer {
visibility,
has_dynamic_offset: true,
min_binding_size: wgpu::BufferSize::new(element_size),
}
}
pub fn storage_buffer_read_only(visibility: wgpu::ShaderStages) -> Self {
Self::StorageBufferReadOnly { visibility, has_dynamic_offset: false, min_binding_size: None }
}
pub fn storage_buffer_read_write(visibility: wgpu::ShaderStages) -> Self {
Self::StorageBufferReadWrite { visibility, has_dynamic_offset: false, min_binding_size: None }
}
pub fn dynamic_storage_buffer(visibility: wgpu::ShaderStages, element_size: u64, read_only: bool) -> Self {
let has_dynamic_offset = true;
let min_binding_size = wgpu::BufferSize::new(element_size);
if read_only {
Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
} else {
Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
}
}
pub fn visibility(&self) -> wgpu::ShaderStages {
match self {
Self::Texture { visibility, .. }
| Self::StorageTexture { visibility, .. }
| Self::Sampler { visibility }
| Self::ComparisonSampler { visibility }
| Self::UniformBuffer { visibility, .. }
| Self::StorageBufferReadOnly { visibility, .. }
| Self::StorageBufferReadWrite { visibility, .. } => *visibility,
}
}
pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
match self {
Self::Texture { visibility, sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::Texture {
sample_type: *sample_type,
view_dimension: *view_dimension,
multisampled: *multisampled,
},
count: None,
},
Self::StorageTexture { visibility, format, access, view_dimension } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::StorageTexture {
access: *access,
format: *format,
view_dimension: *view_dimension,
},
count: None,
},
Self::Sampler { visibility } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
Self::ComparisonSampler { visibility } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
Self::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: *has_dynamic_offset,
min_binding_size: *min_binding_size,
},
count: None,
},
Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: *has_dynamic_offset,
min_binding_size: *min_binding_size,
},
count: None,
},
Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
binding,
visibility: *visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: *has_dynamic_offset,
min_binding_size: *min_binding_size,
},
count: None,
},
}
}
}
#[derive(Clone)]
pub struct BindingEntry {
pub name: &'static str,
pub binding: u32,
pub kind: BindingKind,
}
pub fn build_bind_group_layout(
device: &wgpu::Device,
label: Option<&str>,
entries: &[BindingEntry],
) -> wgpu::BindGroupLayout {
let layout_entries: Vec<_> = entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
let mut seen = std::collections::HashSet::new();
for e in entries {
if !seen.insert(e.binding) {
panic!(
"binding {} assigned more than once building bind group layout{} (entry '{}')",
e.binding,
label.map(|l| format!(" '{l}'")).unwrap_or_default(),
e.name
);
}
}
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label,
entries: &layout_entries,
})
}
pub trait BindGroupTarget {
fn bind_group_layout(&self) -> &wgpu::BindGroupLayout;
fn binding_entries(&self) -> &[BindingEntry];
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn visibility_reports_back_exactly_what_each_constructor_was_given() {
let stages = wgpu::ShaderStages::VERTEX_FRAGMENT;
assert_eq!(BindingKind::texture_2d(stages).visibility(), stages);
assert_eq!(BindingKind::sampler(stages).visibility(), stages);
assert_eq!(BindingKind::uniform_buffer(stages).visibility(), stages);
assert_eq!(
BindingKind::storage_buffer_read_only(wgpu::ShaderStages::COMPUTE).visibility(),
wgpu::ShaderStages::COMPUTE
);
assert_eq!(
BindingKind::storage_buffer_read_write(wgpu::ShaderStages::COMPUTE).visibility(),
wgpu::ShaderStages::COMPUTE
);
}
#[test]
fn dynamic_storage_buffer_picks_read_only_or_read_write_by_flag() {
let read_only = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, true);
let read_write = BindingKind::dynamic_storage_buffer(wgpu::ShaderStages::COMPUTE, 16, false);
assert!(matches!(read_only, BindingKind::StorageBufferReadOnly { .. }));
assert!(matches!(read_write, BindingKind::StorageBufferReadWrite { .. }));
}
#[test]
fn unique_bindings_build_without_panicking() {
crate::wgpu::test_util::with_device!(device, _queue, {
build_bind_group_layout(
&device,
None,
&[
BindingEntry { name: "a", binding: 0, kind: BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT) },
BindingEntry { name: "b", binding: 1, kind: BindingKind::sampler(wgpu::ShaderStages::FRAGMENT) },
],
);
});
}
#[test]
fn two_entries_claiming_the_same_binding_panics() {
crate::wgpu::test_util::with_device!(device, _queue, {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
build_bind_group_layout(
&device,
None,
&[
BindingEntry { name: "a", binding: 0, kind: BindingKind::texture_2d(wgpu::ShaderStages::FRAGMENT) },
BindingEntry { name: "b", binding: 0, kind: BindingKind::sampler(wgpu::ShaderStages::FRAGMENT) },
],
);
}));
assert!(result.is_err(), "expected a panic for a duplicate @binding(0)");
});
}
}