wde_wgpu/pipelines/
bind_group.rs

1//! Bind groups bind buffers, textures, and samplers into a shader-visible layout.
2//!
3//! # Overview
4//! 1. Create a [`BindGroupLayout`] that matches your WGSL `@group` / `@binding` declarations.
5//! 2. Populate GPU resources and build the concrete [`wgpu::BindGroup`].
6//! 3. Set the bind group inside a render or compute pass.
7//!
8//! # Example: PBR material
9//! ```rust,no_run
10//! use wde_wgpu::{
11//!     bind_group::*,
12//!     buffer::{Buffer, BufferUsage},
13//!     instance::RenderInstanceData,
14//!     render_pipeline::ShaderStages,
15//!     texture::{Texture, TextureFormat, TextureUsages},
16//! };
17//!
18//! let uniform_buffer = Buffer::new(instance, "material-uniform", 64, BufferUsage::UNIFORM, None);
19//! let albedo = Texture::new(instance, "albedo", (512, 512), TextureFormat::Rgba8Unorm, TextureUsages::TEXTURE_BINDING, 1, 1);
20//! let normal = Texture::new(instance, "normal", (512, 512), TextureFormat::Rgba8Unorm, TextureUsages::TEXTURE_BINDING, 1, 1);
21//!
22//! let layout = BindGroupLayout::new("material-layout", |builder| {
23//!     builder.add_buffer(0, ShaderStages::FRAGMENT, BufferBindingType::Uniform);
24//!     builder.add_texture_view(1, ShaderStages::FRAGMENT);
25//!     builder.add_texture_sampler(2, ShaderStages::FRAGMENT);
26//!     builder.add_texture_view(3, ShaderStages::FRAGMENT);
27//!     builder.add_texture_sampler(4, ShaderStages::FRAGMENT);
28//! });
29//! let wgpu_layout = layout.build(instance);
30//!
31//! let bind_group = BindGroup::build(
32//!     "material-bind-group",
33//!     instance,
34//!     &wgpu_layout,
35//!     &vec![
36//!         BindGroup::buffer(0, &uniform_buffer),
37//!         BindGroup::texture_view(1, &albedo),
38//!         BindGroup::texture_sampler(2, &albedo),
39//!         BindGroup::texture_view(3, &normal),
40//!         BindGroup::texture_sampler(4, &normal),
41//!     ],
42//! );
43//!
44//! (wgpu_layout, bind_group)
45//! ```
46//!
47//! Depth-only layouts follow the same pattern using `add_depth_texture_view` and
48//! `add_depth_texture_sampler`.
49//!
50//! # Tips
51//! - Keep per-frame data in group 0, per-material in group 1, and per-object in group 2 to
52//!   minimize rebinding during draws.
53//! - The order of `set_bind_groups` on pipelines must match the order of layouts you provide
54//!   here.
55
56use futures_lite::future;
57use wde_logger::prelude::*;
58
59use crate::{
60    buffer::Buffer,
61    instance::{RenderError, RenderInstanceData},
62    render_pipeline::ShaderStages,
63    texture::{Texture, TextureFormat}
64};
65
66// The wgpu bind group type.
67#[derive(Clone)]
68pub struct BindGroup(pub Option<wgpu::BindGroup>);
69impl BindGroup {
70    pub fn empty() -> Self {
71        BindGroup(None)
72    }
73}
74impl From<wgpu::BindGroup> for BindGroup {
75    fn from(value: wgpu::BindGroup) -> Self {
76        BindGroup(Some(value))
77    }
78}
79
80/// The buffer binding type.
81pub type BufferBindingType = wgpu::BufferBindingType;
82
83/// The wgpu bind group layout type.
84pub type WgpuBindGroupLayout = wgpu::BindGroupLayout;
85
86/// The wgpu bind group entry type.
87pub type BindGroupEntry<'a> = wgpu::BindGroupEntry<'a>;
88
89/// Builder for a bind group layout.
90#[derive(Debug, Clone)]
91pub struct BindGroupLayoutBuilder {
92    layout_entries: Vec<wgpu::BindGroupLayoutEntry>
93}
94
95impl BindGroupLayoutBuilder {
96    /// Add a buffer to the bind group.
97    ///
98    /// # Arguments
99    ///
100    /// * `binding` - The binding index of the buffer.
101    /// * `visibility` - The shader stages that can access the buffer.
102    /// * `binding_type` - The type of the buffer binding.
103    pub fn add_buffer(
104        &mut self,
105        binding: u32,
106        visibility: ShaderStages,
107        binding_type: BufferBindingType
108    ) -> &mut Self {
109        // Create bind group layout
110        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
111            binding,
112            visibility,
113            ty: wgpu::BindingType::Buffer {
114                has_dynamic_offset: false,
115                min_binding_size: None,
116                ty: binding_type
117            },
118            count: None
119        });
120
121        self
122    }
123
124    /// Add a texture to the bind group.
125    ///
126    /// # Arguments
127    ///
128    /// * `binding` - The binding index of the texture.
129    /// * `visibility` - The shader stages that can access the texture.
130    /// * `multisampled` - Whether the texture is multisampled.
131    pub fn add_texture_view(
132        &mut self,
133        binding: u32,
134        visibility: ShaderStages,
135        multisampled: bool
136    ) -> &mut Self {
137        // Create bind group layout
138        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
139            binding,
140            visibility,
141            ty: wgpu::BindingType::Texture {
142                multisampled,
143                view_dimension: wgpu::TextureViewDimension::D2,
144                sample_type: wgpu::TextureSampleType::Float {
145                    filterable: !multisampled
146                }
147            },
148            count: None
149        });
150
151        self
152    }
153
154    /// Add a storage texture view to the bind group.
155    /// This is used for textures that will be read and written to in a compute shader.
156    ///
157    /// # Arguments
158    ///
159    /// * `binding` - The binding index of the texture.
160    /// * `format` - The format of the texture.
161    /// * `atomic` - Whether the texture will be used for atomic operations.
162    pub fn add_storage_texture_view(&mut self, binding: u32, format: TextureFormat) -> &mut Self {
163        // Create bind group layout
164        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
165            binding,
166            visibility: ShaderStages::COMPUTE,
167            ty: wgpu::BindingType::StorageTexture {
168                access: wgpu::StorageTextureAccess::ReadWrite,
169                view_dimension: wgpu::TextureViewDimension::D2,
170                format
171            },
172            count: None
173        });
174
175        self
176    }
177
178    /// Add a texture array to the bind group.
179    ///
180    /// # Arguments
181    ///
182    /// * `binding` - The binding index of the texture array.
183    /// * `visibility` - The shader stages that can access the texture array.
184    pub fn add_texture_array_view(&mut self, binding: u32, visibility: ShaderStages) -> &mut Self {
185        // Create bind group layout
186        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
187            binding,
188            visibility,
189            ty: wgpu::BindingType::Texture {
190                multisampled: false,
191                view_dimension: wgpu::TextureViewDimension::D2Array,
192                sample_type: wgpu::TextureSampleType::Float { filterable: true }
193            },
194            count: None
195        });
196
197        self
198    }
199
200    /// Add a depth texture to the bind group.
201    ///
202    /// # Arguments
203    ///
204    /// * `binding` - The binding index of the texture.
205    /// * `visibility` - The shader stages that can access the texture.
206    /// * `multisampled` - Whether the texture is multisampled.
207    pub fn add_depth_texture_view(
208        &mut self,
209        binding: u32,
210        visibility: ShaderStages,
211        multisampled: bool
212    ) -> &mut Self {
213        // Create bind group layout
214        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
215            binding,
216            visibility,
217            ty: wgpu::BindingType::Texture {
218                multisampled,
219                view_dimension: wgpu::TextureViewDimension::D2,
220                sample_type: wgpu::TextureSampleType::Depth
221            },
222            count: None
223        });
224
225        self
226    }
227
228    /// Add a texture to the bind group.
229    ///
230    /// # Arguments
231    ///
232    /// * `binding` - The binding index of the texture sampler.
233    /// * `visibility` - The shader stages that can access the sampler.
234    pub fn add_texture_sampler(&mut self, binding: u32, visibility: ShaderStages) -> &mut Self {
235        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
236            binding,
237            visibility,
238            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
239            count: None
240        });
241
242        self
243    }
244
245    /// Add a depth texture sampler to the bind group.
246    ///
247    /// # Arguments
248    ///
249    /// * `binding` - The binding index of the texture sampler.
250    /// * `visibility` - The shader stages that can access the sampler.
251    pub fn add_depth_texture_sampler(
252        &mut self,
253        binding: u32,
254        visibility: ShaderStages
255    ) -> &mut Self {
256        self.layout_entries.push(wgpu::BindGroupLayoutEntry {
257            binding,
258            visibility,
259            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
260            count: None
261        });
262
263        self
264    }
265}
266
267/// Structure for a bind group layout.
268/// Stores the layout description and builder data.
269#[derive(Clone)]
270pub struct BindGroupLayout {
271    // Bind group description
272    pub label: String,
273    // Access to builder data
274    pub builder: BindGroupLayoutBuilder
275}
276impl BindGroupLayout {
277    /// Create a new bind group layout.
278    ///
279    /// # Arguments
280    ///
281    /// * `label` - The label of the bind group layout. Note that this label is only for GPU debugging purposes and is only used if GPU debugging is enabled.
282    /// * `build_func` - The function to build the bind group layout.
283    pub fn new(label: &str, build_func: impl FnOnce(&mut BindGroupLayoutBuilder)) -> Self {
284        let mut builder = BindGroupLayoutBuilder {
285            layout_entries: Vec::new()
286        };
287
288        build_func(&mut builder);
289
290        BindGroupLayout {
291            label: label.to_string(),
292            builder
293        }
294    }
295
296    /// Create a dummy empty bind group layout. This can be used for render bindings that don't need a bind group.
297    pub fn empty() -> Self {
298        BindGroupLayout {
299            label: "".to_string(),
300            builder: BindGroupLayoutBuilder {
301                layout_entries: Vec::new()
302            }
303        }
304    }
305
306    /// Build the bind group layout.
307    ///
308    /// # Arguments
309    ///
310    /// * `instance` - The render instance data.
311    pub fn build(&self, instance: &RenderInstanceData) -> Result<WgpuBindGroupLayout, RenderError> {
312        event!(
313            LogLevel::TRACE,
314            "Creating bind group layout: {}.",
315            self.label
316        );
317
318        // Add validation to intercept potential errors in bind group layout creation
319        instance
320            .device
321            .push_error_scope(wgpu::ErrorFilter::Validation);
322
323        // Create bind group layout
324        let layout = instance
325            .device
326            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
327                label: Some(format!("{}-bind-group-layout", self.label).as_str()),
328                entries: &self.builder.layout_entries
329            });
330
331        // Check for errors
332        let mut res = Ok(layout);
333        future::block_on(async {
334            let error = instance.device.pop_error_scope().await;
335            match error {
336                Some(wgpu::Error::Validation {
337                    source,
338                    description
339                }) => {
340                    error!(
341                        self.label,
342                        "Failed to create bind group layout with source error: {:#?}. Description: {}. Bind group layout description: {:#?}.",
343                        source,
344                        description,
345                        self.builder.layout_entries
346                    );
347                    res = Err(RenderError::CannotCreateBindGroupLayout);
348                }
349                Some(e) => {
350                    error!(
351                        self.label,
352                        "Failed to create bind group layout with unexpected error: {:#?}. Bind group layout description: {:#?}.",
353                        e,
354                        self.builder.layout_entries
355                    );
356                    res = Err(RenderError::CannotCreateBindGroupLayout);
357                }
358                None => ()
359            }
360        });
361        res
362    }
363}
364
365/// Structure for a bind group.
366pub struct BindGroupBuilder;
367impl BindGroupBuilder {
368    /// Build a bind group.
369    ///
370    /// # Arguments
371    ///
372    /// * `label` - The label of the bind group. Note that this label is only for GPU debugging purposes and is only used if GPU debugging is enabled.
373    /// * `instance` - The render instance data.
374    /// * `layout` - The bind group layout.
375    /// * `entries` - The bind group entries.
376    pub fn build(
377        label: &str,
378        instance: &RenderInstanceData,
379        layout: &wgpu::BindGroupLayout,
380        entries: &Vec<wgpu::BindGroupEntry>
381    ) -> Result<BindGroup, RenderError> {
382        event!(LogLevel::TRACE, "Creating bind group: {}.", label);
383
384        // Add validation to intercept potential errors in bind group creation
385        instance
386            .device
387            .push_error_scope(wgpu::ErrorFilter::Validation);
388
389        // Create bind group
390        let bind_group = instance
391            .device
392            .create_bind_group(&wgpu::BindGroupDescriptor {
393                label: Some(format!("{}-bind-group", label).as_str()),
394                layout,
395                entries
396            });
397
398        // Check for errors
399        let mut res = Ok(bind_group);
400        future::block_on(async {
401            let error = instance.device.pop_error_scope().await;
402            match error {
403                Some(wgpu::Error::Validation {
404                    source,
405                    description
406                }) => {
407                    error!(
408                        label,
409                        "Failed to create bind group with source error: {:#?}. Description: {}.",
410                        source,
411                        description
412                    );
413                    res = Err(RenderError::CannotCreateBindGroup);
414                }
415                Some(e) => {
416                    error!(
417                        label,
418                        "Failed to create bind group with unexpected error: {:#?}.", e
419                    );
420                    res = Err(RenderError::CannotCreateBindGroup);
421                }
422                None => ()
423            }
424        });
425        res.map(BindGroup::from)
426    }
427
428    /// Add a buffer to the bind group.
429    ///
430    /// # Arguments
431    ///
432    /// * `binding` - The binding index of the buffer.
433    /// * `buffer` - The buffer to add to the bind group.
434    pub fn buffer(binding: u32, buffer: &'_ Buffer) -> wgpu::BindGroupEntry<'_> {
435        wgpu::BindGroupEntry {
436            binding,
437            resource: buffer.buffer.as_entire_binding()
438        }
439    }
440
441    /// Add a texture view to the bind group.
442    ///
443    /// # Arguments
444    ///
445    /// * `binding` - The binding index of the texture.
446    /// * `texture` - The texture to add to the bind group.
447    pub fn texture_view(binding: u32, texture: &'_ Texture) -> wgpu::BindGroupEntry<'_> {
448        wgpu::BindGroupEntry {
449            binding,
450            resource: wgpu::BindingResource::TextureView(&texture.view)
451        }
452    }
453
454    /// Add a texture sampler to the bind group.
455    ///
456    /// # Arguments
457    ///
458    /// * `binding` - The binding index of the texture.
459    /// * `texture` - The texture to add to the bind group.
460    pub fn texture_sampler(binding: u32, texture: &'_ Texture) -> wgpu::BindGroupEntry<'_> {
461        wgpu::BindGroupEntry {
462            binding,
463            resource: wgpu::BindingResource::Sampler(&texture.sampler)
464        }
465    }
466}