Trait AsBindGroup

Source
pub trait AsBindGroup {
    type Data: Send + Sync;
    type Param: SystemParam + 'static;

    // Required methods
    fn unprepared_bind_group(
        &self,
        layout: &BindGroupLayout,
        render_device: &RenderDevice,
        param: &mut SystemParamItem<'_, '_, Self::Param>,
        force_no_bindless: bool,
    ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError>;
    fn bind_group_layout_entries(
        render_device: &RenderDevice,
        force_no_bindless: bool,
    ) -> Vec<BindGroupLayoutEntry>
       where Self: Sized;

    // Provided methods
    fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> { ... }
    fn bindless_supported(_: &RenderDevice) -> bool { ... }
    fn label() -> Option<&'static str> { ... }
    fn as_bind_group(
        &self,
        layout: &BindGroupLayout,
        render_device: &RenderDevice,
        param: &mut SystemParamItem<'_, '_, Self::Param>,
    ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> { ... }
    fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
       where Self: Sized { ... }
    fn bindless_descriptor() -> Option<BindlessDescriptor> { ... }
}
Expand description

Converts a value to a BindGroup with a given BindGroupLayout, which can then be used in Bevy shaders. This trait can be derived (and generally should be). Read on for details and examples.

This is an opinionated trait that is intended to make it easy to generically convert a type into a BindGroup. It provides access to specific render resources, such as RenderAssets<GpuImage> and crate::texture::FallbackImage. If a type has a Handle<Image>, these can be used to retrieve the corresponding Texture resource.

AsBindGroup::as_bind_group is intended to be called once, then the result cached somewhere. It is generally ok to do “expensive” work here, such as creating a Buffer for a uniform.

If for some reason a BindGroup cannot be created yet (for example, the Texture for an Image hasn’t loaded yet), just return AsBindGroupError::RetryNextUpdate, which signals that the caller should retry again later.

§Deriving

This trait can be derived. Field attributes like uniform and texture are used to define which fields should be bindings, what their binding type is, and what index they should be bound at:


#[derive(AsBindGroup)]
struct CoolMaterial {
    #[uniform(0)]
    color: LinearRgba,
    #[texture(1)]
    #[sampler(2)]
    color_texture: Handle<Image>,
    #[storage(3, read_only)]
    storage_buffer: Handle<ShaderStorageBuffer>,
    #[storage(4, read_only, buffer)]
    raw_buffer: Buffer,
    #[storage_texture(5)]
    storage_texture: Handle<Image>,
}

In WGSL shaders, the binding would look like this:

@group(2) @binding(0) var<uniform> color: vec4<f32>;
@group(2) @binding(1) var color_texture: texture_2d<f32>;
@group(2) @binding(2) var color_sampler: sampler;
@group(2) @binding(3) var<storage> storage_buffer: array<f32>;
@group(2) @binding(4) var<storage> raw_buffer: array<f32>;
@group(2) @binding(5) var storage_texture: texture_storage_2d<rgba8unorm, read_write>;

Note that the “group” index is determined by the usage context. It is not defined in AsBindGroup. For example, in Bevy material bind groups are generally bound to group 2.

The following field-level attributes are supported:

§uniform(BINDING_INDEX)

  • The field will be converted to a shader-compatible type using the ShaderType trait, written to a Buffer, and bound as a uniform. ShaderType is implemented for most math types already, such as f32, Vec4, and LinearRgba. It can also be derived for custom structs.

§texture(BINDING_INDEX, arguments)

ArgumentsValuesDefault
dimension = “…”"1d", "2d", "2d_array", "3d", "cube", "cube_array""2d"
sample_type = “…”"float", "depth", "s_int" or "u_int""float"
filterable = …true, falsetrue
multisampled = …true, falsefalse
visibility(...)all, none, or a list-combination of vertex, fragment, computevertex, fragment

§storage_texture(BINDING_INDEX, arguments)

ArgumentsValuesDefault
dimension = “…”"1d", "2d", "2d_array", "3d", "cube", "cube_array""2d"
image_format = …any member of TextureFormatRgba8Unorm
access = …any member of StorageTextureAccessReadWrite
visibility(...)all, none, or a list-combination of vertex, fragment, computecompute

§sampler(BINDING_INDEX, arguments)

ArgumentsValuesDefault
sampler_type = “…”"filtering", "non_filtering", "comparison"."filtering"
visibility(...)all, none, or a list-combination of vertex, fragment, computevertex, fragment

§storage(BINDING_INDEX, arguments)

  • The field’s Handle<Storage> will be used to look up the matching Buffer GPU resource, which will be bound as a storage buffer in shaders. If the storage attribute is used, the field is expected a raw buffer, and the buffer will be bound as a storage buffer in shaders. In bindless mode, binding_array() argument that specifies the binding number of the resulting storage buffer binding array must be present.
ArgumentsValuesDefault
visibility(...)all, none, or a list-combination of vertex, fragment, computevertex, fragment
read_onlyif present then value is true, otherwise falsefalse
bufferif present then the field will be assumed to be a raw wgpu buffer
binding_array(...)the binding number of the binding array, for bindless modebindless mode disabled

Note that fields without field-level binding attributes will be ignored.

#[derive(AsBindGroup)]
struct CoolMaterial {
    #[uniform(0)]
    color: LinearRgba,
    this_field_is_ignored: String,
}

As mentioned above, Option<Handle<Image>> is also supported:

#[derive(AsBindGroup)]
struct CoolMaterial {
    #[uniform(0)]
    color: LinearRgba,
    #[texture(1)]
    #[sampler(2)]
    color_texture: Option<Handle<Image>>,
}

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 to “pure white”.

Field uniforms with the same index will be combined into a single binding:

#[derive(AsBindGroup)]
struct CoolMaterial {
    #[uniform(0)]
    color: LinearRgba,
    #[uniform(0)]
    roughness: f32,
}

In WGSL shaders, the binding would look like this:

struct CoolMaterial {
    color: vec4<f32>,
    roughness: f32,
};

@group(2) @binding(0) var<uniform> material: CoolMaterial;

Some less common scenarios will require “struct-level” attributes. These are the currently supported struct-level attributes:

§uniform(BINDING_INDEX, ConvertedShaderType)

  • This also creates a Buffer using ShaderType and binds it as a uniform, much like the field-level uniform attribute. The difference is that the entire AsBindGroup value is converted to ConvertedShaderType, which must implement ShaderType, instead of a specific field implementing ShaderType. This is useful if more complicated conversion logic is required, or when using bindless mode (see below). The conversion is done using the AsBindGroupShaderType<ConvertedShaderType> trait, which is automatically implemented if &Self implements Into<ConvertedShaderType>. Outside of bindless mode, only use AsBindGroupShaderType if access to resources like RenderAssets<GpuImage> is required.

  • In bindless mode (see bindless(COUNT)), this attribute becomes uniform(BINDLESS_INDEX, ConvertedShaderType, binding_array(BINDING_INDEX)). The resulting uniform buffers will be available in the shader as a binding array at the given BINDING_INDEX. The BINDLESS_INDEX specifies the offset of the buffer in the bindless index table.

    For example, suppose that the material slot is stored in a variable named slot, the bindless index table is named material_indices, and that the first field (index 0) of the bindless index table type is named material. Then specifying #[uniform(0, StandardMaterialUniform, binding_array(10)] will create a binding array buffer declared in the shader as var<storage> material_array: binding_array<StandardMaterialUniform> and accessible as material_array[material_indices[slot].material].

§data(BINDING_INDEX, ConvertedShaderType, binding_array(BINDING_INDEX))

  • This is very similar to uniform(BINDING_INDEX, ConvertedShaderType, binding_array(BINDING_INDEX) and in fact is identical if bindless mode isn’t being used. The difference is that, in bindless mode, the data attribute produces a single buffer containing an array, not an array of buffers. For example, suppose you had the following declaration:
#[uniform(0, StandardMaterialUniform, binding_array(10))]
struct StandardMaterial { ... }

In bindless mode, this will produce a binding matching the following WGSL declaration:

@group(2) @binding(10) var<storage> material_array: binding_array<StandardMaterial>;

On the other hand, if you write this declaration:

#[data(0, StandardMaterialUniform, binding_array(10))]
struct StandardMaterial { ... }

Then Bevy produces a binding that matches this WGSL declaration instead:

@group(2) @binding(10) var<storage> material_array: array<StandardMaterial>;
  • Just as with the structure-level uniform attribute, Bevy converts the entire AsBindGroup to ConvertedShaderType, using the AsBindGroupShaderType<ConvertedShaderType> trait.

  • In non-bindless mode, the structure-level data attribute is the same as the structure-level uniform attribute and produces a single uniform buffer in the shader. The above example would result in a binding that looks like this in WGSL in non-bindless mode:

@group(2) @binding(0) var<uniform> material: StandardMaterial;
  • For efficiency reasons, data is generally preferred over uniform unless you need to place your data in individual buffers.

§bind_group_data(DataType)

§bindless

  • This switch enables bindless resources, which changes the way Bevy supplies resources (textures, and samplers) to the shader. When bindless resources are enabled, and the current platform supports them, Bevy will allocate textures, and samplers into binding arrays, separated based on type and will supply your shader with indices into those arrays.
  • Bindless textures and samplers are placed into the appropriate global array defined in bevy_render::bindless (bindless.wgsl).
  • Bevy doesn’t currently support bindless buffers, except for those created with the uniform(BINDLESS_INDEX, ConvertedShaderType, binding_array(BINDING_INDEX)) attribute. If you need to include a buffer in your object, and you can’t create the data in that buffer with the uniform attribute, consider a non-bindless object instead.
  • If bindless mode is enabled, the BINDLESS definition will be available. Because not all platforms support bindless resources, you should check for the presence of this definition via #ifdef and fall back to standard bindings if it isn’t present.
  • By default, in bindless mode, binding 0 becomes the bindless index table, which is an array of structures, each of which contains as many fields of type u32 as the highest binding number in the structure annotated with #[derive(AsBindGroup)]. Again by default, the ith field of the bindless index table contains the index of the resource with binding i within the appropriate binding array.
  • In the case of materials, the index of the applicable table within the bindless index table list corresponding to the mesh currently being drawn can be retrieved with mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu.
  • You can limit the size of the bindless slabs to N resources with the limit(N) declaration. For example, #[bindless(limit(16))] ensures that each slab will have no more than 16 total resources in it. If you don’t specify a limit, Bevy automatically picks a reasonable one for the current platform.
  • The index_table(range(M..N), binding(B)) declaration allows you to customize the layout of the bindless index table. This is useful for materials that are composed of multiple bind groups, such as ExtendedMaterial. In such cases, there will be multiple bindless index tables, so they can’t both be assigned to binding 0 or their bindings will conflict.
    • The binding(B) attribute of the index_table attribute allows you to customize the binding (@binding(B), in the shader) at which the index table will be bound.
    • The range(M, N) attribute of the index_table attribute allows you to change the mapping from the field index in the bindless index table to the bindless index. Instead of the field at index $i$ being mapped to the bindless index $i$, with the range(M, N) attribute the field at index $i$ in the bindless index table is mapped to the bindless index $i$ + M. The size of the index table will be set to N - M. Note that this may result in the table being too small to contain all the bindless bindings.
  • The purpose of bindless mode is to improve performance by reducing state changes. By grouping resources together into binding arrays, Bevy doesn’t have to modify GPU state as often, decreasing API and driver overhead.
  • See the shaders/shader_material_bindless example for an example of how to use bindless mode. See the shaders/extended_material_bindless example for a more exotic example of bindless mode that demonstrates the index_table attribute.
  • The following diagram illustrates how bindless mode works using a subset of StandardMaterial:
     Shader Bindings                          Sampler Binding Array
    +----+-----------------------------+     +-----------+-----------+-----+
+---|  0 | material_indices            |  +->| sampler 0 | sampler 1 | ... |
|   +----+-----------------------------+  |  +-----------+-----------+-----+
|   |  1 | bindless_samplers_filtering +--+        ^
|   +----+-----------------------------+           +-------------------------------+
|   | .. |            ...              |                                           |
|   +----+-----------------------------+      Texture Binding Array                |
|   |  5 | bindless_textures_2d        +--+  +-----------+-----------+-----+       |
|   +----+-----------------------------+  +->| texture 0 | texture 1 | ... |       |
|   | .. |            ...              |     +-----------+-----------+-----+       |
|   +----+-----------------------------+           ^                               |
|   + 10 | material_array              +--+        +---------------------------+   |
|   +----+-----------------------------+  |                                    |   |
|                                         |   Buffer Binding Array             |   |
|                                         |  +----------+----------+-----+     |   |
|                                         +->| buffer 0 | buffer 1 | ... |     |   |
|    Material Bindless Indices               +----------+----------+-----+     |   |
|   +----+-----------------------------+          ^                            |   |
+-->|  0 | material                    +----------+                            |   |
    +----+-----------------------------+                                       |   |
    |  1 | base_color_texture          +---------------------------------------+   |
    +----+-----------------------------+                                           |
    |  2 | base_color_sampler          +-------------------------------------------+
    +----+-----------------------------+
    | .. |            ...              |
    +----+-----------------------------+

The previous CoolMaterial example illustrating “combining multiple field-level uniform attributes with the same binding index” can also be equivalently represented with a single struct-level uniform attribute:

#[derive(AsBindGroup)]
#[uniform(0, CoolMaterialUniform)]
struct CoolMaterial {
    color: LinearRgba,
    roughness: f32,
}

#[derive(ShaderType)]
struct CoolMaterialUniform {
    color: LinearRgba,
    roughness: f32,
}

impl From<&CoolMaterial> for CoolMaterialUniform {
    fn from(material: &CoolMaterial) -> CoolMaterialUniform {
        CoolMaterialUniform {
            color: material.color,
            roughness: material.roughness,
        }
    }
}

Setting bind_group_data looks like this:

#[derive(AsBindGroup)]
#[bind_group_data(CoolMaterialKey)]
struct CoolMaterial {
    #[uniform(0)]
    color: LinearRgba,
    is_shaded: bool,
}

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct CoolMaterialKey {
    is_shaded: bool,
}

impl From<&CoolMaterial> for CoolMaterialKey {
    fn from(material: &CoolMaterial) -> CoolMaterialKey {
        CoolMaterialKey {
            is_shaded: material.is_shaded,
        }
    }
}

Required Associated Types§

Source

type Data: Send + Sync

Data that will be stored alongside the “prepared” bind group.

Source

type Param: SystemParam + 'static

Required Methods§

Source

fn unprepared_bind_group( &self, layout: &BindGroupLayout, render_device: &RenderDevice, param: &mut SystemParamItem<'_, '_, Self::Param>, force_no_bindless: bool, ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError>

Returns a vec of (binding index, OwnedBindingResource).

In cases where OwnedBindingResource is not available (as for bindless texture arrays currently), an implementor may return AsBindGroupError::CreateBindGroupDirectly from this function and instead define as_bind_group directly. This may prevent certain features, such as bindless mode, from working correctly.

Set force_no_bindless to true to require that bindless textures not be used. ExtendedMaterial uses this in order to ensure that the base material doesn’t use bindless mode if the extension doesn’t.

Source

fn bind_group_layout_entries( render_device: &RenderDevice, force_no_bindless: bool, ) -> Vec<BindGroupLayoutEntry>
where Self: Sized,

Returns a vec of bind group layout entries.

Set force_no_bindless to true to require that bindless textures not be used. ExtendedMaterial uses this in order to ensure that the base material doesn’t use bindless mode if the extension doesn’t.

Provided Methods§

Source

fn bindless_slot_count() -> Option<BindlessSlabResourceLimit>

The number of slots per bind group, if bindless mode is enabled.

If this bind group doesn’t use bindless, then this will be None.

Note that the actual slot count may be different from this value, due to platform limitations. For example, if bindless resources aren’t supported on this platform, the actual slot count will be 1.

Source

fn bindless_supported(_: &RenderDevice) -> bool

True if the hardware actually supports bindless textures for this type, taking the device and driver capabilities into account.

If this type doesn’t use bindless textures, then the return value from this function is meaningless.

Source

fn label() -> Option<&'static str>

label

Source

fn as_bind_group( &self, layout: &BindGroupLayout, render_device: &RenderDevice, param: &mut SystemParamItem<'_, '_, Self::Param>, ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError>

Creates a bind group for self matching the layout defined in AsBindGroup::bind_group_layout.

Source

fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
where Self: Sized,

Creates the bind group layout matching all bind groups returned by AsBindGroup::as_bind_group

Source

fn bindless_descriptor() -> Option<BindlessDescriptor>

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§