Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/render_resource/bindless.rs
9350 views
1
//! Types and functions relating to bindless resources.
2
3
use alloc::borrow::Cow;
4
use core::{
5
num::{NonZeroU32, NonZeroU64},
6
ops::Range,
7
};
8
9
use bevy_derive::{Deref, DerefMut};
10
use wgpu::{
11
BindGroupLayoutEntry, SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension,
12
};
13
14
use bevy_material::bind_group_layout_entries::binding_types::{
15
sampler, storage_buffer_read_only_sized, texture_1d, texture_2d, texture_2d_array, texture_3d,
16
texture_cube, texture_cube_array,
17
};
18
19
/// The default value for the number of resources that can be stored in a slab
20
/// on this platform.
21
///
22
/// See the documentation for [`BindlessSlabResourceLimit`] for more
23
/// information.
24
#[cfg(any(target_os = "macos", target_os = "ios"))]
25
pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 64;
26
/// The default value for the number of resources that can be stored in a slab
27
/// on this platform.
28
///
29
/// See the documentation for [`BindlessSlabResourceLimit`] for more
30
/// information.
31
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
32
pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 2048;
33
34
/// The binding numbers for the built-in binding arrays of each bindless
35
/// resource type.
36
///
37
/// In the case of materials, the material allocator manages these binding
38
/// arrays.
39
///
40
/// `bindless.wgsl` contains declarations of these arrays for use in your
41
/// shaders. If you change these, make sure to update that file as well.
42
pub static BINDING_NUMBERS: [(BindlessResourceType, BindingNumber); 9] = [
43
(BindlessResourceType::SamplerFiltering, BindingNumber(1)),
44
(BindlessResourceType::SamplerNonFiltering, BindingNumber(2)),
45
(BindlessResourceType::SamplerComparison, BindingNumber(3)),
46
(BindlessResourceType::Texture1d, BindingNumber(4)),
47
(BindlessResourceType::Texture2d, BindingNumber(5)),
48
(BindlessResourceType::Texture2dArray, BindingNumber(6)),
49
(BindlessResourceType::Texture3d, BindingNumber(7)),
50
(BindlessResourceType::TextureCube, BindingNumber(8)),
51
(BindlessResourceType::TextureCubeArray, BindingNumber(9)),
52
];
53
54
/// The maximum number of resources that can be stored in a slab.
55
///
56
/// This limit primarily exists in order to work around `wgpu` performance
57
/// problems involving large numbers of bindless resources. Also, some
58
/// platforms, such as Metal, currently enforce limits on the number of
59
/// resources in use.
60
///
61
/// This corresponds to `LIMIT` in the `#[bindless(LIMIT)]` attribute when
62
/// deriving [`crate::render_resource::AsBindGroup`].
63
#[derive(Clone, Copy, Default, PartialEq, Debug)]
64
pub enum BindlessSlabResourceLimit {
65
/// Allows the renderer to choose a reasonable value for the resource limit
66
/// based on the platform.
67
///
68
/// This value has been tuned, so you should default to this value unless
69
/// you have special platform-specific considerations that prevent you from
70
/// using it.
71
#[default]
72
Auto,
73
74
/// A custom value for the resource limit.
75
///
76
/// Bevy will allocate no more than this number of resources in a slab,
77
/// unless exceeding this value is necessary in order to allocate at all
78
/// (i.e. unless the number of bindless resources in your bind group exceeds
79
/// this value), in which case Bevy can exceed it.
80
Custom(u32),
81
}
82
83
/// Information about the bindless resources in this object.
84
///
85
/// The material bind group allocator uses this descriptor in order to create
86
/// and maintain bind groups. The fields within this bindless descriptor are
87
/// [`Cow`]s in order to support both the common case in which the fields are
88
/// simply `static` constants and the more unusual case in which the fields are
89
/// dynamically generated efficiently. An example of the latter case is
90
/// `ExtendedMaterial`, which needs to assemble a bindless descriptor from those
91
/// of the base material and the material extension at runtime.
92
///
93
/// This structure will only be present if this object is bindless.
94
pub struct BindlessDescriptor {
95
/// The bindless resource types that this object uses, in order of bindless
96
/// index.
97
///
98
/// The resource assigned to binding index 0 will be at index 0, the
99
/// resource assigned to binding index will be at index 1 in this array, and
100
/// so on. Unused binding indices are set to [`BindlessResourceType::None`].
101
pub resources: Cow<'static, [BindlessResourceType]>,
102
/// The [`BindlessBufferDescriptor`] for each bindless buffer that this
103
/// object uses.
104
///
105
/// The order of this array is irrelevant.
106
pub buffers: Cow<'static, [BindlessBufferDescriptor]>,
107
/// The [`BindlessIndexTableDescriptor`]s describing each bindless index
108
/// table.
109
///
110
/// This list must be sorted by the first bindless index.
111
pub index_tables: Cow<'static, [BindlessIndexTableDescriptor]>,
112
}
113
114
/// The type of potentially-bindless resource.
115
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
116
pub enum BindlessResourceType {
117
/// No bindless resource.
118
///
119
/// This is used as a placeholder to fill holes in the
120
/// [`BindlessDescriptor::resources`] list.
121
None,
122
/// A storage buffer.
123
Buffer,
124
/// A filtering sampler.
125
SamplerFiltering,
126
/// A non-filtering sampler (nearest neighbor).
127
SamplerNonFiltering,
128
/// A comparison sampler (typically used for shadow maps).
129
SamplerComparison,
130
/// A 1D texture.
131
Texture1d,
132
/// A 2D texture.
133
Texture2d,
134
/// A 2D texture array.
135
///
136
/// Note that this differs from a binding array. 2D texture arrays must all
137
/// have the same size and format.
138
Texture2dArray,
139
/// A 3D texture.
140
Texture3d,
141
/// A cubemap texture.
142
TextureCube,
143
/// A cubemap texture array.
144
///
145
/// Note that this differs from a binding array. Cubemap texture arrays must
146
/// all have the same size and format.
147
TextureCubeArray,
148
/// Multiple instances of plain old data concatenated into a single buffer.
149
///
150
/// This corresponds to the `#[data]` declaration in
151
/// [`crate::render_resource::AsBindGroup`].
152
///
153
/// Note that this resource doesn't itself map to a GPU-level binding
154
/// resource and instead depends on the `MaterialBindGroupAllocator` to
155
/// create a binding resource for it.
156
DataBuffer,
157
}
158
159
/// Describes a bindless buffer.
160
///
161
/// Unlike samplers and textures, each buffer in a bind group gets its own
162
/// unique bind group entry. That is, there isn't any `bindless_buffers` binding
163
/// array to go along with `bindless_textures_2d`,
164
/// `bindless_samplers_filtering`, etc. Therefore, this descriptor contains two
165
/// indices: the *binding number* and the *bindless index*. The binding number
166
/// is the `@binding` number used in the shader, while the bindless index is the
167
/// index of the buffer in the bindless index table (which is itself
168
/// conventionally bound to binding number 0).
169
///
170
/// When declaring the buffer in a derived implementation
171
/// [`crate::render_resource::AsBindGroup`] with syntax like
172
/// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
173
/// bindless(BINDING_NUMBER)]`, the bindless index is `BINDLESS_INDEX`, and the
174
/// binding number is `BINDING_NUMBER`. Note the order.
175
#[derive(Clone, Copy, Debug)]
176
pub struct BindlessBufferDescriptor {
177
/// The actual binding number of the buffer.
178
///
179
/// This is declared with `@binding` in WGSL. When deriving
180
/// [`crate::render_resource::AsBindGroup`], this is the `BINDING_NUMBER` in
181
/// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
182
/// bindless(BINDING_NUMBER)]`.
183
pub binding_number: BindingNumber,
184
/// The index of the buffer in the bindless index table.
185
///
186
/// In the shader, this is the index into the table bound to binding 0. When
187
/// deriving [`crate::render_resource::AsBindGroup`], this is the
188
/// `BINDLESS_INDEX` in `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
189
/// bindless(BINDING_NUMBER)]`.
190
pub bindless_index: BindlessIndex,
191
/// The size of the buffer in bytes, if known.
192
pub size: Option<usize>,
193
}
194
195
/// Describes the layout of the bindless index table, which maps bindless
196
/// indices to indices within the binding arrays.
197
#[derive(Clone)]
198
pub struct BindlessIndexTableDescriptor {
199
/// The range of bindless indices that this descriptor covers.
200
pub indices: Range<BindlessIndex>,
201
/// The binding at which the index table itself will be bound.
202
///
203
/// By default, this is binding 0, but it can be changed with the
204
/// `#[bindless(index_table(binding(B)))]` attribute.
205
pub binding_number: BindingNumber,
206
}
207
208
/// The index of the actual binding in the bind group.
209
///
210
/// This is the value specified in WGSL as `@binding`.
211
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)]
212
pub struct BindingNumber(pub u32);
213
214
/// The index in the bindless index table.
215
///
216
/// This table is conventionally bound to binding number 0.
217
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Debug, Deref, DerefMut)]
218
pub struct BindlessIndex(pub u32);
219
220
/// Creates the bind group layout entries common to all shaders that use
221
/// bindless bind groups.
222
///
223
/// `bindless_resource_count` specifies the total number of bindless resources.
224
/// `bindless_slab_resource_limit` specifies the resolved
225
/// [`BindlessSlabResourceLimit`] value.
226
pub fn create_bindless_bind_group_layout_entries(
227
bindless_index_table_length: u32,
228
bindless_slab_resource_limit: u32,
229
bindless_index_table_binding_number: BindingNumber,
230
) -> Vec<BindGroupLayoutEntry> {
231
let bindless_slab_resource_limit =
232
NonZeroU32::new(bindless_slab_resource_limit).expect("Bindless slot count must be nonzero");
233
234
// The maximum size of a binding array is the
235
// `bindless_slab_resource_limit`, which would occur if all of the bindless
236
// resources were of the same type. So we create our binding arrays with
237
// that size.
238
239
vec![
240
// Start with the bindless index table, bound to binding number 0.
241
storage_buffer_read_only_sized(
242
false,
243
NonZeroU64::new(bindless_index_table_length as u64 * size_of::<u32>() as u64),
244
)
245
.build(
246
*bindless_index_table_binding_number,
247
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
248
),
249
// Continue with the common bindless resource arrays.
250
sampler(SamplerBindingType::Filtering)
251
.count(bindless_slab_resource_limit)
252
.build(
253
1,
254
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
255
),
256
sampler(SamplerBindingType::NonFiltering)
257
.count(bindless_slab_resource_limit)
258
.build(
259
2,
260
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
261
),
262
sampler(SamplerBindingType::Comparison)
263
.count(bindless_slab_resource_limit)
264
.build(
265
3,
266
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
267
),
268
texture_1d(TextureSampleType::Float { filterable: true })
269
.count(bindless_slab_resource_limit)
270
.build(
271
4,
272
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
273
),
274
texture_2d(TextureSampleType::Float { filterable: true })
275
.count(bindless_slab_resource_limit)
276
.build(
277
5,
278
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
279
),
280
texture_2d_array(TextureSampleType::Float { filterable: true })
281
.count(bindless_slab_resource_limit)
282
.build(
283
6,
284
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
285
),
286
texture_3d(TextureSampleType::Float { filterable: true })
287
.count(bindless_slab_resource_limit)
288
.build(
289
7,
290
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
291
),
292
texture_cube(TextureSampleType::Float { filterable: true })
293
.count(bindless_slab_resource_limit)
294
.build(
295
8,
296
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
297
),
298
texture_cube_array(TextureSampleType::Float { filterable: true })
299
.count(bindless_slab_resource_limit)
300
.build(
301
9,
302
ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE,
303
),
304
]
305
}
306
307
impl BindlessSlabResourceLimit {
308
/// Determines the actual bindless slab resource limit on this platform.
309
pub fn resolve(&self) -> u32 {
310
match *self {
311
BindlessSlabResourceLimit::Auto => AUTO_BINDLESS_SLAB_RESOURCE_LIMIT,
312
BindlessSlabResourceLimit::Custom(limit) => limit,
313
}
314
}
315
}
316
317
impl BindlessResourceType {
318
/// Returns the binding number for the common array of this resource type.
319
///
320
/// For example, if you pass `BindlessResourceType::Texture2d`, this will
321
/// return 5, in order to match the `@group(2) @binding(5) var
322
/// bindless_textures_2d: binding_array<texture_2d<f32>>` declaration in
323
/// `bindless.wgsl`.
324
///
325
/// Not all resource types have fixed binding numbers. If you call
326
/// [`Self::binding_number`] on such a resource type, it returns `None`.
327
///
328
/// Note that this returns a static reference to the binding number, not the
329
/// binding number itself. This is to conform to an idiosyncratic API in
330
/// `wgpu` whereby binding numbers for binding arrays are taken by `&u32`
331
/// *reference*, not by `u32` value.
332
pub fn binding_number(&self) -> Option<&'static BindingNumber> {
333
match BINDING_NUMBERS.binary_search_by_key(self, |(key, _)| *key) {
334
Ok(binding_number) => Some(&BINDING_NUMBERS[binding_number].1),
335
Err(_) => None,
336
}
337
}
338
}
339
340
impl From<TextureViewDimension> for BindlessResourceType {
341
fn from(texture_view_dimension: TextureViewDimension) -> Self {
342
match texture_view_dimension {
343
TextureViewDimension::D1 => BindlessResourceType::Texture1d,
344
TextureViewDimension::D2 => BindlessResourceType::Texture2d,
345
TextureViewDimension::D2Array => BindlessResourceType::Texture2dArray,
346
TextureViewDimension::Cube => BindlessResourceType::TextureCube,
347
TextureViewDimension::CubeArray => BindlessResourceType::TextureCubeArray,
348
TextureViewDimension::D3 => BindlessResourceType::Texture3d,
349
}
350
}
351
}
352
353
impl From<SamplerBindingType> for BindlessResourceType {
354
fn from(sampler_binding_type: SamplerBindingType) -> Self {
355
match sampler_binding_type {
356
SamplerBindingType::Filtering => BindlessResourceType::SamplerFiltering,
357
SamplerBindingType::NonFiltering => BindlessResourceType::SamplerNonFiltering,
358
SamplerBindingType::Comparison => BindlessResourceType::SamplerComparison,
359
}
360
}
361
}
362
363
impl From<u32> for BindlessIndex {
364
fn from(value: u32) -> Self {
365
Self(value)
366
}
367
}
368
369
impl From<u32> for BindingNumber {
370
fn from(value: u32) -> Self {
371
Self(value)
372
}
373
}
374
375