pub struct BindGroupLayoutBuilder {
entries: Vec<wgpu::BindGroupLayoutEntry>,
}
impl BindGroupLayoutBuilder {
#[must_use]
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[must_use]
pub fn uniform_buffer(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
self
}
#[must_use]
pub fn uniform_buffer_dynamic(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: None,
},
count: None,
});
self
}
#[must_use]
pub fn storage_buffer_readonly(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
self
}
#[must_use]
pub fn storage_buffer(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
self
}
#[must_use]
pub fn texture_2d(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
});
self
}
#[must_use]
pub fn texture_cube(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
});
self
}
#[must_use]
pub fn texture_depth_2d(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
});
self
}
#[must_use]
pub fn sampler(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
});
self
}
#[must_use]
pub fn comparison_sampler(mut self, visibility: wgpu::ShaderStages) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.entries.len() as u32,
visibility,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
});
self
}
#[must_use]
pub fn custom(mut self, entry: wgpu::BindGroupLayoutEntry) -> Self {
self.entries.push(entry);
self
}
#[must_use]
pub fn entries(&self) -> &[wgpu::BindGroupLayoutEntry] {
&self.entries
}
#[must_use]
pub fn into_entries(self) -> Vec<wgpu::BindGroupLayoutEntry> {
self.entries
}
#[must_use]
pub fn build(self, device: &wgpu::Device, label: &str) -> wgpu::BindGroupLayout {
tracing::debug!(
label,
bindings = self.entries.len(),
"creating bind group layout"
);
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(label),
entries: &self.entries,
})
}
}
impl Default for BindGroupLayoutBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_empty() {
let builder = BindGroupLayoutBuilder::new();
assert!(builder.entries().is_empty());
}
#[test]
fn builder_auto_binding_index() {
let builder = BindGroupLayoutBuilder::new()
.uniform_buffer(wgpu::ShaderStages::VERTEX)
.texture_2d(wgpu::ShaderStages::FRAGMENT)
.sampler(wgpu::ShaderStages::FRAGMENT);
let entries = builder.entries();
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].binding, 0);
assert_eq!(entries[1].binding, 1);
assert_eq!(entries[2].binding, 2);
}
#[test]
fn builder_storage_variants() {
let builder = BindGroupLayoutBuilder::new()
.storage_buffer(wgpu::ShaderStages::COMPUTE)
.storage_buffer_readonly(wgpu::ShaderStages::COMPUTE);
let entries = builder.entries();
assert_eq!(entries.len(), 2);
}
#[test]
fn builder_into_entries() {
let entries = BindGroupLayoutBuilder::new()
.uniform_buffer(wgpu::ShaderStages::VERTEX)
.into_entries();
assert_eq!(entries.len(), 1);
}
#[test]
fn builder_default_trait() {
let builder = BindGroupLayoutBuilder::default();
assert!(builder.entries().is_empty());
}
#[test]
fn builder_comparison_sampler() {
let builder = BindGroupLayoutBuilder::new()
.texture_depth_2d(wgpu::ShaderStages::FRAGMENT)
.comparison_sampler(wgpu::ShaderStages::FRAGMENT);
assert_eq!(builder.entries().len(), 2);
}
fn try_gpu() -> Option<wgpu::Device> {
let ctx = pollster::block_on(crate::context::GpuContext::new()).ok()?;
Some(ctx.device)
}
#[test]
fn gpu_build_layout() {
let Some(device) = try_gpu() else { return };
let custom_entry = wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
};
let builder = BindGroupLayoutBuilder::new()
.uniform_buffer_dynamic(wgpu::ShaderStages::VERTEX)
.texture_cube(wgpu::ShaderStages::FRAGMENT)
.comparison_sampler(wgpu::ShaderStages::FRAGMENT)
.custom(custom_entry);
assert_eq!(builder.entries().len(), 4);
let _layout = builder.build(&device, "test");
}
#[test]
fn gpu_all_presets() {
let Some(device) = try_gpu() else { return };
let builder = BindGroupLayoutBuilder::new()
.uniform_buffer(wgpu::ShaderStages::VERTEX)
.uniform_buffer_dynamic(wgpu::ShaderStages::VERTEX)
.storage_buffer(wgpu::ShaderStages::COMPUTE)
.storage_buffer_readonly(wgpu::ShaderStages::COMPUTE)
.texture_2d(wgpu::ShaderStages::FRAGMENT)
.texture_cube(wgpu::ShaderStages::FRAGMENT)
.texture_depth_2d(wgpu::ShaderStages::FRAGMENT)
.sampler(wgpu::ShaderStages::FRAGMENT)
.comparison_sampler(wgpu::ShaderStages::FRAGMENT);
assert_eq!(builder.entries().len(), 9);
let _layout = builder.build(&device, "all_presets");
}
}