Skip to main content

bevy_render/render_resource/
bindless.rs

1//! Types and functions relating to bindless resources.
2
3use alloc::borrow::Cow;
4use core::{
5    num::{NonZeroU32, NonZeroU64},
6    ops::Range,
7};
8
9use bevy_derive::{Deref, DerefMut};
10use wgpu::{
11    BindGroupLayoutEntry, SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension,
12};
13
14use 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"))]
25pub 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")))]
32pub 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.wesl` contains declarations of these arrays for use in your
41/// shaders. If you change these, make sure to update that file as well.
42pub 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)]
64pub 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.
94pub 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)]
116pub 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 handle to a [`ShaderBuffer`](bevy_render::storage::ShaderBuffer).
125    ShaderBuffer,
126    /// A filtering sampler.
127    SamplerFiltering,
128    /// A non-filtering sampler (nearest neighbor).
129    SamplerNonFiltering,
130    /// A comparison sampler (typically used for shadow maps).
131    SamplerComparison,
132    /// A 1D texture.
133    Texture1d,
134    /// A 2D texture.
135    Texture2d,
136    /// A 2D texture array.
137    ///
138    /// Note that this differs from a binding array. 2D texture arrays must all
139    /// have the same size and format.
140    Texture2dArray,
141    /// A 3D texture.
142    Texture3d,
143    /// A cubemap texture.
144    TextureCube,
145    /// A cubemap texture array.
146    ///
147    /// Note that this differs from a binding array. Cubemap texture arrays must
148    /// all have the same size and format.
149    TextureCubeArray,
150    /// Multiple instances of plain old data concatenated into a single buffer.
151    ///
152    /// This corresponds to the `#[data]` declaration in
153    /// [`crate::render_resource::AsBindGroup`].
154    ///
155    /// Note that this resource doesn't itself map to a GPU-level binding
156    /// resource and instead depends on the `MaterialBindGroupAllocator` to
157    /// create a binding resource for it.
158    DataBuffer,
159}
160
161/// Describes a bindless buffer.
162///
163/// Unlike samplers and textures, each buffer in a bind group gets its own
164/// unique bind group entry. That is, there isn't any `bindless_buffers` binding
165/// array to go along with `bindless_textures_2d`,
166/// `bindless_samplers_filtering`, etc. Therefore, this descriptor contains two
167/// indices: the *binding number* and the *bindless index*. The binding number
168/// is the `@binding` number used in the shader, while the bindless index is the
169/// index of the buffer in the bindless index table (which is itself
170/// conventionally bound to binding number 0).
171///
172/// When declaring the buffer in a derived implementation
173/// [`crate::render_resource::AsBindGroup`] with syntax like
174/// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
175/// bindless(BINDING_NUMBER)]`, the bindless index is `BINDLESS_INDEX`, and the
176/// binding number is `BINDING_NUMBER`. Note the order.
177#[derive(Clone, Copy, Debug)]
178pub struct BindlessBufferDescriptor {
179    /// The actual binding number of the buffer.
180    ///
181    /// This is declared with `@binding` in WGSL. When deriving
182    /// [`crate::render_resource::AsBindGroup`], this is the `BINDING_NUMBER` in
183    /// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
184    /// bindless(BINDING_NUMBER)]`.
185    pub binding_number: BindingNumber,
186    /// The index of the buffer in the bindless index table.
187    ///
188    /// In the shader, this is the index into the table bound to binding 0. When
189    /// deriving [`crate::render_resource::AsBindGroup`], this is the
190    /// `BINDLESS_INDEX` in `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
191    /// bindless(BINDING_NUMBER)]`.
192    pub bindless_index: BindlessIndex,
193    /// The size of the buffer in bytes, if known.
194    pub size: Option<usize>,
195}
196
197/// Describes the layout of the bindless index table, which maps bindless
198/// indices to indices within the binding arrays.
199#[derive(Clone)]
200pub struct BindlessIndexTableDescriptor {
201    /// The range of bindless indices that this descriptor covers.
202    pub indices: Range<BindlessIndex>,
203    /// The binding at which the index table itself will be bound.
204    ///
205    /// By default, this is binding 0, but it can be changed with the
206    /// `#[bindless(index_table(binding(B)))]` attribute.
207    pub binding_number: BindingNumber,
208}
209
210/// The index of the actual binding in the bind group.
211///
212/// This is the value specified in WGSL as `@binding`.
213#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)]
214pub struct BindingNumber(pub u32);
215
216/// The index in the bindless index table.
217///
218/// This table is conventionally bound to binding number 0.
219#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Debug, Deref, DerefMut)]
220pub struct BindlessIndex(pub u32);
221
222/// Creates the bind group layout entries common to all shaders that use
223/// bindless bind groups.
224///
225/// `used_resource_types` limits which binding arrays are created,
226/// reducing argument buffer slot usage on constrained platforms.
227pub 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    used_resource_types: &[BindlessResourceType],
232) -> Vec<BindGroupLayoutEntry> {
233    let bindless_slab_resource_limit =
234        NonZeroU32::new(bindless_slab_resource_limit).expect("Bindless slot count must be nonzero");
235
236    let stages = ShaderStages::FRAGMENT | ShaderStages::VERTEX | ShaderStages::COMPUTE;
237
238    // Start the bindless index table; remaining entries are added
239    // below based on which resource types the material actually uses.
240
241    let mut entries = vec![
242        // Start with the bindless index table.
243        storage_buffer_read_only_sized(
244            false,
245            NonZeroU64::new(bindless_index_table_length as u64 * size_of::<u32>() as u64),
246        )
247        .build(*bindless_index_table_binding_number, stages),
248    ];
249
250    // Create binding arrays only for types that this material uses.
251    // This is important for platforms like Metal where each binding array uses a buffer slot
252    // (limited to 31 per the Metal Feature Set Tables:
253    // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf)
254    for &(resource_type, ref binding_number) in BINDING_NUMBERS.iter() {
255        if !used_resource_types.contains(&resource_type) {
256            continue;
257        }
258        let Some(binding_type) = (match resource_type {
259            BindlessResourceType::SamplerFiltering => Some(sampler(SamplerBindingType::Filtering)),
260            BindlessResourceType::SamplerNonFiltering => {
261                Some(sampler(SamplerBindingType::NonFiltering))
262            }
263            BindlessResourceType::SamplerComparison => {
264                Some(sampler(SamplerBindingType::Comparison))
265            }
266            BindlessResourceType::Texture1d => {
267                Some(texture_1d(TextureSampleType::Float { filterable: true }))
268            }
269            BindlessResourceType::Texture2d => {
270                Some(texture_2d(TextureSampleType::Float { filterable: true }))
271            }
272            BindlessResourceType::Texture2dArray => {
273                Some(texture_2d_array(TextureSampleType::Float {
274                    filterable: true,
275                }))
276            }
277            BindlessResourceType::Texture3d => {
278                Some(texture_3d(TextureSampleType::Float { filterable: true }))
279            }
280            BindlessResourceType::TextureCube => {
281                Some(texture_cube(TextureSampleType::Float { filterable: true }))
282            }
283            BindlessResourceType::TextureCubeArray => {
284                Some(texture_cube_array(TextureSampleType::Float {
285                    filterable: true,
286                }))
287            }
288            BindlessResourceType::None
289            | BindlessResourceType::Buffer
290            | BindlessResourceType::ShaderBuffer
291            | BindlessResourceType::DataBuffer => None,
292        }) else {
293            continue;
294        };
295        entries.push(
296            binding_type
297                .count(bindless_slab_resource_limit)
298                .build(**binding_number, stages),
299        );
300    }
301
302    entries
303}
304
305impl BindlessSlabResourceLimit {
306    /// Determines the actual bindless slab resource limit on this platform.
307    pub fn resolve(&self) -> u32 {
308        match *self {
309            BindlessSlabResourceLimit::Auto => AUTO_BINDLESS_SLAB_RESOURCE_LIMIT,
310            BindlessSlabResourceLimit::Custom(limit) => limit,
311        }
312    }
313}
314
315impl BindlessResourceType {
316    /// Returns the binding number for the common array of this resource type.
317    ///
318    /// For example, if you pass `BindlessResourceType::Texture2d`, this will
319    /// return 5, in order to match the `@group(2) @binding(5) var
320    /// bindless_textures_2d: binding_array<texture_2d<f32>>` declaration in
321    /// `bindless.wesl`.
322    ///
323    /// Not all resource types have fixed binding numbers. If you call
324    /// [`Self::binding_number`] on such a resource type, it returns `None`.
325    ///
326    /// Note that this returns a static reference to the binding number, not the
327    /// binding number itself. This is to conform to an idiosyncratic API in
328    /// `wgpu` whereby binding numbers for binding arrays are taken by `&u32`
329    /// *reference*, not by `u32` value.
330    pub fn binding_number(&self) -> Option<&'static BindingNumber> {
331        match BINDING_NUMBERS.binary_search_by_key(self, |(key, _)| *key) {
332            Ok(binding_number) => Some(&BINDING_NUMBERS[binding_number].1),
333            Err(_) => None,
334        }
335    }
336}
337
338impl From<TextureViewDimension> for BindlessResourceType {
339    fn from(texture_view_dimension: TextureViewDimension) -> Self {
340        match texture_view_dimension {
341            TextureViewDimension::D1 => BindlessResourceType::Texture1d,
342            TextureViewDimension::D2 => BindlessResourceType::Texture2d,
343            TextureViewDimension::D2Array => BindlessResourceType::Texture2dArray,
344            TextureViewDimension::Cube => BindlessResourceType::TextureCube,
345            TextureViewDimension::CubeArray => BindlessResourceType::TextureCubeArray,
346            TextureViewDimension::D3 => BindlessResourceType::Texture3d,
347        }
348    }
349}
350
351impl From<SamplerBindingType> for BindlessResourceType {
352    fn from(sampler_binding_type: SamplerBindingType) -> Self {
353        match sampler_binding_type {
354            SamplerBindingType::Filtering => BindlessResourceType::SamplerFiltering,
355            SamplerBindingType::NonFiltering => BindlessResourceType::SamplerNonFiltering,
356            SamplerBindingType::Comparison => BindlessResourceType::SamplerComparison,
357        }
358    }
359}
360
361impl From<u32> for BindlessIndex {
362    fn from(value: u32) -> Self {
363        Self(value)
364    }
365}
366
367impl From<u32> for BindingNumber {
368    fn from(value: u32) -> Self {
369        Self(value)
370    }
371}