bevy_render/render_resource/
bind_group_layout_entries.rs

1use bevy_utils::all_tuples_with_size;
2use std::num::NonZeroU32;
3use wgpu::{BindGroupLayoutEntry, BindingType, ShaderStages};
4
5/// Helper for constructing bind group layouts.
6///
7/// Allows constructing the layout's entries as:
8/// ```ignore (render_device cannot be easily accessed)
9/// let layout = render_device.create_bind_group_layout(
10///     "my_bind_group_layout",
11///     &BindGroupLayoutEntries::with_indices(
12///         // The layout entries will only be visible in the fragment stage
13///         ShaderStages::FRAGMENT,
14///         (
15///             // Screen texture
16///             (2, texture_2d(TextureSampleType::Float { filterable: true })),
17///             // Sampler
18///             (3, sampler(SamplerBindingType::Filtering)),
19///         ),
20///     ),
21/// );
22/// ```
23///
24/// instead of
25///
26/// ```ignore (render_device cannot be easily accessed)
27/// let layout = render_device.create_bind_group_layout(
28///     "my_bind_group_layout",
29///     &[
30///         // Screen texture
31///         BindGroupLayoutEntry {
32///             binding: 2,
33///             visibility: ShaderStages::FRAGMENT,
34///             ty: BindingType::Texture {
35///                 sample_type: TextureSampleType::Float { filterable: true },
36///                 view_dimension: TextureViewDimension::D2,
37///                 multisampled: false,
38///             },
39///             count: None,
40///         },
41///         // Sampler
42///         BindGroupLayoutEntry {
43///             binding: 3,
44///             visibility: ShaderStages::FRAGMENT,
45///             ty: BindingType::Sampler(SamplerBindingType::Filtering),
46///             count: None,
47///         },
48///     ],
49/// );
50/// ```
51///
52/// or
53///
54/// ```ignore (render_device cannot be easily accessed)
55/// render_device.create_bind_group_layout(
56///     "my_bind_group_layout",
57///     &BindGroupLayoutEntries::sequential(
58///         ShaderStages::FRAGMENT,
59///         (
60///             // Screen texture
61///             texture_2d(TextureSampleType::Float { filterable: true }),
62///             // Sampler
63///             sampler(SamplerBindingType::Filtering),
64///         ),
65///     ),
66/// );
67/// ```
68///
69/// instead of
70///
71/// ```ignore (render_device cannot be easily accessed)
72/// let layout = render_device.create_bind_group_layout(
73///     "my_bind_group_layout",
74///     &[
75///         // Screen texture
76///         BindGroupLayoutEntry {
77///             binding: 0,
78///             visibility: ShaderStages::FRAGMENT,
79///             ty: BindingType::Texture {
80///                 sample_type: TextureSampleType::Float { filterable: true },
81///                 view_dimension: TextureViewDimension::D2,
82///                 multisampled: false,
83///             },
84///             count: None,
85///         },
86///         // Sampler
87///         BindGroupLayoutEntry {
88///             binding: 1,
89///             visibility: ShaderStages::FRAGMENT,
90///             ty: BindingType::Sampler(SamplerBindingType::Filtering),
91///             count: None,
92///         },
93///     ],
94/// );
95/// ```
96///
97/// or
98///
99/// ```ignore (render_device cannot be easily accessed)
100/// render_device.create_bind_group_layout(
101///     "my_bind_group_layout",
102///     &BindGroupLayoutEntries::single(
103///         ShaderStages::FRAGMENT,
104///         texture_2d(TextureSampleType::Float { filterable: true }),
105///     ),
106/// );
107/// ```
108///
109/// instead of
110///
111/// ```ignore (render_device cannot be easily accessed)
112/// let layout = render_device.create_bind_group_layout(
113///     "my_bind_group_layout",
114///     &[
115///         BindGroupLayoutEntry {
116///             binding: 0,
117///             visibility: ShaderStages::FRAGMENT,
118///             ty: BindingType::Texture {
119///                 sample_type: TextureSampleType::Float { filterable: true },
120///                 view_dimension: TextureViewDimension::D2,
121///                 multisampled: false,
122///             },
123///             count: None,
124///         },
125///     ],
126/// );
127/// ```
128
129#[derive(Clone, Copy)]
130pub struct BindGroupLayoutEntryBuilder {
131    ty: BindingType,
132    visibility: Option<ShaderStages>,
133    count: Option<NonZeroU32>,
134}
135
136impl BindGroupLayoutEntryBuilder {
137    pub fn visibility(mut self, visibility: ShaderStages) -> Self {
138        self.visibility = Some(visibility);
139        self
140    }
141
142    pub fn count(mut self, count: NonZeroU32) -> Self {
143        self.count = Some(count);
144        self
145    }
146
147    pub fn build(&self, binding: u32, default_visibility: ShaderStages) -> BindGroupLayoutEntry {
148        BindGroupLayoutEntry {
149            binding,
150            ty: self.ty,
151            visibility: self.visibility.unwrap_or(default_visibility),
152            count: self.count,
153        }
154    }
155}
156
157pub struct BindGroupLayoutEntries<const N: usize> {
158    entries: [BindGroupLayoutEntry; N],
159}
160
161impl<const N: usize> BindGroupLayoutEntries<N> {
162    #[inline]
163    pub fn sequential(
164        default_visibility: ShaderStages,
165        entries_ext: impl IntoBindGroupLayoutEntryBuilderArray<N>,
166    ) -> Self {
167        let mut i = 0;
168        Self {
169            entries: entries_ext.into_array().map(|entry| {
170                let binding = i;
171                i += 1;
172                entry.build(binding, default_visibility)
173            }),
174        }
175    }
176
177    #[inline]
178    pub fn with_indices(
179        default_visibility: ShaderStages,
180        indexed_entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray<N>,
181    ) -> Self {
182        Self {
183            entries: indexed_entries
184                .into_array()
185                .map(|(binding, entry)| entry.build(binding, default_visibility)),
186        }
187    }
188}
189
190impl BindGroupLayoutEntries<1> {
191    pub fn single(
192        visibility: ShaderStages,
193        resource: impl IntoBindGroupLayoutEntryBuilder,
194    ) -> [BindGroupLayoutEntry; 1] {
195        [resource
196            .into_bind_group_layout_entry_builder()
197            .build(0, visibility)]
198    }
199}
200
201impl<const N: usize> std::ops::Deref for BindGroupLayoutEntries<N> {
202    type Target = [BindGroupLayoutEntry];
203    fn deref(&self) -> &[BindGroupLayoutEntry] {
204        &self.entries
205    }
206}
207
208pub trait IntoBindGroupLayoutEntryBuilder {
209    fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder;
210}
211
212impl IntoBindGroupLayoutEntryBuilder for BindingType {
213    fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder {
214        BindGroupLayoutEntryBuilder {
215            ty: self,
216            visibility: None,
217            count: None,
218        }
219    }
220}
221
222impl IntoBindGroupLayoutEntryBuilder for BindGroupLayoutEntry {
223    fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder {
224        if self.binding != u32::MAX {
225            bevy_utils::tracing::warn!("The BindGroupLayoutEntries api ignores the binding index when converting a raw wgpu::BindGroupLayoutEntry. You can ignore this warning by setting it to u32::MAX.");
226        }
227        BindGroupLayoutEntryBuilder {
228            ty: self.ty,
229            visibility: Some(self.visibility),
230            count: self.count,
231        }
232    }
233}
234
235impl IntoBindGroupLayoutEntryBuilder for BindGroupLayoutEntryBuilder {
236    fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder {
237        self
238    }
239}
240
241pub trait IntoBindGroupLayoutEntryBuilderArray<const N: usize> {
242    fn into_array(self) -> [BindGroupLayoutEntryBuilder; N];
243}
244macro_rules! impl_to_binding_type_slice {
245    ($N: expr, $(($T: ident, $I: ident)),*) => {
246        impl<$($T: IntoBindGroupLayoutEntryBuilder),*> IntoBindGroupLayoutEntryBuilderArray<$N> for ($($T,)*) {
247            #[inline]
248            fn into_array(self) -> [BindGroupLayoutEntryBuilder; $N] {
249                let ($($I,)*) = self;
250                [$($I.into_bind_group_layout_entry_builder(), )*]
251            }
252        }
253    }
254}
255all_tuples_with_size!(impl_to_binding_type_slice, 1, 32, T, s);
256
257pub trait IntoIndexedBindGroupLayoutEntryBuilderArray<const N: usize> {
258    fn into_array(self) -> [(u32, BindGroupLayoutEntryBuilder); N];
259}
260macro_rules! impl_to_indexed_binding_type_slice {
261    ($N: expr, $(($T: ident, $S: ident, $I: ident)),*) => {
262        impl<$($T: IntoBindGroupLayoutEntryBuilder),*> IntoIndexedBindGroupLayoutEntryBuilderArray<$N> for ($((u32, $T),)*) {
263            #[inline]
264            fn into_array(self) -> [(u32, BindGroupLayoutEntryBuilder); $N] {
265                let ($(($S, $I),)*) = self;
266                [$(($S, $I.into_bind_group_layout_entry_builder())), *]
267            }
268        }
269    }
270}
271all_tuples_with_size!(impl_to_indexed_binding_type_slice, 1, 32, T, n, s);
272
273impl<const N: usize> IntoBindGroupLayoutEntryBuilderArray<N> for [BindGroupLayoutEntry; N] {
274    fn into_array(self) -> [BindGroupLayoutEntryBuilder; N] {
275        self.map(|x| x.into_bind_group_layout_entry_builder())
276    }
277}
278
279pub struct DynamicBindGroupLayoutEntries {
280    default_visibility: ShaderStages,
281    entries: Vec<BindGroupLayoutEntry>,
282}
283
284impl DynamicBindGroupLayoutEntries {
285    pub fn sequential<const N: usize>(
286        default_visibility: ShaderStages,
287        entries: impl IntoBindGroupLayoutEntryBuilderArray<N>,
288    ) -> Self {
289        Self {
290            default_visibility,
291            entries: entries
292                .into_array()
293                .into_iter()
294                .enumerate()
295                .map(|(ix, resource)| resource.build(ix as u32, default_visibility))
296                .collect(),
297        }
298    }
299
300    pub fn extend_sequential<const N: usize>(
301        mut self,
302        entries: impl IntoBindGroupLayoutEntryBuilderArray<N>,
303    ) -> Self {
304        let start = self.entries.last().unwrap().binding + 1;
305        self.entries.extend(
306            entries
307                .into_array()
308                .into_iter()
309                .enumerate()
310                .map(|(ix, resource)| resource.build(start + ix as u32, self.default_visibility)),
311        );
312        self
313    }
314
315    pub fn new_with_indices<const N: usize>(
316        default_visibility: ShaderStages,
317        entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray<N>,
318    ) -> Self {
319        Self {
320            default_visibility,
321            entries: entries
322                .into_array()
323                .into_iter()
324                .map(|(binding, resource)| resource.build(binding, default_visibility))
325                .collect(),
326        }
327    }
328
329    pub fn extend_with_indices<const N: usize>(
330        mut self,
331        entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray<N>,
332    ) -> Self {
333        self.entries.extend(
334            entries
335                .into_array()
336                .into_iter()
337                .map(|(binding, resource)| resource.build(binding, self.default_visibility)),
338        );
339        self
340    }
341}
342
343impl std::ops::Deref for DynamicBindGroupLayoutEntries {
344    type Target = [BindGroupLayoutEntry];
345
346    fn deref(&self) -> &[BindGroupLayoutEntry] {
347        &self.entries
348    }
349}
350
351pub mod binding_types {
352    use crate::render_resource::{
353        BufferBindingType, SamplerBindingType, TextureSampleType, TextureViewDimension,
354    };
355    use encase::ShaderType;
356    use std::num::NonZeroU64;
357    use wgpu::{StorageTextureAccess, TextureFormat};
358
359    use super::*;
360
361    pub fn storage_buffer<T: ShaderType>(has_dynamic_offset: bool) -> BindGroupLayoutEntryBuilder {
362        storage_buffer_sized(has_dynamic_offset, Some(T::min_size()))
363    }
364
365    pub fn storage_buffer_sized(
366        has_dynamic_offset: bool,
367        min_binding_size: Option<NonZeroU64>,
368    ) -> BindGroupLayoutEntryBuilder {
369        BindingType::Buffer {
370            ty: BufferBindingType::Storage { read_only: false },
371            has_dynamic_offset,
372            min_binding_size,
373        }
374        .into_bind_group_layout_entry_builder()
375    }
376
377    pub fn storage_buffer_read_only<T: ShaderType>(
378        has_dynamic_offset: bool,
379    ) -> BindGroupLayoutEntryBuilder {
380        storage_buffer_read_only_sized(has_dynamic_offset, Some(T::min_size()))
381    }
382
383    pub fn storage_buffer_read_only_sized(
384        has_dynamic_offset: bool,
385        min_binding_size: Option<NonZeroU64>,
386    ) -> BindGroupLayoutEntryBuilder {
387        BindingType::Buffer {
388            ty: BufferBindingType::Storage { read_only: true },
389            has_dynamic_offset,
390            min_binding_size,
391        }
392        .into_bind_group_layout_entry_builder()
393    }
394
395    pub fn uniform_buffer<T: ShaderType>(has_dynamic_offset: bool) -> BindGroupLayoutEntryBuilder {
396        uniform_buffer_sized(has_dynamic_offset, Some(T::min_size()))
397    }
398
399    pub fn uniform_buffer_sized(
400        has_dynamic_offset: bool,
401        min_binding_size: Option<NonZeroU64>,
402    ) -> BindGroupLayoutEntryBuilder {
403        BindingType::Buffer {
404            ty: BufferBindingType::Uniform,
405            has_dynamic_offset,
406            min_binding_size,
407        }
408        .into_bind_group_layout_entry_builder()
409    }
410
411    pub fn texture_1d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
412        BindingType::Texture {
413            sample_type,
414            view_dimension: TextureViewDimension::D1,
415            multisampled: false,
416        }
417        .into_bind_group_layout_entry_builder()
418    }
419
420    pub fn texture_2d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
421        BindingType::Texture {
422            sample_type,
423            view_dimension: TextureViewDimension::D2,
424            multisampled: false,
425        }
426        .into_bind_group_layout_entry_builder()
427    }
428
429    pub fn texture_2d_multisampled(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
430        BindingType::Texture {
431            sample_type,
432            view_dimension: TextureViewDimension::D2,
433            multisampled: true,
434        }
435        .into_bind_group_layout_entry_builder()
436    }
437
438    pub fn texture_2d_array(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
439        BindingType::Texture {
440            sample_type,
441            view_dimension: TextureViewDimension::D2Array,
442            multisampled: false,
443        }
444        .into_bind_group_layout_entry_builder()
445    }
446
447    pub fn texture_2d_array_multisampled(
448        sample_type: TextureSampleType,
449    ) -> BindGroupLayoutEntryBuilder {
450        BindingType::Texture {
451            sample_type,
452            view_dimension: TextureViewDimension::D2Array,
453            multisampled: true,
454        }
455        .into_bind_group_layout_entry_builder()
456    }
457
458    pub fn texture_depth_2d() -> BindGroupLayoutEntryBuilder {
459        texture_2d(TextureSampleType::Depth).into_bind_group_layout_entry_builder()
460    }
461
462    pub fn texture_depth_2d_multisampled() -> BindGroupLayoutEntryBuilder {
463        texture_2d_multisampled(TextureSampleType::Depth).into_bind_group_layout_entry_builder()
464    }
465
466    pub fn texture_cube(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
467        BindingType::Texture {
468            sample_type,
469            view_dimension: TextureViewDimension::Cube,
470            multisampled: false,
471        }
472        .into_bind_group_layout_entry_builder()
473    }
474
475    pub fn texture_cube_multisampled(
476        sample_type: TextureSampleType,
477    ) -> BindGroupLayoutEntryBuilder {
478        BindingType::Texture {
479            sample_type,
480            view_dimension: TextureViewDimension::Cube,
481            multisampled: true,
482        }
483        .into_bind_group_layout_entry_builder()
484    }
485
486    pub fn texture_cube_array(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
487        BindingType::Texture {
488            sample_type,
489            view_dimension: TextureViewDimension::CubeArray,
490            multisampled: false,
491        }
492        .into_bind_group_layout_entry_builder()
493    }
494
495    pub fn texture_cube_array_multisampled(
496        sample_type: TextureSampleType,
497    ) -> BindGroupLayoutEntryBuilder {
498        BindingType::Texture {
499            sample_type,
500            view_dimension: TextureViewDimension::CubeArray,
501            multisampled: true,
502        }
503        .into_bind_group_layout_entry_builder()
504    }
505
506    pub fn texture_3d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
507        BindingType::Texture {
508            sample_type,
509            view_dimension: TextureViewDimension::D3,
510            multisampled: false,
511        }
512        .into_bind_group_layout_entry_builder()
513    }
514
515    pub fn texture_3d_multisampled(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
516        BindingType::Texture {
517            sample_type,
518            view_dimension: TextureViewDimension::D3,
519            multisampled: true,
520        }
521        .into_bind_group_layout_entry_builder()
522    }
523
524    pub fn sampler(sampler_binding_type: SamplerBindingType) -> BindGroupLayoutEntryBuilder {
525        BindingType::Sampler(sampler_binding_type).into_bind_group_layout_entry_builder()
526    }
527
528    pub fn texture_storage_2d(
529        format: TextureFormat,
530        access: StorageTextureAccess,
531    ) -> BindGroupLayoutEntryBuilder {
532        BindingType::StorageTexture {
533            access,
534            format,
535            view_dimension: TextureViewDimension::D2,
536        }
537        .into_bind_group_layout_entry_builder()
538    }
539
540    pub fn texture_storage_2d_array(
541        format: TextureFormat,
542        access: StorageTextureAccess,
543    ) -> BindGroupLayoutEntryBuilder {
544        BindingType::StorageTexture {
545            access,
546            format,
547            view_dimension: TextureViewDimension::D2Array,
548        }
549        .into_bind_group_layout_entry_builder()
550    }
551}