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}