bevy_render/render_resource/
bind_group.rs

1use crate::renderer::WgpuWrapper;
2use crate::{
3    define_atomic_id,
4    render_asset::RenderAssets,
5    render_resource::{BindGroupLayout, Buffer, Sampler, TextureView},
6    renderer::RenderDevice,
7    texture::GpuImage,
8};
9use alloc::sync::Arc;
10use bevy_ecs::system::{SystemParam, SystemParamItem};
11pub use bevy_render_macros::AsBindGroup;
12use core::ops::Deref;
13use derive_more::derive::{Display, Error};
14use encase::ShaderType;
15use wgpu::{BindGroupEntry, BindGroupLayoutEntry, BindingResource};
16
17define_atomic_id!(BindGroupId);
18
19/// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers)
20/// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass).
21/// This makes them accessible in the pipeline (shaders) as uniforms.
22///
23/// May be converted from and dereferences to a wgpu [`BindGroup`](wgpu::BindGroup).
24/// Can be created via [`RenderDevice::create_bind_group`](RenderDevice::create_bind_group).
25#[derive(Clone, Debug)]
26pub struct BindGroup {
27    id: BindGroupId,
28    value: Arc<WgpuWrapper<wgpu::BindGroup>>,
29}
30
31impl BindGroup {
32    /// Returns the [`BindGroupId`].
33    #[inline]
34    pub fn id(&self) -> BindGroupId {
35        self.id
36    }
37}
38
39impl From<wgpu::BindGroup> for BindGroup {
40    fn from(value: wgpu::BindGroup) -> Self {
41        BindGroup {
42            id: BindGroupId::new(),
43            value: Arc::new(WgpuWrapper::new(value)),
44        }
45    }
46}
47
48impl<'a> From<&'a BindGroup> for Option<&'a wgpu::BindGroup> {
49    fn from(value: &'a BindGroup) -> Self {
50        Some(value.deref())
51    }
52}
53
54impl<'a> From<&'a mut BindGroup> for Option<&'a wgpu::BindGroup> {
55    fn from(value: &'a mut BindGroup) -> Self {
56        Some(&*value)
57    }
58}
59
60impl Deref for BindGroup {
61    type Target = wgpu::BindGroup;
62
63    #[inline]
64    fn deref(&self) -> &Self::Target {
65        &self.value
66    }
67}
68
69/// Converts a value to a [`BindGroup`] with a given [`BindGroupLayout`], which can then be used in Bevy shaders.
70/// This trait can be derived (and generally should be). Read on for details and examples.
71///
72/// This is an opinionated trait that is intended to make it easy to generically
73/// convert a type into a [`BindGroup`]. It provides access to specific render resources,
74/// such as [`RenderAssets<GpuImage>`] and [`crate::texture::FallbackImage`]. If a type has a [`Handle<Image>`](bevy_asset::Handle),
75/// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource.
76///
77/// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally
78/// ok to do "expensive" work here, such as creating a [`Buffer`] for a uniform.
79///
80/// If for some reason a [`BindGroup`] cannot be created yet (for example, the [`Texture`](crate::render_resource::Texture)
81/// for an [`Image`](bevy_image::Image) hasn't loaded yet), just return [`AsBindGroupError::RetryNextUpdate`], which signals that the caller
82/// should retry again later.
83///
84/// # Deriving
85///
86/// This trait can be derived. Field attributes like `uniform` and `texture` are used to define which fields should be bindings,
87/// what their binding type is, and what index they should be bound at:
88///
89/// ```
90/// # use bevy_render::render_resource::*;
91/// # use bevy_image::Image;
92/// # use bevy_color::LinearRgba;
93/// # use bevy_asset::Handle;
94/// # use bevy_render::storage::ShaderStorageBuffer;
95///
96/// #[derive(AsBindGroup)]
97/// struct CoolMaterial {
98///     #[uniform(0)]
99///     color: LinearRgba,
100///     #[texture(1)]
101///     #[sampler(2)]
102///     color_texture: Handle<Image>,
103///     #[storage(3, read_only)]
104///     storage_buffer: Handle<ShaderStorageBuffer>,
105///     #[storage(4, read_only, buffer)]
106///     raw_buffer: Buffer,
107///     #[storage_texture(5)]
108///     storage_texture: Handle<Image>,
109/// }
110/// ```
111///
112/// In WGSL shaders, the binding would look like this:
113///
114/// ```wgsl
115/// @group(2) @binding(0) var<uniform> color: vec4<f32>;
116/// @group(2) @binding(1) var color_texture: texture_2d<f32>;
117/// @group(2) @binding(2) var color_sampler: sampler;
118/// @group(2) @binding(3) var<storage> storage_buffer: array<f32>;
119/// @group(2) @binding(4) var<storage> raw_buffer: array<f32>;
120/// @group(2) @binding(5) var storage_texture: texture_storage_2d<rgba8unorm, read_write>;
121/// ```
122/// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups
123/// are generally bound to group 2.
124///
125/// The following field-level attributes are supported:
126///
127/// * `uniform(BINDING_INDEX)`
128///     * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a uniform.
129///         [`ShaderType`] is implemented for most math types already, such as [`f32`], [`Vec4`](bevy_math::Vec4), and
130///         [`LinearRgba`](bevy_color::LinearRgba). It can also be derived for custom structs.
131///
132/// * `texture(BINDING_INDEX, arguments)`
133///     * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
134///         GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
135///         most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
136///         [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute
137///         (with a different binding index) if a binding of the sampler for the [`Image`](bevy_image::Image) is also required.
138///
139/// | Arguments             | Values                                                                  | Default              |
140/// |-----------------------|-------------------------------------------------------------------------|----------------------|
141/// | `dimension` = "..."   | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"`          | `"2d"`               |
142/// | `sample_type` = "..." | `"float"`, `"depth"`, `"s_int"` or `"u_int"`                            | `"float"`            |
143/// | `filterable` = ...    | `true`, `false`                                                         | `true`               |
144/// | `multisampled` = ...  | `true`, `false`                                                         | `false`              |
145/// | `visibility(...)`     | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` |
146///
147/// * `storage_texture(BINDING_INDEX, arguments)`
148///     * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
149///         GPU resource, which will be bound as a storage texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
150///         most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
151///         [`None`], the [`crate::texture::FallbackImage`] resource will be used instead.
152///
153/// | Arguments              | Values                                                                                     | Default       |
154/// |------------------------|--------------------------------------------------------------------------------------------|---------------|
155/// | `dimension` = "..."    | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"`                             | `"2d"`        |
156/// | `image_format` = ...   | any member of [`TextureFormat`](crate::render_resource::TextureFormat)                     | `Rgba8Unorm`  |
157/// | `access` = ...         | any member of [`StorageTextureAccess`](crate::render_resource::StorageTextureAccess)       | `ReadWrite`   |
158/// | `visibility(...)`      | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute`                    | `compute`     |
159///
160/// * `sampler(BINDING_INDEX, arguments)`
161///     * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Sampler`] GPU
162///         resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
163///         most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
164///         [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute
165///         (with a different binding index) if a binding of the texture for the [`Image`](bevy_image::Image) is also required.
166///
167/// | Arguments              | Values                                                                  | Default                |
168/// |------------------------|-------------------------------------------------------------------------|------------------------|
169/// | `sampler_type` = "..." | `"filtering"`, `"non_filtering"`, `"comparison"`.                       |  `"filtering"`         |
170/// | `visibility(...)`      | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` |   `vertex`, `fragment` |
171/// * `storage(BINDING_INDEX, arguments)`
172///     * The field's [`Handle<Storage>`](bevy_asset::Handle) will be used to look up the matching [`Buffer`] GPU resource, which
173///       will be bound as a storage buffer in shaders. If the `storage` attribute is used, the field is expected a raw
174///       buffer, and the buffer will be bound as a storage buffer in shaders.
175///     * It supports and optional `read_only` parameter. Defaults to false if not present.
176///
177/// | Arguments              | Values                                                                  | Default              |
178/// |------------------------|-------------------------------------------------------------------------|----------------------|
179/// | `visibility(...)`      | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` |
180/// | `read_only`            | if present then value is true, otherwise false                          | `false`              |
181/// | `buffer`               | if present then the field will be assumed to be a raw wgpu buffer       |                      |
182///
183/// Note that fields without field-level binding attributes will be ignored.
184/// ```
185/// # use bevy_render::{render_resource::AsBindGroup};
186/// # use bevy_color::LinearRgba;
187/// # use bevy_asset::Handle;
188/// #[derive(AsBindGroup)]
189/// struct CoolMaterial {
190///     #[uniform(0)]
191///     color: LinearRgba,
192///     this_field_is_ignored: String,
193/// }
194/// ```
195///
196///  As mentioned above, [`Option<Handle<Image>>`] is also supported:
197/// ```
198/// # use bevy_asset::Handle;
199/// # use bevy_color::LinearRgba;
200/// # use bevy_image::Image;
201/// # use bevy_render::render_resource::AsBindGroup;
202/// #[derive(AsBindGroup)]
203/// struct CoolMaterial {
204///     #[uniform(0)]
205///     color: LinearRgba,
206///     #[texture(1)]
207///     #[sampler(2)]
208///     color_texture: Option<Handle<Image>>,
209/// }
210/// ```
211/// This is useful if you want a texture to be optional. When the value is [`None`], the [`crate::texture::FallbackImage`] will be used for the binding instead, which defaults
212/// to "pure white".
213///
214/// Field uniforms with the same index will be combined into a single binding:
215/// ```
216/// # use bevy_render::{render_resource::AsBindGroup};
217/// # use bevy_color::LinearRgba;
218/// #[derive(AsBindGroup)]
219/// struct CoolMaterial {
220///     #[uniform(0)]
221///     color: LinearRgba,
222///     #[uniform(0)]
223///     roughness: f32,
224/// }
225/// ```
226///
227/// In WGSL shaders, the binding would look like this:
228/// ```wgsl
229/// struct CoolMaterial {
230///     color: vec4<f32>,
231///     roughness: f32,
232/// };
233///
234/// @group(2) @binding(0) var<uniform> material: CoolMaterial;
235/// ```
236///
237/// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes:
238/// * `uniform(BINDING_INDEX, ConvertedShaderType)`
239///     * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a uniform, much
240///         like the field-level `uniform` attribute. The difference is that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`,
241///         which must implement [`ShaderType`], instead of a specific field implementing [`ShaderType`]. This is useful if more complicated conversion
242///         logic is required. The conversion is done using the [`AsBindGroupShaderType<ConvertedShaderType>`] trait, which is automatically implemented
243///         if `&Self` implements [`Into<ConvertedShaderType>`]. Only use [`AsBindGroupShaderType`] if access to resources like [`RenderAssets<GpuImage>`] is
244///         required.
245/// * `bind_group_data(DataType)`
246///     * The [`AsBindGroup`] type will be converted to some `DataType` using [`Into<DataType>`] and stored
247///         as [`AsBindGroup::Data`] as part of the [`AsBindGroup::as_bind_group`] call. This is useful if data needs to be stored alongside
248///         the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute
249///         is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline).
250///
251/// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can
252/// also be equivalently represented with a single struct-level uniform attribute:
253/// ```
254/// # use bevy_render::{render_resource::{AsBindGroup, ShaderType}};
255/// # use bevy_color::LinearRgba;
256/// #[derive(AsBindGroup)]
257/// #[uniform(0, CoolMaterialUniform)]
258/// struct CoolMaterial {
259///     color: LinearRgba,
260///     roughness: f32,
261/// }
262///
263/// #[derive(ShaderType)]
264/// struct CoolMaterialUniform {
265///     color: LinearRgba,
266///     roughness: f32,
267/// }
268///
269/// impl From<&CoolMaterial> for CoolMaterialUniform {
270///     fn from(material: &CoolMaterial) -> CoolMaterialUniform {
271///         CoolMaterialUniform {
272///             color: material.color,
273///             roughness: material.roughness,
274///         }
275///     }
276/// }
277/// ```
278///
279/// Setting `bind_group_data` looks like this:
280/// ```
281/// # use bevy_render::{render_resource::AsBindGroup};
282/// # use bevy_color::LinearRgba;
283/// #[derive(AsBindGroup)]
284/// #[bind_group_data(CoolMaterialKey)]
285/// struct CoolMaterial {
286///     #[uniform(0)]
287///     color: LinearRgba,
288///     is_shaded: bool,
289/// }
290///
291/// #[derive(Copy, Clone, Hash, Eq, PartialEq)]
292/// struct CoolMaterialKey {
293///     is_shaded: bool,
294/// }
295///
296/// impl From<&CoolMaterial> for CoolMaterialKey {
297///     fn from(material: &CoolMaterial) -> CoolMaterialKey {
298///         CoolMaterialKey {
299///             is_shaded: material.is_shaded,
300///         }
301///     }
302/// }
303/// ```
304pub trait AsBindGroup {
305    /// Data that will be stored alongside the "prepared" bind group.
306    type Data: Send + Sync;
307
308    type Param: SystemParam + 'static;
309
310    /// label
311    fn label() -> Option<&'static str> {
312        None
313    }
314
315    /// Creates a bind group for `self` matching the layout defined in [`AsBindGroup::bind_group_layout`].
316    fn as_bind_group(
317        &self,
318        layout: &BindGroupLayout,
319        render_device: &RenderDevice,
320        param: &mut SystemParamItem<'_, '_, Self::Param>,
321    ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
322        let UnpreparedBindGroup { bindings, data } =
323            Self::unprepared_bind_group(self, layout, render_device, param)?;
324
325        let entries = bindings
326            .iter()
327            .map(|(index, binding)| BindGroupEntry {
328                binding: *index,
329                resource: binding.get_binding(),
330            })
331            .collect::<Vec<_>>();
332
333        let bind_group = render_device.create_bind_group(Self::label(), layout, &entries);
334
335        Ok(PreparedBindGroup {
336            bindings,
337            bind_group,
338            data,
339        })
340    }
341
342    /// Returns a vec of (binding index, `OwnedBindingResource`).
343    /// In cases where `OwnedBindingResource` is not available (as for bindless texture arrays currently),
344    /// an implementor may define `as_bind_group` directly. This may prevent certain features
345    /// from working correctly.
346    fn unprepared_bind_group(
347        &self,
348        layout: &BindGroupLayout,
349        render_device: &RenderDevice,
350        param: &mut SystemParamItem<'_, '_, Self::Param>,
351    ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError>;
352
353    /// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`]
354    fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
355    where
356        Self: Sized,
357    {
358        render_device.create_bind_group_layout(
359            Self::label(),
360            &Self::bind_group_layout_entries(render_device),
361        )
362    }
363
364    /// Returns a vec of bind group layout entries
365    fn bind_group_layout_entries(render_device: &RenderDevice) -> Vec<BindGroupLayoutEntry>
366    where
367        Self: Sized;
368}
369
370/// An error that occurs during [`AsBindGroup::as_bind_group`] calls.
371#[derive(Debug, Error, Display)]
372pub enum AsBindGroupError {
373    /// The bind group could not be generated. Try again next frame.
374    #[display("The bind group could not be generated")]
375    RetryNextUpdate,
376    #[display("At binding index {_0}, the provided image sampler `{_1}` does not match the required sampler type(s) `{_2}`.")]
377    InvalidSamplerType(u32, String, String),
378}
379
380/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`].
381pub struct PreparedBindGroup<T> {
382    pub bindings: Vec<(u32, OwnedBindingResource)>,
383    pub bind_group: BindGroup,
384    pub data: T,
385}
386
387/// a map containing `OwnedBindingResource`s, keyed by the target binding index
388pub struct UnpreparedBindGroup<T> {
389    pub bindings: Vec<(u32, OwnedBindingResource)>,
390    pub data: T,
391}
392
393/// An owned binding resource of any type (ex: a [`Buffer`], [`TextureView`], etc).
394/// This is used by types like [`PreparedBindGroup`] to hold a single list of all
395/// render resources used by bindings.
396#[derive(Debug)]
397pub enum OwnedBindingResource {
398    Buffer(Buffer),
399    TextureView(TextureView),
400    Sampler(Sampler),
401}
402
403impl OwnedBindingResource {
404    pub fn get_binding(&self) -> BindingResource {
405        match self {
406            OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(),
407            OwnedBindingResource::TextureView(view) => BindingResource::TextureView(view),
408            OwnedBindingResource::Sampler(sampler) => BindingResource::Sampler(sampler),
409        }
410    }
411}
412
413/// Converts a value to a [`ShaderType`] for use in a bind group.
414///
415/// This is automatically implemented for references that implement [`Into`].
416/// Generally normal [`Into`] / [`From`] impls should be preferred, but
417/// sometimes additional runtime metadata is required.
418/// This exists largely to make some [`AsBindGroup`] use cases easier.
419pub trait AsBindGroupShaderType<T: ShaderType> {
420    /// Return the `T` [`ShaderType`] for `self`. When used in [`AsBindGroup`]
421    /// derives, it is safe to assume that all images in `self` exist.
422    fn as_bind_group_shader_type(&self, images: &RenderAssets<GpuImage>) -> T;
423}
424
425impl<T, U: ShaderType> AsBindGroupShaderType<U> for T
426where
427    for<'a> &'a T: Into<U>,
428{
429    #[inline]
430    fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> U {
431        self.into()
432    }
433}
434
435#[cfg(test)]
436mod test {
437    use super::*;
438    use crate as bevy_render;
439    use bevy_asset::Handle;
440    use bevy_image::Image;
441
442    #[test]
443    fn texture_visibility() {
444        #[derive(AsBindGroup)]
445        pub struct TextureVisibilityTest {
446            #[texture(0, visibility(all))]
447            pub all: Handle<Image>,
448            #[texture(1, visibility(none))]
449            pub none: Handle<Image>,
450            #[texture(2, visibility(fragment))]
451            pub fragment: Handle<Image>,
452            #[texture(3, visibility(vertex))]
453            pub vertex: Handle<Image>,
454            #[texture(4, visibility(compute))]
455            pub compute: Handle<Image>,
456            #[texture(5, visibility(vertex, fragment))]
457            pub vertex_fragment: Handle<Image>,
458            #[texture(6, visibility(vertex, compute))]
459            pub vertex_compute: Handle<Image>,
460            #[texture(7, visibility(fragment, compute))]
461            pub fragment_compute: Handle<Image>,
462            #[texture(8, visibility(vertex, fragment, compute))]
463            pub vertex_fragment_compute: Handle<Image>,
464        }
465    }
466}