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 crate::render_resource::binding_types::storage_buffer_read_only_sized;
15
16use 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"))]
26pub 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")))]
33pub 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.
43pub 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)]
65pub 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.
95pub 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)]
117pub 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)]
177pub 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)]
199pub 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)]
213pub 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)]
219pub 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.
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) -> 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(*bindless_index_table_binding_number, ShaderStages::all()),
247        // Continue with the common bindless resource arrays.
248        sampler(SamplerBindingType::Filtering)
249            .count(bindless_slab_resource_limit)
250            .build(1, ShaderStages::all()),
251        sampler(SamplerBindingType::NonFiltering)
252            .count(bindless_slab_resource_limit)
253            .build(2, ShaderStages::all()),
254        sampler(SamplerBindingType::Comparison)
255            .count(bindless_slab_resource_limit)
256            .build(3, ShaderStages::all()),
257        texture_1d(TextureSampleType::Float { filterable: true })
258            .count(bindless_slab_resource_limit)
259            .build(4, ShaderStages::all()),
260        texture_2d(TextureSampleType::Float { filterable: true })
261            .count(bindless_slab_resource_limit)
262            .build(5, ShaderStages::all()),
263        texture_2d_array(TextureSampleType::Float { filterable: true })
264            .count(bindless_slab_resource_limit)
265            .build(6, ShaderStages::all()),
266        texture_3d(TextureSampleType::Float { filterable: true })
267            .count(bindless_slab_resource_limit)
268            .build(7, ShaderStages::all()),
269        texture_cube(TextureSampleType::Float { filterable: true })
270            .count(bindless_slab_resource_limit)
271            .build(8, ShaderStages::all()),
272        texture_cube_array(TextureSampleType::Float { filterable: true })
273            .count(bindless_slab_resource_limit)
274            .build(9, ShaderStages::all()),
275    ]
276}
277
278impl BindlessSlabResourceLimit {
279    /// Determines the actual bindless slab resource limit on this platform.
280    pub fn resolve(&self) -> u32 {
281        match *self {
282            BindlessSlabResourceLimit::Auto => AUTO_BINDLESS_SLAB_RESOURCE_LIMIT,
283            BindlessSlabResourceLimit::Custom(limit) => limit,
284        }
285    }
286}
287
288impl BindlessResourceType {
289    /// Returns the binding number for the common array of this resource type.
290    ///
291    /// For example, if you pass `BindlessResourceType::Texture2d`, this will
292    /// return 5, in order to match the `@group(2) @binding(5) var
293    /// bindless_textures_2d: binding_array<texture_2d<f32>>` declaration in
294    /// `bindless.wgsl`.
295    ///
296    /// Not all resource types have fixed binding numbers. If you call
297    /// [`Self::binding_number`] on such a resource type, it returns `None`.
298    ///
299    /// Note that this returns a static reference to the binding number, not the
300    /// binding number itself. This is to conform to an idiosyncratic API in
301    /// `wgpu` whereby binding numbers for binding arrays are taken by `&u32`
302    /// *reference*, not by `u32` value.
303    pub fn binding_number(&self) -> Option<&'static BindingNumber> {
304        match BINDING_NUMBERS.binary_search_by_key(self, |(key, _)| *key) {
305            Ok(binding_number) => Some(&BINDING_NUMBERS[binding_number].1),
306            Err(_) => None,
307        }
308    }
309}
310
311impl From<TextureViewDimension> for BindlessResourceType {
312    fn from(texture_view_dimension: TextureViewDimension) -> Self {
313        match texture_view_dimension {
314            TextureViewDimension::D1 => BindlessResourceType::Texture1d,
315            TextureViewDimension::D2 => BindlessResourceType::Texture2d,
316            TextureViewDimension::D2Array => BindlessResourceType::Texture2dArray,
317            TextureViewDimension::Cube => BindlessResourceType::TextureCube,
318            TextureViewDimension::CubeArray => BindlessResourceType::TextureCubeArray,
319            TextureViewDimension::D3 => BindlessResourceType::Texture3d,
320        }
321    }
322}
323
324impl From<SamplerBindingType> for BindlessResourceType {
325    fn from(sampler_binding_type: SamplerBindingType) -> Self {
326        match sampler_binding_type {
327            SamplerBindingType::Filtering => BindlessResourceType::SamplerFiltering,
328            SamplerBindingType::NonFiltering => BindlessResourceType::SamplerNonFiltering,
329            SamplerBindingType::Comparison => BindlessResourceType::SamplerComparison,
330        }
331    }
332}
333
334impl From<u32> for BindlessIndex {
335    fn from(value: u32) -> Self {
336        Self(value)
337    }
338}
339
340impl From<u32> for BindingNumber {
341    fn from(value: u32) -> Self {
342        Self(value)
343    }
344}