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 texture to the bind group.
155 ///
156 /// # Arguments
157 ///
158 /// * `binding` - The binding index of the texture.
159 /// * `visibility` - The shader stages that can access the texture.
160 /// * `multisampled` - Whether the texture is multisampled.
161 /// * `filterable` - Whether the texture is filterable (only relevant if not multisampled).
162 pub fn add_texture_view_filterable(
163 &mut self,
164 binding: u32,
165 visibility: ShaderStages,
166 multisampled: bool,
167 filterable: bool
168 ) -> &mut Self {
169 // Create bind group layout
170 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
171 binding,
172 visibility,
173 ty: wgpu::BindingType::Texture {
174 multisampled,
175 view_dimension: wgpu::TextureViewDimension::D2,
176 sample_type: wgpu::TextureSampleType::Float { filterable }
177 },
178 count: None
179 });
180
181 self
182 }
183
184 /// Add a storage texture view to the bind group.
185 /// This is used for textures that will be read and written to in a compute shader.
186 ///
187 /// # Arguments
188 ///
189 /// * `binding` - The binding index of the texture.
190 /// * `format` - The format of the texture.
191 /// * `atomic` - Whether the texture will be used for atomic operations.
192 pub fn add_storage_texture_view(&mut self, binding: u32, format: TextureFormat) -> &mut Self {
193 // Create bind group layout
194 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
195 binding,
196 visibility: ShaderStages::COMPUTE,
197 ty: wgpu::BindingType::StorageTexture {
198 access: wgpu::StorageTextureAccess::ReadWrite,
199 view_dimension: wgpu::TextureViewDimension::D2,
200 format
201 },
202 count: None
203 });
204
205 self
206 }
207
208 /// Add a storage texture array view to the bind group (for compute read/write of array layers).
209 pub fn add_storage_texture_array_view(
210 &mut self,
211 binding: u32,
212 format: TextureFormat
213 ) -> &mut Self {
214 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
215 binding,
216 visibility: ShaderStages::COMPUTE,
217 ty: wgpu::BindingType::StorageTexture {
218 access: wgpu::StorageTextureAccess::ReadWrite,
219 view_dimension: wgpu::TextureViewDimension::D2Array,
220 format
221 },
222 count: None
223 });
224 self
225 }
226
227 /// Add a texture array to the bind group.
228 ///
229 /// # Arguments
230 ///
231 /// * `binding` - The binding index of the texture array.
232 /// * `visibility` - The shader stages that can access the texture array.
233 pub fn add_texture_array_view(&mut self, binding: u32, visibility: ShaderStages) -> &mut Self {
234 // Create bind group layout
235 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
236 binding,
237 visibility,
238 ty: wgpu::BindingType::Texture {
239 multisampled: false,
240 view_dimension: wgpu::TextureViewDimension::D2Array,
241 sample_type: wgpu::TextureSampleType::Float { filterable: true }
242 },
243 count: None
244 });
245
246 self
247 }
248
249 /// Add a depth texture to the bind group.
250 ///
251 /// # Arguments
252 ///
253 /// * `binding` - The binding index of the texture.
254 /// * `visibility` - The shader stages that can access the texture.
255 /// * `multisampled` - Whether the texture is multisampled.
256 pub fn add_depth_texture_view(
257 &mut self,
258 binding: u32,
259 visibility: ShaderStages,
260 multisampled: bool
261 ) -> &mut Self {
262 // Create bind group layout
263 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
264 binding,
265 visibility,
266 ty: wgpu::BindingType::Texture {
267 multisampled,
268 view_dimension: wgpu::TextureViewDimension::D2,
269 sample_type: wgpu::TextureSampleType::Depth
270 },
271 count: None
272 });
273
274 self
275 }
276
277 /// Add a texture to the bind group.
278 ///
279 /// # Arguments
280 ///
281 /// * `binding` - The binding index of the texture sampler.
282 /// * `visibility` - The shader stages that can access the sampler.
283 pub fn add_texture_sampler(&mut self, binding: u32, visibility: ShaderStages) -> &mut Self {
284 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
285 binding,
286 visibility,
287 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
288 count: None
289 });
290
291 self
292 }
293
294 /// Add a depth texture sampler to the bind group.
295 ///
296 /// # Arguments
297 ///
298 /// * `binding` - The binding index of the texture sampler.
299 /// * `visibility` - The shader stages that can access the sampler.
300 pub fn add_depth_texture_sampler(
301 &mut self,
302 binding: u32,
303 visibility: ShaderStages
304 ) -> &mut Self {
305 self.layout_entries.push(wgpu::BindGroupLayoutEntry {
306 binding,
307 visibility,
308 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
309 count: None
310 });
311
312 self
313 }
314}
315
316/// Structure for a bind group layout.
317/// Stores the layout description and builder data.
318#[derive(Clone)]
319pub struct BindGroupLayout {
320 // Bind group description
321 pub label: String,
322 // Access to builder data
323 pub builder: BindGroupLayoutBuilder
324}
325impl BindGroupLayout {
326 /// Create a new bind group layout.
327 ///
328 /// # Arguments
329 ///
330 /// * `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.
331 /// * `build_func` - The function to build the bind group layout.
332 pub fn new(label: &str, build_func: impl FnOnce(&mut BindGroupLayoutBuilder)) -> Self {
333 let mut builder = BindGroupLayoutBuilder {
334 layout_entries: Vec::new()
335 };
336
337 build_func(&mut builder);
338
339 BindGroupLayout {
340 label: label.to_string(),
341 builder
342 }
343 }
344
345 /// Create a dummy empty bind group layout. This can be used for render bindings that don't need a bind group.
346 pub fn empty() -> Self {
347 BindGroupLayout {
348 label: "".to_string(),
349 builder: BindGroupLayoutBuilder {
350 layout_entries: Vec::new()
351 }
352 }
353 }
354
355 /// Build the bind group layout.
356 ///
357 /// # Arguments
358 ///
359 /// * `instance` - The render instance data.
360 pub fn build(&self, instance: &RenderInstanceData) -> Result<WgpuBindGroupLayout, RenderError> {
361 event!(
362 LogLevel::TRACE,
363 "Creating bind group layout: {}.",
364 self.label
365 );
366
367 // Add validation to intercept potential errors in bind group layout creation
368 instance
369 .device
370 .push_error_scope(wgpu::ErrorFilter::Validation);
371
372 // Create bind group layout
373 let layout = instance
374 .device
375 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
376 label: Some(format!("{}-bind-group-layout", self.label).as_str()),
377 entries: &self.builder.layout_entries
378 });
379
380 // Check for errors
381 let mut res = Ok(layout);
382 future::block_on(async {
383 let error = instance.device.pop_error_scope().await;
384 match error {
385 Some(wgpu::Error::Validation {
386 source,
387 description
388 }) => {
389 error!(
390 self.label,
391 "Failed to create bind group layout with source error: {:#?}. Description: {}. Bind group layout description: {:#?}.",
392 source,
393 description,
394 self.builder.layout_entries
395 );
396 res = Err(RenderError::CannotCreateBindGroupLayout);
397 }
398 Some(e) => {
399 error!(
400 self.label,
401 "Failed to create bind group layout with unexpected error: {:#?}. Bind group layout description: {:#?}.",
402 e,
403 self.builder.layout_entries
404 );
405 res = Err(RenderError::CannotCreateBindGroupLayout);
406 }
407 None => ()
408 }
409 });
410 res
411 }
412}
413
414/// Structure for a bind group.
415pub struct BindGroupBuilder;
416impl BindGroupBuilder {
417 /// Build a bind group.
418 ///
419 /// # Arguments
420 ///
421 /// * `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.
422 /// * `instance` - The render instance data.
423 /// * `layout` - The bind group layout.
424 /// * `entries` - The bind group entries.
425 pub fn build(
426 label: &str,
427 instance: &RenderInstanceData,
428 layout: &wgpu::BindGroupLayout,
429 entries: &Vec<wgpu::BindGroupEntry>
430 ) -> Result<BindGroup, RenderError> {
431 event!(LogLevel::TRACE, "Creating bind group: {}.", label);
432
433 // Add validation to intercept potential errors in bind group creation
434 instance
435 .device
436 .push_error_scope(wgpu::ErrorFilter::Validation);
437
438 // Create bind group
439 let bind_group = instance
440 .device
441 .create_bind_group(&wgpu::BindGroupDescriptor {
442 label: Some(format!("{}-bind-group", label).as_str()),
443 layout,
444 entries
445 });
446
447 // Check for errors
448 let mut res = Ok(bind_group);
449 future::block_on(async {
450 let error = instance.device.pop_error_scope().await;
451 match error {
452 Some(wgpu::Error::Validation {
453 source,
454 description
455 }) => {
456 error!(
457 label,
458 "Failed to create bind group with source error: {:#?}. Description: {}.",
459 source,
460 description
461 );
462 res = Err(RenderError::CannotCreateBindGroup);
463 }
464 Some(e) => {
465 error!(
466 label,
467 "Failed to create bind group with unexpected error: {:#?}.", e
468 );
469 res = Err(RenderError::CannotCreateBindGroup);
470 }
471 None => ()
472 }
473 });
474 res.map(BindGroup::from)
475 }
476
477 /// Add a buffer to the bind group.
478 ///
479 /// # Arguments
480 ///
481 /// * `binding` - The binding index of the buffer.
482 /// * `buffer` - The buffer to add to the bind group.
483 pub fn buffer(binding: u32, buffer: &'_ Buffer) -> wgpu::BindGroupEntry<'_> {
484 wgpu::BindGroupEntry {
485 binding,
486 resource: buffer.buffer.as_entire_binding()
487 }
488 }
489
490 /// Add a texture view to the bind group.
491 ///
492 /// # Arguments
493 ///
494 /// * `binding` - The binding index of the texture.
495 /// * `texture` - The texture to add to the bind group.
496 pub fn texture_view(binding: u32, texture: &'_ Texture) -> wgpu::BindGroupEntry<'_> {
497 wgpu::BindGroupEntry {
498 binding,
499 resource: wgpu::BindingResource::TextureView(&texture.view)
500 }
501 }
502
503 /// Add a texture sampler to the bind group.
504 ///
505 /// # Arguments
506 ///
507 /// * `binding` - The binding index of the texture.
508 /// * `texture` - The texture to add to the bind group.
509 pub fn texture_sampler(binding: u32, texture: &'_ Texture) -> wgpu::BindGroupEntry<'_> {
510 wgpu::BindGroupEntry {
511 binding,
512 resource: wgpu::BindingResource::Sampler(&texture.sampler)
513 }
514 }
515}