Skip to main content

pebble/graphics/pipeline/
binding.rs

1use crate::graphics::{
2    render::Backend,
3    types::{StorageTextureAccess, TextureFormat, TextureSampleType, TextureViewDimension, flags::ShaderStages},
4};
5
6/// What kind of resource one bind group slot expects — texture, sampler, or
7/// buffer, with wgpu-level details (visibility, dynamic offsets, etc).
8/// Usually built via a constructor (`texture_2d`, `uniform_buffer`, ...)
9/// rather than the variants directly.
10#[derive(Copy, Clone, PartialEq, Eq, Hash)]
11pub enum BindingKind {
12    Texture {
13        visibility: ShaderStages,
14        sample_type: TextureSampleType,
15        view_dimension: TextureViewDimension,
16        multisampled: bool,
17    },
18    StorageTexture {
19        visibility: ShaderStages,
20        format: TextureFormat,
21        access: StorageTextureAccess,
22        view_dimension: TextureViewDimension,
23    },
24    Sampler { visibility: ShaderStages },
25    ComparisonSampler { visibility: ShaderStages },
26    UniformBuffer {
27        visibility: ShaderStages,
28        has_dynamic_offset: bool,
29        min_binding_size: Option<u64>,
30    },
31    StorageBufferReadOnly {
32        visibility: ShaderStages,
33        has_dynamic_offset: bool,
34        min_binding_size: Option<u64>,
35    },
36    StorageBufferReadWrite {
37        visibility: ShaderStages,
38        has_dynamic_offset: bool,
39        min_binding_size: Option<u64>,
40    },
41}
42
43impl BindingKind {
44    pub fn texture_2d(visibility: ShaderStages) -> Self {
45        Self::Texture {
46            visibility,
47            sample_type: TextureSampleType::Float { filterable: true },
48            view_dimension: TextureViewDimension::D2,
49            multisampled: false,
50        }
51    }
52
53    pub fn texture_2d_array(visibility: ShaderStages) -> Self {
54        Self::Texture {
55            visibility,
56            sample_type: TextureSampleType::Float { filterable: true },
57            view_dimension: TextureViewDimension::D2Array,
58            multisampled: false,
59        }
60    }
61
62    pub fn texture_cubemap(visibility: ShaderStages) -> Self {
63        Self::Texture {
64            visibility,
65            sample_type: TextureSampleType::Float { filterable: true },
66            view_dimension: TextureViewDimension::Cube,
67            multisampled: false,
68        }
69    }
70
71    pub fn storage_texture(
72        visibility: ShaderStages,
73        format: TextureFormat,
74        access: StorageTextureAccess,
75        view_dimension: TextureViewDimension,
76    ) -> Self {
77        Self::StorageTexture { visibility, format, access, view_dimension }
78    }
79
80    pub fn sampler(visibility: ShaderStages) -> Self {
81        Self::Sampler { visibility }
82    }
83
84    pub fn comparison_sampler(visibility: ShaderStages) -> Self {
85        Self::ComparisonSampler { visibility }
86    }
87
88    pub fn uniform_buffer(visibility: ShaderStages) -> Self {
89        Self::UniformBuffer { visibility, has_dynamic_offset: false, min_binding_size: None }
90    }
91
92    pub fn dynamic_uniform_buffer(visibility: ShaderStages, element_size: u64) -> Self {
93        Self::UniformBuffer {
94            visibility,
95            has_dynamic_offset: true,
96            min_binding_size: Some(element_size),
97        }
98    }
99
100    pub fn storage_buffer_read_only(visibility: ShaderStages) -> Self {
101        Self::StorageBufferReadOnly { visibility, has_dynamic_offset: false, min_binding_size: None }
102    }
103
104    pub fn storage_buffer_read_write(visibility: ShaderStages) -> Self {
105        Self::StorageBufferReadWrite { visibility, has_dynamic_offset: false, min_binding_size: None }
106    }
107
108    pub fn dynamic_storage_buffer(visibility: ShaderStages, element_size: u64, read_only: bool) -> Self {
109        let has_dynamic_offset = true;
110        let min_binding_size = Some(element_size);
111        if read_only {
112            Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
113        } else {
114            Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
115        }
116    }
117
118    pub fn visibility(&self) -> ShaderStages {
119        match self {
120            Self::Texture { visibility, .. }
121            | Self::StorageTexture { visibility, .. }
122            | Self::Sampler { visibility }
123            | Self::ComparisonSampler { visibility }
124            | Self::UniformBuffer { visibility, .. }
125            | Self::StorageBufferReadOnly { visibility, .. }
126            | Self::StorageBufferReadWrite { visibility, .. } => *visibility,
127        }
128    }
129
130    pub(crate) fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
131        match self {
132            Self::Texture { visibility, sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
133                binding,
134                visibility: (*visibility).into(),
135                ty: wgpu::BindingType::Texture {
136                    sample_type: (*sample_type).into(),
137                    view_dimension: (*view_dimension).into(),
138                    multisampled: *multisampled,
139                },
140                count: None,
141            },
142            Self::StorageTexture { visibility, format, access, view_dimension } => wgpu::BindGroupLayoutEntry {
143                binding,
144                visibility: (*visibility).into(),
145                ty: wgpu::BindingType::StorageTexture {
146                    access: (*access).into(),
147                    format: (*format).into(),
148                    view_dimension: (*view_dimension).into(),
149                },
150                count: None,
151            },
152            Self::Sampler { visibility } => wgpu::BindGroupLayoutEntry {
153                binding,
154                visibility: (*visibility).into(),
155                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
156                count: None,
157            },
158            Self::ComparisonSampler { visibility } => wgpu::BindGroupLayoutEntry {
159                binding,
160                visibility: (*visibility).into(),
161                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
162                count: None,
163            },
164            Self::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
165                binding,
166                visibility: (*visibility).into(),
167                ty: wgpu::BindingType::Buffer {
168                    ty: wgpu::BufferBindingType::Uniform,
169                    has_dynamic_offset: *has_dynamic_offset,
170                    min_binding_size: min_binding_size.and_then(wgpu::BufferSize::new),
171                },
172                count: None,
173            },
174            Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
175                binding,
176                visibility: (*visibility).into(),
177                ty: wgpu::BindingType::Buffer {
178                    ty: wgpu::BufferBindingType::Storage { read_only: true },
179                    has_dynamic_offset: *has_dynamic_offset,
180                    min_binding_size: min_binding_size.and_then(wgpu::BufferSize::new),
181                },
182                count: None,
183            },
184            Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
185                binding,
186                visibility: (*visibility).into(),
187                ty: wgpu::BindingType::Buffer {
188                    ty: wgpu::BufferBindingType::Storage { read_only: false },
189                    has_dynamic_offset: *has_dynamic_offset,
190                    min_binding_size: min_binding_size.and_then(wgpu::BufferSize::new),
191                },
192                count: None,
193            },
194        }
195    }
196}
197
198/// A compiled GPU bind group layout, wrapping `wgpu::BindGroupLayout`.
199#[derive(Clone)]
200pub struct BindGroupLayout(wgpu::BindGroupLayout);
201
202impl BindGroupLayout {
203    pub(crate) fn raw(&self) -> &wgpu::BindGroupLayout {
204        &self.0
205    }
206}
207
208/// One named slot in a bind group layout — `name` is how a
209/// [`BindGroupParams`](super::params::BindGroupParams) matches its values
210/// to the right binding index.
211#[derive(Clone, PartialEq, Eq, Hash)]
212pub struct BindingEntry {
213    pub name: &'static str,
214    pub binding: u32,
215    pub kind: BindingKind,
216}
217
218/// Builds a [`BindGroupLayout`] from named [`BindingEntry`] slots.
219#[derive(Default)]
220pub struct BindGroupLayoutBuilder<'a> {
221    label: Option<&'a str>,
222    entries: Vec<BindingEntry>,
223}
224
225impl<'a> BindGroupLayoutBuilder<'a> {
226    pub fn new() -> Self {
227        Self::default()
228    }
229
230    pub fn with_label(mut self, label: impl Into<Option<&'a str>>) -> Self {
231        self.label = label.into();
232        self
233    }
234
235    pub fn with_entry(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
236        self.entries.push(BindingEntry { name, binding, kind });
237        self
238    }
239
240    pub fn with_entries(mut self, entries: impl IntoIterator<Item = BindingEntry>) -> Self {
241        self.entries.extend(entries);
242        self
243    }
244
245    pub fn build(self, backend: &Backend) -> BindGroupLayout {
246        let layout_entries: Vec<_> =
247            self.entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
248
249        let mut seen = std::collections::HashSet::new();
250        for e in &self.entries {
251            if !seen.insert(e.binding) {
252                panic!(
253                    "binding {} assigned more than once building bind group layout{} (entry '{}')",
254                    e.binding,
255                    self.label.map(|l| format!(" '{l}'")).unwrap_or_default(),
256                    e.name
257                );
258            }
259        }
260
261        BindGroupLayout(backend.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
262            label: self.label,
263            entries: &layout_entries,
264        }))
265    }
266}