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