bevy_render/render_resource/
bind_group_layout_entries.rs

1use bevy_utils::all_tuples_with_size;
2use core::num::NonZero;
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<NonZero<u32>>,
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: NonZero<u32>) -> 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> core::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, $(#[$meta:meta])* $(($T: ident, $I: ident)),*) => {
246        $(#[$meta])*
247        impl<$($T: IntoBindGroupLayoutEntryBuilder),*> IntoBindGroupLayoutEntryBuilderArray<$N> for ($($T,)*) {
248            #[inline]
249            fn into_array(self) -> [BindGroupLayoutEntryBuilder; $N] {
250                let ($($I,)*) = self;
251                [$($I.into_bind_group_layout_entry_builder(), )*]
252            }
253        }
254    }
255}
256all_tuples_with_size!(
257    #[doc(fake_variadic)]
258    impl_to_binding_type_slice,
259    1,
260    32,
261    T,
262    s
263);
264
265pub trait IntoIndexedBindGroupLayoutEntryBuilderArray<const N: usize> {
266    fn into_array(self) -> [(u32, BindGroupLayoutEntryBuilder); N];
267}
268macro_rules! impl_to_indexed_binding_type_slice {
269    ($N: expr, $(($T: ident, $S: ident, $I: ident)),*) => {
270        impl<$($T: IntoBindGroupLayoutEntryBuilder),*> IntoIndexedBindGroupLayoutEntryBuilderArray<$N> for ($((u32, $T),)*) {
271            #[inline]
272            fn into_array(self) -> [(u32, BindGroupLayoutEntryBuilder); $N] {
273                let ($(($S, $I),)*) = self;
274                [$(($S, $I.into_bind_group_layout_entry_builder())), *]
275            }
276        }
277    }
278}
279all_tuples_with_size!(impl_to_indexed_binding_type_slice, 1, 32, T, n, s);
280
281impl<const N: usize> IntoBindGroupLayoutEntryBuilderArray<N> for [BindGroupLayoutEntry; N] {
282    fn into_array(self) -> [BindGroupLayoutEntryBuilder; N] {
283        self.map(IntoBindGroupLayoutEntryBuilder::into_bind_group_layout_entry_builder)
284    }
285}
286
287pub struct DynamicBindGroupLayoutEntries {
288    default_visibility: ShaderStages,
289    entries: Vec<BindGroupLayoutEntry>,
290}
291
292impl DynamicBindGroupLayoutEntries {
293    pub fn sequential<const N: usize>(
294        default_visibility: ShaderStages,
295        entries: impl IntoBindGroupLayoutEntryBuilderArray<N>,
296    ) -> Self {
297        Self {
298            default_visibility,
299            entries: entries
300                .into_array()
301                .into_iter()
302                .enumerate()
303                .map(|(ix, resource)| resource.build(ix as u32, default_visibility))
304                .collect(),
305        }
306    }
307
308    pub fn extend_sequential<const N: usize>(
309        mut self,
310        entries: impl IntoBindGroupLayoutEntryBuilderArray<N>,
311    ) -> Self {
312        let start = self.entries.last().unwrap().binding + 1;
313        self.entries.extend(
314            entries
315                .into_array()
316                .into_iter()
317                .enumerate()
318                .map(|(ix, resource)| resource.build(start + ix as u32, self.default_visibility)),
319        );
320        self
321    }
322
323    pub fn new_with_indices<const N: usize>(
324        default_visibility: ShaderStages,
325        entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray<N>,
326    ) -> Self {
327        Self {
328            default_visibility,
329            entries: entries
330                .into_array()
331                .into_iter()
332                .map(|(binding, resource)| resource.build(binding, default_visibility))
333                .collect(),
334        }
335    }
336
337    pub fn extend_with_indices<const N: usize>(
338        mut self,
339        entries: impl IntoIndexedBindGroupLayoutEntryBuilderArray<N>,
340    ) -> Self {
341        self.entries.extend(
342            entries
343                .into_array()
344                .into_iter()
345                .map(|(binding, resource)| resource.build(binding, self.default_visibility)),
346        );
347        self
348    }
349}
350
351impl core::ops::Deref for DynamicBindGroupLayoutEntries {
352    type Target = [BindGroupLayoutEntry];
353
354    fn deref(&self) -> &[BindGroupLayoutEntry] {
355        &self.entries
356    }
357}
358
359pub mod binding_types {
360    use crate::render_resource::{
361        BufferBindingType, SamplerBindingType, TextureSampleType, TextureViewDimension,
362    };
363    use core::num::NonZero;
364    use encase::ShaderType;
365    use wgpu::{StorageTextureAccess, TextureFormat};
366
367    use super::*;
368
369    pub fn storage_buffer<T: ShaderType>(has_dynamic_offset: bool) -> BindGroupLayoutEntryBuilder {
370        storage_buffer_sized(has_dynamic_offset, Some(T::min_size()))
371    }
372
373    pub fn storage_buffer_sized(
374        has_dynamic_offset: bool,
375        min_binding_size: Option<NonZero<u64>>,
376    ) -> BindGroupLayoutEntryBuilder {
377        BindingType::Buffer {
378            ty: BufferBindingType::Storage { read_only: false },
379            has_dynamic_offset,
380            min_binding_size,
381        }
382        .into_bind_group_layout_entry_builder()
383    }
384
385    pub fn storage_buffer_read_only<T: ShaderType>(
386        has_dynamic_offset: bool,
387    ) -> BindGroupLayoutEntryBuilder {
388        storage_buffer_read_only_sized(has_dynamic_offset, Some(T::min_size()))
389    }
390
391    pub fn storage_buffer_read_only_sized(
392        has_dynamic_offset: bool,
393        min_binding_size: Option<NonZero<u64>>,
394    ) -> BindGroupLayoutEntryBuilder {
395        BindingType::Buffer {
396            ty: BufferBindingType::Storage { read_only: true },
397            has_dynamic_offset,
398            min_binding_size,
399        }
400        .into_bind_group_layout_entry_builder()
401    }
402
403    pub fn uniform_buffer<T: ShaderType>(has_dynamic_offset: bool) -> BindGroupLayoutEntryBuilder {
404        uniform_buffer_sized(has_dynamic_offset, Some(T::min_size()))
405    }
406
407    pub fn uniform_buffer_sized(
408        has_dynamic_offset: bool,
409        min_binding_size: Option<NonZero<u64>>,
410    ) -> BindGroupLayoutEntryBuilder {
411        BindingType::Buffer {
412            ty: BufferBindingType::Uniform,
413            has_dynamic_offset,
414            min_binding_size,
415        }
416        .into_bind_group_layout_entry_builder()
417    }
418
419    pub fn texture_1d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
420        BindingType::Texture {
421            sample_type,
422            view_dimension: TextureViewDimension::D1,
423            multisampled: false,
424        }
425        .into_bind_group_layout_entry_builder()
426    }
427
428    pub fn texture_2d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
429        BindingType::Texture {
430            sample_type,
431            view_dimension: TextureViewDimension::D2,
432            multisampled: false,
433        }
434        .into_bind_group_layout_entry_builder()
435    }
436
437    pub fn texture_2d_multisampled(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
438        BindingType::Texture {
439            sample_type,
440            view_dimension: TextureViewDimension::D2,
441            multisampled: true,
442        }
443        .into_bind_group_layout_entry_builder()
444    }
445
446    pub fn texture_2d_array(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
447        BindingType::Texture {
448            sample_type,
449            view_dimension: TextureViewDimension::D2Array,
450            multisampled: false,
451        }
452        .into_bind_group_layout_entry_builder()
453    }
454
455    pub fn texture_2d_array_multisampled(
456        sample_type: TextureSampleType,
457    ) -> BindGroupLayoutEntryBuilder {
458        BindingType::Texture {
459            sample_type,
460            view_dimension: TextureViewDimension::D2Array,
461            multisampled: true,
462        }
463        .into_bind_group_layout_entry_builder()
464    }
465
466    pub fn texture_depth_2d() -> BindGroupLayoutEntryBuilder {
467        texture_2d(TextureSampleType::Depth).into_bind_group_layout_entry_builder()
468    }
469
470    pub fn texture_depth_2d_multisampled() -> BindGroupLayoutEntryBuilder {
471        texture_2d_multisampled(TextureSampleType::Depth).into_bind_group_layout_entry_builder()
472    }
473
474    pub fn texture_cube(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
475        BindingType::Texture {
476            sample_type,
477            view_dimension: TextureViewDimension::Cube,
478            multisampled: false,
479        }
480        .into_bind_group_layout_entry_builder()
481    }
482
483    pub fn texture_cube_multisampled(
484        sample_type: TextureSampleType,
485    ) -> BindGroupLayoutEntryBuilder {
486        BindingType::Texture {
487            sample_type,
488            view_dimension: TextureViewDimension::Cube,
489            multisampled: true,
490        }
491        .into_bind_group_layout_entry_builder()
492    }
493
494    pub fn texture_cube_array(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
495        BindingType::Texture {
496            sample_type,
497            view_dimension: TextureViewDimension::CubeArray,
498            multisampled: false,
499        }
500        .into_bind_group_layout_entry_builder()
501    }
502
503    pub fn texture_cube_array_multisampled(
504        sample_type: TextureSampleType,
505    ) -> BindGroupLayoutEntryBuilder {
506        BindingType::Texture {
507            sample_type,
508            view_dimension: TextureViewDimension::CubeArray,
509            multisampled: true,
510        }
511        .into_bind_group_layout_entry_builder()
512    }
513
514    pub fn texture_3d(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
515        BindingType::Texture {
516            sample_type,
517            view_dimension: TextureViewDimension::D3,
518            multisampled: false,
519        }
520        .into_bind_group_layout_entry_builder()
521    }
522
523    pub fn texture_3d_multisampled(sample_type: TextureSampleType) -> BindGroupLayoutEntryBuilder {
524        BindingType::Texture {
525            sample_type,
526            view_dimension: TextureViewDimension::D3,
527            multisampled: true,
528        }
529        .into_bind_group_layout_entry_builder()
530    }
531
532    pub fn sampler(sampler_binding_type: SamplerBindingType) -> BindGroupLayoutEntryBuilder {
533        BindingType::Sampler(sampler_binding_type).into_bind_group_layout_entry_builder()
534    }
535
536    pub fn texture_storage_2d(
537        format: TextureFormat,
538        access: StorageTextureAccess,
539    ) -> BindGroupLayoutEntryBuilder {
540        BindingType::StorageTexture {
541            access,
542            format,
543            view_dimension: TextureViewDimension::D2,
544        }
545        .into_bind_group_layout_entry_builder()
546    }
547
548    pub fn texture_storage_2d_array(
549        format: TextureFormat,
550        access: StorageTextureAccess,
551    ) -> BindGroupLayoutEntryBuilder {
552        BindingType::StorageTexture {
553            access,
554            format,
555            view_dimension: TextureViewDimension::D2Array,
556        }
557        .into_bind_group_layout_entry_builder()
558    }
559}