Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/render_resource/bind_group.rs
9330 views
1
use crate::{
2
render_asset::RenderAssets,
3
render_resource::{BindGroupLayout, Buffer, PipelineCache, Sampler, TextureView},
4
renderer::{RenderDevice, WgpuWrapper},
5
texture::GpuImage,
6
};
7
use bevy_derive::{Deref, DerefMut};
8
use bevy_ecs::system::{SystemParam, SystemParamItem};
9
use bevy_material::descriptor::BindGroupLayoutDescriptor;
10
pub use bevy_render_macros::AsBindGroup;
11
use bevy_utils::define_atomic_id;
12
use core::ops::Deref;
13
use encase::ShaderType;
14
use thiserror::Error;
15
use wgpu::{
16
BindGroupEntry, BindGroupLayoutEntry, BindingResource, SamplerBindingType, TextureViewDimension,
17
};
18
19
use super::{BindlessDescriptor, BindlessSlabResourceLimit};
20
21
define_atomic_id!(BindGroupId);
22
23
/// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers)
24
/// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass).
25
/// This makes them accessible in the pipeline (shaders) as uniforms.
26
///
27
/// This is a lightweight thread-safe wrapper around wgpu's own [`BindGroup`](wgpu::BindGroup),
28
/// which can be cloned as needed to workaround lifetime management issues. It may be converted
29
/// from and dereferences to wgpu's [`BindGroup`](wgpu::BindGroup).
30
///
31
/// Can be created via [`RenderDevice::create_bind_group`](RenderDevice::create_bind_group).
32
#[derive(Clone, Debug)]
33
pub struct BindGroup {
34
id: BindGroupId,
35
value: WgpuWrapper<wgpu::BindGroup>,
36
}
37
38
impl BindGroup {
39
/// Returns the [`BindGroupId`] representing the unique ID of the bind group.
40
#[inline]
41
pub fn id(&self) -> BindGroupId {
42
self.id
43
}
44
}
45
46
impl PartialEq for BindGroup {
47
fn eq(&self, other: &Self) -> bool {
48
self.id == other.id
49
}
50
}
51
52
impl Eq for BindGroup {}
53
54
impl core::hash::Hash for BindGroup {
55
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
56
self.id.0.hash(state);
57
}
58
}
59
60
impl From<wgpu::BindGroup> for BindGroup {
61
fn from(value: wgpu::BindGroup) -> Self {
62
BindGroup {
63
id: BindGroupId::new(),
64
value: WgpuWrapper::new(value),
65
}
66
}
67
}
68
69
impl<'a> From<&'a BindGroup> for Option<&'a wgpu::BindGroup> {
70
fn from(value: &'a BindGroup) -> Self {
71
Some(value.deref())
72
}
73
}
74
75
impl<'a> From<&'a mut BindGroup> for Option<&'a wgpu::BindGroup> {
76
fn from(value: &'a mut BindGroup) -> Self {
77
Some(&*value)
78
}
79
}
80
81
impl Deref for BindGroup {
82
type Target = wgpu::BindGroup;
83
84
#[inline]
85
fn deref(&self) -> &Self::Target {
86
&self.value
87
}
88
}
89
90
/// Converts a value to a [`BindGroup`] with a given [`BindGroupLayout`], which can then be used in Bevy shaders.
91
/// This trait can be derived (and generally should be). Read on for details and examples.
92
///
93
/// This is an opinionated trait that is intended to make it easy to generically
94
/// convert a type into a [`BindGroup`]. It provides access to specific render resources,
95
/// such as [`RenderAssets<GpuImage>`] and [`crate::texture::FallbackImage`]. If a type has a [`Handle<Image>`](bevy_asset::Handle),
96
/// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource.
97
///
98
/// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally
99
/// ok to do "expensive" work here, such as creating a [`Buffer`] for a uniform.
100
///
101
/// If for some reason a [`BindGroup`] cannot be created yet (for example, the [`Texture`](crate::render_resource::Texture)
102
/// for an [`Image`](bevy_image::Image) hasn't loaded yet), just return [`AsBindGroupError::RetryNextUpdate`], which signals that the caller
103
/// should retry again later.
104
///
105
/// # Deriving
106
///
107
/// This trait can be derived. Field attributes like `uniform` and `texture` are used to define which fields should be bindings,
108
/// what their binding type is, and what index they should be bound at:
109
///
110
/// ```
111
/// # use bevy_render::render_resource::*;
112
/// # use bevy_image::Image;
113
/// # use bevy_color::LinearRgba;
114
/// # use bevy_asset::Handle;
115
/// # use bevy_render::storage::ShaderBuffer;
116
///
117
/// #[derive(AsBindGroup)]
118
/// struct CoolMaterial {
119
/// #[uniform(0)]
120
/// color: LinearRgba,
121
/// #[texture(1)]
122
/// #[sampler(2)]
123
/// color_texture: Handle<Image>,
124
/// #[storage(3, read_only)]
125
/// storage_buffer: Handle<ShaderBuffer>,
126
/// #[storage(4, read_only, buffer)]
127
/// raw_buffer: Buffer,
128
/// #[storage_texture(5)]
129
/// storage_texture: Handle<Image>,
130
/// }
131
/// ```
132
///
133
/// In WGSL shaders, the binding would look like this:
134
///
135
/// ```wgsl
136
/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> color: vec4<f32>;
137
/// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var color_texture: texture_2d<f32>;
138
/// @group(#{MATERIAL_BIND_GROUP}) @binding(2) var color_sampler: sampler;
139
/// @group(#{MATERIAL_BIND_GROUP}) @binding(3) var<storage> storage_buffer: array<f32>;
140
/// @group(#{MATERIAL_BIND_GROUP}) @binding(4) var<storage> raw_buffer: array<f32>;
141
/// @group(#{MATERIAL_BIND_GROUP}) @binding(5) var storage_texture: texture_storage_2d<rgba8unorm, read_write>;
142
/// ```
143
/// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups
144
/// are generally bound to group 2.
145
///
146
/// The following field-level attributes are supported:
147
///
148
/// ## `uniform(BINDING_INDEX)`
149
///
150
/// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a uniform.
151
/// [`ShaderType`] is implemented for most math types already, such as [`f32`], [`Vec4`](bevy_math::Vec4), and
152
/// [`LinearRgba`](bevy_color::LinearRgba). It can also be derived for custom structs.
153
///
154
/// ## `texture(BINDING_INDEX, arguments)`
155
///
156
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
157
/// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
158
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
159
/// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute
160
/// (with a different binding index) if a binding of the sampler for the [`Image`](bevy_image::Image) is also required.
161
///
162
/// | Arguments | Values | Default |
163
/// |-----------------------|-------------------------------------------------------------------------|----------------------|
164
/// | `dimension` = "..." | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"` | `"2d"` |
165
/// | `sample_type` = "..." | `"float"`, `"depth"`, `"s_int"` or `"u_int"` | `"float"` |
166
/// | `filterable` = ... | `true`, `false` | `true` |
167
/// | `multisampled` = ... | `true`, `false` | `false` |
168
/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` |
169
///
170
/// ## `storage_texture(BINDING_INDEX, arguments)`
171
///
172
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
173
/// 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,
174
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
175
/// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead.
176
///
177
/// | Arguments | Values | Default |
178
/// |------------------------|--------------------------------------------------------------------------------------------|---------------|
179
/// | `dimension` = "..." | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"` | `"2d"` |
180
/// | `image_format` = ... | any member of [`TextureFormat`](crate::render_resource::TextureFormat) | `Rgba8Unorm` |
181
/// | `access` = ... | any member of [`StorageTextureAccess`](crate::render_resource::StorageTextureAccess) | `ReadWrite` |
182
/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `compute` |
183
///
184
/// ## `sampler(BINDING_INDEX, arguments)`
185
///
186
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Sampler`] GPU
187
/// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
188
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
189
/// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute
190
/// (with a different binding index) if a binding of the texture for the [`Image`](bevy_image::Image) is also required.
191
///
192
/// | Arguments | Values | Default |
193
/// |------------------------|-------------------------------------------------------------------------|------------------------|
194
/// | `sampler_type` = "..." | `"filtering"`, `"non_filtering"`, `"comparison"`. | `"filtering"` |
195
/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` |
196
///
197
/// ## `storage(BINDING_INDEX, arguments)`
198
///
199
/// * The field's [`Handle<Storage>`](bevy_asset::Handle) will be used to look
200
/// up the matching [`Buffer`] GPU resource, which will be bound as a storage
201
/// buffer in shaders. If the `storage` attribute is used, the field is expected
202
/// a raw buffer, and the buffer will be bound as a storage buffer in shaders.
203
/// In bindless mode, `binding_array()` argument that specifies the binding
204
/// number of the resulting storage buffer binding array must be present.
205
///
206
/// | Arguments | Values | Default |
207
/// |------------------------|-------------------------------------------------------------------------|------------------------|
208
/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` |
209
/// | `read_only` | if present then value is true, otherwise false | `false` |
210
/// | `buffer` | if present then the field will be assumed to be a raw wgpu buffer | |
211
/// | `binding_array(...)` | the binding number of the binding array, for bindless mode | bindless mode disabled |
212
///
213
/// Note that fields without field-level binding attributes will be ignored.
214
/// ```
215
/// # use bevy_render::{render_resource::AsBindGroup};
216
/// # use bevy_color::LinearRgba;
217
/// # use bevy_asset::Handle;
218
/// #[derive(AsBindGroup)]
219
/// struct CoolMaterial {
220
/// #[uniform(0)]
221
/// color: LinearRgba,
222
/// this_field_is_ignored: String,
223
/// }
224
/// ```
225
///
226
/// As mentioned above, [`Option<Handle<Image>>`] is also supported:
227
/// ```
228
/// # use bevy_asset::Handle;
229
/// # use bevy_color::LinearRgba;
230
/// # use bevy_image::Image;
231
/// # use bevy_render::render_resource::AsBindGroup;
232
/// #[derive(AsBindGroup)]
233
/// struct CoolMaterial {
234
/// #[uniform(0)]
235
/// color: LinearRgba,
236
/// #[texture(1)]
237
/// #[sampler(2)]
238
/// color_texture: Option<Handle<Image>>,
239
/// }
240
/// ```
241
/// 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
242
/// to "pure white".
243
///
244
/// Field uniforms with the same index will be combined into a single binding:
245
/// ```
246
/// # use bevy_render::{render_resource::AsBindGroup};
247
/// # use bevy_color::LinearRgba;
248
/// #[derive(AsBindGroup)]
249
/// struct CoolMaterial {
250
/// #[uniform(0)]
251
/// color: LinearRgba,
252
/// #[uniform(0)]
253
/// roughness: f32,
254
/// }
255
/// ```
256
///
257
/// In WGSL shaders, the binding would look like this:
258
/// ```wgsl
259
/// struct CoolMaterial {
260
/// color: vec4<f32>,
261
/// roughness: f32,
262
/// };
263
///
264
/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> material: CoolMaterial;
265
/// ```
266
///
267
/// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes:
268
/// ## `uniform(BINDING_INDEX, ConvertedShaderType)`
269
///
270
/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a
271
/// uniform, much like the field-level `uniform` attribute. The difference is
272
/// that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`,
273
/// which must implement [`ShaderType`], instead of a specific field
274
/// implementing [`ShaderType`]. This is useful if more complicated conversion
275
/// logic is required, or when using bindless mode (see below). The conversion
276
/// is done using the [`AsBindGroupShaderType<ConvertedShaderType>`] trait,
277
/// which is automatically implemented if `&Self` implements
278
/// [`Into<ConvertedShaderType>`]. Outside of bindless mode, only use
279
/// [`AsBindGroupShaderType`] if access to resources like
280
/// [`RenderAssets<GpuImage>`] is required.
281
///
282
/// * In bindless mode (see `bindless(COUNT)`), this attribute becomes
283
/// `uniform(BINDLESS_INDEX, ConvertedShaderType,
284
/// binding_array(BINDING_INDEX))`. The resulting uniform buffers will be
285
/// available in the shader as a binding array at the given `BINDING_INDEX`. The
286
/// `BINDLESS_INDEX` specifies the offset of the buffer in the bindless index
287
/// table.
288
///
289
/// For example, suppose that the material slot is stored in a variable named
290
/// `slot`, the bindless index table is named `material_indices`, and that the
291
/// first field (index 0) of the bindless index table type is named
292
/// `material`. Then specifying `#[uniform(0, StandardMaterialUniform,
293
/// binding_array(10)]` will create a binding array buffer declared in the
294
/// shader as `var<storage> material_array:
295
/// binding_array<StandardMaterialUniform>` and accessible as
296
/// `material_array[material_indices[slot].material]`.
297
///
298
/// ## `data(BINDING_INDEX, ConvertedShaderType, binding_array(BINDING_INDEX))`
299
///
300
/// * This is very similar to `uniform(BINDING_INDEX, ConvertedShaderType,
301
/// binding_array(BINDING_INDEX)` and in fact is identical if bindless mode
302
/// isn't being used. The difference is that, in bindless mode, the `data`
303
/// attribute produces a single buffer containing an array, not an array of
304
/// buffers. For example, suppose you had the following declaration:
305
///
306
/// ```ignore
307
/// #[uniform(0, StandardMaterialUniform, binding_array(10))]
308
/// struct StandardMaterial { ... }
309
/// ```
310
///
311
/// In bindless mode, this will produce a binding matching the following WGSL
312
/// declaration:
313
///
314
/// ```wgsl
315
/// @group(#{MATERIAL_BIND_GROUP}) @binding(10) var<storage> material_array: binding_array<StandardMaterial>;
316
/// ```
317
///
318
/// On the other hand, if you write this declaration:
319
///
320
/// ```ignore
321
/// #[data(0, StandardMaterialUniform, binding_array(10))]
322
/// struct StandardMaterial { ... }
323
/// ```
324
///
325
/// Then Bevy produces a binding that matches this WGSL declaration instead:
326
///
327
/// ```wgsl
328
/// @group(#{MATERIAL_BIND_GROUP}) @binding(10) var<storage> material_array: array<StandardMaterial>;
329
/// ```
330
///
331
/// * Just as with the structure-level `uniform` attribute, Bevy converts the
332
/// entire [`AsBindGroup`] to `ConvertedShaderType`, using the
333
/// [`AsBindGroupShaderType<ConvertedShaderType>`] trait.
334
///
335
/// * In non-bindless mode, the structure-level `data` attribute is the same as
336
/// the structure-level `uniform` attribute and produces a single uniform buffer
337
/// in the shader. The above example would result in a binding that looks like
338
/// this in WGSL in non-bindless mode:
339
///
340
/// ```wgsl
341
/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> material: StandardMaterial;
342
/// ```
343
///
344
/// * For efficiency reasons, `data` is generally preferred over `uniform`
345
/// unless you need to place your data in individual buffers.
346
///
347
/// ## `bind_group_data(DataType)`
348
///
349
/// * The [`AsBindGroup`] type will be converted to some `DataType` using [`Into<DataType>`] and stored
350
/// as [`AsBindGroup::Data`] as part of the [`AsBindGroup::as_bind_group`] call. This is useful if data needs to be stored alongside
351
/// the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute
352
/// is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline).
353
///
354
/// ## `bindless`
355
///
356
/// * This switch enables *bindless resources*, which changes the way Bevy
357
/// supplies resources (textures, and samplers) to the shader. When bindless
358
/// resources are enabled, and the current platform supports them, Bevy will
359
/// allocate textures, and samplers into *binding arrays*, separated based on
360
/// type and will supply your shader with indices into those arrays.
361
/// * Bindless textures and samplers are placed into the appropriate global
362
/// array defined in `bevy_render::bindless` (`bindless.wgsl`).
363
/// * Bevy doesn't currently support bindless buffers, except for those created
364
/// with the `uniform(BINDLESS_INDEX, ConvertedShaderType,
365
/// binding_array(BINDING_INDEX))` attribute. If you need to include a buffer in
366
/// your object, and you can't create the data in that buffer with the `uniform`
367
/// attribute, consider a non-bindless object instead.
368
/// * If bindless mode is enabled, the `BINDLESS` definition will be
369
/// available. Because not all platforms support bindless resources, you
370
/// should check for the presence of this definition via `#ifdef` and fall
371
/// back to standard bindings if it isn't present.
372
/// * By default, in bindless mode, binding 0 becomes the *bindless index
373
/// table*, which is an array of structures, each of which contains as many
374
/// fields of type `u32` as the highest binding number in the structure
375
/// annotated with `#[derive(AsBindGroup)]`. Again by default, the *i*th field
376
/// of the bindless index table contains the index of the resource with binding
377
/// *i* within the appropriate binding array.
378
/// * In the case of materials, the index of the applicable table within the
379
/// bindless index table list corresponding to the mesh currently being drawn
380
/// can be retrieved with
381
/// `mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu`.
382
/// * You can limit the size of the bindless slabs to N resources with the
383
/// `limit(N)` declaration. For example, `#[bindless(limit(16))]` ensures that
384
/// each slab will have no more than 16 total resources in it. If you don't
385
/// specify a limit, Bevy automatically picks a reasonable one for the current
386
/// platform.
387
/// * The `index_table(range(M..N), binding(B))` declaration allows you to
388
/// customize the layout of the bindless index table. This is useful for
389
/// materials that are composed of multiple bind groups, such as
390
/// `ExtendedMaterial`. In such cases, there will be multiple bindless index
391
/// tables, so they can't both be assigned to binding 0 or their bindings will
392
/// conflict.
393
/// - The `binding(B)` attribute of the `index_table` attribute allows you to
394
/// customize the binding (`@binding(B)`, in the shader) at which the index
395
/// table will be bound.
396
/// - The `range(M, N)` attribute of the `index_table` attribute allows you to
397
/// change the mapping from the field index in the bindless index table to the
398
/// bindless index. Instead of the field at index $i$ being mapped to the
399
/// bindless index $i$, with the `range(M, N)` attribute the field at index
400
/// $i$ in the bindless index table is mapped to the bindless index $i$ + M.
401
/// The size of the index table will be set to N - M. Note that this may
402
/// result in the table being too small to contain all the bindless bindings.
403
/// * The purpose of bindless mode is to improve performance by reducing
404
/// state changes. By grouping resources together into binding arrays, Bevy
405
/// doesn't have to modify GPU state as often, decreasing API and driver
406
/// overhead.
407
/// * See the `shaders/shader_material_bindless` example for an example of how
408
/// to use bindless mode. See the `shaders/extended_material_bindless` example
409
/// for a more exotic example of bindless mode that demonstrates the
410
/// `index_table` attribute.
411
/// * The following diagram illustrates how bindless mode works using a subset
412
/// of `StandardMaterial`:
413
///
414
/// ```text
415
/// Shader Bindings Sampler Binding Array
416
/// +----+-----------------------------+ +-----------+-----------+-----+
417
/// +---| 0 | material_indices | +->| sampler 0 | sampler 1 | ... |
418
/// | +----+-----------------------------+ | +-----------+-----------+-----+
419
/// | | 1 | bindless_samplers_filtering +--+ ^
420
/// | +----+-----------------------------+ +-------------------------------+
421
/// | | .. | ... | |
422
/// | +----+-----------------------------+ Texture Binding Array |
423
/// | | 5 | bindless_textures_2d +--+ +-----------+-----------+-----+ |
424
/// | +----+-----------------------------+ +->| texture 0 | texture 1 | ... | |
425
/// | | .. | ... | +-----------+-----------+-----+ |
426
/// | +----+-----------------------------+ ^ |
427
/// | + 10 | material_array +--+ +---------------------------+ |
428
/// | +----+-----------------------------+ | | |
429
/// | | Buffer Binding Array | |
430
/// | | +----------+----------+-----+ | |
431
/// | +->| buffer 0 | buffer 1 | ... | | |
432
/// | Material Bindless Indices +----------+----------+-----+ | |
433
/// | +----+-----------------------------+ ^ | |
434
/// +-->| 0 | material +----------+ | |
435
/// +----+-----------------------------+ | |
436
/// | 1 | base_color_texture +---------------------------------------+ |
437
/// +----+-----------------------------+ |
438
/// | 2 | base_color_sampler +-------------------------------------------+
439
/// +----+-----------------------------+
440
/// | .. | ... |
441
/// +----+-----------------------------+
442
/// ```
443
///
444
/// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can
445
/// also be equivalently represented with a single struct-level uniform attribute:
446
/// ```
447
/// # use bevy_render::{render_resource::{AsBindGroup, ShaderType}};
448
/// # use bevy_color::LinearRgba;
449
/// #[derive(AsBindGroup)]
450
/// #[uniform(0, CoolMaterialUniform)]
451
/// struct CoolMaterial {
452
/// color: LinearRgba,
453
/// roughness: f32,
454
/// }
455
///
456
/// #[derive(ShaderType)]
457
/// struct CoolMaterialUniform {
458
/// color: LinearRgba,
459
/// roughness: f32,
460
/// }
461
///
462
/// impl From<&CoolMaterial> for CoolMaterialUniform {
463
/// fn from(material: &CoolMaterial) -> CoolMaterialUniform {
464
/// CoolMaterialUniform {
465
/// color: material.color,
466
/// roughness: material.roughness,
467
/// }
468
/// }
469
/// }
470
/// ```
471
///
472
/// Setting `bind_group_data` looks like this:
473
/// ```
474
/// # use bevy_render::{render_resource::AsBindGroup};
475
/// # use bevy_color::LinearRgba;
476
/// #[derive(AsBindGroup)]
477
/// #[bind_group_data(CoolMaterialKey)]
478
/// struct CoolMaterial {
479
/// #[uniform(0)]
480
/// color: LinearRgba,
481
/// is_shaded: bool,
482
/// }
483
///
484
/// // Materials keys are intended to be small, cheap to hash, and
485
/// // uniquely identify a specific material permutation.
486
/// #[repr(C)]
487
/// #[derive(Copy, Clone, Hash, Eq, PartialEq)]
488
/// struct CoolMaterialKey {
489
/// is_shaded: bool,
490
/// }
491
///
492
/// impl From<&CoolMaterial> for CoolMaterialKey {
493
/// fn from(material: &CoolMaterial) -> CoolMaterialKey {
494
/// CoolMaterialKey {
495
/// is_shaded: material.is_shaded,
496
/// }
497
/// }
498
/// }
499
/// ```
500
pub trait AsBindGroup {
501
/// Data that will be stored alongside the "prepared" bind group.
502
type Data: Send + Sync;
503
504
type Param: SystemParam + 'static;
505
506
/// The number of slots per bind group, if bindless mode is enabled.
507
///
508
/// If this bind group doesn't use bindless, then this will be `None`.
509
///
510
/// Note that the *actual* slot count may be different from this value, due
511
/// to platform limitations. For example, if bindless resources aren't
512
/// supported on this platform, the actual slot count will be 1.
513
fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
514
None
515
}
516
517
/// True if the hardware *actually* supports bindless textures for this
518
/// type, taking the device and driver capabilities into account.
519
///
520
/// If this type doesn't use bindless textures, then the return value from
521
/// this function is meaningless.
522
fn bindless_supported(_: &RenderDevice) -> bool {
523
true
524
}
525
526
/// label
527
fn label() -> &'static str;
528
529
/// Creates a bind group for `self` matching the layout defined in [`AsBindGroup::bind_group_layout`].
530
fn as_bind_group(
531
&self,
532
layout_descriptor: &BindGroupLayoutDescriptor,
533
render_device: &RenderDevice,
534
pipeline_cache: &PipelineCache,
535
param: &mut SystemParamItem<'_, '_, Self::Param>,
536
) -> Result<PreparedBindGroup, AsBindGroupError> {
537
let layout = &pipeline_cache.get_bind_group_layout(layout_descriptor);
538
539
let UnpreparedBindGroup { bindings } =
540
Self::unprepared_bind_group(self, layout, render_device, param, false)?;
541
542
let entries = bindings
543
.iter()
544
.map(|(index, binding)| BindGroupEntry {
545
binding: *index,
546
resource: binding.get_binding(),
547
})
548
.collect::<Vec<_>>();
549
550
let bind_group = render_device.create_bind_group(Self::label(), layout, &entries);
551
552
Ok(PreparedBindGroup {
553
bindings,
554
bind_group,
555
})
556
}
557
558
fn bind_group_data(&self) -> Self::Data;
559
560
/// Returns a vec of (binding index, `OwnedBindingResource`).
561
///
562
/// In cases where `OwnedBindingResource` is not available (as for bindless
563
/// texture arrays currently), an implementor may return
564
/// `AsBindGroupError::CreateBindGroupDirectly` from this function and
565
/// instead define `as_bind_group` directly. This may prevent certain
566
/// features, such as bindless mode, from working correctly.
567
///
568
/// Set `force_no_bindless` to true to require that bindless textures *not*
569
/// be used. `ExtendedMaterial` uses this in order to ensure that the base
570
/// material doesn't use bindless mode if the extension doesn't.
571
fn unprepared_bind_group(
572
&self,
573
layout: &BindGroupLayout,
574
render_device: &RenderDevice,
575
param: &mut SystemParamItem<'_, '_, Self::Param>,
576
force_no_bindless: bool,
577
) -> Result<UnpreparedBindGroup, AsBindGroupError>;
578
579
/// Creates the bind group layout matching all bind groups returned by
580
/// [`AsBindGroup::as_bind_group`]
581
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
582
where
583
Self: Sized,
584
{
585
render_device.create_bind_group_layout(
586
Self::label(),
587
&Self::bind_group_layout_entries(render_device, false),
588
)
589
}
590
591
/// Creates the bind group layout descriptor matching all bind groups returned by
592
/// [`AsBindGroup::as_bind_group`]
593
/// TODO: we only need `RenderDevice` to determine if bindless is supported
594
fn bind_group_layout_descriptor(render_device: &RenderDevice) -> BindGroupLayoutDescriptor
595
where
596
Self: Sized,
597
{
598
BindGroupLayoutDescriptor {
599
label: Self::label().into(),
600
entries: Self::bind_group_layout_entries(render_device, false),
601
}
602
}
603
604
/// Returns a vec of bind group layout entries.
605
///
606
/// Set `force_no_bindless` to true to require that bindless textures *not*
607
/// be used. `ExtendedMaterial` uses this in order to ensure that the base
608
/// material doesn't use bindless mode if the extension doesn't.
609
fn bind_group_layout_entries(
610
render_device: &RenderDevice,
611
force_no_bindless: bool,
612
) -> Vec<BindGroupLayoutEntry>
613
where
614
Self: Sized;
615
616
fn bindless_descriptor() -> Option<BindlessDescriptor> {
617
None
618
}
619
}
620
621
/// An error that occurs during [`AsBindGroup::as_bind_group`] calls.
622
#[derive(Debug, Error)]
623
pub enum AsBindGroupError {
624
/// The bind group could not be generated. Try again next frame.
625
#[error("The bind group could not be generated")]
626
RetryNextUpdate,
627
#[error("Create the bind group via `as_bind_group()` instead")]
628
CreateBindGroupDirectly,
629
#[error("At binding index {0}, the provided image sampler `{1}` does not match the required sampler type(s) `{2}`.")]
630
InvalidSamplerType(u32, String, String),
631
}
632
633
/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`].
634
pub struct PreparedBindGroup {
635
pub bindings: BindingResources,
636
pub bind_group: BindGroup,
637
}
638
639
/// a map containing `OwnedBindingResource`s, keyed by the target binding index
640
pub struct UnpreparedBindGroup {
641
pub bindings: BindingResources,
642
}
643
644
/// A pair of binding index and binding resource, used as part of
645
/// [`PreparedBindGroup`] and [`UnpreparedBindGroup`].
646
#[derive(Deref, DerefMut)]
647
pub struct BindingResources(pub Vec<(u32, OwnedBindingResource)>);
648
649
/// An owned binding resource of any type (ex: a [`Buffer`], [`TextureView`], etc).
650
/// This is used by types like [`PreparedBindGroup`] to hold a single list of all
651
/// render resources used by bindings.
652
#[derive(Debug)]
653
pub enum OwnedBindingResource {
654
Buffer(Buffer),
655
TextureView(TextureViewDimension, TextureView),
656
Sampler(SamplerBindingType, Sampler),
657
Data(OwnedData),
658
}
659
660
/// Data that will be copied into a GPU buffer.
661
///
662
/// This corresponds to the `#[data]` attribute in `AsBindGroup`.
663
#[derive(Debug, Deref, DerefMut)]
664
pub struct OwnedData(pub Vec<u8>);
665
666
impl OwnedBindingResource {
667
/// Creates a [`BindingResource`] reference to this
668
/// [`OwnedBindingResource`].
669
///
670
/// Note that this operation panics if passed a
671
/// [`OwnedBindingResource::Data`], because [`OwnedData`] doesn't itself
672
/// correspond to any binding and instead requires the
673
/// `MaterialBindGroupAllocator` to pack it into a buffer.
674
pub fn get_binding(&self) -> BindingResource<'_> {
675
match self {
676
OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(),
677
OwnedBindingResource::TextureView(_, view) => BindingResource::TextureView(view),
678
OwnedBindingResource::Sampler(_, sampler) => BindingResource::Sampler(sampler),
679
OwnedBindingResource::Data(_) => panic!("`OwnedData` has no binding resource"),
680
}
681
}
682
}
683
684
/// Converts a value to a [`ShaderType`] for use in a bind group.
685
///
686
/// This is automatically implemented for references that implement [`Into`].
687
/// Generally normal [`Into`] / [`From`] impls should be preferred, but
688
/// sometimes additional runtime metadata is required.
689
/// This exists largely to make some [`AsBindGroup`] use cases easier.
690
pub trait AsBindGroupShaderType<T: ShaderType> {
691
/// Return the `T` [`ShaderType`] for `self`. When used in [`AsBindGroup`]
692
/// derives, it is safe to assume that all images in `self` exist.
693
fn as_bind_group_shader_type(&self, images: &RenderAssets<GpuImage>) -> T;
694
}
695
696
impl<T, U: ShaderType> AsBindGroupShaderType<U> for T
697
where
698
for<'a> &'a T: Into<U>,
699
{
700
#[inline]
701
fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> U {
702
self.into()
703
}
704
}
705
706
#[cfg(test)]
707
mod test {
708
use super::*;
709
use bevy_asset::Handle;
710
use bevy_image::Image;
711
712
#[test]
713
fn texture_visibility() {
714
#[expect(
715
dead_code,
716
reason = "This is a derive macro compilation test. It will not be constructed."
717
)]
718
#[derive(AsBindGroup)]
719
pub struct TextureVisibilityTest {
720
#[texture(0, visibility(all))]
721
pub all: Handle<Image>,
722
#[texture(1, visibility(none))]
723
pub none: Handle<Image>,
724
#[texture(2, visibility(fragment))]
725
pub fragment: Handle<Image>,
726
#[texture(3, visibility(vertex))]
727
pub vertex: Handle<Image>,
728
#[texture(4, visibility(compute))]
729
pub compute: Handle<Image>,
730
#[texture(5, visibility(vertex, fragment))]
731
pub vertex_fragment: Handle<Image>,
732
#[texture(6, visibility(vertex, compute))]
733
pub vertex_compute: Handle<Image>,
734
#[texture(7, visibility(fragment, compute))]
735
pub fragment_compute: Handle<Image>,
736
#[texture(8, visibility(vertex, fragment, compute))]
737
pub vertex_fragment_compute: Handle<Image>,
738
}
739
}
740
}
741
742